labwired test: fast-boot ESP32-S3 from the manifest (unblocks S3 labs via MCP)#586
Merged
Conversation
Migrate the shared I2C model's NXP Kinetis variant off the per-cycle walk. Its tick() is a PURE level-IRQ re-assertion ((S.IICIF & C1.IICIE)); all byte/device work is synchronous in read/write (Cell-based, works under the &self read path), so the timer/systimer held-level re-pend event pattern reproduces it cycle-exactly: - uses_scheduler / needs_legacy_walk true only for the Kinetis variant with a clock attached; tick() inert in that mode. - the level-check event arms when IICIE is set (a C1 write) and self-perpetuates at delay 1 while IICIE stays set, pending the own NVIC line whenever (IICIF & IICIE) -- so a &self D-read that latches IICIF is caught the next cycle, exactly like the walk. The STM32 F1/L4 variants STAY on the legacy walk: their cycles_remaining transaction countdown is driven by &self-read side effects (rxne_consumed / read_dr_consumed) that arm subsequent IRQ-raising transitions, which cannot be delivered through the write-armed event path under the &self read constraint without dropping the IRQ or overrunning the read-gated byte stream (a fidelity loss). Per the non-negotiable fidelity rule they remain walk-driven; scheduler_mode() is false for F1/L4 regardless of clock. Fidelity: per-cycle property harness (master write level IRQ, master read D-latch level IRQ -- the read-set-IICIF crux, IICIE-off no-pend, F1/L4 stay-on-walk guard) comparing walk-tick vs on_event pend cycles + full snapshot every cycle. Board flip: i2c1 was the last walker on the FRDM-KW41Z bus (after #522's mcg/rsim/dwt batch); with it migrated the walk-forcing set is EMPTY and the bus derives walk-deletion with zero hand flags -- the campaign's first full- board flip. kw41z_walk_free_differential flip test un-ignored + forcing-set test rewritten to assert empty. Perf (native release, FRDM-KW41Z, tight spin loop): walk-on interval-1 6.25 MIPS -> walk-deleted interval-64 15.97 MIPS = 2.56x. CI: stm32_dma_walk_differential added to the scheduler-IRQ gate. manifest drift_ack provenance comment for the B4 dma/i2c migration (keys already 2026-07-12).
perf: walk-free STM32 DMA + Kinetis I2C via event scheduler (cycle-exact); flip FRDM-KW41Z
get_ssd1306_framebuffer downcast the I2C peripheral only to the generic STM32 I2c and the ESP32-C3 Esp32c3I2c (fixed in #455 / 2e47dd0). On an ESP32-S3 the OLED sits on the command-list Esp32s3I2c, so the downcast failed and the accessor returned 'not an I2C controller' — the display poll swallowed it and an S3 OLED rendered blank in the playground/embed. - esp32s3/i2c.rs: expose attached_slaves() (mirrors Esp32c3I2c::attached_slaves and I2c::attached_devices). - inspect.rs: get_ssd1306_framebuffer now also tries Esp32s3I2c::attached_slaves() before erroring, downcasting the matched slave to Ssd1306. - test: ssd1306_gddram_is_readable_through_esp32s3_i2c drives a real GDDRAM write through the S3 command-list engine and reads it back through the exact attached_slaves() path inspect.rs uses, asserting the real bytes (not blank).
fix(wasm): read SSD1306 framebuffer from the ESP32-S3 I2C controller
Migrate the ESP32-C3 I2C0 bit-level command-list engine off the per-cycle legacy walk onto the event scheduler (mirrors the SYSTIMER #519 and the generic STM32 SPI bit-engine). Model (peripherals/esp32c3/i2c.rs): - Split the old `tick_elapsed` acc-loop into `advance_engine(cycles)`, shared by BOTH the legacy walk and the scheduler paths, so the num/den module-tick cadence is byte-identical however cycles are batched. - uses_scheduler() once the bus attaches its CycleClock (feature-gated); attach_cycle_clock/sync_to anchor the engine to `last_synced` and advance it before every MMIO write (fresh SR/FIFO_ST/INT_RAW at the write choke). - matrix_irq_sources() exposes the I2C0 level (int_raw & int_ena) — the C3 interrupt matrix re-derives it, replacing the walk's explicit_irqs push. - take_scheduled_events()/on_event() keep exactly ONE module-tick event in flight (the `scheduled` gate): bootstrap arms it from TRANS_START (delay-1 to offset the bus write-path +1); on_event fires one tick and re-arms the successor at cycles_to_next_module_tick until the engine parks. The self-perpetuating per-tick event tracks timing_from_regs() exactly, so wire timing stays tick-interval-independent. force_legacy_walk() detaches the clock for the differential reference / direct-drive unit tests. - Bus is unchanged: the existing sched-source union + matrix routing already handle any uses_scheduler peripheral. Fidelity: new oled_lab_i2c0_walk_on_vs_scheduler_is_byte_identical asserts byte-identical serial + total_cycles + SSD1306 framebuffer (walk vs scheduler) at interval 1, and framebuffer identity at interval 64. I2C0 drops from the C3 walk-pinner ledger (EXPECTED_PINNERS 5->4); derive_walk_deletable stays false (4 real workers remain). Direct-drive I2C0 unit tests pin force_legacy_walk (no Machine scheduler loop).
…e migration The i2c0 walk->scheduler migration touches esp32c3 model files, tripping the silicon-validation drift gate. The change is byte-identical to the walk path (oled_lab_i2c0_walk_on_vs_scheduler_is_byte_identical, interval 1 + 64), so the reset oracle is unaffected; ack the model-file touch and regen VALIDATION_STATUS.md.
perf: walk-free ESP32-C3 I2C0 via self-perpetuating module-tick events
The level-only pair of the C3 walk-free campaign. Both spi2 and apb_saradc are one-shot LEVEL re-emitters whose int_raw is write-armed by a transaction (spi2 launch) / conversion (apb_saradc ONETIME_START) — no free-running counter — so they migrate off the per-cycle walk with uses_scheduler + attach_cycle_clock + a matrix_irq_sources level export and NO scheduled events (simpler than the i2c0 #525 module-tick migration). The bus glue is unchanged: refresh_esp32c3_sched_sources / poll_scheduler_matrix_sources already union any uses_scheduler peripheral, so the walk-skipped level is re-derived each aggregation and routed to the RISC-V line identically to the legacy walk (which still runs — ledc + wifi_mac keep pinning). Walk pinners now 4->2: {ledc, wifi_mac}. Gates (all green, --features jit,event-scheduler): - c3_level_peripheral_matrix_routing: direct IRQ-delivery identity — walk (force_legacy_walk, tick explicit_irqs) vs scheduler (matrix_irq_sources) produce the same riscv_irq_lines for spi2 src 19 / apb_saradc src 43, and both de-assert after INT_CLR. - oled_lab_spi2_apb_saradc_walk_on_vs_scheduler_is_byte_identical: the pair is quiescent in the OLED demo, so serial + total_cycles + SSD1306 framebuffer are byte-identical at interval 1 and 64. - oled_lab_walk_pinners_after_rtc_migration: EXPECTED_PINNERS = {ledc, wifi_mac}; derive_walk_deletable() stays false, max_safe_tick_interval 1. - validation/manifest.yaml: dated 2026-07-12 drift note (ack date unchanged; byte-identical, no live re-capture).
configure_xtensa_esp32s3 hard-attaches board I2C slaves the factory does not model (TMP102 always, opt-in PCA9685). Add an SSD1306 128x64 @ 0x3C the same way so an esp-hal S3 firmware that drives I2C0 paints a real panel the browser twin reads back via get_ssd1306_framebuffer. Distinct address (TMP102 0x48 / PCA9685 0x40), inert until firmware addresses it. Also add configs/systems/esp32s3-oled-demo.yaml: the S3-Zero + SSD1306 lab manifest (oled external_device on i2c0 + the 'oled' board_io readback binding) consumed by the playground esp32s3-oled-lab.
feat(esp32s3): attach SSD1306 OLED @ 0x3C to I2C0 for the S3 OLED lab
perf: walk-free ESP32-C3 spi2 + apb_saradc (level-only matrix export)
Shared, architecture-neutral scaffold for the universal dispatch JIT behind the new `jit-framework` cargo feature. No per-ISA codegen: the only frontend is a passthrough that side-exits every block to the interpreter, proving the full loop (cache -> dispatch -> instantiate -> run -> side-exit -> fallback -> chaining) compiles and runs end-to-end with zero code generation. Adds crates/core/src/cpu/jit_framework/: block_cache (flash-PC keyed, hot-counter promotion, invalidate-all-on-flash-write), side_exit protocol, IsaFrontend trait + PassthroughFrontend, JitRuntime abstraction with the imported-WebAssembly.Memory binding scheme + InterpreterRuntime, JitHost interpreter-fallback hook + SafetyGate correctness rails, the chaining dispatch loop, and the lockstep differential harness. Design doc at docs/engineering/universal-jit-framework.md. Default build and the existing `jit` Xtensa pilot are untouched.
Move the ESP32-C3 LEDC controller onto the event scheduler, the MEDIUM
"timer port" in the C3 walk-free campaign. Its four low-speed timers are
genuine up-counters that latch LSTIMERx_OVF over time, so unlike the
level-only spi2/apb_saradc pair this needs a real timer migration
(SYSTIMER/STM32 TIMx pattern), not just a level export.
- Counters advance LAZILY in closed form off the bus-published CycleClock
(fresh on every &self read / write-path sync_to / matrix level poll);
each overflow rides a SCHEDULED event (take_scheduled_events arms the
nearest wrap, on_event materialises the latch at its exact cycle and
re-arms, an arm-token kills a stale chain on reconfig). Level exported
via matrix_irq_sources; uses_scheduler/needs_legacy_walk/attach_cycle_
clock/force_legacy_walk added. All quirks preserved (RST/PAUSE freeze,
2^DUTY_RES wrap clamped to 14 bits, integer CLK_DIV divider, sticky W1C
OVF latch); legacy walk path unchanged when no clock is attached.
- LEDC was the last chip-yaml walk pinner, so the C3 devkit from_config
bus now derives walk-deletion. Re-derive legacy_walk_disabled at the end
of the rom-boot assembly (after wifi_mac is appended) so the real walk
worker keeps the walk enabled (interval 1) — byte-identical to before.
The bus-level level-pair routing test re-derives the flag after
force_legacy_walk for the same reason.
- Gates: in-module scheduler_mode walk-identity differential (all four
counters + INT_RAW + OVF assert cycles at interval 1, interval-64
batched, no-poll event delivery, non-vacuity OVF>0); bus-level
c3_ledc_matrix_routing (walk vs scheduler route source 23 identically,
force_legacy_walk re-emits, INT_CLR de-asserts); EXPECTED_PINNERS now
{wifi_mac}. validation drift note added.
esp32c3: migrate LEDC off the per-cycle walk (timer port)
refactor(cli): extract artifact serialization
wifi_mac was the final peripheral pinning the per-cycle legacy walk on the
ESP32-C3 OLED rom-boot bus, on TWO axes. Both migrate with no new event
machinery:
* Interrupt LEVEL (matrix source 0, asserted while a MAC event is pending):
move off the walk with uses_scheduler + a matrix_irq_sources export (the
level-only pattern shared with spi2/apb_saradc). On a walk-DELETED bus the
write-armed EVENT_CLR acknowledge is re-derived at the MMIO write choke
(sync_esp32c3_irq_cache_write, gated on legacy_walk_disabled) so the level
de-asserts without a walk tick — the one legitimate bus addition the last
walker needs.
* Descriptor-ring PUMP (tick_with_bus): needs_bus_tick() was unconditionally
true. Make it honest — true only while WiFi is actually up (rx_ring != 0 /
a pending TX / medium mode) — so an idle MAC (WiFi off, as in the OLED demo)
arms nothing and the bus is walk-DELETABLE. The pump rides the existing
write-armed, self-perpetuating bus-tick path (refresh_bus_tick_index); the
frame-moving model is byte-for-byte unchanged.
THE UNLOCK: with wifi_mac migrated the C3 OLED rom-boot bus derives
walk-DELETED, EXPECTED_PINNERS is empty, and max_safe_tick_interval rises to
64 — native OLED-lab throughput ~5.2 -> ~16.2 MIPS.
Gates (all green): the WiFi-exercising pump differential
c3_wifi_mac_walk_differential (TX+RX ring delivery byte-identical
walk-vs-scheduler at interval 1 AND 64, non-vacuity = frames moved); the
direct IRQ-delivery gate c3_wifi_mac_matrix_routing (walk vs scheduler route
MAC source 0 identically; force_legacy_walk re-emits the same source;
EVENT_CLR de-asserts at the write choke on a walk-deleted bus); the endgame
pinner ledger (EXPECTED_PINNERS = {}, walk-deletable, interval > 1); and the
OLED rom-boot lab byte-identical at interval 1 and the now-unlocked 64.
Adds SystemBus::recompute_walk_deletable() so out-of-crate harnesses that flip
a peripheral's drive mode after assembly (build_oled_lab's force_legacy_walk)
re-derive the flag — without it a peripheral pinned back onto a now-deletable
bus is silently starved.
esp32c3: migrate wifi_mac off the per-cycle walk (last C3 pinner → OLED lab ~5.2→16.2 MIPS)
…l gate Foundation milestone of the RISC-V (RV32IMC) frontend for the universal dispatch JIT. Zero codegen: the frontend walks a basic block over the flash CodeView (reusing decoder::riscv::decode_rv32 + the length rule), classifies every instruction (sequential / control-flow / unmodeled), and emits a correct BlockPlan (entry_pc, end_pc, instr_count, side-exit map) with an EMPTY code body, so every block side-exits to the interpreter. - riscv/mod.rs: block-walker, classification, RiscVFrontend (isa_name rv32imc, translate_block), plus the cycle-CSR ignore-index hook. - riscv/host.rs: JitHost adapter for Machine<RiscV> (pc/interpret_one/ resume_at/code_view over bus.flash/safety/snapshot_state/take_flash_dirty) and the StateVec snapshot (x0..x31, pc, mstatus,mie,mip,mtvec,mscratch, mepc,mcause,mtval). - tests/riscv_jit_lockstep.rs: differential harness proving the all-bail frontend is byte-identical to the interpreter over a hand-assembled hot loop (3000 compares, non-vacuous floor). All new code gated behind the existing jit-framework feature.
jit(riscv): RV32IMC frontend foundation (all-bail walker + host adapter + differential gate)
* feat(peripherals): model SH1107 128x128 I2C OLED Register-level SH1107 controller model for 1.5" 128x128 mono OLED modules (e.g. GME128128-01-IIC), registered as the `oled-sh1107` peripheral kit and surfaced through a new `get_sh1107_framebuffer` wasm accessor. SH1107 vs SSD1306: 16 GDDRAM pages (128 rows), 7-bit column address, and single-byte page/vertical addressing-mode selects (0x20/0x21) with all multi-byte commands taking exactly one parameter. Reuses the generic I2C attach path, so it works on the STM32 and both ESP32 I2C controllers. Regenerated the vendored peripherals manifest fixture. * fix(peripherals): satisfy clippy erasing_op/identity_op in SH1107 test
First RV32IMC block that executes instead of bailing. Adds the shared emit
infra chunks D/E build on:
- wasm_encode: minimal binary-wasm encoder (LEB128 + section framing) →
real wasm bytes in BlockPlan.code.
- emit: ALU-emittable block walker + per-op codegen (LUI/AUIPC, reg-imm and
reg-reg arith/logic/shift, RV32M mul/mulh*/div*/rem*, C.ADDI/C.LI/C.MV/
C.ADDI16SP/C.ADDI4SPN/C.SLLI). Register-file-in-locals model: prologue
loads touched regs from an imported "regs" memory into wasm locals, body
operates on locals, epilogue stores back; x0 reads const 0, x0 writes
dropped. div/rem reproduce RISC-V div-by-zero (→ -1 / dividend) and
INT_MIN/-1 overflow exactly, trap-free via guarded if/else.
- exec (jit feature): wasmtime executor filling the scaffold's elided
register handle RISC-V-locally; syncs cpu.x <-> register memory, maps
wire 0 → SideExit::Chain{end_pc}. RiscvJitEngine ties BlockCache
promotion to the executor with per-unit retired-instruction counts.
RiscVFrontend now emits ALU prefixes (all-bail fallback for non-ALU entry).
Gates: differential byte-identity over an ALU hot loop (incl. div/rem edge
cases) with compiled-block execution asserted; per-op fuzz vs interpreter
over 200+ operand pairs incl. divide edges; micro-bench 51x (12.3 → 630
MIPS) on the ALU loop. All-bail lockstep + framework tests stay green.
Emit real wasm for the RV32IMC control-flow terminators — the conditional
branches (Beq/Bne/Blt/Bge/Bltu/Bgeu), Jal/Jalr, and the compressed
C.J/C.JR/C.JALR/C.BEQZ/C.BNEZ (C.JAL arrives as Jal{rd:1}). A block is now
the maximal ALU prefix (chunk C) ended by at most one such terminator.
Exit protocol: introduce a second wire code WIRE_CHAIN_DYNAMIC=1 alongside
WIRE_FALL_THROUGH=0. A terminated block resolves its taken/not-taken/jump
target IN wasm and stores it to a fixed next-PC slot (word 32, byte 128,
just past x31) in the imported register memory, then returns the dynamic
wire; the runtime reads that slot and Chain{}s to it. One code covers every
terminator: conditional branches pick between two compile-time addresses
with an in-wasm `if`, and Jalr/C.JR compute a data-dependent one. The
register sync in CompiledBlock::run grows 128->132 bytes to carry the slot;
WIRE_FALL_THROUGH is unchanged and 2 is left free for chunk E (MEM_FAULT).
Semantics mirror RiscV::step byte-for-byte: sign-extended pc+imm targets,
the (rs1+imm)&!1 low-bit mask for Jalr/C.JR/C.JALR, pc+inst_len links
(so C.JAL links pc+2 and Jal links pc+4), C.JALR links x1=pc+2, rd=x0
drops the link, and the next PC is resolved before the link write so a
`jalr rd, rd` reads the pre-write rs1.
New gate tests/riscv_jit_branch_lockstep.rs (jit + jit-framework):
- branch_hot_loop_is_byte_identical_and_chains: a backward-branch hot loop
whose body block chains back to itself; byte-identical to the interpreter
at every aligned boundary and non-vacuous (compiled=6, block_runs=991,
block_instrs=2975, interpreted=25).
- every_terminator_matches_interpreter: per-terminator differential, taken
and not-taken, covering Jalr/C.JR masking, rd=x0, rd==rs1 Jalr, backward
vs forward targets, and signed vs unsigned compares.
Widen the emittable prefix to RV32(I)C loads/stores (Lb,Lh,Lw,Lbu,Lhu, Sb,Sh,Sw + compressed CLw,CSw,CLwsp,CSwsp). A load/store extends the block rather than ending it. Memory binding: the block range-checks the effective address against a single contiguous guest-RAM window (bus.ram) bound at translate time and carried in the same imported memory at byte offset 256. In-window accesses use inline iN.load/iN.store (widths + sign/zero extension from the wasm opcode); the runtime syncs the RAM window in/out only for blocks that touch memory, so pure-ALU blocks stay byte-identical to chunk C. Fault exit: an out-of-window (MMIO/unknown) access side-exits via the new WIRE_MEM_FAULT = 2 (leaves 1 for chunk D). The faulting op publishes its own resume PC + retired-so-far count to dedicated slots (words 33/34, clear of D's word-32 next-PC slot), flushes only the registers written by prior instructions, and returns; the runtime resumes the interpreter at that PC so MMIO side effects stay interpreter-owned. A block that faults on its entry instruction retires 0, so the dispatcher interprets one instruction to guarantee forward progress. Stores clear the LR/SC reservation precisely: an inline store sets a flag slot and the runtime clears cpu.reservation iff set (a faulted store is cleared by the interpreter that replays it) — mirrors RiscV::step with no spurious over-clearing. New riscv_jit_mem_lockstep.rs: hot loop byte-identity + non-vacuity + RAM byte-identity, per-op differential (all widths, +/- offsets, sign/zero), compressed forms, and the fault-resume path. Full regression: jit,event-scheduler = 2034 passed/0 failed; jit,event-scheduler,jit-framework = 2069 passed/0 failed. fmt+clippy clean.
jit(riscv): wasm emit skeleton + integer-ALU codegen (chunk C)
jit(riscv): branch/jump codegen (chunk D)
Reconcile the RV32IMC wasm-JIT frontend so integer-ALU (chunk C),
branches/jumps (chunk D), and loads/stores (chunk E) all coexist in one
compiled block. The three side-exit wire codes are now disjoint and
served by a single runtime match:
0 WIRE_FALL_THROUGH straight-line body ran to end_pc (C/E)
1 WIRE_CHAIN_DYNAMIC branch/jump terminator resolved next PC in wasm (D)
2 WIRE_MEM_FAULT out-of-window load/store side-exit to interpreter (E)
Slot layout is the union of both designs, all non-overlapping:
regs x0..x31 bytes 0..128
next-PC (D) word 32 byte 128
fault-PC/retired/res-flag (E) words 33/34/35 bytes 132/140
guest-RAM window (E) byte 256
emit.rs: emit_block now walks the maximal straight-line prefix of
{ALU (C) union in-window load/store (E)} body ops, then optionally
appends one branch/jump terminator (D). A block can therefore contain a
load, ALU, a store, and end on a conditional branch. The mem-fault edge
is added whenever the body touches RAM.
exec.rs: CompiledBlock::run syncs the superset of live slots (regs +
word 32 + words 33/34/35) plus E's RAM window for memory-touching
blocks, and its match resolves all three wire codes.
Add crates/core/tests/riscv_jit_combined_lockstep.rs proving the three
chunks compose: a hot loop whose single block mixes ALU + load + store +
a backward conditional-branch terminator stays StateVec byte-identical
to the interpreter (non-vacuously), plus an MMIO-fault case that stays
identical via interpreter-resume. The alu/branch/mem lockstep gates
still pass (branch's run/compile call sites updated to chunk-E
signatures).
jit(riscv): load/store codegen (chunk E)
MERGE BAR #2 gate for the composed RV32IMC frontend (chunks C+D+E): a realistic firmware-style hot loop mixing integer ALU, in-window loads/ stores, and a conditional-branch terminator, timed compiled-vs-interpreter. Ignored by default; run with --ignored --nocapture. Measured (native, arm64): interp 1.1 MIPS, jit 8.5 MIPS = 7.64x, with the loop running almost entirely through compiled blocks (block_instrs=486002 vs interpreted=91). Clears the >=3x bar with headroom.
test: ESP32-S3 I2C/GDMA/SPI, nRF52 EasyDMA, and multi-board display fidelity oracles
# Conflicts: # crates/core/tests/board_coverage_ratchet.rs
- Potentiometer input component (linear divider → ADC counts) with kit + wasm setter - openai-deck-s3 esp-hal firmware: SH1107 OLED render + 10 key switches over USB-serial-JTAG - native boot test asserting framebuffer render + key-press serial protocol - SH1107 LabRef entry linking the deck lab
test: executing walk-vs-scheduler fidelity gates for STM32 f401/l073/l476/h563 + F103 DMA
OpenAI Deck ESP32-S3 example + Potentiometer stimulus device
Add a 12-channel RP2040 DMA transfer engine: READ_ADDR/WRITE_ADDR/ TRANS_COUNT/CTRL_TRIG with the AL1/AL2/AL3 aliased register windows, memory-to-memory byte movement over the chip bus (permanent TREQ), address increment with optional RING wrap, per-beat TRANS_COUNT decrement, CHAIN_TO handoff, MULTI_CHAN_TRIGGER, CHAN_ABORT, and INTR/INTE0/INTF0/INTS0/INTE1/INTF1/INTS1 aggregation onto DMA_IRQ_0 (NVIC 11) and DMA_IRQ_1 (NVIC 12). Wire it into rp2040.yaml at 0x50000000 and the rp2040 factory path. Refs #577
Drive the DMA over the real from_config RP2040 bus with the peripheral walk live, executing Thumb machine code. Asserts byte-exact M2M movement with address increment and TRANS_COUNT drain, a two-channel CHAIN_TO handoff, and INTS0 completion IRQ delivery through NVIC (DMA_IRQ_0, exception 27) with ISR acknowledge. Refs #577
The F401 reused the F1 FLASH and L4 PWR peripheral models, whose family-specific reset words are wrong for F4 silicon, and the shared F4 RCC dropped the HSITRIM default from RCC_CR. - RCC F4Rcc: CR reset 0x03 -> 0x0000_0083 (HSITRIM=0x10 default, bits 7:3). Shared with F407; correct for both parts. - FLASH: add isolated Stm32F4 layout (ACR=0 no F1 prefetch default, CR LOCK at bit 31, OPTCR present) selected by profile stm32f4. - PWR: add PwrF4 (only PWR_CR/PWR_CSR exist, both reset 0; VOSRDY asserted on scale select) selected by profile stm32f4. Refs #576
- flash/pwr profiles set to stm32f4 (were f1 / L4 default). - gpioa/gpiob reset_moder/ospeedr/pupdr set to the SWD/JTAG pin defaults per the STM32F401 SVD (RM0368 section 8.4). Refs #576
Pins RCC/FLASH/PWR/GPIO reset values to the STM32F401 SVD oracle (cross-read against RM0368), mirroring the h563 conformance test. Closes #576
Add a StreamDma peripheral for the STM32F4 stream-based DMA (RM0090 §10), distinct from the F1/L4 channel-based Dma1: two controllers with 8 streams each, split LISR/HISR + LIFCR/HIFCR flag registers, per-stream config (SxCR/SxNDTR/SxPAR/SxM0AR/SxM1AR/SxFCR), CHSEL, circular and double-buffer modes, and per-stream NVIC vectors. The model reuses the F1 Dma1 scheduler-migration machinery (scheduler_mode predicate, one service_stream_once body shared by both drive modes, take_scheduled_events/on_event chain) but forks the register layout, since the stream MMIO map and flag geometry share nothing with the channel model. Modeled: stream enable latch, NDTR countdown, PINC/MINC increment, HTIF/TCIF, per-stream IRQ via HTIE/TCIE, circular + double-buffer reload, and the RM0090 §10.3.3 constraint that memory-to-memory is DMA2-only. FIFO/burst pacing and priority arbitration are documented deferrals. Wire DMA1 (0x40026000) and DMA2 (0x40026400) into stm32f407.yaml via the generic factory (new stm32f4_dma type, dma2 + stream_irqs config), gated on RCC_AHB1ENR DMA1EN/DMA2EN. Tests: model-level walk-vs-scheduler byte-identical differential (full snapshot + request stream + IRQ pend set, every cycle) under event-scheduler, plus an end-to-end stm32f407_dma_walk_differential integration test driving a real M2M transfer on the from_config bus and asserting the RM0090 observables (enable, NDTR->0, PINC/MINC copy, TCIF latch + LIFCR clear, per-stream NVIC routing, DMA1 M2M blocked). Closes #578
The F401 CR reset correction is confined to F4Rcc; L0Rcc is byte-for-byte untouched. Mirrors the 2026-07-04 L4-only rcc.rs precedent.
Model STM32F407 stream-based DMA controllers (DMA1/DMA2)
RP2040 DMA controller model + fidelity oracle
Fix STM32F401 cold-reset register values (RCC/FLASH/PWR/GPIO)
…deck The S3 fast-boot constructor never called attach_esp32_external_devices, so a manifest's declared external_devices were silently dropped on S3 — the classic ESP32 path wired them, S3 did not. The OpenAI-deck SH1107 (declared on i2c0 @ 0x3D) therefore never attached; the panel only rendered in a test that attached it out-of-band, masking the gap. - attach_esp32_external_devices: build_i2c_external_device wires the I2C OLED type (oled-sh1107) to its declared connection at config.i2c_address; SPI e-paper path unchanged (returns None → falls through). - wasm S3 constructor: call the factory after configure_xtensa_esp32s3, like the classic-ESP32 path, so declared devices are connected. - deck boot test: attach the SH1107 through the factory from a declared manifest (no out-of-band attach) so a pass proves the real app/CLI path renders. Existing S3 configs unaffected: esp32s3-zero/spice-dispenser declare no devices; esp32s3-oled-demo's SSD1306 still renders via the pre-existing 0x3C hardcode.
Wire manifest external_devices on ESP32-S3 (render the SH1107 deck OLED)
The test command's non-rom-boot path called build_esp32_system_from_manifest, which builds a CLASSIC ESP32 (LX6) memory map regardless of chip. Loading an ESP32-S3 ELF there put its XIP segments (0x3c00_0020 / 0x4200_0020) outside the map → 'Memory access violation' at step 0. So S3 labs couldn't run via `labwired test` — and the MCP's run_lab uses that path, blocking every S3 lab from the MCP. Add an is_esp32s3 && !rom_boot branch that mirrors the run command + wasm twin: configure_xtensa_esp32s3 + attach_esp32_external_devices (wires the manifest's SH1107) + fast_boot, routing USB_SERIAL_JTAG to the uart sink. Classic ESP32 and S3 --rom-boot paths are unchanged. Verified: `labwired test` on the OpenAI deck now boots and asserts 'openai-deck-s3: boot' + 'OLED ready' (exit 0).
The test command's non-rom-boot path called build_esp32_system_from_manifest, which builds a CLASSIC ESP32 (LX6) memory map regardless of chip. Loading an ESP32-S3 ELF there put its XIP segments (0x3c00_0020 / 0x4200_0020) outside the map → 'Memory access violation' at step 0. So S3 labs couldn't run via `labwired test` — and the MCP's run_lab uses that path, blocking every S3 lab from the MCP. Add an is_esp32s3 && !rom_boot branch that mirrors the run command + wasm twin: configure_xtensa_esp32s3 + attach_esp32_external_devices (wires the manifest's SH1107) + fast_boot, routing USB_SERIAL_JTAG to the uart sink. Classic ESP32 and S3 --rom-boot paths are unchanged. Verified: `labwired test` on the OpenAI deck now boots and asserts 'openai-deck-s3: boot' + 'OLED ready' (exit 0).
w1ne
force-pushed
the
fix/s3-test-path-boot
branch
from
July 18, 2026 20:58
4b2828d to
44d5e34
Compare
# Conflicts: # configs/chips/stm32f401cdu6.yaml # crates/cli/src/commands/test.rs # crates/core/src/system/xtensa/esp32.rs # crates/core/src/system/xtensa/esp32s3.rs # crates/core/src/system/xtensa/mod.rs # crates/core/tests/e2e_i2c_tmp102.rs # crates/core/tests/esp32s3_oled_profile.rs # crates/core/tests/esp32s3_ssd1306_differential.rs # examples/spice-dispenser/VALIDATION.md
…s3-test-path-boot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug (found dogfooding the deck through the MCP path)
labwired test's non-rom-boot path callsbuild_esp32_system_from_manifest, which builds a classic ESP32 (LX6) memory map regardless of chip. Loading an ESP32-S3 ELF there puts its XIP segments (0x3c00_0020/0x4200_0020) outside the map →Memory access violationat step 0. So no S3 lab could run vialabwired test— and the MCP'srun_labuses exactly that path, so every S3 lab was blocked from the MCP. (labwired run --chipfast-boots S3 fine; onlytestwas missing the branch.)The fix
Add an
is_esp32s3 && !rom_bootbranch that mirrors theruncommand + wasm twin:configure_xtensa_esp32s3+attach_esp32_external_devices(wires the manifest's SH1107) +fast_boot, routingUSB_SERIAL_JTAGto the uart sink. Classic ESP32 and S3--rom-bootpaths are untouched.Verified
labwired teston the OpenAI deck now boots and assertsopenai-deck-s3: boot+OLED ready(exit 0). Previously: memory violation at step 0.