diff --git a/docs/FINDINGS.md b/docs/FINDINGS.md index 63ce8ff..2796a4e 100644 --- a/docs/FINDINGS.md +++ b/docs/FINDINGS.md @@ -5229,3 +5229,156 @@ the diff produced is one useful fact about our own input layer, below. generated table carries the attribution. **`tmp/scenegraph.json` is generated, gitignored and derived data**: committing it, or any table built from it, is redistribution and the attribution has to travel with it. + +--- + +## FINDINGS 57 — the 68000 reads the disc itself, and P4 was never blocked + +Session 25. ROADMAP P4, first half. Green light ALL GREEN before and after. +**Emulated** — MAME 0.277, `x68000 -exp1 cz6bs1`. No real hardware. + +### 57.1 The blocker was a missing FILE, not a missing MODEL, and the tree already knew +Session 21 recorded P4 as "blocked in this tree", re-checked rather than +assumed, on three grounds. One of them is wrong: + +> there is still no `scsiexrom.bin` anywhere on this machine ... **MAME's +> `x68000` has no MB89352 path**, and `hd63450.cpp` decodes no DTYP. + +MAME 0.277's `x68000` **does** have one. `-listslots` offers `cz6bs1` on `exp1` +and `exp2`; `-listdevices x68000 -exp1 cz6bs1` shows a *Fujitsu MB89352 SCSI +controller @ 5.00 MHz* on a SCSI bus with a hard disk at ID 0, alongside the +HD63450. **FINDINGS 32.4 had already established this in session 9** — the +CZ-6BS1's DMA glue, `$EA0000`, the data register at `$EA0015` — and 42.5 built +on it in session 14. The session-21 note is a regression in the record, not a +discovery. + +What is genuinely missing is only the 8 KB `scsiexrom.bin` (CRC `7be488de`), +which MAME **requires** to instantiate the card: without it the machine refuses +to start at all. **That is the entire blocker, and it is not one**, because the +player drives the SPC registers directly and never executes that ROM — which was +already the plan in `docs/BENCHMARK.md` item 4, written in session 2, long +before the file turned out to be absent. `tools/bench/scsi_run.sh` supplies a +zero-filled placeholder on its own rompath, leaves the user's romset untouched, +and lets MAME print WRONG CHECKSUMS as it should. + +**The substitution is honest here and would not be everywhere.** Anything that +boots from the card, or calls SCSI IOCS, *does* execute that ROM. Do not reuse +the rompath for those. **B3 is untouched**: it wants the ROM's bytes +disassembled for the DCR it writes, and a blank one has none. + +### 57.2 The register map, measured rather than inferred +32.4 quoted one address. `src/player/scsigate.s` probes `$EA0000..$EA003F` one +address at a time, with a bus-error handler that records the fault, steps the +index and re-enters the loop — so a hole costs an entry in the map rather than +the rest of the run. **60 of 64 addresses answer.** + +| | | +|---|---| +| registers | odd bytes, `$EA0001 + 2n`, `n = 0..14` | +| `$EA0007` (n=3, TMOD) | **BUS ERROR** | +| `$EA001F` (n=15, EXBF) | **BUS ERROR** | +| `$EA0017` (n=11, TEMP) | wrote `$A5`, read back `$A5` | +| `$EA0015` (n=10, DREG) | as 32.4 said | + +The two holes are exactly the two registers the **MB89352 omits and the MB87030 +has**, which independently confirms which part MAME is modelling. It also +**corrects the device's own documentation**: the summary of `mb87030.cpp` says +the MB89351/352 "skip TMOD and EXBF, shifting subsequent indices accordingly", +and the machine says MAME leaves *holes* and shifts nothing — which is what +keeps DREG at index 10 and at `$EA0015`. The bytes win. The first version of +this probe walked upward with `move.b (a0)+`, took a bus error at `$EA0006`, and +knew one address was dead and nothing about the other 57; **a sequential dump +reports the first hole as the answer**. + +### 57.3 The data register is DMA-only on this card, and a PIO write vanishes +`x68k_scsiext.cpp` installs its own handler on `$EA0015` **and on no other +address**: + + write: if (exown()) { if (!drq) dtack_w(1); else dma_w(data); } + else dreg_w(data); + +On this machine `exown()` — the HD63450's OWN, fed back to the slot by +`x68k.cpp` — is asserted where a PIO write needs it not to be. The `else` arm is +unreachable, and **a byte written to `$EA0015` with the SPC in PROGRAM transfer +mode is discarded silently**: no error bit, no status change, no interrupt. +Quieting all four DMAC channels (`CCR = 0`, `CSR = $FF`) does not change it. + +It was **measured, not reasoned about** — the gate writes `$5A` to `$EA0015` and +reads it straight back, and gets `$00` with the FIFO still empty — because ten +command bytes vanishing without trace looks exactly like a target refusing a +command, and that is how it first presented. + +So every transfer issues `SCMD` **without** the PROGRAM bit, which puts the SPC +in DMA mode and makes it raise DRQ, and the CPU then moves the bytes through +`$EA0015` itself, in via `dma_w` and out via `dma_r`. **The CPU stands in for +the DMAC, through the DMAC's own door.** + +**What that costs the argument, stated because it is easy to overclaim.** With +`exown` asserted at idle, MAME **cannot distinguish a CPU-driven byte at +`$EA0015` from a DMAC-driven one**. This rig therefore demonstrates the DATA +PATH and cannot, on its own, demonstrate that the HD63450 is the thing driving +it — which is precisely what ROADMAP calls P4's first job. Whether a real +CZ-6BS1 also refuses PIO here is **not settled**: it is a property of MAME's +model and it wants a board. + +### 57.4 The result +`src/player/scsi.i`, 68000 code, no IOCS and no host in the transfer path: +selects the target, and issues **READ(10)** twice. + + READ(10) OK: 4096 B from LBA 0 match the host's image byte for byte + READ(10) OK: 2048 B from LBA 1000 match too + +The second one is the half that matters: **a driver that emits a malformed LBA +field still passes LBA 0**, because zero is what a malformed field usually is. + +The volume is `tmp/stream_disk.bin` — the *same* file `prep_stream.py` already +writes for the ring rig — so the SCSI volume and the host-file pipe carry +byte-identical bytes, and a difference between the two rigs cannot be a +difference in what they are reading. + +### 57.5 Five bugs, and four of them were silent +Recorded because the pattern is the finding: **nothing in a SCSI bring-up tells +you what you did wrong.** Every one of these presented as a phase that never +arrived. + +1. **`sc_settc` wrote the wrong three bytes.** Three chained `rol.l #8` put the + original bits 31..24, 23..16 and 15..8 into TCH/TCM/TCL, so a count of 10 + loaded a transfer counter of **zero**. MAME completed the TRANSFER instantly + and silently, and the bus sat in command phase. Now written low-byte-first + with `lsr`. The same trap was live in the CDB's block-count field. +2. **A byte handed to a FIFO is not a byte on the bus.** The driver returned + from the command phase with the last byte still in the SPC, asked what phase + the bus was in, and got COMMAND — which reads as a target refusing the + command. Fixed by waiting for `XFER IN PROGRESS` to clear. +3. **A fixed phase sequence is wrong.** The first version ran select → command → + data → status → message; the target came up in MESSAGE OUT with ATN asserted + and the driver called it an unexpected phase. **The bus decides the order.** + `scsi_read` is now a phase loop, which is both shorter and correct. +4. **The discarded PIO write** of 57.3. +5. **The initiator must let go of the bus, in two steps.** After the final + message byte the SPC still holds ACK — `PSNS` reads `$4F`, REQ low and ACK + high — and a target cannot drop BSY into that. It needs `SCMD` reset-ACK/REQ + **and then** bus release. **This one only appeared once there were two + reads**: one read passed byte-exact and every conclusion drawn from it was + sound, and the second could not select. A player issues one command per + record, so the failure would have been universal in the ring and invisible in + the demonstration. + +### 57.6 What this does and does not move +**Does not move `W`.** Not by one clock. MAME's device models are functional, +not transfer-timing accurate (`docs/BENCHMARK.md`), and 42.5 reads its DMAC +configured in wall-clock attotimes rather than per-operand cycles. `W` remains +the project's largest open number and still wants a board. + +**Does not finish P4.** What is done is the correctness half — BENCHMARK's Tier +1, "does our read path work at all". What is left is the half ROADMAP calls +P4's first job: **a DMAC configuration that holds the bus**, and then the driver +behind `ring.i`'s `XF_*` mailbox in place of `stream.lua`'s modelled transport, +gated on the same pixel-exact 120 frames. 57.3 is a warning about the first of +those: this apparatus cannot tell a DMAC-driven byte from a CPU-driven one at +`$EA0015`, so "the DMAC held the bus" will need evidence that does not come from +watching that address. + +**Does move the premise of every delivery rig in the tree.** Until now the +bytes came from a host. They now come off a disc, on the machine's own +instructions, byte-exact at two different LBAs. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6e3f2f3..d081cd4 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -5,6 +5,7 @@ Amended end of session 21: P1 done, P2 half done (FINDINGS 53). Amended end of session 22: P3 done (FINDINGS 54). Amended end of session 23: P5 done (FINDINGS 55). Amended end of session 24: G1 done (FINDINGS 56). +Amended end of session 25: P4 HALF done (FINDINGS 57). **THE COMPLETION TARGET IS M3, THE VERTICAL SLICE** (USER DECISION): one scene tree — a decision point, two outcomes, a death clip — with audio, streaming from @@ -159,7 +160,39 @@ period from `htotal - 8`), so the tree's "1/55.46 s granularity" was 1/56.69 s throughout. No 68000 cycle figure moves — the CPU clock is unrelated to the screen — but anything paced by the raster does. 54.5. -**P4. Real transport.** Drive the MB89352 instead of a host file. **Session 23 +**P4. Real transport. HALF DONE, session 25 — FINDINGS 57.** +~~Drive the MB89352 instead of a host file.~~ `src/player/scsi.i` selects a SCSI +target and issues READ(10) on the 68000, with no IOCS and no host in the +transfer path: **4,096 B from LBA 0 and 2,048 B from LBA 1000, both byte-exact** +against the host's copy of the same volume. + +**This item was listed as blocked and was not.** Session 21 recorded "MAME's +`x68000` has no MB89352 path"; `-exp1 cz6bs1` instantiates one, and FINDINGS +32.4 had read that card's DMA glue back in session 9. The real gap was the 8 KB +`scsiexrom.bin` MAME needs to instantiate the card and **the player never +executes**; a blank placeholder on a separate rompath settles it. **B3 still +wants the real ROM's bytes** and is untouched by this. + +**What is left is the half that decides the project**, and it is now two pieces: + +**P4a. A DMAC configuration that HOLDS THE BUS.** Unchanged as the first job, +but 57.3 changes how it can be shown: `x68k_scsiext.cpp` glues `$EA0015` so that +a write is discarded when `exown()` is asserted and DRQ is low, and on this +machine `exown()` is asserted at idle. Every transfer therefore runs the SPC in +DMA mode with the CPU moving the bytes through the DMAC's own door — and **MAME +cannot then distinguish a CPU-driven byte from a DMAC-driven one at that +address**. "The DMAC held the bus" needs evidence that does not come from +watching `$EA0015`. + +**P4b. `scsi.i` behind `ring.i`'s `XF_*` mailbox**, in place of `stream.lua`'s +modelled transport, gated on the same pixel-exact 120 frames. The seam already +exists: `XF_ACK` stops being a word a host synthesises from emulated time and +becomes one the 68000 bumps when a transfer lands. + +`W` **did not move by one clock** and is still the largest open number here. + +*(original item, still the standing description of the `W` question:)* +Drive the MB89352 instead of a host file. **Session 23 added a second axis to it:** `W` is the clocks stolen per delivered byte, and 55.3 measured that the player's own request loop gives away 3-7% of the pipe before `W` is even asked about. A transport design has to answer both. The `W` diff --git a/docs/STATUS.md b/docs/STATUS.md index fdc4f03..277d374 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,3 +1,94 @@ +# Status & next-session handoff — end of session 25 (2026-08-24) + +## Session 25: the 68000 reads the disc itself, and P4 was never blocked + +**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any +of this and ALL GREEN after**, plus a new SCSI stage. + +**ROADMAP P4 is HALF DONE. FINDINGS 57.** **Emulated** — MAME 0.277, +`x68000 -exp1 cz6bs1`. No real hardware ran. + +**1. THE BLOCKER WAS A MISSING FILE, NOT A MISSING MODEL — and this tree already +knew.** Session 21's handoff recorded, re-checked rather than assumed, that +"MAME's `x68000` has no MB89352 path". It has one: `-exp1 cz6bs1` instantiates a +*Fujitsu MB89352 @ 5.00 MHz* next to the HD63450, and **FINDINGS 32.4 had read +that card's DMA glue in session 9**. What is actually absent is the 8 KB +`scsiexrom.bin`, which MAME requires to instantiate the card and which **the +player never executes** — driving the SPC registers directly has been the plan +since `docs/BENCHMARK.md` item 4 in session 2. A zero-filled placeholder on a +separate rompath starts the machine; the user's romset is untouched. **B3 is +untouched too**: it wants that ROM's bytes disassembled, and a blank one has +none. 57.1. + +**2. THE RESULT.** `src/player/scsi.i` on the 68000, no IOCS and no host in the +transfer path, selects the target and issues READ(10): + +| | | +|---|---| +| 4,096 B from LBA 0 | byte-for-byte against the host's image | +| 2,048 B from LBA 1000 | byte-for-byte | + +The non-zero LBA is the half that matters: **a driver that emits a malformed LBA +field still passes LBA 0**, because zero is what a malformed field usually is. +The volume is `tmp/stream_disk.bin`, the same file the ring rig reads, so the two +rigs cannot differ in what they are reading. 57.4. + +**3. The register map is measured, and it corrects MAME's own documentation.** +The probe walks `$EA0000..$EA003F` one address at a time and survives each bus +error. **60 of 64 answer**; the two holes are `$EA0007` and `$EA001F` — exactly +the TMOD and EXBF the **MB89352 omits and the MB87030 has**. MAME leaves HOLES +and does *not* "shift subsequent indices", which its own device summary claims, +and that is what keeps DREG at index 10 and at `$EA0015`. TEMP took `$A5` and +gave it back. 57.2. + +**4. THE DATA REGISTER IS DMA-ONLY, AND A PIO WRITE VANISHES.** `x68k_scsiext.cpp` +glues `$EA0015` and nothing else: with `exown()` asserted and DRQ low a write is +**discarded silently** — no error bit, no status change, no interrupt. Quieting +all four DMAC channels does not change it. So every transfer runs the SPC in DMA +mode and the CPU moves the bytes through `$EA0015` itself, via `dma_w`/`dma_r`: +**the CPU standing in for the DMAC, through the DMAC's own door.** + +**The cost to the argument, stated because it is easy to overclaim:** with +`exown` asserted at idle, **MAME cannot distinguish a CPU-driven byte at +`$EA0015` from a DMAC-driven one.** This rig shows the DATA PATH and cannot, by +itself, show that the HD63450 is driving it — which is exactly what ROADMAP +calls P4's first job. Whether a real CZ-6BS1 also refuses PIO here is **not +settled**; it is a property of MAME's model. 57.3. + +**5. `W` DID NOT MOVE. Not by one clock.** MAME's device models are functional, +not transfer-timing accurate, and 42.5 reads its DMAC configured in wall-clock +attotimes rather than per-operand cycles. `W` is still the project's largest +open number and still wants a board. + +**6. Five bugs, four of them silent** (57.5), recorded because the pattern is the +finding: a chained-`rol` transfer counter that loaded ZERO; a FIFO byte mistaken +for a bus byte; a fixed phase sequence where the bus decides the order; the +discarded PIO write; and an initiator that must drop ACK *and then* release the +bus. The last **only appeared once there were two reads** — one read passed +byte-exact 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. + +**New in the tree:** `src/player/scsi.i` (the driver), `src/player/scsigate.s` +(the probe and gate front-end), `tools/bench/scsi.lua`, `tools/bench/scsi_run.sh`, +and a `check.sh` stage that builds the volume with `chdman`, gates the register +window at 60 of 64 and both reads byte-exact, and is skipped when `chdman` is +absent. + +**No decoder code changed.** `decode.bin` is still 1,296 B at the same MD5. + +**Next:** **P4's other half**, in two pieces. (a) The HD63450 driving the data +phase in a configuration that HOLDS THE BUS — and 57.3 is a warning about how it +will have to be shown, because this apparatus cannot tell a DMAC-driven byte +from a CPU-driven one at `$EA0015`. (b) `scsi.i` behind `ring.i`'s `XF_*` +mailbox in place of `stream.lua`'s modelled transport, gated on the same +pixel-exact 120 frames — the seam is already there, and `XF_ACK` becomes +something the 68000 bumps when a transfer lands rather than something a host +synthesises. **P2's remaining half** (reserve index 0 as black) is unchanged and +still bundled with the two other re-encode-class questions from 55. + +--- + # Status & next-session handoff — end of session 24 (2026-08-24) ## Session 24: the scene graph is in, and the worst gap between two decision points is zero diff --git a/src/player/scsi.i b/src/player/scsi.i new file mode 100644 index 0000000..30e2d76 --- /dev/null +++ b/src/player/scsi.i @@ -0,0 +1,543 @@ +; ---------------------------------------------------------------- scsi.i +; The MB89352 TRANSPORT, on the 68000. ROADMAP P4. +; +; WHAT THIS REPLACES. src/player/ring.i decides which record to fetch, where in +; the ring to put it and when it is safe; it hands that to a TRANSPORT through +; the XF_* mailbox and polls for completion. Until now the transport was +; tools/bench/stream.lua -- a host moving bytes at a modelled rate. A player has +; no host. This file is the transport: a Fujitsu MB89352 SPC and, later, one +; HD63450 channel. +; +; THE REGISTER MAP IS MEASURED, NOT ASSUMED. src/player/scsigate.s probes +; $EA0000..$EA003F one address at a time and survives the bus errors, and the +; map below is what answered: +; +; registers sit on the ODD bytes, $EA0001 + 2n, n = 0..14 +; n=3 (TMOD) and n=15 (EXBF) BUS ERROR -- the MB89352 omits both, where the +; MB87030 has them, and MAME leaves HOLES rather than shifting the +; later indices down. DREG is index 10 at $EA0015 either way, which is the +; one address FINDINGS 32.4 had quoted. +; TEMP ($EA0017) took $A5 and gave it back, so these are registers and not a +; mirror of something. +; +; That last point is why the probe was worth a run: MAME's own device summary +; says the MB89352 "shifts subsequent indices", and the machine says it does +; not. The bytes win. +; +; THE DATA REGISTER IS DMA-ONLY, AND THAT IS NOT A CHOICE THIS CODE MADE. +; x68k_scsiext.cpp puts its own glue on $EA0015 and on no other address: +; +; write: if (exown()) { if (!drq) dtack_w(1); else dma_w(data); } +; else dreg_w(data); +; +; On this machine `exown()` -- the HD63450's OWN, fed back to the slot by +; x68k.cpp -- is asserted where a PIO write needs it not to be, so the `else` +; arm is unreachable and a byte written to $EA0015 with the SPC in PROGRAM +; transfer mode is DISCARDED. Silently: no error bit, no status change, no +; interrupt. It was measured rather than reasoned about -- scsigate.s writes +; $5A to $EA0015 and reads it straight back, and gets $00 with the FIFO still +; empty -- because ten command bytes vanishing without trace looks exactly like +; a target refusing a command. +; +; So every transfer here issues SCMD WITHOUT the PROGRAM bit, which puts the SPC +; in DMA mode and makes it raise DRQ; the CPU then moves the bytes through +; $EA0015 itself and they go in via `dma_w`/`dma_r`. The CPU is standing in for +; the DMAC, through the DMAC's own door. +; +; WHAT THAT COSTS THE ARGUMENT, stated because 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. So this rig demonstrates THE DATA PATH and +; cannot, on its own, demonstrate that the HD63450 is the thing driving it. +; Whether a real CZ-6BS1 also refuses PIO here is NOT settled by this -- it is a +; property of MAME's model, and it wants a board (ROADMAP B1/B3). +; +; PIO FIRST, DMA SECOND, DELIBERATELY. The thing P4 has to demonstrate is a +; DMAC configuration that HOLDS THE BUS (ROADMAP: "getting the DMAC to hold the +; bus is the difference between 9 and 19 clocks per byte, and demonstrating a +; configuration that does it is P4's first job"). But a DMA bring-up that fails +; cannot tell "the SCSI protocol is wrong" from "the DMAC is misprogrammed". So +; the protocol is settled in PIO, where every byte is the CPU's and nothing else +; can be blamed, and only then does the data phase move to the channel. +; +; NOTHING HERE IS A RATE MEASUREMENT, and it cannot become one. MAME's device +; models are functional, not transfer-timing accurate (docs/BENCHMARK.md), and +; 42.5 reads its DMAC configured in wall-clock attotimes rather than per-operand +; cycles. `W` -- clocks stolen per delivered byte -- is untouched by every line +; below. What this settles is which handshake the player's own code provokes. + +; ---- the SPC, at the CZ-6BS1's decode +SPCB = $EA0001 ; register 0; stride 2, odd lane +SC_BDID = SPCB+0 ; own ID (write the NUMBER; reads a MASK) +SC_SCTL = SPCB+2 +SC_SCMD = SPCB+4 +; SPCB+6 = TMOD, ABSENT on the MB89352 -- reading it BUS ERRORS +SC_INTS = SPCB+8 +SC_PSNS = SPCB+10 +SC_SSTS = SPCB+12 +SC_SERR = SPCB+14 +SC_PCTL = SPCB+16 +SC_MBC = SPCB+18 +SC_DREG = SPCB+20 ; $EA0015, and the DMAC's single address +SC_TEMP = SPCB+22 +SC_TCH = SPCB+24 +SC_TCM = SPCB+26 +SC_TCL = SPCB+28 +; SPCB+30 = EXBF, ABSENT -- reading it BUS ERRORS + +; SCTL +SCTL_RESET = $80 ; reset & disable +; SCMD, command in bits 7-5 +SCMD_RELEASE = $00 ; command 000, let go of the bus +SCMD_RSTACK = $C0 ; command 110, drop ACK/REQ +SCMD_SELECT = $20 +SCMD_RSTATN = $40 ; command 010, drop ATN +SCMD_XFER = $80 +SCMD_PROGRAM = $04 ; set = PIO, clear = DMA. NOT USED, and + ; the reason is the whole of 57.x -- see + ; "THE DATA REGISTER IS DMA-ONLY" above. +; INTS +INTS_RESET = $01 +INTS_HARDERR = $02 +INTS_TIMEOUT = $04 +INTS_SERVICE = $08 +INTS_CMDCOMP = $10 +INTS_DISCON = $20 +; SSTS +SSTS_DREG_E = $01 ; DREG empty +SSTS_DREG_F = $02 ; DREG full +SSTS_TC0 = $04 +SSTS_BUSY = $20 +SSTS_INITCON = $80 +; SCSI bus phases, as PSNS bits 2..0 and as PCTL's low three +PH_DATAOUT = 0 +PH_DATAIN = 1 +PH_CMD = 2 +PH_STATUS = 3 +PH_MSGOUT = 6 +PH_MSGIN = 7 + +SCSI_ID = 7 ; the player is the initiator +SCSI_TGT = 0 ; the disc + +; ---- error codes, reported through SC_ERR +SCE_OK = 0 +SCE_SELTMO = 1 ; the target never answered selection +SCE_PHASE = 2 ; the bus went somewhere unexpected +SCE_TIMEOUT = 3 ; a poll loop ran out of patience +SCE_STATUS = 4 ; the target returned non-zero status + +SC_ERR = $18200 ; u32 last error +SC_STAT = $18204 ; u32 SCSI status byte from the last cmd +SC_PH = $18208 ; u32 phase we were in when it went wrong +SC_CDB = $18210 ; 12 B command block, built here +SC_MSG = $1821C ; 4 B message byte, either direction + +; ---- a TRACE, because a SCSI bring-up cannot be debugged from one error code. +; Four registers at each interesting instant: SSTS, PSNS, INTS, SERR. MAME's +; SCMD_CMD_TRANSFER is a NO-OP unless SSTS_INIT_CONNECTED is set -- it `break`s +; out of the switch without complaint -- so "the transfer did nothing" and "the +; transfer went wrong" look identical from the outside. The trace separates +; them. +SC_TAG = $1822C ; u32 where the next snapshot came from +SC_TRN = $18230 ; u32 trace entries used +SC_TR = $18240 ; 24 x 8 B: SSTS PSNS INTS SERR TCH TCM TCL TAG + +; A poll bound. Every wait in this file is bounded, because a SCSI phase that +; never arrives must be REPORTED -- an unbounded spin is indistinguishable from +; a wedged emulator, and 34.1 already cost this project fifteen minutes to that +; exact confusion. +SC_PATIENCE = 200000 + +; ---------------------------------------------------------------- sc_snap +; Append SSTS/PSNS/INTS/SERR to the trace. Clobbers nothing the callers use. +sc_snap: + movem.l d0/a0,-(sp) + move.l SC_TRN.l,d0 + cmp.l #24,d0 + bge.s sn_out + lea SC_TR.l,a0 + lsl.l #3,d0 + adda.l d0,a0 + move.b SC_SSTS,(a0)+ + move.b SC_PSNS,(a0)+ + move.b SC_INTS,(a0)+ + move.b SC_SERR,(a0)+ + move.b SC_TCH,(a0)+ + move.b SC_TCM,(a0)+ + move.b SC_TCL,(a0)+ + move.b SC_TAG+3,(a0)+ ; WHERE this snapshot was taken + addq.l #1,SC_TRN.l +sn_out: movem.l (sp)+,d0/a0 + rts + +; ---- the HD63450, so that PIO through the card's data register works at all. +; x68k_scsiext.cpp puts DMA-AWARE GLUE on $EA0015 and nowhere else: +; +; write: if (exown()) { if (!drq) dtack_w(1); else dma_w(data); } +; else dreg_w(data); +; +; With OWN asserted and DRQ low the byte is DROPPED, silently. That is the +; bring-up's fourth bug and the least guessable: ten command bytes went into +; $EA0015, the FIFO stayed empty, the transfer counter stayed at 10, and every +; register the SPC has said "waiting". Nothing reports a discarded write. +; OWN is the DMAC's, and the IPL ROM has been running for three seconds before +; the player's first instruction (52.5 reads its channel setup out of the ROM), +; so the player does not inherit a quiet DMAC -- it has to make one. +DMAC = $E84000 +DMAC_CH = $40 ; channels are 64 B apart +dmac_quiet: + lea DMAC,a0 + moveq #3,d1 +dq1: move.b #0,7(a0) ; CCR: no operation + move.b #$FF,0(a0) ; CSR: write-one-to-clear + adda.w #DMAC_CH,a0 + dbra d1,dq1 + rts + +; ---------------------------------------------------------------- scsi_init +; Reset the SPC and claim an initiator ID. Leaves interrupts DISABLED: the +; player polls, because the ring producer is already a polling loop living in +; the pace wait (ring.i) and an interrupt would buy it nothing it does not +; already have. +scsi_init: + bsr dmac_quiet + move.b #SCTL_RESET,SC_SCTL ; reset & disable + moveq #40,d0 +sci1: nop + dbra d0,sci1 + move.b #SCSI_ID,SC_BDID + move.b #0,SC_SCTL ; out of reset; no arbitration, no ints + move.b #$FF,SC_INTS ; INTS is cleared by writing its bits + move.b #0,SC_PCTL + clr.l SC_ERR.l + clr.l SC_TRN.l + move.l #0,SC_TAG.l + bsr sc_snap + rts + +; ---------------------------------------------------------------- sc_settc +; d0 = 24-bit transfer count -> TCH/TCM/TCL +; Written LOW BYTE FIRST with lsr, not as a chain of rol.l #8. The rol version +; was the bring-up's second bug: three rotations put the ORIGINAL bits 31..24, +; 23..16 and 15..8 into TCH/TCM/TCL, so a count of 10 loaded a transfer counter +; of ZERO. MAME then completed the TRANSFER instantly and silently -- SSTS came +; back $85, TC0 set and XFER_IN_PROGRESS clear -- and the bus sat in command +; phase, which surfaced as the same `UNEXPECTED PHASE` as a protocol error. +sc_settc: + move.l d0,-(sp) + move.b d0,SC_TCL ; bits 7..0 + lsr.l #8,d0 + move.b d0,SC_TCM ; bits 15..8 + lsr.l #8,d0 + move.b d0,SC_TCH ; bits 23..16 + move.l (sp)+,d0 + rts + +; ---------------------------------------------------------------- sc_waitreq +; Wait until the SPC reports a REQ with a phase, or patience runs out. +; Returns the phase in d0; sets SC_ERR and returns -1 on timeout. +sc_waitreq: + move.l #SC_PATIENCE,d1 +swr1: move.b SC_PSNS,d0 + btst #7,d0 ; REQ + bne.s swr2 + subq.l #1,d1 + bne.s swr1 + move.l #SCE_TIMEOUT,SC_ERR.l + moveq #-1,d0 + rts +swr2: and.l #7,d0 + rts + +; ---------------------------------------------------------------- sc_waitfree +; Wait for BUS FREE. A command is not over when its last message byte has been +; read: the target still has BSY asserted, and an initiator that starts +; arbitrating into that gets a selection timeout. +; +; This is the bring-up's fifth bug, and it only appeared once there were TWO +; reads. One read passed, byte-exact, and every conclusion drawn from it was +; sound; the SECOND could not select, because nothing had waited for the first +; to let go of the bus. A player issues one of these per record, so the failure +; would have been universal in the ring and invisible in the demonstration. +sc_waitfree: + move.l #SC_PATIENCE,d1 +swf1: move.b SC_PSNS,d0 + btst #3,d0 ; BSY + beq.s swf2 + subq.l #1,d1 + bne.s swf1 + move.l #SCE_TIMEOUT,SC_ERR.l + moveq #-1,d0 + rts +swf2: moveq #0,d0 + rts + +; ---------------------------------------------------------------- sc_select +; Select SCSI_TGT. The selection bitmask goes in TEMP -- both IDs, ours and +; theirs -- and the transfer counter doubles as the selection timeout (MAME: +; SelectionWaitBSY is derived from TC's upper bits, which is the datasheet's +; behaviour too). +sc_select: + move.b #$FF,SC_INTS + move.b #(1< /dev/null; then + bash tools/bench/scsi_run.sh "$DLX" > tmp/scsi_gate.log 2>&1 || { + echo "FAIL: the 68000 could not read the disc."; tail -14 tmp/scsi_gate.log + exit 1; } + grep -aE "ANSWERED|READ\(10\) OK" tmp/scsi_gate.log +else + echo " SKIPPED: no chdman (ships with mame-tools) -- cannot build the volume" +fi + echo "--- session 24: the scene graph, and the gap between branch points (FINDINGS 56) ---" # The arcade scene graph is not in this repo and is not redistributable from # here. tools/import/scenegraph.py is the ONE file in the tree that knows the diff --git a/tools/bench/scsi.lua b/tools/bench/scsi.lua new file mode 100644 index 0000000..b499292 --- /dev/null +++ b/tools/bench/scsi.lua @@ -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) diff --git a/tools/bench/scsi_run.sh b/tools/bench/scsi_run.sh new file mode 100755 index 0000000..b3625e3 --- /dev/null +++ b/tools/bench/scsi_run.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# One MB89352 transport run: the 68000 selects a SCSI target and reads the disc +# itself (ROADMAP P4, first half). +# +# tools/bench/scsi_run.sh [container.dlx] +# +# THE APPARATUS, AND ITS TWO SUBSTITUTIONS, BOTH DELIBERATE AND BOTH LABELLED. +# +# 1. THE BOARD. `x68000 -exp1 cz6bs1`, which is what FINDINGS 42.5 says to +# benchmark and never `x68ksupr` -- the Super/XVI internal SCSI is PIO-only in +# MAME (`// TODO: duplicate DMA glue from CZ-6BS1`), so it would measure a +# fallback the real machine does not have. +# +# 2. THE BOOT ROM. MAME refuses to instantiate the card without an 8 KB +# `scsiexrom.bin` (CRC 7be488de), which is not in this tree and is not on this +# machine. This script writes a ZERO-FILLED placeholder into its own rompath, +# so the user's romset is untouched, and MAME prints WRONG CHECKSUMS as it +# should. That is honest HERE and would not be everywhere: the player drives +# the SPC registers directly and never executes a byte of that ROM -- which +# was already the plan in docs/BENCHMARK.md item 4, long before the ROM turned +# out to be missing. DO NOT reuse this rompath for anything that boots from +# the card or calls SCSI IOCS; those DO execute it. +# +# WHAT A GREEN RUN MEANS: the 68000 reached the SPC, selected a target, issued +# READ(10) twice -- at LBA 0 and at a non-zero LBA -- and both came back byte for +# byte identical to the host's copy of the same image. No IOCS, no host in the +# transfer path. +# +# WHAT IT DOES NOT MEAN: anything at all about RATE. MAME's device models are +# functional, not transfer-timing accurate (docs/BENCHMARK.md), and 42.5 reads +# its DMAC configured in wall-clock attotimes rather than per-operand cycles. +# `W` -- clocks stolen per delivered byte, the project's largest open number -- +# is untouched by this script. +set -e +cd "$(dirname "$0")/../.." +DLX=${1:-tmp/rc_fr_singe_scsi_span.dlx} + +# The container's frame records, laid out as a disc. prep_stream.py already +# writes exactly this file for the ring rig, so the SCSI volume and the host-file +# pipe carry byte-identical bytes and a difference between the two rigs cannot be +# a difference in what they are reading. +[ -f tmp/stream_disk.bin ] || python3 tools/bench/prep_stream.py "$DLX" > /dev/null +if [ ! -f tmp/dlxdisk.chd ] || [ tmp/stream_disk.bin -nt tmp/dlxdisk.chd ]; then + python3 - <<'PY' +d = open("tmp/stream_disk.bin", "rb").read() +n = (len(d) + 511) // 512 +open("tmp/dlxdisk.img", "wb").write(d + b"\0" * (n * 512 - len(d))) +print(f" disc image: {len(d)} B of records -> {n} sectors") +PY + rm -f tmp/dlxdisk.chd + chdman createhd -i tmp/dlxdisk.img -o tmp/dlxdisk.chd -ss 512 > /dev/null 2>&1 +fi + +mkdir -p tmp/p4roms/x68k_cz6bs1 +[ -f tmp/p4roms/x68k_cz6bs1/scsiexrom.bin ] || \ + head -c 8192 /dev/zero > tmp/p4roms/x68k_cz6bs1/scsiexrom.bin + +tools/vasm/vasmm68k_mot -Fbin -o tmp/scsigate.bin src/player/scsigate.s > /dev/null + +# stdbuf -oL: without it a long MAME run is unobservable until it exits, and a +# run that is merely finishing looks exactly like one that is wedged (34.1). +( cd tmp && SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 300 \ + mame x68000 -bios ipl10 -exp1 cz6bs1 \ + -rompath "$HOME/mame/roms;./p4roms" -hard dlxdisk.chd \ + -ramsize 2M -video soft -window -sound none -nothrottle -plugins \ + -autoboot_script ../tools/bench/scsi.lua \ + -seconds_to_run 60 > scsi_run.log 2>&1 ) +# A run that never reached the report must fail as that, not as a missing line. +grep -aq "^\[SCSI\] done" tmp/scsi_run.log || { + echo "FAIL: the SCSI gate did not finish -- no completion marker." + tail -8 tmp/scsi_run.log; exit 1; } +grep -a "^\[SCSI\]" tmp/scsi_run.log | grep -avE "^\[SCSI\] +[0-9]+:" \ + | sed 's/^\[SCSI\] / /' + +# THE ASSERTIONS. Printing a result and gating on it are different things, and +# this project has already paid once for a stage that printed. +grep -aq "ANSWERED 60 of 64" tmp/scsi_run.log || { + echo "FAIL: the card's register window is no longer 60 of 64 addresses." + echo " The MB89352 omits TMOD (index 3) and EXBF (index 15) where the" + echo " MB87030 has them, and MAME leaves HOLES rather than shifting the" + echo " later indices down -- which is what puts DREG at \$EA0015. If this" + echo " moved, src/player/scsi.i's whole register map moved with it." + exit 1; } +grep -aq "READ(10) OK: 4096 B from LBA 0" tmp/scsi_run.log || { + echo "FAIL: the 68000 did not read LBA 0 off the SCSI volume byte-exact." + exit 1; } +grep -aq "READ(10) OK: 2048 B from LBA 1000" tmp/scsi_run.log || { + echo "FAIL: the read at a NON-ZERO LBA did not match. A driver that emits a" + echo " malformed LBA field still passes LBA 0, because zero is what a" + echo " malformed field usually is -- so this is the half that matters." + exit 1; } +grep -aq "scsi_read FAILED" tmp/scsi_run.log && { + echo "FAIL: a read reported an error."; exit 1; } +exit 0