Ask the chip which decoder it is, and find four wrong axes where one was expected

ROADMAP P6a, on the machine. 68000 code programs HD63450 channel 3 with the
IPL ROM's own ADPCM bytes -- dual address, 8-bit port, cycle steal, external
request -- and feeds the MSM6258 a designed 1,678-nibble stream at the chip's
own pace: 839 B in 0.1074 s = 7,811.4 B/s against the format's 7,812.5, CER=$00.
That transport is P6b's, not scaffolding.

Sixteen candidate decoder models, three capture decimations and a searched
prologue are fitted to MAME's capture. Exactly one reproduces it sample-exact
over all 1,678 samples, and every axis carries a negative control: flip it
alone and the closest survivor disagrees on 826, 1,504, 156 and 1,522 samples.

The chip runs 'terms', takes the LOW nibble of a byte first, clamps the
accumulator at 10 bits and starts it at -2. tools/encoder/adpcm.py defaulted to
the opposite of all four, and 65.2 named the wrong axis as the risk: the delta
formula is worth -2.88 dB and the NIBBLE ORDER is worth -25.74 dB. 65.1's "high
first, measured" was a measurement of ffmpeg, i.e. of the VOX file convention,
which is a different question from what a chip does with a byte in its data
register.

The 10-bit clamp is free on the Singe window and only because that window peaks
at 435 of 511 -- 1.4 dB of headroom on a -13.4 dBFS passage, 12.1 dB below where
the encoder was clamping, and inside the recursion. So the audio level is an
open choice again, downward, and the loudest passage on the disc is unmeasured.

Session 33's silence had two ordinary causes: the PPI's port C is an input until
control word $92 says otherwise, and $01 is COMMAND_STOP. And a rig fact worth
the space: the 8 MHz ADPCM clock is CT1 in the YM2151's $1B, delivered on the
sound system's schedule rather than at the store, so a transfer started in the
same breath as the setup plays its first ~17 ms at the old clock and no model
fits a stream that changed rate part way through.

Name the layer: this is MAME 0.277's okim6258 device model measured end to end
through the machine's real transport. It settles the rig and not the silicon.
Also struck: 64.4's "no MAME source tree is on this machine" -- there is none on
disk, but the machine has network and the upstream tag fetches.

