// license:BSD-3-Clause // copyright-holders:Curt Coder /********************************************************************** NEC uPD3301 Programmable CRT Controller emulation **********************************************************************/ /* TODO: - attributes - N interrupt - 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) */ #include "emu.h" #include "upd3301.h" #include "screen.h" //#define VERBOSE 1 #include "logmacro.h" //************************************************************************** // MACROS / CONSTANTS //************************************************************************** #define COMMAND_MASK 0xe0 #define COMMAND_RESET 0x00 #define COMMAND_START_DISPLAY 0x20 #define COMMAND_SET_INTERRUPT_MASK 0x40 #define COMMAND_READ_LIGHT_PEN 0x60 // not supported #define COMMAND_LOAD_CURSOR_POSITION 0x80 #define COMMAND_RESET_INTERRUPT 0xa0 #define COMMAND_RESET_COUNTERS 0xc0 // not supported #define STATUS_VE 0x10 #define STATUS_U 0x08 // not supported #define STATUS_N 0x04 // not supported #define STATUS_E 0x02 #define STATUS_LP 0x01 // not supported enum { MODE_NONE, MODE_RESET, MODE_READ_LIGHT_PEN, MODE_LOAD_CURSOR_POSITION, MODE_RESET_COUNTERS }; //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** DEFINE_DEVICE_TYPE(UPD3301, upd3301_device, "upd3301", "NEC uPD3301") //************************************************************************** // LIVE DEVICE //************************************************************************** //------------------------------------------------- // 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) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void upd3301_device::device_start() { screen().register_screen_bitmap(m_bitmap); // resolve callbacks m_write_drq.resolve_safe(); m_write_int.resolve_safe(); m_write_hrtc.resolve_safe(); m_write_vrtc.resolve_safe(); m_display_cb.resolve(); // allocate timers m_hrtc_timer = timer_alloc(TIMER_HRTC); m_vrtc_timer = timer_alloc(TIMER_VRTC); m_drq_timer = timer_alloc(TIMER_DRQ); // state saving save_item(NAME(m_y)); save_item(NAME(m_hrtc)); save_item(NAME(m_vrtc)); save_item(NAME(m_mode)); save_item(NAME(m_status)); save_item(NAME(m_param_count)); save_item(NAME(m_data_fifo_pos)); save_item(NAME(m_attr_fifo_pos)); save_item(NAME(m_input_fifo)); save_item(NAME(m_mn)); save_item(NAME(m_me)); save_item(NAME(m_dma_mode)); save_item(NAME(m_h)); save_item(NAME(m_b)); save_item(NAME(m_l)); save_item(NAME(m_s)); save_item(NAME(m_c)); 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_attr)); save_item(NAME(m_attr_blink)); save_item(NAME(m_attr_frame)); save_item(NAME(m_cm)); save_item(NAME(m_cx)); save_item(NAME(m_cy)); save_item(NAME(m_cursor_blink)); save_item(NAME(m_cursor_frame)); save_item(NAME(m_data_fifo)); save_item(NAME(m_attr_fifo)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void upd3301_device::device_reset() { set_interrupt(0); set_drq(0); recompute_parameters(); } //------------------------------------------------- // device_clock_changed - handle clock change //------------------------------------------------- void upd3301_device::device_clock_changed() { recompute_parameters(); } //------------------------------------------------- // device_timer - handle timer events //------------------------------------------------- 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); m_write_hrtc(param); m_hrtc = param; update_hrtc_timer(param); break; case TIMER_VRTC: LOG("UPD3301 VRTC: %u\n", param); m_write_vrtc(param); m_vrtc = param; 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; case TIMER_DRQ: break; } } //------------------------------------------------- // read - //------------------------------------------------- READ8_MEMBER( upd3301_device::read ) { uint8_t data = 0; switch (offset & 0x01) { case 0: // data break; case 1: // status data = m_status; m_status &= ~(STATUS_LP | STATUS_E |STATUS_N | STATUS_U); break; } return data; } //------------------------------------------------- // write - //------------------------------------------------- WRITE8_MEMBER( upd3301_device::write ) { switch (offset & 0x01) { case 0: // data switch (m_mode) { case MODE_RESET: switch (m_param_count) { 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); 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 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 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; 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; break; } m_param_count++; 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 1: m_cy = data & 0x3f; LOG("UPD3301 CY: %u\n", m_cy); m_mode = MODE_NONE; 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_READ_LIGHT_PEN: LOG("UPD3301 Read Light Pen\n"); 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_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(); break; } break; } } //------------------------------------------------- // dack_w - //------------------------------------------------- WRITE8_MEMBER( upd3301_device::dack_w ) { if (m_y >= (m_l * m_r)) { return; } if (m_data_fifo_pos < m_h) { m_data_fifo[m_data_fifo_pos][m_input_fifo] = data; m_data_fifo_pos++; } else { m_attr_fifo[m_attr_fifo_pos][m_input_fifo] = data; m_attr_fifo_pos++; } if ((m_data_fifo_pos == m_h) && (m_attr_fifo_pos == (m_attr << 1))) { m_input_fifo = !m_input_fifo; m_data_fifo_pos = 0; m_attr_fifo_pos = 0; draw_scanline(); if (m_y == (m_l * m_r)) { // end DMA transfer set_drq(0); } } } //------------------------------------------------- // lpen_w - //------------------------------------------------- WRITE_LINE_MEMBER( upd3301_device::lpen_w ) { } //------------------------------------------------- // hrtc_r - //------------------------------------------------- READ_LINE_MEMBER( upd3301_device::hrtc_r ) { return m_hrtc; } //------------------------------------------------- // vrtc_r - //------------------------------------------------- READ_LINE_MEMBER( upd3301_device::vrtc_r ) { return m_vrtc; } //------------------------------------------------- // draw_scanline - //------------------------------------------------- void upd3301_device::draw_scanline() { for (int lc = 0; lc < m_r; lc++) { 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 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); } } m_y += m_r; } //------------------------------------------------- // update_screen - //------------------------------------------------- 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; 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; } copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); } else { bitmap.fill(rgb_t(0x00,0x00,0x00), cliprect); } return 0; } //------------------------------------------------- // set_interrupt - //------------------------------------------------- void upd3301_device::set_interrupt(int state) { LOG("UPD3301 Interrupt: %u\n", state); m_write_int(state); if (!state) { m_status &= ~(STATUS_N | STATUS_E); } } //------------------------------------------------- // set_drq - //------------------------------------------------- void upd3301_device::set_drq(int state) { LOG("UPD3301 DRQ: %u\n", state); m_write_drq(state); } //------------------------------------------------- // set_display - //------------------------------------------------- void upd3301_device::set_display(int state) { if (state) { m_status |= STATUS_VE; } else { m_status &= ~STATUS_VE; } } //------------------------------------------------- // reset_counters - //------------------------------------------------- void upd3301_device::reset_counters() { set_interrupt(0); set_drq(0); } //------------------------------------------------- // update_hrtc_timer - //------------------------------------------------- void upd3301_device::update_hrtc_timer(int state) { int y = screen().vpos(); int next_x = state ? m_h : 0; int next_y = state ? y : ((y + 1) % ((m_l + m_v) * m_width)); attotime duration = screen().time_until_pos(next_y, next_x); m_hrtc_timer->adjust(duration, !state); } //------------------------------------------------- // update_vrtc_timer - //------------------------------------------------- void upd3301_device::update_vrtc_timer(int state) { int next_y = state ? (m_l * m_r) : 0; attotime duration = screen().time_until_pos(next_y, 0); m_vrtc_timer->adjust(duration, !state); } //------------------------------------------------- // recompute_parameters - //------------------------------------------------- void upd3301_device::recompute_parameters() { int horiz_pix_total = (m_h + m_z) * m_width; int vert_pix_total = (m_l + m_v) * m_r; attoseconds_t refresh = HZ_TO_ATTOSECONDS(clock()) * horiz_pix_total * vert_pix_total; rectangle visarea; visarea.set(0, (m_h * m_width) - 1, 0, (m_l * m_r) - 1); LOG("UPD3301 Screen: %u x %u @ %f Hz\n", horiz_pix_total, vert_pix_total, 1 / ATTOSECONDS_TO_DOUBLE(refresh)); LOG("UPD3301 Visible Area: (%u, %u) - (%u, %u)\n", visarea.min_x, visarea.min_y, visarea.max_x, visarea.max_y); screen().configure(horiz_pix_total, vert_pix_total, visarea, refresh); update_hrtc_timer(0); update_vrtc_timer(0); }