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
This commit is contained in:
@@ -67,6 +67,8 @@ DM_CCR = DMACB+$07 ; channel control
|
||||
DM_MTC = DMACB+$0A ; memory transfer count, WORD
|
||||
DM_MAR = DMACB+$0C ; memory address, LONG
|
||||
DM_DAR = DMACB+$14 ; device address, LONG
|
||||
DM_BTC = DMACB+$1A ; base transfer count, WORD (array chain)
|
||||
DM_BAR = DMACB+$1C ; base address, LONG (array chain)
|
||||
DM_MFC = DMACB+$29
|
||||
DM_CPR = DMACB+$2D
|
||||
DM_DFC = DMACB+$31
|
||||
@@ -113,6 +115,17 @@ DM_CERF = $18518 ; u32 CER with it
|
||||
DM_MTCF = $1851C ; u32 MTC with it
|
||||
DM_MARF = $18520 ; u32 MAR with it -- where it stopped
|
||||
DM_LEN = $18524 ; u32 bytes the channel was asked for
|
||||
; ---- SEQUENTIAL ARRAY CHAINING, and it is OFF unless a caller asks for it.
|
||||
; A device->GVRAM transfer cannot be one contiguous run: a picture row is 256 B
|
||||
; of a 1024 B line stride, so 192 rows want 192 destinations. The MC68450 walks
|
||||
; an array of 6-byte {u32 MAR, u16 MTC} entries for exactly this, and MAME's
|
||||
; hd63450 implements it (`(ocr & 0x0c) == 0x08`, dma_transfer_start and
|
||||
; dma_transfer_continue). DM_BARV = 0 means no chaining and NOTHING below
|
||||
; changes, which is what stream.s gets: this file is included by the player as
|
||||
; well as by the gate, and a mailbox that defaults to a new behaviour is how
|
||||
; DM_USE landed on ring.i's slot and turned a stage red (above).
|
||||
DM_BARV = $18528 ; u32 array base address, 0 = no chain
|
||||
DM_BTCV = $1852C ; u32 array entry count (BTC)
|
||||
DM_PATIENCE = 4000000 ; the wait is bounded like every other
|
||||
|
||||
; ---------------------------------------------------------------- sc_in_dma
|
||||
@@ -144,8 +157,20 @@ sc_in_dma:
|
||||
move.b #$05,DM_MFC ; the function codes the IPL ROM uses
|
||||
move.b #$05,DM_DFC
|
||||
move.b #$01,DM_CPR
|
||||
move.l DM_BARV.l,d0
|
||||
bne.s .chain
|
||||
move.w d5,DM_MTC
|
||||
move.l a1,DM_MAR
|
||||
bra.s .darset
|
||||
.chain:
|
||||
; MAR and MTC are NOT written: the channel loads both from the array's
|
||||
; first entry when it starts, and reloads them from the next entry at
|
||||
; every count-out. Writing them here would be writing registers the
|
||||
; hardware is about to overwrite, which reads like a contract and is not.
|
||||
move.l d0,DM_BAR
|
||||
move.l DM_BTCV.l,d0
|
||||
move.w d0,DM_BTC
|
||||
.darset:
|
||||
move.l #SC_DREG,DM_DAR ; $EA0015 -- the DMAC's door, and now
|
||||
; the DMAC is the one going through it
|
||||
move.b #SCMD_XFER,SC_SCMD ; no PROGRAM bit: the SPC raises DRQ
|
||||
|
||||
+115
-4
@@ -37,10 +37,22 @@
|
||||
; burst modelled as held.
|
||||
|
||||
DGFLAG = $18600 ; 0 idle / 1 done
|
||||
DGREC = $18610 ; 3 x 32 B: rc, err, mtc0, spin, csr, cer, mtc, mar
|
||||
DGREC = $18610 ; 6 x 32 B: rc, err, mtc0, spin, csr, cer, mtc, mar
|
||||
DGREC_SZ = 32
|
||||
DGWIN = $18680 ; u32 return of the WINDOWED dma read (want -1)
|
||||
DGWERR = $18684 ; u32 SC_ERR after it (want SCE_WINDOW)
|
||||
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)
|
||||
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
|
||||
DGCHA = $19000 ; the array-chain table: 8 x {u32 MAR, u16 MTC}
|
||||
DGCHN = 8 ; entries
|
||||
DGCROW = 256 ; bytes an entry carries -- one packed picture row
|
||||
DGDST5 = $C10000 ; and the 8 row bases, at the 1024 B line stride
|
||||
CRTC20 = $E80028 ; CRTC R20
|
||||
DGDST3 = $C08000 ; DMA straight into GVRAM, BUFFER MODE (47.6.2)
|
||||
DGLBA = 1000 ; a NON-ZERO LBA throughout: a driver that emits
|
||||
; a malformed LBA field still passes LBA 0
|
||||
DGBLK = 4 ; 4 x 512 = 2,048 B
|
||||
@@ -88,7 +100,106 @@ start:
|
||||
lea DGREC+2*DGREC_SZ,a0
|
||||
bsr dg_save
|
||||
|
||||
; ---- 4. and a WINDOWED read through the channel, which must be REFUSED. This
|
||||
; ---- 4. THE CHANNEL WRITING GVRAM, IN BUFFER MODE. 47.6.2: "the DMAC has not
|
||||
; been near this" -- 44.7 costed a device->GVRAM transfer and 47 built the packed
|
||||
; layout, and no run in this tree has ever pointed a channel at $C00000. Two
|
||||
; separate things are being asked at once and both are write-path questions:
|
||||
;
|
||||
; a. can a channel write GVRAM AT ALL? Nothing says it cannot -- the DMAC
|
||||
; writes through the same program address space the CPU does -- but a
|
||||
; transport that silently drops its writes into a device handler is exactly
|
||||
; the failure this gate exists to catch, and the SPC's own bytes make a
|
||||
; better witness than a fill pattern.
|
||||
; b. does a BYTE-wide channel fill the PACKED layout? 47.1 measured the write
|
||||
; path with word writes from the CPU. A dual-address channel with an 8-bit
|
||||
; device port writes BYTES, and MAME's gvram_w in buffer mode passes
|
||||
; mem_mask straight through -- so an even byte should land in the HIGH half
|
||||
; of its word (page 1) and an odd byte in the LOW half (page 0). If it
|
||||
; does, a linear DMA of a stream interleaved (right<<8)|left -- which is
|
||||
; exactly show_frame256_packed.lua's layout -- fills the screen with no CPU
|
||||
; in the loop at all.
|
||||
;
|
||||
; R20 bit 11 is left SET across the readback on purpose: gvram_r returns the raw
|
||||
; word in buffer mode and the masked byte outside it, so clearing it first would
|
||||
; hide half of what is being measured.
|
||||
;
|
||||
; R20 IS WRITTEN OUTRIGHT AND NOT OR-ED INTO. The first cut of this run set bit
|
||||
; 11 on top of whatever the IPL left, and the IPL leaves $0B16 (22.1) -- bit 11
|
||||
; ALREADY SET, and COL = %11, the 65,536-colour setup, which writes whole words
|
||||
; with or without the bit. The run passed and proved nothing: it was a test that
|
||||
; could not fail. Run 5 below is the control that makes this one mean something,
|
||||
; and the two differ in EXACTLY BIT 11.
|
||||
bsr scsi_init
|
||||
move.w #R20_BUF,CRTC20.l
|
||||
move.l #R20_BUF,DGR20.l
|
||||
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 #DGBLK,d4
|
||||
lea DGDST3,a1
|
||||
bsr scsi_read
|
||||
lea DGREC+3*DGREC_SZ,a0
|
||||
bsr dg_save
|
||||
|
||||
; ---- 5. THE NEGATIVE CONTROL: the same transfer with bit 11 CLEAR. In masked
|
||||
; 256-colour mode gvram_w takes `data & 0x00ff` and IGNORES mem_mask, so a byte
|
||||
; written to an EVEN address -- where the 68000 puts the MS byte, and where every
|
||||
; other disc byte lands -- contributes nothing and cannot be read back. Half the
|
||||
; transfer must be lost, and if it is not, run 4 was not measuring the bit.
|
||||
bsr scsi_init
|
||||
move.w #R20_MSK,CRTC20.l
|
||||
move.l #R20_MSK,DGR20N.l
|
||||
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 #DGBLK,d4
|
||||
lea DGDST4,a1
|
||||
bsr scsi_read
|
||||
lea DGREC+4*DGREC_SZ,a0
|
||||
bsr dg_save
|
||||
move.w #R20_BUF,CRTC20.l ; back to buffer mode, so the host reads
|
||||
; RAW WORDS out of both destinations
|
||||
|
||||
; ---- 6. THE ROW STRIDE, WHICH IS THE REAL SHAPE OF THE TRANSFER. Runs 4 and 5
|
||||
; wrote 2,048 contiguous bytes, and no picture is contiguous: a packed row is
|
||||
; 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).
|
||||
;
|
||||
; 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
|
||||
; the question is not whether the CPU can restart the channel 192 times a frame;
|
||||
; it is whether it has to at all. Eight rows here, out of the same LBA and the
|
||||
; same 2,048 B, so the ONLY difference from run 4 is where the bytes land.
|
||||
bsr scsi_init
|
||||
move.w #R20_BUF,CRTC20.l
|
||||
move.l #R20_BUF,DGR20C.l
|
||||
lea DGCHA,a0
|
||||
lea DGDST5,a1
|
||||
moveq #DGCHN-1,d5
|
||||
dg_mkch:
|
||||
move.l a1,(a0)+ ; MAR: this row's base
|
||||
move.w #DGCROW,(a0)+ ; MTC: 256 bytes of it
|
||||
lea 1024(a1),a1 ; the next row is a line stride away
|
||||
dbra d5,dg_mkch
|
||||
move.l #DM_HELD_DCR,DM_DCRV.l
|
||||
move.l #DM_HELD_OCR|$08,DM_OCRV.l ; OCR CHAIN = %10, array chain
|
||||
move.l #DGCHA,DM_BARV.l
|
||||
move.l #DGCHN,DM_BTCV.l
|
||||
move.l #1,DM_USE.l
|
||||
move.l #DGLBA,d3
|
||||
moveq #DGBLK,d4
|
||||
lea DGDST5,a1 ; ignored under chaining; passed so the
|
||||
; call site reads the same as the others
|
||||
bsr scsi_read
|
||||
lea DGREC+5*DGREC_SZ,a0
|
||||
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
|
||||
; 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.
|
||||
|
||||
@@ -25,3 +25,17 @@ SPCU = 12 ; bytes of code per COARSE span unit (24 px)
|
||||
SPCN = 11 ; coarse units: 11*24 = 264 px >= one row
|
||||
SPFU = 2 ; bytes of code per FINE span unit (2 px)
|
||||
SPFN = 11 ; fine units: 11*2 = 22 px > one coarse unit
|
||||
|
||||
; RECORD ALIGNMENT, and it is a property of the CONTAINER rather than of the
|
||||
; 68000. DLX2 padded each record up to 4, which is all `move.l (a0)+` needs
|
||||
; (FINDINGS 28.3). DLX5 pads up to a 512 B SECTOR, so that a DMA channel can
|
||||
; read a record as whole sectors straight into the ring with no window and no
|
||||
; bounce copy -- `sc_in_data` REFUSES a windowed read when the data phase is
|
||||
; the channel's (59.4), and 117 of 120 records needed one under DLX4.
|
||||
;
|
||||
; The consumer has to know it too: the decoder releases the ring up to the end
|
||||
; of the record it was handed, and a decoder that released only the bytes it
|
||||
; READ would leave the pad unreclaimed and drift the producer's free-space
|
||||
; arithmetic by up to RECALN-1 per record. The ring base must therefore be
|
||||
; RECALN-aligned, which is asserted where the ring is placed.
|
||||
RECALN = 512
|
||||
|
||||
+9
-5
@@ -312,12 +312,16 @@ nostall:
|
||||
cmpa.l SCR_END.l,a0 ; bitstream desync is silent otherwise
|
||||
bne desync
|
||||
|
||||
; ---- release. Round up to 4 the same way decode.s does: the producer lays
|
||||
; records on 4-byte boundaries, so the byte one past this record's padded
|
||||
; end is the first byte the producer may reuse.
|
||||
; ---- release. Round up to RECALN: the producer lays records on the
|
||||
; container's own record boundaries (geom.i), so the byte one past this
|
||||
; record's PADDED end is the first byte it may reuse. Releasing only the
|
||||
; bytes actually read would strand up to RECALN-1 of pad per record and
|
||||
; the producer's free-space arithmetic would drift by that much a frame.
|
||||
; This is an absolute address, so it is only the record's padded end
|
||||
; because the ring base is RECALN-aligned as well.
|
||||
move.l a0,d0
|
||||
addq.l #3,d0
|
||||
and.b #$FC,d0
|
||||
addi.l #RECALN-1,d0
|
||||
andi.l #~(RECALN-1),d0
|
||||
move.l d0,RD_PTR.l
|
||||
addq.l #1,FR_TAIL.l
|
||||
|
||||
|
||||
Reference in New Issue
Block a user