Skip to content

Bench instruments part 2, timing fidelity, imperfections, and the Visual Benchmark v0.1 workbench (English and French) - #112

Open
teo1747 wants to merge 25 commits into
mainfrom
claude/affectionate-allen-40uv0o
Open

teo1747 wants to merge 25 commits into
mainfrom
claude/affectionate-allen-40uv0o

Conversation

@teo1747

@teo1747 teo1747 commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

This PR adds the last two bench instruments and their front panels. It also carries the timing-fidelity work the owner asked for on 2026-11-01, which comes before any release. docs/engineering/timing-fidelity.md holds the audit, the accuracy model, the plan, and what each change validates. It ends with the Visual Benchmark v0.1 redesign of the workbench, with settings and English and French (docs/engineering/visual-benchmark.md).

Bench instruments

  • Nordic PPK2 (emblink/nordic_ppk2), from the PPK2 user guide §8:
    • source meter at 0.8–5 V, limited at 600 mA, or ampere meter up to 1 A;
    • 100 kS/s, each sample the average over its window, quantised to the five ranges' resolutions;
    • a logic port;
    • a live panel.
  • Rigol DS1054Z (emblink/rigol_ds1054z), from the DS1000Z guide ch. 18:
    • four 1 MΩ ∥ 15 pF inputs behind their probes;
    • 1 GSa/s with one channel, down to 250 MSa/s with four;
    • 8 bits, with coupling, bandwidth limit and edge trigger in Auto, Normal or Single;
    • a live screen with the automatic measurements.
  • Front panels open from the inspector, the schematic's context menu, the bench list or the palette. They dock in Run or float, moved and resized with pointer or keyboard.
  • Accessibility: axe checks every instrument panel in both themes. Contrast fixes: the scope's badges, the light accent, and the measurement labels.
  • User guide: docs/user/instruments.md.

Timing fidelity

  1. Audit and accuracy model. Four levels (functional, estimated, cycle-exact for a stated scope, electrical), and the level each target is held to.

  2. ARMv7-M exceptions (F446, F103, nRF52840):

    • 12 cycles to enter, 12 to return and 6 to tail-chain, with late arrival (ARM DDI 0439B §3.9.1).
    • The F103's entry and tail-chain are confirmed by the Cortex-M3 TRM (DDI 0337E §5.5–5.6). That manual gives no return figure, so the return's 12 comes from the M4 manual and is labelled as such.
  3. Cortex-M0+ (RP2040) from its own TRM (DDI 0484C Table 3-1), instead of the M4's table:

    • loads and stores 2 cycles, 1 on the SIO's single-cycle I/O port (RP2040 datasheet §2.3.1);
    • a taken branch 2, BL 3, POP {.., PC} 3+N;
    • MULS 1, for the RP2040's fast multiplier;
    • a 15-cycle entry, with late arrival.

    The return and tail-chain have no published figure, so they are not charged, and the fidelity says so.

  4. STM32F4 flash and ART accelerator (RM0390 §3.4):

    • FLASH_ACR's wait states per 128-bit line;
    • the prefetch, the 64-line instruction cache and the 8-line data cache, both LRU;
    • ICRST and DCRST, which empty the caches.

    test_flash_read_timing covers RM0390 Figure 4's prefetch case, 5 wait states, a cached loop and cached literals.

  5. Measurements. run --measure FROM..TO gives cycles and simulated picoseconds between boundaries: a symbol or address starting, exc:N, pend:N or ret:N. Host wall time is reported apart, and the measurement adds no cycles to the firmware. On FreeRTOS V11.1.0 on the NUCLEO-F446RE at 16 MHz, pend:PendSV..ret:PendSV measures 155–167 cycles.

  6. Traces. run --trace FILE records each instruction, and each exception pending, taken and returned, with cycle and simulated time. It writes a table, or a .vcd waveform for GTKWave and similar viewers.

  7. Speed.

    • Analog islands now settle at the solver's absolute tolerances (1 µV, 1 pA), which takes FreeRTOS on the NUCLEO from 0.07× to 0.28× real time.
    • tools/perf/bench_mcu.py reports simulated and host time apart.
    • The remaining slow cases are explained in §4.4: probes sampling at 1 MHz, and two cores' worth of code on the RP2040.

Imperfections on by default (ADR-034)

  • Push buttons and toggle switches bounce; R, C and L are drawn inside their tolerances from settings.seed; settings.imperfections: "ideal" gives ideal parts. examples/button-bounce shows the chatter and its RC and Schmitt cure.
  • A session reset is a fresh start (test_reset_is_load). Paced slices end on a 1 ms grid, so a sliced run equals an uncut one (test_grid_slices).

