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
+33 -1
View File
@@ -143,6 +143,35 @@ configuration this project has found and 17.5x the best.** Getting the DMAC to
hold the bus is no longer worth 9 against 19; it is worth 87 against either, and
it is the only thing left before a player (FINDINGS 58).
**The DMAC drives the data phase now, and it holds the bus.** `src/player/dma.i`
programs an HD63450 channel and hands it the SCSI data phase: **the same 2,048
bytes come off the disc three ways — PIO, the channel with the bus held, the
channel stealing cycles — and all three are byte-exact.** The evidence that the
DMAC and not the CPU is driving it never looks at the data register, which
cannot answer the question: with the DMAC's OWN asserted, MAME cannot tell a
CPU-driven byte at `$EA0015` from a DMAC-driven one. What it looks at instead is
**the CPU's own progress**. MTC is sampled by the instruction *after* the one
that starts the channel; held, it reads **zero of 2,048** — the whole transfer
happened between two instructions, because the 68000 did not execute in between
— while the stealing configuration reads the full count and the CPU then goes
round its own loop 426 times. Put the stealing registers in the held slot and
the run still delivers every byte and the gate goes **red**, which is what says
the counter can come out different (FINDINGS 59.1).
**And auto-request is charged by time, not by byte.** The card as MAME models it
has **no request line to the DMAC at all** — its flow control is DTACK — so
every configuration that can be run against it is auto-request, and an
auto-requested channel does not know whether the device is ready: it spends its
share of the bus either way. Every `W` in this project is clocks per *delivered*
byte, which presumes the device asks; here the cost scales with **how long the
record takes to arrive**, so halving the delivery rate *doubles* the CPU cost of
the same record. Priced from the MC68450's own limited-rate constants against an
explicit 460 KB/s: max rate costs the whole **95.3% of a frame** the record takes
to land, and of the four bus shares the GCR can be programmed for — 50, 25,
12.5, 6.25% — **only 50% carries the rate**, at 10.61 clocks a byte and 47.6% of
a frame. The GCR is a design lever nothing in this tree had named (FINDINGS
59.3).
**A record is not a sector, and the cheapest fix is a re-encode.** 117 of 120
records start part way into a 512 B block, and reading whole blocks into the
ring corrupts the neighbouring records rather than merely wasting bytes — the
@@ -152,7 +181,10 @@ moment a DMA channel takes over. Priced three ways: windowed PIO is +1.34% on
the wire and cannot be done by a channel at all; a bounce buffer is +1.34% and
**+5 clocks on every delivered byte**, 22.4% of a frame; sector-aligning records
in the container is **+0.43% and zero clocks**. The last wins on both axes and
joins the re-encode bundle (FINDINGS 58.3).
joins the re-encode bundle (FINDINGS 58.3). **Session 27 made it a
precondition rather than a preference**: the transport now *refuses* a windowed
read when the data phase is the channel's, so the container has to meet it
before the DMAC can sit behind the ring (FINDINGS 59.4).
**The scene graph is in, and the worst gap between two decision points is
zero.** `tools/import/scenegraph.py` imports the arcade scene graph — 40 scenes,