#!/bin/bash # What the PIO transport costs the 68000, by subtraction (ROADMAP P4b, # FINDINGS 58.2). # # tools/bench/xfer_cost.sh [ring_kb] # # THE MEASUREMENT IS AN A/B AND HAS TO BE. Nothing in the machine can time # itself finely enough: src/player/clock.i counts V-DISP at 56.69 Hz and the # thing being priced is a per-BYTE cost. So the same 120 frames are decoded # twice, from the same ring, by the same src/player/stream.s, with the same # src/player/ring.i placing every record -- and the ONLY difference is which # side of the XF_* mailbox answers: # # model tools/bench/stream.lua, an unlimited modelled pipe. Bytes appear # in the ring for free; the emulated time is decode plus ring_poll. # scsi src/player/xfer.i and src/player/scsi.i, a real MB89352 and a real # volume. The CPU moves every byte itself. # # Both runs are FREE-RUNNING (DLX_PACE=0). A paced run would measure the pace, # not the work, and the difference this script exists for would vanish into the # wait loop. # # WHAT THE SUBTRACTION IS HONEST ABOUT, and it is worth stating twice: this is a # count of 68000 CLOCKS, not a delivery rate. It says what the player pays to # move a byte with its own instruction stream. It says nothing about how fast # the medium can supply one -- MAME's device models are functional, not # transfer-timing accurate (docs/BENCHMARK.md, 42.5), and `W`, the clocks a DMAC # steals per delivered byte, is untouched by every line here. # # The two run boundaries are sampled on the machine-frame notifier, so each end # is up to 17.64 ms coarse (54.5). Against a difference of tens of seconds that # is under a tenth of a percent, and the analytic cross-check below is what # actually establishes the figure -- the subtraction only has to agree with it. set -e cd "$(dirname "$0")/../.." RING=${1:-256} command -v chdman > /dev/null || { echo "needs chdman (mame-tools)"; exit 2; } echo "=== modelled transport, unlimited pipe" DLX_PACE=0 DLX_RINGOWN=1 DLX_QDEPTH=2 bash tools/bench/pace_run.sh "$RING" 0 \ > tmp/xfer_cost_model.log 2>&1 echo "=== real transport, a CZ-6BS1 and a real volume" DLX_PACE=0 DLX_RINGOWN=1 DLX_QDEPTH=2 DLX_XFER=scsi \ bash tools/bench/pace_run.sh "$RING" 0 > tmp/xfer_cost_scsi.log 2>&1 python3 - <<'PY' import re, sys def one(tag, log): t = open(log, "rb").read().decode("latin1") m = re.search(r"decoded (\d+) frames in ([0-9.]+) s emulated", t) if not m: sys.exit(f"{log}: no completion line -- the pass did not finish") return int(m.group(1)), float(m.group(2)), t nfr, t_model, _ = one("model", "tmp/pace_r256_k0_p0_own.log") nfr2, t_scsi, ts = one("scsi", "tmp/pace_r256_k0_p0_own_scsi.log") assert nfr == nfr2, "the two runs decoded different frame counts" nb = int(re.search(r"REAL TRANSPORT: \d+ READ\(10\)s by the 68000, (\d+) B", ts).group(1)) nw = int(re.search(r"SECTOR OVERHEAD: (\d+) B off the disc", ts).group(1)) CPUHZ = 10_000_000 FPS = 12 budget = CPUHZ / FPS d = (t_scsi - t_model) * CPUHZ print(f" {nfr} frames, {nb:,} B of record, {nw:,} B off the disc") print(f" decode + ring_poll alone {t_model:8.4f} s emulated " f"= {100*t_model*CPUHZ/nfr/budget:5.1f}% of a {FPS} fps frame") print(f" ...with the real transport {t_scsi:8.4f} s emulated " f"= {100*t_scsi*CPUHZ/nfr/budget:5.1f}%") print(f" TRANSPORT COST: {d:,.0f} clocks = {d/nb:.2f} per delivered byte, " f"{d/nw:.2f} per byte off the FIFO") print(f" {d/nfr:,.0f} clk/frame = " f"{100*d/nfr/budget:.1f}% of a {FPS} fps frame") # ---- THE CROSS-CHECK, and it is the half that makes the number portable. # If the measured cost is the 68000's own instruction stream, it must equal the # 68000's cycle table for the loop in src/player/scsi.i. If it does NOT, the # difference is time spent waiting on MAME's SPC model -- which is emulator # behaviour and would not survive contact with a board. LOOP = [("move.l #SC_PATIENCE,d3", 12), ("move.b SC_SSTS,d0 (xxx).L->Dn", 16), ("btst #0,d0", 10), ("beq.s taken", 10), ("move.b SC_DREG,(a1)+ (xxx).L->(An)+", 20), ("subq.l #1,d7", 8), ("bne.s taken", 10)] loop = sum(c for _, c in LOOP) print(f"\n the keep loop in src/player/scsi.i, from the 68000's cycle table:") for n, c in LOOP: print(f" {c:3d} {n}") pred = loop * nw / nb print(f" --- {loop} clk per byte off the FIFO, and the FIFO carries the " f"dropped\n window bytes too: {pred:.2f} per DELIVERED byte") err = 100 * abs(pred - d/nb) / (d/nb) print(f" MEASURED {d/nb:.2f} vs PREDICTED {pred:.2f} -- {err:.1f}% apart, so " f"the cost is the\n instruction stream and not MAME's SPC: it is a " f"figure a real board would also pay.") resid = d/nb - pred print(f" residual {resid:+.2f} clk/B = {resid*nb/nfr:,.0f} clk/record of " f"select, CDB, status,\n message and xf_service -- the per-COMMAND cost, " f"which is the part that does not\n scale with the record.") print(f"\n AGAINST THE LADDER, in the same units (clocks charged to the CPU " f"per delivered\n byte). W is the DMAC's steal, 52.5's bracket:") for W, name in [(5, "single address, bus HELD"), (9, "dual address, held"), (12, "single address, arbitrated"), (19, "dual address, arbitrated -- the IPL ROM's own disk ch")]: print(f" W = {W:2d} {name:52s} {100*W*nb/nfr/budget:5.1f}% of the frame") print(f" PIO {d/nb:.0f} this rig, measured " f" {100*d/nfr/budget:15.1f}%") print(f"\n So the PIO transport is {d/nb/19:.1f}x the WORST DMA configuration " f"this project has\n found and {d/nb/5:.1f}x the best. P4a is not an " f"optimisation of this; it is the\n difference between a player and a " f"slideshow.") PY