Visual Benchmark v0.1: the workbench redesigned

  • Two-row header: navigation (project, perspectives, diagnostics, Settings, commands), then the simulation bar (transport, readout, speed, fidelity, undo).

  • Readout: the state is named in words with an icon (running, paused, stopped, loading, failed, disconnected), never by colour alone. Simulated time is shown apart from the measured Host rate, which reads "—" when nothing is measured.

  • Status bar: leads with the simulation, then errors and warnings, with the engine connection last. A connection screen with Reconnect replaces the perspective when the engine is gone.

  • Settings (Ctrl+,):

    • language;
    • theme, text size, density;
    • panel layout restore and reset;
    • time unit, time precision, host-rate format.

    Each is applied at once and persisted.

  • English and French from typed catalogs, with English fallback. The shared packages take their words through uiText. Technical identifiers, catalog data, engine diagnostics and front-panel legends stay as written.

  • Design system: tokens for state and value colours, a text-size scale and compact density. Panels, ribbons, library rows, controls, meters and registers are brought onto them, with no overflow at 768 px.

  • Fixed: a pointer going down near the edge of a pin's target started no wire. The pin was looked up by distance; the pin under the pointer is now taken.

  • Gallery: the same twelve workflows before, after, and in French in docs/gallery/visual-benchmark-v0.1.

Evidence

  • Tests. ctest 56/56 on dev, plus the changed areas on ci-clang, ci-tcc and asan. They include test_cm_timing (ARMv7-M and M0+) and test_flash_read_timing. All 640 catalog fixtures pass with their ngspice references.
  • FreeRTOS vendor suite. It checks that:
    • the tick is exactly 16 000 cycles;
    • each switch equals its request latency plus PendSV's entry to return;
    • two runs measure the same;
    • the trace's first switch equals the measurement's.
  • UI.
    • 370 unit tests, including i18n.test.ts: key parity, placeholders, plural pairs.
    • The Playwright suite, with e2e/settings-language.spec.ts: settings applied and kept over a reload; the workbench in French without overflow; back to English.
    • workspace-layout.spec.ts at 1440, 900 and 768 px.
    • Lint and typecheck.
  • Docs. check_docs, check_banned and tools/validate.py are clean.

Not yet

  • A real-hardware capture of the context switch to compare against. It needs a physical NUCLEO-F446RE.
  • The STM32F103's and the nRF52840's flash timing.
  • Bus contention between the RP2040's two cores and DMA.
  • Peripheral and memory accesses in the trace.
  • The scope's measurements still use the screen's 1200 points.
  • Catalog descriptions, fidelity notes and engine diagnostics in French. They are data, not interface text.
  • Compact density on the canvas and plots.
  • Panels moving between perspectives.
  • Languages beyond English and French.

emblink/nordic_ppk2 from Nordic's PPK2 user guide (section 8): as a source
meter it powers the board (0.8 to 5.0 V, at most 600 mA, then holds 600 mA
as its output falls), as an ampere meter it sits in series with the
board's own supply. The current into the board is sampled at 100 kS/s,
each sample the 10 us average at the resolution of the range it falls in
(0.2, 0.5, 5, 50, 1000 uA for the five ranges, switched by themselves);
negative currents read 0. Its logic port samples D0-D7 at 100 kHz on the
same grid. It is a composite of its measurement core (emblink/ppk2_core,
the new power_profiler primitive, samples completed lazily so a quiet
island costs nothing) and its logic port (emblink/ppk2_logic_port, the
logic analyzer's sampler at a fixed rate). instrument.read gives its
samples (kind: power).

The Run perspective's panel shows it as Nordic's Power Profiler app does:
Start/Stop, mode and supply voltage, the current followed live, the
window's or A-B selection's average, peak and charge, the logic port
underneath, CSV export.

Evidence: six fixtures on the core and one on the kit, and
tests/protocol/test_power_profiler.py (a load sleeping at 1 mA and waking
for 1 ms every 10 ms at 101 mA: 5000 samples in 50 ms at their ranges'
resolution, a 10 % duty, an 11.00 mA average, D0's edges where the current
steps); powerCapture.test.ts; the gallery.
… trigger, Auto/Normal/Single, live screen

The scope loads the circuit through its probe (1x or 10x), samples at the
DS1054Z's rate for the channels on (1 GSa/s with one, 500 MSa/s with two,
250 MSa/s with three or four) over its 12/6/3 Mpts memory, applies DC, AC
(5 Hz) or GND coupling and the 50 MHz (20 MHz limited) bandwidth, and
quantises to 8 bits at 25 codes a division. The edge trigger takes source,
level, slope and holdoff; Auto forces a sweep when no trigger comes, Normal
waits, Single stops after one acquisition and re-arms on Run.

