Claude-side Vivado out-of-context (OOC) synthesis gate results. This is the gate
ChatGPT cannot run (no Vivado in its environment); it is mandatory before any
board build per docs/workflow.md. Not a board result — no silicon behavior is
claimed here. Raw reports live under build/ooc/ (gitignored); this file is the
persisted summary.
Tool: Vivado 2025.2 · make vivado-ooc (scripts/vivado_ooc_uart_stream.tcl).
- Part:
xc7z010clg400-1,synth_design -mode out_of_context - Verdict: PASS —
Synth Design complete, 0 errors, 0 critical warnings, 12 benign warnings. - Date: 2026-07-07
| Resource | Used | Avail | % |
|---|---|---|---|
| Slice LUTs (all Logic) | 3371 | 17600 | 19.15 |
| Slice Registers (FF) | 738 | 35200 | 2.10 |
| DSP48E1 | 3 | 80 | 3.75 |
| Block RAM Tile | 0 | 60 | 0.00 |
| LUT as Memory | 0 | 6000 | 0.00 |
payload_mem[0:63] maps to logic/distributed, not BRAM (0 BRAM). The 3 DSPs come
from the integer multiplies (frame_idx*0x1F3D, jitter_milli*32,
flip_ppm*65535).
- 10×
[Synth 8-6014] Unused sequential element tmp_*_reg removed— thetmp_*regs are blocking-assignment temporaries inside the clockedalways; Vivado classifies them as sequential then trims them because they hold no cross-cycle state. Cosmetic only; iverilog's 144-vector gate already proves functional equivalence to the Python oracle. Style cleanup suggestion: declare them asautomatic/local or split combinational vs sequential logic to silence. - 1×
[Synth 8-3936] flip_threshold_reg trimmed 29→16 bits— correct: onlyflip_threshold[15:0]is used, and the value (flip_ppm*65535/1e6, maxflip_ppm=1e6→ 65535) fits in 16 bits. No functional impact. [Netlist 29-101] not ideal for floorplanning (large number of primitives)— flat OOC synth of a module with dividers/modulo; enable hierarchy for the real island build.[Timing 38-493]/[38-242]— no clock buffer /HD.CLK_SRCunset in OOC mode →report_timing_summaryhas no meaningful number here. Real timing must be taken at the island build with a proper clock constraint at FCLK0 = 50 MHz.
3371 LUTs for a single-frame evaluator is dominated by two non-power-of-2 operations that synthesize to large combinational dividers:
tmp_state1 % ((2*noise_span)+1)(variable modulo), andround_div(x, 1000)(divide-by-1000).
Comfortable at 19% of the whole device now, but when this core becomes a DFX RP
inside a pblock it is the first shrink target. Options: make the noise span /
scaling factors powers of two so the modulo/divide become shifts (the same class
of rework as EHW-4.2's 48-DSP → 18-DSP fix in zynq-ehw). Any such change must
re-pass the 144-vector RTL gate and be re-synthesized here.
MMIO register wrapper (rtl/uart_stream_island_regs.v) around
uart_stream_eval_core; the register map matches sw/uart_stream_regs.h byte-for-byte.
- Part:
xc7z010clg400-1,synth_design -mode out_of_context, top =uart_stream_island_regs - Verdict: PASS — 0 errors, 0 critical warnings, 12 warnings (same
benign classes as the core: trimmed
tmp_*temporaries + OOC no-BUFG/no-XDC clock notes). - Date: 2026-07-07
| Resource | Used | Avail | % | Δ vs bare core |
|---|---|---|---|---|
| Slice LUTs | 3480 | 17600 | 19.77 | +109 (MMIO decode) |
| Slice Registers (FF) | 866 | 35200 | 2.46 | +128 (register file) |
| DSP48E1 | 3 | 80 | 3.75 | +0 |
| Block RAM Tile | 0 | 60 | 0.00 | +0 |
The wrapper adds only the register file + address decode over the evaluator core; the 3371-LUT core still dominates, so the shrink note above applies unchanged. Timing again not meaningful in OOC-no-XDC mode (real timing at island build, FCLK0=50 MHz).
sw/uart_stream_regs.h offsets and CTRL/STATUS bit definitions match the RTL
decode in uart_stream_island_regs.v (CTRL bit0=start / bit1=clear-done; STATUS
bit0=busy / bit1=done / bit2=pass). The tb_uart_stream_island_regs smoke drives
this path and returns status=0x6 (done+pass) on a known-passing vector. The
autoehw_mmio_backend write/poll sequence (clear-done → start → poll STATUS.DONE
with timeout) is consistent with the RTL; it is compiled and host-consistent
only — not yet board-verified (no real base address bound, no silicon).
DFX reconfigurable-module wrapper (rtl/dfx/tpu_rp_rm_uart_stream.v, module
tpu_rp) that keeps the zynq-ehw NEORV32 XBUS port contract and wraps
uart_stream_island_regs (island base 0xF0000000).
- Part:
xc7z010clg400-1,synth_design -mode out_of_context, top =tpu_rp - Verdict: PASS after a Claude synth-fix — see below. Post-fix: 0 errors,
0 critical warnings, 61 warnings (benign: wide unused XBUS bits —
xbus_selfanin, tied handshake nets — plus the inherited eval-core temporaries and OOC no-BUFG/no-XDC clock notes). - Date: 2026-07-08
| Resource | Used | Avail | % |
|---|---|---|---|
| Slice LUTs | 3487 | 17600 | 19.81 |
| Slice Registers (FF) | 867 | 35200 | 2.46 |
| DSP48E1 | 3 | 80 | 3.75 |
| Block RAM Tile | 0 | 60 | 0.00 |
Essentially the island_regs wrapper cost (3480 LUT) plus the XBUS ack handshake.
As delivered, the module declared `default_nettype none (line 1) but wrote
its ports in bare Verilog-2001 style (input clk, output [31:0] xbus_dat_r,
…). Icarus accepts this and the RTL smoke passed, but Vivado synth failed with
21 errors ([Synth 8-6735] net type must be explicitly specified … when default_nettype is none / [Synth 8-9844] non-net port cannot be of mode input). The two older RTL files avoided this by using explicit input wire /
output reg; only this new wrapper regressed.
Fix (Claude, mechanical): made every port explicit input wire / output wire. Behavior unchanged — the RTL smoke still returns status=0x6. This is the
same class as the EHW-1.1-fabric lesson in zynq-ehw: iverilog accepts what
Vivado synth rejects; the OOC gate is the real gate for board-bound RTL.
Note for ChatGPT: when a module sets `default_nettype none, all module
ports need an explicit wire/reg net type, not bare input/output. Keep the
input wire / output wire style already used in uart_stream_eval_core.v and
uart_stream_island_regs.v.
- No pblock/resource-bound assertion in either OOC tcl yet — they report, they do
not fail on a budget. Add a pblock +
report_utilizationthreshold check when the DFX island is defined. - No place/route, no timing signoff, no board result.
autoehw_mmio_backendbase address / timeout not yet bound to a real island.
The sim-vs-synth arithmetic rework (explicit round_div_s32, and the % operator
replaced by a hand-written 16-iteration restoring-division mod_u16_by_u6) is
functionally correct (host 384-vector gate PASS) and synthesizes 0 err/0 crit, but
the manual restoring-division is much larger than the Vivado-inferred divider
it replaced:
| module | before (smoke #2) | after 8ba1413 |
|---|---|---|
uart_stream_eval_core |
3371 LUT | 4654 LUT |
uart_stream_island_regs |
3480 LUT | 4772 LUT |
tpu_rp (the RM) |
3490 LUT | 4781 LUT |
Blocker: the DFX RP pblock (pblock_rp, SLICE_X22Y0:SLICE_X43Y49 = 1100
slices = 4400 LUT6) cannot hold a 4781-LUT RM. The DFX build would fail
placement. Caught by the OOC gate before the ~25-min DFX build — its purpose.
Recommendation to the RTL owner (avoids a golden re-baseline): keep the modulo
numerically exact but drop the hand-rolled restoring division. The real sim-vs-synth
cause was the mixed signed/unsigned operands, not the % operator itself — so
keep Vivado's inferred % (which was compact, part of the original 3371 LUT) but
make its operands explicitly unsigned with fixed width (e.g.
{16'd0,tmp_state1} % {26'd0, divisor6} with both unsigned), matching the oracle's
unsigned remainder. That removes the ambiguity and fits the pblock. A 2^k-scaling
rework would also shrink it but would change the numbers and force a golden
re-baseline — not preferred. (If a bigger footprint is truly needed, the RP pblock
could be enlarged, but keeping the RM ≤4400 LUT is the clean path.)
All three OOC gates re-run with the graded score datapath
(graded_score[9:0] popcount+accumulator sharing the hard-compare XOR,
UART_REG_GRADED_SCORE=0x38 readback): 0 errors / 0 critical warnings
on xc7z010clg400-1.
| module | pre-graded (compact-modulo era) | with graded (a46a429) |
|---|---|---|
uart_stream_eval_core |
~4100* | 4144 LUT / 758 FF / 4 DSP |
uart_stream_island_regs |
— | 4256 LUT / 886 FF / 4 DSP |
tpu_rp (the RM) |
4223 LUT | 4266 LUT / 929 FF / 4 DSP |
*eval_core standalone wasn't re-recorded in the compact-modulo entry; delta derived from the RM: graded costs ≈ +43 LUT (+1%) — the datapath-sharing approach worked as intended (the accumulator reuses the existing decoded^source XOR; only the popcount tree and 10-bit register are new).
Fit assessment: RM 4266 LUT < 4400 LUT6 envelope of the original
quadrant pblock, and comfortably inside the current tracked pblock
(rtl/dfx/pblock_rp.xdc, full right half = 2200 slices = 8800 LUT6).
Slice-packing pressure is essentially unchanged from the board-proven
smoke-#3 build (FF count +~60). No DFX rebuild risk indicated. Board-facing
firmware/golden prereg may proceed.
The delivered external/shell/axil_framebuf.vhd adds the NEORV32 write inside
the existing AXI write process. Behaviourally correct (GHDL smoke passes), but
it does not synthesize: Vivado cannot map two independent write sources onto
the RAM and dissolves the whole 2048×32 array into flip-flops.
WARNING: [Synth 8-4767] Trying to implement RAM 'ram_reg' in registers.
Block RAM or DRAM implementation is not possible
WARNING: [Synth 8-13159] ... dissolved into (65536) registers bits
| variant | LUT | FF | BRAM | verdict |
|---|---|---|---|---|
baseline (zynq_ehw/rtl/axil_framebuf.vhd) |
5 (0.03 %) | 4 | 4 | reference |
| delivered 525ccaa | 123 215 (700 %) | 65 604 (186 %) | 0 | unbuildable |
| gate probe A: shared-variable TDP, 1 process/port | — | — | — | ERROR [Synth 8-2914] Unsupported RAM template |
| gate probe B: two half-size arrays, both read twice | 3 960 (22.5 %) | 101 | 0 | LUTRAM, still no BRAM |
| gate probe C: param RAM untouched + separate store RAM | 38 | 5 | 5 | infers BRAM — viable direction |
Root cause. A BRAM port carries one address bus, so a block RAM offers two addresses total. The original topology is 1 write / 2 reads (AXI write, AXI read, NEORV32 read) and Vivado satisfies it by replicating into 4 tiles. Adding a second write source creates four independent addresses with two writers, which no supported template covers. This is invisible to behavioural simulation — the same class as the M1 smoke-#1 XBUS handshake and the smoke-#2 arithmetic divergence.
Viable direction (probe C, measured): leave the PS param RAM byte-identical — its topology and its board-verified PS path are then untouched by construction — and add a separate store array for the board-origin record, written by NEORV32 and read over AXI, muxed into the AXI read data by the address MSB. Cost over baseline: +1 BRAM tile, +33 LUT.
Two constraints probe C must still satisfy, both found by reading the delivered firmware rather than the RTL:
champion_store_v2_current_write_count()reads the record back, so the store array needs the NEORV32 read port too (1 write / 2 reads, i.e. the same topology the param RAM already proves) — not the write-only SDP of probe C. Expect one or two more tiles; there are 60.- The store and the restore paths are different directions and must not share
an address region: restore is PS→board (PS writes over AXI, firmware reads)
and stays in the param RAM; store is board→PS. The delivered firmware uses
one
AUTOEHW_FRAMEBUF_BOARD_STORE_WORDbase for both, which the split topology cannot honour.
Also flagged (not a blocker today): the SoC registers the write, so the RAM updates ~2 cycles after the XBUS write is acked. A read-back placed immediately after a write would return stale data. Today's firmware appears to read the write count before writing, which is safe — keep that order and state it as an ABI rule.
No DFX build, no bitstream, no board: the gate stopped here. 525ccaa stays
local and unpushed.
The follow-up keeps the param/restore RAM and the board-store RAM as two separate arrays:
- low half (
0..1023): PS-written parameter/restore RAM, NEORV32-readable; - high half (
1024..2047): NEORV32-written board-store RAM, PS- and NEORV32-readable.
This removes the second writer from the original RAM template while preserving
the existing address convention (0x40000000 low half for restore,
0x40001000 high half for fatwrite). GHDL behavioural smoke covers
NEORV32-write/NEORV32-read, NEORV32-write/AXI-read, and AXI-write/NEORV32-read.
It is not an OOC PASS claim until Vivado confirms BRAM inference.
Splitting into param_ram / store_ram removed the register explosion but the
array still does not reach block RAM:
| variant | LUT | LUT as Memory | FF | BRAM |
|---|---|---|---|---|
| baseline | 5 | 0 | 4 | 4 |
| 525ccaa (shared array, two writers) | 123 215 | 0 | 65 604 | 0 |
| 48b6f0a (split arrays) | 3 958 | 3 456 | 68 | 0 |
| gate probe D: 48b6f0a + mux moved after the registers | 70 | 0 | 6 | 4 |
Cause, isolated and verified by probe D: both read processes select between the two arrays before the output register —
if rd_addr(ADDR_BITS-1) = '1' then rd_data_r <= store_ram(ri);
else rd_data_r <= param_ram(ri); end if;so one register is fed by two different memories and neither matches a RAM template; Vivado falls back to distributed RAM (3 456 LUT of LUTRAM). Probe D changes nothing else — same split, same address policy, same ports — and only gives each array its own output register, muxing afterwards:
rd_param <= param_ram(ri); rd_store <= store_ram(ri);
nrsel_r <= rd_addr(ADDR_BITS-1);
...
rd_data <= rd_store when nrsel_r = '1' else rd_param;Result: 4 BRAM, 0 LUTRAM, 70 LUT — back to the baseline tile count, +65 LUT
for the two post-register muxes. Same treatment is needed on the AXI read
(rdata_param / rdata_store + rsel_r).
Rule for this file: each RAM read lands in its own dedicated register; the half-select mux happens after the registers, never before.
Re-audited against the firmware, both constraints from round 1 hold in 48b6f0a:
- board store base = word 1024 (high half) → NEORV32 write is permitted, and
champion_store_v2_current_write_count()can read it back because the NEORV32 read port now spans both halves. - v1 restore reads words 0..4 (low half), which is where the PS can write — so store (board→PS) and restore (PS→board) no longer collide.
Nothing converts a flushed v2 store back into low-half restore words:
host/m1_persist_framebuf_words.py still builds them from a hand-written
SamplerConfig. Until that exists the NV round trip
(store → fatwrite → power cycle → fatload → restore) cannot be closed on the
board, which is the whole point of the addendum.
The RTL follow-up applies probe D's rule directly: each RAM now has its own read
output register (param and store), and the half-select mux is after those
registers for both AXI and NEORV32 read ports. This should preserve the split
topology while restoring the block-RAM inference template.
The host restore gap is also closed at the tool level:
host/m1_persist_framebuf_words.py --from-store-hex <dump> validates the v2
store record checksum and emits low-half mw.l 0x40000000 ... restore words.
That gives the board procedure a concrete bridge from the high-half
board-written store dump to the low-half post-power-cycle restore input.
GHDL behavioural smoke and make all pass locally. This remains pending OOC
until Vivado confirms BRAM inference and resource use.
| variant | LUT | LUT as Memory | FF | BRAM | err / crit |
|---|---|---|---|---|---|
| baseline | 5 | 0 | 4 | 4 | 0 / 0 |
| 222c51b | 72 | 0 | 6 | 4 | 0 / 0 |
Matches gate probe D (70 LUT) — read muxes now sit after the RAM output registers, block RAM inference is restored, no distributed RAM. The framebuf change is clear to go into a DFX build. Host gate independently reproduced: 44 tests OK.
host/m1_persist_framebuf_words.py --from-store-hex byte-copies the 16-word
v2 store record into low-half words 0..15. Verified by running it against a real
--dump-store-window dump: the emitted words are the v2 record verbatim,
starting mw.l 0x40000000 0x43484d50 / mw.l 0x40000004 0x00020000.
The only firmware reader of the low half is load_persisted_champion(), which
reads words 0..4 and requires
#define AUTOEHW_CHAMPION_STORE_VERSION 0x00010000u
...
(meta & 0xFFFF0000u) != AUTOEHW_CHAMPION_STORE_VERSION -> rejectThe converted record presents 0x00020000 in that position, and the v1 layout
(word 1 = meta, word 2 = packed config, word 4 = checksum) does not match the
v2 layout at all. The firmware will reject every restore produced by this
tool — so store → fatwrite → power cycle → fatload → restore, the entire
point of the addendum, cannot close.
The new assertions do not catch it because they only check the emitted text
(16 lines, first two words, last address, and that --from-store-hex agrees
with the md-format parser). Nothing asserts that a restore actually happens.
Recommended fix — firmware side, not converter side. The v1 restore ABI
carries a SamplerConfig (phase / threshold / majority window) while a v2
champion is a 39-bit genome, so a v2→v1 conversion is lossy by construction and
must not be attempted. Instead give the firmware a v2-aware restore reader at
low-half base 0 (validate magic + 0x00020000 + the 16-word checksum,
dispatch on the version word, keep v1 for the historical fixture). The
converter's byte-copy then becomes correct as delivered.
Test requirement: the round-trip test must drive the restore, not the text — seed the host stub's low half with the converted words, run the restore path, and assert the recovered champion equals the stored one. A green test over formatting is what let this through.
Gate decision: no DFX build yet. The fix changes firmware, firmware is baked into the bitstream's BRAM INIT, so a build now would be thrown away. Building when the restore reader lands.
The firmware follow-up keeps the converter's v2 byte-copy semantics and fixes the actual consumer:
load_persisted_champion_v2()reads low-half words 0..15, validatesCHMP, schema0x00020000, the 16-word record checksum, and the 39-bit raw-genome range, then restores the v2 champion without converting it to the lossy v1SamplerConfigABI.- Page 15 keeps its original digest payloads in positions 0..7 and extends the
page with restore status plus restored raw-genome chunks. A board observer can
now require
restore_valid && restored_raw == stored_rawafter the power-cyclefatload. - The host gate now drives the restore path: it generates a high-half store record, clears the fake framebuf to model reconfiguration, writes that record into low-half restore words, runs the firmware restore reader, and asserts the recovered v2 raw genome equals the stored one.
This resolves the false-green text-only test gap. RTL OOC remains the already measured 222c51b result (72 LUT / 0 LUTRAM / 4 BRAM); the remaining gate is a fresh firmware/DFX build and the physical NV round trip on the 4203.
RTL untouched this round (git diff 222c51b b78dfcc -- external/shell rtl/ is
empty), so the round-3 framebuf OOC PASS stands. Host gate reproduced: 44 tests
OK. The restore reader itself reviewed and correct — reads low-half words 0..15,
validates magic + 0x00020000 + the 16-word checksum, rejects a raw genome
wider than 39 bits, decodes losslessly.
Blocker: AUTOEHW_BOARD_STORE_DIGEST_MODE fails to build for the board.
autoehw_board_mbox.c:1379: error: 'board_eval_frame_graded' undeclared
autoehw_board_mbox.c:1399: error: 'publish_v9_confirm_progress' undeclared
The new board block at line 1376 uses two helpers whose definitions are still guarded for V9-confirm only:
| line | guard | needs |
|---|---|---|
| 602 | #if defined(AUTOEHW_HOST_STUB) || defined(AUTOEHW_BOARD_V9_CONFIRM_MODE) |
|| defined(AUTOEHW_BOARD_STORE_DIGEST_MODE) |
| 934 | #if defined(AUTOEHW_HOST_STUB) || defined(AUTOEHW_BOARD_V9_CONFIRM_MODE) |
same |
| 956 | already includes AUTOEHW_BOARD_STORE_DIGEST_MODE |
— |
One of the three guards was updated and two were not. No host-side gate can
see this: the stub build defines AUTOEHW_HOST_STUB, which pulls both helpers
in regardless. Only the cross-compiled board firmware exposes it.
Second finding — the board mode would cost 2 h per iteration. The
STORE_DIGEST block derives a full budget from measured throughput
(v9_confirm_arm_budget(measure_v9_graded_evals_per_sec(...))), i.e. it re-runs
the whole ~2 h Set B confirm before it writes the store. The existing smoke
paths use AUTOEHW_V9_CONFIRM_SMOKE_BUDGET = 8. The NV claim does not depend on
run length, and the round trip needs store → fatwrite → power cycle → fatload → restore iterated in minutes. Recommend the store-digest board mode use the
smoke budget (or a build-time switch), keeping a long variant for later if
wanted.
Third finding (host-only, no rebuild needed) — the checker still trusts a
board-computed flag. Negative controls run against
check_store_digest_mailbox.py:
| control (page checksum recomputed each time, so the page is internally valid) | expected | actual |
|---|---|---|
restore status cleared, --require-restore |
FAIL | FAIL ✓ restore status mismatch |
| restore status cleared, no flag | PASS reporting restore=no |
PASS, restore=no ✓ |
| restored genome word flipped, status still claims restored+matches | FAIL | PASS ✗ |
So the checker verifies that the board claims a matching restore, but never compares the reported restored genome against the stored one. Given the standing rule in this repo — recompute host-side, never fixed-compare a host fake — the checker should cross-check payloads 9/10 against the stored raw genome rather than trusting the match bit.
Gate decision: no DFX build. Firmware does not compile for the board, and firmware is baked into the bitstream.
The firmware/checker follow-up addresses all three round-4 findings:
- The two V9 helper guards now also include
AUTOEHW_BOARD_STORE_DIGEST_MODE, soboard_eval_frame_graded()and the progress helper are visible in the board STORE_DIGEST build. - The board STORE_DIGEST mode uses
AUTOEHW_V9_CONFIRM_SMOKE_BUDGETand no progress callback. The NV round trip tests store/flush/power-cycle/restore; it must not re-run the full two-hour Set B confirm on every iteration. host/check_store_digest_mailbox.py --require-restore --expect-raw-from-store <dump>validates the v2 record checksum from the dumped store window and compares mailbox payloads 9/10 against the stored raw genome. A recomputed page checksum plus a forgedrestored+matchesbit is no longer enough to pass.
The host unit test now includes that forged-payload negative control. The next
gate should include a board-mode compile-only check for
AUTOEHW_BOARD_STORE_DIGEST_MODE, then DFX and the physical 4203 NV round trip.
Local follow-up gates:
make allPASS (44 tests plus RTL smoke).make framebuf-vhdl-smokePASS.cc -fsyntax-only -DNEORV32_H -DAUTOEHW_BOARD_STORE_DIGEST_MODE -DAUTOEHW_NO_MAIN ... sw/autoehw_board_mbox.cPASS, covering the board-only STORE_DIGEST preprocessor path that host stub builds cannot see.- A full
riscv64-unknown-elf-gcccompile was attempted but the installed toolchain is missing standard newlib headers (inttypes.h), so that exact cross-compile remains for Claude's board toolchain.
The three round-4 items are addressed: the mode guards compile, the board mode
uses the smoke budget, and --expect-raw-from-store makes the checker recompute
instead of trusting the board's match bit. Compilation gets past the guards.
New blocker — link fails:
ld: main.elf section `.text' will not fit in region `rom'
ld: region `rom' overflowed by 1884 bytes
neorv32_soc_dfx.vhd sets IMEM_SIZE => 32*1024 and the fw Makefile pins
__neorv32_rom_size=32k, so the image must fit 32 768 B. The store-digest image
needs 34 652 B — 5.4 % over, not a magnitude, but the linker is exact.
--gc-sections and -ffunction-sections/-fdata-sections are already on
(common.mk:147-148), so this is post-collection: the code is genuinely linked.
Per-object .text* totals (sum every .text* section — plain size -A hides
them under function sections):
| object | text | rodata |
|---|---|---|
uart_stream_v2.c.o |
9 742 | 987 |
neorv32_aux.c.o |
4 432 | 3 350 |
autoehw_firmware_v2.c.o |
3 962 | 52 |
autoehw_board_mbox.c.o |
2 670 | 8 |
uart_stream_v1.c.o |
1 668 | 475 |
Recommended fix — drop the search from this mode entirely. The store-digest
mode still links the full graded v9 confirm search
(autoehw_v2_firmware_confirm_island8_graded_v9, ~4 KB, plus the graded
evaluation paths it keeps alive in uart_stream_v2.c), only with a smaller
budget. But the addendum claims persistence, not search: it needs a champion,
not an earned one. Seeding the frozen v9 champion as a constant and going
straight to store → digest → restore removes far more than 1 884 B, makes the
expected values fixed and host-checkable, and removes the last reason for this
mode to be slow. A separate minimal main() for the store test would have the
same effect.
Enlarging IMEM_SIZE to 64 KB is the alternative, but it perturbs a static
shell that just passed OOC and spends ~8 more BRAM tiles for a testability
problem. Not recommended while a trim is available.
Gate decision: still no DFX build — there is no linkable board image.
The STORE_DIGEST board mode now tests persistence as a persistence path, not as a fresh search:
- The board mode no longer calls
measure_v9_graded_evals_per_sec(),autoehw_v2_firmware_confirm_island8_graded_v9(), graded MMIO evaluation, or progress publication. - It emits the record/blob from the already board-verified Set B constants:
eps
1570, budget22078, variant raw0x7c009161b2, random raw0x06c219610c, hard holdouts128/1024and15/1024, graded holdouts382336and332459, and board-convention frame evals5651968. - The STORE_DIGEST block now owns its carousel and does not fall through into the legacy M1 main loop.
- Unused page helpers are guarded by their actual modes, so the STORE_DIGEST
board translation unit only references
uart_v2_encode_genome()anduart_v2_decode_genome()from the v2 library.
Local compile-only evidence:
riscv64-unknown-elf-gcc ... -DAUTOEHW_BOARD_STORE_DIGEST_MODE -c sw/autoehw_board_mbox.c
PASS
size -A /tmp/autoehw_board_store_digest.o
.text 1320
.rodata 88
Total 1490
nm -u /tmp/autoehw_board_store_digest.o
U uart_v2_decode_genome
U uart_v2_encode_genome
This does not replace Claude's full NEORV32 link, but it shows the overflow cause has been removed from this translation unit: no PBIL/search/evaluator symbols remain live in STORE_DIGEST mode.
Store-only mode fixed the overflow decisively: image 1 640 B (was 34 652 B
against a 32 768 B IMEM), verify-image OK, image_size_c = 1640.
Full DFX build: 0 errors, 101 critical warnings (the known-good count),
routing completed with 0 failed nets, dfx_top.bit 2 083 859 B
md5 2e6d7f0d5e0330729b71f17312ecd643. Utilization 8 080 LUT (45.9 %),
4 962 FF, 10.5 BRAM tiles, LUT-as-Memory only 148 — the framebuf split kept
its block RAM, as the OOC probe predicted.
Timing, and a correction to this repo's own lore. WNS = −19.394 ns against the previously recorded −17 baseline. The worst path is
Source: u_soc/wb_tpu_inst/u_island/eval_core/noise_state_reg[4]/C
Destination: u_soc/wb_tpu_inst/u_island/eval_core/graded_score_reg[9]/D
Logic Levels: 61 (CARRY4=36 LUT2=6 LUT3=7 LUT4=2 LUT5=1 LUT6=9)
i.e. entirely inside the pre-existing RM evaluation datapath — none of the
addendum's logic appears on it, and the −17 → −19.4 delta is placement noise
on that same old path. Recorded for the record: the note carried in project
memory that this negative WNS comes from "the BD declaring FCLK high while we
sign off at 50" is wrong. The clock summary shows clk_fpga_0 constrained
at exactly 20.000 ns / 50 MHz, and the path's data delay is 39.093 ns, so the
violation is real against the constraint. What is genuinely established is
empirical: this same path has produced bit-exact results for hours, across two
different dice, at 50 MHz. Why it does is not explained anywhere in this repo
— the likely reason is that the path is functionally multi-cycle (the island
runs a start/busy/done handshake, ~31 µs per frame evaluation) with no
multicycle constraint declared, but that has not been demonstrated. It should be
investigated and constrained properly, separately from this drop.