Implements the rest of the SPI interface (docs §8.1) on top of
spi_slave.v from the previous commit:
- rtl/spi_engine.v: opcode FSM + register bank, all 8 opcodes (NOP,
WRITE_RAM, READ_RAM, RESET, SET_BASE, START, STATUS, READ_OUTPUT,
READ_CONFIG). tx_byte is driven combinationally from live state
(not reactively on tx_byte_req), applying the prefetch-vs-consume
contract documented on spi_slave.v. STATUS.done is a sticky,
clear-on-read latch. RAM master port uses the same byte-level
convention as neuron_memory.v's external mem_* port.
- rtl/mem_arbiter.v: fixed-priority (neuron_memory > spi_engine)
grant-and-forward arbiter sharing one byte-level memory port
between spi_engine's WRITE_RAM/READ_RAM and neuron_memory's own
X/W/bias reads during a run.
- rtl/spi_neuron_top.v: full integration -- spi_slave -> spi_engine
-> mem_arbiter -> a single shared int8_memory_access ->
memory_interface -> psram_controller -> PSRAM pins. neuron_memory's
rst is global rst OR'd with the RESET opcode's soft-reset pulse.
The host has no direct electrical path to the RAM, only through
this chain.
Testing:
- sim/spi_engine_tb.v: 10 tests (one per opcode + WRITE_RAM/READ_RAM,
START idle-vs-busy, STATUS sticky/clear-on-read, extra-MOSI-bytes-
ignored, back-to-back transactions) against a synthetic 2-cycle-
latency RAM model, isolating the opcode FSM from PSRAM timing.
Found and fixed two testbench-only bugs (RTL needed no change):
the same delta-zero clock-edge race as spi_slave_tb.v (blocking
`nm_done=1` landing on the same sim time as a posedge -- fixed via
negedge-based pulsing) and a missing RAM sentinel initialization.
- sim/spi_neuron_top_tb.v: end-to-end test against the **real**
psram_model.v (not a mock) -- RESET/READ_CONFIG/WRITE_RAM/
READ_RAM/SET_BASE/START/STATUS/READ_OUTPUT all driven purely over
simulated SPI. 3/3 scenarios (sum, saturation, ReLU) pass on the
first attempt; confirms the arbiter and shared byte<->word bridge
are correct against real PSRAM timing, not just a synthetic mock.
Real-toolchain verification (Yosys + nextpnr-ecp5 + ecppack):
spi_slave.v and spi_engine.v synthesize clean and comfortably clear
80 MHz in isolation (403 MHz / 191 MHz, no DSP usage). The full
spi_neuron_top.v integration, however, does NOT meet 80 MHz
(~52-56 MHz depending on PARALLEL) -- the critical path is entirely
inside neuron_parallel.v's existing saturation comparator (no
contribution from the new SPI/arbiter logic), but its routed delay
is ~57% worse than in the isolated benchmark due to placement/
routing congestion once SPI + PSRAM logic shares the fabric with
it, not resource exhaustion (2% DSP usage). Documented as a Phase
4/7 finding in docs/FPGA-NeuralNetwork-Engine.md -- a floorplanning/
pipelining problem for Phase 7, not a functional-correctness issue
(verified independently in simulation against real PSRAM timing).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt