--- a/src/mame/sharp/x68k.h 2026-08-04 16:54:53.872522860 -0500 +++ b/src/mame/sharp/x68k.h 2026-08-04 16:54:53.876522842 -0500 @@ -195,6 +195,12 @@ bool m_exp_nmi[2]{}; uint8_t m_current_ipl = 0; int m_led_state = 0; + // Extra cycles charged per VRAM access, over and above a main-memory + // access, in 16.16 fixed point. Set from measured values in machine_start + // for the machines those measurements cover; 0 (unmodelled) otherwise. + uint32_t m_gvram_wait = 0; + uint32_t m_tvram_wait = 0; + uint32_t m_vram_wait_accum = 0; // 16.16 carry between accesses emu_timer* m_mouse_timer = nullptr; emu_timer* m_led_timer = nullptr; unsigned char m_scc_prev = 0; @@ -265,6 +271,15 @@ uint16_t exp_r(offs_t offset, uint16_t mem_mask = ~0); void exp_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + // VRAM wait states. The X68000's video RAM is slower than main memory; + // MAME charged nothing for it, so VRAM-bound code ran optimistically + // fast. These trampolines charge the difference and forward to the CRTC. + void charge_vram_wait(uint32_t increment); + uint16_t gvram_wait_r(offs_t offset); + void gvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + uint16_t tvram_wait_r(offs_t offset); + void tvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); + uint16_t spritereg_r(offs_t offset); void spritereg_w(offs_t offset, uint16_t data, uint16_t mem_mask = ~0); uint16_t spriteram_r(offs_t offset); --- a/src/mame/sharp/x68k.cpp 2026-08-04 16:54:53.871522864 -0500 +++ b/src/mame/sharp/x68k.cpp 2026-08-04 16:54:53.876522842 -0500 @@ -992,12 +992,80 @@ // Is this an undocumented MB89352 feature, an ASIC register, an original code bug or a bad dump? } +// VRAM wait states. +// +// The 68000 in an X68000 does not reach video RAM as fast as it reaches main +// memory, and MAME modelled no wait at all -- so any VRAM-bound inner loop ran +// optimistically fast, which is exactly the code a graphics library cares +// about. The figures used here are Makoto Kamada's real-hardware measurements +// distributed with XEiJ (credited there to uchopon, tnb and ita), taken by +// timing tst.w against each region on five 10 MHz X68000 variants: +// +// region tst.w time (us) derived wait (cycles) +// main memory 1.642 .. 1.663 0.123 +// GVRAM 1.734 .. 1.770 1.145 +// TVRAM 1.829 .. 1.850 2.046 +// +// MAME charges nothing for main memory, so what is added here is the wait +// RELATIVE to main memory: 1.145 - 0.123 = 1.022 cycles for GVRAM and +// 2.046 - 0.123 = 1.923 for TVRAM. Cross-check against the raw measurements: +// GVRAM - main memory averages ~1.03 cycles and TVRAM - main memory ~1.92 +// across the five machines, which agrees. +// +// Main memory's own 0.123 cycles (DRAM refresh) is deliberately NOT modelled: +// it would mean interposing on the hottest path in the driver to recover ~0.1 +// cycles per access, and it is a uniform offset that does not change the +// relative cost of VRAM versus RAM, which is the thing being measured. +// +// The wait is fractional, so it accumulates in 16.16 fixed point and whole +// cycles are charged as they carry out -- the same technique the apple2gs +// driver uses for its 1 MHz cycles (see slow_cycle() there). +#define X68K_GVRAM_WAIT 0x000105a2 // 1.022 cycles, 16.16 +#define X68K_TVRAM_WAIT 0x0001ec4a // 1.923 cycles, 16.16 + +void x68k_state::charge_vram_wait(uint32_t increment) +{ + // side_effects_disabled() covers debugger and Lua reads, which must not + // perturb the timing they are measuring. + if (increment == 0 || machine().side_effects_disabled()) + return; + m_vram_wait_accum += increment; + int const cycles = m_vram_wait_accum >> 16; + m_vram_wait_accum &= 0xffff; + if (cycles != 0) + m_maincpu->adjust_icount(-cycles); +} + +uint16_t x68k_state::gvram_wait_r(offs_t offset) +{ + charge_vram_wait(m_gvram_wait); + return m_crtc->gvram_r(offset); +} + +void x68k_state::gvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + charge_vram_wait(m_gvram_wait); + m_crtc->gvram_w(offset, data, mem_mask); +} + +uint16_t x68k_state::tvram_wait_r(offs_t offset) +{ + charge_vram_wait(m_tvram_wait); + return m_crtc->tvram_r(offset); +} + +void x68k_state::tvram_wait_w(offs_t offset, uint16_t data, uint16_t mem_mask) +{ + charge_vram_wait(m_tvram_wait); + m_crtc->tvram_w(offset, data, mem_mask); +} + void x68k_state::x68k_base_map(address_map &map) { map(0x000000, 0xbffffb).rw(FUNC(x68k_state::emptyram_r), FUNC(x68k_state::emptyram_w)); map(0xbffffc, 0xbfffff).rw(FUNC(x68k_state::rom0_r), FUNC(x68k_state::rom0_w)); - map(0xc00000, 0xdfffff).rw(m_crtc, FUNC(x68k_crtc_device::gvram_r), FUNC(x68k_crtc_device::gvram_w)); - map(0xe00000, 0xe7ffff).rw(m_crtc, FUNC(x68k_crtc_device::tvram_r), FUNC(x68k_crtc_device::tvram_w)); + map(0xc00000, 0xdfffff).rw(FUNC(x68k_state::gvram_wait_r), FUNC(x68k_state::gvram_wait_w)); + map(0xe00000, 0xe7ffff).rw(FUNC(x68k_state::tvram_wait_r), FUNC(x68k_state::tvram_wait_w)); map(0xe80000, 0xe81fff).rw(m_crtc, FUNC(x68k_crtc_device::crtc_r), FUNC(x68k_crtc_device::crtc_w)); map(0xe82400, 0xe83fff).rw(FUNC(x68k_state::vid_r), FUNC(x68k_state::vid_w)); map(0xe84000, 0xe85fff).rw(m_hd63450, FUNC(hd63450_device::read), FUNC(hd63450_device::write)); @@ -1155,6 +1223,24 @@ m_spriteram = (uint16_t*)(memregion("user1")->base()); space.install_ram(0x000000,m_ram->size()-1,m_ram->pointer()); + // VRAM wait states, applied only to the machine the measurements cover. + // The published figures are for a 10 MHz 68000 X68000; XEiJ's table also + // lists a 25 MHz X68030 (GVRAM 6.377, TVRAM 6.623) but those machines are + // MACHINE_NOT_WORKING here and cannot be validated, so they are left + // unmodelled rather than given invented numbers. + if (m_maincpu->clock() == 10'000'000) + { + m_gvram_wait = X68K_GVRAM_WAIT; + m_tvram_wait = X68K_TVRAM_WAIT; + } + else + { + m_gvram_wait = 0; + m_tvram_wait = 0; + } + m_vram_wait_accum = 0; + save_item(NAME(m_vram_wait_accum)); + // start mouse timer m_mouse_timer->adjust(attotime::zero, 0, attotime::from_msec(1)); // a guess for now m_mouse.inputtype = 0;