Files
prosolis 1be428c270 Align the container to the disc, and find the decoder-free packed player fits
Two sessions, unrecorded until now, committed together because their edits
share files and cannot be split cleanly after the fact.

Session 28 (FINDINGS 60): the container is DLX5 -- every record sector-aligned,
120/120 starting on a boundary where 3/120 did, +0.48% on the wire and zero
clocks -- and the ring's release rounds to RECALN so no pad is stranded.  Two
encoder levers measured and refused: `--spans all` buys +0.19 dB for +67% of
the wire, and joint span/lam selection emits byte-identical containers because
`lam` never leaves its floor on any of 120 frames.

Session 29 (FINDINGS 61): the packed full-frame blit is 27.3% of a 12 fps
frame, a channel fills GVRAM in buffer mode off the disc with the CPU halted,
and it walks the 1,024 B line stride itself through array chaining.  At the
9 clk/B dual-address floor the codec is 110.4% of a frame and a decoder-free
packed literal player is 55.2%, at +4.89 dB -- 2.75 dB past a ceiling the
codec's scene-wide palette cannot cross.  Encoder work is parked; the codec is
kept and not built on.

check.sh is ALL GREEN before and after, plus one new stage that gates the ORDER
of the measured paint costs rather than their values.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-25 06:54:27 -07:00

523 lines
22 KiB
ArmAsm

; Full-frame GVRAM blit cost on a stock 68000 @ 10MHz.
;
; Answers: what fraction of a 12fps frame budget (833,333 cycles) does simply
; PUTTING a decoded 256x192 frame on screen cost, before any decoding?
;
; Geometry (tools/bench/crtc_mode.lua): 256-colour page, one pixel per WORD of
; CPU address space, 1024-byte line stride, picture in rows 32..223 of a
; 256-row page. So a row is 512 contiguous bytes of writes, then a 512-byte
; skip. 192 rows = 98,304 bytes of GVRAM write traffic per frame.
;
; Confirmed from MAME 0.277 x68k_crtc.cpp:501 (gvram_w, case 0x0100): a CPU
; write in 256-colour mode is masked to 0x00ff, so the HIGH byte of every word
; written is discarded by the hardware. V1 exploits this -- it never has to
; clear the odd bytes of its source.
;
; Three variants, selected by VAR, each looped ITER times:
; V1 movem.l blit from a word-expanded RAM frame (96KB). The realistic
; "decode to RAM, then blit" design. Reads 96KB, writes 96KB.
; V2 naive byte-source expansion (move.b / move.w per pixel). The obvious
; implementation, kept as the baseline V1 has to beat.
; V3 write-only floor: registers preloaded once, no source read at all.
; Nothing that puts this many pixels on screen can beat V3. The gap
; V1-V3 is the price of reading a source frame at all.
; V4 the SAME 96KB of writes, but issued in 4x4 BLOCK order instead of
; row-linear order. This is the access pattern a decoder that writes
; codewords straight into GVRAM actually has, and it is the number that
; picks the decoder architecture: compose-in-RAM-then-blit (V1) versus
; decode-direct-to-GVRAM (V4 scaled by the fraction of non-SKIP blocks).
; Each block is 4 rows of 8 bytes at a 1024-byte stride, so the
; destination displacements 0/1024/2048/3072 all fit a 16-bit offset and
; the block needs only one base pointer. V4 deliberately scrambles the
; picture (it reads a row-linear source in block order); it is a timing
; probe, which is why the correctness snapshot is taken after V1.
; V5 ROW-LINEAR LITERAL SPANS, the mode priced in FINDINGS 29 and never
; measured. Walks a stream of per-row span records
; row: u16 nspans, then nspans * { u16 x, u16 npix, npix*u16 pixels }
; for 192 rows, copying each span's word-expanded pixels straight from
; the stream buffer into GVRAM. Unlike V1-V4 the work per call is set by
; the STREAM, not by the code, so one variant measures every span length:
; tools/bench/prep_spans.py generates a stream per span length and
; tools/bench/span.lua times them and fits cycles = A*spans + B*pixels.
; The point of the measurement is A -- the per-span overhead FINDINGS 29
; guessed at 50 cycles -- and how much B degrades from V1's 9.08 when a
; span is too short to burst. Every config covers the whole frame, so
; V5 draws the SAME picture V1 does and can be verified, not just timed.
;
; Bursts are 8 registers (d0-d3/a3-a6 = 32 bytes = 16 pixels), not V1's
; 12: a0/a1/a2 and d4-d7 are all live across a span (stream, row base,
; destination, and three counters). The remainder is copied move.l at a
; time with a leading move.w when it is odd, so a 4-pixel span never
; reaches a movem at all -- which is exactly the case FINDINGS 29's
; full-row-width extrapolation flatters.
;
; V6 the SAME spans with the arithmetic moved into the encoder. V5 measures
; a decoder that is handed (x, npix) and has to work out how to copy it;
; most of its per-span cost is that working-out, and an encoder can do it
; once at build time instead of 12 times a second. V6's record is
; { u32 absolute GVRAM address, u16 jump displacement } -- no row
; structure, no counters, no remainder logic -- and the displacement
; jumps into an unrolled chain of 24-pixel copy units, so a span of any
; supported length is straight-line code with no loop at all.
; GVRAM sits at a fixed $C00000 on every X68000, so absolute destinations
; are a legitimate thing for an encoder to bake in.
;
; Two consequences of the format. Span lengths are multiples of 24
; pixels, and a span may overrun the 256 visible pixels of its row by up
; to 23 -- harmless, because the line stride is 1024 bytes and only the
; first 512 are displayed, so the overrun lands in the invisible half.
; And with row and remainder handling gone, 12 registers are free again
; (d0-d6/a1/a3-a6), which is why the unit is 24 pixels and not V5's 16.
;
; V7 v6 with a SECOND, finer chain for the tail (FINDINGS 39.4). v6 pays for
; its 24-pixel quantum in padding: an average span wastes ~11 pixels, and
; FINDINGS 39.3 attributes 86% of the DMAC array-chain's advantage over v6
; to exactly that. V7 keeps the 24-pixel coarse chain and appends a chain
; of 2-pixel units, so a span is 24*c + 2*f pixels and the padding is at
; most one pixel -- ZERO for the real case, where a span is a run of 4x4
; blocks and its length is a multiple of 4.
;
; The fine unit is `move.l (a0)+,(a2)+` (20 cycles, 2 pixels), NOT a
; 2-register movem: movem.l (a0)+,d0-d1 plus movem.l d0-d1,(a2) plus the
; lea is 52+8 cycles for 4 pixels, so the obvious "smaller movem" tail is
; 50% dearer per pixel than the plainest instruction on the machine.
;
; The second entry point costs a second dispatch, and the trick that pays
; for it is that the fine displacement is NOT in the span record: it sits
; in the STREAM, after the coarse pixels and before the fine ones. The
; coarse chain falls out into `move.w (a0)+,d0 / jmp`, by which point d0
; is dead payload and a0 is pointing exactly at it. So v7 holds nothing
; extra across the copy and keeps all 12 payload registers -- a record is
; still {u32 address, u16 displacement}, with one more u16 mid-span.
;
; 12 registers per movem burst (d0-d7/a2-a5 = 48 bytes) is the maximum
; available: a0=src, a1=dst, a6=end sentinel. The row counter lives in the
; a1-vs-a6 compare rather than a d-register for exactly this reason.
; 512 = 10*48 + 32, hence ten 12-register bursts and one 8-register tail.
; Destination uses (d16,a1) displacement rather than post-increment because
; movem cannot post-increment a destination; the displacement costs 4 cycles
; per burst but saves an 8-cycle lea, so it is the cheaper of the two.
FLAG = $18000 ; 0 idle / 1 running / $FF done
VAR = $18004 ; variant selector, written by Lua
ITER = $18008 ; iteration count, written by Lua
SPTR = $1800C ; V5 span stream pointer, written by Lua
SRCW = $60000 ; word-expanded frame 192*512 = 96KB
SRCB = $80000 ; byte-per-pixel frame 192*256 = 48KB
SRCP = $90000 ; PACKED frame 192*256 = 48KB (V8): two picture
; bytes per word, already interleaved by the
; encoder, so the blit is a straight copy
DST0 = $C08000 ; GVRAM + 32*1024 (first picture row)
DSTE = $C38000 ; GVRAM + 224*1024 (one past last)
ROWS = 192 ; picture rows a V5 stream describes
V6UNIT = 12 ; bytes of code per V6 chain unit
V6MAX = 11 ; chain units = 11*24 = 264 pixels >= one row
V7CU = 12 ; bytes of code per V7 COARSE unit (24 px)
V7CN = 11 ; coarse units: 11*24 = 264 px >= one row
V7FU = 2 ; bytes of code per V7 FINE unit (2 px)
V7FN = 11 ; fine units: 11*2 = 22 px > one coarse unit
org $10000
start:
move.l VAR.l,d0
move.l #1,FLAG.l ; timer starts here
cmp.l #1,d0
beq v1
cmp.l #2,d0
beq v2
cmp.l #4,d0
beq v4
cmp.l #5,d0
beq v5
cmp.l #6,d0
beq v6
cmp.l #7,d0
beq v7
cmp.l #8,d0
beq v8
cmp.l #9,d0
beq v9
cmp.l #10,d0
beq v10
bra v3
; ---------------------------------------------------------------- V1
v1: lea SRCW,a0
lea DST0,a1
lea DSTE,a6
v1row: movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,48(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,96(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,144(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,192(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,240(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,288(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,336(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,384(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,432(a1)
movem.l (a0)+,d0-d7
movem.l d0-d7,480(a1)
lea 1024(a1),a1
cmpa.l a6,a1
bne v1row
subq.l #1,ITER.l
bne v1
bra done
; ---------------------------------------------------------------- V2
v2: lea SRCB,a0
lea DST0,a1
lea DSTE,a6
v2row: move.w #255,d1
v2px: move.b (a0)+,d0
move.w d0,(a1)+ ; high byte is discarded by gvram_w
dbra d1,v2px
lea 512(a1),a1 ; skip the unused half of the line
cmpa.l a6,a1
bne v2row
subq.l #1,ITER.l
bne v2
bra done
; ---------------------------------------------------------------- V3
v3: lea SRCW,a0
movem.l (a0),d0-d7/a2-a5 ; load the burst once, outside the loop
lea DST0,a1
lea DSTE,a6
v3row: movem.l d0-d7/a2-a5,(a1)
movem.l d0-d7/a2-a5,48(a1)
movem.l d0-d7/a2-a5,96(a1)
movem.l d0-d7/a2-a5,144(a1)
movem.l d0-d7/a2-a5,192(a1)
movem.l d0-d7/a2-a5,240(a1)
movem.l d0-d7/a2-a5,288(a1)
movem.l d0-d7/a2-a5,336(a1)
movem.l d0-d7/a2-a5,384(a1)
movem.l d0-d7/a2-a5,432(a1)
movem.l d0-d7,480(a1)
lea 1024(a1),a1
cmpa.l a6,a1
bne v3row
subq.l #1,ITER.l
bne v3
bra done
; ---------------------------------------------------------------- V4
v4: lea SRCW,a0
lea DST0,a3 ; base of the current block row
lea DSTE,a4 ; one past the last block row
v4brow: move.l a3,a1
lea 512(a3),a5 ; 64 blocks * 8 bytes
v4blk: movem.l (a0)+,d0-d7 ; 32 bytes = one 4x4 block, expanded
movem.l d0-d1,(a1)
movem.l d2-d3,1024(a1)
movem.l d4-d5,2048(a1)
movem.l d6-d7,3072(a1)
addq.l #8,a1
cmpa.l a5,a1
bne.s v4blk
lea 4096(a3),a3 ; next block row is 4 picture lines
cmpa.l a4,a3
bne v4brow
subq.l #1,ITER.l
bne v4
bra done
; ---------------------------------------------------------------- V5
; a0 stream, a1 row base, a2 span destination, d7 rows, d6 spans, d5 pixels,
; d4 burst/tail counter. Everything else (d0-d3/a3-a6) is burst payload.
v5: move.l SPTR.l,a0
lea DST0,a1
move.w #ROWS-1,d7
v5row: move.w (a0)+,d6 ; spans in this row
subq.w #1,d6
bmi.s v5eor ; a row may legitimately have none
v5span: move.w (a0)+,d0 ; x, in pixels
add.w d0,d0 ; one pixel = one word
lea 0(a1,d0.w),a2
move.w (a0)+,d5 ; pixels in this span
move.w d5,d4
lsr.w #4,d4 ; 16-pixel bursts
beq.s v5tail
subq.w #1,d4
v5burst: movem.l (a0)+,d0-d3/a3-a6 ; 32 bytes straight out of the stream
movem.l d0-d3/a3-a6,(a2)
lea 32(a2),a2
dbra d4,v5burst
v5tail: moveq #15,d4
and.w d5,d4 ; 0..15 pixels left
beq.s v5eos
lsr.w #1,d4 ; C = odd pixel count
bcc.s v5t2
move.w (a0)+,(a2)+
v5t2: subq.w #1,d4
bmi.s v5eos
v5tl: move.l (a0)+,(a2)+
dbra d4,v5tl
v5eos: dbra d6,v5span
v5eor: lea 1024(a1),a1
dbra d7,v5row
subq.l #1,ITER.l
bne v5
bra done
; ---------------------------------------------------------------- V6
; a0 stream, a2 destination, d7 spans remaining; everything else is payload.
v6: move.l SPTR.l,a0
move.w (a0)+,d7 ; total spans in the frame
subq.w #1,d7
v6span: move.l (a0)+,a2 ; absolute GVRAM destination
move.w (a0)+,d0 ; (V6MAX - units) * V6UNIT, from the encoder
jmp v6ch(pc,d0.w)
v6ch:
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
dbra d7,v6span
subq.l #1,ITER.l
bne v6
bra done
; ---------------------------------------------------------------- V7
; a0 stream, a2 destination, d7 spans remaining; everything else is payload.
; Stream per span: u32 dest, u16 coarse disp, c*48 B pixels,
; u16 fine disp, f*4 B pixels.
v7: move.l SPTR.l,a0
move.w (a0)+,d7 ; total spans in the frame
subq.w #1,d7
v7span: move.l (a0)+,a2 ; absolute GVRAM destination
move.w (a0)+,d0 ; (V7CN - coarse) * V7CU
jmp v7ch(pc,d0.w)
v7ch:
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
movem.l (a0)+,d0-d6/a1/a3-a6
movem.l d0-d6/a1/a3-a6,(a2)
lea 48(a2),a2
v7cx: move.w (a0)+,d0 ; (V7FN - fine) * V7FU, from mid-stream
jmp v7fh(pc,d0.w)
v7fh:
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
move.l (a0)+,(a2)+
dbra d7,v7span
subq.l #1,ITER.l
bne v7
bra done
; ---------------------------------------------------------------- V8
; THE PACKED FULL-FRAME BLIT (FINDINGS 46.6/47.2). Identical in shape to V1 --
; a row-linear movem.l chain out of a RAM frame into GVRAM -- and different in
; exactly one thing: a row is 128 WORDS, not 256, because R20 bit 11 lets one
; word carry two picture bytes. 256 = 5*48 + 16, so five 12-register bursts
; and a 4-register tail, against V1's ten and one.
;
; TIMING ONLY, and it does not set bit 11. MAME's gvram_w carries no timing in
; either arm (blit.lua's header), so the bit cannot move a cycle here; what it
; moves is the PICTURE, and the picture is what tools/bench/show_frame256_packed.lua
; and tools/bench/gvpack already verify pixel-exactly. Setting it here would
; make this variant's snapshot right and its measurement no different, and
; would put a display-mode change inside a timing loop for no gain.
;
; The source is PRE-INTERLEAVED by the host, which is the honest half of the
; claim: the packing is an encoder-side transform (46.3's argument for the text
; plane, and the same one here), so the decoder-free player's per-frame work is
; this copy and nothing else. If the interleave had to happen at run time this
; variant would be V2, not V1.
v8: lea SRCP,a0
lea DST0,a1
lea DSTE,a6
v8row: movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,48(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,96(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,144(a1)
movem.l (a0)+,d0-d7/a2-a5
movem.l d0-d7/a2-a5,192(a1)
movem.l (a0)+,d0-d3
movem.l d0-d3,240(a1)
lea 1024(a1),a1
cmpa.l a6,a1
bne v8row
subq.l #1,ITER.l
bne v8
bra done
; ---------------------------------------------------------------- V9
; WHAT THE PACKED LAYOUT COSTS A BLOCK DECODER (FINDINGS 47.6.4, open).
;
; V4 is the access pattern of a decoder that writes 4x4 codewords straight into
; GVRAM: 4 rows of 8 contiguous bytes at a 1024-byte stride, so each row is one
; `movem.l` of two registers. Under the packed layout that pattern is GONE.
; A block at columns x..x+3 owns the LOW bytes of four consecutive words -- four
; bytes at STRIDE 2 -- and the high bytes of those same words belong to the
; block 128 columns away. There is no burst that writes every other byte, so
; the block is sixteen `move.b`s.
;
; V9 does the pair together, low block then high block off one base, so it
; writes every byte it touches and covers the same 49,152 pixels V1/V4/V8 do.
; It is the HONEST version of "keep the codec and pack the screen": the mode
; map is unchanged, SKIP still works per block, and the writes go byte at a
; time. V10 below is the other option, and the comparison is the point.
v9: lea SRCB,a0
lea DST0,a3
lea DSTE,a4
v9brow: move.l a3,a1
lea 256(a3),a5 ; 32 block PAIRS * 8 bytes
v9blk:
move.b (a0)+,(a1)
move.b (a0)+,2(a1)
move.b (a0)+,4(a1)
move.b (a0)+,6(a1)
move.b (a0)+,1024(a1)
move.b (a0)+,1026(a1)
move.b (a0)+,1028(a1)
move.b (a0)+,1030(a1)
move.b (a0)+,2048(a1)
move.b (a0)+,2050(a1)
move.b (a0)+,2052(a1)
move.b (a0)+,2054(a1)
move.b (a0)+,3072(a1)
move.b (a0)+,3074(a1)
move.b (a0)+,3076(a1)
move.b (a0)+,3078(a1)
move.b (a0)+,1(a1)
move.b (a0)+,3(a1)
move.b (a0)+,5(a1)
move.b (a0)+,7(a1)
move.b (a0)+,1025(a1)
move.b (a0)+,1027(a1)
move.b (a0)+,1029(a1)
move.b (a0)+,1031(a1)
move.b (a0)+,2049(a1)
move.b (a0)+,2051(a1)
move.b (a0)+,2053(a1)
move.b (a0)+,2055(a1)
move.b (a0)+,3073(a1)
move.b (a0)+,3075(a1)
move.b (a0)+,3077(a1)
move.b (a0)+,3079(a1)
addq.l #8,a1
cmpa.l a5,a1
bne v9blk
lea 4096(a3),a3
cmpa.l a4,a3
bne v9brow
subq.l #1,ITER.l
bne v9
bra done
; ---------------------------------------------------------------- V10
; THE OTHER OPTION: PAIR THE BLOCKS IN THE ENCODER. If the codec codes the
; block at x and the block at x+128 as ONE unit, the destination is whole words
; again and V4's `movem.l` shape comes straight back -- the same instructions,
; the same 32 bytes of source per unit, and TWICE the pixels, because a word now
; carries two of them. So V10 is V4's inner loop run half as many times.
;
; WHAT IT COSTS IS NOT IN THIS MEASUREMENT. A pair skips only if BOTH of its
; blocks skip, and the two are 128 columns apart with nothing in the picture
; relating them. That is a CONTAINER question -- what fraction of the mode map
; survives pairing -- and 08_mode_map.py has the data to answer it. V10 prices
; the paint; it does not price the SKIPs the pairing loses.
v10: lea SRCP,a0
lea DST0,a3
lea DSTE,a4
v10brow: move.l a3,a1
lea 256(a3),a5 ; 32 block PAIRS * 8 bytes
v10blk: movem.l (a0)+,d0-d7 ; 32 bytes = one PAIR of 4x4 blocks
movem.l d0-d1,(a1)
movem.l d2-d3,1024(a1)
movem.l d4-d5,2048(a1)
movem.l d6-d7,3072(a1)
addq.l #8,a1
cmpa.l a5,a1
bne.s v10blk
lea 4096(a3),a3
cmpa.l a4,a3
bne v10brow
subq.l #1,ITER.l
bne v10
bra done
done: move.l #$FF,FLAG.l ; timer stops here
halt: bra.s halt