Measure the finer chain tail: 84/120 becomes 18/120, and the derivation was right by cancellation

blit.s gains v7 -- v6's 24-pixel movem chain plus a second chain whose unit is
one `move.l (a0)+,(a2)+`. Measured over 13 span lengths by span.sh, every config
pixel-exact:

    cycles = 66.0 per span + 9.143 per COARSE pixel + 9.978 per FINE pixel

fitting all 13 to within 0.2%. v5 and v6 re-measure to FINDINGS 30 exactly, so
the harness has not drifted underneath the new variant.

Rescored against the same scsi window and the same additive model, v7 takes
84/120 frames over budget to 18/120 -- exactly what FINDINGS 39.4 derived, and
that agreement is two cancelling errors: the derivation's 2-register movem tail
is 29% too dear per pixel, and its "nothing per span" for the second chain entry
is 22.3 clocks too cheap. The plain post-incrementing move.l is the right tail
instruction, and it makes the padding quantum 2 pixels, which a run of 4x4
blocks pads to exactly zero.

The DMAC stays dropped on a measurement now rather than an argument: v7 takes
back 37 of the 43 frames the array chain would, with no reserved channel and no
timing neither emulator here can verify. Break-even against all-V1 moves from
L=4 blocks to L=2.

The fine displacement is carried mid-stream rather than in the span record, so
the decoder holds nothing across the copy and keeps all 12 payload registers --
which is the whole reason the coarse unit is 24 pixels.

span.sh is now -seconds_to_run 200 (30 s wall, 36 configs) and takes its
expected snapshot count from the generated metadata instead of a literal 23.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-23 19:02:23 -07:00
parent c5ca56330e
commit c520a89e14
9 changed files with 522 additions and 52 deletions
+15 -10
View File
@@ -41,8 +41,11 @@ CPU + DMA, additive. Session 10's first pass used max(CPU, bus) and got 53/120
where the additive model gives 84/120; FINDINGS 35's flat debit was right.
So the only material difference left is v6's 24-pixel padding quantum -- and
that is a property of v6's unrolled chain, not of the CPU. The `v6 fine tail`
column prices fixing it in software instead.
that is a property of v6's unrolled chain, not of the CPU. The `v7 fine tail`
column prices fixing it in software instead, and as of session 11 that column
is MEASURED on the 68000 (blit.s v7, tools/bench/span.sh, FINDINGS 40) rather
than derived: 66.0 clocks per span + 9.143 per coarse pixel + 9.978 per fine
pixel, with a 2-pixel quantum that a run of 4x4 blocks pads to exactly.
"""
import sys, os, argparse
sys.path.insert(0, "tools/encoder")
@@ -100,8 +103,8 @@ def span_cost(design, L):
if design == "v6":
px = B.pad24(4 * L)
return 4 * px, 4 * (B.V6_SPAN_CYC + px * B.V6_PX_CYC)
if design == "v6fine":
px, c = B.v6_fine(4 * L)
if design == "v7":
px, c = B.v7_span(4 * L)
return 4 * px, 4 * c
px = 4 * L
return 4 * px, 4 * (B.DMA_CHAIN_CLK + px * a.dma_px_clk)
@@ -127,7 +130,9 @@ def score(design):
cur_b = sum(BLK_B[int(b)] for b in m[by][i:j])
px, sc = span_cost(design, L)
sc += L * C_SKIP_MIXED # the dispatch still happens
span_b = 4 * SPAN_HDR + px * SPAN_BYTES_PX
# v7 carries a second u16 (the fine displacement) per span.
hdr = B.V7_SPAN_HDR if design == "v7" else SPAN_HDR
span_b = 4 * hdr + px * SPAN_BYTES_PX
if sc < cur_c:
cand.append((cur_c - sc, span_b - cur_b, by, i, j, sc, L))
cand.sort(key=lambda s: -(s[0] / max(s[1], 1)))
@@ -154,7 +159,7 @@ def score(design):
DESIGNS = [("today", "none"), ("v6 span", "v6"),
("v6 fine tail", "v6fine"), ("DMAC chain", "dmac")]
("v7 fine tail", "v7"), ("DMAC chain", "dmac")]
res = {n: score(k) for n, k in DESIGNS}
print(f"{a.container}: {d.nframes} frames, {d.nb} blocks, {a.fps:g} fps")
@@ -188,11 +193,11 @@ print(f"\n ADDITIVE: frame = CPU + span painting + disk DMA. The 68000 has no"
# What is left of the case, isolated.
v6m = int((res["v6 span"][0] > FRAME_CYC).sum())
finem = int((res["v6 fine tail"][0] > FRAME_CYC).sum())
finem = int((res["v7 fine tail"][0] > FRAME_CYC).sum())
dmam = int((res["DMAC chain"][0] > FRAME_CYC).sum())
print(f"\nWHAT THE DMAC ACTUALLY BUYS, decomposed")
print(f" v6 as built {v6m}/{d.nframes} frames over")
print(f" v6 with a finer chain tail (software) {finem}/{d.nframes}")
print(f" v7, a finer chain tail (MEASURED) {finem}/{d.nframes}")
print(f" DMAC chain {dmam}/{d.nframes}")
print(f" -> of the gap between v6 and the DMAC, "
f"{100*(v6m-finem)/max(v6m-dmam,1):.0f}% is the 24-pixel padding")
@@ -206,9 +211,9 @@ print(f" in software. The rest is 1.7% a pixel and 7.7 clocks a span.")
print(f"\nbreak-even against all-V1 ({C_V1:.1f} cycles/block), clocks per block")
print(f" {'L':<16}" + "".join(f"{L:>8}" for L in (1, 2, 3, 4, 8, 16, 64)))
for nm, dz in (("v6 as built", "v6"), ("v6 fine tail", "v6fine"), ("DMAC chain", "dmac")):
for nm, dz in (("v6 as built", "v6"), ("v7 fine tail", "v7"), ("DMAC chain", "dmac")):
print(f" {nm:<16}" + "".join(f"{span_cost(dz, L)[1]/L:>8.0f}"
for L in (1, 2, 3, 4, 8, 16, 64)))
for nm, dz in (("v6 as built", "v6"), ("v6 fine tail", "v6fine"), ("DMAC chain", "dmac")):
for nm, dz in (("v6 as built", "v6"), ("v7 fine tail", "v7"), ("DMAC chain", "dmac")):
brk = next((L for L in range(1, 65) if span_cost(dz, L)[1] < L * C_V1), None)
print(f" {nm:<16} beats all-V1 from L={brk} blocks up")