Put the container on the chip, and find the held bus costs 463 times the seam
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
This commit is contained in:
@@ -7219,3 +7219,152 @@ whole sectors and the wire pays for the zero ones.
|
||||
disc is still unmeasured.
|
||||
* **The lump buffer is not allocated anywhere.** 65.3 charges 14,336 B for
|
||||
double-buffering the cadence and no player holds it.
|
||||
|
||||
## 68. The container is played — and holding the bus costs the audio 463 times the seam (session 36)
|
||||
|
||||
**ROADMAP P6c. Session 36.** `src/player/packed.s` (the audio path),
|
||||
`src/player/dma.i` (`DM_HOOK`), `src/player/scsi.i`, `tools/encoder/adpcm.py`
|
||||
(`decode_state`), `tools/bench/verify_packed_audio.py`,
|
||||
`tools/bench/prep_packed.py`, `tools/bench/packed.lua`,
|
||||
`tools/bench/packed_run.sh`, `tools/bench/check.sh`.
|
||||
|
||||
**NAME THE LAYER.** 68000 code fetched an interleaved DLXP2 off a real MB89352
|
||||
volume, ran the video path against it, and fed the audio lumps to the machine's
|
||||
**MSM6258 as MAME models it** through the IPL ROM's own HD63450 channel 3. The
|
||||
capture is MAME's `-wavwrite`. **No board ran**, and every number below is the
|
||||
emulated machine's. What is new is that a byte of this container has now been
|
||||
**heard** on some layer, which 67.6 recorded that none had.
|
||||
|
||||
### 68.1 It plays, and the gate is the speaker rather than the counters
|
||||
|
||||
All **78,125 B** of the container's audio reached the chip, in order, and every
|
||||
sample the chip produced from them is exact against `adpcm.py` run with the four
|
||||
axes **out of the container's own header** — **156,257 samples**, `signal =
|
||||
sample/8` recovering the chip's stream with a worst residual of 1 count.
|
||||
|
||||
`packed.s` also reports 11 of 11 lumps armed, 11 fetched, 78,125 B of payload,
|
||||
**0 starves and 0 re-arms with bytes still outstanding**. Those numbers are not
|
||||
the gate and must not be: see 68.4.
|
||||
|
||||
### 68.2 THE HEADLINE. Held, the bus costs the audio 236 ms of replayed byte
|
||||
|
||||
The MSM6258 has no FIFO and **no starvation state**: when its DMA channel counts
|
||||
out it goes on decoding nibbles out of whatever byte its data register still
|
||||
holds (MAME `okim6258::sound_stream_update` reads `m_data_in` unconditionally
|
||||
while PLAYING). So the interval between a channel counting out and the CPU
|
||||
arming the next lump is not a gap — it is a **held nibble pair driving the
|
||||
predictor**, and it is measurable in the capture as bytes that were played for
|
||||
more than two nibbles.
|
||||
|
||||
Same container, same player, same 120 frames, one bit of DCR different:
|
||||
|
||||
| | stealing | held |
|
||||
|---|---:|---:|
|
||||
| audio service calls | **250,240** | **369** |
|
||||
| ...per frame shown | 2,085 | 3.1 |
|
||||
| lump boundaries with a seam | 4 of 10 | **10 of 10** |
|
||||
| worst seam | 2 samples, **0.13 ms** | 1,138 samples, **72.83 ms** |
|
||||
| total replayed | 8 samples, 0.51 ms | 3,694 samples, **236.42 ms** |
|
||||
| ...as a share of the audio | 0.0051% | **2.31%** |
|
||||
| bytes wrong | 0 | 0 |
|
||||
|
||||
**The bytes are identical and the sound is not.** Held is 673x fewer looks at
|
||||
the channel and 463x the seam. A 72.8 ms seam is most of a 12 fps frame of one
|
||||
byte pair repeating: not a dropout, a buzz, once every 0.92 s.
|
||||
|
||||
This is FINDINGS 64.3 arriving on the audio side. 64.3 measured a held channel
|
||||
costing the frame CLOCK 47% of its ticks *without the clock being able to tell*;
|
||||
the same halt costs the audio its service, and the audio can tell — in the only
|
||||
place the interval exists, which is the sound.
|
||||
|
||||
**And it is why the seam is small in the stealing configuration at all.** The
|
||||
one place a 68000 driving this video path has time is **inside the transfer
|
||||
wait**. Of the 250,240 service calls, **240 are the frame loop's** two-a-frame
|
||||
and the other **250,000 came from `DM_HOOK`**, dma.i's per-trip hook. Held, the
|
||||
same player gets 369: the same 240, plus the 129 single trips round the wait
|
||||
that 131 burst transfers (120 records and 11 lumps) still allow. Servicing the
|
||||
audio only from the frame loop is a 90 ms seam by construction, in either
|
||||
configuration — the hook is the whole of the difference.
|
||||
|
||||
### 68.3 The drift is measured now, not derived
|
||||
|
||||
67.2 computed that feeding the chip the whole 7,168 B lump instead of the
|
||||
payload runs 0.09% fast, and priced it at 1.25 s of lip-sync over the game. The
|
||||
accumulator (`acc += F*hz; n = acc/(2*fps); acc %= 2*fps`, three lines in
|
||||
`pg_apay`) is now in a player and the capture prices what it avoided, over the
|
||||
10 lumps that carry a whole group:
|
||||
|
||||
payload 71,614 B against 71,680 B of lump space
|
||||
excess 66 B = 0.092% = 8.45 ms over 9.17 s
|
||||
-> 1.26 s over the game's 22.8 min
|
||||
|
||||
**67.2 predicted 1.25 and the machine played 1.26.** The accumulator ends the
|
||||
scene at 1/24, which is where the arithmetic says it should.
|
||||
|
||||
The gate does not merely *check* the payload lengths, it **cannot pass without
|
||||
them**: the walk accounts for every byte of `DLXP.audio()`, and a whole-lump
|
||||
player's stream contains 6.54 B of zero at the end of each group that are not in
|
||||
that stream at all.
|
||||
|
||||
### 68.4 Two bugs, and neither is visible from inside the player
|
||||
|
||||
**(a) Clearing `DM_BARV` does not unchain a channel.** `dma.i` reads `DM_BARV`
|
||||
to decide whether to write MAR and MTC, but the register the *channel* obeys is
|
||||
**OCR bits 3-2**, which `pg_dmacfg` sets to %10 for the whole scene. So the
|
||||
lump fetch wrote MAR and MTC and the channel ignored both and loaded them from
|
||||
whatever BAR held (`hd63450.cpp` `dma_transfer_start`). It does not fail as a
|
||||
bad address: the SPC's phase never completes and the read returns **POLL
|
||||
TIMEOUT — on the lump and then on every record after it**. A wedged transport
|
||||
that reads exactly like a disc problem.
|
||||
|
||||
**(b) The refill overwrote the lump that was playing.** The resident set is
|
||||
`AKF - AK + 1` lumps, not `AKF - AK`: `AK` is the *next* lump to arm, so the one
|
||||
channel 3 is reading is `AK-1` and its slot is not free. Off by one, the fetcher
|
||||
ran three lumps ahead of a three-slot ring and lump 3 landed on top of lump 0
|
||||
**while the channel was reading it**.
|
||||
|
||||
**Every counter in the player stayed right**: 11 of 11 armed, 11 fetched, 78,125
|
||||
B of payload, no starve, no late re-arm — and the sound was wrong from **0.2 s**
|
||||
in. This is 67.4's failure mode arriving on the audio side. Nothing parses a
|
||||
packed container, so a wrong byte is not an error, it is a sound; the only
|
||||
instrument that can see it is the stream the chip produced.
|
||||
|
||||
### 68.5 The rig fact that shapes the gate: a byte is not always two nibbles
|
||||
|
||||
MAME's `okim6258::data_w` sets `m_data_in` **and resets `m_nibble_shift` to 0**.
|
||||
The DMA channel writes at 7,812.5 B/s and the stream decodes at 15,625 Hz, and
|
||||
those are two independent clocks — so where a write lands inside a sound-stream
|
||||
slice decides whether the byte before it was played as two nibbles, as one (the
|
||||
high nibble dropped), or as three or more.
|
||||
|
||||
Measured over the stealing run: **78,120 of 78,125 bytes were played as exactly
|
||||
two nibbles (99.994%)**, one as one, four as four. So a whole-stream
|
||||
`decode(data) == capture` cannot be the check, and a tolerance would not be one
|
||||
either. The gate's model is one line — *byte `b` was played as `c` nibbles from
|
||||
the cycle `(b&15, b>>4)`, `c >= 1`* — and it finds `c` for every byte, requiring
|
||||
all `c` samples and 32 samples of continuation to be exact. **Every `c > 2` in
|
||||
both runs was the last byte of a lump**, asserted, which is what makes the same
|
||||
walk both the correctness check and the seam measurement.
|
||||
|
||||
This is a property of the emulator's audio scheduling, not of the machine and
|
||||
not of the format. On silicon the chip latches a byte and consumes both nibbles.
|
||||
|
||||
### 68.6 What was NOT established
|
||||
|
||||
* **Two slots was not tried.** Three lump slots and a prefill of two is what
|
||||
ran; the minimum buffering depth is an experiment nobody has done, so 68's
|
||||
49,152 B of slot (21,504 B of lump) is an upper bound and not a requirement.
|
||||
The slot stride is 16,384 B for a 7,168 B lump to make the address a shift;
|
||||
a shipping player should charge `A*512` and pay the `mulu`.
|
||||
* **The seam figures are this apparatus's.** They are set by how often the CPU
|
||||
looks at the channel, which is set by how long the emulated transport takes to
|
||||
deliver a record (90.7 ms against a 12 fps slot of 83.3). The *ratio* between
|
||||
held and stealing is the finding; the absolute milliseconds are not `W`.
|
||||
* **Nothing has been listened to.** The capture is an instrument. 66's risk is
|
||||
unchanged and so is 66.3's: **the level is still open downward and the loudest
|
||||
passage on the disc is still unmeasured.**
|
||||
* **A second pass does not replay the audio.** `PG_ITER > 1` repeats the video
|
||||
and leaves `PG_AK` at `PG_NLUMP`; the run that matters is one pass.
|
||||
* **The silicon is untouched.** All four decoder axes are still MAME's, and
|
||||
session 34's fifth hardware item — play a known nibble stream on a real
|
||||
MSM6258V and record the line out — is still the cheap way to settle them.
|
||||
|
||||
Reference in New Issue
Block a user