fix: real SPI MISO bit-corruption bug found+fixed; add register file + pin plan (EXP-0075)

Found a real, previously-masked bug in spi_host_bridge_v3.v's physical
layer (inherited unchanged from V1/V2): the bit_count==0 MISO bypass
corrupts the last bit of any multi-byte response whose value happens
to end in a 1 -- every prior test's response data coincidentally
ended in 0, hiding it until the new DEVICE_ID register (0x...01)
exposed it via a real bit-exact mismatch. Fixed by removing the
bypass (verified unnecessary for this protocol's actual usage).

Added REG_WRITE/REG_READ opcodes (0x30/0x31) and a register file
(DEVICE_ID/CONTROL/STATUS/N_SLOTS) for general device control beyond
job submission, per explicit user request. Full regression: 38/38
PASS, including new cases specifically targeting the bit-corruption
bug for both REG_READ and READ_MEM.

New hardware/v3/constraints/n2_system_ddr3_top.xdc: reserves the
FPGA's dedicated Master-SPI config-flash pins (found colliding with
auto-placed design ports in the real routed checkpoint) and assigns
the neural-processor management SPI to real, verified-free, edge-
adjacent pins on xc7a100tcsg324-2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-19 18:31:51 +02:00
co-authored by Claude Sonnet 5
parent 835ae881dc
commit ca17765fe3
5 changed files with 440 additions and 10 deletions
+93
View File
@@ -4644,3 +4644,96 @@ disclosed architectural gaps (real activation-fetch engine, N-slot
scaling beyond 2, board LOC constraints for SPI once the PCB pinout
is fixed) are the natural next steps once the user is back and can
weigh in on priority.
EXP-0075 -- real SPI physical-layer bug found+fixed (masked since V1);
register-file control interface added to spi_host_bridge_v3.v
(2026-09-19, same autonomous continuation, user's own explicit
request: "Creiamo un sistema configurabile con dei registri")
CONTEXT: extending spi_host_bridge_v3.v with REG_WRITE/REG_READ
opcodes (0x30/0x31) and a small register file (0x00 DEVICE_ID,
0x01 CONTROL, 0x02 STATUS, 0x03 N_SLOTS) for general device control/
status beyond job submission and raw memory access. Wrote hardware/
v3/sim/tb_spi_host_bridge_v3.v tests for the new opcodes; the
DEVICE_ID register (0x4E505601, whose LSB=0x01 has bit0 SET) was the
first response value in this module's entire test history whose last
transmitted bit is a real 1 -- every prior multi-byte MISO response
this project has ever tested (WRITE_JOB/READ_MEM's 0x1234, STATUS's
various bit patterns) coincidentally had a LAST bit of 0.
ROOT CAUSE (found via a DUT-internal $display, not guessing): the
physical layer's `assign miso = (cs_active && bit_count==3'd0) ?
tx_byte[7] : miso_shift_bit` -- present unchanged since hardware/v1/
rtl/spi_slave.v, carried into every SPI bridge this project has ever
built -- has a real bug. `bit_count` reads 0 not only right before a
fresh byte's first bit, but also for the ENTIRE remainder of the bit
period immediately AFTER a byte's LAST bit was sampled (it doesn't
advance again until the next byte's own first sampling edge). During
that whole tail window, the bypass shows tx_byte[7] (context for a
hypothetical NEW byte) instead of the correctly-prepared
miso_shift_bit (the OLD byte's real last bit) -- corrupting the last
bit of every response byte, WHENEVER a real (non-instantaneous) SPI
master's sample point falls inside that tail window, which is the
normal case for any real host. This was invisible in every previous
verified opcode purely because every test's own response data
happened to have a last bit of 0, matching the substituted tx_byte[7]
by coincidence, not because the value was actually correct.
FIX: removed the bypass entirely -- `assign miso = miso_shift_bit`.
Verified this doesn't need it: the special case only matters for a
genuinely fresh byte with ZERO prior falling edges in the current CS
session, which never happens for any of this protocol's real response
bytes (always preceded by the opcode byte and other payload bytes,
so miso_shift_bit is always already primed by the ordinary falling-
edge mechanism). Full regression re-run: 38/38 PASS, including a new
READ_MEM case (0x5679, LSB bit0=1) added specifically to catch this
class of bug for the memory-read path too, and the original WRITE_
JOB/STATUS/READ_MEM tests all still pass unchanged.
SCOPE (disclosed, not fixed today): hardware/v1/rtl/spi_slave.v and
hardware/v2/rtl/spi_host_bridge.v carry the SAME bypass, unchanged --
V2 is the frozen/archived ECP5 baseline (fork-before-promote
discipline, not touched this session) and V1 is even older, so
neither was modified, but BOTH almost certainly have the identical
real bug, silently corrupting the last bit of any MISO response whose
value happens to end in a 1. This does not affect any of this
session's DDR3/P&R work (independent module, no interaction with
memory timing), but is a real, disclosed correctness gap in the
V1/V2 SPI bridges for anyone revisiting them.
Also (separate, smaller): a `fork`/`join`-based watchdog was needed
for the new REG_WRITE test (Test K) -- REG_WRITE applies its effect
immediately on the last data byte, not after CS rises like RESET
does, so a watchdog that only starts watching after CS rises misses
the pulse entirely; fixed by running the watchdog concurrently with
the triggering spi_byte() call (same fork/join technique as
tb_sdram_arbiter_n.v's own one-shot-pulse watchers) -- another real,
now-documented testbench-timing lesson, not an RTL defect.
DECISION: spi_host_bridge_v3.v's physical layer is now genuinely
correct (not just "correct on the specific bit patterns tested so
far"). The register file (DEVICE_ID/CONTROL/STATUS/N_SLOTS, opcodes
0x30/0x31) gives host software a general-purpose control/status path
beyond job submission, per the user's own request.
Real board-level finding, same session: querying the routed n2_
system_ddr3_top's real device checkpoint (get_package_pins/get_ports
on xc7a100tcsg324-2) showed Vivado had auto-placed several of this
design's own unconstrained ports (job_out_done, two result-data bits)
directly onto the FPGA's DEDICATED Master-SPI configuration-flash
pins (FCS_B=L13, RDWR_B=R16, CSI_B=V15) -- a real conflict with any
future config-flash wiring. New hardware/v3/constraints/n2_system_
ddr3_top.xdc: (1) PROHIBITs those pins (plus D00_MOSI/D01_DIN/EMCCLK)
from ever being used by this design's own ports; (2) assigns the
neural-processor management SPI (sclk/mosi/miso/cs_n) to real,
verified-free pins A15/B16/B17/A16 (bank 15, package edge column,
physically adjacent for short PCB traces), chosen per the user's own
request ("pin più esterni possibile e vicini").
next_action: re-run the real in-context P&R (n2_system_ddr3_top.v +
new XDC + the register-file/physical-layer fixes) for a final,
up-to-date timing/resource signoff; write up the FPGA configuration
(boot) options for the user -- JTAG vs Master SPI with an external
config flash, using the real dedicated pins found this session
(PROGRAM_B=P9, INIT_B=P7, DONE=P10, M0=P12/M1=P13/M2=P11, CCLK=E9,
D00_MOSI=K17, D01_DIN=K18, FCS_B=L13).