82 lines
3.2 KiB
Lua
82 lines
3.2 KiB
Lua
-- 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 <target>.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 <TARGET>.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)
|