// license:BSD-3-Clause // copyright-holders:Aaron Giles, Nathan Woods /*************************************************************************** v9938 / v9958 emulation Vertical display parameters from Yamaha V9938 Technical Data Book. NTSC: page 146, Table 7-2 PAL: page 147, Table 7-3 Vertical timing: PAL NTSC 192(LN=0) 212(LN=1) 192(LN=0) 212(LN=1) ------------------- -------------------- 1. Top erase (top blanking) 13 13 13 13 2. Top border 53 43 26 16 3. Active display 192 212 192 212 4. Bottom border 49 39 25 15 5. Bottom erase (bottom blanking) 3 3 3 3 6. Vertical sync (bottom blanking) 3 3 3 3 7. Total 313 313 262 262 Refresh rate 50.158974 59.922743 ***************************************************************************/ /* todo: - sprite collision - vdp engine -- make run at correct speed - vr/hr/fh flags: double-check all of that - make vdp engine work in exp. ram */ #include "emu.h" #include "v9938.h" #define LOG_WARN (1U << 1) #define LOG_INT (1U << 2) #define LOG_STATUS (1U << 3) #define LOG_REGWRITE (1U << 4) #define LOG_COMMAND (1U << 5) #define LOG_MODE (1U << 6) #define LOG_NOTIMP (1U << 7) #define LOG_DETAIL (1U << 8) // Minimum log should be warnings #define VERBOSE (LOG_GENERAL | LOG_WARN) #include "logmacro.h" enum { V9938_MODE_TEXT1 = 0, V9938_MODE_MULTI, V9938_MODE_GRAPHIC1, V9938_MODE_GRAPHIC2, V9938_MODE_GRAPHIC3, V9938_MODE_GRAPHIC4, V9938_MODE_GRAPHIC5, V9938_MODE_GRAPHIC6, V9938_MODE_GRAPHIC7, V9938_MODE_TEXT2, V9938_MODE_UNKNOWN }; #define MODEL_V9938 (0) #define MODEL_V9958 (1) #define EXPMEM_OFFSET 0x20000 #define V9938_LONG_WIDTH (512 + 32) static const char *const v9938_modes[] = { "TEXT 1", "MULTICOLOR", "GRAPHIC 1", "GRAPHIC 2", "GRAPHIC 3", "GRAPHIC 4", "GRAPHIC 5", "GRAPHIC 6", "GRAPHIC 7", "TEXT 2", "UNKNOWN" }; //************************************************************************** // GLOBAL VARIABLES //************************************************************************** /* Similar to the TMS9928, the V9938 has an own address space. It can handle at most 192 KiB RAM (128 KiB base, 64 KiB expansion). */ void v99x8_device::memmap(address_map &map) { map.global_mask(0x3ffff); map(0x00000, 0x2ffff).ram(); } // devices DEFINE_DEVICE_TYPE(V9938, v9938_device, "v9938", "Yamaha V9938 VDP") DEFINE_DEVICE_TYPE(V9958, v9958_device, "v9958", "Yamaha V9958 VDP") v99x8_device::v99x8_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, int model) : device_t(mconfig, type, tag, owner, clock), device_memory_interface(mconfig, *this), device_palette_interface(mconfig, *this), device_video_interface(mconfig, *this), m_space_config("vram", ENDIANNESS_BIG, 8, 18), m_model(model), m_pal_config(false), m_offset_x(0), m_offset_y(0), m_visible_y(0), m_mode(0), m_pal_write_first(0), m_cmd_write_first(0), m_pal_write(0), m_cmd_write(0), m_read_ahead(0), m_v9958_sp_mode(0), m_address_latch(0), m_vram_size(0), m_int_state(0), m_int_callback(*this), m_scanline(0), m_blink(0), m_blink_count(0), m_mx_delta(0), m_my_delta(0), m_button_state(0), m_vdp_ops_count(0), m_vdp_engine(nullptr), m_pal_ntsc(0) { set_addrmap(AS_DATA, address_map_constructor(FUNC(v99x8_device::memmap), this)); } v9938_device::v9938_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : v99x8_device(mconfig, V9938, tag, owner, clock, MODEL_V9938) { } v9958_device::v9958_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : v99x8_device(mconfig, V9958, tag, owner, clock, MODEL_V9958) { } device_memory_interface::space_config_vector v99x8_device::memory_space_config() const { return space_config_vector { std::make_pair(AS_DATA, &m_space_config) }; } void v99x8_device::device_config_complete() { if (!has_screen()) return; if (!screen().refresh_attoseconds()) screen().set_raw(clock(), HTOTAL, 0, HVISIBLE - 1, (m_pal_config ? VTOTAL_PAL : VTOTAL_NTSC) * 2, VERTICAL_ADJUST * 2, (m_pal_config ? VVISIBLE_PAL : VVISIBLE_NTSC) * 2 - 1 - VERTICAL_ADJUST * 2); if (!screen().has_screen_update()) screen().set_screen_update(*this, FUNC(v99x8_device::screen_update)); } TIMER_CALLBACK_MEMBER(v99x8_device::update_line) { int scanline = (m_scanline - (m_scanline_start + m_offset_y)); update_command(); // set flags if (m_scanline == (m_scanline_start + m_offset_y)) { m_stat_reg[2] &= ~0x40; } else if (m_scanline == (m_scanline_start + m_offset_y + m_visible_y)) { m_stat_reg[2] |= 0x40; m_stat_reg[0] |= 0x80; } if ( (scanline >= 0) && (scanline <= m_scanline_max) && (((scanline + m_cont_reg[23]) & 255) == m_cont_reg[19]) ) { m_stat_reg[1] |= 1; LOGMASKED(LOG_INT, "Scanline interrupt (%d)\n", scanline); } else if (!(m_cont_reg[0] & 0x10)) { m_stat_reg[1] &= 0xfe; } check_int(); // check for start of vblank if (m_scanline == m_vblank_start) { interrupt_start_vblank(); } // render the current line if (m_scanline < m_vblank_start) { refresh_line(scanline); } if (++m_scanline >= m_height) { m_scanline = 0; // PAL/NTSC changed? int pal = m_cont_reg[9] & 2; if (m_pal_ntsc != pal) { m_pal_ntsc = pal; configure_pal_ntsc(); } //screen().reset_origin(); m_offset_y = position_offset(m_cont_reg[18] >> 4); set_screen_parameters(); } } void v99x8_device::set_screen_parameters() { if (m_pal_ntsc) { // PAL m_scanline_start = (m_cont_reg[9] & 0x80) ? 43 : 53; m_scanline_max = 255; } else { // NTSC m_scanline_start = (m_cont_reg[9] & 0x80) ? 16 : 26; m_scanline_max = (m_cont_reg[9] & 0x80) ? 234 : 244; } m_visible_y = (m_cont_reg[9] & 0x80) ? 212 : 192; } // FIXME: this doesn't really allow for external clock configuration void v99x8_device::configure_pal_ntsc() { if (m_pal_ntsc) { // PAL m_height = VTOTAL_PAL; rectangle visible; visible.set(0, HVISIBLE - 1, VERTICAL_ADJUST * 2, VVISIBLE_PAL * 2 - 1 - VERTICAL_ADJUST * 2); screen().configure(HTOTAL, VTOTAL_PAL * 2, visible, HZ_TO_ATTOSECONDS(50.158974)); } else { // NTSC m_height = VTOTAL_NTSC; rectangle visible; visible.set(0, HVISIBLE - 1, VERTICAL_ADJUST * 2, VVISIBLE_NTSC * 2 - 1 - VERTICAL_ADJUST * 2); screen().configure(HTOTAL, VTOTAL_NTSC * 2, visible, HZ_TO_ATTOSECONDS(59.922743)); } m_vblank_start = m_height - VERTICAL_SYNC - TOP_ERASE; /* Sync + top erase */ } /* Colorbus inputs vdp will process mouse deltas only if it is in mouse mode Reg 8: MS LP x x x x x x */ void v99x8_device::colorbus_x_input(int mx_delta) { if ((m_cont_reg[8] & 0xc0) == 0x80) { m_mx_delta += mx_delta; if (m_mx_delta < -127) m_mx_delta = -127; if (m_mx_delta > 127) m_mx_delta = 127; } } void v99x8_device::colorbus_y_input(int my_delta) { if ((m_cont_reg[8] & 0xc0) == 0x80) { m_my_delta += my_delta; if (m_my_delta < -127) m_my_delta = -127; if (m_my_delta > 127) m_my_delta = 127; } } void v99x8_device::colorbus_button_input(bool switch1_pressed, bool switch2_pressed) { // save button state m_button_state = (switch2_pressed? 0x80 : 0x00) | (switch1_pressed? 0x40 : 0x00); } /*************************************************************************** Palette functions ***************************************************************************/ /* About the colour burst registers: The color burst registers will only have effect on the composite video output from the V9938. but the output is only NTSC (Never The Same Color ,so the effects are already present) . this system is not used in europe the european machines use a separate PAL (Phase Alternating Line) encoder or no encoder at all , only RGB output. Erik de Boer. -- Right now they're not emulated. For completeness sake they should -- with a dip-switch to turn them off. I really don't know how they work though. :( */ /* In screen 8, the colors are encoded as: 7 6 5 4 3 2 1 0 +--+--+--+--+--+--+--+--+ |g2|g1|g0|r2|r1|r0|b2|b1| +--+--+--+--+--+--+--+--+ b0 is set if b2 and b1 are set (remember, color bus is 3 bits) */ void v9938_device::palette_init() { } /* The v9958 can display up to 19286 colours. For this we need a larger palette. The colours are encoded in 17 bits; however there are just 19268 different colours. Here we calculate the palette and a 2^17 reference table to the palette, which is: s_pal_indYJK. It's 256K in size, but I can't think of a faster way to emulate this. Also it keeps the palette a reasonable size. :) */ uint32_t v99x8_device::s_pal_indYJK[0x20000]; void v9958_device::palette_init() { int r,g,b,y,j,k,k0,j0; // set up YJK table LOGMASKED(LOG_DETAIL, "Building YJK table for V9958 screens, may take a while ... \n"); for (y=0;y<32;y++) for (k=0;k<64;k++) for (j=0;j<64;j++) { // calculate the color if (k >= 32) k0 = (k - 64); else k0 = k; if (j >= 32) j0 = (j - 64); else j0 = j; r = y + j0; b = (y * 5 - 2 * j0 - k0) / 4; g = y + k0; if (r < 0) r = 0; else if (r > 31) r = 31; if (g < 0) g = 0; else if (g > 31) g = 31; if (b < 0) b = 0; else if (b > 31) b = 31; v99x8_device::s_pal_indYJK[y | j << 5 | k << (5 + 6)] = uint32_t(rgb_t(pal5bit(r), pal5bit(g), pal5bit(b))); } } uint32_t v99x8_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { copybitmap(bitmap, m_bitmap, 0, 0, 0, 0, cliprect); return 0; } uint8_t v99x8_device::read(offs_t offset) { switch (offset & 3) { case 0: return vram_r(); case 1: return status_r(); } return 0xff; } void v99x8_device::write(offs_t offset, uint8_t data) { switch (offset & 3) { case 0: vram_w(data); break; case 1: command_w(data); break; case 2: palette_w(data); break; case 3: register_w(data); break; } } uint8_t v99x8_device::vram_r() { uint8_t ret; int address; address = ((int)m_cont_reg[14] << 14) | m_address_latch; m_cmd_write_first = 0; ret = m_read_ahead; if (m_cont_reg[45] & 0x40) // Expansion memory { if ( (m_mode == V9938_MODE_GRAPHIC6) || (m_mode == V9938_MODE_GRAPHIC7) ) address >>= 1; // correct? // Expansion memory only offers 64 K if (m_vram_size > 0x20000 && ((address & 0x10000)==0)) m_read_ahead = m_vram_space->read_byte(address + EXPMEM_OFFSET); else m_read_ahead = 0xff; } else { m_read_ahead = vram_read(address); } m_address_latch = (m_address_latch + 1) & 0x3fff; if ((!m_address_latch) && (m_cont_reg[0] & 0x0c) ) // correct ??? { m_cont_reg[14] = (m_cont_reg[14] + 1) & 7; } return ret; } uint8_t v99x8_device::status_r() { int reg; uint8_t ret; m_cmd_write_first = 0; reg = m_cont_reg[15] & 0x0f; if (reg > 9) return 0xff; switch (reg) { case 0: ret = m_stat_reg[0]; m_stat_reg[0] &= 0x1f; break; case 1: ret = m_stat_reg[1]; m_stat_reg[1] &= 0xfe; // mouse mode: add button state if ((m_cont_reg[8] & 0xc0) == 0x80) ret |= m_button_state & 0xc0; break; case 2: /*update_command ();*/ /* WTF is this? Whatever this was intended to do, it is nonsensical. Might as well pick a random number.... This was an attempt to emulate H-Blank flag ;) n = cycles_currently_ran (); if ( (n < 28) || (n > 199) ) vdp.statReg[2] |= 0x20; else vdp.statReg[2] &= ~0x20; */ if (machine().rand() & 1) m_stat_reg[2] |= 0x20; else m_stat_reg[2] &= ~0x20; ret = m_stat_reg[2]; break; case 3: if ((m_cont_reg[8] & 0xc0) == 0x80) { // mouse mode: return x mouse delta ret = m_mx_delta; m_mx_delta = 0; } else ret = m_stat_reg[3]; break; case 5: if ((m_cont_reg[8] & 0xc0) == 0x80) { // mouse mode: return y mouse delta ret = m_my_delta; m_my_delta = 0; } else ret = m_stat_reg[5]; break; case 7: ret = m_stat_reg[7]; m_stat_reg[7] = m_cont_reg[44] = vdp_to_cpu () ; break; default: ret = m_stat_reg[reg]; break; } LOGMASKED(LOG_STATUS, "Read %02x from S#%d\n", ret, reg); check_int (); return ret; } void v99x8_device::palette_w(uint8_t data) { int indexp; if (m_pal_write_first) { // store in register indexp = m_cont_reg[0x10] & 15; m_pal_reg[indexp*2] = m_pal_write & 0x77; m_pal_reg[indexp*2+1] = data & 0x07; // update palette set_pen16(indexp, uint32_t(rgb_t(pal3bit((m_pal_write & 0x70) >> 4), pal3bit(data & 0x07), pal3bit(m_pal_write & 0x07)))); m_cont_reg[0x10] = (m_cont_reg[0x10] + 1) & 15; m_pal_write_first = 0; } else { m_pal_write = data; m_pal_write_first = 1; } } void v99x8_device::vram_w(uint8_t data) { int address; /*update_command ();*/ m_cmd_write_first = 0; address = ((int)m_cont_reg[14] << 14) | m_address_latch; if (m_cont_reg[45] & 0x40) { if ( (m_mode == V9938_MODE_GRAPHIC6) || (m_mode == V9938_MODE_GRAPHIC7) ) address >>= 1; // correct? if (m_vram_size > 0x20000 && ((address & 0x10000)==0)) m_vram_space->write_byte(EXPMEM_OFFSET + address, data); } else { vram_write(address, data); } m_address_latch = (m_address_latch + 1) & 0x3fff; if ((!m_address_latch) && (m_cont_reg[0] & 0x0c) ) // correct ??? { m_cont_reg[14] = (m_cont_reg[14] + 1) & 7; } } void v99x8_device::command_w(uint8_t data) { if (m_cmd_write_first) { if (data & 0x80) { if (!(data & 0x40)) register_write (data & 0x3f, m_cmd_write); } else { m_address_latch = (((uint16_t)data << 8) | m_cmd_write) & 0x3fff; if ( !(data & 0x40) ) vram_r (); // read ahead! } m_cmd_write_first = 0; } else { m_cmd_write = data; m_cmd_write_first = 1; } } void v99x8_device::register_w(uint8_t data) { int reg; reg = m_cont_reg[17] & 0x3f; if (reg != 17) register_write(reg, data); // true ? if (!(m_cont_reg[17] & 0x80)) m_cont_reg[17] = (m_cont_reg[17] + 1) & 0x3f; } /*************************************************************************** Init/stop/reset/Interrupt functions ***************************************************************************/ void v99x8_device::device_start() { m_vdp_ops_count = 1; m_vdp_engine = nullptr; screen().register_screen_bitmap(m_bitmap); // Video RAM is allocated as an own address space m_vram_space = &space(AS_DATA); // allocate VRAM assert(m_vram_size > 0); if (m_vram_size < 0x20000) { // set unavailable RAM to 0xff for (int addr = m_vram_size; addr < 0x30000; addr++) m_vram_space->write_byte(addr, 0xff); } m_line_timer = timer_alloc(FUNC(v99x8_device::update_line), this); palette_init(); save_item(NAME(m_offset_x)); save_item(NAME(m_offset_y)); save_item(NAME(m_visible_y)); save_item(NAME(m_mode)); save_item(NAME(m_pal_write_first)); save_item(NAME(m_cmd_write_first)); save_item(NAME(m_pal_write)); save_item(NAME(m_cmd_write)); save_item(NAME(m_pal_reg)); save_item(NAME(m_stat_reg)); save_item(NAME(m_cont_reg)); save_item(NAME(m_read_ahead)); save_item(NAME(m_v9958_sp_mode)); save_item(NAME(m_address_latch)); save_item(NAME(m_int_state)); save_item(NAME(m_scanline)); save_item(NAME(m_blink)); save_item(NAME(m_blink_count)); save_item(NAME(m_mx_delta)); save_item(NAME(m_my_delta)); save_item(NAME(m_button_state)); save_item(NAME(m_mmc.SX)); save_item(NAME(m_mmc.SY)); save_item(NAME(m_mmc.DX)); save_item(NAME(m_mmc.DY)); save_item(NAME(m_mmc.TX)); save_item(NAME(m_mmc.TY)); save_item(NAME(m_mmc.NX)); save_item(NAME(m_mmc.NY)); save_item(NAME(m_mmc.MX)); save_item(NAME(m_mmc.ASX)); save_item(NAME(m_mmc.ADX)); save_item(NAME(m_mmc.ANX)); save_item(NAME(m_mmc.CL)); save_item(NAME(m_mmc.LO)); save_item(NAME(m_mmc.CM)); save_item(NAME(m_mmc.MXS)); save_item(NAME(m_mmc.MXD)); save_item(NAME(m_vdp_ops_count)); save_item(NAME(m_pal_ntsc)); save_item(NAME(m_scanline_start)); save_item(NAME(m_vblank_start)); save_item(NAME(m_scanline_max)); save_item(NAME(m_height)); } void v99x8_device::device_reset() { int i; // offset reset m_offset_x = 8; m_offset_y = 0; m_visible_y = 192; // register reset reset_palette (); // palette registers for (i=0;i<10;i++) m_stat_reg[i] = 0; m_stat_reg[2] = 0x0c; if (m_model == MODEL_V9958) m_stat_reg[1] |= 4; for (i=0;i<48;i++) m_cont_reg[i] = 0; m_cmd_write_first = m_pal_write_first = 0; m_int_state = 0; m_read_ahead = 0; m_address_latch = 0; // ??? // FIXME: this drifts the scanline number wrt screen h/vpos m_scanline = 0; // MZ: The status registers 4 and 6 hold the high bits of the sprite // collision location. The unused bits are set to 1. // SR3: x x x x x x x x // SR4: 1 1 1 1 1 1 1 x // SR5: y y y y y y y y // SR6: 1 1 1 1 1 1 y y // Note that status register 4 is used in detection algorithms to tell // apart the tms9929 from the v99x8. // TODO: SR3-S6 do not yet store the information about the sprite collision m_stat_reg[4] = 0xfe; m_stat_reg[6] = 0xfc; // Start the timer m_line_timer->adjust(attotime::from_ticks(HTOTAL*2, m_clock), 0, attotime::from_ticks(HTOTAL*2, m_clock)); configure_pal_ntsc(); set_screen_parameters(); } void v99x8_device::reset_palette() { // taken from V9938 Technical Data book, page 148. it's in G-R-B format static const uint8_t pal16[16*3] = { 0, 0, 0, // 0: black/transparent 0, 0, 0, // 1: black 6, 1, 1, // 2: medium green 7, 3, 3, // 3: light green 1, 1, 7, // 4: dark blue 3, 2, 7, // 5: light blue 1, 5, 1, // 6: dark red 6, 2, 7, // 7: cyan 1, 7, 1, // 8: medium red 3, 7, 3, // 9: light red 6, 6, 1, // 10: dark yellow 6, 6, 4, // 11: light yellow 4, 1, 1, // 12: dark green 2, 6, 5, // 13: magenta 5, 5, 5, // 14: gray 7, 7, 7 // 15: white }; int i, red; for (i=0;i<16;i++) { // set the palette registers m_pal_reg[i*2+0] = pal16[i*3+1] << 4 | pal16[i*3+2]; m_pal_reg[i*2+1] = pal16[i*3]; // set the reference table set_pen16(i, uint32_t(rgb_t(pal3bit(pal16[i*3+1]), pal3bit(pal16[i*3]), pal3bit(pal16[i*3+2])))); } // set internal palette GRAPHIC 7 for (i=0;i<256;i++) { red = (i << 1) & 6; if (red == 6) red++; set_pen256(i, uint32_t(rgb_t(pal3bit((i & 0x1c) >> 2), pal3bit((i & 0xe0) >> 5), pal3bit(red)))); } } /*************************************************************************** Memory functions ***************************************************************************/ void v99x8_device::vram_write(int offset, int data) { int newoffset; if ( (m_mode == V9938_MODE_GRAPHIC6) || (m_mode == V9938_MODE_GRAPHIC7) ) { newoffset = ((offset & 1) << 16) | (offset >> 1); if (newoffset < m_vram_size) m_vram_space->write_byte(newoffset, data); } else { if (offset < m_vram_size) m_vram_space->write_byte(offset, data); } } int v99x8_device::vram_read(int offset) { if ( (m_mode == V9938_MODE_GRAPHIC6) || (m_mode == V9938_MODE_GRAPHIC7) ) return m_vram_space->read_byte(((offset & 1) << 16) | (offset >> 1)); else return m_vram_space->read_byte(offset); } void v99x8_device::check_int() { uint8_t n; n = ( (m_cont_reg[1] & 0x20) && (m_stat_reg[0] & 0x80) /*&& m_vblank_int*/) || ( (m_stat_reg[1] & 0x01) && (m_cont_reg[0] & 0x10) ); #if 0 if(n && m_vblank_int) { m_vblank_int = 0; } #endif if (n != m_int_state) { m_int_state = n; LOGMASKED(LOG_INT, "IRQ line %s\n", n ? "up" : "down"); } /* ** Somehow the IRQ request is going down without cpu_irq_line () being ** called; because of this Mr. Ghost, Xevious and SD Snatcher don't ** run. As a patch it's called every scanline */ // FIXME: breaks nichibutsu hrdvd.cpp & nichild.cpp, really needs INPUT_MERGER instead. m_int_callback(n); } /*************************************************************************** Register functions ***************************************************************************/ void v99x8_device::register_write (int reg, int data) { static uint8_t const reg_mask[] = { 0x7e, 0x7b, 0x7f, 0xff, 0x3f, 0xff, 0x3f, 0xff, 0xfb, 0xbf, 0x07, 0x03, 0xff, 0xff, 0x07, 0x0f, 0x0f, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x7f, 0x3f, 0x07 }; if (reg <= 27) { data &= reg_mask[reg]; if (m_cont_reg[reg] == data) return; } if (reg > 46) { LOGMASKED(LOG_WARN, "Attempted to write to non-existent R#%d\n", reg); return; } /*update_command ();*/ switch (reg) { // registers that affect interrupt and display mode case 0: case 1: m_cont_reg[reg] = data; set_mode(); check_int(); LOGMASKED(LOG_MODE, "Mode = %s\n", v9938_modes[m_mode]); break; case 18: case 9: m_cont_reg[reg] = data; // recalc offset m_offset_x = 8 + position_offset(m_cont_reg[18] & 0x0f); // Y offset is only applied once per frame? break; case 15: m_pal_write_first = 0; break; // color burst registers aren't emulated case 20: case 21: case 22: LOGMASKED(LOG_NOTIMP, "Write %02xh to R#%d; color burst not emulated\n", data, reg); break; case 25: case 26: case 27: if (m_model != MODEL_V9958) { LOGMASKED(LOG_WARN, "Attempting to write %02xh to R#%d (invalid on v9938)\n", data, reg); data = 0; } else { if(reg == 25) m_v9958_sp_mode = data & 0x18; } break; case 44: cpu_to_vdp (data); break; case 46: command_unit_w (data); break; } if (reg != 15) LOGMASKED(LOG_REGWRITE, "Write %02x to R#%d\n", data, reg); m_cont_reg[reg] = data; } /*************************************************************************** Refresh / render function ***************************************************************************/ inline bool v99x8_device::v9938_second_field() { return !(((m_cont_reg[9] & 0x04) && !(m_stat_reg[2] & 2)) || m_blink); } void v99x8_device::default_border(uint32_t *ln) { pen_t pen; int i; pen = pen16(m_cont_reg[7] & 0x0f); i = V9938_LONG_WIDTH; while (i--) *ln++ = pen; } void v99x8_device::graphic7_border(uint32_t *ln) { pen_t pen; int i; pen = pen256(m_cont_reg[7]); i = V9938_LONG_WIDTH; while (i--) *ln++ = pen; } void v99x8_device::graphic5_border(uint32_t *ln) { int i; pen_t pen0; pen_t pen1; pen1 = pen16(m_cont_reg[7] & 0x03); pen0 = pen16((m_cont_reg[7] >> 2) & 0x03); i = V9938_LONG_WIDTH / 2; while (i--) { *ln++ = pen0; *ln++ = pen1; } } void v99x8_device::mode_text1(uint32_t *ln, int line) { int pattern, x, xx, name, xxx; pen_t fg, bg, pen; int nametbl_addr, patterntbl_addr; patterntbl_addr = m_cont_reg[4] << 11; nametbl_addr = m_cont_reg[2] << 10; fg = pen16(m_cont_reg[7] >> 4); bg = pen16(m_cont_reg[7] & 15); name = (line/8)*40; pen = pen16(m_cont_reg[7] & 0x0f); xxx = (m_offset_x + 8) * 2; while (xxx--) *ln++ = pen; for (x=0;x<40;x++) { pattern = m_vram_space->read_byte(patterntbl_addr + (m_vram_space->read_byte(nametbl_addr + name) * 8) + ((line + m_cont_reg[23]) & 7)); for (xx=0;xx<6;xx++) { *ln++ = (pattern & 0x80) ? fg : bg; *ln++ = (pattern & 0x80) ? fg : bg; pattern <<= 1; } /* width height 212, characters start repeating at the bottom */ name = (name + 1) & 0x3ff; } xxx = ((16 - m_offset_x) + 8) * 2; while (xxx--) *ln++ = pen; } void v99x8_device::mode_text2(uint32_t *ln, int line) { int pattern, x, charcode, name, xxx, patternmask, colourmask; pen_t fg, bg, fg0, bg0, pen; int nametbl_addr, patterntbl_addr, colourtbl_addr; patterntbl_addr = m_cont_reg[4] << 11; colourtbl_addr = ((m_cont_reg[3] & 0xf8) << 6) + (m_cont_reg[10] << 14); #if 0 colourmask = ((m_cont_reg[3] & 7) << 5) | 0x1f; /* cause a bug in Forth+ v1.0 on Geneve */ #else colourmask = ((m_cont_reg[3] & 7) << 6) | 0x3f; /* verify! */ #endif nametbl_addr = ((m_cont_reg[2] & 0xfc) << 10); patternmask = ((m_cont_reg[2] & 3) << 10) | 0x3ff; /* seems correct */ fg = pen16(m_cont_reg[7] >> 4); bg = pen16(m_cont_reg[7] & 15); fg0 = pen16(m_cont_reg[12] >> 4); bg0 = pen16(m_cont_reg[12] & 15); name = (line/8)*80; xxx = (m_offset_x + 8) * 2; pen = pen16(m_cont_reg[7] & 0x0f); while (xxx--) *ln++ = pen; for (x=0;x<80;x++) { charcode = m_vram_space->read_byte(nametbl_addr + (name&patternmask)); if (m_blink) { pattern = m_vram_space->read_byte(colourtbl_addr + ((name/8)&colourmask)); if (pattern & (0x80 >> (name & 7) ) ) { pattern = m_vram_space->read_byte(patterntbl_addr + ((charcode * 8) + ((line + m_cont_reg[23]) & 7))); *ln++ = (pattern & 0x80) ? fg0 : bg0; *ln++ = (pattern & 0x40) ? fg0 : bg0; *ln++ = (pattern & 0x20) ? fg0 : bg0; *ln++ = (pattern & 0x10) ? fg0 : bg0; *ln++ = (pattern & 0x08) ? fg0 : bg0; *ln++ = (pattern & 0x04) ? fg0 : bg0; name++; continue; } } pattern = m_vram_space->read_byte(patterntbl_addr + ((charcode * 8) + ((line + m_cont_reg[23]) & 7))); *ln++ = (pattern & 0x80) ? fg : bg; *ln++ = (pattern & 0x40) ? fg : bg; *ln++ = (pattern & 0x20) ? fg : bg; *ln++ = (pattern & 0x10) ? fg : bg; *ln++ = (pattern & 0x08) ? fg : bg; *ln++ = (pattern & 0x04) ? fg : bg; name++; } xxx = (16 - m_offset_x + 8) * 2; while (xxx--) *ln++ = pen; } void v99x8_device::mode_multi(uint32_t *ln, int line) { int nametbl_addr, patterntbl_addr, colour; int name, line2, x, xx; pen_t pen, pen_bg; nametbl_addr = (m_cont_reg[2] << 10); patterntbl_addr = (m_cont_reg[4] << 11); line2 = (line - m_cont_reg[23]) & 255; name = (line2/8)*32; pen_bg = pen16(m_cont_reg[7] & 0x0f); xx = m_offset_x * 2; while (xx--) *ln++ = pen_bg; for (x=0;x<32;x++) { colour = m_vram_space->read_byte(patterntbl_addr + (m_vram_space->read_byte(nametbl_addr + name) * 8) + ((line2/4)&7)); pen = pen16(colour >> 4); /* eight pixels */ *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; pen = pen16(colour & 15); /* eight pixels */ *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; name++; } xx = (16 - m_offset_x) * 2; while (xx--) *ln++ = pen_bg; } void v99x8_device::mode_graphic1(uint32_t *ln, int line) { pen_t fg, bg, pen; int nametbl_addr, patterntbl_addr, colourtbl_addr; int pattern, x, xx, line2, name, charcode, colour, xxx; nametbl_addr = (m_cont_reg[2] << 10); colourtbl_addr = (m_cont_reg[3] << 6) + (m_cont_reg[10] << 14); patterntbl_addr = (m_cont_reg[4] << 11); line2 = (line - m_cont_reg[23]) & 255; name = (line2/8)*32; pen = pen16(m_cont_reg[7] & 0x0f); xxx = m_offset_x * 2; while (xxx--) *ln++ = pen; for (x=0;x<32;x++) { charcode = m_vram_space->read_byte(nametbl_addr + name); colour = m_vram_space->read_byte(colourtbl_addr + charcode/8); fg = pen16(colour >> 4); bg = pen16(colour & 15); pattern = m_vram_space->read_byte(patterntbl_addr + (charcode * 8 + (line2 & 7))); for (xx=0;xx<8;xx++) { *ln++ = (pattern & 0x80) ? fg : bg; *ln++ = (pattern & 0x80) ? fg : bg; pattern <<= 1; } name++; } xx = (16 - m_offset_x) * 2; while (xx--) *ln++ = pen; } void v99x8_device::mode_graphic23(uint32_t *ln, int line) { const int colourmask = ((m_cont_reg[3] & 0x7f) << 3) | 7; const int patternmask = ((m_cont_reg[4] & 0x03) << 8) | 0xff; const int scrolled_y = (line + m_cont_reg[23]) & 0xff; const int colourtbl_addr = ((m_cont_reg[3] & 0x80) << 6) + (m_cont_reg[10] << 14); const int patterntbl_addr = ((m_cont_reg[4] & 0x3c) << 11); const pen_t border_pen = pen16(m_cont_reg[7] & 0x0f); int nametbl_base = (m_cont_reg[2] << 10) + ((scrolled_y / 8) * 32); int nametbl_offset = (m_cont_reg[26] & 0x1f); if (BIT(m_cont_reg[25], 0) && BIT(m_cont_reg[26], 5)) nametbl_base ^= 0x8000; for (int x = m_offset_x * 2; x > 0; x--) *ln++ = border_pen; int dot_scroll = m_cont_reg[27] & 0x07; int pixels_to_mask = BIT(m_cont_reg[25], 1) ? 8 - dot_scroll : 0; int pixels_to_draw = 256 - dot_scroll; while (dot_scroll--) { *ln++ = border_pen; *ln++ = border_pen; } do { const int charcode = m_vram_space->read_byte(nametbl_base + nametbl_offset) + ((scrolled_y & 0xc0) << 2); const u8 colour = m_vram_space->read_byte(colourtbl_addr + ((charcode & colourmask) << 3) + (scrolled_y & 7)); u8 pattern = m_vram_space->read_byte(patterntbl_addr + ((charcode & patternmask) << 3) + (scrolled_y & 7)); const pen_t fg = pen16(colour >> 4); const pen_t bg = pen16(colour & 0x0f); for (int x = 0; x < 8 && pixels_to_draw > 0; x++) { if (!pixels_to_mask) { *ln++ = (pattern & 0x80) ? fg : bg; *ln++ = (pattern & 0x80) ? fg : bg; } else { pixels_to_mask--; *ln++ = border_pen; *ln++ = border_pen; } pixels_to_draw--; pattern <<= 1; } nametbl_offset = (nametbl_offset + 1) & 0x1f; if (BIT(m_cont_reg[25], 0) && !nametbl_offset) nametbl_base ^= 0x8000; } while (pixels_to_draw > 0); for (int x = (16 - m_offset_x) * 2; x > 0; x--) *ln++ = border_pen; } void v99x8_device::mode_graphic4(uint32_t *ln, int line) { int linemask = ((m_cont_reg[2] & 0x1f) << 3) | 7; const int scrolled_y = ((line + m_cont_reg[23]) & linemask) & 0xff; const pen_t border_pen = pen16(m_cont_reg[7] & 0x0f); int nametbl_base = ((m_cont_reg[2] & 0x40) << 10) + (scrolled_y << 7); int nametbl_offset = (m_cont_reg[26] & 0x1f) << 2; if (!BIT(m_cont_reg[25], 0) && BIT(m_cont_reg[2], 5) && v9938_second_field()) nametbl_base += 0x8000; if (BIT(m_cont_reg[25], 0) && BIT(m_cont_reg[26], 5)) nametbl_base ^= 0x8000; for (int x = m_offset_x * 2; x > 0; x--) *ln++ = border_pen; int dot_scroll = m_cont_reg[27] & 0x07; int pixels_to_mask = BIT(m_cont_reg[25], 1) ? 8 - dot_scroll : 0; int pixels_to_draw = 256 - dot_scroll; while (dot_scroll--) { *ln++ = border_pen; *ln++ = border_pen; } do { bool mask_pixel_1 = false; bool mask_pixel_2 = false; if (pixels_to_mask) { mask_pixel_1 = true; pixels_to_mask--; if (pixels_to_mask) { mask_pixel_2 = true; pixels_to_mask--; } } const u8 colour = m_vram_space->read_byte(nametbl_base + nametbl_offset); const pen_t pen1 = mask_pixel_1 ? border_pen : pen16(colour >> 4); const pen_t pen2 = mask_pixel_2 ? border_pen : pen16(colour & 0x0f); *ln++ = pen1; *ln++ = pen1; pixels_to_draw--; if (pixels_to_draw) { *ln++ = pen2; *ln++ = pen2; pixels_to_draw--; } nametbl_offset = (nametbl_offset + 1) & 0x7f; if (BIT(m_cont_reg[25], 0) && !nametbl_offset) nametbl_base ^= 0x8000; } while (pixels_to_draw > 0); for (int x = (16 - m_offset_x) * 2; x > 0; x--) *ln++ = border_pen; } void v99x8_device::mode_graphic5(uint32_t *ln, int line) { int nametbl_addr, colour; int line2, linemask, x, xx; pen_t pen_bg0[4]; pen_t pen_bg1[4]; linemask = ((m_cont_reg[2] & 0x1f) << 3) | 7; line2 = ((line + m_cont_reg[23]) & linemask) & 255; nametbl_addr = ((m_cont_reg[2] & 0x40) << 10) + line2 * 128; if ( (m_cont_reg[2] & 0x20) && v9938_second_field() ) nametbl_addr += 0x8000; pen_bg1[0] = pen16(m_cont_reg[7] & 0x03); pen_bg0[0] = pen16((m_cont_reg[7] >> 2) & 0x03); xx = m_offset_x; while (xx--) { *ln++ = pen_bg0[0]; *ln++ = pen_bg1[0]; } x = (m_cont_reg[8] & 0x20) ? 0 : 1; for (;x<4;x++) { pen_bg0[x] = pen16(x); pen_bg1[x] = pen16(x); } for (x=0;x<128;x++) { colour = m_vram_space->read_byte(nametbl_addr++); *ln++ = pen_bg0[colour>>6]; *ln++ = pen_bg1[(colour>>4)&3]; *ln++ = pen_bg0[(colour>>2)&3]; *ln++ = pen_bg1[(colour&3)]; } pen_bg1[0] = pen16(m_cont_reg[7] & 0x03); pen_bg0[0] = pen16((m_cont_reg[7] >> 2) & 0x03); xx = 16 - m_offset_x; while (xx--) { *ln++ = pen_bg0[0]; *ln++ = pen_bg1[0]; } } void v99x8_device::mode_graphic6(uint32_t *ln, int line) { uint8_t colour; int line2, linemask, x, xx, nametbl_addr; pen_t pen_bg, fg0; pen_t fg1; linemask = ((m_cont_reg[2] & 0x1f) << 3) | 7; line2 = ((line + m_cont_reg[23]) & linemask) & 255; nametbl_addr = line2 << 8 ; if ( (m_cont_reg[2] & 0x20) && v9938_second_field() ) nametbl_addr += 0x10000; pen_bg = pen16(m_cont_reg[7] & 0x0f); xx = m_offset_x * 2; while (xx--) *ln++ = pen_bg; if (m_cont_reg[2] & 0x40) { for (x=0;x<32;x++) { nametbl_addr++; colour = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); fg0 = pen16(colour >> 4); fg1 = pen16(colour & 15); *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; *ln++ = fg0; *ln++ = fg1; nametbl_addr += 7; } } else { for (x=0;x<256;x++) { colour = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); *ln++ = pen16(colour >> 4); *ln++ = pen16(colour & 15); nametbl_addr++; } } xx = (16 - m_offset_x) * 2; while (xx--) *ln++ = pen_bg; } void v99x8_device::mode_graphic7(uint32_t *ln, int line) { uint8_t colour; int line2, linemask, x, xx, nametbl_addr; pen_t pen, pen_bg; linemask = ((m_cont_reg[2] & 0x1f) << 3) | 7; line2 = ((line + m_cont_reg[23]) & linemask) & 255; nametbl_addr = line2 << 8; if ( (m_cont_reg[2] & 0x20) && v9938_second_field() ) nametbl_addr += 0x10000; pen_bg = pen256(m_cont_reg[7]); xx = m_offset_x * 2; while (xx--) *ln++ = pen_bg; if ((m_v9958_sp_mode & 0x18) == 0x08) // v9958 screen 12, puzzle star title screen { for (x=0;x<64;x++) { int colour[4]; int ind; colour[0] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); nametbl_addr++; colour[1] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); nametbl_addr++; colour[2] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); nametbl_addr++; colour[3] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); ind = (colour[0] & 7) << 11 | (colour[1] & 7) << 14 | (colour[2] & 7) << 5 | (colour[3] & 7) << 8; *ln++ = s_pal_indYJK[ind | ((colour[0] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[0] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[1] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[1] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[2] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[2] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[3] >> 3) & 31)]; *ln++ = s_pal_indYJK[ind | ((colour[3] >> 3) & 31)]; nametbl_addr++; } } else if ((m_v9958_sp_mode & 0x18) == 0x18) // v9958 screen 10/11, puzzle star & sexy boom gameplay { for (x=0;x<64;x++) { int colour[4]; int ind; colour[0] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); nametbl_addr++; colour[1] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); nametbl_addr++; colour[2] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); nametbl_addr++; colour[3] = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); ind = (colour[0] & 7) << 11 | (colour[1] & 7) << 14 | (colour[2] & 7) << 5 | (colour[3] & 7) << 8; *ln++ = colour[0] & 8 ? pen16(colour[0] >> 4) : s_pal_indYJK[ind | ((colour[0] >> 3) & 30)]; *ln++ = colour[0] & 8 ? pen16(colour[0] >> 4) : s_pal_indYJK[ind | ((colour[0] >> 3) & 30)]; *ln++ = colour[1] & 8 ? pen16(colour[1] >> 4) : s_pal_indYJK[ind | ((colour[1] >> 3) & 30)]; *ln++ = colour[1] & 8 ? pen16(colour[1] >> 4) : s_pal_indYJK[ind | ((colour[1] >> 3) & 30)]; *ln++ = colour[2] & 8 ? pen16(colour[2] >> 4) : s_pal_indYJK[ind | ((colour[2] >> 3) & 30)]; *ln++ = colour[2] & 8 ? pen16(colour[2] >> 4) : s_pal_indYJK[ind | ((colour[2] >> 3) & 30)]; *ln++ = colour[3] & 8 ? pen16(colour[3] >> 4) : s_pal_indYJK[ind | ((colour[3] >> 3) & 30)]; *ln++ = colour[3] & 8 ? pen16(colour[3] >> 4) : s_pal_indYJK[ind | ((colour[3] >> 3) & 30)]; nametbl_addr++; } } else if (m_cont_reg[2] & 0x40) { for (x=0;x<32;x++) { nametbl_addr++; colour = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); pen = pen256(colour); *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; *ln++ = pen; nametbl_addr++; } } else { for (x=0;x<256;x++) { colour = m_vram_space->read_byte(((nametbl_addr&1) << 16) | (nametbl_addr>>1)); pen = pen256(colour); *ln++ = pen; *ln++ = pen; nametbl_addr++; } } xx = (16 - m_offset_x) * 2; while (xx--) *ln++ = pen_bg; } void v99x8_device::mode_unknown(uint32_t *ln, int line) { const pen_t fg = pen16(m_cont_reg[7] >> 4); const pen_t bg = pen16(m_cont_reg[7] & 0x0f); for (int x = m_offset_x * 2; x > 0; x--) *ln++ = bg; for (int x = 512; x > 0; x--) *ln++ = fg; for (int x = (16 - m_offset_x) * 2; x > 0; x--) *ln++ = bg; } void v99x8_device::default_draw_sprite(uint32_t *ln, uint8_t *col) { int i; ln += m_offset_x * 2; for (i=0;i<256;i++) { if (col[i] & 0x80) { *ln++ = pen16(col[i] & 0x0f); *ln++ = pen16(col[i] & 0x0f); } else { ln += 2; } } } void v99x8_device::graphic5_draw_sprite(uint32_t *ln, uint8_t *col) { int i; ln += m_offset_x * 2; for (i=0;i<256;i++) { if (col[i] & 0x80) { *ln++ = pen16((col[i] >> 2) & 0x03); *ln++ = pen16(col[i] & 0x03); } else { ln += 2; } } } void v99x8_device::graphic7_draw_sprite(uint32_t *ln, uint8_t *col) { static const uint16_t g7_ind16[16] = { 0, 2, 192, 194, 48, 50, 240, 242, 482, 7, 448, 455, 56, 63, 504, 511 }; int i; ln += m_offset_x * 2; for (i=0;i<256;i++) { if (col[i] & 0x80) { rgb_t color = rgb_t(pal3bit(g7_ind16[col[i] & 0x0f] >> 6), pal3bit(g7_ind16[col[i] & 0x0f] >> 3), pal3bit(g7_ind16[col[i] & 0x0f])); *ln++ = uint32_t(color); *ln++ = uint32_t(color); } else { ln += 2; } } } void v99x8_device::sprite_mode1 (int line, uint8_t *col) { int attrtbl_addr, patterntbl_addr, pattern_addr; int x, y, p, height, c, p2, i, n, pattern; memset(col, 0, 256); // are sprites disabled? if (m_cont_reg[8] & 0x02) return; attrtbl_addr = (m_cont_reg[5] << 7) + (m_cont_reg[11] << 15); patterntbl_addr = (m_cont_reg[6] << 11); // 16x16 or 8x8 sprites height = (m_cont_reg[1] & 2) ? 16 : 8; // magnified sprites (zoomed) if (m_cont_reg[1] & 1) height *= 2; p2 = p = 0; while (1) { y = m_vram_space->read_byte(attrtbl_addr); if (y == 208) break; y = (y - m_cont_reg[23]) & 255; if (y > 208) y = -(~y&255); else y++; // if sprite in range, has to be drawn if ( (line >= y) && (line < (y + height) ) ) { if (p2 == 4) { // max maximum sprites per line! if ( !(m_stat_reg[0] & 0x40) ) m_stat_reg[0] = (m_stat_reg[0] & 0xa0) | 0x40 | p; break; } // get x x = m_vram_space->read_byte(attrtbl_addr + 1); if (m_vram_space->read_byte(attrtbl_addr + 3) & 0x80) x -= 32; // get pattern pattern = m_vram_space->read_byte(attrtbl_addr + 2); if (m_cont_reg[1] & 2) pattern &= 0xfc; n = line - y; pattern_addr = patterntbl_addr + pattern * 8 + ((m_cont_reg[1] & 1) ? n/2 : n); pattern = (m_vram_space->read_byte(pattern_addr) << 8) | m_vram_space->read_byte(pattern_addr+16); // get colour c = m_vram_space->read_byte(attrtbl_addr + 3) & 0x0f; // draw left part n = 0; while (1) { if (n == 0) pattern = m_vram_space->read_byte(pattern_addr); else if ( (n == 1) && (m_cont_reg[1] & 2) ) pattern = m_vram_space->read_byte(pattern_addr + 16); else break; n++; for (i=0;i<8;i++) { if (pattern & 0x80) { if ( (x >= 0) && (x < 256) ) { if (col[x] & 0x40) { // we have a collision! if (p2 < 4) m_stat_reg[0] |= 0x20; } if ( !(col[x] & 0x80) ) { if (c || (m_cont_reg[8] & 0x20) ) col[x] |= 0xc0 | c; else col[x] |= 0x40; } // if zoomed, draw another pixel if (m_cont_reg[1] & 1) { if (col[x+1] & 0x40) { // we have a collision! if (p2 < 4) m_stat_reg[0] |= 0x20; } if ( !(col[x+1] & 0x80) ) { if (c || (m_cont_reg[8] & 0x20) ) col[x+1] |= 0xc0 | c; else col[x+1] |= 0x80; } } } } if (m_cont_reg[1] & 1) x += 2; else x++; pattern <<= 1; } } p2++; } if (p >= 31) break; p++; attrtbl_addr += 4; } if ( !(m_stat_reg[0] & 0x40) ) m_stat_reg[0] = (m_stat_reg[0] & 0xa0) | p; } void v99x8_device::sprite_mode2 (int line, uint8_t *col) { int attrtbl_addr, patterntbl_addr, pattern_addr, colourtbl_addr; int x, i, y, p, height, c, p2, n, pattern, colourmask, first_cc_seen; memset(col, 0, 256); // are sprites disabled? if (m_cont_reg[8] & 0x02) return; attrtbl_addr = ( (m_cont_reg[5] & 0xfc) << 7) + (m_cont_reg[11] << 15); colourtbl_addr = ( (m_cont_reg[5] & 0xf8) << 7) + (m_cont_reg[11] << 15); patterntbl_addr = (m_cont_reg[6] << 11); colourmask = ( (m_cont_reg[5] & 3) << 3) | 0x7; // check this! // 16x16 or 8x8 sprites height = (m_cont_reg[1] & 2) ? 16 : 8; // magnified sprites (zoomed) if (m_cont_reg[1] & 1) height *= 2; p2 = p = first_cc_seen = 0; while (1) { y = vram_read(attrtbl_addr); if (y == 216) break; y = (y - m_cont_reg[23]) & 255; if (y > 216) y = -(~y&255); else y++; // if sprite in range, has to be drawn if ( (line >= y) && (line < (y + height) ) ) { if (p2 == 8) { // max maximum sprites per line! if ( !(m_stat_reg[0] & 0x40) ) m_stat_reg[0] = (m_stat_reg[0] & 0xa0) | 0x40 | p; break; } n = line - y; if (m_cont_reg[1] & 1) n /= 2; // get colour c = vram_read(colourtbl_addr + (((p&colourmask)*16) + n)); // don't draw all sprite with CC set before any sprites // with CC = 0 are seen on this line if (c & 0x40) { if (!first_cc_seen) goto skip_first_cc_set; } else first_cc_seen = 1; // get pattern pattern = vram_read(attrtbl_addr + 2); if (m_cont_reg[1] & 2) pattern &= 0xfc; pattern_addr = patterntbl_addr + pattern * 8 + n; pattern = (vram_read(pattern_addr) << 8) | vram_read(pattern_addr + 16); // get x x = vram_read(attrtbl_addr + 1); if (c & 0x80) x -= 32; n = (m_cont_reg[1] & 2) ? 16 : 8; while (n--) { for (i=0;i<=(m_cont_reg[1] & 1);i++) { if ( (x >= 0) && (x < 256) ) { if ( (pattern & 0x8000) && !(col[x] & 0x10) ) { if ( (c & 15) || (m_cont_reg[8] & 0x20) ) { if ( !(c & 0x40) ) { if (col[x] & 0x20) col[x] |= 0x10; else col[x] |= 0x20 | (c & 15); } else col[x] |= c & 15; col[x] |= 0x80; } } else { if ( !(c & 0x40) && (col[x] & 0x20) ) col[x] |= 0x10; } if ( !(c & 0x60) && (pattern & 0x8000) ) { if (col[x] & 0x40) { // sprite collision! if (p2 < 8) m_stat_reg[0] |= 0x20; } else col[x] |= 0x40; } x++; } } pattern <<= 1; } skip_first_cc_set: p2++; } if (p >= 31) break; p++; attrtbl_addr += 4; } if ( !(m_stat_reg[0] & 0x40) ) m_stat_reg[0] = (m_stat_reg[0] & 0xa0) | p; } const v99x8_device::v99x8_mode v99x8_device::s_modes[] = { { 0x02, &v99x8_device::mode_text1, &v99x8_device::default_border, nullptr, nullptr }, { 0x01, &v99x8_device::mode_multi, &v99x8_device::default_border, &v99x8_device::sprite_mode1, &v99x8_device::default_draw_sprite }, { 0x00, &v99x8_device::mode_graphic1, &v99x8_device::default_border, &v99x8_device::sprite_mode1, &v99x8_device::default_draw_sprite }, { 0x04, &v99x8_device::mode_graphic23, &v99x8_device::default_border, &v99x8_device::sprite_mode1, &v99x8_device::default_draw_sprite }, { 0x08, &v99x8_device::mode_graphic23, &v99x8_device::default_border, &v99x8_device::sprite_mode2, &v99x8_device::default_draw_sprite }, { 0x0c, &v99x8_device::mode_graphic4, &v99x8_device::default_border, &v99x8_device::sprite_mode2, &v99x8_device::default_draw_sprite }, { 0x10, &v99x8_device::mode_graphic5, &v99x8_device::graphic5_border, &v99x8_device::sprite_mode2, &v99x8_device::graphic5_draw_sprite }, { 0x14, &v99x8_device::mode_graphic6, &v99x8_device::default_border, &v99x8_device::sprite_mode2, &v99x8_device::default_draw_sprite }, { 0x1c, &v99x8_device::mode_graphic7, &v99x8_device::graphic7_border, &v99x8_device::sprite_mode2, &v99x8_device::graphic7_draw_sprite }, { 0x0a, &v99x8_device::mode_text2, &v99x8_device::default_border, nullptr, nullptr }, { 0xff, &v99x8_device::mode_unknown, &v99x8_device::default_border, nullptr, nullptr } }; void v99x8_device::set_mode() { int n,i; n = (((m_cont_reg[0] & 0x0e) << 1) | ((m_cont_reg[1] & 0x18) >> 3)); for (i=0;;i++) { if ( (s_modes[i].m == n) || (s_modes[i].m == 0xff) ) break; } // MZ: What happens when the mode is changed during command execution? // This is left unspecified in the docs. On a Geneve, experiments showed // that the command is not aborted (the CE flag is still 1) and runs for // about 90% of the nominal execution time, but VRAM is only correctly // filled up to the time of switching, and after that, isolated locations // within the normally affected area are changed, but inconsistently. // Obviously, it depends on the time when the switch happened. // This behavior occurs on every switch from a mode Graphics4 and higher // to another mode, e.g. also from Graphics7 to Graphics6. // Due to the lack of more information, we simply abort the command. if (m_vdp_engine && m_mode != i) { LOGMASKED(LOG_WARN, "Command aborted due to mode change\n"); m_vdp_engine = nullptr; m_stat_reg[2] &= 0xFE; } m_mode = i; } void v99x8_device::refresh_32(int line) { bool double_lines = false; uint8_t col[256]; uint32_t *ln, *ln2 = nullptr; if (m_cont_reg[9] & 0x08) { ln = &m_bitmap.pix(m_scanline*2+((m_stat_reg[2]>>1)&1)); } else { ln = &m_bitmap.pix(m_scanline*2); ln2 = &m_bitmap.pix(m_scanline*2+1); double_lines = true; } if ( !(m_cont_reg[1] & 0x40) || (m_stat_reg[2] & 0x40) ) { (this->*s_modes[m_mode].border_32)(ln); } else { (this->*s_modes[m_mode].visible_32)(ln, line); if (s_modes[m_mode].sprites) { (this->*s_modes[m_mode].sprites)(line, col); (this->*s_modes[m_mode].draw_sprite_32)(ln, col); } } if (double_lines) memcpy(ln2, ln, (512 + 32) * sizeof(*ln)); } void v99x8_device::refresh_line(int line) { pen_t ind16, ind256; ind16 = pen16(0); ind256 = pen256(0); if ( !(m_cont_reg[8] & 0x20) && (m_mode != V9938_MODE_GRAPHIC5) ) { set_pen16(0, pen16(m_cont_reg[7] & 0x0f)); set_pen256(0, pen256(m_cont_reg[7])); } refresh_32(line); if ( !(m_cont_reg[8] & 0x20) && (m_mode != V9938_MODE_GRAPHIC5) ) { set_pen16(0, ind16); set_pen256(0, ind256); } } /* From: awulms@inter.nl.net (Alex Wulms) *** About the HR/VR topic: this is how it works according to me: *** HR: HR is very straightforward: -HR=1 during 'display time' -HR=0 during 'horizontal border, horizontal retrace' I have put 'display time' and 'horizontal border, horizontal retrace' between quotes because HR does not only flip between 0 and 1 during the display of the 192/212 display lines, but also during the vertical border and during the vertical retrace. *** VR: VR is a little bit tricky -VR always gets set to 0 when the VDP starts with display line 0 -VR gets set to 1 when the VDP reaches display line (192 if LN=0) or (212 if LN=1) -The VDP displays contents of VRAM as long as VR=0 As a consequence of this behaviour, it is possible to program the famous overscan trick, where VRAM contents is shown in the borders: Generate an interrupt at line 230 (or so) and on this interrupt: set LN=1 Generate an interrupt at line 200 (or so) and on this interrupt: set LN=0 Repeat the above two steps *** The top/bottom border contents during overscan: On screen 0: 1) The VDP keeps increasing the name table address pointer during bottom border, vertical retrace and top border 2) The VDP resets the name table address pointer when the first display line is reached On the other screens: 1) The VDP keeps increasing the name table address pointer during the bottom border 2) The VDP resets the name table address pointer such that the top border contents connects up with the first display line. E.g., when the top border is 26 lines high, the VDP will take: 'logical' vram line TOPB000 256-26 ... TOPB025 256-01 DISPL000 000 ... DISPL211 211 BOTB000 212 ... BOTB024 236 *** About the horizontal interrupt All relevant definitions on a row: -FH: Bit 0 of status register 1 -IE1: Bit 4 of mode register 0 -IL: Line number in mode register 19 -DL: The line that the VDP is going to display (corrected for vertical scroll) -IRQ: Interrupt request line of VDP to Z80 At the *start* of every new line (display, bottom border, part of vertical display), the VDP does: -FH = (FH && IE1) || (IL==DL) After reading of status register 1 by the CPU, the VDP does: -FH = 0 Furthermore, the following is true all the time: -IRQ = FH && IE1 The resulting behaviour: When IE1=0: -FH will be set as soon as display of line IL starts -FH will be reset as soon as status register 1 is read -FH will be reset as soon as the next display line is reached When IE=1: -FH and IRQ will be set as soon as display line IL is reached -FH and IRQ will be reset as soon as status register 1 is read Another subtile result: If, while FH and IRQ are set, IE1 gets reset, the next happens: -IRQ is reset immediately (since IRQ is always FH && IE1) -FH will be reset as soon as display of the next line starts (unless the next line is line IL) *** About the vertical interrupt: Another relevant definition: -FV: Bit 7 of status register 0 -IE0: Bit 5 of mode register 1 I only know for sure the behaviour when IE0=1: -FV and IRQ will be set as soon as VR changes from 0 to 1 -FV and IRQ will be reset as soon as status register 0 is read A consequence is that NO vertical interrupts will be generated during the overscan trick, described in the VR section above. I do not know the behaviour of FV when IE0=0. That is the part that I still have to test. */ void v99x8_device::interrupt_start_vblank() { #if 0 if (machine.input().code_pressed (KEYCODE_D) ) { for (i=0;i<24;i++) osd_printf_debug ("R#%d = %02x\n", i, m_cont_reg[i]); } #endif // at every frame, vdp switches fields m_stat_reg[2] = (m_stat_reg[2] & 0xfd) | (~m_stat_reg[2] & 2); // color blinking if (!(m_cont_reg[13] & 0xf0)) m_blink = 0; else if (!(m_cont_reg[13] & 0x0f)) m_blink = 1; else { // both on and off counter are non-zero: timed blinking if (m_blink_count) m_blink_count--; if (!m_blink_count) { m_blink = !m_blink; if (m_blink) m_blink_count = (m_cont_reg[13] >> 4) * 10; else m_blink_count = (m_cont_reg[13] & 0x0f) * 10; } } } /*************************************************************************** Command unit ***************************************************************************/ /*************************************************************/ /** Completely rewritten by Alex Wulms: **/ /** - VDP Command execution 'in parallel' with CPU **/ /** - Corrected behaviour of VDP commands **/ /** - Made it easier to implement correct S7/8 mapping **/ /** by concentrating VRAM access in one single place **/ /** - Made use of the 'in parallel' VDP command exec **/ /** and correct timing. You must call the function **/ /** LoopVDP() from LoopZ80 in MSX.c. You must call it **/ /** exactly 256 times per screen refresh. **/ /** Started on : 11-11-1999 **/ /** Beta release 1 on: 9-12-1999 **/ /** Beta release 2 on: 20-01-2000 **/ /** - Corrected behaviour of VRM <-> Z80 transfer **/ /** - Improved performance of the code **/ /** Public release 1.0: 20-04-2000 **/ /*************************************************************/ #define VDP_VRMP5(MX, X, Y) ((!MX) ? (((Y&1023)<<7) + ((X&255)>>1)) : (EXPMEM_OFFSET + ((Y&511)<<7) + ((X&255)>>1))) #define VDP_VRMP6(MX, X, Y) ((!MX) ? (((Y&1023)<<7) + ((X&511)>>2)) : (EXPMEM_OFFSET + ((Y&511)<<7) + ((X&511)>>2))) //#define VDP_VRMP7(MX, X, Y) ((!MX) ? (((Y&511)<<8) + ((X&511)>>1)) : (EXPMEM_OFFSET + ((Y&255)<<8) + ((X&511)>>1))) #define VDP_VRMP7(MX, X, Y) ((!MX) ? (((X&2)<<15) + ((Y&511)<<7) + ((X&511)>>2)) : (EXPMEM_OFFSET + ((Y&511)<<7) + ((X&511)>>2))/*(EXPMEM_OFFSET + ((Y&255)<<8) + ((X&511)>>1))*/) //#define VDP_VRMP8(MX, X, Y) ((!MX) ? (((Y&511)<<8) + (X&255)) : (EXPMEM_OFFSET + ((Y&255)<<8) + (X&255))) #define VDP_VRMP8(MX, X, Y) ((!MX) ? (((X&1)<<16) + ((Y&511)<<7) + ((X>>1)&127)) : (EXPMEM_OFFSET + ((Y&511)<<7) + ((X>>1)&127))/*(EXPMEM_OFFSET + ((Y&255)<<8) + (X&255))*/) #define VDP_VRMP(M, MX, X, Y) VDPVRMP(M, MX, X, Y) #define VDP_POINT(M, MX, X, Y) VDPpoint(M, MX, X, Y) #define VDP_PSET(M, MX, X, Y, C, O) VDPpset(M, MX, X, Y, C, O) #define CM_ABRT 0x0 #define CM_POINT 0x4 #define CM_PSET 0x5 #define CM_SRCH 0x6 #define CM_LINE 0x7 #define CM_LMMV 0x8 #define CM_LMMM 0x9 #define CM_LMCM 0xA #define CM_LMMC 0xB #define CM_HMMV 0xC #define CM_HMMM 0xD #define CM_YMMM 0xE #define CM_HMMC 0xF /************************************************************* Many VDP commands are executed in some kind of loop but essentially, there are only a few basic loop structures that are re-used. We define the loop structures that are re-used here so that they have to be entered only once *************************************************************/ #define pre_loop \ while ((cnt-=delta) > 0) { #define post_loop \ } // Loop over DX, DY #define post__x_y(MX) \ if (!--ANX || ((ADX+=TX)&MX)) { \ if (!(--NY&1023) || (DY+=TY)==-1) \ break; \ else { \ ADX=DX; \ ANX=NX; \ } \ } \ post_loop // Loop over DX, SY, DY #define post__xyy(MX) \ if ((ADX+=TX)&MX) { \ if (!(--NY&1023) || (SY+=TY)==-1 || (DY+=TY)==-1) \ break; \ else \ ADX=DX; \ } \ post_loop // Loop over SX, DX, SY, DY #define post_xxyy(MX) \ if (!--ANX || ((ASX+=TX)&MX) || ((ADX+=TX)&MX)) { \ if (!(--NY&1023) || (SY+=TY)==-1 || (DY+=TY)==-1) \ break; \ else { \ ASX=SX; \ ADX=DX; \ ANX=NX; \ } \ } \ post_loop /*************************************************************/ /** Variables visible only in this module **/ /*************************************************************/ static const uint8_t Mask[4] = { 0x0F,0x03,0x0F,0xFF }; static const int PPB[4] = { 2,4,2,1 }; static const int PPL[4] = { 256,512,512,256 }; // SprOn SprOn SprOf SprOf // ScrOf ScrOn ScrOf ScrOn static const int srch_timing[8]={ 818, 1025, 818, 830, // ntsc 696, 854, 696, 684 // pal }; static const int line_timing[8]={ 1063, 1259, 1063, 1161, 904, 1026, 904, 953 }; static const int hmmv_timing[8]={ 439, 549, 439, 531, 366, 439, 366, 427 }; static const int lmmv_timing[8]={ 873, 1135, 873, 1056, 732, 909, 732, 854 }; static const int ymmm_timing[8]={ 586, 952, 586, 610, 488, 720, 488, 500 }; static const int hmmm_timing[8]={ 818, 1111, 818, 854, 684, 879, 684, 708 }; static const int lmmm_timing[8]={ 1160, 1599, 1160, 1172, 964, 1257, 964, 977 }; /** VDPVRMP() **********************************************/ /** Calculate addr of a pixel in vram **/ /*************************************************************/ inline int v99x8_device::VDPVRMP(uint8_t M,int MX,int X,int Y) { switch(M) { case 0: return VDP_VRMP5(MX,X,Y); case 1: return VDP_VRMP6(MX,X,Y); case 2: return VDP_VRMP7(MX,X,Y); case 3: return VDP_VRMP8(MX,X,Y); } return 0; } /** VDPpoint5() ***********************************************/ /** Get a pixel on screen 5 **/ /*************************************************************/ inline uint8_t v99x8_device::VDPpoint5(int MXS, int SX, int SY) { return (m_vram_space->read_byte(VDP_VRMP5(MXS, SX, SY)) >> (((~SX)&1)<<2) )&15; } /** VDPpoint6() ***********************************************/ /** Get a pixel on screen 6 **/ /*************************************************************/ inline uint8_t v99x8_device::VDPpoint6(int MXS, int SX, int SY) { return (m_vram_space->read_byte(VDP_VRMP6(MXS, SX, SY)) >> (((~SX)&3)<<1) )&3; } /** VDPpoint7() ***********************************************/ /** Get a pixel on screen 7 **/ /*************************************************************/ inline uint8_t v99x8_device::VDPpoint7(int MXS, int SX, int SY) { return (m_vram_space->read_byte(VDP_VRMP7(MXS, SX, SY)) >> (((~SX)&1)<<2) )&15; } /** VDPpoint8() ***********************************************/ /** Get a pixel on screen 8 **/ /*************************************************************/ inline uint8_t v99x8_device::VDPpoint8(int MXS, int SX, int SY) { return m_vram_space->read_byte(VDP_VRMP8(MXS, SX, SY)); } /** VDPpoint() ************************************************/ /** Get a pixel on a screen **/ /*************************************************************/ inline uint8_t v99x8_device::VDPpoint(uint8_t SM, int MXS, int SX, int SY) { switch(SM) { case 0: return VDPpoint5(MXS,SX,SY); case 1: return VDPpoint6(MXS,SX,SY); case 2: return VDPpoint7(MXS,SX,SY); case 3: return VDPpoint8(MXS,SX,SY); } return(0); } /** VDPpsetlowlevel() ****************************************/ /** Low level function to set a pixel on a screen **/ /** Make it inline to make it fast **/ /*************************************************************/ inline void v99x8_device::VDPpsetlowlevel(int addr, uint8_t CL, uint8_t M, uint8_t OP) { // If this turns out to be too slow, get a pointer to the address space // and work directly on it. uint8_t val = m_vram_space->read_byte(addr); switch (OP) { case 0: val = (val & M) | CL; break; case 1: val = val & (CL | M); break; case 2: val |= CL; break; case 3: val ^= CL; break; case 4: val = (val & M) | ~(CL | M); break; case 8: if (CL) val = (val & M) | CL; break; case 9: if (CL) val = val & (CL | M); break; case 10: if (CL) val |= CL; break; case 11: if (CL) val ^= CL; break; case 12: if (CL) val = (val & M) | ~(CL|M); break; default: LOGMASKED(LOG_WARN, "Invalid operation %d in pset\n", OP); } m_vram_space->write_byte(addr, val); } /** VDPpset5() ***********************************************/ /** Set a pixel on screen 5 **/ /*************************************************************/ inline void v99x8_device::VDPpset5(int MXD, int DX, int DY, uint8_t CL, uint8_t OP) { uint8_t SH = ((~DX)&1)<<2; VDPpsetlowlevel(VDP_VRMP5(MXD, DX, DY), CL << SH, ~(15<>6)&1)|(m_cont_reg[8]&2)|((m_cont_reg[9]<<1)&4)]); } /** SrchEgine()** ********************************************/ /** Search a dot **/ /*************************************************************/ void v99x8_device::srch_engine() { int SX=m_mmc.SX; int SY=m_mmc.SY; int TX=m_mmc.TX; int ANX=m_mmc.ANX; uint8_t CL=m_mmc.CL; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(srch_timing); cnt = m_vdp_ops_count; #define post_srch(MX) \ { m_stat_reg[2]|=0x10; /* Border detected */ break; } \ if ((SX+=TX) & MX) { m_stat_reg[2] &= 0xEF; /* Border not detected */ break; } switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop if ((VDPpoint5(MXD, SX, SY)==CL) ^ANX) post_srch(256) post_loop break; case V9938_MODE_GRAPHIC5: pre_loop if ((VDPpoint6(MXD, SX, SY)==CL) ^ANX) post_srch(512) post_loop break; case V9938_MODE_GRAPHIC6: pre_loop if ((VDPpoint7(MXD, SX, SY)==CL) ^ANX) post_srch(512) post_loop break; case V9938_MODE_GRAPHIC7: pre_loop if ((VDPpoint8(MXD, SX, SY)==CL) ^ANX) post_srch(256) post_loop break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2] &= 0xFE; m_vdp_engine = nullptr; // Update SX in VDP registers m_stat_reg[8] = SX & 0xFF; m_stat_reg[9] = (SX>>8) | 0xFE; } else { m_mmc.SX=SX; } } /** LineEgine()** ********************************************/ /** Draw a line **/ /*************************************************************/ void v99x8_device::line_engine() { int DX=m_mmc.DX; int DY=m_mmc.DY; int TX=m_mmc.TX; int TY=m_mmc.TY; int NX=m_mmc.NX; int NY=m_mmc.NY; int ASX=m_mmc.ASX; int ADX=m_mmc.ADX; uint8_t CL=m_mmc.CL; uint8_t LO=m_mmc.LO; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(line_timing); cnt = m_vdp_ops_count; #define post_linexmaj(MX) \ DX+=TX; \ if ((ASX-=NY)<0) { \ ASX+=NX; \ DY+=TY; \ } \ ASX&=1023; /* Mask to 10 bits range */\ if (ADX++==NX || (DX&MX)) \ break; \ post_loop #define post_lineymaj(MX) \ DY+=TY; \ if ((ASX-=NY)<0) { \ ASX+=NX; \ DX+=TX; \ } \ ASX&=1023; /* Mask to 10 bits range */\ if (ADX++==NX || (DX&MX)) \ break; \ post_loop if ((m_cont_reg[45]&0x01)==0) // X-Axis is major direction switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop VDPpset5(MXD, DX, DY, CL, LO); post_linexmaj(256) break; case V9938_MODE_GRAPHIC5: pre_loop VDPpset6(MXD, DX, DY, CL, LO); post_linexmaj(512) break; case V9938_MODE_GRAPHIC6: pre_loop VDPpset7(MXD, DX, DY, CL, LO); post_linexmaj(512) break; case V9938_MODE_GRAPHIC7: pre_loop VDPpset8(MXD, DX, DY, CL, LO); post_linexmaj(256) break; } else // Y-Axis is major direction switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop VDPpset5(MXD, DX, DY, CL, LO); post_lineymaj(256) break; case V9938_MODE_GRAPHIC5: pre_loop VDPpset6(MXD, DX, DY, CL, LO); post_lineymaj(512) break; case V9938_MODE_GRAPHIC6: pre_loop VDPpset7(MXD, DX, DY, CL, LO); post_lineymaj(512) break; case V9938_MODE_GRAPHIC7: pre_loop VDPpset8(MXD, DX, DY, CL, LO); post_lineymaj(256) break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; m_cont_reg[38]=DY & 0xFF; m_cont_reg[39]=(DY>>8) & 0x03; } else { m_mmc.DX=DX; m_mmc.DY=DY; m_mmc.ASX=ASX; m_mmc.ADX=ADX; } } /** lmmv_engine() *********************************************/ /** VDP -> Vram **/ /*************************************************************/ void v99x8_device::lmmv_engine() { int DX=m_mmc.DX; int DY=m_mmc.DY; int TX=m_mmc.TX; int TY=m_mmc.TY; int NX=m_mmc.NX; int NY=m_mmc.NY; int ADX=m_mmc.ADX; int ANX=m_mmc.ANX; uint8_t CL=m_mmc.CL; uint8_t LO=m_mmc.LO; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(lmmv_timing); cnt = m_vdp_ops_count; switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop VDPpset5(MXD, ADX, DY, CL, LO); post__x_y(256) break; case V9938_MODE_GRAPHIC5: pre_loop VDPpset6(MXD, ADX, DY, CL, LO); post__x_y(512) break; case V9938_MODE_GRAPHIC6: pre_loop VDPpset7(MXD, ADX, DY, CL, LO); post__x_y(512) break; case V9938_MODE_GRAPHIC7: pre_loop VDPpset8(MXD, ADX, DY, CL, LO); post__x_y(256) break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!NY) DY+=TY; m_cont_reg[38]=DY & 0xFF; m_cont_reg[39]=(DY>>8) & 0x03; m_cont_reg[42]=NY & 0xFF; m_cont_reg[43]=(NY>>8) & 0x03; } else { m_mmc.DY=DY; m_mmc.NY=NY; m_mmc.ANX=ANX; m_mmc.ADX=ADX; } } /** lmmm_engine() *********************************************/ /** Vram -> Vram **/ /*************************************************************/ void v99x8_device::lmmm_engine() { int SX=m_mmc.SX; int SY=m_mmc.SY; int DX=m_mmc.DX; int DY=m_mmc.DY; int TX=m_mmc.TX; int TY=m_mmc.TY; int NX=m_mmc.NX; int NY=m_mmc.NY; int ASX=m_mmc.ASX; int ADX=m_mmc.ADX; int ANX=m_mmc.ANX; uint8_t LO=m_mmc.LO; int MXS = m_mmc.MXS; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(lmmm_timing); cnt = m_vdp_ops_count; switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop VDPpset5(MXD, ADX, DY, VDPpoint5(MXS, ASX, SY), LO); post_xxyy(256) break; case V9938_MODE_GRAPHIC5: pre_loop VDPpset6(MXD, ADX, DY, VDPpoint6(MXS, ASX, SY), LO); post_xxyy(512) break; case V9938_MODE_GRAPHIC6: pre_loop VDPpset7(MXD, ADX, DY, VDPpoint7(MXS, ASX, SY), LO); post_xxyy(512) break; case V9938_MODE_GRAPHIC7: pre_loop VDPpset8(MXD, ADX, DY, VDPpoint8(MXS, ASX, SY), LO); post_xxyy(256) break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!NY) { SY+=TY; DY+=TY; } else if (SY==-1) DY+=TY; m_cont_reg[42]=NY & 0xFF; m_cont_reg[43]=(NY>>8) & 0x03; m_cont_reg[34]=SY & 0xFF; m_cont_reg[35]=(SY>>8) & 0x03; m_cont_reg[38]=DY & 0xFF; m_cont_reg[39]=(DY>>8) & 0x03; } else { m_mmc.SY=SY; m_mmc.DY=DY; m_mmc.NY=NY; m_mmc.ANX=ANX; m_mmc.ASX=ASX; m_mmc.ADX=ADX; } } /** lmcm_engine() *********************************************/ /** Vram -> CPU **/ /*************************************************************/ void v99x8_device::lmcm_engine() { if ((m_stat_reg[2]&0x80)!=0x80) { m_stat_reg[7]=m_cont_reg[44]=VDP_POINT(((m_mode >= 5) && (m_mode <= 8)) ? (m_mode-5) : 0, m_mmc.MXS, m_mmc.ASX, m_mmc.SY); m_vdp_ops_count-=get_vdp_timing_value(lmmv_timing); m_stat_reg[2]|=0x80; if (!--m_mmc.ANX || ((m_mmc.ASX+=m_mmc.TX)&m_mmc.MX)) { if (!(--m_mmc.NY & 1023) || (m_mmc.SY+=m_mmc.TY)==-1) { m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!m_mmc.NY) m_mmc.DY+=m_mmc.TY; m_cont_reg[42]=m_mmc.NY & 0xFF; m_cont_reg[43]=(m_mmc.NY>>8) & 0x03; m_cont_reg[34]=m_mmc.SY & 0xFF; m_cont_reg[35]=(m_mmc.SY>>8) & 0x03; } else { m_mmc.ASX=m_mmc.SX; m_mmc.ANX=m_mmc.NX; } } } } /** lmmc_engine() *********************************************/ /** CPU -> Vram **/ /*************************************************************/ void v99x8_device::lmmc_engine() { if ((m_stat_reg[2]&0x80)!=0x80) { uint8_t SM=((m_mode >= 5) && (m_mode <= 8)) ? (m_mode-5) : 0; m_stat_reg[7]=m_cont_reg[44]&=Mask[SM]; VDP_PSET(SM, m_mmc.MXD, m_mmc.ADX, m_mmc.DY, m_cont_reg[44], m_mmc.LO); m_vdp_ops_count-=get_vdp_timing_value(lmmv_timing); m_stat_reg[2]|=0x80; if (!--m_mmc.ANX || ((m_mmc.ADX+=m_mmc.TX)&m_mmc.MX)) { if (!(--m_mmc.NY&1023) || (m_mmc.DY+=m_mmc.TY)==-1) { m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!m_mmc.NY) m_mmc.DY+=m_mmc.TY; m_cont_reg[42]=m_mmc.NY & 0xFF; m_cont_reg[43]=(m_mmc.NY>>8) & 0x03; m_cont_reg[38]=m_mmc.DY & 0xFF; m_cont_reg[39]=(m_mmc.DY>>8) & 0x03; } else { m_mmc.ADX=m_mmc.DX; m_mmc.ANX=m_mmc.NX; } } } } /** hmmv_engine() *********************************************/ /** VDP --> Vram **/ /*************************************************************/ void v99x8_device::hmmv_engine() { int DX=m_mmc.DX; int DY=m_mmc.DY; int TX=m_mmc.TX; int TY=m_mmc.TY; int NX=m_mmc.NX; int NY=m_mmc.NY; int ADX=m_mmc.ADX; int ANX=m_mmc.ANX; uint8_t CL=m_mmc.CL; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(hmmv_timing); cnt = m_vdp_ops_count; switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop m_vram_space->write_byte(VDP_VRMP5(MXD, ADX, DY), CL); post__x_y(256) break; case V9938_MODE_GRAPHIC5: pre_loop m_vram_space->write_byte(VDP_VRMP6(MXD, ADX, DY), CL); post__x_y(512) break; case V9938_MODE_GRAPHIC6: pre_loop m_vram_space->write_byte(VDP_VRMP7(MXD, ADX, DY), CL); post__x_y(512) break; case V9938_MODE_GRAPHIC7: pre_loop m_vram_space->write_byte(VDP_VRMP8(MXD, ADX, DY), CL); post__x_y(256) break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!NY) DY+=TY; m_cont_reg[42]=NY & 0xFF; m_cont_reg[43]=(NY>>8) & 0x03; m_cont_reg[38]=DY & 0xFF; m_cont_reg[39]=(DY>>8) & 0x03; } else { m_mmc.DY=DY; m_mmc.NY=NY; m_mmc.ANX=ANX; m_mmc.ADX=ADX; } } /** hmmm_engine() *********************************************/ /** Vram -> Vram **/ /*************************************************************/ void v99x8_device::hmmm_engine() { int SX=m_mmc.SX; int SY=m_mmc.SY; int DX=m_mmc.DX; int DY=m_mmc.DY; int TX=m_mmc.TX; int TY=m_mmc.TY; int NX=m_mmc.NX; int NY=m_mmc.NY; int ASX=m_mmc.ASX; int ADX=m_mmc.ADX; int ANX=m_mmc.ANX; int MXS = m_mmc.MXS; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(hmmm_timing); cnt = m_vdp_ops_count; switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop m_vram_space->write_byte(VDP_VRMP5(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP5(MXS, ASX, SY))); post_xxyy(256) break; case V9938_MODE_GRAPHIC5: pre_loop m_vram_space->write_byte(VDP_VRMP6(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP6(MXS, ASX, SY))); post_xxyy(512) break; case V9938_MODE_GRAPHIC6: pre_loop m_vram_space->write_byte(VDP_VRMP7(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP7(MXS, ASX, SY))); post_xxyy(512) break; case V9938_MODE_GRAPHIC7: pre_loop m_vram_space->write_byte(VDP_VRMP8(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP8(MXS, ASX, SY))); post_xxyy(256) break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!NY) { SY+=TY; DY+=TY; } else if (SY==-1) DY+=TY; m_cont_reg[42]=NY & 0xFF; m_cont_reg[43]=(NY>>8) & 0x03; m_cont_reg[34]=SY & 0xFF; m_cont_reg[35]=(SY>>8) & 0x03; m_cont_reg[38]=DY & 0xFF; m_cont_reg[39]=(DY>>8) & 0x03; } else { m_mmc.SY=SY; m_mmc.DY=DY; m_mmc.NY=NY; m_mmc.ANX=ANX; m_mmc.ASX=ASX; m_mmc.ADX=ADX; } } /** ymmm_engine() *********************************************/ /** Vram -> Vram **/ /*************************************************************/ void v99x8_device::ymmm_engine() { int SY=m_mmc.SY; int DX=m_mmc.DX; int DY=m_mmc.DY; int TX=m_mmc.TX; int TY=m_mmc.TY; int NY=m_mmc.NY; int ADX=m_mmc.ADX; int MXD = m_mmc.MXD; int cnt; int delta; delta = get_vdp_timing_value(ymmm_timing); cnt = m_vdp_ops_count; switch (m_mode) { default: case V9938_MODE_GRAPHIC4: pre_loop m_vram_space->write_byte(VDP_VRMP5(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP5(MXD, ADX, SY))); post__xyy(256) break; case V9938_MODE_GRAPHIC5: pre_loop m_vram_space->write_byte(VDP_VRMP6(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP6(MXD, ADX, SY))); post__xyy(512) break; case V9938_MODE_GRAPHIC6: pre_loop m_vram_space->write_byte(VDP_VRMP7(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP7(MXD, ADX, SY))); post__xyy(512) break; case V9938_MODE_GRAPHIC7: pre_loop m_vram_space->write_byte(VDP_VRMP8(MXD, ADX, DY), m_vram_space->read_byte(VDP_VRMP8(MXD, ADX, SY))); post__xyy(256) break; } if ((m_vdp_ops_count=cnt)>0) { // Command execution done m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!NY) { SY+=TY; DY+=TY; } else if (SY==-1) DY+=TY; m_cont_reg[42]=NY & 0xFF; m_cont_reg[43]=(NY>>8) & 0x03; m_cont_reg[34]=SY & 0xFF; m_cont_reg[35]=(SY>>8) & 0x03; m_cont_reg[38]=DY & 0xFF; m_cont_reg[39]=(DY>>8) & 0x03; } else { m_mmc.SY=SY; m_mmc.DY=DY; m_mmc.NY=NY; m_mmc.ADX=ADX; } } /** hmmc_engine() *********************************************/ /** CPU -> Vram **/ /*************************************************************/ void v99x8_device::hmmc_engine() { if ((m_stat_reg[2]&0x80)!=0x80) { m_vram_space->write_byte(VDP_VRMP(((m_mode >= 5) && (m_mode <= 8)) ? (m_mode-5) : 0, m_mmc.MXD, m_mmc.ADX, m_mmc.DY), m_cont_reg[44]); m_vdp_ops_count -= get_vdp_timing_value(hmmv_timing); m_stat_reg[2]|=0x80; if (!--m_mmc.ANX || ((m_mmc.ADX+=m_mmc.TX)&m_mmc.MX)) { if (!(--m_mmc.NY&1023) || (m_mmc.DY+=m_mmc.TY)==-1) { m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; if (!m_mmc.NY) m_mmc.DY+=m_mmc.TY; m_cont_reg[42]=m_mmc.NY & 0xFF; m_cont_reg[43]=(m_mmc.NY>>8) & 0x03; m_cont_reg[38]=m_mmc.DY & 0xFF; m_cont_reg[39]=(m_mmc.DY>>8) & 0x03; } else { m_mmc.ADX=m_mmc.DX; m_mmc.ANX=m_mmc.NX; } } } } /** VDPWrite() ***********************************************/ /** Use this function to transfer pixel(s) from CPU to m_ **/ /*************************************************************/ void v99x8_device::cpu_to_vdp(uint8_t V) { m_stat_reg[2]&=0x7F; m_stat_reg[7]=m_cont_reg[44]=V; if(m_vdp_engine&&(m_vdp_ops_count>0)) (this->*m_vdp_engine)(); } /** VDPRead() ************************************************/ /** Use this function to transfer pixel(s) from VDP to CPU. **/ /*************************************************************/ uint8_t v99x8_device::vdp_to_cpu() { m_stat_reg[2]&=0x7F; if(m_vdp_engine&&(m_vdp_ops_count>0)) (this->*m_vdp_engine)(); return(m_cont_reg[44]); } /** report_vdp_command() ***************************************/ /** Report VDP Command to be executed **/ /*************************************************************/ void v99x8_device::report_vdp_command(uint8_t Op) { static const char *const Ops[16] = { "SET ","AND ","OR ","XOR ","NOT ","NOP ","NOP ","NOP ", "TSET","TAND","TOR ","TXOR","TNOT","NOP ","NOP ","NOP " }; static const char *const Commands[16] = { " ABRT"," ????"," ????"," ????","POINT"," PSET"," SRCH"," LINE", " LMMV"," LMMM"," LMCM"," LMMC"," HMMV"," HMMM"," YMMM"," HMMC" }; uint8_t CL, CM, LO; int SX,SY, DX,DY, NX,NY; // Fetch arguments CL = m_cont_reg[44]; SX = (m_cont_reg[32]+((int)m_cont_reg[33]<<8)) & 511; SY = (m_cont_reg[34]+((int)m_cont_reg[35]<<8)) & 1023; DX = (m_cont_reg[36]+((int)m_cont_reg[37]<<8)) & 511; DY = (m_cont_reg[38]+((int)m_cont_reg[39]<<8)) & 1023; NX = (m_cont_reg[40]+((int)m_cont_reg[41]<<8)) & 1023; NY = (m_cont_reg[42]+((int)m_cont_reg[43]<<8)) & 1023; CM = Op>>4; LO = Op&0x0F; LOGMASKED(LOG_COMMAND, "Opcode %02x %s-%s s=(%d,%d), d=(%d,%d), c=%02x, wh=[%d,%d]%s\n", Op, Commands[CM], Ops[LO], SX,SY, DX,DY, CL&0xff, m_cont_reg[45]&0x04? -NX:NX, m_cont_reg[45]&0x08? -NY:NY, m_cont_reg[45]&0x70? " on ExtVRAM":"" ); } /** VDPDraw() ************************************************/ /** Perform a given V9938 operation Op. **/ /*************************************************************/ uint8_t v99x8_device::command_unit_w(uint8_t Op) { // V9938 ops only work in SCREENs 5-8 if (m_mode<5 || m_mode>8) return(0); int SM = m_mode-5; // Screen mode index 0..3 m_mmc.CM = Op>>4; if ((m_mmc.CM & 0x0C) != 0x0C && m_mmc.CM != 0) // Dot operation: use only relevant bits of color m_stat_reg[7]=(m_cont_reg[44]&=Mask[SM]); // if(Verbose&0x02) report_vdp_command(Op); if ((m_vdp_engine != nullptr) && (m_mmc.CM != CM_ABRT)) LOGMASKED(LOG_WARN, "Command overrun; previous command not completed\n"); switch(Op>>4) { case CM_ABRT: m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; return 1; case CM_POINT: m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; m_stat_reg[7]=m_cont_reg[44]= VDP_POINT(SM, (m_cont_reg[45] & 0x10) != 0, m_cont_reg[32]+((int)m_cont_reg[33]<<8), m_cont_reg[34]+((int)m_cont_reg[35]<<8)); return 1; case CM_PSET: m_stat_reg[2]&=0xFE; m_vdp_engine=nullptr; VDP_PSET(SM, (m_cont_reg[45] & 0x20) != 0, m_cont_reg[36]+((int)m_cont_reg[37]<<8), m_cont_reg[38]+((int)m_cont_reg[39]<<8), m_cont_reg[44], Op&0x0F); return 1; case CM_SRCH: m_vdp_engine=&v99x8_device::srch_engine; break; case CM_LINE: m_vdp_engine=&v99x8_device::line_engine; break; case CM_LMMV: m_vdp_engine=&v99x8_device::lmmv_engine; break; case CM_LMMM: m_vdp_engine=&v99x8_device::lmmm_engine; break; case CM_LMCM: m_vdp_engine=&v99x8_device::lmcm_engine; break; case CM_LMMC: m_vdp_engine=&v99x8_device::lmmc_engine; break; case CM_HMMV: m_vdp_engine=&v99x8_device::hmmv_engine; break; case CM_HMMM: m_vdp_engine=&v99x8_device::hmmm_engine; break; case CM_YMMM: m_vdp_engine=&v99x8_device::ymmm_engine; break; case CM_HMMC: m_vdp_engine=&v99x8_device::hmmc_engine; break; default: LOGMASKED(LOG_WARN, "Unrecognized opcode %02Xh\n",Op); return(0); } // Fetch unconditional arguments m_mmc.SX = (m_cont_reg[32]+((int)m_cont_reg[33]<<8)) & 511; m_mmc.SY = (m_cont_reg[34]+((int)m_cont_reg[35]<<8)) & 1023; m_mmc.DX = (m_cont_reg[36]+((int)m_cont_reg[37]<<8)) & 511; m_mmc.DY = (m_cont_reg[38]+((int)m_cont_reg[39]<<8)) & 1023; m_mmc.NY = (m_cont_reg[42]+((int)m_cont_reg[43]<<8)) & 1023; m_mmc.TY = m_cont_reg[45]&0x08? -1:1; m_mmc.MX = PPL[SM]; m_mmc.CL = m_cont_reg[44]; m_mmc.LO = Op&0x0F; m_mmc.MXS = (m_cont_reg[45] & 0x10) != 0; m_mmc.MXD = (m_cont_reg[45] & 0x20) != 0; // Argument depends on uint8_t or dot operation if ((m_mmc.CM & 0x0C) == 0x0C) { m_mmc.TX = m_cont_reg[45]&0x04? -PPB[SM]:PPB[SM]; m_mmc.NX = ((m_cont_reg[40]+((int)m_cont_reg[41]<<8)) & 1023)/PPB[SM]; } else { m_mmc.TX = m_cont_reg[45]&0x04? -1:1; m_mmc.NX = (m_cont_reg[40]+((int)m_cont_reg[41]<<8)) & 1023; } // X loop variables are treated specially for LINE command if (m_mmc.CM == CM_LINE) { m_mmc.ASX=((m_mmc.NX-1)>>1); m_mmc.ADX=0; } else { m_mmc.ASX = m_mmc.SX; m_mmc.ADX = m_mmc.DX; } // NX loop variable is treated specially for SRCH command if (m_mmc.CM == CM_SRCH) m_mmc.ANX=(m_cont_reg[45]&0x02)!=0; // Do we look for "==" or "!="? else m_mmc.ANX = m_mmc.NX; // Command execution started m_stat_reg[2]|=0x01; // Start execution if we still have time slices if(m_vdp_engine&&(m_vdp_ops_count>0)) (this->*m_vdp_engine)(); // Operation successfully initiated return(1); } /** LoopVDP() ************************************************ Run X steps of active VDP command *************************************************************/ void v99x8_device::update_command() { if(m_vdp_ops_count<=0) { m_vdp_ops_count+=13662; if(m_vdp_engine&&(m_vdp_ops_count>0)) (this->*m_vdp_engine)(); } else { m_vdp_ops_count=13662; if(m_vdp_engine) (this->*m_vdp_engine)(); } } void v99x8_device::device_post_load() // TODO: is there a better way to restore this? { switch(m_mmc.CM) { case CM_ABRT: case CM_POINT: case CM_PSET: m_vdp_engine=nullptr; break; case CM_SRCH: m_vdp_engine=&v99x8_device::srch_engine; break; case CM_LINE: m_vdp_engine=&v99x8_device::line_engine; break; case CM_LMMV: m_vdp_engine=&v99x8_device::lmmv_engine; break; case CM_LMMM: m_vdp_engine=&v99x8_device::lmmm_engine; break; case CM_LMCM: m_vdp_engine=&v99x8_device::lmcm_engine; break; case CM_LMMC: m_vdp_engine=&v99x8_device::lmmc_engine; break; case CM_HMMV: m_vdp_engine=&v99x8_device::hmmv_engine; break; case CM_HMMM: m_vdp_engine=&v99x8_device::hmmm_engine; break; case CM_YMMM: m_vdp_engine=&v99x8_device::ymmm_engine; break; case CM_HMMC: m_vdp_engine=&v99x8_device::hmmc_engine; break; } }