Deep electrical/functional audit of the ESP32 Emu Turbo hardware design. Finds bugs that prevent power-on, component operation, or emulator functionality. Cross-checks schematics, PCB, datasheets, and firmware via automated gates + manual domain-by-domain review.
Iterative deep-dive to find electrical, connectivity, and functional bugs that would prevent the device from working.
This audit has two layers and BOTH must run:
.kicad_pcb cache. All gates must PASS before Layer 2.Historical context: prior rounds of this audit (R1-R4) relied only on Layer 2 and never caught the v3.3 trace-through-pad regression from commit 775e9fd — because the bugs lived in cache geometry, not prose. Layer 1 was added in 2026-04-10 to close that gap.
Run the full gate suite. If ANY of these fail, STOP and fix before attempting the manual domain review — a board with a geometric short, a broken power chain, or a drifted schematic is not worth auditing in prose.
cd /Users/pierrejonnycau/Documents/WORKS/esp32-emu-turbo
# ── Fab-short gate (MOST IMPORTANT) ──────────────────────────────
# Catches netted traces physically crossing unnetted pads (the v3.3
# regression class). Checks F.Cu and B.Cu.
python3 scripts/verify_trace_through_pad.py # MUST be "1 passed, 0 failed"
# ── Trace-crossings gate (R9-CRIT-1 class) ──────────────────────
# Catches two traces on the SAME copper layer belonging to DIFFERENT
# nets whose capsules overlap — the physical-short class that
# verify_trace_through_pad.py does not see because it only checks
# trace-vs-pad. Missing this gate caused the R7/R8 BTN_START bridge
# to cross LCD_CS/DC/WR without anyone noticing.
python3 scripts/verify_trace_crossings.py # MUST be "1 passed, 0 failed"
# ── Copper-clearance gate (R13 class) ───────────────────────────
# Shapely polygon-based check: for each copper layer, merges all
# features per net and measures min polygon distance between every
# different-net pair. Reports any gap < 0.10mm as DANGER and
# 0.10-0.15mm as WARN (JLCPCB preferred minimum, what JLCDFM uses
# as Warning threshold). Catches the 0.110-0.145mm track-to-pad
# gaps that KiCad DRC misses because .kicad_dru is tuned to the
# 0.09mm absolute minimum. JLCDFM measures mask-aperture-to-mask-
# aperture, subtracting ~0.05mm mask expansion per side, so a
# 0.110mm copper-edge gap becomes ~0.010mm on JLCDFM's view and
# gets flagged as Danger.
python3 scripts/verify_copper_clearance.py # MUST be "0 DANGER"
# ── Per-net copper connectivity (R5-CRIT class) ─────────────────
# Walks the per-net copper graph and asserts every net forms a
# single connected component. Catches R5-CRIT-1..9 bugs where
# pad-net labels are correct but copper is fragmented (BAT+ L1.1
# isolated, VBUS decoupling floating, button pull-ups disconnected,
# SW_BOOT non-functional, etc). Missing this gate caused R5 bugs
# to ship undetected in v3.3.
python3 scripts/verify_net_connectivity.py # MUST be "0 failed"
# ── DFM / DFA / JLCPCB manufacturing ─────────────────────────────
python3 scripts/verify_dfm_v2.py # 119 tests (incl zone fill + silk-to-pad)
python3 scripts/verify_dfa.py # 9 tests
python3 scripts/validate_jlcpcb.py # 26 tests
python3 scripts/verify_bom_cpl_pcb.py # 13 tests (incl field completeness)
python3 scripts/verify_polarity.py # 47 tests
# ── JLCPCB official capabilities + stencil + drill ──────────────
python3 scripts/verify_jlcpcb_capabilities.py # 12 tests (JLCPCB published limits)
python3 scripts/verify_stencil_aperture.py # 6 tests (IPC-7525 stencil analysis)
python3 scripts/verify_drill_standards.py # 6 tests (ISO metric + drill-to-pad ratio)
# ── Datasheet pinout + physical verification ─────────────────────
python3 scripts/verify_datasheet_nets.py # 259 pin→net checks
python3 scripts/verify_datasheet.py # 29 physical tests
# ── Cross-source consistency (schematic ↔ PCB ↔ firmware) ────────
python3 scripts/verify_design_intent.py # 362 checks, T1-T22
python3 scripts/verify_schematic_pcb_sync.py # R4 sync guard
python3 scripts/verify_netlist_diff.py # schematic-PCB netlist diff
python3 scripts/generate_board_config.py --check # config.py vs board_config.h
# ── Electrical review (power + boot) ─────────────────────────────
python3 scripts/verify_strapping_pins.py # 12 tests
python3 scripts/verify_decoupling_adequacy.py # 25 tests
python3 scripts/verify_power_sequence.py # 26 tests
python3 scripts/verify_power_paths.py # 19 tests
# ── KiCad native ERC + DRC ───────────────────────────────────────
python3 scripts/erc_check.py --run # schematic ERC
kicad-cli pcb drc \
--output /tmp/drc_audit_report.json \
--format json \
--severity-all --units mm --all-track-errors \
hardware/kicad/esp32-emu-turbo.kicad_pcb
# DRC: 0 shorting_items (real), 0 via_dangling, 0 unconnected_items
Gate summary to report back to the user:
| Gate | Expected | Actual | Status |
|---|---|---|---|
Fab shorts (verify_trace_through_pad) | 0 overlaps | ? | PASS/FAIL |
Trace crossings (verify_trace_crossings) | 0 crossings | ? | PASS/FAIL |
Copper clearance (verify_copper_clearance) | 0 DANGER | ? | PASS/FAIL |
DFM (verify_dfm_v2) | 115/115 | ? | PASS/FAIL |
DFA (verify_dfa) | 9/9 | ? | PASS/FAIL |
Polarity (verify_polarity) | 47/47 | ? | PASS/FAIL |
Datasheet nets (verify_datasheet_nets) | 259/259 | ? | PASS/FAIL |
Datasheet physical (verify_datasheet) | 29/29 | ? | PASS/FAIL |
Design intent (verify_design_intent) | 362/362 | ? | PASS/FAIL |
R4 sync guard (verify_schematic_pcb_sync) | PASS | ? | PASS/FAIL |
Netlist diff (verify_netlist_diff) | 4/4 | ? | PASS/FAIL |
Strapping pins (verify_strapping_pins) | 12/12 | ? | PASS/FAIL |
Decoupling adequacy (verify_decoupling_adequacy) | 25/25 | ? | PASS/FAIL |
Power sequence (verify_power_sequence) | 26/26 | ? | PASS/FAIL |
Power paths (verify_power_paths) | 19/19 | ? | PASS/FAIL |
ERC (erc_check) | 0 critical | ? | PASS/FAIL |
| KiCad DRC | 0 shorts, 0 dangling | ? | PASS/FAIL |
RULE: If any gate fails, stop and write the failure into
hardware-audit-bugs.md as the first bug of the new round. Do not
proceed to Layer 2 prose review until Layer 1 is clean OR the user
explicitly asks for a prose-only review acknowledging the gate failure.
Trace: USB-C → IP5306 → +5V → AMS1117 → +3.3V → ESP32
Read and cross-check:
scripts/generate_schematics/sheets/power.py — schematicscripts/generate_pcb/routing.py — PCB routing (_power_traces)hardware/datasheet_specs.py — IP5306, AMS1117 pinoutshardware/datasheets/U2_IP5306_*.pdf + U3_AMS1117_*.pdfsoftware/main/board_config.h — power management notesCheck:
Check strapping pins at boot time:
Must verify R14 (BTN_L pull-up) is skipped in routing, because
external pull-up on GPIO45 forces VDD_SPI = 1.8 V and kills the Octal
PSRAM. Firmware enables internal pull-up post-boot. This is checked
automatically by verify_strapping_pins.py but the prose audit should
re-read the commit 9709bea and confirm the logic still makes sense.
Also verify:
sdkconfig PSRAM mode is Octal (not Quad)CONFIG_SPIRAM_MODE_OCT=yTarget: ILI9488 3.95" 320x480 8-bit 8080 parallel via 40P FPC.
CRITICAL reading: hardware/datasheet_specs.py::COMPONENT_SPECS['J4']
now documents the connector-pad ↔ panel-pin reversal
(connector_pad = 41 - panel_pin). R4-CRIT-1 was a false positive
against this reversal; do not re-raise it.
Cross-check:
scripts/generate_schematics/sheets/display.py (docstring uses panel-side)hardware/datasheet_specs.py::J4 (PCB uses connector-side)scripts/generate_pcb/routing.py::_lcd_traces (B.Cu routing)hardware/datasheets/U1_ESP32-S3-WROOM-1_*.pdf (GPIO → LCD pins)Check:
Target: ESP32 I2S PDM → PAM8403 → 28 mm speaker.
PAM8403 is analog input; firmware must use PDM TX mode (not standard I2S) so the ESP32 outputs a 1-bit sigma-delta stream that the cap C21 (PAM_VREF) + PAM8403 internal filter reconstruct into audio.
Check:
software/main/audio.c uses i2s_pdm_tx_config_t, not standard I2Sverify_ground_loops.py warns but does not fail — advisory)Target: TF-01A micro SD slot, SPI 1-bit mode @ 25 MHz.
Check:
verify_trace_through_pad.py will catch
any trace physically crossing them.12 buttons + 1 menu combo diode D1 (BAT54C) + power switch SW_PWR.
Check:
verify_design_intent T1-T3)Target: USB-C native (ESP32-S3 built-in FS USB) + CC pull-downs + ESD.
Check:
verify_usb_impedance.py)verify_usb_return_path.py)Target: SNES @ 60 fps on ESP32-S3 240 MHz + Octal PSRAM.
Check:
website/docs/software/snes-optimization.md for current profileWrite findings to hardware-audit-bugs.md under a new section
## Round N Findings (YYYY-MM-DD). Include:
### Step 0 gates
| Gate | Result |
|------|--------|
| verify_trace_through_pad | ... |
| verify_dfm_v2 | ... |
...
### Domain findings
- **Power chain**: N findings
- **ESP32 boot**: N findings
- **Display**: N findings
- **Audio**: N findings
- **SD card**: N findings
- **Buttons**: N findings
- **USB**: N findings
- **Emulator performance**: N findings
### Bug list
#### R{N}-CRIT-{i} — {title}
- **Files**: ...
- **Problem**: ...
- **Root cause**: ...
- **Fix**: ...
#### R{N}-HIGH-{i} — ...
#### R{N}-MED-{i} — ...
#### R{N}-LOW-{i} — ...
Severity guide:
scripts/verify_trace_through_pad.py — fab-short hard gatescripts/verify_dfm_v2.py — DFM (115 tests)scripts/verify_datasheet_nets.py — pin→net (259 checks)scripts/verify_design_intent.py — cross-source (362 checks)scripts/verify_schematic_pcb_sync.py — R4 sync guardscripts/verify_strapping_pins.py — ESP32 boot gatescripts/verify_decoupling_adequacy.py — per-IC cap checkscripts/verify_power_sequence.py — power chain topologyscripts/verify_power_paths.py — copper path tracingscripts/erc_check.py — KiCad native ERCscripts/generate_schematics/sheets/ — schematic generator (all sheets)scripts/generate_pcb/routing.py — PCB trace routinghardware/datasheet_specs.py — component pin→net single source of truthsoftware/main/board_config.h — firmware GPIO confighardware/datasheets/ — component datasheetshardware-audit-bugs.md — output: historical audit findings