Put the transport on the 68000, and find PIO costs 87 clocks a byte
ROADMAP P4b. src/player/xfer.i answers src/player/ring.i's XF_* mailbox with a real READ(10) to a real MB89352 in place of tools/bench/stream.lua's modelled transport: 120 records, 4,488,588 B, pixel-exact out of a 256 KB ring, with a real mid-stream seek in a second pass. The tiling is the SAME 18 wraps and 14.7 KB mean hole that 49.4's host producer and 55.4's modelled transport produced -- a third transport, same placement, which is the assertion that ring.i could not tell which side of the seam answered it. What it costs is the finding. tools/bench/xfer_cost.sh subtracts the same 120 frames run twice and gets 87.28 clocks per delivered byte, against the 68000's own cycle table for the loop, which says 87.15 -- 0.2% apart, so the cost is the instruction stream and not MAME's device model, and it is the first number this rig has produced that survives leaving the emulator. That is 391.8% of a 12 fps frame; the machine's own V-DISP clock agrees from the other end at 2.57 fps. Against the ladder, W=5 held is 22.4% of a frame and W=19 is 85.3%, so P4a is worth 4.6x the worst DMA configuration in this tree and 17.5x the best -- where before this session it was worth 9 against 19. W itself did not move by a clock. "UNDERRUNS: 0/120" is vacuous with a synchronous transport, and stream.lua now prints that argument next to the zero: a frame cannot start before its record has landed because the decoder IS the transport. The counter that means something is NO IDLE, 119/120 with a worst overrun of 441 whole ticks. Same class of error as 49.7.2's free-running ring passing at 48 KB. 58.3: a record is not a sector -- 117 of 120 start part way into one, and reading whole sectors into the ring corrupts the neighbours rather than wasting bytes (49.2, no bounds check). scsi.i reads the covering sectors and stores only the window, which is free in PIO and stops being free the moment P4a succeeds. tools/analysis/26_sector_align.py prices the three ways out and sector-aligned records win on both axes: +0.43% wire and zero clocks, against +1.34% and a bounce copy at +5 clk/B. ROADMAP now carries a four-item re-encode bundle and P4a should be attempted against a sector-aligned container. check.sh gains two stages and was ALL GREEN before and after. 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:
@@ -5382,3 +5382,160 @@ 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.
|
||||
|
||||
---
|
||||
|
||||
## FINDINGS 58 — the player runs off the disc, and PIO costs 87 clocks a byte (session 25b/26)
|
||||
|
||||
**Emulated.** MAME 0.277, `x68000 -exp1 cz6bs1 -ramsize 2M`, a blank
|
||||
`scsiexrom.bin` on a private rompath (57.1's substitution, unchanged). No real
|
||||
hardware ran. `./tools/bench/check.sh` was ALL GREEN before this and ALL GREEN
|
||||
after, with two new stages.
|
||||
|
||||
**ROADMAP P4b is DONE. P4a is not, and 58.2 is why it is now the item that
|
||||
decides the project rather than one of two that do.**
|
||||
|
||||
### 58.1 The seam closed: 120 pixel-exact frames, off a real volume
|
||||
|
||||
`src/player/xfer.i` sits behind `src/player/ring.i`'s `XF_*` mailbox in place of
|
||||
`tools/bench/stream.lua`'s modelled transport. `XF_GO` is answered by a real
|
||||
READ(10) to a real MB89352 and `XF_ACK` is a word the 68000 bumps when the bytes
|
||||
have landed — not one a host synthesises from emulated time.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| records fetched by the 68000 | **120**, one READ(10) each |
|
||||
| bytes into the ring | **4,488,588**, and the decode is **pixel-exact** |
|
||||
| bytes off the disc | **4,548,608** — see 58.3 |
|
||||
| ring | 256 KB, **18 wraps, 14.7 KB mean hole** |
|
||||
| a real mid-stream seek | **pass 2 pixel-exact**, ring thrown away and rebuilt |
|
||||
|
||||
**The 18 wraps are the load-bearing assertion, not the byte count.** They are
|
||||
the same 18 the host producer produced in 49.4 and the same 18 `ring.i`
|
||||
produced against a modelled transport in 55.4 — a *third* transport, same
|
||||
tiling. `ring.i` is not supposed to be able to tell which side of the mailbox
|
||||
answered it, and this is the number that says it could not.
|
||||
|
||||
**The change above the seam is two `bsr`s.** One in `ring_poll`, one in
|
||||
`ring_seek`'s quiet-wait — and the second is not optional. With the transport
|
||||
*inside* the machine, the only thing that can retire an outstanding request is
|
||||
that wait loop itself, so a seek issued with a request in flight spins forever
|
||||
without it. A host transport retired it on its own time. That is exactly the
|
||||
kind of difference the seam exists to hide, and it is the one it could not.
|
||||
|
||||
### 58.2 What it costs: 87.28 clocks per delivered byte, and the number is portable
|
||||
|
||||
`tools/bench/xfer_cost.sh` decodes the same 120 frames twice — same ring, same
|
||||
`stream.s`, same `ring.i` placing every record — and changes only which side of
|
||||
the mailbox answers:
|
||||
|
||||
| | emulated | per frame |
|
||||
|---|---|---|
|
||||
| decode + `ring_poll` alone | 6.7737 s | **67.7%** of a 12 fps frame |
|
||||
| ...with the real transport | 45.9516 s | **459.5%** |
|
||||
| **the transport** | **391,779,000 clk** | **391.8%** |
|
||||
|
||||
**= 87.28 clocks per delivered byte**, 86.13 per byte off the FIFO.
|
||||
|
||||
**AND IT IS NOT MAME'S NUMBER, WHICH IS THE HALF THAT MATTERS.** The keep loop
|
||||
in `src/player/scsi.i`, priced against the 68000's own cycle table — 12 patience
|
||||
reload, 16 SSTS read, 10 `btst`, 10 branch, 20 DREG read to `(a1)+`, 8 `subq`,
|
||||
10 branch — is **86 clocks**, and the FIFO also carries the dropped window bytes
|
||||
of 58.3, which makes it **87.15 per delivered byte**. Measured 87.28. **0.2%
|
||||
apart.** So the cost is the instruction stream and not a wait on MAME's SPC
|
||||
model: it is a figure a real board would also pay, and it is the first number
|
||||
this rig has produced that survives leaving the emulator. The residual, +0.13
|
||||
clk/B = 4,989 clocks per record, is the per-*command* cost — select, CDB,
|
||||
status, message, `xf_service` — and it is the part that does not scale with the
|
||||
record.
|
||||
|
||||
**Against the ladder, in the same units** (clocks charged to the CPU per
|
||||
delivered byte, at this container's 37,405 B mean record):
|
||||
|
||||
| | | share of a 12 fps frame |
|
||||
|---|---|---|
|
||||
| `W` = 5 | single address, bus HELD | 22.4% |
|
||||
| `W` = 9 | dual address, held | 40.4% |
|
||||
| `W` = 12 | single address, arbitrated | 53.9% |
|
||||
| `W` = 19 | dual address, arbitrated — the IPL ROM's own disk channel (52.5) | 85.3% |
|
||||
| **PIO 87** | **this rig, measured** | **391.8%** |
|
||||
|
||||
**The PIO transport is 4.6x the worst DMA configuration this project has found
|
||||
and 17.5x the best.** P4a is not an optimisation of this. It is the difference
|
||||
between a player and a slideshow, and it is now the *only* thing between the
|
||||
tree and M2.
|
||||
|
||||
**The player's own clock says the same thing, independently.** Self-paced off
|
||||
V-DISP, the machine decoded 120 frames in **560 slots** of a 12 fps clock:
|
||||
**2.57 fps**. That agrees with 12 / 4.595 = 2.61 from the cycle accounting
|
||||
above, from a completely different instrument.
|
||||
|
||||
**AND "UNDERRUNS: 0/120" IS VACUOUS IN THIS RUN.** A synchronous transport
|
||||
cannot underrun by construction — a frame cannot start before its record has
|
||||
landed, because the decoder *is* the transport. The counter that means anything
|
||||
here is `NO IDLE`: **119 of 120 frames found their slot already open, worst
|
||||
overrun 441 whole ticks.** `tools/bench/stream.lua` now prints that argument
|
||||
next to the zero rather than leaving the zero to be quoted. This is the same
|
||||
class of error as 49.7.2's free-running ring passing at 48 KB: a rig
|
||||
configuration in which the failure it tests for cannot occur.
|
||||
|
||||
**The resource that binds also flipped, and it is worth naming.** Against a
|
||||
modelled 488 KB/s pipe this container was rate-bound and the ring never filled
|
||||
(55.4). Here the ring **fills — 90 refusals for space** — while the decoder
|
||||
starves. Not of bytes: of *time*. Every previous delivery result in this tree
|
||||
was measured on a rig where the transport cost the CPU nothing.
|
||||
|
||||
### 58.3 A record is not a sector, and the cheapest fix is a re-encode
|
||||
|
||||
`ring.i` asks for a byte offset and a length, both 4-byte aligned (28.3);
|
||||
a target answers in 512 B blocks. **On the gate container 117 of 120 records
|
||||
start part way into a sector.** This is not a rounding nuisance: the bytes on
|
||||
either side of a record belong to *other* records the decoder may still be
|
||||
reading, and the block loop walks `a0` with no bounds check (49.2), so a
|
||||
transport that reads whole sectors straight into the ring **corrupts its
|
||||
neighbours** — wrong pixels, not a fault.
|
||||
|
||||
`tools/analysis/26_sector_align.py` prices the three ways out:
|
||||
|
||||
| | wire | clocks |
|
||||
|---|---|---|
|
||||
| **A. windowed PIO** — read the covering sectors, store only the record | **+1.34%** | 86/B on every byte off the FIFO, and **a DMAC cannot do it at all** |
|
||||
| **B. bounce buffer** — DMA whole sectors elsewhere, then copy | +1.34% | **+5/B on every delivered byte** = 22.4% of the frame, on top of `W` |
|
||||
| **C. sector-aligned records** — pad to 512 in the container | **+0.43%** | **zero** |
|
||||
|
||||
A is what shipped in `scsi.i` and what 58.1 ran, and it is free *only* because
|
||||
the CPU is already touching every byte — the property that disappears the moment
|
||||
P4a succeeds. B is the cost `aligned` was chosen over `split` to avoid (49.3)
|
||||
arriving by a different door, and on every byte instead of on a wrap.
|
||||
|
||||
**C wins on both axes**: it is cheaper on the wire than A and B by 0.91 points
|
||||
of the payload (40,940 B on this scene), and it is the only one of the three a
|
||||
DMA channel can run without a copy. What it costs is a **container revision** —
|
||||
a re-encode plus a re-measurement of every constant fitted to the gate
|
||||
container. That is the class of change ROADMAP already has bundled with P2's
|
||||
other half (reserve index 0 as black) and 55's two open re-encode questions.
|
||||
**It should join that bundle**, and P4a should be attempted against a
|
||||
sector-aligned container rather than against this one.
|
||||
|
||||
It also grows the largest record from 40,984 to 41,472 B, which a 256 KB ring
|
||||
still holds six times over — so it costs nothing in ring size.
|
||||
|
||||
### 58.4 What this does and does not move
|
||||
|
||||
**Does not move `W`.** Again, and for the same reason 57.6 gives. Nothing here
|
||||
programmed a DMAC channel; `xf_service` is the CPU standing in for one, through
|
||||
the DMAC's own door (57.3).
|
||||
|
||||
**Does not measure a delivery rate,** and the rig now refuses to be asked. A
|
||||
`DLX_XFER=scsi` run rejects a `--kbps` argument outright rather than ignoring
|
||||
it, and `stream.lua` suppresses `CHANNEL IDLE`, `DEADLINE` and `REQUIRED
|
||||
PREFILL` instead of printing them as zeros — a zero there reads as "the channel
|
||||
never stopped", which would be a claim about a medium this tree has never timed.
|
||||
|
||||
**Does close P4b, and does hand P4a a sharper question than it had.** Before
|
||||
this session, "get the DMAC to hold the bus" was worth 9 clk/B against 19. It is
|
||||
now worth 87 against either.
|
||||
|
||||
**Does put a real number on the layer below every previous delivery result.**
|
||||
Everything in FINDINGS 49, 51 and 55 was measured with the bytes arriving free.
|
||||
They are not free, and 58.2 is the first measurement of what they cost.
|
||||
|
||||
Reference in New Issue
Block a user