Put the SPC on the 68000, and find P4 was blocked on a file nobody needed
ROADMAP P4, first half. Every byte the player has ever consumed was placed in emulated RAM by a host: decode.lua preloaded a container, stream.lua answered a mailbox at a modelled rate. src/player/scsi.i selects a SCSI target on a real MB89352 and issues READ(10) itself -- 4,096 B from LBA 0 and 2,048 B from LBA 1000, both byte-for-byte against the host's copy of the same volume, with no IOCS and no host in the transfer path. The non-zero LBA is the half that matters: a driver that emits a malformed LBA field still passes block 0, because zero is what a malformed field usually is. P4 was recorded as blocked in this tree and was not. Session 21's handoff said MAME's x68000 has no MB89352 path; -exp1 cz6bs1 instantiates one next to the HD63450, and FINDINGS 32.4 had read that card's DMA glue in session 9. The session-21 note is a regression in the record, not a discovery. What is genuinely absent is the 8 KB scsiexrom.bin MAME requires to INSTANTIATE the card and the player never executes -- driving the SPC registers directly has been the plan since BENCHMARK item 4 in session 2 -- so scsi_run.sh supplies a zero-filled placeholder on its own rompath, leaves the user's romset alone, and lets MAME print WRONG CHECKSUMS as it should. B3 is untouched: it wants that ROM's bytes disassembled and a blank one has none. The register map is measured, not inferred, and it corrects MAME's own documentation. The probe walks $EA0000..$EA003F one address at a time with a bus-error handler that records the fault and steps the index, because a sequential dump reports the first hole as the answer -- the earlier version took a bus error at $EA0006 and knew nothing about the other 57. 60 of 64 answer; the two holes are exactly the TMOD and EXBF the MB89352 omits and the MB87030 has. MAME leaves HOLES and does not shift the later indices down, which its own device summary claims it does, and that is what keeps DREG at $EA0015. The data register is DMA-only here and a PIO write vanishes. x68k_scsiext.cpp glues $EA0015 and nothing else, and with exown() asserted and DRQ low the byte is discarded: no error bit, no status change, no interrupt. Quieting all four DMAC channels does not change it. Measured rather than reasoned about -- write $5A, read back $00 with the FIFO still empty -- because ten command bytes vanishing without trace looks exactly like a target refusing a command, which is how it first presented. So every transfer runs the SPC in DMA mode and the CPU moves the bytes through the DMAC's own door. That costs the argument something, and it is easy to overclaim here: with exown asserted at idle MAME cannot distinguish a CPU-driven byte at $EA0015 from a DMAC-driven one. This shows the DATA PATH and cannot by itself show that the HD63450 is driving it, which is precisely what ROADMAP calls P4's first job. Whether a real CZ-6BS1 also refuses PIO there is not settled; it is a property of MAME's model and it wants a board. W did not move by one clock, and could not have. MAME's device models are functional rather than transfer-timing accurate and 42.5 reads its DMAC configured in wall-clock attotimes, so this is BENCHMARK Tier 1 -- does the read path work -- and never Tier 2. W is still the largest open number here. Five bugs, four of them silent, recorded in 57.5 because the pattern is the finding: a chain of rol.l #8 that loaded a transfer counter of ZERO from a count of 10; a byte handed to a FIFO mistaken for a byte on the bus; a fixed phase sequence where the bus decides the order; the discarded PIO write; and an initiator that must drop ACK and only then release the bus. The last appeared only once there were TWO reads -- one passed byte-exact and every conclusion from it was sound, and the second could not select. A player issues one command per record, so that failure would have been universal in the ring and invisible in a one-read demonstration. No decoder code changed; decode.bin is still 1,296 B at the same MD5. check.sh gains a SCSI stage that builds the volume out of the same stream_disk.bin the ring rig reads, gates the register window at 60 of 64 and both reads byte-exact, and skips when chdman is absent. ALL GREEN before and after. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
-- Drive src/player/scsigate.s: can the 68000 reach the MB89352? (ROADMAP P4)
|
||||
--
|
||||
-- Bus errors are EXPECTED and are data: the gate probes one address at a time
|
||||
-- and survives each fault, so a hole in the decode costs an entry in the map
|
||||
-- rather than the rest of the run. See src/player/scsigate.s.
|
||||
--
|
||||
-- THE APPARATUS, STATED UP FRONT. This runs `x68000 -exp1 cz6bs1`, which is
|
||||
-- the board FINDINGS 42.5 says to benchmark and never `x68ksupr` (whose
|
||||
-- internal SCSI is PIO-only in MAME -- `// TODO: duplicate DMA glue from
|
||||
-- CZ-6BS1`). MAME refuses to instantiate the card without an 8 KB
|
||||
-- `scsiexrom.bin`, which is not in this tree; the rig supplies a ZERO-FILLED
|
||||
-- placeholder on a SEPARATE rompath so the user's own romset is untouched.
|
||||
--
|
||||
-- THAT PLACEHOLDER IS HONEST HERE AND WOULD NOT BE EVERYWHERE. The CZ-6BS1's
|
||||
-- boot ROM exists to make the card bootable through IOCS. This player drives
|
||||
-- the SPC registers directly and never executes a byte of it -- that was
|
||||
-- already the plan in docs/BENCHMARK.md item 4, long before the ROM was missing
|
||||
-- -- so a blank one changes nothing this rig measures. What it WOULD change is
|
||||
-- anything that booted from the card or called SCSI IOCS; do not reuse it for
|
||||
-- that. The run prints the substitution rather than burying it.
|
||||
--
|
||||
-- WHAT A GREEN RUN MEANS, and what it does not. It means the 68000 reaches the
|
||||
-- SPC and the register map is the one the driver will be written against. It
|
||||
-- says NOTHING about rate: MAME's device models are functional, not
|
||||
-- transfer-timing accurate (docs/BENCHMARK.md), and 42.5 shows its DMAC is
|
||||
-- configured in wall-clock attotimes rather than per-operand cycles. `W` is
|
||||
-- untouched by anything here.
|
||||
local M = manager.machine
|
||||
local SP = M.devices[":maincpu"].spaces["program"]
|
||||
local function P(s) print("[SCSI] "..s) end
|
||||
local function T() local t=M.time; return t.seconds + t.attoseconds/1e18 end
|
||||
|
||||
local SCFLAG, SCN, SCVAL, SCOK, SCTMP, SCTMPOK =
|
||||
0x18080, 64, 0x18100, 0x18140, 0x180D0, 0x180D4
|
||||
local SCRD, SCDST, SCBLKS = 0x180D8, 0x20000, 8
|
||||
local SC_ERR, SC_STAT, SC_PH = 0x18200, 0x18204, 0x18208
|
||||
local ERRNAME = {[0]="OK", "SELECTION TIMEOUT -- no target answered",
|
||||
"UNEXPECTED PHASE", "POLL TIMEOUT -- a phase never arrived",
|
||||
"NON-ZERO SCSI STATUS"}
|
||||
local DISK = os.getenv("DLX_SCSI_IMG") or "dlxdisk.img"
|
||||
|
||||
local code do local f=io.open("scsigate.bin","rb"); code=f:read("a"); f:close() end
|
||||
|
||||
local st = "boot"
|
||||
SUB = emu.add_machine_frame_notifier(function()
|
||||
local ok, err = pcall(function()
|
||||
if st == "boot" then
|
||||
if T() < 3.0 then return end
|
||||
for i = 1, #code do SP:write_u8(0x10000+i-1, string.byte(code,i)) end
|
||||
SP:write_u32(SCFLAG, 0)
|
||||
local cpu = M.devices[":maincpu"]
|
||||
cpu.state["SR"].value = 0x2700
|
||||
cpu.state["SP"].value = 0x8000
|
||||
cpu.state["PC"].value = 0x10000
|
||||
P(string.format("scsigate.bin=%d B loaded at $10000", #code))
|
||||
st = "wait"; return
|
||||
end
|
||||
if st == "wait" then
|
||||
local fl = SP:read_u32(SCFLAG)
|
||||
if fl ~= 1 then
|
||||
if T() > 30 then P("TIMEOUT: the gate never finished"); P("done"); M:exit() end
|
||||
return
|
||||
end
|
||||
-- THE MAP, address by address. A dead address is a bus error the
|
||||
-- gate survived, not a run that stopped.
|
||||
local live, dead = {}, {}
|
||||
for i = 0, SCN-1 do
|
||||
if SP:read_u8(SCOK+i) == 1 then live[#live+1] = i else dead[#dead+1] = i end
|
||||
end
|
||||
for row = 0, (SCN/16)-1 do
|
||||
local t = {}
|
||||
for i = 0, 15 do
|
||||
local a = row*16 + i
|
||||
t[#t+1] = (SP:read_u8(SCOK+a) == 1)
|
||||
and string.format("%02X", SP:read_u8(SCVAL+a)) or "--"
|
||||
end
|
||||
P(string.format(" $EA%04X: %s", row*16, table.concat(t, " ")))
|
||||
end
|
||||
P(string.format("ANSWERED %d of %d addresses; \"--\" is a bus error.",
|
||||
#live, SCN))
|
||||
-- The lane question, decided by which addresses answer.
|
||||
local odd_live, even_live = 0, 0
|
||||
for _, a in ipairs(live) do
|
||||
if a % 2 == 1 then odd_live = odd_live + 1 else even_live = even_live + 1 end
|
||||
end
|
||||
P(string.format("LANES: %d odd addresses answer, %d even.", odd_live, even_live))
|
||||
if SP:read_u32(SCTMPOK) == 1 then
|
||||
P(string.format("TEMP writeback ($EA0017): wrote $A5, read back $%02X",
|
||||
SP:read_u32(SCTMP) & 0xFF))
|
||||
else
|
||||
P("TEMP writeback ($EA0017): BUS ERROR -- not a writable register here.")
|
||||
end
|
||||
P(string.format("DREG writeback ($EA0015): wrote $5A, SSTS then $%02X "
|
||||
.."(FIFO %s), read back $%02X",
|
||||
SP:read_u32(0x180E0) & 0xFF,
|
||||
((SP:read_u32(0x180E0) & 1) == 1) and "EMPTY -- the write "
|
||||
.."never reached dreg_w" or "has a byte",
|
||||
SP:read_u32(0x180DC) & 0xFF))
|
||||
-- The trace, whatever happened.
|
||||
local ntr = SP:read_u32(0x18230)
|
||||
if ntr > 0 then
|
||||
P("trace (SSTS PSNS INTS SERR | TCH TCM TCL | where):")
|
||||
for i = 0, ntr-1 do
|
||||
local b = {}
|
||||
for k = 0, 7 do b[k+1] = string.format("%02X", SP:read_u8(0x18240+i*8+k)) end
|
||||
local WH = {[0]="init", "selected", "INTS cleared", "phase loop saw REQ",
|
||||
"TRANSFER issued (out)", "bytes handed over",
|
||||
"after xfer wait", "TRANSFER issued (in)",
|
||||
"bytes taken from FIFO", "after IN xfer wait",
|
||||
"after bus release"}
|
||||
P(string.format(" %2d: %s %s %s %s | %s %s %s | %s", i,
|
||||
b[1], b[2], b[3], b[4], b[5], b[6], b[7],
|
||||
WH[tonumber(b[8], 16)] or b[8]))
|
||||
end
|
||||
end
|
||||
-- THE READ. Verified against the host's copy of the same image: a
|
||||
-- transport that returns the wrong bytes without saying so is exactly the
|
||||
-- failure a checksum-free ring cannot survive (49.2).
|
||||
local rd = SP:read_u32(SCRD)
|
||||
local e = SP:read_u32(0x180E8)
|
||||
if rd ~= 0 or e ~= 0 then
|
||||
P(string.format("scsi_read FAILED: err=%d (%s), status=$%02X, phase=%d",
|
||||
e, ERRNAME[e] or "?", SP:read_u32(SC_STAT) & 0xFF,
|
||||
SP:read_u32(SC_PH)))
|
||||
else
|
||||
local f = io.open(DISK, "rb")
|
||||
if not f then
|
||||
P("scsi_read returned OK but "..DISK.." is not here to check it against.")
|
||||
else
|
||||
local want = f:read(SCBLKS * 512); f:close()
|
||||
local bad, first = 0, nil
|
||||
for i = 1, #want do
|
||||
if SP:read_u8(SCDST + i - 1) ~= string.byte(want, i) then
|
||||
bad = bad + 1; first = first or (i-1)
|
||||
end
|
||||
end
|
||||
if bad == 0 then
|
||||
P(string.format("READ(10) OK: %d B from LBA 0 match %s byte for byte.",
|
||||
#want, DISK))
|
||||
else
|
||||
P(string.format("READ(10) WRONG: %d of %d bytes differ, first at +%d.",
|
||||
bad, #want, first))
|
||||
end
|
||||
end
|
||||
end
|
||||
-- The second read, at a non-zero LBA.
|
||||
local rd2 = SP:read_u32(0x180E4)
|
||||
if rd2 ~= 0 then
|
||||
P(string.format("second scsi_read FAILED: err=%d (%s)",
|
||||
SP:read_u32(0x180EC),
|
||||
ERRNAME[SP:read_u32(0x180EC)] or "?"))
|
||||
else
|
||||
local f = io.open(DISK, "rb")
|
||||
if f then
|
||||
f:seek("set", 1000 * 512)
|
||||
local want = f:read(4 * 512); f:close()
|
||||
local bad = 0
|
||||
for i = 1, #want do
|
||||
if SP:read_u8(0x28000 + i - 1) ~= string.byte(want, i) then bad = bad + 1 end
|
||||
end
|
||||
P(bad == 0
|
||||
and string.format("READ(10) OK: %d B from LBA 1000 match too.", #want)
|
||||
or string.format("READ(10) WRONG at LBA 1000: %d of %d differ.", bad, #want))
|
||||
end
|
||||
end
|
||||
P("done"); M:exit(); return
|
||||
end
|
||||
end)
|
||||
if not ok then P("LUA ERROR: "..tostring(err)); P("done"); M:exit() end
|
||||
end)
|
||||
Reference in New Issue
Block a user