The bench script
One text file drives a run on the head (head/headd.py, sent by
tools/bench.py run) and, with the same words, the model
(nes-console's pad-log takes the AT lines). A line is a word and
its arguments; # starts a comment. Latch indices count the console's
controller strobes since the last RESET, from zero, which is the
index the bridge's log and the model's log share.
| line | on the head | on the model |
|---|---|---|
RESET | zero the bridge's counters, then pulse the reset relay | the console powers on at latch zero (pad-log); bench-script holds the CPU's /RESET for the head's half second and releases it, the warm reset |
POWER ON / POWER OFF | the power relay | nothing |
MODE PASS / MODE INJECT | passed to the bridge: the original pad's byte, or the scripted one | the model has no pad in hand: INJECT is the only mode |
SET hh | the byte to hold now (bit 0 = A, set = pressed) | the byte from the start |
AT n hh | from latch n on, hold hh; written after latch n-1 so it is in the register at latch n | the controller's schedule, applied at the strobe's rise before latch n |
TRIG n | the bridge raises the trigger line at latch n (the DS1054Z has no EXT input, so the line needs a channel of its own; whether it has one is in head/leads.toml, and a run whose trig signal has no probe says so in its log); put it after the ARM, which takes seconds | nothing (the model renders the frame at n directly) |
ARM name [signals] [scale] [offset] [source] [s/div] [depth] | the scope set for a single shot: the channels and the trigger source as signal names, video, strobe, d0, clk, trig (or a channel by number, 3, 1,2,4, CH3), each resolved through head/leads.toml, the map the instrument writes (scope-leads.md); a signal with no probe is refused by name, and so is EXT, since the DS1054Z has none. The defaults are video triggered on strobe. Then the timebase and the memory depth (12 M points with one or two channels, at most 6 M with more); the capture is name.u8 (one channel) or name-chN.u8 (several) with name.toml beside them naming the rate, the trigger's sample and which channel carried which signal | nothing |
CAPTURE | wait for the armed trigger and read the record (a WAIT past the trigger does this too) | nothing |
WAIT n | until the bridge has logged latch n since its last RESET (the head clears its count on the bridge's # reset; before 2026-09-18 it kept the last session's, and a WAIT after a RESET could return at once, open-items.md) | run to poll n (bench-script counts from each RESET, as the bridge does) |
WAIT s S | seconds | nothing |
A B1 script, as the plan describes it:
POWER ON
WAIT 2 S
MODE INJECT
SET 00
RESET
AT 120 08 # Start, from poll 120
AT 122 00
ARM game-t300 video,trig 0.2 -0.5 trig # the video at 200 mV/div with the trigger line beside it, stopped by TRIG
TRIG 300
WAIT 320
CAPTURE
That script names signals, so it reads the same whichever channel the
probes are in; what it cannot do is run while the trigger line has no
probe (head/leads.toml says whether it does), because the ARM on
trig is refused. Arming on strobe instead catches the first strobe
after the scope is armed, which is some frame after the ARM returns
and not a chosen latch: a capture at latch 300 exactly waits for the
trigger line to have a probe again.
The head writes the run to runs/<stamp>/ (script.txt, head.log
with each line as it played, bridge.log with every line the bridge
printed, the captures) and bench.py fetches it. compare-logs.py
diffs bridge.log against pad-log's output for the same script.
A B2 script, one power-on (tools/b2-align.py sweep plays it N times
and classifies each run):
POWER OFF
WAIT 3 S
ARM align 1,2,4 1.0 -2.0 CH4 0.0005 1200000 # master clock, M2, ALE; triggered on ALE's first rise, when rendering starts
POWER ON
CAPTURE
POWER OFF
Pulled at build time from nes-bench/docs/script.md; the repository is the one copy. The reports use some working words of their own: Words the reports use.