Skip to content

feat(core): opt-in H5 FLASH fidelity gates — program write-buffer/errors + read-while-write (silicon-validated)#335

Merged
w1ne merged 4 commits into
mainfrom
feat/h563-sim-fidelity
Jun 22, 2026
Merged

feat(core): opt-in H5 FLASH fidelity gates — program write-buffer/errors + read-while-write (silicon-validated)#335
w1ne merged 4 commits into
mainfrom
feat/h563-sim-fidelity

Conversation

@w1ne

@w1ne w1ne commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Adds two opt-in STM32H5 FLASH fidelity gates that make the simulator refuse the kinds of operations real H563 silicon rejects, so firmware bugs that the forgiving model used to pass become visible. Both default OFF (per-peripheral config: keys), so every existing example and chip is byte-identical; a chip/example opts in explicitly.

Both gates were validated against a real NUCLEO-H563ZI over SWD, and the silicon corrected the datasheet-from-memory model on several points (recorded below).

1. Program write-buffer + error fidelity (config: { error_flags: true })

Models the H5 quad-word programming path as it actually behaves on silicon, rather than writing every store straight through:

  • Sub-quad-word writes accumulate in a 16-byte write buffer and set WBNE; nothing commits until a full aligned quad-word is present, which then commits and sets EOP.
  • A misaligned/inconsistent quad-word (a program run that does not align to a single 16-byte slot) raises INCERR alone and commits nothing.
  • Program-over-not-erased is allowed (no error); the result is the bitwise AND of old and new, since flash only flips 1 to 0.
  • Status flags are cleared via NSCCR (0x30); writing NSSR does not clear them.

Silicon corrections captured during validation: the first cut raised PGSERR on misaligned (silicon raises INCERR alone), rejected program-over-not-erased with PGSERR (silicon allows it and ANDs), and made NSSR write-to-clear (silicon clears via NSCCR). All three were wrong and are now fixed to match the chip.

2. Read-while-write fault (config: { read_while_write: true })

On real H5 you cannot fetch/read from a flash bank while that same bank is being erased or programmed. When enabled, a sector erase whose target physical bank equals the bank the CPU is executing from faults (SWAP_BANK-aware), instead of silently succeeding. Cross-bank operations (execute from the active bank, erase/program the inactive one) proceed unchanged. This forces firmware that erases a sector of its own bank to run the flash routine from SRAM, as silicon requires.

Scope notes

  • WRPERR is not raised (no per-region write-protection modeled yet); the constant is defined but unset.
  • Program-commit RWW is not gated (only erase); the H5 program path is direct bus writes with no FlashOp to hook, and the erase gate already covers the boot-state-sector hazard.

Companion to the udslib examples/h563_uds_bootloader/ work (w1ne/udslib#64); these gates underpin upcoming firmware-correctness hardening.

Gates: cargo test -p labwired-core --lib 1427 passed; cargo fmt --check and cargo clippy -D warnings clean.

w1ne added 4 commits June 22, 2026 22:16
…ERR)

Add an opt-in fidelity gate to the STM32H5 FLASH model. When enabled via
the flash peripheral config (error_flags: true), a program (a write into
the flash region) is validated against RM0481 §7 silicon programming
rules before it commits: a non-16-byte (quad-word) aligned target sets
PGSERR + INCERR, and a target not in the erased (0xFF) state sets PGSERR.
On a violation the NSSR sticky flags are set and the write is rejected
(does not store), matching real silicon. NSSR error bits are W1C.

Default off: with the gate off the flash-region write path is
byte-identical to before (every program commits, no flag), so existing
tests and configs are unchanged. The gate is a no-op on non-H5 layouts.

WRPERR is scoped out: the model does not yet track per-region write
protection, so raising it would be inaccurate.

The check is keyed to the flash register peripheral so it can set NSSR.
The bus caches the gated flash index in rebuild_peripheral_ranges and
consults it on flash-region byte writes (the single write_u8 funnel that
u16/u32 region writes decompose into).
The opt-in H5 program-fidelity gate was built from datasheet guesses that
silicon testing on a NUCLEO-H563ZI disproved. Replace the per-byte alignment
reject with the verified write-buffer state machine:

- Track a single pending 16-byte quad-word buffer (aligned base + which bytes
  written + buffered values), gated on NSCR.PG.
- Partial quad-word sets WBNE (live status), commits nothing, no error.
- A full aligned quad-word commits as the bitwise AND of new & existing
  (flash flips only 1->0), sets EOP, clears WBNE. Program-over-not-erased is
  allowed (the AND models it) — not PGSERR.
- A misaligned/inconsistent run (first byte off the 16-aligned base, or a jump
  to another quad-word before the current one completes) raises INCERR alone
  and commits nothing.
- Flags clear via NSCCR (0x30) W1C; writing NSSR no longer clears.
- The bus owns the flash backing memory, so h5_program_byte returns an
  H5ProgAction the bus acts on; the AND-commit happens on the bus side.

Gate-off path stays byte-identical (the bus never calls the state machine when
flash_error_flags_idx is None). Unit + bus-level tests rewritten to mirror the
silicon trace.
On real STM32H563 silicon (RM0481 §7) a flash bank cannot be fetched
from or read while that same bank is being erased or programmed; code
executing from that bank stalls and cannot make progress, so production
flash routines run from SRAM. The forgiving sim let an OTA bootloader
erase the boot-state sector of the bank it executes from, which works in
sim but stalls/faults on the real part.

Add an opt-in per-peripheral gate (config { read_while_write: true },
default off, H5-only) that mirrors the existing error_flags opt-in. When
on, applying an EraseSector whose target physical bank is the one the CPU
is currently executing from raises SimulationError::Other with a reason
string telling the firmware to run the routine from SRAM, instead of
silently succeeding. The bank comparison is SWAP_BANK-aware: both the
BKSEL logical erase bank and PC's bank are translated to a physical bank
through the active swap state (toggled when SwapAndReset is applied). PC
outside the flash region (the SRAM-resident routine) never faults, and a
cross-bank erase proceeds unchanged. Gate off and every non-H5 chip stay
byte-identical.
…+ drift_ack

Ran the FLASH program/erase behavioural live-diff on the NUCLEO-H563ZI over
SWD: write buffer (WBNE) commits a 16-byte quad-word with EOP only on
completion, misaligned quad-word raises INCERR alone and commits nothing,
program-over-not-erased ANDs the bits with no PGSERR, and flags clear via
NSCCR not NSSR. The opt-in H5 program error-flag and read-while-write
fidelity gates were corrected to match this capture. Bump last_capture and
drift_ack to 2026-06-22 and regenerate VALIDATION_STATUS.md.
@w1ne w1ne merged commit 5d019c8 into main Jun 22, 2026
2 checks passed
@w1ne w1ne deleted the feat/h563-sim-fidelity branch June 22, 2026 21:43
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