Files
prosolis 07f36c2af9 Put the palette on the channel, and find one start paints a whole frame
ROADMAP K1, the packed player's one open structural item.  A frame is a picture
AND a palette, and no run in this tree had pointed a DMA channel at the palette
registers.  dmagate.s runs 7-9, gated by dma_run.sh and check.sh:

  7. 512 B off the disc into $E82000, bus held -- byte-exact in 256 register
     words, read back OUT OF the registers by the 68000;
  8. the SAME transfer aimed at RAM -- byte-exact at $2C000, and 256 of 256
     palette words still read the poison the CPU wrote, which is what attributes
     run 7 to the channel's MAR rather than to the readback path;
  9. ONE array-chained start across two kinds of destination -- the palette and
     six picture rows at the 1,024 B line stride, 2,048 B byte-exact.

So a packed frame is one channel start: a 193-entry array, palette first, CPU
halted from the first byte to the last.  The array is scene-constant, because
the packed layout spends both 256-colour pages and there is no page to flip.
What is left on the CPU per frame in the video path is the channel start and the
READ(10) -- no per-frame PAINT, which is not the same claim as no per-frame CPU.

The destination is POISONED first (62.1).  Runs 4-6 wrote into RAM that was zero
and GVRAM that was stale against a record that is mostly pad; "it matches the
disc" was weaker than it read as.  The host counts whether the poison actually
discriminates instead of assuming it: 511 of 512, and the gate refuses under 500.

And it opened a hardware item (62.4, ROADMAP B4).  MAME maps the palette to
palette_device over memory_array, whose write16 is a plain COMBINE_DATA -- RAM
that honours mem_mask, with no handler that could refuse a byte write.  Unlike
GVRAM's 256-colour arm there is nothing here to be wrong about, so the run
bounds the model and not the board.  What a real palette register does with a
byte write is unmeasured.  A negative costs 0.28% of a frame and nothing else.

29_packed_player.py now also prints the two rows with the per-frame palette
charged -- 55.7% of a frame on the chain, 582 KB/s -- alongside the picture-only
figures the codec comparison is quoted against.

check.sh ALL GREEN before (tmp/check_s30_start.log) and after
(tmp/check_s30_end.log).

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-25 07:06:44 -07:00

155 lines
8.9 KiB
Bash
Executable File

