The bus is 4x idle while the CPU is pinned: price the trade
The codec was designed when bytes were scarce, so every decision in it trades
cycles to save bytes. That is now backwards: sasi spends 110 KB/s of a 488 KB/s
pipe while missing 31% of frames on CPU.
The cheapest thing a 68000 can be handed is the most expensive thing to store.
Measured, per pixel: row-linear copy from word-expanded memory 9.08 cycles,
block-order 12.98, V1 codebook 18.74, RAW byte literals 25.03. So the 1024-byte
stride costs 43% and unpacking bytes to words costs more than the write itself.
Pricing one new mode -- a per-row span of word-expanded literals movem.l'd
straight from the stream buffer -- against the UNCHANGED mode maps:
sasi median 74.4% -> 43.0%, worst 136.2% -> 106.2%, misses 37 -> 8/120,
101.7 -> 453.2 KB/s
scsi median 94.9% -> 69.4%, misses 51 -> 18/120, 272 -> 479.7 KB/s
scsi gains less precisely because it has less idle bandwidth left to trade.
Two consequences worth flagging. A word-expanded literal block derives to ~240
cycles, cheaper than V1's measured 299.9 and pixel-exact -- so every codebook
mode is CPU-dominated by a literal, and the codebook is a byte optimisation
that now costs cycles. And 28.5's "a scene cut cannot fit at 12fps" reopens:
CPU needs >=19% of the frame as spans, the bus allows <=39%, and that interval
is not empty.
DERIVED, NOT MEASURED, and labelled as such everywhere. The 9.08 cycles/pixel
is real but was measured at full row width with 12-register bursts, so short
spans are flattered. Measuring one span on the 68000 is now step 0 of the next
session, ahead of the cost-aware mode decision, because it changes the mode set
that decision optimises over.
FINDINGS 29. tools/analysis/12_span_tradeoff.py.
Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
@@ -1423,3 +1423,114 @@ Caveat: this ordering is a property of *this* decoder, not of the codec. V4's
|
||||
cost is four indexed `movem.l` lookups; pairing sub-block rows into
|
||||
`movem.l d0/d2,(a4)` would save ~16 of 448 cycles, which narrows the gap to RAW
|
||||
without closing it.
|
||||
|
||||
---
|
||||
|
||||
## 29. Trading bytes for cycles: the bus has 4x the headroom the CPU has (session 7)
|
||||
|
||||
> **STATUS: DERIVED, NOT MEASURED.** No 68000 has executed a span decoder. The
|
||||
> per-pixel figure it rests on *is* measured (FINDINGS 24 V1) but at full row
|
||||
> width; the per-span overhead is hand-derived. Treat every number below as a
|
||||
> hypothesis with a test attached, not as a result. FINDINGS 4 is why.
|
||||
|
||||
FINDINGS 28 leaves the project CPU-bound while the **bus sits 4x idle**: `sasi`
|
||||
spends 110 KB/s of a 488 KB/s pipe. That asymmetry is exploitable, because the
|
||||
codec was designed when bytes were the scarce thing and every one of its
|
||||
decisions trades cycles to save them.
|
||||
|
||||
### 29.1 The decoder pays per changed PIXEL; the disk pays per BYTE
|
||||
Per-pixel costs, all measured:
|
||||
|
||||
| what | cycles/pixel | source |
|
||||
|---|---:|---|
|
||||
| write-only floor (no source read) | 4.59 | FINDINGS 24 V3 |
|
||||
| **row-linear copy from word-expanded RAM** | **9.08** | FINDINGS 24 V1 |
|
||||
| block-order copy, same bytes | 12.98 | FINDINGS 24 V4 |
|
||||
| V1 codebook block | 18.74 | FINDINGS 28.2 |
|
||||
| RAW, byte literals unpacked to words | 25.03 | FINDINGS 28.2 |
|
||||
| naive per-pixel byte expansion | 26.13 | FINDINGS 24 V2 |
|
||||
|
||||
Two structural facts fall out. **The 1024-byte stride costs 43%** — the same
|
||||
bytes cost 12.98 cycles/px in 4x4 block order against 9.08 row-linear, because
|
||||
the stride breaks the `movem.l` burst. And **unpacking bytes to words costs more
|
||||
than the write itself**: 25.03 against 9.08.
|
||||
|
||||
So the two cheapest things a decoder can be handed are *word-expanded* pixels
|
||||
in *row-linear runs* — and both cost bytes on disc, which is what we have.
|
||||
|
||||
### 29.2 Codebooks are a byte optimisation that now costs cycles
|
||||
A word-expanded literal 4x4 block, `movem.l (a0)+,d0-d7` straight from the
|
||||
stream buffer into GVRAM, derives to **~240 cycles** — cheaper than V1's
|
||||
measured 299.9, and pixel-exact. V1 is dearer *because* it is compressed: it
|
||||
pays an index decode and an indexed `movem.l` that a literal does not, and then
|
||||
does exactly the same four writes. It buys 31 bytes and spends 60 cycles.
|
||||
|
||||
**Every codebook mode is CPU-dominated by a literal.** V4 was already dominated
|
||||
by RAW (28.8); with word-expanded literals available, so is V1. The VQ codebook
|
||||
earns its place only while bytes are scarce.
|
||||
|
||||
### 29.3 Row-linear literal spans, priced against the real mode maps
|
||||
Replace the per-block escape with a per-row **span**: `(x, count, word-expanded
|
||||
pixels)`, decoded with `movem.l` bursts. A run of L horizontally adjacent dirty
|
||||
blocks becomes 4 spans of 4L pixels, deriving to `4 * (50 + 4L * 9.08)` cycles
|
||||
against `300L` for V1 — **cheaper for any run of 2 blocks or more**, at 32 bytes
|
||||
per block instead of 1.
|
||||
|
||||
Applied greedily (buy the best cycles-saved-per-byte until the bus budget is
|
||||
gone) to the *unchanged* mode maps of the `sasi` Singe window:
|
||||
|
||||
| | today | + literal spans |
|
||||
|---|---:|---:|
|
||||
| median frame | 74.4% | **43.0%** |
|
||||
| p90 frame | 115.1% | **83.6%** |
|
||||
| worst frame | 136.2% | **106.2%** |
|
||||
| frames missing the budget | **37/120** | **8/120** |
|
||||
| bitrate | 101.7 KB/s | 453.2 KB/s (bus 488) |
|
||||
|
||||
And the fit is structural rather than lucky: **spans get cheaper exactly where
|
||||
blocks get expensive.** A span amortises its overhead over a long run, and long
|
||||
runs are what a high-change frame is made of. The frames that miss today are the
|
||||
frames spans help most.
|
||||
|
||||
### 29.4 This reopens 28.5, which said a scene cut cannot fit
|
||||
28.5 concluded that no mode assignment fits a 100%-changed frame at 12fps,
|
||||
because the cheapest full redraw available — all-V1 — is 110.5%. That was true
|
||||
of *the mode set the codec has*. Adding a byte-expensive, cycle-cheap mode
|
||||
changes the arithmetic: mixing a fraction `x` of the frame as spans against V1
|
||||
for the rest,
|
||||
|
||||
- CPU needs `x >= 0.19`
|
||||
- the 40,977 B/frame bus budget allows `x <= 0.39`
|
||||
|
||||
**The interval is not empty.** A scene cut fits at 12fps if roughly a quarter to
|
||||
a third of it arrives as word-expanded row-linear literals. 28.5's "structural
|
||||
ceiling" was a ceiling of the bitstream, not of the machine.
|
||||
|
||||
### 29.5 What has to be measured before any of this is believed
|
||||
1. **Span cost on the 68000.** The 50-cycle per-span overhead is derived, and
|
||||
the 9.08 cycles/px is measured at *full row width* with 12-register bursts —
|
||||
a short or oddly-aligned span cannot burst as well, so short spans are
|
||||
flattered here. Extend `tools/bench/blit.s` with a span variant and measure
|
||||
it against run length. **This is the load-bearing number.**
|
||||
2. **Re-run the ring-buffer simulation at ~450 KB/s.** FINDINGS 21's zero
|
||||
required prefill was established at 110 and 280 KB/s against a 488 KB/s pipe.
|
||||
At 453 the margin is a tenth of what it was, and 21's own caveat was that the
|
||||
test is cumulative — it needs redoing, not extrapolating.
|
||||
3. **Confirm the 4 Mbps figure**, which is user-supplied with no recorded
|
||||
provenance and which this design would run at 93% of. It has been a "would be
|
||||
nice" since session 1; a design that leans on it makes it load-bearing.
|
||||
4. **Confirm DMA, not PIO** (STATUS priority 5). At 453 KB/s a PIO fallback puts
|
||||
the transfer cost on the CPU we are trying to relieve. Cheapest check
|
||||
available and now the most consequential.
|
||||
|
||||
### 29.6 The other lever, not yet costed: let the DMAC do the copy
|
||||
The X68000 has an HD63450 DMAC (4 channels, `x68k.cpp:1046`). Channel 3 is
|
||||
ADPCM — confirmed, `adpcm_drq_tick` asserts `drq3_w` — but memory-to-memory
|
||||
transfer on a free channel would take the GVRAM copy off the CPU entirely,
|
||||
leaving it only the parsing. This is the one idea here that could move the
|
||||
budget without spending a single extra byte.
|
||||
|
||||
It cannot be settled in MAME: like the SCSI/SASI devices (BENCHMARK.md), the
|
||||
HD63450 is a functional model, so a timing number out of it would measure the
|
||||
emulator's scheduler. It needs hand-derivation against the datasheet plus real
|
||||
hardware — the same three-tier approach the disk benchmark already documents.
|
||||
|
||||
Reference in New Issue
Block a user