Skip to main content

Virtual Bench

A netlist-driven bench test for the board, run entirely in software: a virtual PSU and a virtual LiPo feed a model of this board, built from the KiCad netlist and from component models written against the datasheets in hardware/datasheets/. The deliverable is what a physical bench delivers — rails come up, strapping pins read a valid boot mode, the display and audio chains check out — plus a log of every voltage, current and junction temperature that produced it.

Source of truth: the full phased plan lives in docs/archived/virtual-bench-plan.md in the repo; the implementation is the scripts/vbench/ package, gated by scripts/test_vbench.py inside make verify-all.

What it proves — and what it does not​

Guarantees are per failure class, never global:

Failure classCovered
Wiring/netlist: swapped pin, inverted D/C, missing pull-up, button on the wrong GPIO✅
Strapping-pin state at reset (BTN_SELECT=GPIO0, BTN_R=GPIO3, BTN_L=GPIO45, LCD_WR=GPIO46)✅
DC operating point: every net voltage, dividers, LED currents, 3V3-vs-5V compatibility✅
Electrical conflicts: two drivers on one net, an output tied into a rail✅
Thermal: junction temperature of IP5306 / SY8089 / PAM8403 at a declared ambient✅
Firmware↔hardware contract (what firmware assumes vs what the netlist says)✅
Geometric shorts, clearance, acid traps❌ — belongs to verify_isolation / DRC / short-circuit analysis; deliberately not duplicated
Signal integrity (20 MHz i80, PSRAM 80 MHz, USB eye), EMI, ESD, crystal startup❌ — prototype only
Assembly (solder, tolerances, CPL rotation)❌ — the CPL has its own gates
A component model that misreads its datasheet❌ — the bench would confirm the bug with confidence; this is why the honesty rules below exist

The honesty rules​

The bench's core risk is Round 25's lesson (a justification comment outranks the datasheet) relocated into new files. Three rules defend against it:

  1. Every number must be cited. A component model validates only if each parameter carries a datasheet citation that resolves to a PDF actually in hardware/datasheets/ — an uncited, weaselly or unlocatable number does not load. A part with no datasheet in the repo cannot be modelled at all.
  2. Values are derived, not quoted. The +3V3 rail must come out at 3.327 V by walking the feedback divider in the netlist — not because 3.3 was typed anywhere. A floating node stays floating rather than defaulting to 0 V. The PAM8403's 8 Ω output power is the Diodes EC table's own cited point (1.8 W at 10 % THD) since 2026-07-31 — the earlier halved-from-4-Ω derivation is retired.
  3. A corpus of historical bugs must be rediscovered. Real bugs from this project's history are recorded with citations; the bench must find each one when it is injected, and scripts/test_vbench.py mutation-tests the whole machine — a corpus entry that hand-writes its own verdict, cites a line that moved, or reuses an id fails to load.

Phases​

PhaseScopeStatus
−1prerequisite: close verify_netlist_diff (two netlist sources agree)done
0foundation: netlist extraction, model schema, bug corpusdone
1analog: rails, operating point, conflicts, thermaldone (with self-named holes)
2digital fabric: every ESP32 pin, every button, the power switch, boot-mode derivationdone
3peripherals: LCD (panel view and ILI9488 controller state machine with i80 timing), audio (cited gain/power, computed rail sag), SD (bus and card protocol: CMD0/CMD8/ACMD41, CMD17 block reads)done 2026-07-31 — residue declared per module
4firmware in the loop and the demo appT4.1 / T4.3 / T4.4 done; T4.2 unlocked (ESP-IDF builds, locally and via make firmware-build)
5make it a gate that cannot lieT5.1–T5.3 done: corpus 22/22 rediscovered, the plan's five mutations implemented, test_vbench + test_vbench_display + test_vbench_sdcard all in verify-all. T5.4/T5.5 (prototype calibration) await instruments — every report carries CALIBRATION: no until then
6optional: QEMU devicenot started

Running it​

make bench             # interactive: SDL window, live instruments
make bench-ci # every scenario, headless, JUnit output
make bench-all # the whole bench, every phase, exit 0 or die
make bench-display # panel wiring + controller frame + i80 timing
make bench-sdcard # SD protocol: init + block reads of a real file
python3 scripts/test_vbench.py # the mutation suite (also in verify-all)

The package modules map to bench instruments: rails.py (DC operating point), conflicts.py (driver conflicts), thermal.py (junction temperatures), transients.py (SPICE decks with the BOM's real values), pins.py / buttons.py (strapping and boot mode), display.py + ili9488_ctrl.py (panel wiring, controller command sequence, write-side AC timing), audio.py, sdcard.py + sdcard_protocol.py (bus wiring and the card's SPI protocol), with corpus.py holding the historical-bug corpus and scenarios/ the end-to-end bench scripts.

What the bench has actually caught, its measured limits, and the third-release checklist live in Findings & limits.