check.sh ALL GREEN before (tmp/check_s34_start.log) and after
(tmp/check_s34_end.log), with one new stage.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
This commit is contained in:
prosolis
2026-08-25 09:50:03 -07:00
parent f925a1dd9a
commit 6dd3fb3597
15 changed files with 1240 additions and 22 deletions
+108
View File
@@ -0,0 +1,108 @@
; ---------------------------------------------------------------------------
; adpcm.i -- the MSM6258V, driven the way the machine's own ROM drives it.
; ROADMAP P6a, and it is P6b's transport arriving early rather than scaffolding.
;
; NOTHING HERE IS INVENTED. Every register value below is one that
; tools/analysis/21_iplrom_dmac.py decodes OUT OF THE IPL ROM's own bytes, at
; the addresses it prints: channel 3's DCR/SCR/MFC/CPR/DFC/DAR at $FF0C2E and
; the per-transfer OCR = $32 plus command $02 at $FF9A82. That is the one
; ADPCM path on this board that is known-correct because Sharp wrote it.
;
; WHY THIS FILE EXISTS AT ALL. Session 33 fed the chip from Lua and got
; silence, swept control 0..3 against port C 0..15, and stopped rather than
; guess (65.5). Two of the reasons are visible from here and neither is a
; register semantic anybody had to guess:
;
; * THE PPI'S PORT C IS NOT AN OUTPUT UNTIL IT IS TOLD TO BE. The ADPCM pan
; and the sample-rate divider are port C bits, and an i8255 in its reset
; state has every port an INPUT -- so a write to $E9A005 changes a latch
; nobody is reading and the pan never leaves wherever it was. Control word
; $92 (mode 0, A and B input, both halves of C OUTPUT) is what makes the
; other write mean anything.
; * AND FEEDING IT SLOWLY IS NOT FEEDING IT. The chip has no FIFO and no
; starvation state: it consumes a nibble every sample period out of whatever
; its data register last held, forever. A byte per host frame is not a
; quiet chip, it is the same two nibbles 130 times, which saturates in six
; samples. The feed has to be paced by the chip, which is what channel 3
; and its request line are FOR.
;
; THE CLOCK IS TWO WRITES AND THEY ARE IN DIFFERENT DEVICES. 15,625 Hz is
; 8 MHz / 512: the 8 MHz comes from CT1 in the YM2151's port register $1B, and
; the /512 from port C bits 3,2 = 10. Neither is readable, so the rate is
; verified from the OTHER end -- the capture's own sample count.
AD_CTRLR = $E92001 ; W: command R: status (bit7 = NOT playing)
AD_DATAR = $E92003 ; W: the byte the chip takes two nibbles from
AD_PLAY = $02 ; COMMAND_PLAY -- session 33's probes wrote $01,
AD_STOP = $01 ; which is COMMAND_STOP
PPI_PC = $E9A005
PPI_CTL = $E9A007
PPI_COUT = $92 ; mode 0, A/B input, BOTH halves of C output
PPI_RATE = $08 ; pan 00 = both, rate 10 = /512 = 15,625 Hz
YM_ADDR = $E90001
YM_DATA = $E90003
YM_CT = $1B ; CT1 in bit 1: 0 = ADPCM master clock 8 MHz
AD_DMAC = $E840C0 ; HD63450 channel 3 -- the ADPCM channel, and
A3_CSR = AD_DMAC+$00 ; the one the ROM points at $E92003
A3_CER = AD_DMAC+$01
A3_DCR = AD_DMAC+$04
A3_OCR = AD_DMAC+$05
A3_SCR = AD_DMAC+$06
A3_CCR = AD_DMAC+$07
A3_MTC = AD_DMAC+$0A
A3_MAR = AD_DMAC+$0C
A3_DAR = AD_DMAC+$14
A3_MFC = AD_DMAC+$29
A3_CPR = AD_DMAC+$2D
A3_DFC = AD_DMAC+$31
A3_DCRV = $80 ; XRM 10 cycle steal w/o hold, DTYP 00 dual
; address, DPS 0 8-bit port (IPL $FF0C2E)
A3_OCRV = $32 ; DIR memory->device, SIZE 11 byte unpacked,
; CHAIN 00, REQG 10 EXTERNAL REQUEST (IPL
; $FF9A82). External request is what makes the
; chip the pacemaker: one byte per #DRQ3, and
; #DRQ3 ticks at half the sample rate.
A3_SCRV = $04 ; MAC 01 memory increment, DAC 00 -- the device
; address is a REGISTER and must not walk
A3_CCRST = $80
; --------------------------------------------------------------- ad_setup
; The clock and the pan. No arguments, no result; trashes d0.
ad_setup:
move.b #YM_CT,YM_ADDR
moveq #60,d0 ; the YM2151 wants settling between the
.ymw: subq.l #1,d0 ; address write and the data write
bne.s .ymw
move.b #$00,YM_DATA ; CT1 = 0 -> ADPCM master clock 8 MHz
move.b #PPI_COUT,PPI_CTL ; ...and NOW port C drives something
move.b #PPI_RATE,PPI_PC ; pan both, /512
rts
; ----------------------------------------------------------------- ad_arm
; Arm channel 3 to feed (a1) for d1 bytes and start it. Trashes d0.
; The channel is started BEFORE the chip is told to play (see ad_play), so that
; byte 0 is already in the data register when the accumulator is reset.
ad_arm:
move.b #$FF,A3_CSR ; CSR is write-one-to-clear: a stale COC
; would pass the wait loop instantly
move.b #A3_DCRV,A3_DCR
move.b #A3_SCRV,A3_SCR
move.b #$05,A3_MFC
move.b #$05,A3_DFC
move.b #$01,A3_CPR ; the ROM's own priority: ADPCM outranks
; the disk at the arbiter (52.5 item 5)
move.l #AD_DATAR,A3_DAR
move.b #A3_OCRV,A3_OCR
move.l a1,A3_MAR
move.w d1,A3_MTC
move.b #A3_CCRST,A3_CCR
rts
ad_play:
move.b #AD_PLAY,AD_CTRLR
rts
ad_halt:
move.b #AD_STOP,AD_CTRLR
rts
+112
View File
@@ -0,0 +1,112 @@
; Front-end for the ADPCM transport (ROADMAP P6a), for the rig.
;
; It plays ONE buffer of nibbles the host pushed into RAM and reports what the
; channel did. What is being measured is not this code -- it is the CHIP: which
; delta formula, which nibble of a byte first, where the accumulator clamps, and
; what it starts at. tools/bench/verify_adpcm_chip.py reads all four out of
; MAME's own -wavwrite capture.
;
; THE ONE THING THIS FILE HAS TO GET RIGHT is the order of the two starts. The
; chip resets its accumulator, its step index AND its nibble select when it is
; told to PLAY, and it begins consuming immediately out of whatever its data
; register holds. So the channel goes first and the CPU waits for MTC to move
; -- proof that a byte has actually been taken -- before the PLAY. The stream
; still has a prologue of unknown length, because the gap between PLAY and the
; NEXT #DRQ3 is not ours to set; the prologue is 16 zero nibbles for exactly
; that reason and the verifier reads its length off the capture.
include "src/player/geom.i"
AD_FLAG = $18600 ; u32 0 idle, 1 armed, 2 playing, $FF done, $EE error
AD_BUF = $18604 ; u32 where the nibble bytes are
AD_LEN = $18608 ; u32 how many bytes
AD_MTC0 = $1860C ; u32 MTC at the instant PLAY was written
AD_CSRF = $18610 ; u32 CSR at completion
AD_CERF = $18614 ; u32 CER with it
AD_MTCF = $18618 ; u32 MTC with it
AD_MARF = $1861C ; u32 MAR with it -- where the channel stopped
AD_SPIN = $18620 ; u32 trips round the wait loop
AD_STAT = $18624 ; u32 the chip's own status byte while playing
AD_PATIENCE = 60000000
AD_SETTLE = 60000 ; ~100 ms at 10 MHz, 18 clocks a trip ; the wait is bounded like every other
org $10000
start:
bsr ad_setup
; ---- SETTLE, and it is not superstition. The 8 MHz ADPCM clock is
; CT1 in the YM2151's port register, and this machine delivers that
; write to the ADPCM chip on the SOUND system's own schedule rather
; than at the instant of the store -- so a transfer started in the same
; breath as ad_setup plays its first ~17 ms at the PREVIOUS clock. The
; symptom is exact: the capture's first 130-odd samples come out in
; identical PAIRS, because the chip is clocking half as fast as the
; capture, and every model then fails to fit a stream that changed rate
; part way through. ~100 ms of nothing costs the gate nothing and a
; player sets its clock once at boot.
move.l #AD_SETTLE,d0
.settle:subq.l #1,d0
bne.s .settle
movea.l AD_BUF.l,a1
move.l AD_LEN.l,d1
bsr ad_arm
move.l #1,AD_FLAG.l
; ---- wait for the channel to actually take byte 0. Not a delay loop:
; the condition is MTC having moved, which is the channel's own account.
move.l #AD_PATIENCE,d3
.first: move.w A3_MTC,d0
andi.l #$FFFF,d0
cmp.l AD_LEN.l,d0
bne.s .go
subq.l #1,d3
bne.s .first
bra bad
.go: move.l d0,AD_MTC0.l
bsr ad_play
move.l #2,AD_FLAG.l
moveq #0,d0
move.b AD_CTRLR,d0 ; bit 7 clear = the chip says it is playing
move.l d0,AD_STAT.l
clr.l AD_SPIN.l
move.l #AD_PATIENCE,d3
.wait: addq.l #1,AD_SPIN.l
move.b A3_CSR,d4
btst #4,d4 ; ERR -- CER says which
bne.s bad
btst #7,d4 ; COC
bne.s .fin
subq.l #1,d3
bne.s .wait
bra.s bad
.fin: bsr report
; The chip is left PLAYING deliberately: it goes on replaying the last
; byte it was given, which the verifier ignores. Stopping here would
; put a silence in the capture at a point the host would then have to
; find, and the capture already has a length it knows.
move.l #$FF,AD_FLAG.l
hold: bra.s hold
bad: bsr report
move.l #$EE,AD_FLAG.l
bra.s hold
report:
moveq #0,d0
move.b A3_CSR,d0
move.l d0,AD_CSRF.l
moveq #0,d0
move.b A3_CER,d0
move.l d0,AD_CERF.l
move.w A3_MTC,d0
andi.l #$FFFF,d0
move.l d0,AD_MTCF.l
move.l A3_MAR,d0
move.l d0,AD_MARF.l
rts
include "src/player/adpcm.i"