Files
prosolis 5921fab118 Put the transport on the 68000, and find PIO costs 87 clocks a byte
ROADMAP P4b. src/player/xfer.i answers src/player/ring.i's XF_* mailbox with a
real READ(10) to a real MB89352 in place of tools/bench/stream.lua's modelled
transport: 120 records, 4,488,588 B, pixel-exact out of a 256 KB ring, with a
real mid-stream seek in a second pass. The tiling is the SAME 18 wraps and
14.7 KB mean hole that 49.4's host producer and 55.4's modelled transport
produced -- a third transport, same placement, which is the assertion that
ring.i could not tell which side of the seam answered it.

What it costs is the finding. tools/bench/xfer_cost.sh subtracts the same 120
frames run twice and gets 87.28 clocks per delivered byte, against the 68000's
own cycle table for the loop, which says 87.15 -- 0.2% apart, so the cost is the
instruction stream and not MAME's device model, and it is the first number this
rig has produced that survives leaving the emulator. That is 391.8% of a 12 fps
frame; the machine's own V-DISP clock agrees from the other end at 2.57 fps.
Against the ladder, W=5 held is 22.4% of a frame and W=19 is 85.3%, so P4a is
worth 4.6x the worst DMA configuration in this tree and 17.5x the best -- where
before this session it was worth 9 against 19. W itself did not move by a clock.

"UNDERRUNS: 0/120" is vacuous with a synchronous transport, and stream.lua now
prints that argument next to the zero: a frame cannot start before its record
has landed because the decoder IS the transport. The counter that means
something is NO IDLE, 119/120 with a worst overrun of 441 whole ticks. Same
class of error as 49.7.2's free-running ring passing at 48 KB.

58.3: a record is not a sector -- 117 of 120 start part way into one, and
reading whole sectors into the ring corrupts the neighbours rather than wasting
bytes (49.2, no bounds check). scsi.i reads the covering sectors and stores only
the window, which is free in PIO and stops being free the moment P4a succeeds.
tools/analysis/26_sector_align.py prices the three ways out and sector-aligned
records win on both axes: +0.43% wire and zero clocks, against +1.34% and a
bounce copy at +5 clk/B. ROADMAP now carries a four-item re-encode bundle and
P4a should be attempted against a sector-aligned container.

check.sh gains two stages and was ALL GREEN before and after. decode.bin is
unchanged at 1,296 B and the same MD5.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-24 23:15:53 -07:00

121 lines
6.7 KiB
Bash
Executable File

