From c920ea859313e9ac4fa3e95d69f9dfaed3269e5c Mon Sep 17 00:00:00 2001 From: AJR Date: Mon, 1 Oct 2018 03:26:33 -0400 Subject: [PATCH] dp8350: More complete line-by-line emulation of timing outputs (nw) --- src/devices/video/dp8350.cpp | 311 ++++++++++++++++++++++++----------- src/devices/video/dp8350.h | 35 +++- 2 files changed, 246 insertions(+), 100 deletions(-) diff --git a/src/devices/video/dp8350.cpp b/src/devices/video/dp8350.cpp index 0eddc51e601..1cd725ca4fb 100644 --- a/src/devices/video/dp8350.cpp +++ b/src/devices/video/dp8350.cpp @@ -5,11 +5,20 @@ National Semiconductor DP8350 Series CRT Controllers These dedicated CRTC devices are “programmable” by specifying a - long list of mask parameters. The number of parameters adjustable - through software is relatively small, there being no dedicated - microprocessor data bus. In practice, however, the CPU's D0 and - D1 are usually tied to the Register Select lines, due to shared - use of the address bus for DMA purposes. + long list of mask parameters. Since the dot clock is generated + internally, these parameters determine the character field size + (16 x 16 maximum) as well as the number of characters displayed + on screen and the timing, width and polarity of the sync pulses + (though two different vertical timings are incorporated so the + refresh rate can be switched between 60 Hz and 50 Hz). + + The number of parameters adjustable through software control is + relatively small, there being no dedicated microprocessor data bus. + In practice, however, the CPU's D0 and D1 are usually tied to the + Register Select lines, due to shared use of the address bus for + DMA purposes. A per-row interrupt can be derived in various ways + from the timing outputs; this can be used to reload the Row Start + Register to point to display memory at nonconsecutive addresses. National released the DP8350 and a few other standard versions. DP8367 appears to have originally been a custom mask variant @@ -17,7 +26,9 @@ later datasheets; its parameters have been derived from the information presented in Manual Part No. 13220-91087. (The 8367 designation also comes from there, since the actual IC is merely - marked with its HP part number, 1820-2373.) + marked with its HP part number, 1820-2373.) DP8369 is another + variant ordered by General Terminal Corp. for their SW10 terminal; + option “K” is its National designation. Variant Dot rate Monitor type ------- -------- ------------ @@ -25,6 +36,7 @@ DP8352 7.02 MHz RS-170 compatible DP8353 17.6256 MHz Motorola M3003 DP8367 25.7715 MHz HP 13220 + DP8369 12.2472 MHz GTC SW10 **********************************************************************/ @@ -76,12 +88,27 @@ dp835x_device::dp835x_device(const machine_config &mconfig, device_type type, co , m_hsync_active(hsync_active) , m_vsync_active(vsync_active) , m_vblank_active(vblank_active) + , m_dots_per_line(char_width * chars_per_line) + , m_dots_per_row(char_width * chars_per_row) + , m_video_scan_lines(char_height * rows_per_frame) + , m_lrc_callback(*this) + , m_clc_callback(*this) + , m_lc_callback(*this) + , m_lbre_callback(*this) , m_hsync_callback(*this) , m_vsync_callback(*this) , m_vblank_callback(*this) , m_60hz_refresh(true) + , m_cgpi(false) + , m_topr(0) + , m_rsr(0) + , m_cr(0) + , m_row_start(0) + , m_lc(0) { - // some parameters are not used yet + // some parameters are not used (at least not directly) + (void)m_rows_per_frame; + (void)m_chars_per_line; (void)m_cursor_on_all_lines; (void)m_lbc_0_width; (void)m_hsync_serration; @@ -97,7 +124,7 @@ dp8350_device::dp8350_device(const machine_config &mconfig, const char *tag, dev 7, 10, 80, 24, 4, 10, 20, 30, 10, 72, - 100, 0, 4, 1, // HSYNC width given in datasheet as an impossible 43 character times + 100, 0, 43, 1, // yes, the horizontal sync pulse is more than twice as long as the blanking period true, 4, 0, true, false, true) { @@ -129,14 +156,11 @@ void dp835x_device::device_config_complete() { if (has_screen() && screen().refresh_attoseconds() == 0) { - int dots_per_line = m_char_width * m_chars_per_line; - int dots_per_row = m_char_width * m_chars_per_row; - int lines_per_frame = m_char_height * m_rows_per_frame + m_vblank_interval[m_60hz_refresh ? 1 : 0]; - + int lines_per_frame = m_video_scan_lines + m_vblank_interval[m_60hz_refresh ? 1 : 0]; if (m_half_shift) - screen().set_raw(clock() * 2, dots_per_line * 2, 0, dots_per_row * 2, lines_per_frame, 0, m_char_height * m_rows_per_frame); + screen().set_raw(clock() * 2, m_dots_per_line * 2, 0, m_dots_per_row * 2, lines_per_frame, 0, m_video_scan_lines); else - screen().set_raw(clock(), dots_per_line, 0, dots_per_row, lines_per_frame, 0, m_char_height * m_rows_per_frame); + screen().set_raw(clock(), m_dots_per_line, 0, m_dots_per_row, lines_per_frame, 0, m_video_scan_lines); } } @@ -149,6 +173,10 @@ void dp835x_device::device_config_complete() void dp835x_device::device_resolve_objects() { + m_lrc_callback.resolve_safe(); + m_clc_callback.resolve_safe(); + m_lc_callback.resolve_safe(); + m_lbre_callback.resolve_safe(); m_hsync_callback.resolve_safe(); m_vsync_callback.resolve_safe(); m_vblank_callback.resolve_safe(); @@ -162,15 +190,19 @@ void dp835x_device::device_resolve_objects() void dp835x_device::device_start() { // create timers + m_hblank_start_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::hblank_start), this)); + m_hblank_near_end_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::hblank_near_end), this)); m_hsync_on_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::hsync_update), this)); m_hsync_off_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::hsync_update), this)); - m_vsync_on_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::vsync_update), this)); - m_vsync_off_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::vsync_update), this)); - m_vblank_on_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::vblank_update), this)); - m_vblank_off_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(dp835x_device::vblank_update), this)); // save state save_item(NAME(m_60hz_refresh)); + save_item(NAME(m_cgpi)); + save_item(NAME(m_lc)); + save_item(NAME(m_topr)); + save_item(NAME(m_rsr)); + save_item(NAME(m_cr)); + save_item(NAME(m_row_start)); } @@ -180,6 +212,10 @@ void dp835x_device::device_start() void dp835x_device::device_reset() { + m_topr = 0; + m_rsr = 0; + m_cr = 0; + m_row_start = 0; } @@ -200,70 +236,68 @@ void dp835x_device::device_clock_changed() void dp835x_device::reconfigure_screen() { - int dots_per_line = m_char_width * m_chars_per_line; - int dots_per_row = m_char_width * m_chars_per_row; - int lines_per_frame = m_char_height * m_rows_per_frame + m_vblank_interval[m_60hz_refresh ? 1 : 0]; - attotime refresh = clocks_to_attotime(lines_per_frame * dots_per_line); + int lines_per_frame = m_video_scan_lines + m_vblank_interval[m_60hz_refresh ? 1 : 0]; + attotime scan_period = clocks_to_attotime(m_dots_per_line); + attotime refresh = clocks_to_attotime(lines_per_frame * m_dots_per_line); if (m_half_shift) { - rectangle visarea(0, 2 * dots_per_row - 1, 0, m_char_height * m_rows_per_frame - 1); - screen().configure(2 * dots_per_line, lines_per_frame, visarea, refresh.as_attoseconds()); + rectangle visarea(0, 2 * m_dots_per_row - 1, 0, m_video_scan_lines - 1); + screen().configure(2 * m_dots_per_line, lines_per_frame, visarea, refresh.as_attoseconds()); } else { - rectangle visarea(0, dots_per_row - 1, 0, m_char_height * m_rows_per_frame - 1); - screen().configure(dots_per_line, lines_per_frame, visarea, refresh.as_attoseconds()); + rectangle visarea(0, m_dots_per_row - 1, 0, m_video_scan_lines - 1); + screen().configure(m_dots_per_line, lines_per_frame, visarea, refresh.as_attoseconds()); } logerror("Frame rate refresh: %.2f Hz (f%d); horizontal rate scan: %.4f kHz; character rate: %.4f MHz; dot rate: %.5f MHz\n", ATTOSECONDS_TO_HZ(refresh.as_attoseconds()), m_60hz_refresh ? 1 : 0, - clock() / (dots_per_line * 1000.0), + clock() / (m_dots_per_line * 1000.0), clock() / (m_char_width * 1000000.0), clock() / 1000000.0); - // get current screen position - int hpos = screen().hpos(); - int vpos = screen().vpos(); + // get current screen position (note that this method is more accurate than calling hpos and vpos separately) + u32 dpos = attotime_to_clocks(refresh - screen().time_until_pos(lines_per_frame - 1, m_dots_per_row * (m_half_shift ? 2 : 1))); + int hpos = (dpos + m_dots_per_row) % m_dots_per_line; + int vpos = dpos / m_dots_per_line; - // set horizontal sync timers - int hsync_begin = (dots_per_row + m_char_width * m_hsync_delay) * (m_half_shift ? 2 : 1); - int hsync_end = hsync_begin + m_char_width * m_hsync_width * (m_half_shift ? 2 : 1); - if (hpos > hsync_begin) - hsync_begin += dots_per_line * (m_half_shift ? 2 : 1); - m_hsync_on_timer->adjust(clocks_to_attotime(hsync_begin - hpos) / (m_half_shift ? 2 : 1), m_hsync_active, clocks_to_attotime(dots_per_line)); - if (hpos > hsync_end) - hsync_end += dots_per_line * (m_half_shift ? 2 : 1); - m_hsync_off_timer->adjust(clocks_to_attotime(hsync_end - hpos) / (m_half_shift ? 2 : 1), !m_hsync_active, clocks_to_attotime(dots_per_line)); + // set line rate clock timers + int hblank_begin = m_dots_per_row; + int hblank_near_end = m_dots_per_line - m_char_width * 5; + if (hpos >= hblank_begin) + hblank_begin += m_dots_per_line; + m_hblank_start_timer->adjust(clocks_to_attotime(hblank_begin - hpos), 0, scan_period); + if (hpos >= hblank_near_end) + hblank_near_end += m_dots_per_line; + m_hblank_near_end_timer->adjust(clocks_to_attotime(hblank_near_end - hpos), 0, scan_period); + + // set horizontal sync timers (note that HSYNC may precede horizontal blanking or even outlast it, as on the DP8350) + int hsync_begin = m_dots_per_row + m_char_width * m_hsync_delay; + int hsync_end = hsync_begin + m_char_width * m_hsync_width; + if (hpos >= hsync_begin) + hsync_begin += m_dots_per_line; + m_hsync_on_timer->adjust(clocks_to_attotime(hsync_begin - hpos), m_hsync_active, scan_period); + if (hpos >= hsync_end) + hsync_end += m_dots_per_line; + else if (hpos < hsync_end - m_dots_per_line) + hsync_end -= m_dots_per_line; + m_hsync_off_timer->adjust(clocks_to_attotime(hsync_end - hpos), !m_hsync_active, scan_period); + logerror("hblank_begin: %d; hsync_begin: %d; hsync_end: %d; hpos: %d\n", hblank_begin, hsync_begin, hsync_end, hpos); // calculate vertical sync and blanking parameters - int hblank_begin = dots_per_row * (m_half_shift ? 2 : 1); - int vblank_begin = m_char_height * m_rows_per_frame - 1; - int vsync_begin = vblank_begin + m_vsync_delay[m_60hz_refresh ? 1 : 0]; + int vsync_begin = m_video_scan_lines + m_vsync_delay[m_60hz_refresh ? 1 : 0]; int vsync_end = vsync_begin + m_vsync_width[m_60hz_refresh ? 1 : 0]; - int vblank_end = lines_per_frame - m_vblank_stop - 1; - logerror("vblank_begin: %d; vsync_begin: %d; vsync_end: %d; vblank_end: %d; vpos: %d\n", vblank_begin, vsync_begin, vsync_end, vblank_end, vpos); - if (hpos > hblank_begin) - { - hblank_begin += dots_per_line * (m_half_shift ? 2 : 1); - vpos++; - } - attotime until_hblank = clocks_to_attotime(hblank_begin - hpos) / (m_half_shift ? 2 : 1); + int vblank_end = lines_per_frame - m_vblank_stop; + logerror("vblank_begin: %d; vsync_begin: %d; vsync_end: %d; vblank_end: %d; vpos: %d\n", m_video_scan_lines, vsync_begin, vsync_end, vblank_end, vpos); - // set vertical sync and blanking timers - if (vpos > vsync_begin) - vsync_begin += lines_per_frame; - m_vsync_on_timer->adjust(clocks_to_attotime((vsync_begin - vpos) * dots_per_line) + until_hblank, m_vsync_active, refresh); - if (vpos > vsync_end) - vsync_end += lines_per_frame; - m_vsync_off_timer->adjust(clocks_to_attotime((vsync_end - vpos) * dots_per_line) + until_hblank, !m_vsync_active, refresh); - if (vpos > vblank_begin) - vblank_begin += lines_per_frame; - m_vblank_on_timer->adjust(clocks_to_attotime((vblank_begin - vpos) * dots_per_line) + until_hblank, m_vblank_active, refresh); - if (vpos > vblank_end) - vblank_end += lines_per_frame; - m_vblank_off_timer->adjust(clocks_to_attotime((vblank_end - vpos) * dots_per_line) + until_hblank, !m_vblank_active, refresh); + // set counters + m_line = vpos; + if (m_line >= lines_per_frame - m_char_height) + m_lc = m_line - (lines_per_frame - m_char_height); + else + m_lc = m_line % m_char_height; } @@ -283,6 +317,21 @@ WRITE_LINE_MEMBER(dp835x_device::refresh_control) } +//------------------------------------------------- +// character_generator_program - set or configure +// the character generator program input (CGPI): +// 0 = video begins on scan line 0; new addresses +// loaded on last scan line of previous character +// 1 = video begins on scan line 1; new addresses +// loaded on scan line 0 +//------------------------------------------------- + +WRITE_LINE_MEMBER(dp835x_device::character_generator_program) +{ + m_cgpi = bool(state); +} + + //------------------------------------------------- // register_load - write to one of three CRTC // address registers @@ -302,21 +351,45 @@ void dp835x_device::register_load(u8 rs, u16 addr) case 1: // A = 0, B = 1: Top-of-Page logerror("Top-of-Page Register = %03X\n", addr); + m_topr = addr; break; case 2: // A = 1, B = 0: Row Start (also Top-of-Page during vertical blanking) - logerror("Row Start Register = %03X\n", addr); + if (m_line >= m_video_scan_lines) + { + logerror("Row Start Register = %03X (redirected to Top-of-Page)\n", addr); + m_topr = addr; + } + else + { + logerror("Row Start Register = %03X\n", addr); + m_rsr = addr; + } break; case 3: // A = 1, B = 1: Cursor logerror("Cursor Register = %03X\n", addr); + m_cr = addr; break; } } +//------------------------------------------------- +// lrc_r - report line rate clock state +//------------------------------------------------- + +READ_LINE_MEMBER(dp835x_device::lrc_r) +{ + if (m_hblank_start_timer->remaining() > m_hblank_near_end_timer->remaining()) + return 0; + else + return 1; +} + + //------------------------------------------------- // hsync_r - report horizontal sync state //------------------------------------------------- @@ -331,15 +404,14 @@ READ_LINE_MEMBER(dp835x_device::hsync_r) //------------------------------------------------- -// vblank_r - report vertical sync state +// vsync_r - report vertical sync state //------------------------------------------------- READ_LINE_MEMBER(dp835x_device::vsync_r) { - if (m_vsync_on_timer->remaining() > m_vsync_off_timer->remaining()) - return m_vsync_active; - else - return !m_vsync_active; + int vsync_begin = m_video_scan_lines + m_vsync_delay[m_60hz_refresh ? 1 : 0]; + int vsync_end = vsync_begin + m_vsync_width[m_60hz_refresh ? 1 : 0]; + return (m_line >= vsync_begin && m_line < vsync_end) ? m_vsync_active : !m_vsync_active; } @@ -349,10 +421,83 @@ READ_LINE_MEMBER(dp835x_device::vsync_r) READ_LINE_MEMBER(dp835x_device::vblank_r) { - if (m_vblank_on_timer->remaining() > m_vblank_off_timer->remaining()) - return m_vblank_active; + int vblank_end = m_video_scan_lines + m_vblank_interval[m_60hz_refresh ? 1 : 0] - m_vblank_stop; + return (m_line >= m_video_scan_lines && m_line < vblank_end) ? m_vblank_active : !m_vblank_active; +} + + +//------------------------------------------------- +// hblank_start - update timing outputs at the +// start of horizontal blanking +//------------------------------------------------- + +TIMER_CALLBACK_MEMBER(dp835x_device::hblank_start) +{ + int lines_per_frame = m_video_scan_lines + m_vblank_interval[m_60hz_refresh ? 1 : 0]; + int vsync_begin = m_video_scan_lines + m_vsync_delay[m_60hz_refresh ? 1 : 0]; + int vsync_end = vsync_begin + m_vsync_width[m_60hz_refresh ? 1 : 0]; + int vblank_end = lines_per_frame - m_vblank_stop; + + // falling edge of line rate clock + m_lrc_callback(0); + + // increment line counter or reset it + if (m_lc < m_char_height - 1 && m_line != lines_per_frame - m_char_height) + m_lc++; else - return !m_vblank_active; + { + m_lc = 0; + m_clc_callback(0); + } + m_lc_callback(m_lc); + + // increment internal 9-bit scan counter + m_line++; + //assert(m_line == screen().vpos() + 1); + + // update line buffer recirculate enable output based on address mode + bool lbre = m_lc != (m_cgpi ? 0 : m_char_height - 1); + m_lbre_callback(lbre ? 1 : 0); + + if (m_line >= lines_per_frame - m_char_height && m_line < lines_per_frame) + { + m_row_start = m_rsr = m_topr; + } + else if (!lbre || m_line >= m_video_scan_lines) + { + // calculate starting address of next row (address counter runs continuously during VBLANK) + m_row_start = m_rsr; + m_rsr = (m_row_start + m_chars_per_row) & 0xfff; + } + + // update vertical blanking output + if (m_line == m_video_scan_lines) + m_vblank_callback(m_vblank_active); + else if (m_line == vblank_end) + m_vblank_callback(!m_vblank_active); + + // update vertical sync output + if (m_line == vsync_begin) + m_vsync_callback(m_vsync_active); + else if (m_line == vsync_end) + m_vsync_callback(!m_vsync_active); + + if (m_line == lines_per_frame) + m_line = 0; +} + + +//------------------------------------------------- +// hblank_near_end - update line rate outputs +// five chars before horizontal blanking ends +//------------------------------------------------- + +TIMER_CALLBACK_MEMBER(dp835x_device::hblank_near_end) +{ + // rising edge of line rate clock + m_lrc_callback(1); + if (m_lc == 0) + m_clc_callback(1); } @@ -365,25 +510,3 @@ TIMER_CALLBACK_MEMBER(dp835x_device::hsync_update) { m_hsync_callback(param); } - - -//------------------------------------------------- -// vsync_update - update state of vertical -// sync output -//------------------------------------------------- - -TIMER_CALLBACK_MEMBER(dp835x_device::vsync_update) -{ - m_vsync_callback(param); -} - - -//------------------------------------------------- -// vblank_update - update state of vertical -// blanking output -//------------------------------------------------- - -TIMER_CALLBACK_MEMBER(dp835x_device::vblank_update) -{ - m_vblank_callback(param); -} diff --git a/src/devices/video/dp8350.h b/src/devices/video/dp8350.h index 4ee885540a7..9f820a35c4b 100644 --- a/src/devices/video/dp8350.h +++ b/src/devices/video/dp8350.h @@ -44,6 +44,10 @@ class dp835x_device : public device_t, public device_video_interface { public: // device configuration + auto lrc_callback() { return m_lrc_callback.bind(); } + auto clc_callback() { return m_clc_callback.bind(); } + auto lc_callback() { return m_lc_callback.bind(); } + auto lbre_callback() { return m_lbre_callback.bind(); } auto hsync_callback() { return m_hsync_callback.bind(); } auto vsync_callback() { return m_vsync_callback.bind(); } auto vblank_callback() { return m_vblank_callback.bind(); } @@ -51,9 +55,12 @@ public: // write handlers DECLARE_WRITE_LINE_MEMBER(refresh_control); + DECLARE_WRITE_LINE_MEMBER(character_generator_program); void register_load(u8 rs, u16 addr); // read handlers + DECLARE_READ_LINE_MEMBER(lrc_r); + u8 lc_r() { return m_lc; } DECLARE_READ_LINE_MEMBER(hsync_r); DECLARE_READ_LINE_MEMBER(vsync_r); DECLARE_READ_LINE_MEMBER(vblank_r); @@ -80,9 +87,9 @@ private: void reconfigure_screen(); // timer callbacks + TIMER_CALLBACK_MEMBER(hblank_start); + TIMER_CALLBACK_MEMBER(hblank_near_end); TIMER_CALLBACK_MEMBER(hsync_update); - TIMER_CALLBACK_MEMBER(vsync_update); - TIMER_CALLBACK_MEMBER(vblank_update); // mask parameters const int m_char_width; // character field cell size (width in dots) @@ -103,24 +110,40 @@ private: const bool m_vsync_active; // active level of vertical sync pulse const bool m_vblank_active; // active level of vertical blanking pulse + // derived parameters + const int m_dots_per_line; // number of dots per scan line + const int m_dots_per_row; // number of dots displayed in each scan line + const int m_video_scan_lines; // number of active scan lines between VBLANK periods + // misc. configuration bool m_half_shift; // adjust screen parameters to allow half-dot shifting // device callbacks + devcb_write_line m_lrc_callback; // line rate clock output (active high) + devcb_write_line m_clc_callback; // clear line counter output (active low during blanking) + devcb_write8 m_lc_callback; // line counter output + devcb_write_line m_lbre_callback; // line buffer recirculate enable output (active high) devcb_write_line m_hsync_callback; // horizontal sync output (polarity may vary by type) devcb_write_line m_vsync_callback; // vertical sync output (polarity may vary by type) devcb_write_line m_vblank_callback; // vertical blanking output (polarity may vary by type) // internal registers and control parameters bool m_60hz_refresh; // refresh rate selector (true = f1, false = f0) + bool m_cgpi; // character generator program/address mode input + u16 m_topr; // top-of-page register + u16 m_rsr; // row start register (to be loaded during next row) + u16 m_cr; // cursor register + u16 m_row_start; // start of current row + u16 m_line; // current scan line (starting at HBLANK) + + // output state + u8 m_lc; // 4-bit line counter // timers + emu_timer *m_hblank_start_timer; + emu_timer *m_hblank_near_end_timer; emu_timer *m_hsync_on_timer; emu_timer *m_hsync_off_timer; - emu_timer *m_vsync_on_timer; - emu_timer *m_vsync_off_timer; - emu_timer *m_vblank_on_timer; - emu_timer *m_vblank_off_timer; }; // ======================> dp8350_device