-- VRAM wait-state measurement. -- -- Assembles a tight 68000 loop into RAM and gets it executed by hooking the -- interrupt vectors -- MAME's m68k ignores writes to the rPC state entry, so -- the CPU cannot simply be pointed at the code. Whichever interrupt fires -- first enters the routine, which masks interrupts, runs the loop, writes a -- sentinel and RTEs. -- -- Loop body is 8 unrolled `tst.w .L` + subq.l + bne.s: -- nominal 68000 cost = 8*18 + 8 + 10 = 162 cycles/iteration at zero wait. -- Running the identical loop against main memory and against VRAM isolates the -- per-access wait: the only difference between runs is the address touched. -- -- TARGET_ADDR and ITER_COUNT are substituted by the harness. local cpu = manager.machine.devices[":maincpu"] local mem = cpu.spaces["program"] local frame = 0 local CODE = 0x010000 local SENTINEL = 0x011000 local TARGET = TARGET_ADDR local ITERS = ITER_COUNT local UNROLL = 8 local NOMINAL = 8 * 16 + 8 + 10 -- 146: TST.W (xxx).L = 4 + 12 ea local armed, t0 = false, nil local function w16(a, v) mem:write_u16(a, v) end local function assemble() local p = CODE w16(p, 0x007C); p = p + 2 -- ori.w #$0700,sr w16(p, 0x0700); p = p + 2 w16(p, 0x203C); p = p + 2 -- move.l #ITERS,d0 w16(p, (ITERS >> 16) & 0xFFFF); p = p + 2 w16(p, ITERS & 0xFFFF); p = p + 2 local loop = p for _ = 1, UNROLL do w16(p, 0x4A79); p = p + 2 -- tst.w .L w16(p, (TARGET >> 16) & 0xFFFF); p = p + 2 w16(p, TARGET & 0xFFFF); p = p + 2 end w16(p, 0x5380); p = p + 2 -- subq.l #1,d0 w16(p, 0x6600 | ((loop - (p + 2)) & 0xFF)); p = p + 2 -- bne.s loop w16(p, 0x33FC); p = p + 2 -- move.w #$BEEF,SENTINEL.L w16(p, 0xBEEF); p = p + 2 w16(p, (SENTINEL >> 16) & 0xFFFF); p = p + 2 w16(p, SENTINEL & 0xFFFF); p = p + 2 w16(p, 0x4E73) -- rte end emu.register_frame_done(function() frame = frame + 1 if frame == 240 then w16(SENTINEL, 0x0000) assemble() if mem:read_u16(CODE) ~= 0x007C then io.write("WAITPROBE FAIL code did not land in RAM\n") io.flush(); manager.machine:exit(); return end -- Point the autovectors ($60-$7F) and the MFP vector block ($100-$13F) -- at the routine; whichever interrupt fires first runs it once. for a = 0x60, 0x7C, 4 do mem:write_u32(a, CODE) end for a = 0x100, 0x13C, 4 do mem:write_u32(a, CODE) end t0 = manager.machine.time:as_double() armed = true elseif armed and mem:read_u16(SENTINEL) == 0xBEEF then local dt = manager.machine.time:as_double() - t0 local cycles = dt * 10000000.0 local periter = cycles / ITERS io.write(string.format( "WAITPROBE target=%06X iters=%d sec=%.6f cyc=%.0f per_iter=%.3f extra_per_access=%.4f\n", TARGET, ITERS, dt, cycles, periter, (periter - NOMINAL) / UNROLL)) io.flush() manager.machine:exit() elseif frame > 2000 then io.write("WAITPROBE TIMEOUT (routine never entered)\n") io.flush() manager.machine:exit() end end)