Put the data phase on the DMAC, and find auto-request is charged by time

ROADMAP P4a. src/player/dma.i programs HD63450 channel 1 and takes the SCSI DATA
IN phase off the CPU; src/player/dmagate.s reads the same 2,048 B at LBA 1000
three ways -- PIO, the channel with the bus held, the channel stealing cycles --
and all three are byte-exact against the host's copy of the volume.

The evidence never reads $EA0015, because 57.3 established that it cannot: with
the DMAC's OWN asserted MAME cannot tell a CPU-driven byte there from a
DMAC-driven one. The discriminator is the CPU's own progress. MTC is sampled by
the INSTRUCTION AFTER the one that starts the channel, and held it reads 0 of
2,048 -- the whole transfer happened between two instructions, because the 68000
did not execute in between -- against the full count and 426 loop trips for the
stealing configuration. Put the stealing registers in the held slot and every
byte still arrives and tools/bench/dma_run.sh goes RED, which is what says the
counter can come out different; 58.3's vacuous "UNDERRUNS: 0/120" is the trap
being avoided. tools/analysis/27_dmac_config.py decodes the four register bytes
out of the player's own source, with the MC68450 field tables now in one copy
(tools/analysis/mc68450.py) shared with 21_iplrom_dmac.py, so the player's
configuration and the IPL ROM's 16..19 clk/B one are the same decoding.

Three bounds on the apparatus, read out of MAME 0.277 rather than inferred: the
CZ-6BS1 has NO request line to the DMAC (its flow control is DTACK), so external
request cannot be run; single address cannot be run either, because only channel
0 has device callbacks; and only burst is modelled as held. Of the four rows of
the W ladder exactly one -- dual address held -- has a code path here, and it is
the one demonstrated. W did not move by one clock, for the third session running.

What outlives the emulator is the currency. Every W in this project is clocks per
DELIVERED byte, which presumes the device asks; an auto-requested channel spends
its share of the bus whether or not a byte is there, so a record costs what it
costs to ARRIVE -- halve the delivery rate and the CPU cost of the same record
doubles. tools/analysis/28_autorequest_cost.py prices it from MC68450 3.8 and
5.2.3.3.2, gating its formulas against Table 5-3's sixteen rows first. At 37,405
B and an explicit 460 KB/s: max rate costs the whole 95.3% of a frame the record
takes to land, and of the GCR's four bus shares only BR=00, 50%, carries the
rate -- 10.61 clk/B, 47.6% of a frame, against 40.4% for the W=9 row and 391.8%
measured for PIO. The GCR is a design lever nothing in this tree had named.

59.4 changes what is left. sc_in_data now REFUSES a windowed read when the data
phase is the channel's (SCE_WINDOW), because a channel writes a contiguous run
and cannot drop the 300 B in front of a record. 117 of 120 records need one, so
sector-aligned records have gone from a preference in ROADMAP's re-encode bundle
to the precondition the transport enforces -- and that bundle is now the only
thing between this tree and M2.

