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
+57 -11
View File
@@ -108,7 +108,8 @@ local function launch(cfg)
push(STREAM, blob, cfg.off+1, cfg.len)
clear_picture()
-- ~4 emulated seconds per config: 1/55.46 s granularity costs under 0.5%.
local est = cfg.nspans*(cfg.var == 6 and 50 or 60) + cfg.npix*10
local est = cfg.nspans*(cfg.var == 5 and 60 or (cfg.var == 6 and 50 or 70))
+ cfg.npix*10
cfg.iter = math.max(4, math.floor(4*CPUHZ/est))
SP:write_u32(FLAG, 0)
SP:write_u32(VAR, cfg.var)
@@ -143,6 +144,34 @@ local function fit(rs)
return (sy*pp - py*sp)/det, (ss*py - sp*sy)/det
end
-- v7 has two per-pixel costs -- the 24-pixel coarse chain and the 2-pixel fine
-- chain -- so its fit is cycles = A*spans + Bc*coarse_px + Bf*fine_px, solved
-- by plain Gaussian elimination on the 3x3 normal equations. prep_spans.py
-- picks span lengths so every fine remainder a real span can have (0,4,..,20)
-- appears, which is what makes the three terms separable.
local function fit3(rs)
local M3 = {{0,0,0,0},{0,0,0,0},{0,0,0,0}}
for _,r in ipairs(rs) do
local x = {r.cfg.nspans, r.cfg.cpx, r.cfg.fpx}
for i=1,3 do
for j=1,3 do M3[i][j] = M3[i][j] + x[i]*x[j] end
M3[i][4] = M3[i][4] + x[i]*r.cyc
end
end
for c=1,3 do
local piv = c
for r=c+1,3 do if math.abs(M3[r][c]) > math.abs(M3[piv][c]) then piv=r end end
M3[c], M3[piv] = M3[piv], M3[c]
for r=1,3 do
if r ~= c then
local f = M3[r][c]/M3[c][c]
for k=c,4 do M3[r][k] = M3[r][k] - f*M3[c][k] end
end
end
end
return M3[1][4]/M3[1][1], M3[2][4]/M3[2][2], M3[3][4]/M3[3][3]
end
local step, st, t0 = 0, "boot", nil
SUB = emu.add_machine_frame_notifier(function()
@@ -159,7 +188,7 @@ SUB = emu.add_machine_frame_notifier(function()
report(SPEC.configs[step], t - (t0 or t))
st = "snap"; return
end
if t > 300 then P("TIMEOUT flag="..string.format("%08X",fl)); M:exit() end
if t > 900 then P("TIMEOUT flag="..string.format("%08X",fl)); M:exit() end
return
end
if st == "snap" then
@@ -174,7 +203,7 @@ SUB = emu.add_machine_frame_notifier(function()
end
if st == "finish" then
P("---- measured (instruction cycles only; real GVRAM adds wait states) ----")
for _,v in ipairs{5,6} do
for _,v in ipairs{5,6,7} do
local sub = {}
for _,r in ipairs(results) do if r.cfg.var == v then sub[#sub+1] = r end end
-- v5's fit is over its BURSTING configs only (span length a multiple of
@@ -183,13 +212,22 @@ SUB = emu.add_machine_frame_notifier(function()
-- instead, which is where the remainder shows up as error.
local fitset = {}
for _,r in ipairs(sub) do
if v == 6 or r.cfg.p % 16 == 0 then fitset[#fitset+1] = r end
if v ~= 5 or r.cfg.p % 16 == 0 then fitset[#fitset+1] = r end
end
local A, Bp, Bf
if v == 7 then
A, Bp, Bf = fit3(fitset)
P(string.format("-- v7: cycles = %.1f per span + %.3f per COARSE pixel"
.." + %.3f per FINE pixel (fitted on %d of %d configs)",
A, Bp, Bf, #fitset, #sub))
else
A, Bp = fit(fitset)
Bf = Bp
P(string.format("-- v%d: cycles = %.1f per span + %.3f per pixel"
.." (fitted on %d of %d configs)", v, A, Bp, #fitset, #sub))
end
local A, Bp = fit(fitset)
P(string.format("-- v%d: cycles = %.1f per span + %.3f per pixel"
.." (fitted on %d of %d configs)", v, A, Bp, #fitset, #sub))
for _,r in ipairs(sub) do
local model = A*r.cfg.nspans + Bp*r.cfg.npix
local model = A*r.cfg.nspans + Bp*r.cfg.cpx + Bf*r.cfg.fpx
P(string.format(" span %4s px %8.0f cyc %5.2f cyc/px %6.1f cyc/span"
.." vs fit %+6.1f%%", r.cfg.name, r.cyc,
r.cyc/r.cfg.npix, r.cyc/r.cfg.nspans, 100*(model/r.cyc-1)))
@@ -199,9 +237,17 @@ SUB = emu.add_machine_frame_notifier(function()
-- v6 pads each to a whole 24-pixel chain unit.
local line = " -> cycles per 4x4 block in a run of L blocks: "
for _,L in ipairs{1,2,4,8,16,64} do
local px = 4*L
if v == 6 then px = math.ceil(px/24)*24 end
line = line..string.format("L=%d %.0f ", L, 4*(A + px*Bp)/L)
local px, cyc = 4*L, nil
if v == 6 then
px = math.ceil(px/24)*24
cyc = A + px*Bp
elseif v == 7 then
local c = math.floor(px/24)*24
cyc = A + c*Bp + (px-c)*Bf -- a multiple of 4 pads to nothing
else
cyc = A + px*Bp
end
line = line..string.format("L=%d %.0f ", L, 4*cyc/L)
end
P(line.."(V1 is 299.9)")
if v == 5 then