Two sessions, unrecorded until now, committed together because their edits share files and cannot be split cleanly after the fact. Session 28 (FINDINGS 60): the container is DLX5 -- every record sector-aligned, 120/120 starting on a boundary where 3/120 did, +0.48% on the wire and zero clocks -- and the ring's release rounds to RECALN so no pad is stranded. Two encoder levers measured and refused: `--spans all` buys +0.19 dB for +67% of the wire, and joint span/lam selection emits byte-identical containers because `lam` never leaves its floor on any of 120 frames. Session 29 (FINDINGS 61): the packed full-frame blit is 27.3% of a 12 fps frame, a channel fills GVRAM in buffer mode off the disc with the CPU halted, and it walks the 1,024 B line stride itself through array chaining. At the 9 clk/B dual-address floor the codec is 110.4% of a frame and a decoder-free packed literal player is 55.2%, at +4.89 dB -- 2.75 dB past a ceiling the codec's scene-wide palette cannot cross. Encoder work is parked; the codec is kept and not built on. check.sh is ALL GREEN before and after, plus one new stage that gates the ORDER of the measured paint costs rather than their values. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
211 lines
11 KiB
Lua
211 lines
11 KiB
Lua
-- Drive src/player/dmagate.s: does the HD63450 drive the SCSI data phase, and
|
|
-- does it HOLD THE BUS? (ROADMAP P4a)
|
|
--
|
|
-- THE APPARATUS is tools/bench/scsi_run.sh's, unchanged and stated again
|
|
-- because it is two substitutions deep: `x68000 -exp1 cz6bs1` (the board 42.5
|
|
-- says to benchmark, never x68ksupr, whose internal SCSI is PIO-only in MAME),
|
|
-- and a ZERO-FILLED scsiexrom.bin on a private rompath, which is honest only
|
|
-- because the player drives the SPC registers directly and never executes a
|
|
-- byte of that ROM.
|
|
--
|
|
-- WHAT THIS RIG DOES NOT DO, and it is the point of the whole design: it never
|
|
-- looks at $EA0015. 57.3 showed that address cannot answer the question --
|
|
-- with the DMAC's OWN asserted MAME cannot tell a CPU-driven byte there from a
|
|
-- DMAC-driven one. What separates the two configurations below is whether the
|
|
-- 68000 EXECUTED ANYTHING while the bytes were arriving, which is a fact about
|
|
-- the CPU and is read out of the DMAC's own registers plus a counter the
|
|
-- machine incremented itself.
|
|
--
|
|
-- AND IT IS NOT A RATE. MAME's DMAC is configured in wall-clock attotimes
|
|
-- (42.5); its burst mode halts the CPU outright rather than charging it cycles
|
|
-- per operand. `W` is untouched here and still wants a board.
|
|
local M = manager.machine
|
|
local SP = M.devices[":maincpu"].spaces["program"]
|
|
local function P(s) print("[DMA] "..s) end
|
|
local function T() local t=M.time; return t.seconds + t.attoseconds/1e18 end
|
|
|
|
local DGFLAG, DGREC, DGREC_SZ = 0x18600, 0x18610, 32
|
|
local DGWIN, DGWERR, DGR20, DGR20N, DGR20C = 0x18700, 0x18704, 0x18708, 0x1870C, 0x18710
|
|
local CHROW, CHN, CHBASE = 256, 8, 0xC10000
|
|
local R20OF -- filled in after the mailbox addresses are known
|
|
local GV = 0xC00000
|
|
local DGLBA, DGBLK = 1000, 4
|
|
local DST = {0x20000, 0x24000, 0x28000, 0xC08000, 0xC0C000, 0xC10000}
|
|
local NAME = {"PIO (the path FINDINGS 58 measured)",
|
|
"DMA, BUS HELD (DCR $00 burst, OCR $81 max rate)",
|
|
"DMA, STEALING (DCR $80 cycle steal, OCR $80 limited)",
|
|
"DMA -> GVRAM (bus held, R20 bit 11 = BUFFER MODE) [47.6.2]",
|
|
"DMA -> GVRAM (the SAME, bit 11 CLEAR -- NEGATIVE CONTROL)",
|
|
"DMA -> GVRAM (ARRAY CHAINED, 8 rows at the 1024 B line stride)"}
|
|
local SHORT = {"pio", "held", "steal", "gvram", "masked", "chain"}
|
|
local ERRNAME = {[0]="OK", "SELECTION TIMEOUT -- no target answered",
|
|
"UNEXPECTED PHASE", "POLL TIMEOUT -- a phase never arrived",
|
|
"NON-ZERO SCSI STATUS",
|
|
"WINDOWED READ REFUSED -- a channel cannot drop bytes"}
|
|
R20OF = {[3]=DGR20, [4]=DGR20N, [5]=DGR20C}
|
|
local DISK = os.getenv("DLX_SCSI_IMG") or "dlxdisk.img"
|
|
|
|
local code do local f=io.open("dmagate.bin","rb"); code=f:read("a"); f:close() end
|
|
|
|
-- the disc's own bytes, once, for all three comparisons
|
|
local want do
|
|
local f = io.open(DISK, "rb")
|
|
if f then f:seek("set", DGLBA*512); want = f:read(DGBLK*512); f:close() end
|
|
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(DGFLAG, 0)
|
|
local cpu = M.devices[":maincpu"]
|
|
cpu.state["SR"].value = 0x2700
|
|
cpu.state["SP"].value = 0x8000
|
|
cpu.state["PC"].value = 0x10000
|
|
P(string.format("dmagate.bin=%d B loaded at $10000; reading LBA %d, %d B, "
|
|
.."three ways, then once more into GVRAM",
|
|
#code, DGLBA, DGBLK*512))
|
|
st = "wait"; return
|
|
end
|
|
if st == "wait" then
|
|
if SP:read_u32(DGFLAG) ~= 1 then
|
|
if T() > 60 then P("TIMEOUT: the gate never finished"); P("done"); M:exit() end
|
|
return
|
|
end
|
|
if not want then P("no "..DISK.." to check against"); P("done"); M:exit(); return end
|
|
local LEN = DGBLK*512
|
|
for i = 0, 5 do
|
|
local b = DGREC + i*DGREC_SZ
|
|
local rc = SP:read_u32(b)
|
|
local e = SP:read_u32(b+4)
|
|
local mtc0 = SP:read_u32(b+8)
|
|
local spin = SP:read_u32(b+12)
|
|
local csr = SP:read_u32(b+16)
|
|
local cer = SP:read_u32(b+20)
|
|
local mtcf = SP:read_u32(b+24)
|
|
local marf = SP:read_u32(b+28)
|
|
P(NAME[i+1])
|
|
if rc ~= 0 then
|
|
P(string.format(" FAILED: err=%d (%s)", e, ERRNAME[e] or "?"))
|
|
else
|
|
local bad, first = 0, nil
|
|
-- The GVRAM run is read back a WORD at a time and split by hand.
|
|
-- SP:read_u8 on $C00000 goes through gvram_r, which in buffer mode
|
|
-- returns the whole word; asking for one byte of it would hand back
|
|
-- whichever half MAME's address space happens to hand over, and the
|
|
-- question here is precisely WHICH HALF each disc byte landed in.
|
|
-- Even disc byte -> high half (page 1), odd -> low half (page 0),
|
|
-- because the 68000 is big-endian and an even address is the MS byte.
|
|
local pg1, pg0, bad_hi, bad_lo = 0, 0, 0, 0
|
|
for k = 1, LEN do
|
|
local got
|
|
if i == 5 then
|
|
-- The chained run's destination is not linear: byte k of the
|
|
-- transfer is byte k%256 of row k//256, and the rows are a full
|
|
-- 1024 B line stride apart. If the channel had ignored the array
|
|
-- and run contiguously, every byte past the first row would be
|
|
-- in the wrong place and this comparison would say so.
|
|
local off = (k-1) % CHROW
|
|
local a = CHBASE + ((k-1) // CHROW) * 1024 + (off & ~1)
|
|
local w = SP:read_u16(a)
|
|
if (off % 2) == 0 then got = (w >> 8) & 0xff; pg1 = pg1 + 1
|
|
else got = w & 0xff; pg0 = pg0 + 1 end
|
|
elseif i >= 3 then
|
|
local w = SP:read_u16(DST[i+1] + ((k-1) & ~1))
|
|
if ((k-1) % 2) == 0 then got = (w >> 8) & 0xff; pg1 = pg1 + 1
|
|
else got = w & 0xff; pg0 = pg0 + 1 end
|
|
else
|
|
got = SP:read_u8(DST[i+1]+k-1)
|
|
end
|
|
if got ~= string.byte(want, k) then
|
|
bad = bad + 1; first = first or (k-1)
|
|
if ((k-1) % 2) == 0 then bad_hi = bad_hi + 1
|
|
else bad_lo = bad_lo + 1 end
|
|
end
|
|
end
|
|
if i >= 3 then
|
|
P(string.format(" R20 during the run = $%04X (bit 11 %s); %d bytes "
|
|
.."read back as page 1 (high half) and %d as page 0",
|
|
SP:read_u32(R20OF[i]),
|
|
((SP:read_u32(R20OF[i]) & 0x0800) ~= 0)
|
|
and "SET" or "CLEAR",
|
|
pg1, pg0))
|
|
end
|
|
if bad == 0 then
|
|
P(string.format(" BYTES OK: %d B from LBA %d match %s byte for byte "
|
|
.."[%s]", LEN, DGLBA, DISK, SHORT[i+1]))
|
|
if i == 3 then
|
|
P(" A CHANNEL FILLS THE PACKED LAYOUT: every disc byte landed in "
|
|
.."its own half of a GVRAM word, with the CPU halted -- so a "
|
|
.."stream interleaved (right<<8)|left goes from disc to screen "
|
|
.."with no CPU in the loop (47.6.2, first half).")
|
|
end
|
|
if i == 5 then
|
|
P(string.format(" THE CHANNEL WALKED THE ARRAY ITSELF: %d rows of "
|
|
.."%d B landed at a %d B line stride from ONE start, CPU halted "
|
|
.."throughout. A frame is %d such entries; the CPU does not "
|
|
.."restart the channel per row.", CHN, CHROW, 1024, 192))
|
|
end
|
|
if i == 4 then
|
|
P(" CONTROL DID NOT FAIL: the masked write path delivered every "
|
|
.."byte too, so the run above is not evidence about R20 bit 11.")
|
|
end
|
|
elseif i == 4 then
|
|
-- THE CLAIM IS NOT "half the bytes differ". In masked 256-colour
|
|
-- mode gvram_w takes `data & 0x00ff` and ignores mem_mask, so a byte
|
|
-- written to an EVEN address is never stored and the high half keeps
|
|
-- whatever it held; some of those stale halves match the disc by
|
|
-- coincidence, and this record is full of pad, so a lot of them do.
|
|
-- The mechanism's signature is WHERE the damage is, not how much:
|
|
-- every ODD byte must survive and only EVEN ones may be lost.
|
|
P(string.format(" BYTES LOST [masked]: %d of %d differ (first at "
|
|
.."+%d) -- %d at EVEN offsets, %d at ODD.",
|
|
bad, LEN, first, bad_hi, bad_lo))
|
|
if bad_lo == 0 and bad_hi > 0 then
|
|
P(string.format(" EXACTLY THE MECHANISM: all %d survivors of the "
|
|
.."high half are stale GVRAM that happens to match "
|
|
.."(this record is mostly pad); not one of the %d "
|
|
.."ODD bytes was harmed. Bit 11 is what carried the "
|
|
.."even ones in the run above.", LEN//2 - bad_hi, LEN//2))
|
|
end
|
|
else
|
|
P(string.format(" BYTES WRONG [%s]: %d of %d differ, first at +%d",
|
|
SHORT[i+1], bad, LEN, first))
|
|
end
|
|
end
|
|
if i > 0 then
|
|
-- THE DISCRIMINATOR. MTC as the instruction after START saw it, and
|
|
-- the number of times the CPU went round its own wait loop.
|
|
P(string.format(" MTC one instruction after START: %d of %d -> the "
|
|
.."CPU %s while the transfer ran [%s]",
|
|
mtc0, LEN,
|
|
(mtc0 == 0) and "NEVER EXECUTED" or "kept executing",
|
|
SHORT[i+1]))
|
|
P(string.format(" CPU trips round the wait loop: %d [%s]", spin, SHORT[i+1]))
|
|
P(string.format(" channel: CSR=$%02X (%s%s%s) CER=$%02X MTC=%d "
|
|
.."MAR=$%06X (+%d) [%s]",
|
|
csr,
|
|
((csr & 0x80) ~= 0) and "COC " or "",
|
|
((csr & 0x10) ~= 0) and "ERR " or "",
|
|
((csr & 0x08) ~= 0) and "ACT" or "idle",
|
|
cer, mtcf, marf, marf - DST[i+1], SHORT[i+1]))
|
|
end
|
|
end
|
|
-- The refusal. Expected to fail, and the run is only green if it did.
|
|
local w, we = SP:read_u32(DGWIN), SP:read_u32(DGWERR)
|
|
if w == 0xFFFFFFFF and we == 5 then
|
|
P("WINDOWED DMA READ REFUSED, as it must be: a channel writes a "
|
|
.."contiguous run and cannot drop the 300 B in front of the record "
|
|
.."(58.3). P4a's precondition is a SECTOR-ALIGNED container.")
|
|
else
|
|
P(string.format("WINDOW NOT REFUSED: rc=%d err=%d -- the transport would "
|
|
.."have written the neighbours' bytes into the ring.", w, we))
|
|
end
|
|
P("done"); M:exit(); return
|
|
end
|
|
end)
|
|
if not ok then P("LUA ERROR: "..tostring(err)); P("done"); M:exit() end
|
|
end)
|