#!/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 [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_k.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