#!/bin/bash
# One paced ring-buffer run (STATUS item 4, FINDINGS 49.7.2).
#
# tools/bench/check.sh runs the ring pass FREE-RUNNING, which is right for what
# it gates -- wrap correctness at a fixed ring size, delivery removed as a
# variable by an unlimited pipe. It cannot answer the buffering question,
# because a free-running decoder never lets the ring back up.
#
# This runs the same rig with the decoder held to the container's frame rate,
# so the ring fills and FR_HEAD-FR_TAIL means "frames the decoder could still
# draw with the pipe dead". Every run is verified PIXEL-EXACT: a paced decode
# that drops a pixel is not a slack measurement, it is a bug.
#
# tools/bench/pace_run.sh <ring_kb> <kbps> [cut_at_tick] [cut_frames]
#
# kbps 0 = unlimited pipe. There is no default rate anywhere in this tree
# (FINDINGS 50) and there is none here either.
#
# DLX_RINGOWN=1 hands the RING to the 68000 as well (ROADMAP P5,
# src/player/ring.i): this script's Lua stops placing records and becomes a
# transport that answers one request at a time. DLX_PREFILL_FR is then the
# prefill policy, in whole records. It needs a DLX4 container, because the
# machine cannot learn a record's length by walking a stream it has not fetched.
#
# DLX_ITER=2 runs the scene TWICE, which under DLX_RINGOWN means a real seek
# between the passes: the channel goes quiet, the ring is declared empty and the
# whole accumulated lookahead is thrown away and rebuilt from the prefill. It
# needs DLX_PACE=2, because rebasing the frame clock across a pass is the
# machine's to do and a host-written tick would carry on counting.
#
# DLX_XFER=scsi replaces the MODELLED transport with a real one (ROADMAP P4b,
# src/player/xfer.i): the machine gets a CZ-6BS1 and the same volume the SCSI
# gate reads, this script stops moving bytes altogether, and every record is
# fetched by the 68000 with READ(10). It needs DLX_RINGOWN=1 -- the mailbox it
# answers is ring.i's -- and it FORBIDS a modelled rate, because there is no
# longer anything for one to model.
#
# DLX_PACE selects WHO KEEPS THE TIME: 1 (default) is the host writing the tick,
# 2 is the 68000 writing it off the CRTC's V-DISP (ROADMAP P3, FINDINGS 54).
# Everything else about the run is identical, which is the whole point -- the
# pace gate in src/player/stream.s cannot tell them apart, so a difference in
# the result is a difference in the CLOCK and not in the rig.
set -e
cd "$(dirname "$0")/../.."
RING=${1:?ring KB}; KBPS=${2:?pipe KB/s, or 0 for unlimited}
CUT_AT=$3; CUT_FR=${4:-1}
DLX=${DLX:-tmp/rc_fr_singe_scsi_span.dlx}
PACE=${DLX_PACE:-1}
OWN=${DLX_RINGOWN:-0}
ITERS=${DLX_ITER:-1}
XFER=${DLX_XFER:-model}
# EMULATED seconds the run is allowed. A pass that is cut short compares a
# half-drawn screen and reads as a wrap bug, so this is raised deliberately
# rather than left to a timeout: a DLX_XFER=scsi pass costs ~46 s of emulated
# time against the modelled transport's ~7, because the CPU moves every byte
# itself (FINDINGS 58.2), and two of them do not fit in 90.
SECS=${DLX_SECONDS:-90}
if [ "$XFER" = scsi ]; then
[ "$OWN" = 1 ] || { echo "DLX_XFER=scsi needs DLX_RINGOWN=1: the transport in"
echo "src/player/xfer.i answers src/player/ring.i's mailbox, and with the"
echo "host owning the ring there is no mailbox to answer."; exit 2; }
# A rate is not merely ignored here, it is REFUSED. The bytes now arrive on
# the emulated machine's own time, and a run labelled "488 KB/s" that did not
# deliver at 488 KB/s is exactly the kind of number this project has twice
# paid for. There is no rate in a DLX_XFER=scsi run, and the log says so.
[ "$KBPS" = 0 ] || { echo "DLX_XFER=scsi takes kbps 0. The transport is real,"
echo "so nothing here delivers at a modelled rate -- and MAME's device"
echo "models are functional, not transfer-timing accurate, so the rate it"
echo "DOES deliver at is not a measurement either (docs/BENCHMARK.md)."
exit 2; }
command -v chdman > /dev/null || { echo "DLX_XFER=scsi needs chdman (ships"
echo "with mame-tools) to build the volume."; exit 2; }
bash tools/bench/mkvol.sh "$DLX"
fi
if [ "$OWN" = 1 ] && [ "$ITERS" != 1 ] && [ "$PACE" != 2 ]; then
echo "DLX_ITER>1 needs DLX_PACE=2: the frame clock is rebased per pass by"
echo "src/player/stream.s, and a host-written tick would go on counting"
echo "through the seek and open every slot of the second pass at once."
exit 2
fi
TAG="r${RING}_k${KBPS}${CUT_AT:+_cut${CUT_AT}x${CUT_FR}}"
# The default tag is left ALONE when the host keeps the time: tools/bench/
# pace_sweep.sh reads tmp/pace_r<ring>_k<kbps>.log by name, and renaming the
# host-paced logs would break a sweep that has nothing to do with this option.
if [ "$PACE" != 1 ]; then TAG="${TAG}_p$PACE"; fi
if [ "$OWN" = 1 ]; then TAG="${TAG}_own"; fi
if [ "$ITERS" != 1 ]; then TAG="${TAG}_x$ITERS"; fi
if [ "$XFER" != model ]; then TAG="${TAG}_$XFER"; fi
MAMEX=()
if [ "$XFER" = scsi ]; then
MAMEX=(-exp1 cz6bs1 -rompath "$HOME/mame/roms;./p4roms" -hard dlxdisk.chd)
fi
tools/vasm/vasmm68k_mot -Fbin -o tmp/stream.bin src/player/stream.s > /dev/null
[ -f tmp/stream_disk.bin ] || python3 tools/bench/prep_stream.py "$DLX" > tmp/prep_stream.log
mkdir -p "tmp/snap_pace_$TAG"; rm -f "tmp/snap_pace_$TAG/x68000"/*.png
# `env` rather than an assignment prefix: an empty ${CUT_AT:+...} in the middle
# of a prefix is not an assignment token, so bash takes the next word as the
# command and the run dies with "SDL_VIDEODRIVER=dummy: command not found".
CUTENV=(); [ -n "$CUT_AT" ] && CUTENV=(DLX_CUT_AT="$CUT_AT" DLX_CUT_FR="$CUT_FR")
( cd tmp && env DLX_PACE=$PACE DLX_RING_KB=$RING DLX_STREAM_KBPS=$KBPS \
DLX_RINGOWN=$OWN DLX_ITER=$ITERS DLX_XFER=$XFER \
${DLX_PREFILL_FR:+DLX_PREFILL_FR=$DLX_PREFILL_FR} \
"${CUTENV[@]}" DLX_SLACK_CSV="slack_$TAG.csv" \
SDL_VIDEODRIVER=dummy stdbuf -oL timeout -k 5 900 \
mame x68000 -bios ipl10 "${MAMEX[@]}" -ramsize 2M -video soft -window \
-sound none \
-nothrottle -plugins -autoboot_script ../tools/bench/stream.lua \
-snapshot_directory "./snap_pace_$TAG" -snapview native \
-seconds_to_run $SECS \
> "pace_$TAG.log" 2>&1 )
# The completion marker is not optional: a run killed mid-decode compares a
# half-drawn screen and reads as a wrap bug rather than as a truncated run.
grep -q "snapshot taken" "tmp/pace_$TAG.log" || {
echo "FAIL($TAG): no snapshot marker -- the pass did not complete."
tail -6 "tmp/pace_$TAG.log"; exit 1; }
echo "=== $TAG"
grep -aE "decoder (SELF-PACED|PACED|FREE)|FRAME CLOCK|ring: |UNDERRUNS|NO IDLE|SEEK SLACK|RING-BOUND|RATE-BOUND|BUILD TIME|PIPE CUT|DEADLINE|REQUIRED|MACHINE-OWNED|PREFILL:|CHANNEL IDLE|MISPLACED|SEEK PASS|REAL TRANSPORT|SECTOR OVERHEAD|TRANSPORT FAILED|IS VACUOUS" \
"tmp/pace_$TAG.log" | sed "s/\[STR\] / /"
python3 tools/bench/verify_decode.py "$DLX" --snap "tmp/snap_pace_$TAG" | tail -2