/*************************************************************************** zx.c video hardware Juergen Buchmueller , Dec 1999 The ZX has a very unorthodox video RAM system. To start a scanline, the CPU must jump to video RAM at 0xC000, which is a mirror of the RAM at 0x4000. The video chip (ULA?) pulls a switcharoo and changes the video bytes as they are fetched by the CPU. The video chip draws the scanline until a HALT instruction (0x76) is reached, which indicates no further video RAM for this scanline. Any other video byte is used to generate a tile and at the same time, appears to the CPU as a NOP (0x00) instruction. ****************************************************************************/ #include "includes/zx.h" void zx_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) { switch (id) { case TIMER_TAPE_PULSE: zx_tape_pulse(ptr, param); break; case TIMER_ULA_NMI: zx_ula_nmi(ptr, param); break; case TIMER_ULA_IRQ: zx_ula_irq(ptr, param); break; default: assert_always(FALSE, "Unknown id in zx_state::device_timer"); } } /* * Toggle the video output between black and white. * This happens whenever the ULA scanline IRQs are enabled/disabled. * Normally this is done during the synchronized zx_ula_r() function, * which outputs 8 pixels per code, but if the video sync is off * (during tape IO or sound output) zx_ula_bkgnd() is used to * simulate the display of a ZX80/ZX81. */ void zx_state::zx_ula_bkgnd(UINT8 color) { screen_device *screen = machine().first_screen(); int width = screen->width(); int height = screen->height(); const rectangle &visarea = screen->visible_area(); if (m_ula_frame_vsync == 0 && color != m_old_c) { int y, new_x, new_y; rectangle r; bitmap_ind16 &bitmap = m_bitmap; new_y = machine().primary_screen->vpos(); new_x = machine().primary_screen->hpos(); /* logerror("zx_ula_bkgnd: %3d,%3d - %3d,%3d\n", state->m_old_x, state->m_old_y, new_x, new_y);*/ y = m_old_y; for (;;) { if (y == new_y) { r.set(m_old_x, new_x, y, y); bitmap.fill(color, r); break; } else { r.set(m_old_x, visarea.max_x, y, y); bitmap.fill(color, r); m_old_x = 0; } if (++y == height) y = 0; } m_old_x = (new_x + 1) % width; m_old_y = new_y; m_old_c = color; } } /* * PAL: 310 total lines, * 0.. 55 vblank * 56..247 192 visible lines * 248..303 vblank * 304... vsync * NTSC: 262 total lines * 0.. 31 vblank * 32..223 192 visible lines * 224..233 vblank */ TIMER_CALLBACK_MEMBER(zx_state::zx_ula_nmi) { /* * An NMI is issued on the ZX81 every 64us for the blanked * scanlines at the top and bottom of the display. */ screen_device *screen = machine().first_screen(); int height = screen->height(); const rectangle& r1 = screen->visible_area(); rectangle r; bitmap_ind16 &bitmap = m_bitmap; r.set(r1.min_x, r1.max_x, m_ula_scanline_count, m_ula_scanline_count); bitmap.fill(1, r); // logerror("ULA %3d[%d] NMI, R:$%02X, $%04x\n", machine().primary_screen->vpos(), ula_scancode_count, (unsigned) m_maincpu->state_int(Z80_R), (unsigned) m_maincpu->state_int(Z80_PC)); m_maincpu->set_input_line(INPUT_LINE_NMI, PULSE_LINE); if (++m_ula_scanline_count == height) m_ula_scanline_count = 0; } TIMER_CALLBACK_MEMBER(zx_state::zx_ula_irq) { /* * An IRQ is issued on the ZX80/81 whenever the R registers * bit 6 goes low. In MESS this IRQ timed from the first read * from the copy of the DFILE in the upper 32K in zx_ula_r(). */ if (m_ula_irq_active) { // logerror("ULA %3d[%d] IRQ, R:$%02X, $%04x\n", machine().primary_screen->vpos(), ula_scancode_count, (unsigned) m_maincpu->state_int(Z80_R), (unsigned) m_maincpu->state_int(Z80_PC)); m_ula_irq_active = 0; m_maincpu->set_input_line(0, HOLD_LINE); } } void zx_state::zx_ula_r(int offs, memory_region *region, const UINT8 param) { int offs0 = offs & 0x7fff; UINT8 *rom = m_region_maincpu->base(); UINT8 chr = rom[offs0]; if ((!m_ula_irq_active) && (chr == 0x76)) { bitmap_ind16 &bitmap = m_bitmap; UINT16 y, *scanline; UINT16 ireg = m_maincpu->state_int(Z80_I) << 8; UINT8 data, *chrgen, creg; if (param) creg = m_maincpu->state_int(Z80_B); else creg = m_maincpu->state_int(Z80_C); chrgen = region->base(); if ((++m_ula_scanline_count == m_screen->height()) || (creg == 32)) { m_ula_scanline_count = 0; m_offs1 = offs0; } m_ula_frame_vsync = 3; m_charline_ptr = 0; for (y = m_offs1+1; ((y < offs0) && (m_charline_ptr < ARRAY_LENGTH(m_charline))); y++) { m_charline[m_charline_ptr] = rom[y]; m_charline_ptr++; } for (y = m_charline_ptr; y < ARRAY_LENGTH(m_charline); y++) m_charline[y] = 0; timer_set(m_maincpu->cycles_to_attotime(((32 - m_charline_ptr) << 2)), TIMER_ULA_IRQ); m_ula_irq_active++; scanline = &bitmap.pix16(m_ula_scanline_count); y = 0; for (m_charline_ptr = 0; m_charline_ptr < ARRAY_LENGTH(m_charline); m_charline_ptr++) { chr = m_charline[m_charline_ptr]; data = chrgen[ireg | ((chr & 0x3f) << 3) | ((8 - creg)&7) ]; if (chr & 0x80) data ^= 0xff; scanline[y++] = (data >> 7) & 1; scanline[y++] = (data >> 6) & 1; scanline[y++] = (data >> 5) & 1; scanline[y++] = (data >> 4) & 1; scanline[y++] = (data >> 3) & 1; scanline[y++] = (data >> 2) & 1; scanline[y++] = (data >> 1) & 1; scanline[y++] = (data >> 0) & 1; m_charline[m_charline_ptr] = 0; } if (creg == 1) m_offs1 = offs0; } } void zx_state::video_start() { m_ula_nmi = timer_alloc(TIMER_ULA_NMI); m_ula_irq_active = 0; m_screen->register_screen_bitmap(m_bitmap); } void zx_state::screen_eof_zx(screen_device &screen, bool state) { // rising edge if (state) { /* decrement video synchronization counter */ if (m_ula_frame_vsync) --m_ula_frame_vsync; } } UINT32 zx_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); return 0; }