Skip to content

Latest commit

 

History

History
1027 lines (798 loc) · 51.5 KB

File metadata and controls

1027 lines (798 loc) · 51.5 KB

Board results — EBAZ4205 (XC7Z010)

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.


M1 board smoke #1 — uart_stream autonomous loop (2026-07-08) — PARTIAL / FAIL

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 vs golden (word by word)

# 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 — matches, but see note
4 0xAA000020 0xAA013020 ❌ train 0/32 (golden 19/32)
5 0xAB000020 0xAB011020 ❌ holdout 0/32 (golden 17/32)
6 0xAC000200 0xAC000200 ✅ evals=512

Diagnosis (decisive)

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 0xC0DE the first random_config is 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 0xA8001008 for ~155 s before the lifecycle completed). That is the signature of the MMIO backend hitting its full timeout=1,000,000 poll on every frame: the island's STATUS.DONE bit is never observed, so each autoehw_mmio_eval_frame times out and returns 0.

Root-cause locus: the tpu_rpuart_stream_island_regsuart_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.

Hypotheses for the fix (for the RTL owner)

  1. The soc_dfx XBUS holds xbus_cyc high across accesses and pulses tpu_stb per transfer; the ad-hoc ack_q <= cyc & tpu_stb & !ack_q may 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 returning done_latched.
  2. Prefer mirroring the base RM's known-good Wishbone-slave handshake rather than the bespoke ack_q/wr_en && !ack_q gating.
  3. Confirm xbus_sel is non-zero on NEORV32 word writes (the island gates writes on xbus_sel != 0; the base RM ignores sel).

What this run does establish on silicon

  • Full DFX build + fpga loadb of a fresh single-RM design works.
  • NEORV32 static shell (reused from zynq-ehw, copied read-only) + rm_uart_stream RM 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.


M1 board smoke #2 — after XBUS handshake fix (2026-07-08) — PROGRESS / still FAIL

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 vs golden

# 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

Diagnosis — handshake FIXED, new functional (sim-vs-synth) discrepancy

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):

  1. 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).
  2. Strengthen the gate beyond behavioral iverilog: add a post-synthesis (gate-level) simulation of eval_core against 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.

What smoke #2 establishes on silicon

  • 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.

M1 board smoke #3 — after arithmetic-fit fix + pblock enlarge (2026-07-08) — PASS ✅

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 (0x002004000x00200a00), fpga loadb OK.

Observed vs golden — FULL MATCH

# 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.

What M1-foundation now establishes on silicon

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.


M1-full scaffold smoke — telemetry + replay words (2026-07-08) — PASS ✅ (scaffold scope)

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 vs expected — ALL 11 WORDS, first roll

# 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.

Scope (explicit, per the consolidation decision)

This is the M1-full scaffold, not full M1 PASS:

  • AE gives 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_bundle JSON 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.

Two-phase board sequence, both first roll

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:

  1. Seeded the champion-store record from the PS via U-Boot into the static axil_framebuf (mw.l 0x40000000… ×5: magic CHMP, meta, config 0x000F05B7, budget 1000, checksum; md readback verified).
  2. 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.
  3. NEORV32 rebooted, re-ran the search, validated magic+checksum on the store at 0xF5000000, and restored the champion: all 13 words matched with tail 0xB2010101 0xB30F05B7 (restore-success status + restored champion phase=15/maj=5/thr=−73). No extras, steady carousel.

Scope (explicit)

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.


M1-full bad-candidate rejection + recovery scaffold (2026-07-08) — PASS ✅ (scaffold scope)

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.

Observed — ALL 15 WORDS, first roll

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.

Scope (explicit)

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.


M1-full paged mailbox extension (2026-07-08) — PASS ✅ (scaffold scope)

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.

Observed — full 23-word carousel, checker PASS

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.txtPASS (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).

Measurement-method note (for future paged extensions)

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.

Scope

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.


M1-full page sequence counters (2026-07-08) — PASS ✅

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.

Observed — full 23 words captured by a PLAIN latched poll, checker PASS

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.pyPASS (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 C1410101no 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.


M1-full derived long-run budget page (2026-07-08) — PASS ✅

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.

Observed — full 31-word carousel, plain poll, checker PASS

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.pyPASS (recomputes page 2 from the observed AE). Sequence counters held (plain distinct-value poll captured all 31 words).

Meaning

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.


M1-full MULTI-HOUR autonomous run (2026-07-08→09) — PASS ✅ (the run itself)

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 run

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 0x0300F1 present). Both check_m1_mailbox.py and check_longrun_live_mailbox.py --require-finalPASS.

Result — the long run found a better train champion, bit-exact to the oracle

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.

Scope (explicit, honest)

  • 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.

Result

  • v1 31-word prefix: check_m1_mailbox.py PASS.
  • 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.

Scope (explicit)

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.

Two measurement-tooling notes (board was correct in both)

  1. check_v2_ab_mailbox.py validates the host-stub's standalone 21-word --v2-ab-mailbox-smoke format (prefix a7000000 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.
  2. 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.

M1-full v2 headroom MULTI-HOUR same-boot A/B (beats-random judgment) (2026-07-09→10) — PARTIAL

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.

Result — GA beats random on TRAIN, ties on HOLDOUT

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.

Why the holdout tie, and the fix (the real blocker now)

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.

Scope

  • ✅ 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.

Result (both arms bit-exact to the host v2 oracle, incl. the 1024-frame holdout)

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.

Diagnosis — the fitness signal, not the holdout, is now the limiter

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).

Standing

  • ✅ 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.

Board-derived calibration (page 8, hand-decoded + arithmetic-exact)

  • 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.

Diagnosis — the dense train signal worked; the GA search is now the limiter

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.

Standing

  • ✅ 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.

Graded-fitness board smoke — FIRST GRADED SILICON READBACK ✅ (2026-07-11)

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.

Result: page 9 carousel == oracle, checker PASS

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.

Scope

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.


★★★ v9 Set B CONFIRMATORY RUN — SUCCESS (2026-07-11): beats-random CONFIRMED on silicon

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 md5 4b823f543f67… (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.

Verdict — all preregistered criteria met

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.

What this completes

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.


★★★ Set B CROSS-CHIP REPRODUCTION on a second board — EBAZ4203 (2026-08-01)

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.

Bitstream provenance (established before the run — the whole argument rests on it)

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

Loader port (4205 → 4203)

/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_RE matches both zynq-uboot> (4205 vendor U-Boot) and Zynq> (4203 mainline U-Boot). board_mbox_cadence.py and board_longrun_monitor.py now use it; scripts/board_uboot_fpga_load.py is 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 magic 0x00200a00 (IO PLL 1000 / 10 / 2). The 4203's IO PLL is 1600 MHz and its vendor ps7_init already gives FPGA0_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.

Load smoke

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.

Run — bit-identical to the 4205

~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 (diff clean 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.py PASS on the 4203 capture.
  • Independent hand-decode: measured eps 1570 → board-derived budget 22,078 → evals 5,651,968 = 22,078 × 256; variant 0x7c009161b2 train 83489/83968, hard holdout 128/1024, graded 382,336; random 0x06c219610c train 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.

What this adds, and what it does not

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.

The 40 words (identical on both boards)

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.

The round trip, step by step

# 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] = c1000000restore=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 12

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.)

Board-origin replay bundle

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.

Honest scope

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.


★ Timing-margin measurement — what the −19 ns WNS actually means (EBAZ4203, 2026-08-02)

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.

The path is genuinely single-cycle — a multicycle constraint would be false

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.

Measured margin (scripts/board_freq_margin.py)

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".

What this changes

  • 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).