#!/bin/bash
# One HD63450 data-phase run: does the DMAC drive the SCSI data phase, and does
# it HOLD THE BUS? (ROADMAP P4a, the last item before M2.)
#
# tools/bench/dma_run.sh [container.dlx]
#
# The apparatus is tools/bench/scsi_run.sh's -- `x68000 -exp1 cz6bs1` and a
# zero-filled scsiexrom.bin on a private rompath -- and the volume is
# tools/bench/mkvol.sh's, the same bytes the host-file ring rig reads.
#
# WHAT A GREEN RUN MEANS: the same 2,048 B came off the disc three ways -- PIO,
# the channel with the bus held, the channel stealing cycles -- all three
# byte-exact against the host's copy; and in the held configuration THE WHOLE
# TRANSFER HAPPENED BETWEEN TWO INSTRUCTIONS, which is what holding the bus
# means and is not a claim about $EA0015 (57.3).
#
# WHAT IT DOES NOT MEAN: anything about `W`. MAME's DMAC runs on wall-clock
# attotimes (42.5) and models a held bus by HALTING the CPU rather than by
# charging it cycles per operand. This settles which configuration works.
set -e
cd "$(dirname "$0")/../.."
DLX=${1:-tmp/rc_fr_singe_scsi_span.dlx}
bash tools/bench/mkvol.sh "$DLX"
tools/vasm/vasmm68k_mot -Fbin -o tmp/dmagate.bin src/player/dmagate.s > /dev/null
# What the player will program, decoded out of the same constants it programs.
python3 tools/analysis/27_dmac_config.py
# stdbuf -oL: without it a long MAME run is unobservable until it exits, and a
# run that is merely finishing looks exactly like one that is wedged (34.1).
( cd tmp && SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 300 \
mame x68000 -bios ipl10 -exp1 cz6bs1 \
-rompath "$HOME/mame/roms;./p4roms" -hard dlxdisk.chd \
-ramsize 2M -video soft -window -sound none -nothrottle -plugins \
-autoboot_script ../tools/bench/dma.lua \
-seconds_to_run 90 > dma_run.log 2>&1 )
grep -aq "^\[DMA\] done" tmp/dma_run.log || {
echo "FAIL: the DMA gate did not finish -- no completion marker."
tail -8 tmp/dma_run.log; exit 1; }
grep -a "^\[DMA\]" tmp/dma_run.log | sed 's/^\[DMA\] / /'
# THE ASSERTIONS. Printing a result and gating on it are different things.
fail() { echo "FAIL: $1"; exit 1; }
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[pio\]" tmp/dma_run.log || \
fail "the PIO reference read did not match -- nothing below is about the DMAC."
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[held\]" tmp/dma_run.log || \
fail "the bus-held DMA read did not deliver the disc's bytes."
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[steal\]" tmp/dma_run.log || \
fail "the cycle-stealing DMA read did not deliver the disc's bytes."
grep -aq "MTC one instruction after START: 0 of 2048 .*NEVER EXECUTED .*\[held\]" \
tmp/dma_run.log || \
fail "the bus was NOT held: the CPU executed while the channel ran, so this is
not the configuration ROADMAP P4a asks for. That MTC is the whole of the
evidence that does not come from watching \$EA0015 (57.3)."
grep -aq "CPU trips round the wait loop: 1 \[held\]" tmp/dma_run.log || \
fail "the held configuration's CPU went round its wait loop more than once --
it was running, so the bus was not held for the whole transfer."
# A NEGATIVE ASSERTION IS WRITTEN AS AN `if`, not as `grep ... && fail`: under
# `set -e` a failing grep in an AND-list takes the whole script's exit status
# with it, so the run would report the failure it was looking for as a pass.
SPIN=$(sed -n 's/.*CPU trips round the wait loop: \([0-9]*\) \[steal\].*/\1/p' \
tmp/dma_run.log)
[ -n "$SPIN" ] && [ "$SPIN" -ge 100 ] || \
fail "the cycle-stealing configuration did not leave the CPU running (spin
= ${SPIN:-none}) -- the two configurations are meant to DIFFER in exactly
that, and a contrast of one against one is not a contrast."
if grep -aq "MTC one instruction after START: 0 of 2048 .*\[steal\]" tmp/dma_run.log
then
fail "the cycle-stealing configuration also finished between two instructions,
so the comparison has no contrast in it and the discriminator is measuring
something other than bus ownership."
fi
grep -aq "COC .*CER=\$00 MTC=0 .*(+2048) \[held\]" tmp/dma_run.log || \
fail "the held channel did not report a clean completion of every byte."
grep -aq "COC .*CER=\$00 MTC=0 .*(+2048) \[steal\]" tmp/dma_run.log || \
fail "the stealing channel did not report a clean completion of every byte."
# ---- the GVRAM run and its control (47.6.2). A channel that writes GVRAM in
# buffer mode is the decoder-free packed player's entire per-frame path, and a
# run with no control is 58.3's vacuous "UNDERRUNS: 0/120" again -- the IPL
# leaves R20 = $0B16, bit 11 ALREADY SET, so the first cut of this test could
# not have failed.
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[gvram\]" tmp/dma_run.log || \
fail "the channel did not fill GVRAM in buffer mode -- a device->GVRAM
transfer is the whole of the decoder-free packed player's frame."
grep -aq "R20 during the run = \$0916 (bit 11 SET)" tmp/dma_run.log || \
fail "the GVRAM run did not run in buffer mode with a KNOWN R20."
grep -aq "R20 during the run = \$0116 (bit 11 CLEAR)" tmp/dma_run.log || \
fail "the negative control did not run with bit 11 clear."
if grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[masked\]" tmp/dma_run.log
then
fail "the MASKED control delivered every byte, so the run above is not a
measurement of R20 bit 11 -- it is a measurement of nothing."
fi
grep -aq "EXACTLY THE MECHANISM" tmp/dma_run.log || \
fail "the masked control lost bytes at ODD offsets too, or lost none at all.
The claim is not a COUNT -- stale GVRAM matches the disc by coincidence
wherever the record is pad -- it is a PLACE: gvram_w's 256-colour arm
drops what the channel wrote to EVEN addresses and stores what it wrote
to odd ones. Damage anywhere else is a different mechanism."
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[chain\]" tmp/dma_run.log || \
fail "the array-chained run did not put the bytes at the row bases its array
named. A picture row is 256 B of a 1024 B line stride, so a frame is 192
destinations; if the channel cannot walk them the CPU has to restart it
per row and the decoder-free path costs a per-row front end."
grep -aq "THE CHANNEL WALKED THE ARRAY ITSELF" tmp/dma_run.log || \
fail "the chained run did not report walking its own array."
# ---- THE PALETTE (ROADMAP K1, FINDINGS 61.9). If the registers at $E82000 take
# a byte-wide DMA the way GVRAM does in buffer mode, a per-frame palette is a
# 193rd array-chain entry and ONE channel start paints a whole frame; if they do
# not, the CPU writes 256 words a frame and the architecture still stands. The
# run is poisoned first and controlled twice -- once by aiming the same transfer
# elsewhere, once by counting how many of the 512 positions the poison and the
# disc actually differ in.
grep -aq "BYTES OK: 512 B from LBA 1000 .*\[pal\]" tmp/dma_run.log || \
fail "the channel did not write the palette registers at \$E82000 -- so a
per-frame palette costs the CPU 256 word writes and cannot ride the
frame's array chain (61.9). That is a RESULT, not a broken run: check the
PALETTE WRONG line above for whether the bytes were dropped or misplaced."
DIFF=$(sed -n 's/.*PALETTE POISON IS A DISCRIMINATOR: \([0-9]*\) of 512.*/\1/p' \
tmp/dma_run.log)
[ -n "$DIFF" ] && [ "$DIFF" -ge 500 ] || \
fail "the poison and the disc's bytes agree in ${DIFF:-?} of 512 positions, so
the palette run could have passed without a channel writing anything --
this is run 4's could-not-fail trap in a new place. Change DGPOIS."
grep -aq "BYTES OK: 512 B from LBA 1000 .*\[palctl\]" tmp/dma_run.log || \
fail "the ATTRIBUTION control's read did not land in RAM, so its palette claim
is about a transfer that did not happen."
grep -aq "PALETTE UNTOUCHED BY THE CONTROL: 256 of 256 words" tmp/dma_run.log || \
fail "the palette changed during a transfer aimed 20 KB away from it. Then
what reached \$E82000 in the run above was not decided by the channel's
MAR, and that run measured something else."
if grep -aq "CONTROL DID NOT FAIL \[palctl\]" tmp/dma_run.log
then
fail "the control reported its own failure -- see the line above it."
fi
grep -aq "BYTES OK: 2048 B from LBA 1000 .*\[palchain\]" tmp/dma_run.log || \
fail "ONE array-chained start could not cross from the palette registers into
GVRAM. A frame is one palette entry and 192 row entries; if the two kinds
of destination cannot share a chain, the CPU is back in the video path
once a frame to start the second half of it."
grep -aq "ONE START PAINTED THE PALETTE AND 6 ROWS" tmp/dma_run.log || \
fail "the palette+rows run did not report the crossing it exists to show."
grep -aq "WINDOWED DMA READ REFUSED" tmp/dma_run.log || \
fail "a WINDOWED read through the channel was not refused. 117 of 120 records
start part way into a sector (58.3), and a channel cannot drop the bytes
in front of one -- so it would write the neighbouring records into the
ring, over data the decoder has not finished with, with no bounds check
to catch it (49.2)."
exit 0