; Front-end for the HD63450 DATA PHASE (ROADMAP P4a), for the rig. ; ; THE QUESTION. FINDINGS 58 put the transport on the 68000 and priced it: the ; CPU moving every byte itself costs 87.28 clocks per delivered byte, 391.8% of ; a 12 fps frame. Against that, the cheapest DMA row of the ladder is 22.4% and ; the dearest is 85.3%, so everything left before M2 turns on getting the DMAC ; to drive the data phase with the bus HELD. 57.3 is why it cannot be shown by ; watching the data register: with the DMAC's OWN asserted, which it is at idle ; here, MAME cannot tell a CPU-driven byte at $EA0015 from a DMAC-driven one. ; ; THE EVIDENCE THIS GATE PRODUCES, and none of it is a read of $EA0015: ; ; 1. THE SAME BYTES. The same sectors are read three times -- once by the PIO ; path FINDINGS 58 measured, once by the channel with the bus held, once by ; the channel stealing cycles -- and the HOST compares all three against its ; own copy of the image. A transport that returns the wrong bytes without ; saying so is the failure a checksum-free ring cannot survive (49.2). ; 2. THE CPU'S OWN PROGRESS. MTC is sampled by the INSTRUCTION AFTER the one ; that starts the channel. Held, it reads zero: the entire transfer ; happened between two instructions, because the 68000 did not execute in ; between. Stealing, it reads nearly the full count and the CPU then goes ; round its wait loop thousands of times while the bytes arrive. That ; difference IS "the DMAC held the bus", and it is a fact about the CPU. ; 3. THE CHANNEL'S OWN ACCOUNT. CSR, CER, the final MTC and the final MAR: ; the channel says it completed without error, moved every byte, and left ; its memory pointer exactly one transfer-length past where it started. ; 4. THE WINDOW IS REFUSED. A windowed read (58.3: 117 of 120 records start ; part way into a sector) is rejected by the transport rather than silently ; delivering the neighbouring records' bytes into the ring. P4a's ; precondition is stated by the code that has it, not by a comment. ; ; WHAT IT DOES NOT SHOW. Not `W`. Not one clock of it. MAME's DMAC runs on ; wall-clock attotimes (42.5) and its burst mode halts the CPU outright rather ; than costing it cycles per operand, so this gate settles WHICH CONFIGURATION ; WORKS and not what one costs. See src/player/dma.i for the three ways this ; model bounds the question -- no EXREQ wiring, no single-address path, and only ; burst modelled as held. DGFLAG = $18600 ; 0 idle / 1 done 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 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 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 ; ---- 1. the PIO path, unchanged, as the reference the other two are measured ; against. It is here so that a DMA failure cannot be confused with a SCSI ; protocol failure: if this one is wrong, nothing below is about the DMAC. bsr scsi_init move.l #DGLBA,d3 moveq #DGBLK,d4 lea DGDST0,a1 bsr scsi_read lea DGREC,a0 bsr dg_save ; ---- 2. the channel, WITH THE BUS HELD bsr scsi_init move.l #DM_HELD_DCR,DM_DCRV.l move.l #DM_HELD_OCR,DM_OCRV.l move.l #1,DM_USE.l ; after scsi_init, which clears it move.l #DGLBA,d3 moveq #DGBLK,d4 lea DGDST1,a1 bsr scsi_read lea DGREC+DGREC_SZ,a0 bsr dg_save ; ---- 3. the channel, STEALING CYCLES. Same bytes, same code, two register ; values different -- which is what makes the comparison a comparison. bsr scsi_init move.l #DM_STEAL_DCR,DM_DCRV.l move.l #DM_STEAL_OCR,DM_OCRV.l move.l #1,DM_USE.l move.l #DGLBA,d3 moveq #DGBLK,d4 lea DGDST2,a1 bsr scsi_read lea DGREC+2*DGREC_SZ,a0 bsr dg_save ; ---- 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 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 ; 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. 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. bsr scsi_init move.l #DM_HELD_DCR,DM_DCRV.l move.l #DM_HELD_OCR,DM_OCRV.l move.l #1,DM_USE.l move.l #300,SC_WSKIP.l ; a record that starts 300 B into a sector move.l #1024,SC_WKEEP.l move.l #DGLBA,d3 moveq #DGBLK,d4 lea DGDST2,a1 bsr scsi_read_win move.l d0,DGWIN.l move.l SC_ERR.l,DGWERR.l move.l #1,DGFLAG.l hold: bra.s hold ; ---- one config's result, copied out of the shared reporting words before the ; next run overwrites them. d0 = scsi_read's return; a0 = where it goes. dg_save: move.l d0,(a0)+ move.l SC_ERR.l,(a0)+ move.l DM_MTC0.l,(a0)+ move.l DM_SPIN.l,(a0)+ move.l DM_CSRF.l,(a0)+ move.l DM_CERF.l,(a0)+ move.l DM_MTCF.l,(a0)+ move.l DM_MARF.l,(a0)+ ; and clear them, so a config that never reached the channel reports ; zeros of its own rather than the previous config's numbers. clr.l DM_MTC0.l clr.l DM_SPIN.l clr.l DM_CSRF.l clr.l DM_CERF.l clr.l DM_MTCF.l 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"