Skip to content

fix: write per 64KB block so a dropped probe costs one block, not the chip - #14

Merged
jackulau merged 2 commits into
mainfrom
fix/resumable-per-block-write
Jul 16, 2026
Merged

jackulau merged 2 commits into
mainfrom
fix/resumable-per-block-write

Conversation

@jackulau

Copy link
Copy Markdown
Owner

The bug

write_chip erased every block, then programmed, then verified:

2. erase all 512 blocks     <- chip is now 100% blank
3. program page-by-page      <- minutes pass
4. verify by re-reading 32MB <- more minutes

The whole chip sits at 0xFF for the entire programming pass. Lose contact there and the board has no BIOS and cannot boot to fix itself. On a hand-held probe against a WSON8 part that window is minutes long, and losing contact mid-operation is not hypothetical: it already produced a backup that was 67% dropout zeros.

The fix

Each 64KB block is erased, programmed and read back as one unit before the next block is touched.

before after
Blank window whole chip, minutes one 64KB block, ~2s
Dropout costs everything one block
Resumable no yes
Re-run full re-erase + rewrite skips finished blocks
Total time same

Resume falls out of it

Blocks already holding the target bytes are skipped, so re-running an interrupted write continues where it stopped. Running the same write twice is harmless, and it self-heals a half-written chip.

The chip itself is the progress record - no sidecar file to go stale or lie about what was done.

A failed block retries WRITE_BLOCK_ATTEMPTS times, re-erasing first so a half-programmed block cannot AND stale zero bits into the result.

Costs nothing

  • The skip baseline is the mandatory backup read, which was already happening.
  • With skip_backup there is no baseline and no skip. It does not go read the chip anyway to synthesise one: the caller opted out of a whole-chip read. Losing resume is the cost of that flag.
  • The whole-chip verify pass is gone. Every block is read back after programming, or shown by the liveness-checked baseline to already match. A final pass would re-read 32MB the loop just read, doubling the time the probe must stay put for no new information.

The test fake was lying

LoopbackFlash modelled sector erase but ignored BLOCK_ERASE (0xD8/0xDC) - write_chip's primary erase opcode. Any end-to-end write test against it was passing on stale data the program step happened to AND correctly.

It now models block erase, and logs (opcode, address) so tests can assert the sequence, not just the final bytes. Both orderings end with identical chip contents, so bytes alone cannot catch a regression in the blank window.

Tests

520 pass, 0 fail. Clippy and fmt clean.

Both new tests were mutation-checked rather than trusted:

  • write_chip_skips_blocks_that_already_hold_the_target_image - defeating the skip makes it fail (all 16 blocks erased instead of 13)
  • write_chip_erases_only_the_block_it_is_about_to_program - erasing one block ahead makes it fail

jackulau added 2 commits July 15, 2026 22:41
… chip

write_chip erased EVERY block, then programmed, then verified. That leaves the
whole chip at 0xFF for the entire programming pass: lose contact there and the
board has no BIOS and cannot boot to fix itself. On a hand-held probe against a
WSON8 part that window is minutes long, and losing contact mid-read is already
a thing that happens in practice.

Now each 64KB block is erased, programmed and read back as one unit before the
next block is touched. The erased window is one block for the ~2s it takes to
refill it; everything outside it is either already-correct new data or intact
old data.

The same restructure makes a write resumable. Blocks that already hold the
target bytes are skipped entirely, so re-running an interrupted write continues
from where it stopped rather than starting over, and running the same write
twice is harmless. The chip itself is the progress record - no sidecar state to
go stale or lie. A failed block retries WRITE_BLOCK_ATTEMPTS times, re-erasing
first so a half-programmed block cannot AND stale zero bits into the result.

The skip baseline is the mandatory backup read, which was already happening, so
the check costs no extra probe time. With skip_backup there is no baseline and
no skip: the caller opted out of a whole-chip read, so this does not go read the
chip anyway to synthesise one. Losing resume is the cost of that flag.

The whole-chip verify pass is gone. Every block is now read back after
programming, or shown by the liveness-checked baseline to already match, so a
final pass would re-read 32MB the loop just read and double the time the probe
must stay put for no new information.

Also fixes the test fake: LoopbackFlash modelled sector erase but ignored
BLOCK_ERASE (0xD8/0xDC), which is write_chip's PRIMARY erase opcode. Any
end-to-end write test against it was passing on stale data that the program step
happened to AND correctly. It now models block erase and logs (opcode, address)
so tests can assert the erase/program SEQUENCE, not just the final bytes - both
orderings end with identical chip contents, so bytes alone cannot catch a
regression in the blank window.

Both new tests were mutation-checked: defeating the skip fails the resume test
(all 16 blocks erased instead of 13), and erasing one block ahead fails the
blank-window test.

520 tests pass; clippy and fmt clean.

Goal: 020-soic-debug-identify-fix-ship
Goal: 020-soic-debug-identify-fix-ship
@jackulau
jackulau merged commit 3300324 into main Jul 16, 2026
1 check passed
@jackulau
jackulau deleted the fix/resumable-per-block-write branch July 16, 2026 03:45
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