ROADMAP P6c, FINDINGS 68. 78,125 B of a DLXP2's audio out of channel 3, sample-exact, while the video channel fetches records off the same disc. The two pieces 67.6 said were missing: the lump buffer (pg_afill/pg_afetch, three slots and the minimum is unmeasured) and 67.2's remainder accumulator (pg_apay). The capture prices what the accumulator avoided at 1.26 s of lip-sync over the game, against 67.2's predicted 1.25. The finding is the third piece, which nothing had named: the MSM6258 has no starvation state, so the gap between a channel counting out and the next arm is a held nibble pair driving the predictor. Stealing, the seam is 0.51 ms over ten seconds because dma.i's new DM_HOOK services the chip from inside the transfer wait -- 250,000 of 250,240 looks. Held, the 68000 is halted and gets 369: every one of the ten lump boundaries has a seam, worst 72.8 ms, 2.31% of the audio. Identical bytes, different sound. 64.3 reaching the audio. Two bugs, and no counter in the player could see either. Clearing DM_BARV does not unchain a channel -- OCR bits 3-2 are what it obeys -- and the symptom is POLL TIMEOUT on the lump and every record after it. And the refill ran one lump ahead of its ring and overwrote the buffer the channel was reading: 11 of 11 armed, 11 fetched, no starve, and the sound wrong from 0.2 s in. Which is why the gate is a WAV: verify_packed_audio.py walks the stream one delivered byte at a time, because MAME's okim6258 resets the nibble select on every write and a byte is two nibbles only 99.994% of the time. check.sh ALL GREEN before (tmp/check_s36_start.log) and after (tmp/check_s36_end.log), with the new stage. Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
274 lines
14 KiB
OpenEdge ABL
274 lines
14 KiB
OpenEdge ABL
; The HD63450 driving the SCSI data phase. ROADMAP P4a.
|
|
;
|
|
; WHAT P4a HAS TO SHOW, and why it needed a new kind of evidence. FINDINGS 58
|
|
; measured the CPU moving every byte itself at 87.28 clocks per delivered byte
|
|
; -- 391.8% of a 12 fps frame, against 22.4% for the cheapest DMA row of the
|
|
; ladder and 85.3% for the dearest. So the whole of what is left before M2 is a
|
|
; DMAC configuration that HOLDS THE BUS. 57.3 is why it could not simply be
|
|
; watched into existence: x68k_scsiext.cpp glues $EA0015 so that with the DMAC's
|
|
; OWN asserted -- which it is at idle on this machine -- MAME CANNOT DISTINGUISH
|
|
; a CPU-driven byte at that address from a DMAC-driven one. Watching the data
|
|
; register cannot answer the question it looks like it answers.
|
|
;
|
|
; THE DISCRIMINATOR USED HERE IS THE CPU'S OWN PROGRESS, and it never reads
|
|
; $EA0015 at all. A DMAC that holds the bus is one the CPU is not running
|
|
; against; so the witness is a single instruction:
|
|
;
|
|
; move.b #CCR_START,DM_CCR ; the channel is told to go
|
|
; move.w DM_MTC,d0 ; <- sampled by the VERY NEXT instruction
|
|
;
|
|
; If the bus was held for the transfer, the whole transfer happened between
|
|
; those two instructions and d0 reads ZERO. If it was not, d0 reads very nearly
|
|
; the full count and the CPU goes on to spin thousands of times while the
|
|
; channel trickles. Both configurations deliver the same bytes; what separates
|
|
; them is whether the 68000 got to execute anything meanwhile, which is exactly
|
|
; what "holds the bus" means and is not a fact about $EA0015.
|
|
;
|
|
; WHAT MAME CAN AND CANNOT BE ASKED, stated here because it bounds the claim and
|
|
; it is not obvious from the outside:
|
|
;
|
|
; * THE CARD HAS NO EXREQ PATH. x68k_scsiext.cpp's drq_w only stores a flag;
|
|
; the expansion slot has no request line to the DMAC at all (x68k.cpp wires
|
|
; drq0 from the FDC and drq3 from ADPCM, and nothing else). The card's flow
|
|
; control is DTACK: on a DMAC cycle with DRQ low the card NEGATES DTACK and
|
|
; the HD63450 discards that operand and retries. So every configuration
|
|
; below is AUTO-REQUEST; REQG=10, external request -- the mode the ladder's
|
|
; W=5 and W=12 rows assume -- has no wiring in this model and cannot be run.
|
|
; * SINGLE ADDRESS CANNOT BE RUN EITHER. hd63450.cpp only takes the implicit
|
|
; path when a channel has a dma_read/dma_write callback, and on this machine
|
|
; only channel 0 (the FDC) has one. DTYP=10/11 on channels 1..3 falls
|
|
; through to the dual-address code.
|
|
; * ONLY BURST IS MODELLED AS HELD. The device tests `(dcr & 0xc0) == 0`, so
|
|
; XRM=10 (cycle steal without hold) and XRM=11 (cycle steal WITH hold) are
|
|
; one code path. The bus is held, and the CPU halted, only for XRM=00 burst
|
|
; with REQG=01 max rate.
|
|
;
|
|
; So of the four rows of the per-byte ladder, exactly ONE -- dual address, bus
|
|
; held, 9 clk/B -- has a code path in this model, and it is the one demonstrated
|
|
; below. That is a bound on the apparatus and not a result about the board.
|
|
;
|
|
; AND IT IS STILL NOT A RATE. MAME's DMAC is configured in wall-clock attotimes
|
|
; (42.5), not per-operand cycles: set_burst_clocks gives channel 1 450 ns an
|
|
; operand no matter what the 68000 is doing. `W` is untouched by every line in
|
|
; this file and still wants a board (ROADMAP B1/B3).
|
|
|
|
; ---- the channel. 1, not 0: channel 0 is the FDC's and is the one channel
|
|
; with device callbacks, which would silently take the implicit-address path.
|
|
; Channel 1 is also the channel the IPL ROM points at the SASI data register
|
|
; (52.5), so this is the machine's own disk channel programmed differently.
|
|
DMA_CH = 1
|
|
DMACB = DMAC+DMA_CH*DMAC_CH ; $E84040
|
|
DM_CSR = DMACB+$00 ; channel status (write 1s to clear)
|
|
DM_CER = DMACB+$01 ; channel error (read only)
|
|
DM_DCR = DMACB+$04 ; device control
|
|
DM_OCR = DMACB+$05 ; operation control
|
|
DM_SCR = DMACB+$06 ; sequence control
|
|
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
|
|
|
|
; CSR bits
|
|
CSR_COC = $80 ; channel operation complete
|
|
CSR_BTC = $40
|
|
CSR_NDT = $20 ; normal device termination
|
|
CSR_ERR = $10 ; channel error -- CER says which
|
|
CSR_ACT = $08 ; channel active
|
|
CCR_START = $80
|
|
|
|
; ---- the two configurations, as (DCR, OCR) pairs. Both are decoded by
|
|
; tools/analysis/27_dmac_config.py out of THESE bytes, using the same MC68450
|
|
; field tables 21_iplrom_dmac.py reads the IPL ROM's channels with -- so what
|
|
; the run claims it programmed and what it programmed cannot drift apart.
|
|
;
|
|
; HELD : DCR $00 = XRM 00 burst, DTYP 00 dual address, DPS 0 8-bit port
|
|
; OCR $81 = DIR device->memory, SIZE byte, no chain, REQG 01 max rate
|
|
; STEAL: DCR $80 = XRM 10 cycle steal WITHOUT hold, otherwise identical
|
|
; OCR $80 = REQG 00 auto-request at limited rate
|
|
DM_HELD_DCR = $00
|
|
DM_HELD_OCR = $81
|
|
DM_STEAL_DCR = $80
|
|
DM_STEAL_OCR = $80
|
|
|
|
; ---- what the run reports. Every one of these is a DMAC register or a count
|
|
; of the CPU's own instructions; none of them is a read of $EA0015.
|
|
; $18500 AND NOT $18300, WHICH IS WHERE THIS FIRST WENT. scsi.i's trace ends at
|
|
; $182FF and the next 160 bytes are the RING's: $18300 is ring.i's XF_SLOT
|
|
; mailbox, and tools/bench/stream.lua reads the same addresses from outside.
|
|
; dma.i is included by stream.s as well as by the gate, so DM_USE landed on the
|
|
; transfer request slot and the ring rig's first record request read as "use the
|
|
; DMAC" -- P4b's stage went red on a run that never reached its snapshot. The
|
|
; symptom was in a stage this session did not touch, which is the whole argument
|
|
; for check.sh being run before and after rather than only after.
|
|
DM_USE = $18500 ; u32 0 = PIO data phase, 1 = this file
|
|
DM_DCRV = $18504 ; u32 the DCR byte to program
|
|
DM_OCRV = $18508 ; u32 the OCR byte to program
|
|
DM_MTC0 = $1850C ; u32 MTC one instruction after START
|
|
DM_SPIN = $18510 ; u32 times the CPU went round the wait
|
|
DM_CSRF = $18514 ; u32 CSR when the channel finished
|
|
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)
|
|
; ---- THE SERVICE HOOK, and it is the whole of what a SECOND consumer needs
|
|
; from this file. ROADMAP P6c. A player that feeds ADPCM has to look at the
|
|
; audio channel more often than once a frame: the MSM6258 has no FIFO, so the
|
|
; instant its channel counts out the chip goes on replaying whatever byte its
|
|
; data register still holds -- and at 12 fps a once-a-frame re-arm makes that
|
|
; replay 90 ms long, which is not a gap, it is a buzz.
|
|
;
|
|
; THE ONLY PLACE A 68000 HAS TO SPARE IS INSIDE THIS WAIT. In the STEALING
|
|
; configuration the CPU goes round the loop below thousands of times per record
|
|
; (measured: 1,100,520 trips over 120 frames) and every one of them is time the
|
|
; disc is delivering and the CPU is not. So the hook is called from there, and
|
|
; the second consumer costs the video path nothing it was using.
|
|
;
|
|
; AND IN THE HELD CONFIGURATION THE HOOK CANNOT RUN AT ALL, which is not a bug
|
|
; in it: a burst channel HALTS the 68000 (dma_transfer_start asserts
|
|
; INPUT_LINE_HALT) and the CPU does not execute the loop, or anything else,
|
|
; until the record has landed. That asymmetry is the measurement -- FINDINGS
|
|
; 64.3 showed a held channel costs the frame CLOCK half its ticks, and this is
|
|
; the same fact reaching the audio.
|
|
;
|
|
; ZERO BY DEFAULT and every other front-end in this tree leaves it zero, so the
|
|
; cost to them is a `move.l` and a `beq` per trip. A mailbox that defaulted to
|
|
; a new behaviour is how DM_USE landed on ring.i's slot (above).
|
|
DM_HOOK = $18530 ; u32 0 = none, else a routine to call
|
|
; on every trip round the transfer wait.
|
|
; d0 is dead here and a0 is saved round
|
|
; the call, so the hook may trash both;
|
|
; it must preserve EVERYTHING else,
|
|
; because sc_in_dma's own d3/d4/d5 and
|
|
; scsi_read's a1 are live across it.
|
|
DM_PATIENCE = 4000000 ; the wait is bounded like every other
|
|
|
|
; ---------------------------------------------------------------- sc_in_dma
|
|
; Receive d1 bytes into (a1) in phase d2, WITHOUT the CPU touching one of them.
|
|
; Entered from sc_in_data when DM_USE is set; same registers, same contract.
|
|
;
|
|
; ORDER MATTERS AND IT IS NOT THE OBVIOUS ONE. The SPC is put into DMA transfer
|
|
; BEFORE the channel is started, because in the held configuration the 68000
|
|
; stops executing at the CCR write and does not run again until the transfer is
|
|
; over -- so anything the SPC needs to be told has to have been told already.
|
|
sc_in_dma:
|
|
movem.l d3-d5,-(sp)
|
|
move.l d1,d5 ; keep the length for the report
|
|
move.l d5,DM_LEN.l
|
|
move.b d2,SC_PCTL
|
|
move.l d1,d0
|
|
bsr sc_settc ; the SPC counts the same bytes down
|
|
|
|
; ---- the channel, quiet first: CSR is write-one-to-clear and a stale
|
|
; COC from a previous record would pass the wait loop instantly.
|
|
move.b #$FF,DM_CSR
|
|
move.l DM_DCRV.l,d0
|
|
move.b d0,DM_DCR
|
|
move.l DM_OCRV.l,d0
|
|
move.b d0,DM_OCR
|
|
move.b #$04,DM_SCR ; MAC 01 memory increment, DAC 00 none:
|
|
; the device address is a REGISTER and
|
|
; must not walk off it.
|
|
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
|
|
move.l #11,SC_TAG.l ; 11 = channel armed, SPC in DMA mode
|
|
bsr sc_snap
|
|
|
|
; ---- START, and the witness immediately after it
|
|
move.b #CCR_START,DM_CCR
|
|
move.w DM_MTC,d0 ; THE DISCRIMINATOR. Held: zero.
|
|
andi.l #$FFFF,d0
|
|
move.l d0,DM_MTC0.l
|
|
|
|
; ---- wait for the channel, counting the CPU's own trips round the loop.
|
|
; In the held configuration this is one trip, because the CPU did not
|
|
; get to run until the transfer was over. In the stealing one it is
|
|
; thousands, and every one of them is a 68000 instruction that executed
|
|
; while the disc was delivering -- which is the whole point of P4a.
|
|
clr.l DM_SPIN.l
|
|
move.l #DM_PATIENCE,d3
|
|
.wait: addq.l #1,DM_SPIN.l
|
|
move.l DM_HOOK.l,d0 ; the second consumer's slot -- see above
|
|
beq.s .nohook
|
|
move.l a0,-(sp)
|
|
movea.l d0,a0
|
|
jsr (a0)
|
|
movea.l (sp)+,a0
|
|
.nohook:
|
|
move.b DM_CSR,d4
|
|
btst #4,d4 ; ERR
|
|
bne.s .err
|
|
btst #7,d4 ; COC
|
|
bne.s .fin
|
|
subq.l #1,d3
|
|
bne.s .wait
|
|
bsr .report
|
|
movem.l (sp)+,d3-d5
|
|
move.l #SCE_TIMEOUT,SC_ERR.l
|
|
moveq #-1,d0
|
|
rts
|
|
.err: bsr .report
|
|
movem.l (sp)+,d3-d5
|
|
move.l #SCE_TIMEOUT,SC_ERR.l ; a channel error is a dead transport
|
|
moveq #-1,d0
|
|
rts
|
|
.fin: bsr .report
|
|
move.b #$FF,DM_CSR ; leave the channel as we found it
|
|
move.l #12,SC_TAG.l ; 12 = channel reported COC
|
|
bsr sc_snap
|
|
movem.l (sp)+,d3-d5
|
|
bsr sc_xferend ; the SPC's own transfer, not the DMAC's
|
|
move.l d0,-(sp)
|
|
move.l #9,SC_TAG.l
|
|
bsr sc_snap
|
|
move.l (sp)+,d0
|
|
rts
|
|
|
|
; ---- the channel's own account of what it did, read out of its registers
|
|
.report:
|
|
moveq #0,d0
|
|
move.b DM_CSR,d0
|
|
move.l d0,DM_CSRF.l
|
|
moveq #0,d0
|
|
move.b DM_CER,d0
|
|
move.l d0,DM_CERF.l
|
|
move.w DM_MTC,d0
|
|
andi.l #$FFFF,d0
|
|
move.l d0,DM_MTCF.l
|
|
move.l DM_MAR,d0
|
|
move.l d0,DM_MARF.l
|
|
rts
|