instrument.read returns the acquisition (kind "scope"); the workbench shows
it on a 12x8 graticule with the front panel's controls and the automatic
measurements.
…Run or floating

The inspector's Open front panel (and the palette's Open, Float and Dock
commands) shows a DS1054Z's, PPK2's or Logic 8's panel: unfolded and focused
in the Run perspective's dock, or brought forward where it floats. Float
moves a panel into a window over every perspective, moved and resized by
pointer or keyboard and remembered across sessions; Dock puts it back.
revealDockItem in the design system unfolds a dock item wherever it lives.
@teo1747 teo1747 changed the title Bench instruments, part 2: the Nordic PPK2 power profiler and the Rigol DS1054Z oscilloscope Bench instruments, part 2: the Nordic PPK2, the Rigol DS1054Z, and their front panels docked or floating Oct 31, 2026
…oes its state

setup_devices zeroes every device's state before its setup runs, which lost
the pointer to the DS1054Z's history ring (and could the PPK2's sample ring)
allocated outside the island's allocator: LeakSanitizer found 6 MB leaked
per scope fixture in CI's asan job. The rings are freed first; a reset
clears the capture and the next sample allocates them again.
…ext menu and the bench's parts list

Right-clicking a DS1054Z, PPK2 or Logic 8 on the schematic offers Open front
panel; selecting one in the bench's On the bench list does too. Both go to
the panel docked in Run or bring its floating window forward.
…atus badges' contrast

