Put the palette on the channel, and find one start paints a whole frame

ROADMAP K1, the packed player's one open structural item.  A frame is a picture
AND a palette, and no run in this tree had pointed a DMA channel at the palette
registers.  dmagate.s runs 7-9, gated by dma_run.sh and check.sh:

  7. 512 B off the disc into $E82000, bus held -- byte-exact in 256 register
     words, read back OUT OF the registers by the 68000;
  8. the SAME transfer aimed at RAM -- byte-exact at $2C000, and 256 of 256
     palette words still read the poison the CPU wrote, which is what attributes
     run 7 to the channel's MAR rather than to the readback path;
  9. ONE array-chained start across two kinds of destination -- the palette and
     six picture rows at the 1,024 B line stride, 2,048 B byte-exact.

So a packed frame is one channel start: a 193-entry array, palette first, CPU
halted from the first byte to the last.  The array is scene-constant, because
the packed layout spends both 256-colour pages and there is no page to flip.
What is left on the CPU per frame in the video path is the channel start and the
READ(10) -- no per-frame PAINT, which is not the same claim as no per-frame CPU.

The destination is POISONED first (62.1).  Runs 4-6 wrote into RAM that was zero
and GVRAM that was stale against a record that is mostly pad; "it matches the
disc" was weaker than it read as.  The host counts whether the poison actually
discriminates instead of assuming it: 511 of 512, and the gate refuses under 500.

And it opened a hardware item (62.4, ROADMAP B4).  MAME maps the palette to
palette_device over memory_array, whose write16 is a plain COMBINE_DATA -- RAM
that honours mem_mask, with no handler that could refuse a byte write.  Unlike
GVRAM's 256-colour arm there is nothing here to be wrong about, so the run
bounds the model and not the board.  What a real palette register does with a
byte write is unmeasured.  A negative costs 0.28% of a frame and nothing else.

29_packed_player.py now also prints the two rows with the per-frame palette
charged -- 55.7% of a frame on the chain, 582 KB/s -- alongside the picture-only
figures the codec comparison is quoted against.

check.sh ALL GREEN before (tmp/check_s30_start.log) and after
(tmp/check_s30_end.log).

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 07:06:44 -07:00
parent 1be428c270
commit 07f36c2af9
9 changed files with 664 additions and 32 deletions
+163 -3
View File
@@ -37,13 +37,21 @@
; burst modelled as held.
DGFLAG = $18600 ; 0 idle / 1 done
DGREC = $18610 ; 6 x 32 B: rc, err, mtc0, spin, csr, cer, mtc, mar
DGREC = $18800 ; 9 x 32 B: rc, err, mtc0, spin, csr, cer, mtc, mar
; $18800 AND NOT $18610, WHERE THIS LIVED: nine
; records of 32 B run to $188FF, and from $18610
; they would have run over DGWIN at $18700 --
; the window run's own result, which run 10 then
; writes back. A silent overlap between two
; runs' evidence is the kind of thing that makes
; a gate report the wrong run's numbers.
DGREC_SZ = 32
DGWIN = $18700 ; u32 return of the WINDOWED dma read (want -1)
DGWERR = $18704 ; u32 SC_ERR after it (want SCE_WINDOW)
DGR20 = $18708 ; u32 R20 as it stood during run 4
DGR20N = $1870C ; u32 R20 as it stood during run 5 (the control)
DGR20C = $18710 ; u32 R20 as it stood during run 6 (chained)
DGR20P = $18714 ; u32 R20 as it stood during run 9 (palette+rows)
R20_BUF = $0916 ; 256-colour, 31.5 kHz, G-VRAM SET TO BUFFER
R20_MSK = $0116 ; the SAME, bit 11 CLEAR -- the negative control
DGDST4 = $C0C000 ; where the control writes
@@ -60,6 +68,25 @@ DGDST0 = $20000 ; PIO
DGDST1 = $24000 ; DMA, bus held
DGDST2 = $28000 ; DMA, cycle stealing
; ---- runs 7-9: THE PALETTE (ROADMAP K1, FINDINGS 61.9's first open item).
DGPAL = $E82000 ; the GRAPHIC palette: 256 words, GGGGGRRRRRBBBBBI
DGPALN = 256 ; words in it -- and 512 B is exactly ONE sector
DGPBLK = 1 ; so the whole palette is one block off the disc
DGPOIS = $A500 ; the poison: word i = DGPOIS|i. A palette that
; still reads this was not written by anything.
DGDST6 = $2C000 ; run 8's destination: RAM, so the palette is
; left alone and must still read poison
DGDST7 = $C14000 ; run 9's six GVRAM rows, at the 1024 B stride
DGCHA2 = $19100 ; run 9's array: 7 x {u32 MAR, u16 MTC}
DGCHN2 = 7 ; palette 512 B + 6 rows x 256 B = 2,048 B
DGCROW2 = 256
DGPS7 = $1A000 ; the palette as it stood after run 7 ...
DGPS8 = $1A200 ; ... after run 8 (the control: poison) ...
DGPS9 = $1A400 ; ... and after run 9 (chained). SNAPSHOTS, not
; a late read: each run overwrites the previous
; run's palette, so a host that looks once at
; the end sees only the last of the three.
org $10000
start:
clr.l DGFLAG.l
@@ -167,7 +194,7 @@ start:
; 256 B of a 1024 B line stride, so a frame is 192 destinations and not one.
; 46.6 said "no stride for a DMAC to skip" about the bytes WITHIN a row and left
; the rows themselves unexamined; a channel cannot skip 768 B any more than it
; could skip the 300 B in front of a record (run 7 below).
; could skip the 300 B in front of a record (run 10 below).
;
; The MC68450 answers this with SEQUENTIAL ARRAY CHAINING -- an array of 6-byte
; {u32 MAR, u16 MTC} entries it walks by itself -- and MAME implements it. So
@@ -199,7 +226,109 @@ dg_mkch:
bsr dg_save
clr.l DM_BARV.l ; and OFF again, so run 7 is unchained
; ---- 7. and a WINDOWED read through the channel, which must be REFUSED. This
; ---- 7. THE PALETTE. Can a channel write $E82000? (FINDINGS 61.9, ROADMAP
; K1.) Runs 4-6 put the PICTURE on the channel; a packed frame is a picture AND
; a palette, and if the palette registers take a byte-wide DMA the way GVRAM
; does in buffer mode then the palette is a 193rd array-chain entry and ONE
; channel start paints a whole frame -- no per-frame CPU work in the video path
; at all. If they do not, the CPU writes 256 words a frame (61.9 derives that
; at ~2,370 clocks, 0.28% of a frame) and the architecture still stands; this is
; the difference between cheap and free, and it is worth one run to know which.
;
; 512 B IS THE WHOLE PALETTE AND EXACTLY ONE SECTOR, which is why this run reads
; one block where the others read four: a transfer that ran long would write
; $E82200 (the TEXT palette) and then $E82400 (the video controller's own
; registers, priority included), and a probe that reconfigures the video
; controller as a side effect is not a probe.
;
; THE PALETTE IS POISONED FIRST, and that is what stops this being run 4's trap
; a second time. A destination that already holds the right bytes cannot tell a
; channel that wrote them from a channel that did nothing; RAM at $20000 was
; zero and the record is mostly pad, so "it matches" has been a weak claim all
; session. Word i is set to $A500|i, which no 512 B of container matches by
; accident, and the host reports how many of the 512 positions the poison and
; the disc actually differ in rather than assuming all of them.
bsr scsi_init
bsr dg_poison
move.l #DM_HELD_DCR,DM_DCRV.l
move.l #DM_HELD_OCR,DM_OCRV.l
move.l #1,DM_USE.l
move.l #DGLBA,d3
moveq #DGPBLK,d4
lea DGPAL,a1
bsr scsi_read
lea DGPS7,a1
bsr dg_palsnap ; before run 8 overwrites it
lea DGREC+6*DGREC_SZ,a0
bsr dg_save
; ---- 8. THE NEGATIVE CONTROL, and it is an ATTRIBUTION control rather than a
; mechanism one. Run 5's control could point at a mode bit; there is no mode
; bit here, so what has to be excluded is that run 7's palette held the disc's
; bytes for some reason OTHER than the channel having written them there --
; a readback that aliases somewhere else, the SPC's own path touching the
; registers, the poison never having landed.
;
; Same transfer, same channel, same bytes; ONE thing different, the destination
; address. The disc's bytes must appear at $2C000, and the palette must still
; read poison in all 256 words. Two claims from one run, and the second is the
; one that makes run 7 mean anything: it also proves the CPU's poison writes
; reach the registers the host reads back, which is the positive half.
bsr scsi_init
bsr dg_poison
move.l #DM_HELD_DCR,DM_DCRV.l
move.l #DM_HELD_OCR,DM_OCRV.l
move.l #1,DM_USE.l
move.l #DGLBA,d3
moveq #DGPBLK,d4
lea DGDST6,a1
bsr scsi_read
lea DGPS8,a1
bsr dg_palsnap ; must be poison, word for word
lea DGREC+7*DGREC_SZ,a0
bsr dg_save
; ---- 9. THE 193rd ENTRY: one start, the palette AND the picture rows. This is
; the run K1 exists for. Runs 7 and 8 only show that a channel can write the
; palette registers; what the architecture needs is that ONE array chain can
; cross from a hardware register area into GVRAM without the CPU between them,
; because a frame is a palette entry followed by 192 row entries and the whole
; claim is that the CPU starts the channel once.
;
; Seven entries, 2,048 B, out of the same LBA as everything else: 512 B into the
; palette and then six rows of 256 B at the 1024 B line stride. The destination
; regions are of two different KINDS -- device registers and video RAM in buffer
; mode -- which is exactly the crossing that has never been run.
bsr scsi_init
bsr dg_poison
move.w #R20_BUF,CRTC20.l
move.l #R20_BUF,DGR20P.l
lea DGCHA2,a0
move.l #DGPAL,(a0)+ ; entry 0: the palette, a whole sector
move.w #DGPALN*2,(a0)+
lea DGDST7,a1
moveq #DGCHN2-2,d5 ; the remaining six are picture rows
dg_mkch2:
move.l a1,(a0)+
move.w #DGCROW2,(a0)+
lea 1024(a1),a1
dbra d5,dg_mkch2
move.l #DM_HELD_DCR,DM_DCRV.l
move.l #DM_HELD_OCR|$08,DM_OCRV.l ; OCR CHAIN = %10, array chain
move.l #DGCHA2,DM_BARV.l
move.l #DGCHN2,DM_BTCV.l
move.l #1,DM_USE.l
move.l #DGLBA,d3
moveq #DGBLK,d4
lea DGDST7,a1 ; ignored under chaining, as in run 6
bsr scsi_read
lea DGPS9,a1
bsr dg_palsnap
lea DGREC+8*DGREC_SZ,a0
bsr dg_save
clr.l DM_BARV.l ; and OFF again, so run 10 is unchained
; ---- 10. and a WINDOWED read through the channel, which must be REFUSED. This
; is the one test here that is expected to fail, and it has to fail LOUDLY: the
; alternative is a channel writing a whole sector into a ring that has room for
; a record, over the top of records the decoder has not finished with.
@@ -240,4 +369,35 @@ dg_save:
clr.l DM_MARF.l
rts
; ---- the poison. Word i of the graphic palette <- $A500|i, written by the
; 68000 itself. Two jobs: a destination that cannot hold the right answer by
; accident, and a known pattern the control run reads back out of the registers
; to show the readback path shows what is actually in them.
dg_poison:
movem.l d0-d2/a0,-(sp)
lea DGPAL,a0
moveq #0,d1 ; i
move.w #DGPALN-1,d0
.p: move.w d1,d2
ori.w #DGPOIS,d2 ; $A500|i, and i never exceeds 255
move.w d2,(a0)+
addq.w #1,d1
dbra d0,.p
movem.l (sp)+,d0-d2/a0
rts
; ---- the palette as it stands, copied to (a1) by the 68000 READING THE
; REGISTERS. A snapshot and not a late host read: each palette run overwrites
; the last one's result, so all three have to be kept while they are true. It
; is also the same shape of evidence 53.3 used for pal_pack -- the words come
; back out of $E82000 rather than out of the buffer they were built in.
dg_palsnap:
movem.l d0/a0-a1,-(sp)
lea DGPAL,a0
move.w #DGPALN-1,d0
.s: move.w (a0)+,(a1)+
dbra d0,.s
movem.l (sp)+,d0/a0-a1
rts
include "src/player/scsi.i"