Moving p2core and p2iss into embsim¶
Scope for promoting the P2 instruction-set simulator out of MaD and into
embsim, so that embsim becomes the
library of MCUs we can simulate and its CI is what proves the ISS matches
silicon. This is the promotion step the P4 entry in
embsim-improvements.md planned for
("Spike may live in MaD SIL/p2core/ then promote").
Scoped 2026-09-18 against feat/iss-rom-serial-flash (uncommitted WIP included)
and embsim origin/main at 21a25d5. Revised 2026-09-19: embsim receives P2
ISS items only, and flashing gets its own embsim test. Revised again the same
day: board ownership, the P2-EC32MB module goes to embsim, MaD's boards come
home.
Bottom line: feasible and mostly mechanical. p2core was written dep-free
for exactly this, and embsim already carries embsim-cpu-oracle as the generic
half of the silicon-golden method. The silicon, ROM and pure tests replay from
committed assets, so they gate embsim CI with no board and no MaD firmware.
The acceptance tests that need MaD's MaD/program (the firmware image;
MaDEdge/program when this was scoped) stay in MaD.
Three things need untangling first: the MaD-specific bits inside the crates,
the flash test that borrows its stub from the PWA, and the Vibes path
dependency.
The rule: embsim receives P2 ISS items only. No MaD tests, no MaD
software, no MaD firmware image, nothing that reads from Software/ or
Firmware/. A test written in MaD that exercises only the chip (ROM, flash,
silicon goldens) is a P2 item and moves. A test that needs MaD's image or the
PWA stays in MaD, and where embsim needs the same coverage it gets its own test
built from P2 parts; see the bootloader test below.
Inventory¶
| Piece | Size | Notes |
|---|---|---|
p2core/src |
~10.5k lines incl. the generated decoder | zero dependencies by design |
p2core/tests |
15 files, ~3.2k lines | 11 portable, 3 need MaD firmware or its flexcc listing, 1 reads the PWA's vendored loadp2 stub and stays |
p2core/examples |
30 files | 8 default to the MaD firmware path |
p2core/hwtest |
5 FlexC/Spin programs, 1.5 MB goldens, 2 baselines, 2 capture tools | replay needs nothing; capture needs flexcc + loadp2 + a P2-EVAL |
p2core/vendor/parseUtils.ts |
238 KB | PNut-TS encoding table, MIT per README, no in-file header |
p2iss/src |
~1.6k lines | depends on embsim-board + embsim-core |
p2iss/tests |
7 files, ~1.6k lines | 2 portable, 5 need MaD firmware |
p2iss/rom |
ROM booter + stage1, .bin + .spin2 |
Parallax's published ROM source, trimmed for flexspin |
| Tests across both crates | 94 (--all-targets) |
unit tests in flash, sdimage, pulse, smartbus, flashimage move with their modules |
What moves, what stays¶
| Piece | Destination | Notes |
|---|---|---|
SIL/p2core src, generated decoder, vendored table |
embsim mcus/p2/iss/ as embsim-p2-iss |
the name P4 reserved |
SIL/p2core/hwtest/ programs, goldens, baselines, hw_probe.py, hw_compare.py, gen_decoder.py |
with the ISS crate | the P2 adapter embsim-cpu-oracle's README already points at |
SIL/p2iss/rom/ binaries + sources, make bootrom |
into the ISS crate as a script | p2core tests already reach into ../p2iss/rom from six places |
SIL/p2iss |
embsim mcus/p2/iss-net/ as embsim-p2-iss-net |
name is a call to make; it depends on embsim-board, so it stays a second crate |
p2iss::sdimage::mad_card |
stays in SIL/MaDSim |
generic sdimage::build and Dir go upstream |
10 firmware acceptance tests, decoder_golden.rs, tools/gen_golden.py |
stay in MaD under SIL/MaDSim/tests/ |
need the MaD P2 image and its flexcc listing |
p2core/tests/flash_program.rs |
stays in MaD | reads the PWA's image.ts; embsim gets the bootloader test below instead |
P2-EC32MB module: p2_ec32mb.net, its registry, ec32mb_module.rs |
embsim mcus/p2/ec32mb/ as embsim-p2-ec32mb, imported by MaDSim |
today a test fixture in embsim/board/tests/; see Board ownership |
EdgeBoard + DS2: mad_edge.net, ds2_addon.net, their registry, harnesses, seven test binaries |
MaD, new crate SIL/boards/ |
today inside embsim's test suite; see Board ownership |
SIL/MaDSim/src/{main,iss_description}.rs, SIL/makefile, Software/Control/e2e/run-all.mjs FW-ISS, four docs/dev/sil-*.md, CLAUDE.md |
edited in the MaD bump PR | imports rename to the new crate names |
Optional later: relocate platforms/p2 to mcus/p2/hal/ so the MCU library has
one shape: mcus/p2/{hal, iss, iss-net, ec32mb} under one directory.
Board ownership¶
The rule, already written in embsim's BOARD_ENGINE.md: the engine is
generic; part registry entries, harness files and plant wiring live in the
consuming repo. Applied to boards: a vendor module that carries the MCU is
part of simulating that MCU and belongs in embsim. A board MaD designed is
MaD's and belongs in MaD. So the P2-EC32MB module goes to embsim, and the
EdgeBoard carrier and the DS2 add-on come home.
Where things are today, in embsim/board/tests/:
| Fixture | Board | Consumed by |
|---|---|---|
fixtures/p2_ec32mb.net |
Parallax P2-EC32MB Rev B, transcribed from the vendor PDF, CC-BY-SA 4.0 | ec32mb_module.rs, machine_system.rs |
fixtures/mad_edge.net |
MaD EdgeBoard, kicad-cli export, 3 sheets |
edgeboard.rs, isolation_bridge.rs, rs422_determinism.rs, machine_system.rs, hierarchical_netlist.rs, fixture_smoke.rs |
fixtures/ds2_addon.net |
MaD DS2 force-gauge add-on | ds2_live_force_path.rs, ds2_regressions.rs, machine_system.rs, fixture_smoke.rs |
All three registries, every harness and every scenario fragment sit in one
1,650-line test module, machine_parts/mod.rs, whose own header calls it the
"transitional home before MaD grows its own registry". The P2 in it,
P2EdgeModule, wraps the native McuComponent only; the ISS cannot mount on
the module today. MaDSim meanwhile registers the ADS122U04 a second time in
both system descriptions, so the DS2 registry exists twice.
Target, embsim side: embsim-p2-ec32mb.
p2_ec32mb.netwith its provenance header, the module registry (TCXO, 16 MB SPI flash, PSRAM, buck, LDO, brown-out detector, DIP switch, polarity FET, the 2G04 inverter), thePCB/NC_Netstub refs, and the 80-finger edge as the module's harness surface.ec32mb_module.rsmoves with it.- The module takes either MCU: the native
McuComponentorP2Iss.U100's netlist pins are already namedP0..P63, the same namesp2iss::pin_namegives the ISS, so mounting is a facade overP2Issthat declares the supply pinsPowerIn. When the ISS is mounted, the module's on-board flash stub is replaced by the realSpiFlashnode on the module's own P58..P61 nets. - The Edge-module bootloader test runs on this module, not on a bare ISS:
the ROM boots the P2 at
U100, the flash is the module's, the serial loader arrives on the P62/P63 edge fingers. That is what "test the P2 ISS against the module" means in practice.
Target, MaD side: SIL/boards/, a new crate. (It is landing as
SIL/mad-catalog, embsim MIGRATING-MAD.md section 3: both netlists are in
SIL/mad-catalog/netlists/, bundled by the board kinds mad-edge and
mad-ds2.)
mad_edge.net,ds2_addon.net(now inSIL/mad-catalog/netlists/), the EdgeBoard and DS2 registries and stubs, the harnesses (module_socket_harness,force_gauge_harness,machine_harness,bench_rails) and scenario fragments, and the seven test binaries above. MaDSim's two system descriptions consume this crate instead of registering parts themselves.- No firmware link, so the tests run without
libfirmware.a, andmachine_system.rscan mount the ISS module. MaD'ssil-rustjob runs them as part ofcargo test --workspace. - The design doc's Phase 3 gate, regenerating the netlists from
Hardware/in CI and diff-checking them, becomes a MaD CI step here. The vendor module netlist is exempt, as the design doc already says.
MaD consumes the module as a dependency. SIL/MaDSim takes
embsim-p2-ec32mb as a path dependency through the submodule, the way it
already takes every other embsim crate, and both system descriptions mount the
module instead of a bare P2: the native one with McuComponent in the slot,
the ISS one with P2Iss. The module plugs into the EdgeBoard through the
80-finger socket harness from SIL/boards, and the EdgeBoard carries the DS2
add-on and the plant. The bench P2EVAL component in system_description.rs
and the bare-ISS pin wiring in main.rs retire with that. One module, two
uses: embsim tests the ISS on it, MaD simulates the machine on it. This is
option 2 of the design doc's "P2 Edge Module gap", module-as-board, with the
module owned upstream.
Generic IC models stay generic. The RS-422 driver and receiver
(AM26LS31, AM26LV32) and the ISO6731 serial isolator are TI parts with
behaviour, currently test-local in machine_parts. Promote them into
embsim-models next to the ISO67xx, NSI50010, VO2631 and ADS122U04
models that already live there. Only the registry entries that map MaD's BOM
value strings onto those models go to MaD.
What embsim loses when the MaD boards leave, and must backfill first:
mad_edge.netis the only hierarchical, three-sheet fixture. The netlist parser needs a small synthetic hierarchical fixture upstream.isolation_bridge.rsis the only test ofembsim_models::isolation. Those models need unit tests on a hand-built board.ds2_regressions.rsholds the Phase 1 findings gate: floating~RESET, unsourced AVDD, crossed TX/RX. The engine diagnostics they exercise need synthetic equivalents upstream; the bench-truth versions stay MaD's.rs422_determinism.rsfollows the RS-422 models: re-express it on a synthetic board when they move toembsim-models.BOARD_ENGINE.mdcites the EdgeBoard and DS2 in nine places andTESTING.mdin one; re-point them at the module and the synthetic fixtures.
Tests: what each file needs¶
| Input needed | p2core/tests |
p2iss/tests |
Goes to embsim CI |
|---|---|---|---|
| Silicon goldens only | op_coverage, probe_coverage, silicon_oracle, silicon_probe, silicon_reports |
yes | |
| P2 boot ROM only | rom_boot_chain, rom_serial |
rom_boot_net, rom_serial_net |
yes |
P2 boot ROM + loadp2's flash_loader.bin, read today from Software/Control/src/firmware/image.ts |
flash_program |
no, stays in MaD; superseded upstream by the bootloader test | |
| Nothing | poll_fast_forward, sd_block_write |
yes | |
MaD MaD/program |
block_fill, boot, per_cog_dir, sd_spi_words |
level_pins, protocol_on_levels, pty_protocol, sd_mount, sd_node |
no, stays in MaD |
MaD image and its flexcc .p2asm listing |
decoder_golden |
no, stays in MaD |
The silicon rows are the point of the move: probe.txt holds 1550
one-instruction records from a P2-EVAL, the two baselines record the 98 cases
in 12 mnemonics that still diverge, and the gate fails on any unlisted
divergence and on any listed one that starts matching. That burn-down list
becomes embsim's.
The Edge-module bootloader test¶
embsim has to prove the flashing chain works on the Edge module with no MaD
software in the loop: the P2 MCU, its boot ROM, the SPI flash on P58–P61, and
the flashing sequence injected over the serial bus on P62/P63. flash_program.rs
proves half of this today, since the real loadp2 stub runs as machine code and
programs the flash, but it takes the stub from the PWA and places the image in
hub by hand, skipping the ROM's serial loader. This test covers the whole chain
and is written from P2 parts only.
Parts, all P2:
- the P2-EC32MB module from
embsim-p2-ec32mbwith the ISS mounted atU100, boot ROM in place, and the boot straps on P59/P60/P61 set for the flash path, asrom_boot_netsets them; - the module's own SPI flash as the
SpiFlashnode (flashnode) on its P58..P61 nets, initially blank; - a host on the programming UART driving bytes as levels onto the P63 edge
finger and reading P62, as
rom_serial_netdoes throughHostPty; - loadp2's
flash_loader.bin, vendored from totalspectrum/loadp2 (MIT) or assembled from its.spin2by the same script that builds the ROM. Never read from the PWA; - a tiny payload built like the
hwtestprograms and committed as a binary, whose only job is to print a marker on P62.
Sequence injected on the wire. This is the ROM's own protocol, the one loadp2 speaks, not anything of MaD's:
- reset, then
> Prop_Chk 0 0 0 0; expect\r\nProp_Ver G; > Prop_Hex 0 0 0 0followed by the hex of the stub then the payload, the header longs patched the way loadp2 patches them so the checksum lands on"Prop", sent in chunks and terminated by~;- the ROM launches the download from hub
$0; the stub erases and programs the flash over P58–P61 and reboots; - the ROM's strap decision now takes the flash path, loads the first
kilobyte, verifies
"Prop", and launches loadp2's stage-1, which loads the payload.
Assert: the flash holds loadp2's stage-1 at $000 with a valid checksum
and the payload after it, and after the reboot the payload's marker appears on
P62.
Work it implies: the ROM's hex download path executes on the ISS for the
first time, since rom_serial stops at Prop_Chk; loadp2's production stage-1
uses Fast Read Dual Output, which the flash model does not implement today
($03, $02, $06, $20, $D8 and $C7 are there); the stub itself already
runs (SKIP, LOC PTRA, the streamer on a transition pin), per the
flash_program WIP. It lives in embsim-p2-iss-net, because "over the serial
bus" means levels on nets; a core-level twin through Board::push_rx is cheap
once it passes.
The simulator-only stage1.spin2 and flashimage.rs chain (rom_boot_chain,
rom_boot_net) stays as a P2 item: it is the fast unit-level boot chain, and
the bootloader test is the acceptance check with the real stage-1. MaD keeps
FW-ISS in run-all.mjs as its own test of the PWA against this ROM.
embsim CI¶
No new jobs. cargo test --workspace --all-targets on ubuntu and macOS picks
the crates up. Measured cost on MaD main, debug mode, ubuntu: the probe replay
is 53 s and the whole silicon suite about a minute.
Add:
- [ ] a "goldens are unmodified" step for
hwtest/golden/and the two baseline files, mirroring the existingboard/tests/fixtures/tracescheck, so a replay that rewrites its own oracle cannot pass. - [ ] a TESTING.md section: silicon goldens are the spec; recapture needs a
P2-EVAL and the PlatformIO flexcc + loadp2 packages (
P2_PORTselects the FTDI); baseline lines are burned down and deleted, never added to hide a regression. State that the goldens and ROM are chip-level fixtures, so the "every crate is firmware-free" rule still holds. - [ ]
README.mdcrate table rows for both crates.
Gates checked 2026-09-18 on this branch:
| Gate | Result | Action |
|---|---|---|
cargo fmt --check |
clean | none |
cargo clippy --all-targets -D warnings |
3 errors, all in the uncommitted WIP | fix in the WIP; main is clean because MaD CI already gates it |
cargo doc -D warnings |
1 broken intra-doc link to crate::board::tests in p2core |
fix before PR A; MaD never gated docs so it never surfaced |
MSRV cargo +1.88 build |
builds | none |
cargo deny check |
p2core has no deps; p2iss adds nix + libc, both already allowed |
none |
| licence hygiene | vendor/parseUtils.ts has no header; ROM is Parallax's published source |
add vendor/LICENSE (MIT, Iron Sheep Productions / Parallax) and a ROM provenance NOTICE; confirm redistribution terms for an MIT repo |
Coupling to remove before the git mv¶
- [ ]
p2core/src/board.rsis titled "A MaD board". Pins 58–63 are the P2 boot-pin convention (flash/SD SPI, ROM serial), so the struct is really a P2 reference board. Only the protocol link on 30/32 (PIN_PROTO_RX/TX) is MaD. Rename, and make the protocol pins a constructor parameter. - [ ]
p2iss::sdimage::mad_cardbuilds MaD's FAT layout (profile record and two directories). Keepbuild/Dirupstream; movemad_cardand its unit testthe_mad_card_has_the_directories_the_firmware_opens_intoto MaDSim. - [ ]
flash_program.rsstays in MaD. It decodes the base64 inSoftware/Control/src/firmware/image.tson purpose, so it exercises the bytes the app ships; that makes it a MaD test. Two cases inside it are pure ISS facts and move upstream as plain tests:loc_ptra_writes_the_absolute_hub_addressandskip_cancels_a_slot_without_decoding_it. The flashing coverage embsim needs comes from the bootloader test above, not from this file. - [ ] ROM assets move into the ISS crate;
p2issand the tests read them from there instead of../p2iss/rom. - [ ] Vibes.
flash_program,rom_serialandrom_serial_netdeclare behaviours throughvibes-behaviour, a path dep into MaD'sVibessubmodule that embsim does not have. Eight claims inbehaviours.jsonlcite them andSIL/vibes.suite.jsonruns them.flash_programstays, so its six claims stay.rom_serialandrom_serial_netmove unannotated; drop their two claims and the two suite commands. The Prop_Chk handshake is a ROM fact, and MaD'sFW-ISSe2e suite remains the MaD-side claim for UI flashing. - [ ]
tools/gen_golden.pydefaults to MaD'sprogram.p2asm; it stays in MaD withdecoder_golden. - [ ] Examples: 8 of 30 in
p2coreand 3 of 4 inp2issdefault to the MaD firmware path. Make the image path an argument. - [ ] One doc comment in
p2core/src/lib.rs("Every MaD cog runs the same kernel") reads as a FlexC ABI fact; reword.
Sequencing¶
- [x] MaD #110 merged 2026-09-20.
It re-pins embsim to
main, which already has the UART codec andSerialLevelBridgethatp2issneeds.feat/iss-rom-serial-flashstill points its gitlink at a 16-commit side branch with no PR; rebase it ontomain. - [ ] Land this branch's WIP in MaD with the clippy and rustdoc fixes, so the embsim PR is a pure relocation plus the generalisations above.
- [ ] embsim PR A:
embsim-p2-issfromp2core, withhwtest/, ROM, vendor table + LICENSE, the board rename, the CI step and docs. Dep-free, so it can start now. - [ ] embsim PR B:
embsim-p2-iss-netfromp2issminusmad_cardand the firmware tests. Can fold into A. - [ ] embsim PR C:
embsim-p2-ec32mbfrom the module half ofmachine_parts,ec32mb_module.rsandp2_ec32mb.net, with the MCU slot taking native or ISS; then the bootloader test on it. Promote the RS-422 andISO6731models intoembsim-modelsand add the synthetic fixtures and tests listed under Board ownership. - [ ] MaD boards PR: create
SIL/boards/from the EdgeBoard and DS2 half ofmachine_parts, the two netlists, the harnesses and the seven test binaries, copied from the pinned embsim commit; point both system descriptions at it. Then embsim PR D deletes those files upstream, and the next pin bump closes the loop. - [ ] MaD bump PR: bump the pin, delete
SIL/p2coreandSIL/p2iss, drop them fromSIL/Cargo.toml, re-home the acceptance tests,flash_program,decoder_golden,gen_golden.pyandmad_cardinto MaDSim, update imports,makefile(bootrom,ROM_IMAGE,playground-iss*),docs/dev/sil-*.md, the Vibes suite and ledger, andCLAUDE.md. The existingembsim-cijob then runs the silicon suite on every SIL PR through the pinned commit.
Outside the move, worth knowing¶
- The ten acceptance tests already skip in MaD CI:
sil-rustbuilds onlynative_emulator, and the tests skip whenMaDEdge/program(nowMaD/program) is absent. The release job builtMaDEdge(now the oneMaDimage) on ubuntu without trouble, so un-skipping them is a cheap follow-up once they live in MaDSim. - On this WIP branch
silicon_proberan locally for over seven minutes at low CPU before being stopped, against 53 s onmainin CI. Look before the WIP lands. - The
Vibessubmodule was uninitialised on the scoping machine, with staledist/andnode_modules/from the pre-submodule prototype in its path, so the SIL workspace would not load.git submodule update --initneeds that directory empty.