feat: integrate flash #1 (neural-network data) RTL into V2 top-level

Closes the flash #1 RTL gap flagged in DEC-0041: real, unmodified V1
subsystem (flash_slot_manager.v/flash_copy_engine.v/spi_flash_master.v/
crc32.v) now instantiated in fpga_neural_v2_top.v, bridged to the AR
memory bus via a new flash_mem_adapter.v (byte<->word, matches
nms_memory_manager_stream_wide.v's own real masking convention), and
commandable over SPI via a new spi_host_bridge.v opcode (OP_FLASH_CMD,
0x30) using the same byte-counting idiom as OP_WRITE_JOB. Real balls
now in the LPF: flash_sclk=B2, flash_mosi=E2, flash_miso=F2,
flash_cs_n=F3.

New tb_flash_integration_smoke.v: real SPI-triggered OP_FLASH_READ_BLOCK
verified bit-exact (64/64 bytes) against a real V1 flash_model.v
instance, through the new adapter and the widened (2->3 port) host-arb
arbiter; WRITE_JOB regression confirms the new 3rd port doesn't disturb
existing traffic. Full existing regression re-run clean: D-Stress N=4/
N=8 (bit-exact + data_ready PASS), board-level smoke test (11/11),
isolated spi_host_bridge test (18/18).

Honest, disclosed finding: a full 8-seed P&R re-verification shows
N_SLOTS=4 @ 64MHz regressed from 8/8 to 3/8 PASS (worst 60.18MHz).
Root cause traced via the real critical-path report: the SAME
pre-existing arbiter-to-sdram-backend bottleneck already documented all
session, made worse by flash's added die-area placement pressure --
not a new path through the flash logic itself. N_SLOTS=8 essentially
unchanged (6/8, was 5/8). See decisions.log DEC-0042 for full detail
and open decision points.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-07 18:31:50 +02:00
co-authored by Claude Sonnet 5
parent b4f33388a9
commit 59901a4905
6 changed files with 605 additions and 20 deletions
+98
View File
@@ -2402,3 +2402,101 @@ STATUS: two-flash architecture and FPGA_DATA_READY CLOSED and
verified (bit-exact + real placement). Flash #1's RTL port
(`flash_copy_engine.v` integration into V2's top-level) remains a
real, separate, OPEN task -- ball positions reserved, not wired.
DEC-0042 -- Flash #1 RTL integration completed; real timing regression
found and disclosed (N_SLOTS=4 @ 64MHz 8/8 -> 3/8 PASS)
DATE: 2026-09-07
CONTEXT: closing DEC-0041's own remaining open item -- flash #1
(neural-network weights/graph data) had real ball reservations
(B2/E2/F2/F3) but no RTL port. User asked for this to be completed,
then a meticulous full-stack verification (RTL, tests, timing).
INTEGRATION (real, functional, verified):
- Instantiated the real, unmodified V1 subsystem
(`flash_slot_manager.v`, which owns `flash_copy_engine.v`, which
owns `spi_flash_master.v`; `crc32.v` used internally) directly in
`fpga_neural_v2_top.v` -- zero modifications to any of these four
files, matching this project's own established "reuse, don't
re-derive" precedent for verified V1 modules.
- New file `hardware/v2/rtl/flash_mem_adapter.v`: bridges flash_slot_
manager's real "Port D" (PSRAM-style byte interface: d_req/d_wr/
d_addr/d_wdata/d_rdata/d_ready, 8-bit signed) to the AR-port
convention (word address, 16-bit data, lb_n/ub_n byte-lane masking)
used by `slot_mem_arbiter.v` -- byte<->word convention matches
`nms_memory_manager_stream_wide.v`'s own real, already-verified
result-writeback logic exactly (not invented): word_addr =
byte_addr[ADDR_WIDTH-1:1], byte_addr[0] selects lower/upper lane,
write data replicated to both halves with the mask picking which
the controller actually writes.
- `slot_mem_arbiter.v`'s host-arb instance (the "AR level 2" already
combining compute-side traffic and the SPI host's raw memory port)
widened from N_PORTS=2 to N_PORTS=3, flash at the new highest index
(confirmed lowest-priority in this arbiter's real "lowest-index-
wins" scheme -- matches V1's own "Port D, lowest priority"
convention for this exact traffic class).
- `spi_host_bridge.v`: new real opcode `OP_FLASH_CMD` (0x30), 19
payload bytes (op_code/slot_id/new_offset/new_length/new_type/
ext_psram_addr/ext_length/raw_flash_addr), built with the same
byte-counting state-machine idiom already used by OP_WRITE_JOB
(including the same cs_fell mid-transaction protection). STATUS
byte (0x20) extended: bit3=flash_busy, bit4=flash_done (sticky),
bit5=flash_err (sticky), both cleared by the next OP_FLASH_CMD.
Catalog readback (cat_read_sel/cat_out_*) deliberately NOT wired to
SPI in this pass -- diagnostic-only, not required for the core
LOAD/SAVE/raw-block functionality; a real, disclosed, separate
follow-up.
- Real balls (already reserved in DEC-0041, now actually in the LPF):
flash_sclk=B2, flash_mosi=E2, flash_miso=F2, flash_cs_n=F3.
VERIFICATION (real, not assumed):
- New testbench `tb_flash_integration_smoke.v`: real SPI OP_FLASH_CMD
transaction (OP_FLASH_READ_BLOCK) drives flash_slot_manager through
the new adapter and the new 3rd arbiter port, into the real
sdram_unified_backend/sdram_controller/AS4C32M16SB-7BIN chain,
against a real V1 `flash_model.v` instance preloaded with a known
64-byte pattern -- PASS, 64/64 bytes bit-exact. A WRITE_JOB
regression check in the same file confirms the pre-existing job-
registration path still completes without hanging with the new 3rd
arbiter port present -- PASS.
- Full existing regression re-run, zero functional regressions:
D-Stress N_SLOTS=4 (49927 cycles) and N_SLOTS=8 (49909 cycles), both
256/256 bit-exact vs golden, both `data_ready` PASS; the real board-
level smoke test `tb_fpga_neural_v2_top_smoke.v` (11/11 PASS); the
isolated `tb_spi_host_bridge.v` (18/18 PASS, confirming the new
OP_FLASH_CMD opcode and widened STATUS byte did not disturb the
existing WRITE_JOB/WRITE_MEM/READ_MEM/STATUS behavior).
- Real synthesis (Yosys, 0 errors, 0 new warnings vs. the pre-flash
baseline -- confirmed by exact warning-count diff) and real
nextpnr-ecp5 placement (0 errors, all 4 new flash balls placed
correctly at their reserved sites).
HONEST TIMING FINDING (disclosed, not hidden): a full, real 8-seed
nextpnr-ecp5 P&R matrix with flash #1 now included shows a REAL
regression at the previously rock-solid N_SLOTS=4 @ 64MHz production
baseline:
N_SLOTS=4 @ 64MHz: 8/8 -> **3/8 PASS** (seeds 0,2,5 PASS; worst
60.18MHz seed3, best 67.34MHz seed2).
N_SLOTS=8 @ 64MHz: was 5/8, now 6/8 PASS (seeds 1,3,4,5,6,7 PASS;
worst 61.09MHz seed0, best 67.71MHz seed4) -- essentially
unchanged/slightly better, within seed-to-seed noise.
ROOT CAUSE (real, traced via the actual critical-path report, not
guessed): the worst N=4 seed's critical path is the SAME pre-existing
class already documented all session (`u_arbiter_wide.m_addr` ->
`u_sdram_backend.state`, 84% routing-dominated) -- NOT a new path
through the flash subsystem itself. Flash #1's real logic footprint
(flash_slot_manager + flash_copy_engine + spi_flash_master + crc32,
an entire secondary state-machine-heavy subsystem) measurably
increases die utilization and placement pressure, degrading this
SAME pre-existing bottleneck's achievable placement -- the identical
mechanism already observed when N_SLOTS scaled from 4 to 8 earlier
this session, now triggered by added area rather than added N_SLOTS.
STATUS: flash #1 integration is functionally CLOSED and verified
(bit-exact). N_SLOTS=4 @ 64MHz's own timing closure is now OPEN,
regressed by this integration -- explicitly NOT silently declared
still-closed. This is a real, disclosed trade-off requiring a
decision: accept and pursue a further optimization pass (same class
of fix as ERR-0027/ERR-0028/ERR-0029), constrain placement to isolate
flash logic from the sensitive region, or defer/gate flash #1 behind
a build-time option that keeps the pre-flash timing profile available.
Not decided in this pass -- flagged for the user.