One collision, recorded because the procedure is the finding: DM_USE first sat at
$18300, which is ring.i's XF_SLOT mailbox, and the P4b stage -- untouched by this
work -- went red on a run that never reached its snapshot. check.sh was ALL GREEN
before any of this, which is what made that red unambiguous. ALL GREEN after too,
with one new stage. decode.bin is unchanged at 1,296 B and the same MD5.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-24 23:55:07 -07:00
parent 5921fab118
commit 621a5bb457
14 changed files with 1342 additions and 58 deletions
+86
View File
@@ -1,3 +1,89 @@
# Status & next-session handoff — end of session 27 (2026-08-24)
## Session 27: the DMAC drives the data phase, and auto-request is charged by time
**Green light first and last: `./tools/bench/check.sh` was ALL GREEN before any
of this and ALL GREEN after**, plus one new stage.
**ROADMAP P4a is DONE at the transport level. FINDINGS 59.** **Emulated**
MAME 0.277, `x68000 -exp1 cz6bs1 -ramsize 2M`. No real hardware ran.
**1. THE CHANNEL DRIVES THE DATA PHASE.** `src/player/dma.i` programs HD63450
channel 1 and `src/player/dmagate.s` reads the same 2,048 B at LBA 1000 three
ways, with the host comparing all three against its own copy of the image:
| | bytes | MTC one instruction after START | CPU trips round its wait loop |
|---|---|---|---|
| PIO, the path 58.2 measured | byte-exact | — | — |
| **DMA, bus HELD** (DCR `$00`, OCR `$81`) | **byte-exact** | **0 of 2048** | **1** |
| **DMA, cycle STEALING** (DCR `$80`, OCR `$80`) | **byte-exact** | 2048 of 2048 | 426 |
Both channels reported `CSR = $E0`, `CER = $00`, `MTC = 0`, `MAR` exactly
`+2048`.
**2. THE EVIDENCE NEVER READS `$EA0015`, which is the whole design.** 57.3 said
it could not: with the DMAC's OWN asserted, MAME cannot tell a CPU-driven byte
at that address from a DMAC-driven one. So the discriminator is **the CPU's own
progress** — MTC sampled by the instruction *after* the one that starts the
channel. Held, the whole transfer happened between two instructions. **The gate
was checked against its own negative**: with the stealing register pair in the
held slot, every byte still arrives and `dma_run.sh` goes RED. A counter that
cannot come out different is 58.3's vacuous "UNDERRUNS: 0/120" again.
**3. THREE THINGS THIS MODEL CANNOT BE ASKED**, read out of MAME's source: the
card has **no request line to the DMAC** (its flow control is DTACK, so external
request cannot be run); **single address** cannot be run either (only channel 0
has device callbacks); and **only burst is modelled as held** (`(dcr & 0xc0) ==
0`, so cycle-steal-with-hold shares the not-held path). Of the four rows of the
`W` ladder, exactly one — dual address, held, 9 clk/B — has a code path here,
and it is the one demonstrated. The slot pinout has `#EXREQ` at B36, so a real
card plausibly drives it; that is ROADMAP B3, sharpened.
**4. AUTO-REQUEST IS CHARGED BY TIME, NOT BY BYTE — and this outlives the
emulator.** Every `W` in this project presumes the device asks for each byte. An
auto-requested channel spends its share of the bus whether or not a byte is
there, so a record's cost scales with **how long it takes to arrive**: halve the
rate and the CPU cost of the same record doubles. `tools/analysis/
28_autorequest_cost.py` prices it from MC68450 §3.8/§5.2.3.3.2, gating its
formulas against Table 5-3's sixteen rows first. At 37,405 B and **460 KB/s, an
explicit rate**: max rate costs the whole **95.3% of a frame**; of the GCR's
four bus shares only **BR = 00, 50%, carries the rate**, at 10.61 clk/B and
47.6% of a frame. **The GCR is a design lever nothing in this tree had named.**
**5. THE WINDOW IS REFUSED, so the re-encode is now a precondition.** A channel
writes a contiguous run and cannot drop the 300 B in front of a record, so
`sc_in_data` refuses a windowed read when the data phase is the DMAC's
(`SCE_WINDOW`) rather than writing the neighbours into the ring (49.2). 58.3's
"sector-aligned records should join the bundle" has become "the transport will
not run without them".
**ONE COLLISION, AND THE BEFORE-HALF OF `check.sh` CAUGHT IT.** `DM_USE` was
first placed at `$18300`, which is `ring.i`'s `XF_SLOT` mailbox — `scsi.i`'s
trace ends at `$182FF` and the next 160 bytes are the ring's. The ring rig's
first record request therefore wrote a non-zero word into what the transport
read as "use the DMAC", and **the P4b stage, which this session did not touch,
went red**. The map is now `$18500`. Green-before is not a formality: it is what
made a red in an untouched stage unambiguous. 59.6.
**New in the tree:** `src/player/dma.i`, `src/player/dmagate.s`,
`tools/bench/dma.lua`, `tools/bench/dma_run.sh`,
`tools/analysis/27_dmac_config.py` (decodes what the player programs, out of the
player's own source), `tools/analysis/28_autorequest_cost.py`,
`tools/analysis/mc68450.py` (the MC68450 field tables, now ONE copy, shared with
`21_iplrom_dmac.py`), and one `check.sh` stage. `src/player/scsi.i` gained the
`DM_USE` dispatch and `SCE_WINDOW`.
**No decoder code changed.** `decode.bin` is still 1,296 B at the same MD5.
**Next: THE RE-ENCODE BUNDLE, and it is now the only thing between this tree and
M2.** Four items sharing one re-measurement (ROADMAP P2): index-0 black,
`--spans all` as default, joint `lam`, and **sector-aligned records**, which
59.4 promoted from preference to precondition. After it, P4a's channel goes
behind `ring.i`'s mailbox and the 120-frame pixel-exact pass runs with the CPU
free — which is the shape of M2.
---
# Status & next-session handoff — end of session 26 (2026-08-24)
## Session 26: the player runs off the disc, and PIO costs 87 clocks a byte