-- 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)