The front-panels spec runs axe-core in the browser on the DS1054Z's panel
docked in Run and floating over Design, with layout, so contrast is checked.
It found the STOP badge (white on #d9534f, 4.0:1) and the AUTO badge (white
on #4a90d9, 3.5:1) below WCAG's 4.5:1; they are #b52b27 (6.3:1) and #2a6db5
(5.3:1) now.
docs/user/instruments.md: wiring the Logic 8, the PPK2 and the DS1054Z
(each lead's load), opening and floating their front panels, what each
panel measures and how (the rate limits, the 10 us windows and ranges, the
scope's rate by channels, the measurements on the screen's points), and
instrument.read for scripts. Linked from docs/README.md.
…ight accent and measurement labels' contrast

The front-panels spec checks the DS1054Z, Logic 8 and PPK2 panels, docked and
floating, in light and dark, with axe-core in the browser (the demo session
gains stand-in entries for the Logic 8 and the PPK2). Fixed what it found:
the light theme's accent (white on #0e8a66, 4.33:1) is #0c7f5e (4.98:1), and
the measurement strip's labels use --text-muted instead of --text-faint.
docs/engineering/timing-fidelity.md: what is sound (picosecond time, exact
rational clocks, deterministic ordering, on-chip peripherals following the
core cycle by cycle, the AVR exact) and the gaps that matter to measuring an
RTOS context switch: Cortex-M exception entry, return and tail-chaining
charge no cycles (ARM DDI 0439B 3.9.1 gives 12, 12 and 6), no late arrival,
FP-frame or abandoned-divide timing, a fixed pipeline refill, one M4 table
for the M0+ and M3, no flash wait states or ART, DWT with CYCCNT only, no
measurement instrument, no performance benchmark. The levels (functional,
estimated, cycle-exact for a stated scope, electrical) and what each target
aims for; the eight steps.
…e the TRM's cycles

On ARMv7-M an exception's entry charges 12 cycles, its return 12 and a
tail-chain 6 in place of both (ARM DDI 0439B 3.9.1, zero wait states); they
cost nothing before. The entry's cycles pass before the exception is chosen,
so an interrupt that becomes pending while a frame is stacked and outranks
the one being entered takes the frame, the other then tail-chaining (late
arrival). DWT.CYCCNT counts them; exc_cycles totals them.

tests/unit/test_cm_timing.c times an entry, a tail-chain and a return, and
SysTick firing at each cycle of an IRQ's stacking. Not charged, for want of a
documented figure: the FP extended frame and lazy stacking, wait states on
stacking and the vector fetch, an interrupt during unstacking, and ARMv6-M.
The MCUs' timed fidelity and timing-fidelity.md 4.1 say so.
… FreeRTOS's context switch

core/mcu/latency.c, an analysis beside the profile and the stacks: each
--measure [NAME=]FROM..TO gives, each time it happens, the cycles and the
simulated picoseconds between two boundaries: an instruction at a symbol or
address starting, an exception taken (exc:N), an exception becoming pending
(pend:N, a hook in the Cortex-M's NVIC) or an exception's return done
(ret:N). Time is the core's cycles at the clock that ran them, exactly; the
host's wall time is reported apart. The analysis observes and adds no cycle
to the firmware. An ARMv7-M return's cycles are now a step of their own, so
an instruction's start is stamped after them.

tests/vendor/freertos/run.py measures the unmodified FreeRTOS V11.1.0 on the
NUCLEO-F446RE at 16 MHz: the tick averages exactly 16000 cycles; each context
switch (pend:PendSV..ret:PendSV, 155-167 cycles) equals its request latency
plus PendSV's entry to return, sample by sample; two runs measure the same.
…enchmark of fixed workloads

An island was settled only when a step moved no unknown by more than 1e-14
relative, so an RC tail (the NUCLEO-F446RE's reset capacitor charging through
its pull-up) kept the board's supply island awake for about 130 ms of each
run, every step cut at the running core's 1 us quantum: 95% of the host time
of a FreeRTOS run. Settled is now no node voltage moving by more than vntol
(1 uV) and no branch current by more than abstol (1 pA), what Newton's
convergence test resolves; reltol is left out so a slow ramp toward a
threshold still steps. FreeRTOS on the NUCLEO runs at 0.23x real time instead
of 0.07x, its measured context switch unchanged; all tests, the catalog's
fixtures and the ngspice references pass unchanged.

tools/perf/bench_mcu.py: example projects and FreeRTOS run headless for a
fixed simulated time, simulated and host time reported apart; the results
and what is still slow are in timing-fidelity.md 4.4.
@teo1747 teo1747 changed the title Bench instruments, part 2: the Nordic PPK2, the Rigol DS1054Z, and their front panels docked or floating Bench instruments part 2 (PPK2, DS1054Z, front panels) and timing fidelity: exception timing, measurements, speed Nov 1, 2026
…he SIO's single-cycle I/O port

The RP2040's cores were charged the Cortex-M4's table. esk_cm0p_cost takes
the Cortex-M0+ TRM's Table 3-1 (ARM DDI 0484C): loads and stores 2 cycles on
AHB-Lite and 1 on the single-cycle I/O port (the RP2040's 0xd0000000 segment,
its datasheet 2.3.1), a taken branch 2, BL 3, POP with PC 3+N, MRS/MSR and
barriers 3, MULS 1 for the RP2040's fast multiplier (datasheet 2.4). An
exception's entry takes the TRM's worst-case 15 cycles (3.4, datasheet 2.4),
with late arrival; the return and tail-chain have no figure in either and
are not charged. tests/unit/test_cm_timing.c times each class, an IOPORT load
and store, and the entry.
flash_f4.c times the flash's read interface from FLASH_ACR: 128-bit lines
read in LATENCY + 1 cycles (Table 5); a fetch from the line executing waits
nothing; PRFTEN's prefetch of the next line, ready LATENCY + 1 cycles after
the current one arrived; ICEN's 64-line LRU instruction cache, filled on a
miss; a miss's LATENCY cycles (RM0390's least); a literal read through
D-Code waits LATENCY unless DCEN's 8-line data cache holds its line; ICRST
and DCRST empty the caches. The machine's ftime hook, keyed by the flash's
memory so the boot alias counts, gives the core each fetch's and data
read's stall.

test_flash_read_timing: RM0390 Figure 4 (3 WS, sequential 32-bit
instructions: 7 cycles a line without prefetch, 4 with it), 5 WS with
prefetch (6 a line), a two-line loop with and without ICEN, a literal with
and without DCEN, 0 WS. FreeRTOS at 16 MHz is unchanged.
…dump

latency.c records each instruction start (with its function), and each
exception pending, taken and returned, with its cycle and simulated time,
stamped at the measurements' boundaries. --trace FILE writes a table, or
with .vcd a value change dump at 1 ps (pc, the exception active, the last
pended, the cycle count) for GTKWave and other viewers; --trace-start
BOUNDARY and --trace-limit N bound it. tests/vendor/freertos/run.py checks
that the traced first context switch, pend to ret, equals the measurement's
first sample.
ARM DDI 0337E (r1p1) 5.5 and 5.6 give a 12-cycle interrupt latency and a
6-cycle tail-chain, the figures the M3 was charged from the M4 TRM. It gives
no return figure, so the return's 12 stays the M4 TRM's; the F103's timed
fidelity and timing-fidelity.md say so.
The RC filter's time is its two 1 MHz probes' exact solves (10.6x real time
with 10 kHz probes); interpolated probes would no longer report the solver's
solution, so the cost stays. The RP2040's is its two cores' code (25 analog
steps a second); FreeRTOS's first 50 ms is the reset RC really moving.
@teo1747 teo1747 changed the title Bench instruments part 2 (PPK2, DS1054Z, front panels) and timing fidelity: exception timing, measurements, speed Bench instruments part 2 (PPK2, DS1054Z, front panels) and timing fidelity: exception timing, M0+, flash/ART, measurements, traces, speed Nov 1, 2026
…d time host-independent

Contact bounce is on by default on push buttons and toggle switches. R, C
and L carry IEC 60062 tolerances, drawn per instance from settings.seed and
the instance's id. settings.imperfections "ideal" labels ideal parts. The
run report's snapshot.drawn lists the drawn values. examples/button-bounce
shows the chatter and an RC with a Schmitt inverter curing it.

The Blue Pill gains its 100 nF NRST capacitor. The Uno's DTR auto-reset is
a bounce-free part of its own. EmbSIM's own firmware debounces its buttons.
Tests and examples that check exact values declare their parts exact, each
with its reason.

A session reset stops the analog islands before the models reset. A stale
crossing of the previous run no longer leaks into the next one, so a replay
matches a recording begun mid-run (test_reset_is_load).

Islands take a step boundary on a 1 ms simulated grid, and paced slices end
on it, so a sliced run equals an uncut one bit for bit (test_grid_slices).

The vendor expectations follow the cycle-accurate cores: EEPROM_Emulation
ends near 15.6 s; the Pico's polls and PWM wraps follow the M0+'s costs.
With the boards' EN parts drawn inside their tolerances, they boot a few
microseconds apart. When the peer's broadcast comes second in a round, the
example's handshake takes one round more, and its first unicast arrives at
7.48 s instead of 6.48 s, as on real boards. 10 s covers both orders; checked
with five seeds and with ideal parts.
A two-row header (navigation, then the simulation bar) with the state
named in words and an icon, simulated time apart from the host rate, a
status bar led by the simulation, a connection screen in place of an
empty perspective, and a Settings page (language, theme, text size,
density, layout restore and reset, time unit, decimals, rate format)
applied at once and kept between sessions. Shell strings come from typed
English and French catalogs with English fallback. Design tokens gain
state and value colours and a scale for text size and density. The
before screenshots and the capture script are in the gallery.
…the pin under the pointer

Every perspective's panel titles, tabs, toolbars, menus, context actions,
dialogs, empty states and toasts come from the catalogs, with French for
each. The shared packages (canvas, instruments, dock, command palette,
dialogs) take their words through uiText in @embsim/design, which the
application feeds with its catalog's pkg.* messages. Library categories
are translated; part names, catalog descriptions, net, pin and register
names stay as written. A language change rebuilds the perspectives; the
layout, project and session are kept.

A pointer going down on a pin now starts the wire from that pin, rather
than from the pin nearest within 12 units: a click on the edge of a
terminal's target used to fall through to the sheet (the visual-language
spec on macOS and Windows, whose fonts move the pin label's box).

The Debug registers read as one row (name, hex, decimal); Verify's panes no
longer repeat their panel's title. New e2e spec: settings apply at once and
persist, and the workbench in French.
…ccount, settings and language docs

The same twelve workflows after the redesign, in English and in French,
beside the before set in the gallery. docs/engineering/visual-benchmark.md
gives the review, what changed, the design system, the default workspaces,
the settings, the languages, how it was verified and the limits; the
workspace guide explains the readout, Settings and the language. Debug's
register table alone is sized to its content, so the source and
disassembly keep their full-width rows. The capture script takes
--settings for text size and density.
@teo1747 teo1747 changed the title Bench instruments part 2 (PPK2, DS1054Z, front panels) and timing fidelity: exception timing, M0+, flash/ART, measurements, traces, speed Bench instruments part 2, timing fidelity, imperfections, and the Visual Benchmark v0.1 workbench (English and French) Nov 1, 2026
The jumper tool names a contact within 11 screen pixels; the measure tool
snapped only within 1.5 mm of the surface it hit. On a smaller bench canvas
(the new header) a click the jumper tool would call R2.1 measured from a
breadboard hole instead (bench-tools.spec on CI). Both now pick the contact
the same way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant