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:
prosolis
2026-08-24 22:53:58 -07:00
parent 00232bb22b
commit e935d8661c
8 changed files with 1262 additions and 1 deletions
+153
View File
@@ -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 table carries the attribution. **`tmp/scenegraph.json` is
generated, gitignored and derived data**: committing it, or any table built generated, gitignored and derived data**: committing it, or any table built
from it, is redistribution and the attribution has to travel with it. 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.
+34 -1
View File
@@ -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 22: P3 done (FINDINGS 54).
Amended end of session 23: P5 done (FINDINGS 55). Amended end of session 23: P5 done (FINDINGS 55).
Amended end of session 24: G1 done (FINDINGS 56). 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 **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 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 throughout. No 68000 cycle figure moves — the CPU clock is unrelated to the
screen — but anything paced by the raster does. 54.5. 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 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 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` before `W` is even asked about. A transport design has to answer both. The `W`
+91
View File
@@ -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) # 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 ## Session 24: the scene graph is in, and the worst gap between two decision points is zero
+543
View File
@@ -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<<SCSI_ID)|(1<<SCSI_TGT),SC_TEMP
move.l #$002000,d0
bsr sc_settc
move.b #0,SC_PCTL
move.b #SCMD_SELECT,SC_SCMD
move.l #SC_PATIENCE,d1
ssel1: move.b SC_INTS,d0
btst #4,d0 ; COMMAND COMPLETE = selection won
bne.s ssel_ok
btst #2,d0 ; TIMEOUT = nobody there
bne.s ssel_tmo
subq.l #1,d1
bne.s ssel1
move.l #SCE_TIMEOUT,SC_ERR.l
moveq #-1,d0
rts
ssel_tmo:
move.b #$FF,SC_INTS
move.l #SCE_SELTMO,SC_ERR.l
moveq #-1,d0
rts
ssel_ok:
move.l #1,SC_TAG.l
bsr sc_snap
move.b #$FF,SC_INTS
move.l #2,SC_TAG.l
bsr sc_snap
moveq #0,d0
rts
; ---------------------------------------------------------------- sc_xferend
; Wait for the SPC to finish the TRANSFER it was given, rather than for the last
; byte to have been HANDED to it.
;
; This is the bring-up's one real bug and it is worth recording. Without it,
; sc_out_pio wrote all ten command bytes and returned, the caller immediately
; asked what phase the bus was in, and the answer was STILL COMMAND -- because
; the SPC had the last byte in its FIFO and had not yet run the REQ/ACK for it.
; The symptom was `UNEXPECTED PHASE, phase=2` at the DATA-IN check, which reads
; like a target refusing the command and is nothing of the kind. A byte handed
; to a FIFO is not a byte on the bus.
sc_xferend:
move.l #SC_PATIENCE,d3
sxe1: move.b SC_SSTS,d0
btst #4,d0 ; XFER IN PROGRESS
beq.s sxe2
subq.l #1,d3
bne.s sxe1
move.l #SCE_TIMEOUT,SC_ERR.l
moveq #-1,d0
rts
sxe2: moveq #0,d0
rts
; ---------------------------------------------------------------- sc_out_pio
; Send d1 bytes from (a0) in phase d2. Command blocks and nothing else, so it
; is the small, simple one.
sc_out_pio:
move.b d2,SC_PCTL
move.l d1,d0
bsr sc_settc
move.b #SCMD_XFER,SC_SCMD
move.l #4,SC_TAG.l ; 4 = TRANSFER issued for an OUT phase
bsr sc_snap
sop1: move.l #SC_PATIENCE,d3
sop2: move.b SC_SSTS,d0
btst #1,d0 ; DREG FULL -- wait for room
beq.s sop3
subq.l #1,d3
bne.s sop2
move.l #SCE_TIMEOUT,SC_ERR.l
moveq #-1,d0
rts
sop3: move.b (a0)+,SC_DREG
subq.l #1,d1
bne.s sop1
move.l #5,SC_TAG.l ; 5 = every byte handed to the FIFO
bsr sc_snap
bsr sc_xferend
move.l d0,-(sp)
move.l #6,SC_TAG.l ; 6 = after waiting for the transfer
bsr sc_snap
move.l (sp)+,d0
rts
; ---------------------------------------------------------------- sc_in_pio
; Receive d1 bytes into (a1) in phase d2. This is the path the DMA version
; replaces; it stays because it is what makes a DMA failure diagnosable.
sc_in_pio:
move.b d2,SC_PCTL
move.l d1,d0
bsr sc_settc
move.b #SCMD_XFER,SC_SCMD
move.l #7,SC_TAG.l ; 7 = TRANSFER issued for an IN phase
bsr sc_snap
sip1: move.l #SC_PATIENCE,d3
sip2: move.b SC_SSTS,d0
btst #0,d0 ; DREG EMPTY -- wait for a byte
beq.s sip3
subq.l #1,d3
bne.s sip2
move.l #SCE_TIMEOUT,SC_ERR.l
moveq #-1,d0
rts
sip3: move.b SC_DREG,(a1)+
subq.l #1,d1
bne.s sip1
move.l #8,SC_TAG.l ; 8 = every byte taken from the FIFO
bsr sc_snap
bsr sc_xferend
move.l d0,-(sp)
move.l #9,SC_TAG.l ; 9 = after waiting for the IN transfer
bsr sc_snap
move.l (sp)+,d0
rts
; ---------------------------------------------------------------- scsi_read
; READ(10) of d4 blocks from LBA d3 into (a1). READ(10) rather than READ(6)
; because a 21-bit LBA and a 256-block ceiling are limits this container will
; reach -- 4,488,588 B of frame records is already 8,767 sectors, and a full
; disc is 1.09 GiB (ROADMAP C3).
;
; DRIVEN BY THE PHASE, NOT BY A SCRIPT, and that is the third thing the bring-up
; taught. The first version ran a fixed sequence -- select, command, data,
; status, message -- and broke the moment the target asked for something else:
; it came up in MESSAGE OUT with ATN asserted and the driver, which "knew" the
; next phase was COMMAND, called it an unexpected phase and gave up. The bus
; decides the order. A driver that reads the phase and services whatever it
; finds is both shorter and correct, and it is what the target is entitled to.
scsi_read:
movem.l d3-d5/a1,-(sp)
; ---- the command block, built before anything is on the bus
lea SC_CDB.l,a0
move.b #$28,(a0)+ ; READ(10)
clr.b (a0)+
move.l d3,d0 ; LBA, big-endian u32
rol.l #8,d0
move.b d0,(a0)+ ; 31..24
rol.l #8,d0
move.b d0,(a0)+ ; 23..16
rol.l #8,d0
move.b d0,(a0)+ ; 15..8
rol.l #8,d0
move.b d0,(a0)+ ; 7..0
clr.b (a0)+
move.l d4,d0 ; block count, big-endian u16. Same
lsr.l #8,d0 ; trap as sc_settc had: a rol chain here
move.b d0,(a0)+ ; would have emitted bits 31..24/23..16
move.b d4,(a0)+ ; of a count that lives in 15..0.
clr.b (a0)+
; ---- d5 = bytes of data still expected
move.l d4,d5
lsl.l #8,d5
lsl.l #1,d5 ; blocks * 512
clr.l SC_STAT.l
bsr sc_select
tst.l d0
bmi scr_out
; Drop ATN. We have no message to send, so asking the target not to ask
; for one is cheaper than answering. The MSGOUT arm below still exists,
; because "cheaper" is not "guaranteed".
move.b #SCMD_RSTATN,SC_SCMD
; ---- service whatever the bus asks for, until the target ends the command
scr_ph:
bsr sc_waitreq
tst.l d0
bmi scr_out
move.l d0,-(sp)
move.l #3,SC_TAG.l ; 3 = the phase loop saw a REQ
bsr sc_snap
move.l (sp)+,d0
cmp.l #PH_CMD,d0
beq.s scr_cmd
cmp.l #PH_DATAIN,d0
beq.s scr_din
cmp.l #PH_STATUS,d0
beq.s scr_st
cmp.l #PH_MSGIN,d0
beq.s scr_min
cmp.l #PH_MSGOUT,d0
beq.s scr_mout
bra scr_phase
scr_cmd:
lea SC_CDB.l,a0
moveq #10,d1
moveq #PH_CMD,d2
bsr sc_out_pio
tst.l d0
bmi scr_out
bra scr_ph
scr_din:
; KNOWN LIMITATION, harmless here and not harmless forever: this asks
; for the WHOLE remaining count every time the bus enters DATA IN. A
; target that split one READ(10) across two data phases would be served
; the full length twice and overrun the caller's buffer. This one does
; not split -- 4,096 B and 2,048 B both arrive in a single phase -- but
; a real drive may, and P4b's mailbox integration is where d5 has to
; start being decremented by what each phase actually delivered.
move.l 12(sp),a1 ; the caller's destination. movem.l
; d3-d5/a1,-(sp) lays them out ASCENDING
; from sp as d3,d4,d5,a1 -- a1 is at 12.
move.l d5,d1
moveq #PH_DATAIN,d2
bsr sc_in_pio
tst.l d0
bmi scr_out
bra scr_ph
scr_st:
lea SC_STAT.l,a1
addq.l #3,a1 ; the byte lands in the u32's low end
moveq #1,d1
moveq #PH_STATUS,d2
bsr sc_in_pio
tst.l d0
bmi scr_out
bra scr_ph
scr_min:
lea SC_MSG.l,a1
moveq #1,d1
moveq #PH_MSGIN,d2
bsr sc_in_pio
tst.l d0
bmi scr_out
; A message in ends the command. Anything non-zero in the status byte
; is the target refusing, and a transport that ignored it would hand the
; ring a buffer of stale bytes and call it a record.
move.l SC_STAT.l,d0
beq.s scr_ok
move.l #SCE_STATUS,SC_ERR.l
moveq #-1,d0
bra.s scr_out
scr_mout:
; Nothing to say: IDENTIFY, no disconnect, LUN 0.
lea SC_MSG.l,a1
move.b #$80,(a1)
move.l a1,a0
moveq #1,d1
moveq #PH_MSGOUT,d2
bsr sc_out_pio
tst.l d0
bmi scr_out
bra scr_ph
scr_ok: ; END OF COMMAND, and it takes two steps rather than one. After the
; final message byte the SPC is still holding ACK -- PSNS reads $4F,
; REQ low and ACK high -- and a target cannot drop BSY into that. So
; ACK is dropped explicitly, and only then is the bus released.
move.b #SCMD_RSTACK,SC_SCMD
move.b #SCMD_RELEASE,SC_SCMD
move.l #10,SC_TAG.l ; 10 = after the bus release command
bsr sc_snap
bsr sc_waitfree ; leave the bus as we found it
tst.l d0
bmi scr_out
moveq #0,d0
scr_out:
movem.l (sp)+,d3-d5/a1
rts
scr_phase:
move.l d0,SC_PH.l
move.l #SCE_PHASE,SC_ERR.l
moveq #-1,d0
bra.s scr_out
+150
View File
@@ -0,0 +1,150 @@
; Front-end for the MB89352 PROBE (ROADMAP P4, first step), for the rig.
;
; WHY A PROBE AND NOT A DRIVER. P4 replaces tools/bench/stream.lua's modelled
; transport with a real SPC behind the XF_* mailbox src/player/ring.i already
; talks to. Before any of that can be written, the register map has to be a
; FACT on the emulated machine rather than a reading of somebody's datasheet.
; FINDINGS 32.4 quotes MAME mapping the data register at $EA0015 -- register
; index 10 at a stride of 2 from $EA0001, i.e. registers on the ODD bytes. That
; is an inference from ONE address, and every access the driver makes rests on
; it.
;
; WHY IT ENUMERATES INSTEAD OF DUMPING. The first version of this walked
; $EA0000 upwards with a plain `move.b (a0)+`, and took a bus error at $EA0006 --
; at which point it knew one address was dead and nothing about the other 57.
; A sequential dump stops at the first hole and reports the hole as the answer.
; So this probes ONE ADDRESS AT A TIME with the index in MEMORY, and a bus error
; handler that records the fault, steps the index and re-enters the loop. A
; dead address costs an entry in the map rather than the rest of the run.
;
; The 68000 cannot resume a faulted instruction -- RTE re-runs it and faults
; again -- so the handler does not try. It restores a stack pointer saved
; before the loop and jumps back to the loop head, which reloads everything it
; needs from memory. Nothing lives in a register across a fault.
;
; This gate drives no SCSI bus and moves no data. It is the smallest thing that
; can turn "MAME instantiates an MB89352" into "the 68000 can reach it, HERE".
SCFLAG = $18080 ; 0 idle / 1 done
SCN = 64 ; addresses probed, from SPCBASE up
SCIDX = $18084 ; u32 probe index, lives in memory across faults
SCSAVSP = $18088 ; u32 stack pointer saved before the loop
SCVAL = $18100 ; SCN bytes: what each address read
SCOK = $18140 ; SCN bytes: 1 = answered, 0 = bus error
SCTMP = $180D0 ; u32: TEMP writeback -- $A5 written, read back
SCTMPOK = $180D4 ; u32: 1 = the writeback completed without fault
SCRD = $180D8 ; u32: scsi_read's return, 0 = the read worked
SCDRG = $180DC ; u32: $5A written to DREG then read straight back
SCDRS = $180E0 ; u32: SSTS between that write and that read
SCDST = $20000 ; where the read lands
SCBLKS = 8 ; 8 x 512 B, enough to cross a sector boundary
SCRD2 = $180E4 ; u32: the second read's return
SCER1 = $180E8 ; u32: SC_ERR as it stood after the first read
SCER2 = $180EC ; u32: ...and after the second
SCDST2 = $28000 ; where the second read lands
SCLBA2 = 1000 ; a NON-ZERO LBA: block 0 would pass even if the
; LBA bytes of the command block were ignored
SCBLK2 = 4
SPCBASE = $EA0000
org $10000
start:
move.l #buserr,$8.w ; vector 2
clr.l SCFLAG.l
clr.l SCTMP.l
clr.l SCTMPOK.l
clr.l SCIDX.l
move.l sp,SCSAVSP.l
; ---- probe SCN addresses, one at a time, surviving each fault
ploop:
move.l SCIDX.l,d0
cmp.l #SCN,d0
bge.s pdone
lea SPCBASE,a0
adda.l d0,a0
lea SCVAL.l,a1
lea SCOK.l,a2
move.b #1,0(a2,d0.l) ; assume it answers; the handler undoes
move.b (a0),d1 ; <- the access under test
move.b d1,0(a1,d0.l)
addq.l #1,SCIDX.l
bra.s ploop
pdone:
; ---- TEMP (register 11 on the believed map, $EA0017) is a scratch latch on a
; real MB89352. Writing a pattern and reading it back separates "these odd
; bytes are registers" from "these odd bytes are a mirror of something".
; Guarded the same way: if it faults, the handler lands in ploop with SCIDX
; already past the end, falls through here again, and SCTMPOK stays 0.
move.b #$A5,SPCBASE+23
moveq #0,d0
move.b SPCBASE+23,d0
move.l d0,SCTMP.l
move.l #1,SCTMPOK.l
; ---- DOES A WRITE TO THE DATA REGISTER REACH THE CHIP AT ALL?
; $EA0015 is the one address x68k_scsiext.cpp puts its own glue on, and that
; glue DROPS a write when the DMAC's OWN is asserted and DRQ is low. A dropped
; write is invisible: no error, no status bit, nothing. So it is tested
; directly, before any SCSI protocol can be blamed for it. dreg_w enqueues into
; the FIFO, so DREG_EMPTY must fall between the write and the read, and the read
; must give the byte back.
bsr scsi_init
move.b #$5A,SPCBASE+21
moveq #0,d0
move.b SPCBASE+13,d0 ; SSTS: is the FIFO still empty?
move.l d0,SCDRS.l
moveq #0,d0
move.b SPCBASE+21,d0
move.l d0,SCDRG.l
; ---- the SPC is reachable; now make it fetch something. A read of the first
; SCBLKS sectors, in PIO, verified BY THE HOST against the same bytes in
; tmp/dlxdisk.img. That is the whole of P4's correctness half in one line: the
; player's own code selected a target, issued a READ(10) and got the disc's
; bytes back, with no IOCS and no host in the path.
bsr scsi_init
moveq #0,d3 ; LBA 0
moveq #SCBLKS,d4
lea SCDST,a1
bsr scsi_read
move.l d0,SCRD.l
move.l SC_ERR.l,SCER1.l ; SC_ERR is the LAST error, so it is
; captured per read: reading it once at
; the end reported the second read's
; failure against the first read's name.
; ---- and again, somewhere else on the disc. A read of LBA 0 is passed by a
; driver that emits a malformed LBA field, because zero is what a malformed
; field usually is. This one is not.
move.l #SCLBA2,d3
moveq #SCBLK2,d4
lea SCDST2,a1
bsr scsi_read
move.l d0,SCRD2.l
move.l SC_ERR.l,SCER2.l
move.l #1,SCFLAG.l
hold: bra.s hold
; ---- bus error. Mark the address dead, step past it, re-enter the loop with a
; stack pointer that is known good. The stacked frame is abandoned deliberately:
; there is nothing in it worth more than the next 57 addresses.
buserr:
move.l SCSAVSP.l,sp
move.l SCIDX.l,d0
cmp.l #SCN,d0
bge.s btmp
lea SCOK.l,a2
clr.b 0(a2,d0.l)
lea SCVAL.l,a1
move.b #$FF,0(a1,d0.l)
addq.l #1,SCIDX.l
jmp ploop
btmp: ; the fault was the TEMP writeback
move.l #1,SCFLAG.l
be: bra.s be
include "src/player/scsi.i"
+27
View File
@@ -406,6 +406,33 @@ grep -q "^OK" tmp/ringseek_check.log || {
echo "FAIL: the decode after the seek was not pixel-exact." echo "FAIL: the decode after the seek was not pixel-exact."
tail -4 tmp/ringseek_check.log; exit 1; } tail -4 tmp/ringseek_check.log; exit 1; }
echo "--- session 25: the 68000 reads the disc itself (FINDINGS 57) ---"
# ROADMAP P4, first half. Until now every byte the player consumed was placed in
# emulated RAM by a host: decode.lua preloaded a container, stream.lua answered a
# mailbox at a modelled rate. A player has no host. src/player/scsi.i selects a
# SCSI target on a real MB89352 and issues READ(10) itself.
#
# Session 21 recorded this as blocked -- "MAME's x68000 has no MB89352 path" --
# and that was wrong: `-exp1 cz6bs1` instantiates one, and FINDINGS 32.4 had
# already read its DMA glue in session 9. What was actually missing was the
# card's 8 KB boot ROM, which MAME requires to instantiate the device and which
# the player never executes. scsi_run.sh supplies a blank one on its own rompath.
#
# WHAT IS GATED: the register window (60 of 64 addresses -- the two holes ARE the
# MB89352's missing TMOD and EXBF, and they are what put DREG at $EA0015), and
# two READ(10)s verified byte-for-byte against the host's copy of the same image,
# one of them at a NON-ZERO LBA. Nothing here is gated on rate, and nothing here
# can be: MAME's device models are functional, not transfer-timing accurate.
# Skipped rather than failed when chdman is absent -- it ships with mame-tools.
if command -v chdman > /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) ---" 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 # 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 # here. tools/import/scenegraph.py is the ONE file in the tree that knows the
+170
View File
@@ -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)
+94
View File
@@ -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