Structured board observations for the two-AI handoff. Exact mailbox hex, pass/fail per word, and where/how it diverged from the host golden. Board truth lives here.
Board: EBAZ4205, XC7Z010-1CLG400I. JTAG Digilent HS3; UART CH340 /dev/ebaz-uart.
Loader: U-Boot fpga loadb over ymodem. Mailbox observed at PS AXI-GPIO
0x41200000 via U-Boot md.
Bitstream: build/dfx/dfx.runs/impl_1/dfx_top.bit (full, static NEORV32 shell +
rm_uart_stream), DFX build scripts/vivado_dfx_uart_stream_build.tcl. Firmware
sw/autoehw_board_mbox.c (budget=16, frames=8, seed=0xC0DE), baked IMEM
(text=1880 B, verify-image OK). OOC gate: all three modules 0 err/0 crit.
FCLK0=50 preflight (mandatory): board-set-fclk50.py → before
FPGA0_CLK_CTRL=0x00200400, after 0x00200a00, PASS (50 MHz pinned).
Load: fpga loadb OK (2 083 740 bytes, part 7z010clg400). NEORV32 booted and
published the mailbox lifecycle; the board-only republish loop cycles the six
evidence words steadily (~4.5 s/word, stable over >180 s of polling).
| # | Observed (steady) | Golden | Verdict |
|---|---|---|---|
| 1 | 0xA7000000 |
0xA7000000 |
✅ reached main |
| 2 | 0xA8001008 |
0xA8001008 |
✅ budget=16, frames=8 |
| 3 | 0xA90F05B7 |
0xA90F05B7 |
|
| 4 | 0xAA000020 |
0xAA013020 |
❌ train 0/32 (golden 19/32) |
| 5 | 0xAB000020 |
0xAB011020 |
❌ holdout 0/32 (golden 17/32) |
| 6 | 0xAC000200 |
0xAC000200 |
✅ evals=512 |
The autonomous control plane works on silicon; the fabric evaluator does not
complete. NEORV32 ran the full train-only search loop (512 evals), published the
lifecycle, and the PS reads the mailbox — control plane, XBUS→mailbox, and
FCLK0=50 signoff are all good. But every hardware eval_frame returned 0:
train and holdout both collapse to 0/32.
- The word-3 champion match is not independent evidence the evaluator worked.
With all evals scoring 0, the search keeps the first candidate (nothing beats an
initial best of 0). For seed
0xC0DEthe firstrandom_configis exactly phase=15/thr=−73/maj=5, which also happens to be the host's true champion (verified host-side). So the champion degenerated to the first config and coincides with golden; the real evaluator evidence is the pass counts, which are 0. - The run took ~2.5 min (mailbox stuck at word 2
0xA8001008for ~155 s before the lifecycle completed). That is the signature of the MMIO backend hitting its fulltimeout=1,000,000poll on every frame: the island'sSTATUS.DONEbit is never observed, so eachautoehw_mmio_eval_frametimes out and returns 0.
Root-cause locus: the tpu_rp ↔ uart_stream_island_regs ↔
uart_stream_eval_core completion handshake over the real neorv32_soc_dfx
XBUS. It passes the iverilog testbench (tb_tpu_rp_uart_stream, status=0x6 =
done+pass) but not against the actual soc_dfx XBUS arbiter (registered
per-transaction ack ack_q <= cyc & tpu_stb & !ack_q; wr_en gated by
!ack_q and xbus_sel != 0; start_pulse/busy edge trigger). This is a
board-only, sim-invisible bug — the same class as the OOC lesson (the iverilog
tb is not the real bus master), and it belongs to the RTL author.
- The soc_dfx XBUS holds
xbus_cychigh across accesses and pulsestpu_stbper transfer; the ad-hocack_q <= cyc & tpu_stb & !ack_qmay not present a clean Wishbone ack/handshake to the master the way the proven base RM (wb_tpu_accel, a real Wishbone slave) does — leading to writes (START) not landing or the STATUS read not returningdone_latched. - Prefer mirroring the base RM's known-good Wishbone-slave handshake rather than
the bespoke
ack_q/wr_en && !ack_qgating. - Confirm
xbus_selis non-zero on NEORV32 word writes (the island gates writes onxbus_sel != 0; the base RM ignoressel).
- Full DFX build +
fpga loadbof a fresh single-RM design works. - NEORV32 static shell (reused from zynq-ehw, copied read-only) +
rm_uart_streamRM elaborate, place, route (DRC 0 err) and run. - The autonomous train-only loop, mailbox publish, and PS observability all work.
- FCLK0=50 preflight is correct.
Not established: the fabric evaluator's per-frame result (blocked by the handshake bug above). Full golden match is pending that RTL fix + rebuild + re-verify.
Bitstream rebuilt with ChatGPT's tpu_rp handshake fix (commit cbde38d:
pending-transaction + registered xbus_ack/xbus_dat_r, writes no longer gated
on xbus_sel != 0). Same firmware/IMEM (unchanged). New dfx_top.bit
md5 bd8b55b5…. OOC re-verified 0 err/0 crit (3490 LUT / 909 FF). FCLK0=50
preflight PASS (0x00200a00). fpga loadb OK. No reset/power-cycle needed
(board was already at the U-Boot prompt).
| # | Observed (steady) | Golden | Verdict |
|---|---|---|---|
| 1 | 0xA7000000 |
0xA7000000 |
✅ |
| 2 | 0xA8001008 |
0xA8001008 |
✅ |
| 3 | 0xA91E056F (phase=30, maj=5, thr=+111) |
0xA90F05B7 (phase=15, maj=5, thr=−73) |
❌ different champion |
| 4 | 0xAA008020 (train 8/32) |
0xAA013020 (19/32) |
❌ |
| 5 | 0xAB006020 (holdout 6/32) |
0xAB011020 (17/32) |
❌ |
| 6 | 0xAC000200 (evals=512) |
0xAC000200 |
✅ |
The handshake fix worked: the island now completes every eval (search runs
fast, non-zero results, steady republish) — the timeout/DONE-never-asserts
failure from smoke #1 is gone. New problem: the on-chip evaluator's per-frame
results differ slightly from the oracle, deterministically.
Decisive cross-check (host oracle vs island, for the island's own champion phase=30/thr=111/maj=5, frames=8):
| metric | island (on silicon) | host oracle | |
|---|---|---|---|
| train | 8/32 | 10/32 | off by 2 frames |
| holdout | 6/32 | 6/32 | exact match |
So the hardware eval ≈ oracle but flips a few frames on some inputs (holdout for this config matches exactly; train is off by 2). That small per-frame error shifts the search argmax, so the on-chip search selects phase=30 instead of the golden phase=15. The champion, train, and holdout all differ because the evaluator is slightly wrong per-frame, not because the search or handshake is broken.
Root cause locus: rtl/uart_stream_eval_core.v — a simulation-vs-synthesis
arithmetic discrepancy. The iverilog 144-vector gate (tb_uart_stream_eval_core)
matches the Python oracle bit-exactly, but Vivado-synthesized hardware diverges on
some frames. Prime suspects (the same non-power-of-2 ops flagged in the OOC
resource note): the variable modulo tmp_state1 % ((2*noise_span)+1) and the
round_div(_, 1000) divides, and mixed signed/unsigned width handling in the
signal/noise/threshold arithmetic. Behavioral iverilog and Vivado can differ on
these.
Suggested fixes (RTL owner):
- Rework the eval arithmetic to be synthesis-deterministic: explicit signedness
and widths; avoid mixed signed/unsigned
%and/; consider power-of-two scaling so modulo/divide become masks/shifts (also shrinks the 3.5k-LUT core). - Strengthen the gate beyond behavioral iverilog: add a post-synthesis
(gate-level) simulation of
eval_coreagainst the oracle vectors, or a wider randomized vector sweep, so this class of sim-vs-synth divergence is caught on the host before the board.
- The XBUS handshake fix is confirmed good: island evals complete, search is fast, results are non-zero and deterministic; full DFX reload from an existing U-Boot prompt (no reset) works.
- Remaining gap is purely the eval_core arithmetic's sim-vs-synth fidelity.
Bitstream rebuilt with ChatGPT's compact deterministic modulo (commit 4b9bb0b:
Vivado-inferred % with explicit unsigned fixed-width operands, keeping the
signed/width fixes) + a Claude pblock enlargement (RP extended into clock region
X1Y1 → full right half, 2200 slices; the RM did not pack into the single-region
1100-slice pblock — 1885 LUT fit but needed ~1170 slices). New dfx_top.bit
md5 2df5de81…, DRC 0 err. Reset to U-Boot from a Buildroot-Linux state (board
had auto-booted during the long builds), FCLK0=50 preflight PASS
(0x00200400→0x00200a00), fpga loadb OK.
| # | Observed (steady) | Golden | Verdict |
|---|---|---|---|
| 1 | 0xA7000000 |
0xA7000000 |
✅ reached main |
| 2 | 0xA8001008 |
0xA8001008 |
✅ budget=16, frames=8 |
| 3 | 0xA90F05B7 |
0xA90F05B7 |
✅ champion phase=15/maj=5/thr=−73 |
| 4 | 0xAA013020 |
0xAA013020 |
✅ train 19/32 |
| 5 | 0xAB011020 |
0xAB011020 |
✅ holdout 17/32 |
| 6 | 0xAC000200 |
0xAC000200 |
✅ evals=512 |
Steady republish, 2+ full cycles, all six words match the host golden, no extras. RESULT: ALL 6 GOLDEN MATCHED.
The uart_stream autonomous train-only loop runs end-to-end on the EBAZ4205 and
its result is bit-exact to the host oracle: NEORV32 (no PC in the
candidate-selection or fitness loop) drives the fabric evaluator island over XBUS,
runs 512 evals, selects the golden champion (phase=15), and reports train 19/32 /
holdout 17/32 — matching sim/uart_stream_v1.py exactly. Control plane, fabric
evaluator, XBUS handshake, mailbox, FCLK0=50 signoff, and the reused NEORV32 DFX
shell are all confirmed on hardware.
This is a foundation/smoke PASS, not the full M1 Claim A/C PASS. Still pending for full M1: measured evals/sec-derived long-run budget, equal-budget random baseline evidence, replay bundle, champion persistence with write-budget accounting, bad-candidate rejection/recovery, and the eventual multi-hour run.
Lineage of the three fixes it took: smoke #1 (XBUS DONE handshake never asserted) → smoke #2 (handshake fixed; eval arithmetic sim-vs-synth mismatch) → OOC fit-fail (deterministic modulo blew the pblock) → smoke #3 (compact modulo + enlarged pblock → full golden). No hardware damage; no power-cycle required across all attempts.
Bitstream rebuilt with ChatGPT's M1-full scaffold firmware (commit 7d7c911; RTL
untouched, so the smoke-#3 OOC/fit results stand). Firmware text=3560 B,
verify-image OK. New dfx_top.bit md5 8fa3184d…. Board was still at the U-Boot
prompt (no reset needed), FCLK0=50 preflight PASS (0x00200a00 persisted),
fpga loadb OK.
| # | Observed (steady) | Expected | Verdict |
|---|---|---|---|
| 1–6 | A7000000 A8001008 A90F05B7 AA013020 AB011020 AC000200 |
foundation golden, unchanged | ✅ exact |
| 7 | 0xAD00C0DE |
seed=0xC0DE | ✅ exact |
| 8 | 0xAE007C7F |
tag 0xAE + plausible nonzero (board-measured, not host fake) | ✅ 31 871 evals/sec measured on the real fabric path |
| 9 | 0xAF011020 |
random equal-budget holdout 17/32 | ✅ exact |
| 10 | 0xB00013E8 |
write_counter=1 / budget=1000 | ✅ exact |
| 11 | 0xB10F05B7 |
persisted champion stub = phase15/maj5/thr-73 | ✅ exact |
Steady republish (11-word carousel, ~4.5 s/word), 2+ full cycles, no extras.
This is the M1-full scaffold, not full M1 PASS:
AEgives the first real measured evals/sec = 31 871 (512 evals ≈ 16 ms on the fabric evaluator incl. MMIO overhead) — the number the M1 long-run window must be derived from.- The random equal-budget baseline ties the champion on holdout (17/32 vs 17/32) — so no beats-random claim; the benchmark/search needs headroom before Claim A/C can be tested (expected: this scaffold run uses the tiny budget-16 smoke configuration).
- Champion persistence is an in-RAM stub with a write-budget counter; it does not yet survive reset/reload.
- Host-side
run_log/write_budget/replay_bundleJSON fixtures exist and are schema-versioned; the board does not emit them itself yet.
Still pending for full M1 (tech_report §3): multi-hour run with derived budget, persistence across reset/reload, board-side replay bundle, beats-random on holdout, bad-candidate rejection + recovery.
M1-full persistence-restore scaffold — framebuf champion restore (2026-07-08) — PASS ✅ (scaffold scope)
Bitstream rebuilt with ChatGPT's framebuf restore scaffold (commit 92ac6b4; RTL
untouched — OOC/fit stands). Firmware verify-image OK. dfx_top.bit md5
b088a2d0…. FCLK0=50 preflight PASS (persisted 0x00200a00), fpga loadb OK
from the existing U-Boot prompt.
Phase 1 — default (no external store): all 13 words matched, tail
0xB2000001 0xB3000000 (no store found, no restore). Measured evals/sec this
image: 0xAE007CEE = 31 982 (consistent with the previous 31 871).
Phase 2 — seeded restore across a logic reset:
- Seeded the champion-store record from the PS via U-Boot into the static
axil_framebuf(mw.l 0x40000000…×5: magicCHMP, meta, config0x000F05B7, budget 1000, checksum;mdreadback verified). - Pulsed
FPGA_RST_CTRL(SLCR unlock →0xF8000240=0xF → 0x0) — the proven zynq-ehw EHW-5.4b logic-restart that reboots NEORV32 without a bitstream reload and preserves BRAM contents. - NEORV32 rebooted, re-ran the search, validated magic+checksum on the store at
0xF5000000, and restored the champion: all 13 words matched with tail0xB2010101 0xB30F05B7(restore-success status + restored champion phase=15/maj=5/thr=−73). No extras, steady carousel.
This proves the restore ABI across a logic reset: a champion record written
by the PS into the static-shell framebuf survives FPGA_RST_CTRL and is
validated (magic + checksum + config-validity) and restored by firmware on
reboot. It is not non-volatile persistence — the record lives in static-shell
RAM and would not survive a full reconfiguration or power cycle. NV champion
storage (QSPI/SD/NAND) with a real write budget remains open for full M1, along
with: multi-hour run with derived budget, board-side replay bundle emission,
beats-random headroom, and bad-candidate rejection + recovery.
Bitstream rebuilt with ChatGPT's rejection/recovery scaffold (commit e8ad35c;
RTL untouched — OOC/fit stands). verify-image OK, dfx_top.bit md5 5598d4dc….
FCLK0=50 preflight PASS, fpga loadb from the existing U-Boot prompt.
First 13 words unchanged (default-mode tail B2000001/B3000000; measured
0xAE0076E4 = 30 436 evals/sec, consistent with the 30–32k band). New tail:
| # | Observed | Meaning | Verdict |
|---|---|---|---|
| 14 | 0xB4010101 |
injected illegal-but-safe config (phase=99/thr=0/maj=4) rejected by the safety gate before eval | ✅ exact |
| 15 | 0xB5010101 |
post-rejection recovery probe: golden champion re-run through the real fabric backend (T0 frame 2) passes — known-good path intact | ✅ exact |
Steady 15-word carousel, 2+ cycles, no extras.
This is the event/recovery scaffold: a config-level safety gate rejects an invalid candidate pre-eval, and a known-good probe confirms the evaluator path is undamaged afterwards. It is not the full M1 recovery claim (raw-bitstream bad candidate + ICAP/golden reload without power-cycle). Full-M1 remainder: NV champion store + real write budget, multi-hour run with derived budget, board-side replay bundle emission, beats-random headroom.
Bitstream rebuilt with ChatGPT's paged-mailbox scaffold (commit 388d7c7; RTL
untouched — OOC/fit stands). verify-image OK, dfx_top.bit md5 4e259022….
FCLK0=50 preflight PASS, fpga loadb from the existing U-Boot prompt.
Legacy 15-word prefix identical to the previous round (this image's
0xAE007B48 = 31 560 evals/sec, in the stable 30–32k band). Typed summary
page follows:
C0010006 page header: page_id=1, count=6
C101000F C1007B48 C1011020 data: champion, evals/sec, holdout
C1000001 C1010101 C1010101 data: restore-status, reject, recovery
C2A51A87 checksum (recomputed over board payloads: OK)
python3 host/check_m1_mailbox.py < observed_words.txt → PASS (handles the
board-varying AE payload and recomputes the page checksum — the C2 word differs
from the host fixture exactly because the board's measured evals/sec differs).
The soc_dfx mailbox is a latched register, so two consecutive identical words
are invisible as a transition — the summary page legitimately publishes
C1010101 twice (reject + recovery payloads are equal), and a distinct-value
poller collapses them to one. Resolution: dwell-time analysis — every word dwells
~4.8 s, while C1010101 dwelt 8.5 s / 9.8 s across two independent cycles
(≈2× single dwell), proving the duplicate publish. The full 23-word sequence
reconstructed with that duplicate passes the checker. Recommendation for the
next page revision: fold a 1–2-bit sequence counter into each C1 word (payload
is 24 bits; top bits are free) so consecutive words are never byte-identical and
a plain latched-register poll suffices.
Paged, checksummed telemetry ABI board-verified (legacy prefix + typed page). This closes the mailbox tag-space concern. Full-M1 remainder unchanged: NV champion store + real write budget, multi-hour run with derived budget, board-side replay bundle emission, beats-random headroom.
Bitstream rebuilt with ChatGPT's sequence-counter fix (commit babfd66; RTL
untouched). verify-image OK, dfx_top.bit md5 a74086d9…. FCLK0=50 preflight
PASS, fpga loadb from the existing U-Boot prompt.
legacy 15 unchanged (AE007CEE = 31 982 evals/sec)
C0010006 header
C101000F C1407CEE C1811020 C1C00001 C1 data, seq 0,1,2,3
C1010101 C1410101 C1 data, seq 0,1
C2A51D21 checksum (recomputed over decoded payloads: OK)
host/check_m1_mailbox.py → PASS (seq pattern 0,1,2,3,0,1 verified, payloads
decoded, checksum recomputed against the board's measured AE).
The previous round's measurement problem is confirmed fixed on silicon: the
formerly byte-identical consecutive pair (C1010101 C1010101) is now
C1010101 C1410101 — no adjacent identical words, so a plain
distinct-value poll of the latched mailbox captures the complete carousel with
no dwell-time analysis needed. Telemetry ABI is now robust for future pages.
Bitstream rebuilt with ChatGPT's budget-page scaffold (commit 354022e; RTL
untouched). verify-image OK, dfx_top.bit md5 61d20b9c…. FCLK0=50 preflight
PASS, fpga loadb from the existing U-Boot prompt.
Legacy 15 + page 1 unchanged in structure (this image 0xAE00788E =
30 862 evals/sec). New page 2 (id=2, 6 payloads + checksum):
C0020006 header: page_id=2, count=6
C1020078 version=2, target=120 min
C1400020 32 train frame-evals per candidate
C1BE99C0 C1C00034 target_evals low/high 22-bit chunks
C129F4CE C1400001 cand_budget low/high 22-bit chunks
C2B01F82 checksum (recomputed: OK)
Derivation verified exactly against the board's own measurement (decoded by hand, independently of the checker):
target_evals= (0x34 << 22) | 0x3E99C0 = 222 206 400 = 30 862 × 7200 ✅candidate_budget= (1 << 22) | 0x29F4CE = 6 943 950 = 222 206 400 / 32 ✅
host/check_m1_mailbox.py → PASS (recomputes page 2 from the observed AE).
Sequence counters held (plain distinct-value poll captured all 31 words).
The board now derives its 2-hour long-run window from its own measured throughput — the M1 PASS requirement "run window derived from measured evals/sec × a budget, not an arbitrary wall-clock" has its on-silicon arithmetic verified. The actual multi-hour run itself remains the open item, along with NV champion store, board-side replay bundle emission, and beats-random headroom.
Long-run image built with -DAUTOEHW_BOARD_LONGRUN_MODE. verify-image OK,
dfx_top.bit md5 3f757815…. Board auto-booted to Linux between builds →
recovered to U-Boot via JTAG SLCR reset; FCLK0=50 preflight PASS; fpga loadb.
Monitored by scratchpad/longrun_monitor.py (stuck-alert 90 s, CH340-tolerant,
final-carousel autodetect).
The board first emitted the normal 31-word smoke, derived its own 2-hour candidate budget from its measured throughput (page 2), then ran a train-only search of that size against the fabric evaluator, publishing page-3 heartbeats live (~every 10 s). PC was pure observer — no candidate selection, no fitness.
- Duration: ~116 min (T+6950 s), 845 mailbox transitions logged.
- Heartbeat cadence: steady 8–10 s throughout; 0 stuck alerts, 0 serial errors.
- Completed: generation = 6,968,250 candidates, evals = 222,984,000
frame-evals (= 6,968,250 × 32 exactly; monotonic; final marker
0x0300F1present). Bothcheck_m1_mailbox.pyandcheck_longrun_live_mailbox.py --require-final→ PASS.
| phase | thr | maj | train | holdout | |
|---|---|---|---|---|---|
| longrun champion (final page-3 + tail) | 16 | 56 | 5 | 22/32 | 17/32 |
| smoke champion (budget-16) | 15 | −73 | 5 | 19/32 | 17/32 |
Host oracle cross-check of the longrun champion: train 22/32, holdout
17/32 — bit-exact to the board's aa016020 / ab011020. The 2-hour search
genuinely improved train fitness (19→22) with far more budget.
- ✅ The multi-hour autonomous run is silicon-verified: long-running, PC-free candidate selection and fitness, budget derived from measured throughput (not arbitrary wall-clock), live telemetry that distinguishes progress from stuck (the 845-heartbeat monotonic log with 0 alerts is that evidence), champion bit-exact to the oracle.
- ❌ Not beats-random on holdout. Train improved 19→22 but holdout stayed 17→17 (same as the random baseline). This is expected and now quantified: the config space is only 32×256×3 = 24,576 points, and 6.97 M candidates covers it ~280×, so both the search and random find the holdout optimum; train optimization does not transfer to holdout. This is a clean train/holdout-gap demonstration, and the evals/sec-driven quantification is itself the key input for designing a harder benchmark where beats-random becomes testable.
- Persistence remains the framebuf/logic-reset restore ABI (not NV); replay bundles remain host-side. Those + a headroom benchmark are the remaining full-M1 (Claim A/C) items.
M1-full v2 headroom A/B on-board (MMIO decode path) (2026-07-09) — PASS ✅ (plumbing smoke, not the verdict)
Board image built with -DAUTOEHW_BOARD_V2_AB_MODE (firmware sources +
sw/uart_stream_v2.c + sw/autoehw_firmware_v2.c). No RTL change — the v2
39-bit genome is decoded in firmware to a per-condition effective sampler config
that drives the already-board-verified uart_stream MMIO island. dfx_top.bit
md5 4c35407f…, verify-image OK. FCLK0=50 preflight PASS, fpga loadb.
- v1 31-word prefix:
check_m1_mailbox.pyPASS. - v2 A/B pages 4 (GA arm) and 5 (random arm), reconstructed positionally from the carousel, are byte-for-byte identical to the host oracle's pages:
| arm | genome (board) | genome (host) | train | holdout |
|---|---|---|---|---|
| GA (page 4) | 0x4e85cbc206 |
0x4e85cbc206 |
1/16 | 2/16 |
| random (page 5) | 0x6cbfb15fd8 |
0x6cbfb15fd8 |
2/16 | 2/16 |
So the whole v2 chain — 39-bit genome → firmware decode → real fabric MMIO evaluator → same-boot A/B telemetry — runs on silicon bit-exact to the model. The low-risk (no-RTL) path works.
This is the v2 plumbing smoke, not the beats-random verdict. The A/B ran at the trivial smoke budget (16 candidates, 4 frames); GA 1/16 vs random 2/16 is pure noise at that budget and carries no comparative meaning. The actual beats-random judgment needs the large-budget (multi-hour) same-boot A/B on the 39-bit space, which is the next run.
check_v2_ab_mailbox.pyvalidates the host-stub's standalone 21-word--v2-ab-mailbox-smokeformat (prefixa7000000 a8001004 …), which the board carousel does not emit — the board appends pages 4/5 after its normal 31-word v1 smoke (frames=8). The correct board gate is the v1 prefix checker plus a byte-match of pages 4/5 against the host golden.- My one-shot collector's global first-seen dedup dropped page 5's data words that are byte-identical to page 4's (both arms share holdout 2/16 → equal payloads across pages). Fixed by reconstructing one positional cycle from the timestamped transition trace (no dedup). Not a board or firmware issue.
Long-run image -DAUTOEHW_BOARD_V2_AB_LONGRUN_MODE, dfx_top.bit md5 6f103810….
Recovered from a full usbipd detach (user re-attached FT232H + CH340) + Linux
auto-boot (JTAG SLCR reset) → U-Boot; FCLK0=50 PASS; fpga loadb.
Calibration fix confirmed on silicon. The previous attempt's budget was
derived from the v1 evals/sec and would have run ~37 h (187 s heartbeats). The
v2-speed-probe fix (commit 15a1d7f, page 8 calibration) was verified live: the
measured heartbeat cadence is now ~10.4 s (median over 75 s), so the run
landed at ~129 min (T+7767 s, 861 transitions, 0 stuck alerts). v2
throughput ≈ 1,658 evals/sec (18.7× slower than v1), exactly as the board-measured
diagnosis predicted.
Final champions decoded positionally from the carousel; both bit-exact to the host v2 oracle:
| arm | genome | train | holdout |
|---|---|---|---|
| GA (page 4) | 0x08d590f3ee |
4/16 | 2/16 |
| random (page 5) | 0x6cbfb15fd8 |
2/16 | 2/16 |
- GA beats random on train, 4/16 vs 2/16 (2×), at equal budget (~373 k candidates/arm, same-boot, same seed rule). This is the first time the GA arm's structure measurably beats random search — the 39-bit headroom space made the comparison meaningful (v1's 24,576-point space could not).
- Holdout is a 2/16 tie → still not beats-random on holdout.
The holdout is only 16 evals (4 conditions × 4 frames). Its resolution is 1/16 = 6.25 %, far coarser than the GA-vs-random gap. The GA's clear train advantage cannot manifest in a 16-sample holdout — a 2/16 tie is inside the quantization noise. The beats-random-on-holdout judgment needs the final champions evaluated on a large holdout set (many frames, e.g. 128–256), so score resolution is finer than the arm gap. The search can stay budget-limited; only the final champion holdout evaluation needs the extra frames.
- ✅ Multi-hour same-boot A/B verified on silicon: correctly calibrated (~2 h, 10 s heartbeat), PC-free, equal-budget, bit-exact to the oracle; GA > random on the optimization objective (train).
- ❌ Not beats-random on holdout — blocked by holdout statistical resolution (16 samples), not by the search. Next drop: high-frame final-champion holdout evaluation (+ optionally a reported noise band) to make the judgment decidable.
M1-full v2 A/B with HIGH-RES holdout — the beats-random verdict (2026-07-10) — DECIDABLE: NOT beats-random; train edge is overfit
Rerun of the multi-hour same-boot A/B with the high-resolution final holdout
(commit 187e83c: final champions evaluated on 4×256 = 1024 holdout samples;
search, equal-budget, heartbeats, firewall unchanged). dfx_top.bit md5
6337a4ef…, calibration verified live (~10.1 s heartbeats), run ≈ 122 min
(anchored by the ymodem log: load 04:19 → final ≈ 06:22), 0 stuck alerts.
| arm | genome | train | holdout | holdout % |
|---|---|---|---|---|
| GA | 0x09571273ce |
4/16 | 60/1024 | 5.86 % |
| random | 0x6cbfb15fd8 |
2/16 | 64/1024 | 6.25 % |
Δ(holdout) = 4/1024 with σ(diff of two binomials at ~6 %, n=1024) ≈ 10.7 → z ≈ 0.4: a statistical tie. The resolution problem from the previous run is fixed (6.25 % quantization → ~0.1 %), so the question is now decidable — and the answer is: the GA's clear train advantage (4/16 vs 2/16, reproduced across two independent runs) does not generalize to holdout.
The train fitness is only 16 deterministic evals (4 conditions × 4 frames)
per candidate. At this task difficulty (~6 % per-frame pass), a 39-bit-genome GA
hill-climbing on a fixed 16-sample signal memorizes those exact frames: the two
runs' GA champions differ (0x08d590f3ee, 0x09571273ce) yet both hit 4/16
train and ~6 % holdout. The random arm's champion is identical across runs
(first-found tie-keeping over a coarse score scale — many genomes tie at 2/16
and the earliest wins, which the budget-16 smoke also found).
Fix direction (relayed): enrich the train signal so fitness reflects generalizable quality — more train frames per candidate (16 → 64–128) and/or multiple stimulus seeds per condition. Throughput cost (~4–8×) is absorbed by the v2 speed-probe calibration (budget shrinks accordingly). Holdout method stays as-is (1024-frame final eval, now proven).
- ✅ Multi-hour autonomous A/B machinery: fully board-verified, calibrated, bit-exact, replayable — the runtime claims stand.
- ✅ GA > random on the optimization objective (train), reproduced twice.
- ❌ Beats-random on holdout: decidably not, with adequate resolution — an honest falsification-in-progress of the benchmark's train-signal design, exactly what the M0 claim discipline is for. Next benchmark iteration targets the train signal.
M1-full v2 A/B with DENSE train signal (2026-07-10) — still not beats-random: random now wins outright at equal budget
Rerun of the multi-hour same-boot A/B with the densified train signal
(commit 9fe5cd8: 64 train frames per condition → train_total = 256 per
candidate; 1024-frame final holdout unchanged; budget/heartbeat/page-8
auto-rescaled by the v2 speed probe). dfx_top.bit md5 7160715f0cb8…
(routed WNS bit-identical to the previous board-verified builds — firmware is
BRAM-INIT-only). FCLK0 pinned 50 MHz (was 125 again), cadence probe GO
(median 10.1 s), run start ≈ 10:01, final carousel from ≈ T+7096 s,
862 logged transitions, 0 stuck alerts / 0 serial errors.
- v2 evals/sec measured 1592 (vs 1658 in the plumbing smoke) → arm budget 22,387 candidates = ⌊1592×7200/(2×256)⌋ exact; evals/arm 5,731,072 = 22,387×256 exact; heartbeat 62 candidates ≈ 10 s; probe cost 8,192 evals. Train evals/candidate = 0x100 as designed.
- v1 legacy 15-word prefix unchanged (v1 eps 31,047 — 30–32 k band).
- Final carousel = 59 words/cycle; two consecutive cycles captured and byte-identical; positional reconstruction (never first-seen dedup).
Result (both arms bit-exact to the host v2 oracle — genome, train AND 1024-frame holdout; C-twin replay 130 s)
| arm | genome | train | train % | holdout | holdout % |
|---|---|---|---|---|---|
| GA | 0x60894268a2 |
33/256 | 12.9 % | 62/1024 | 6.05 % |
| random | 0x6a8ba845d4 |
42/256 | 16.4 % | 87/1024 | 8.50 % |
Δ(holdout) = 25/1024, σ ≈ 11.7 → z ≈ 2.1: random beats GA, on train and holdout.
The overfit failure mode is gone: train ranking now transfers to holdout (random higher on both; per-arm train% ≈ 2× holdout% consistently, no more memorize-16-frames cliff). What the honest signal reveals is that at this budget regime (22 k candidates/arm — 16× fewer than the 4-frame runs, since eval cost went 16×) the GA underperforms best-of-stream random search on a 39-bit space. Candidate hypotheses for the next iteration: premature convergence (population/mutation params were tuned in the cheap-eval, 373 k-candidate regime), missing diversity maintenance/restarts, or simply that random best-of-22k is a strong baseline when fitness is low-noise.
- ✅ Runtime claims all hold again: autonomous ~125 min, PC-free, calibrated budget derived on-board, 10 s telemetry, bit-exact replay.
- ✅ Train-signal fix validated: train→holdout transfer restored (the previous run's failure mode is decidably repaired).
- ❌ Beats-random: decidably not — inverted. Random > GA at z ≈ 2.1. The benchmark's fitness signal is no longer the suspect; the search (GA hyperparameters/structure for the expensive-eval regime) is.
First board run of the graded datapath (prereg_graded_board_smoke_v1.md).
RTL changed for the first time since M1 smoke #3 (graded_score[9:0] +
UART_REG_GRADED_SCORE=0x38, commit a46a429) → full DFX rebuild (dfx_top.bit
md5 06d6e91792a9…, DRC 0 err; critical-warning set identical to the
board-verified 9fe5cd8 build incl. the known WNS baseline). Firmware
AUTOEHW_BOARD_GRADED_SMOKE_MODE (10,688 B, verify-image OK). Board leg:
auto-booted Buildroot → SLCR reset → U-Boot → FCLK0 pinned 50 MHz (was 125
again) → loadb OK, first roll.
12-word carousel (A7 marker + C0 header 0x09000A + 8 vector payloads +
graded-sum word + C2 checksum), two full cycles captured, plain distinct
poll. host/check_graded_mailbox.py PASS on the raw trace.
Gatekeeper hand-decode of all 8 vectors — board MMIO graded score vs Python
oracle frame_bit_matches:
| vec | condition/frame | genome | board hard/graded | oracle | |
|---|---|---|---|---|---|
| 0 | T0v2 f0 | 0x60894268a2 | 0 / 262 | 262 | ✓ |
| 1 | T1v2 f3 | 0x6a8ba845d4 | 0 / 261 | 261 | ✓ |
| 2 | T2v2 f5 | 0x09571273ce | 0 / 232 | 232 | ✓ |
| 3 | T3v2 f7 | 0x08d590f3ee | 0 / 268 | 268 | ✓ |
| 4 | H0v2 f1 | 0x4e85cbc206 | 0 / 327 | 327 | ✓ |
| 5 | H1v2 f7 | 0x6cbfb15fd8 | 0 / 352 | 352 | ✓ |
| 6 | H2v2 f2 | 0x60894268a2 | 0 / 188 | 188 | ✓ |
| 7 | H3v2 f4 | 0x6a8ba845d4 | 0 / 377 | 377 | ✓ |
All four golden layers now bit-exact: Python oracle == C twin == RTL sim == board MMIO readback. The graded scores span 188–377 (of 264–456 total bits per frame), confirming real dynamic range on silicon, while every hard pass bit is 0 — exactly the cliff-vs-gradient contrast the graded route exists to exploit.
Plumbing smoke only: no search, no Set A screening, Set B sealed. Next per prereg boundary: graded search-arm firmware + Set A screening on the graded signal (host first), then — if a variant finally passes the gate — the Set B confirmatory board run.
The single-shot Set B confirmation of the frozen variant, per
prereg_search_v9_graded_islands.md and v9_setb_confirm_golden.md.
- Variant:
pbil_island8_graded_v9(frozen after v9 Set A) - Seed: 0xB17D (Set B; never used in any selection)
- Firmware
AUTOEHW_BOARD_V9_CONFIRM_MODE(15,968 B, verify-image OK); bitstream rebuild md54b823f543f67…(RTL unchanged; warning set == baseline); FCLK0 50 MHz; loadb first roll; cadence probe GO (10.0 s). - Run: ~124 min PC-free, 836 heartbeat transitions, 0 stuck alerts, 0 serial errors; final carousel (40 words) captured twice, byte-identical cycles, positional reconstruction.
| criterion | required | board | |
|---|---|---|---|
| mailbox vs host golden | bit-exact | check_v9_confirm_mailbox.py PASS |
✓ |
| graded speed probe → derived budget | board-measured | eps 1570 → budget 22,078 (=⌊1570×7200/512⌋) | ✓ |
| hard holdout delta vs random | ≥ +8/1024 | +113/1024 (128 vs 15) | ✓ |
Gatekeeper hand-decode cross-check (independent of the checker): variant
genome 0x7c009161b2 and random genome 0x06c219610c match the host
recompute at the board-derived budget 22,078 exactly; hard holdout packings
(0x80400 = 128/1024, 0xF400 = 15/1024) and graded holdouts (382,336 vs
332,459 of 385,024) all bit-exact. The champion at budget 22,078 is
identical to the nominal-budget (22,387) golden — the search outcome is
insensitive to the ~300-candidate budget difference, closing the
budget-sensitivity risk noted before the run.
M1's beats-random gating item is closed. On this benchmark, at equal board-measured budget, on a preregistered fresh seed, the frozen graded-signal K=8-island PBIL search beats best-of-stream random search on the held-out hard metric — end-to-end on silicon, PC out of the loop, 四-layer goldens equal (Python == C == RTL sim == board mailbox).
Scope kept honest: one benchmark family, one budget regime; the search advantage is conditional on the graded train signal + K=8 configuration discovered through the nine preregistered screening rounds (v3–v9); single-seed confirmation is the preregistered spot-check of the 16-seed Set A evidence, not an independent large-sample replication. Claim B (map-guided evolution) and structural/bitstream evolution remain out of scope for M1.
Full-M1 engineering remainder (unchanged): NV champion store, board-side replay bundle emission.
The same dfx_top.bit that produced the 2026-07-11 Set B confirmation, re-run
on a different die in a different board design: an EBAZ4203 (XC7Z010CLG400-1,
same part number as the 4205), TF-card boot, U-Boot SPL + mainline U-Boot
2026.04-rc5, no Xilinx FSBL anywhere. Board 17A6, console CH340
/dev/ebaz-uart.
The firmware mode is baked into BRAM INIT and is invisible from the .bit, so
provenance was established four ways, no rebuild:
| evidence | value | matches |
|---|---|---|
dfx_top.bit md5 |
4b823f543f67d5613376354764c56a85 |
the Set B record above (4b823f543f67…) ✓ |
.bit header |
dfx_top / 7z010clg400 / Vivado 2025.2 / 2026/07/11 09:01:02 |
Set B build day ✓ |
| IMEM image | external/fw_build/neorv32_imem_image.vhd md5-identical to the installed external/neorv32/rtl/core/neorv32_imem_image.vhd, image_size_c = 15968 |
recorded firmware size 15,968 B ✓ |
| firmware mode | ELF defines the V9-gated symbols publish_v9_confirm_progress / append_v9_confirm_arm_page, and defines no LONGRUN / GRADED_SMOKE / V2_AB mode symbols |
AUTOEHW_BOARD_V9_CONFIRM_MODE ✓ |
/dev/ebaz-uart already follows either board's CH340, so the port name did not
change — the prompt and the clock check did:
scripts/board_serial.py(new):PROMPT_REmatches bothzynq-uboot>(4205 vendor U-Boot) andZynq>(4203 mainline U-Boot).board_mbox_cadence.pyandboard_longrun_monitor.pynow use it;scripts/board_uboot_fpga_load.pyis the zynq_xpart loader ported in so the board flow is self-contained.scripts/board_set_fclk50.py(new) — the trap that a constant-compare would have walked into. The 4205 pins FCLK0 by writing the magic0x00200a00(IO PLL 1000 / 10 / 2). The 4203's IO PLL is 1600 MHz and its vendorps7_initalready givesFPGA0_CLK_CTRL = 0x00400800= 1600/8/4 = 50 MHz. Writing the 4205 magic onto a 4203 would have produced 80 MHz — silently out of signoff, the exact failure class of EHW-5.2. The new script decodes the PLL registers and computes the real frequency instead of comparing a constant. Board reading:1600.0/8/4 = 50.00 MHz, PASS, no write needed.
loady → ymodem 2,083,859 B → fpga loadb; U-Boot echoed the header above,
PCFG_DONE=1, LVL_SHFTR_EN=0xF, FPGA_RST_CTRL=0. Mailbox reads 0 for the
first seconds after config, then starts publishing. Cadence probe: 8
transitions in 75 s, median 10.0 s — the same heartbeat cadence as the 4205
Set B run. GO.
Note: mainline U-Boot prints INFO: post config was not run — harmless here,
the SPL's vendor ps7_init has already enabled the level shifters and released
FPGA_RST_CTRL.
~124 min PC-free (T+7414s at monitor exit; first final-carousel marker at
T+7044s), 832 transitions, 0 stuck alerts, 0 serial errors, log
runs/setb_4203_2026-08-01.log.
- Final 40-word carousel is byte-identical to the 4205's, word for word
(
diffclean against the cycle reconstructed from the 2026-07-11 trace), and the two captured cycles are byte-identical to each other. host/check_v9_confirm_mailbox.pyPASS on the 4203 capture.- Independent hand-decode: measured eps 1570 → board-derived budget
22,078 → evals 5,651,968 = 22,078 × 256; variant
0x7c009161b2train 83489/83968, hard holdout 128/1024, graded 382,336; random0x06c219610ctrain 38/256, hard holdout 15/1024, graded 332,459 → Δ = +113/1024. Every number equals the 4205's. - Whole-run cross-check: all 717 progress-page checksum words (
c2…) match the 4205 trace in order, zero mismatches (5-word start offset, because the cadence probe ran before the monitor was armed; 4205 logged 722). The two traces differ only in which transient carousel words the 0.6 s poll happened to latch (sequence similarity 0.966) — the published stream itself is identical.
Adds: the Set B result is not a property of one die or one board. Same bitstream, same seed, different silicon, different boot chain (TF-card U-Boot SPL vs miner NAND FSBL) → identical measured throughput, identical derived budget, identical champions, identical holdout scores.
Does not add: this is a reproduction, not a new confirmation. Seed 0xB17D stays spent; it buys no additional statistical evidence for beats-random. The architecture was deliberately untouched (brain stays NEORV32) precisely so this would remain a cross-chip reproduction rather than a different experiment.
New tooling: scripts/board_carousel_extract.py rebuilds a carousel
POSITIONALLY from a monitor trace (never from a first-seen set) and reports
whether repeated cycles are byte-identical.
Recorded here because the raw traces live under gitignored runs/ and build/.
a7000000 ad00b17d
c00a0008 c10a0078 c1400622 c1800100 c1c0563e c1000000 c140003d c1800000 c1c02000 c2affa59
c00b000c c10b0008 c15161b2 c181f002 c1c14621 c1000000 c1414800 c1800000 c1c80400 c105d580
c1400000 c1963e00 c1c00001 c2a5ba98
c00c000c c10b0002 c159610c c1801b08 c1c00026 c1000000 c1400100 c1800000 c1c0f400 c10512ab
c1400000 c1963e00 c1c00001 c2a46e0a
Page 10 = calibration (eps 0x622 = 1570, budget 0x563e = 22,078), page 11 =
variant arm 8, page 12 = random arm 2; c1c80400 / c1c0f400 are the packed
hard holdouts 128/1024 and 15/1024.
★★★ M1 engineering addendum — NV champion store + board-origin replay bundle (EBAZ4203, 2026-08-01/02)
Both M1 engineering remainders closed on silicon. Board 17A6, bitstream
dfx_top.bit md5 2e6d7f0d5e0330729b71f17312ecd643 (0 err / 101 crit /
0 failed nets), firmware AUTOEHW_BOARD_STORE_DIGEST_MODE 1 640 B,
verify-image OK. FCLK0 preflight 50.00 MHz.
| # | step | evidence |
|---|---|---|
| 1 | fpga loadb, firmware boots |
mailbox publishes page 15 header c00f000b |
| 2 | board writes its own v2 record into the framebuf high half | md 0x40001000 — every field equals the Set B golden (below) |
| 3 | page 15 before any restore | payload[8] = c1000000 → restore=no, honestly reported |
| 4 | flush to NV: cp.l 0x40001000 0x1000000 0x28 then fatwrite mmc 0:1 0x1000000 champ.bin 0xA0 |
160 bytes written; fatls shows champ.bin next to an untouched BOOT.BIN |
| 5 | physical power cycle (5 V barrel unplugged) | — |
| 6 | reload bitstream, inspect the framebuf low half | md 0x40000000 = all zeros — the PL BRAM really did not survive |
| 7 | fatload mmc 0:1 0x1000000 champ.bin + cp.l 0x1000000 0x40000000 0x10 |
low half reads back the record |
| 8 | FPGA_RST_CTRL pulse (SLCR unlock, 0xF8000240 0xF→0) — logic reset, BRAM preserved |
firmware re-boots and validates |
| 9 | page 15 after restore | payload[8] c1000101 = restored + matches; payloads[9]/[10] c15161b2/c181f002 → raw 0x7c009161b2; write_count 1→2 |
Board-written record, hand-decoded (md 0x40001000 10), against the Set B
golden:
43484d50 'CHMP' 00020000 v2 2c493646/0ff54172 build_id
0000b17d seed B17D 0000563e budget 22078
009161b2/0000007c raw genome 0x7c009161b2
00014621 train 83489 00014800 total 83968
00080400 hard holdout (128<<12)|1024 0005d580 graded 382336
00563e00 evals 5651968 = 22078 x 256 00000001 write_count
Every value matches. Official checker on the post-restore carousel:
check_store_digest_mailbox.py --require-restore \
--expect-raw-from-store <board store dump> --expect-write-count 2
-> PASS schema=0x010100 blob_words=24 write_count=2 restore=yes
(--expect-write-count defaults to 1, which is right for a first-write smoke
and wrong for every post-restore run — a restore-then-write cycle is legitimately
2. Board recipe must pass the incremented value.)
The 40-word store window (md 0x40001000 28) was assembled by
host/assemble_replay_bundle.py --hex-words into a replay_bundle 1.1.0 JSON
carrying board-origin values only, and host/verify_replay_bundle.py
re-ran the C twin for both arms (variant at seed^0x4A4A, random at
seed^0xBEEF, budget 22078 taken from the bundle) — PASS in 91.7 s,
matching raw genome, train score, hard holdout and graded holdout for both.
A bundle built purely from words the board wrote reproduces both champions
bit-exactly on the host.
Closed: champion persistence across real power loss on real NV media (TF card, FAT); a replay bundle whose content originates on the board and independently replays; a write-budget counter that survives and increments across the restore (1 → 2).
Not closed / not claimed: the store-digest firmware seeds the frozen Set B
champion as a constant rather than earning it in the same session — the earning
was already board-verified in the Set B run, and this drop deliberately
separates persistence from search (it also made the image fit: 1 640 B vs the
34 652 B search-carrying build against a 32 768 B IMEM). NV write endurance is
not studied (one file, one write per run). The flush and the restore both go
through U-Boot by design (S2), so this is not autonomous re-flush. The 4205 has
no SD and would need nand write with the same record ABI — out of scope.
The routed design has always reported a large setup violation (WNS ≈ −17…−19 ns) while producing bit-exact results for hours on two different dice. This repo never explained why, and project memory carried a wrong explanation ("the BD declares FCLK high while we sign off at 50"). Both were resolved here — by reading the RTL, then by measuring on silicon.
rtl/uart_stream_eval_core.v state S_VOTE (lines 210-231) computes, inside one
clock edge:
noise_state → lfsr_next → mod_u16_by_u6 (16-iteration restoring division)
→ compare → majority vote → XOR → popcount8 → graded_score
S_VOTE executes on consecutive cycles, so the launch register updates every
cycle. The routed path detail confirms the analyzer is describing real logic
(61 levels: 36 CARRY4 + 25 LUTs, all inside pblock_rp, named
ones[2]_i_2xx — the divider's comparator chain). The earlier hypothesis that
this path is functionally multi-cycle is falsified; declaring
set_multicycle_path here would be a false constraint hiding a real path.
FCLK0 is runtime-settable from U-Boot, and the graded smoke drives exactly this datapath with 8 known-answer vectors, so the real limit can simply be measured:
| FCLK0 | period | graded smoke |
|---|---|---|
| 50.0 MHz | 20.00 ns | PASS 8/8 bit-exact (twice) |
| 57.1 MHz | 17.50 ns | PASS 8/8 bit-exact |
| 61.5 MHz | 16.25 ns | FAIL — and non-deterministically: vector 5 came back +17 on one run and +32 on the next |
| 66.7 MHz | 15.00 ns | FAIL (deltas +1/+1/+17/+1/+6, one hard bit flipped) |
The non-determinism is the decisive discriminator: a logic bug reproduces the same wrong value, a timing violation does not.
So the real critical path on this die at room temperature is between 16.25 and 17.50 ns, against a 20 ns period — roughly 13-19 % margin at 50 MHz — while the slow-corner report claims 39.09 ns, i.e. it is about 2.3× pessimistic here.
This also explains the old EHW-5.2 failures at 125 MHz (8 ns period against a ~17 ns path — far past the cliff), which were previously attributed only to "signoff at 50".
- Historical results stand. Every board result in this repo was produced at 50 MHz, where the design demonstrably computes correctly — now with a measured margin, on two dice, over ~10¹¹ evaluations.
- The design is still not signoff-clean, and that matters going forward. The 15 %-ish margin is measured at room temperature on one die; the slow corner exists precisely to cover temperature, voltage and process spread, and we have no evidence about any of them. Margin is also a per-build property — each rebuild re-rolls placement, so passing is re-gambled every time.
- Recommended fix before any milestone reuses this evaluator: pipeline
mod_u16_by_u6. The 16-iteration restoring-division chain is the whole 61-level path; splitting it into ~4 registered stages should bring WNS positive. It changes cycles-per-vote, hence throughput and any budget derived from measured eps — so it needs fresh goldens, and it must not change the arithmetic. A zero-RTL alternative is to run at 25 MHz for ~2× margin at half the throughput. - Do not paper over it with a multicycle exception (see above).