Measure the span: the mode survives, and it is an encoder format

FINDINGS 29 priced a literal-span mode at 4*(50 + 4L*9.08) cycles and labelled
the whole section DERIVED. Session 8 step 0 was to measure it before optimising
over the mode set it implies. Two variants in blit.s, one stream per span length
from prep_spans.py, timed by span.lua, driven by span.sh in ~25 s:

  v5, handed (x, npix) and left to work the copy out:  97.9/span + 10.459/px
  v6, handed an address and a jump displacement:       43.7/span +  9.152/px
  29 assumed                                           50.0/span +  9.080/px

So 29's arithmetic was right about a format nobody had written. The difference
is not tuning: v5 spends ~122 cycles a span computing a destination, dividing
npix into bursts and handling a 0..15 remainder, all of which the encoder knows
at build time. v6's record is {u32 absolute GVRAM address, u16 jump
displacement} into an unrolled chain of 24-pixel copy units -- no loop, no
remainder, no arithmetic -- and it fits 11 span lengths to 0.3%.

Three things that measurement showed and derivation could not:

  - The per-pixel cost is a function of REGISTER PRESSURE. FINDINGS 24's 9.08
    was a fixed blit with 12 registers free; v5 can spare 8 and pays 10.46; v6
    gets 12 back only because the encoder holds the state.
  - Short spans die in the remainder path -- a 12-pixel span costs MORE than a
    16-pixel one -- and the fix is padding, not avoidance.
  - Odd-x alignment is free (259.0 vs 261.8 cycles/span), as a 16-bit bus
    implies but nobody had checked.

Re-priced against the unchanged mode maps, sasi: median 74.4% -> 52.0% (29 said
43.0), misses 37 -> 10/120 (29 said 8), 448.0 KB/s. Break-even moved from runs
of 2 blocks to runs of 4. 29.4 survives: a scene cut needs x >= 0.196 of the
frame as spans and the bus allows x <= 0.373, so it fits at 12fps.

All 23 timing configs are also checked pixel-exact, so none of this was timed
against a decoder that quietly skipped work.

FINDINGS 30. Next: lever B, the cost-aware mode decision.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-23 15:45:51 -07:00
parent 3641f37e28
commit 29eb78a599
9 changed files with 780 additions and 73 deletions
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# Measure the cost of a row-linear literal span on the 68000 (FINDINGS 30).
# ~25 s. Run from the repo root. Needs tmp/frame256.bin (check.sh makes it).
#
# NOT part of check.sh, for the same reason blit.s is not: the output is a wall
# timing, so gating on it would make the green light host-sensitive. What IS
# gated here is correctness -- all 23 configs must draw a pixel-exact frame,
# which is what stops a config timing fast by quietly writing nothing.
set -e
cd "$(dirname "$0")/../.."
[ -f tmp/frame256.bin ] || { echo "need tmp/frame256.bin -- run tools/bench/check.sh"; exit 2; }
python3 tools/bench/prep_spans.py
tools/vasm/vasmm68k_mot -Fbin -o tmp/blit.bin tools/bench/blit.s > /dev/null
mkdir -p tmp/snap_span
rm -f tmp/snap_span/x68000/*.png
( cd tmp && SDL_VIDEODRIVER=dummy timeout -k 5 1800 mame x68000 -bios ipl10 \
-ramsize 2M -video soft -window -sound none -nothrottle -plugins \
-autoboot_script ../tools/bench/span.lua \
-snapshot_directory ./snap_span -snapview native -seconds_to_run 150 \
> span.log 2>&1 )
grep -a "^\[SPAN\]" tmp/span.log
n=0
for f in tmp/snap_span/x68000/*.png; do
python3 tools/bench/verify_frame256.py "$f" > /dev/null || {
echo "FAIL: $f is not pixel-exact"; python3 tools/bench/verify_frame256.py "$f"; exit 1; }
n=$((n+1))
done
[ "$n" -eq 23 ] || { echo "FAIL: $n snapshots, expected 23"; exit 1; }
echo "OK $n/23 span configs drew a pixel-exact frame"