diff options
Diffstat (limited to 'src/devices/video/upd3301.cpp')
-rw-r--r-- | src/devices/video/upd3301.cpp | 537 |
1 files changed, 333 insertions, 204 deletions
diff --git a/src/devices/video/upd3301.cpp b/src/devices/video/upd3301.cpp index 9da9f70af7c..1608b3c74a2 100644 --- a/src/devices/video/upd3301.cpp +++ b/src/devices/video/upd3301.cpp @@ -1,5 +1,5 @@ // license:BSD-3-Clause -// copyright-holders:Curt Coder +// copyright-holders:Curt Coder, Angelo Salese /********************************************************************** NEC uPD3301 Programmable CRT Controller emulation @@ -10,12 +10,20 @@ TODO: - - attributes - - N interrupt + - pinpoint how much of pc8001/pc8801 drawing functions should actually be inherited + here; + - N interrupt (special control character) - light pen - reset counters - proper DMA timing (now the whole screen is transferred at the end of the frame, accurate timing requires CCLK timer which kills performance) + - DMA burst mode; + - cleanup: variable namings should be more verbose + (i.e. not be a single letter like m_y, m_z, m_b ...); + - sorcerml (pc8801) has buggy DMA burst mode, causing an underrun (hence a status U interrupt); + - jettermi (pc8801) expects to colorize its underlying 400 b&w mode by masking with the + text color attributes here; + - xak2 (pc8801) throws text garbage on legacy renderer (verify); */ @@ -77,33 +85,35 @@ DEFINE_DEVICE_TYPE(UPD3301, upd3301_device, "upd3301", "NEC uPD3301") // upd3301_device - constructor //------------------------------------------------- -upd3301_device::upd3301_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : - device_t(mconfig, UPD3301, tag, owner, clock), - device_video_interface(mconfig, *this), - m_write_int(*this), - m_write_drq(*this), - m_write_hrtc(*this), - m_write_vrtc(*this), - m_display_cb(*this), - m_width(0), - m_status(0), - m_param_count(0), - m_data_fifo_pos(0), - m_attr_fifo_pos(0), - m_input_fifo(0), - m_me(0), - m_h(80), - m_l(20), - m_r(10), - m_v(6), - m_z(32), - m_attr_blink(0), - m_attr_frame(0), - m_cm(0), - m_cx(0), - m_cy(0), - m_cursor_blink(0), - m_cursor_frame(0) +upd3301_device::upd3301_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, UPD3301, tag, owner, clock) + , device_video_interface(mconfig, *this) + , m_write_int(*this) + , m_write_drq(*this) + , m_write_hrtc(*this) + , m_write_vrtc(*this) + , m_write_rvv(*this) + , m_display_cb(*this) + , m_attr_fetch_cb(*this) + , m_width(0) + , m_status(0) + , m_param_count(0) + , m_data_fifo_pos(0) + , m_attr_fifo_pos(0) + , m_input_fifo(0) + , m_me(0) + , m_h(80) + , m_l(20) + , m_r(10) + , m_v(6) + , m_z(32) + , m_attr_blink(0) + , m_attr_frame(0) + , m_cm(0) + , m_cx(0) + , m_cy(0) + , m_cursor_blink(0) + , m_cursor_frame(0) { } @@ -120,7 +130,9 @@ void upd3301_device::device_start() m_write_int.resolve_safe(); m_write_hrtc.resolve_safe(); m_write_vrtc.resolve_safe(); + m_write_rvv.resolve(); m_display_cb.resolve(); + m_attr_fetch_cb.resolve(); // allocate timers m_hrtc_timer = timer_alloc(TIMER_HRTC); @@ -148,9 +160,7 @@ void upd3301_device::device_start() save_item(NAME(m_r)); save_item(NAME(m_v)); save_item(NAME(m_z)); - save_item(NAME(m_at1)); - save_item(NAME(m_at0)); - save_item(NAME(m_sc)); + save_item(NAME(m_gfx_mode)); save_item(NAME(m_attr)); save_item(NAME(m_attr_blink)); save_item(NAME(m_attr_frame)); @@ -161,6 +171,7 @@ void upd3301_device::device_start() save_item(NAME(m_cursor_frame)); save_item(NAME(m_data_fifo)); save_item(NAME(m_attr_fifo)); + save_item(NAME(m_reverse_display)); } @@ -173,6 +184,10 @@ void upd3301_device::device_reset() set_interrupt(0); set_drq(0); + m_cm = 0; + m_b = 48; + m_reverse_display = false; + recompute_parameters(); } @@ -191,40 +206,68 @@ void upd3301_device::device_clock_changed() // device_timer - handle timer events //------------------------------------------------- +// this snipped was inside screen_update fn +// bad idea: it causes all sort of desync glitches when emulation unthrottles +// TODO: verify if FIFO clear-out happens on vblank-in or -out +inline void upd3301_device::reset_fifo_vrtc() +{ + m_y = 0; + m_data_fifo_pos = 0; + m_attr_fifo_pos = 0; + + m_cursor_frame++; + + if (m_cursor_frame == m_b) + { + m_cursor_frame = 0; + m_cursor_blink = !m_cursor_blink; + } + + m_attr_frame++; + if (m_attr_frame == (m_b << 1)) + { + m_attr_frame = 0; + m_attr_blink = !m_attr_blink; + } +} + void upd3301_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { - case TIMER_HRTC: - LOG("UPD3301 HRTC: %u\n", param); + case TIMER_HRTC: + LOG("UPD3301 HRTC: %u\n", param); - m_write_hrtc(param); - m_hrtc = param; + m_write_hrtc(param); + m_hrtc = param; - update_hrtc_timer(param); - break; + update_hrtc_timer(param); + break; - case TIMER_VRTC: - LOG("UPD3301 VRTC: %u\n", param); + case TIMER_VRTC: + LOG("UPD3301 VRTC: %u\n", param); - m_write_vrtc(param); - m_vrtc = param; + m_write_vrtc(param); + m_vrtc = param; - update_vrtc_timer(param); - if(!(m_status & STATUS_VE)) - break; + update_vrtc_timer(param); + if(!(m_status & STATUS_VE)) + break; - if (param && !m_me) - { - m_status |= STATUS_E; - set_interrupt(1); - } - else if(!param) - set_drq(1); - break; + if(!param) + reset_fifo_vrtc(); + + if (param && !m_me) + { + m_status |= STATUS_E; + set_interrupt(1); + } + else if(!param) + set_drq(1); + break; - case TIMER_DRQ: - break; + case TIMER_DRQ: + break; } } @@ -239,13 +282,16 @@ uint8_t upd3301_device::read(offs_t offset) switch (offset & 0x01) { - case 0: // data - break; + case 0: // data + // TODO: light pen + if (!machine().side_effects_disabled()) + popmessage("light pen reading"); + break; - case 1: // status - data = m_status; - m_status &= ~(STATUS_LP | STATUS_E |STATUS_N | STATUS_U); - break; + case 1: // status + data = m_status; + m_status &= ~(STATUS_LP | STATUS_E |STATUS_N | STATUS_U); + break; } return data; @@ -260,135 +306,185 @@ void upd3301_device::write(offs_t offset, uint8_t data) { switch (offset & 0x01) { - case 0: // data - switch (m_mode) - { - case MODE_RESET: - switch (m_param_count) + case 0: // data + switch (m_mode) { - case 0: - m_dma_mode = BIT(data, 7); - m_h = (data & 0x7f) + 2; - LOG("UPD3301 DMA Mode: %s\n", m_dma_mode ? "character" : "burst"); - LOG("UPD3301 H: %u\n", m_h); + case MODE_RESET: + switch (m_param_count) + { + case 0: + m_dma_mode = BIT(data, 7); + // number of characters per line -2 + // TODO: doesn't seem to like anything beyond 80 + m_h = (data & 0x7f) + 2; + if (m_h > 80) + popmessage("Illegal width set %d", m_h); + LOG("UPD3301 DMA Mode: %s\n", m_dma_mode ? "character" : "burst"); + LOG("UPD3301 H: %u\n", m_h); + break; + + case 1: + // cursor/attribute blink rate + m_b = ((data >> 6) + 1) * 16; + // number of lines displayed -1 + // (or in other words, tilemap y size) + m_l = (data & 0x3f) + 1; + LOG("UPD3301 B: %u\n", m_b); + LOG("UPD3301 L: %u\n", m_l); + break; + + case 2: + // skip line (pseudo-interlace?) + m_s = BIT(data, 7); + if (m_s) + popmessage("skip line enable"); + // cursor mode + // (00) not blinking underline cursor + // (01) blinking underline cursor + // (10) not blinking solid cursor + // (11) blinking solid cursor + // NB: there must be at least 14 lines per char to make underline valid + m_c = (data >> 5) & 0x03; + if (m_c != 3) + popmessage("cursor mode %02x", m_c); + // Number of lines per character -1 + m_r = (data & 0x1f) + 1; + LOG("UPD3301 S: %u\n", m_s); + LOG("UPD3301 C: %u\n", m_c); + LOG("UPD3301 R: %u\n", m_r); + break; + + case 3: + // vblank lines -1 (1 to 8) + m_v = (data >> 5) + 1; + // hblank width -2 (6 to 33) + m_z = (data & 0x1f) + 2; + LOG("UPD3301 V: %u\n", m_v); + LOG("UPD3301 Z: %u\n", m_z); + recompute_parameters(); + break; + + case 4: + // AT|SC + // (00|0) transparent b&w with special control character + // (00|1) no attributes, no special control + // (01|0) transparent color + // (10|0) non-transparent b&w, special control + // (10|1) non-transparent b&w, no special control + // any other setting are invalid + //m_at1 = BIT(data, 7); + //m_at0 = BIT(data, 6); + //m_sc = BIT(data, 5); + m_gfx_mode = (data & 0xe0) >> 5; + if (m_gfx_mode & 0x5) + popmessage("attr mode %02x", m_gfx_mode); + // Max number of attributes per line -1 + // can't be higher than 20 + m_attr = std::min((data & 0x1f) + 1, 20); + LOG("UPD3301 AT1: %u AT0: %u SC: %u\n", BIT(data, 7), BIT(data, 6), BIT(data, 5)); + LOG("UPD3301 ATTR: %u\n", m_attr); + + m_mode = MODE_NONE; + break; + } + + m_param_count++; break; - case 1: - m_b = ((data >> 6) + 1) * 16; - m_l = (data & 0x3f) + 1; - LOG("UPD3301 B: %u\n", m_b); - LOG("UPD3301 L: %u\n", m_l); - break; + case MODE_LOAD_CURSOR_POSITION: + switch (m_param_count) + { + case 0: + m_cx = data & 0x7f; + LOG("UPD3301 CX: %u\n", m_cx); + break; - case 2: - m_s = BIT(data, 7); - m_c = (data >> 4) & 0x03; - m_r = (data & 0x1f) + 1; - LOG("UPD3301 S: %u\n", m_s); - LOG("UPD3301 C: %u\n", m_c); - LOG("UPD3301 R: %u\n", m_r); - break; + case 1: + m_cy = data & 0x3f; + LOG("UPD3301 CY: %u\n", m_cy); - case 3: - m_v = (data >> 5) + 1; - m_z = (data & 0x1f) + 2; - LOG("UPD3301 V: %u\n", m_v); - LOG("UPD3301 Z: %u\n", m_z); - recompute_parameters(); - break; + m_mode = MODE_NONE; + break; + } - case 4: - m_at1 = BIT(data, 7); - m_at0 = BIT(data, 6); - m_sc = BIT(data, 5); - m_attr = (data & 0x1f) + 1; - LOG("UPD3301 AT1: %u\n", m_at1); - LOG("UPD3301 AT0: %u\n", m_at0); - LOG("UPD3301 SC: %u\n", m_sc); - LOG("UPD3301 ATTR: %u\n", m_attr); - - m_mode = MODE_NONE; + m_param_count++; break; - } - m_param_count++; + default: + LOG("UPD3301 Invalid Parameter Byte %02x!\n", data); + } break; - case MODE_LOAD_CURSOR_POSITION: - switch (m_param_count) + case 1: // command + m_mode = MODE_NONE; + m_param_count = 0; + + switch (data & 0xe0) { - case 0: - m_cx = data & 0x7f; - LOG("UPD3301 CX: %u\n", m_cx); + case COMMAND_RESET: + LOG("UPD3301 Reset\n"); + m_mode = MODE_RESET; + // TODO: this also disables external display such as Graphic VRAM in PC-8801 + set_display(0); + set_interrupt(0); break; - case 1: - m_cy = data & 0x3f; - LOG("UPD3301 CY: %u\n", m_cy); - - m_mode = MODE_NONE; + case COMMAND_START_DISPLAY: + { + LOG("UPD3301 Start Display\n"); + bool new_rvv = bool(BIT(data, 0)); + // misscmd (pc8001) enables this + if (m_reverse_display != new_rvv) + { + m_reverse_display = new_rvv; + if (!m_write_rvv.isnull()) + m_write_rvv(m_reverse_display); + else if (m_reverse_display == true) + logerror("%s: reverse display enabled (warning)\n", machine().describe_context()); + } + set_display(1); + reset_counters(); break; } - m_param_count++; - break; - - default: - LOG("UPD3301 Invalid Parameter Byte %02x!\n", data); - } - break; - - case 1: // command - m_mode = MODE_NONE; - m_param_count = 0; - - switch (data & 0xe0) - { - case COMMAND_RESET: - LOG("UPD3301 Reset\n"); - m_mode = MODE_RESET; - set_display(0); - set_interrupt(0); - break; - - case COMMAND_START_DISPLAY: - LOG("UPD3301 Start Display\n"); - set_display(1); - reset_counters(); - break; - - case COMMAND_SET_INTERRUPT_MASK: - LOG("UPD3301 Set Interrupt Mask\n"); - m_me = BIT(data, 0); - m_mn = BIT(data, 1); - LOG("UPD3301 ME: %u\n", m_me); - LOG("UPD3301 MN: %u\n", m_mn); - break; + case COMMAND_SET_INTERRUPT_MASK: + LOG("UPD3301 Set Interrupt Mask\n"); + // vblank irq mask + m_me = BIT(data, 0); + // special control character irq mask + m_mn = BIT(data, 1); + // TODO: writing a negated bit 0 makes status bit 7 to be held high? + LOG("UPD3301 ME: %u\n", m_me); + LOG("UPD3301 MN: %u\n", m_mn); + break; - case COMMAND_READ_LIGHT_PEN: - LOG("UPD3301 Read Light Pen\n"); - m_mode = MODE_READ_LIGHT_PEN; - break; + case COMMAND_READ_LIGHT_PEN: + LOG("UPD3301 Read Light Pen\n"); + // TODO: similar to cursor parameters except on read + // (plus an HR to bit 7 param [0]) + m_mode = MODE_READ_LIGHT_PEN; + break; - case COMMAND_LOAD_CURSOR_POSITION: - LOG("UPD3301 Load Cursor Position\n"); - m_mode = MODE_LOAD_CURSOR_POSITION; - m_cm = BIT(data, 0); - LOG("UPD3301 CM: %u\n", m_cm); - break; + case COMMAND_LOAD_CURSOR_POSITION: + LOG("UPD3301 Load Cursor Position\n"); + m_mode = MODE_LOAD_CURSOR_POSITION; + // (1) show cursor (0) disable cursor + m_cm = BIT(data, 0); + LOG("UPD3301 CM: %u\n", m_cm); + break; - case COMMAND_RESET_INTERRUPT: - LOG("UPD3301 Reset Interrupt\n"); - set_interrupt(0); - break; + case COMMAND_RESET_INTERRUPT: + LOG("UPD3301 Reset Interrupt\n"); + set_interrupt(0); + break; - case COMMAND_RESET_COUNTERS: - LOG("UPD3301 Reset Counters\n"); - m_mode = MODE_RESET_COUNTERS; - reset_counters(); + case COMMAND_RESET_COUNTERS: + LOG("UPD3301 Reset Counters\n"); + m_mode = MODE_RESET_COUNTERS; + reset_counters(); + break; + } break; - } - break; } } @@ -406,17 +502,23 @@ void upd3301_device::dack_w(uint8_t data) if (m_data_fifo_pos < m_h) { - m_data_fifo[m_data_fifo_pos][m_input_fifo] = data; + m_data_fifo[m_input_fifo][m_data_fifo_pos] = data; m_data_fifo_pos++; } else { - m_attr_fifo[m_attr_fifo_pos][m_input_fifo] = data; + m_attr_fifo[m_input_fifo][m_attr_fifo_pos] = data; m_attr_fifo_pos++; } if ((m_data_fifo_pos == m_h) && (m_attr_fifo_pos == (m_attr << 1))) { + const u8 attr_max_size = 80; + // first attribute start is always overwritten with a 0 + m_attr_fifo[m_input_fifo][0] = 0; + // last parameter always extends up to the end of the row + // (7narabe (pc8001) fills last row value with white when exausting available slots) + m_attr_fifo[m_input_fifo][40] = attr_max_size; m_input_fifo = !m_input_fifo; m_data_fifo_pos = 0; @@ -466,23 +568,70 @@ int upd3301_device::vrtc_r() // draw_scanline - //------------------------------------------------- +UPD3301_FETCH_ATTRIBUTE( upd3301_device::default_attr_fetch ) +{ + const u8 attr_max_size = 80; + std::array<u16, attr_max_size> attr_extend_info; + + // TODO: uPD3301 may actually fetch in LIFO order + for (int ex = 0; ex < attr_fifo_size; ex+=2) + { + u8 attr_start = std::min(attr_row[ex], attr_max_size); + u8 attr_value = attr_row[ex+1]; + u8 attr_end = std::min(attr_row[ex+2], attr_max_size); + // if the target is == 0 then just consider max size instead + // (starfire (pc8001) wants this otherwise will black screen on gameplay) + if (attr_end == 0) + attr_end = attr_max_size; + + //printf("%04x %d %d [%02x]\n", ex, attr_start, attr_end, attr_value); + + for (int i = attr_start; i < attr_end; i++) + attr_extend_info[i] = attr_value; + + if (attr_end == attr_max_size) + break; + } + + return attr_extend_info; +} + void upd3301_device::draw_scanline() { + // Olympia Boss never bothers in writing a correct attribute table for rows on resident OS, + // it just extends the full attribute RAM with a start: 0 end: 0xff value: 0. + // According to doc notes anything beyond width 80 is puked by the CRTC, therefore we clamp. + const u8 attr_max_size = 80; + const std::array<u8, 41> attr_fifo = m_attr_fifo[!m_input_fifo]; + + // expose attribute handling to our client + // PC-8801 schematics definitely shows extra TTL connections for handling its "8 to 16-bit" attribute conversion. + // It also practically needs to read the attribute mapping for various extra side-effects such as colorized 400 line 1bpp + // cfr. "その他 / other" section at http://mydocuments.g2.xrea.com/html/p8/vraminfo.html + std::array<u16, attr_max_size> extend_attr = m_attr_fetch_cb(attr_fifo, m_gfx_mode, m_y, m_attr << 1, m_h); + for (int lc = 0; lc < m_r; lc++) { + bool is_lowestline = lc == m_r - 1; for (int sx = 0; sx < m_h; sx++) { int y = m_y + lc; - uint8_t cc = m_data_fifo[sx][!m_input_fifo]; - int hlgt = 0; // TODO - int rvv = 0; // TODO - int vsp = 0; // TODO - int sl0 = 0; // TODO - int sl12 = 0; // TODO + uint8_t cc = m_data_fifo[!m_input_fifo][sx]; int csr = m_cm && m_cursor_blink && ((y / m_r) == m_cy) && (sx == m_cx); - int gpa = 0; // TODO - m_display_cb(m_bitmap, y, sx, cc, lc, hlgt, rvv, vsp, sl0, sl12, csr, gpa); + // datasheet mentions these but I find zero unambiguous information for PC-8001/PC-8801, i.e.: + // - "highlight" should be attribute blinking? + // - is "gpa" actually NEC-ese for attr bus? + // - is sl0 / sl12 NEC names for upper/lower line? +// int hlgt = 0; +// int rvv = 0; +// int vsp = 0; +// int sl0 = 0; +// int sl12 = 0; +// int gpa = 0; + +// m_display_cb(m_bitmap, y, sx, cc, lc, hlgt, rvv, vsp, sl0, sl12, csr, gpa); + m_display_cb(m_bitmap, y, sx, cc, lc, csr, m_attr_blink, extend_attr[sx], m_gfx_mode, is_lowestline); } } @@ -496,33 +645,13 @@ void upd3301_device::draw_scanline() uint32_t upd3301_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { - if (m_status & STATUS_VE) - { - m_y = 0; - m_data_fifo_pos = 0; - m_attr_fifo_pos = 0; + bitmap.fill(rgb_t(0x00,0x00,0x00), cliprect); - m_cursor_frame++; + if (!(m_status & STATUS_VE)) + return 0; - if (m_cursor_frame == m_b) - { - m_cursor_frame = 0; - m_cursor_blink = !m_cursor_blink; - } - - m_attr_frame++; + copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); - if (m_attr_frame == (m_b << 1)) - { - m_attr_frame = 0; - m_attr_blink = !m_attr_blink; - } - copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); - } - else - { - bitmap.fill(rgb_t(0x00,0x00,0x00), cliprect); - } return 0; } |