feat: complete Phase 4 SPI RTL (engine, arbiter, top) + real-RAM e2e test
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
This commit is contained in:
@@ -753,10 +753,33 @@ Implement:
|
||||
- status and control.
|
||||
|
||||
- [x] Protocol/opcode set drafted — see §8.1 SPI Protocol v1
|
||||
- [ ] SPI controller RTL (physical layer: shift register, CS/clock sync)
|
||||
- [ ] Register bank RTL (SET_BASE, sticky STATUS, READ_CONFIG constants)
|
||||
- [ ] RAM access passthrough RTL (WRITE_RAM/READ_RAM -> memory_interface)
|
||||
- [ ] Testbench (SPI master BFM + full stack, mirroring neuron_memory_tb.v style)
|
||||
- [x] SPI controller RTL — `rtl/spi_slave.v` (physical layer: Mode 0, MSB-first, 3-stage CDC synchronizer for SCLK/MOSI/CS_N)
|
||||
- [x] Register bank RTL — `rtl/spi_engine.v` (all 8 opcodes: NOP, WRITE_RAM, READ_RAM, RESET, SET_BASE, START, STATUS, READ_OUTPUT, READ_CONFIG; sticky clear-on-read STATUS.done)
|
||||
- [x] RAM access passthrough RTL — `rtl/mem_arbiter.v` (fixed-priority arbiter, neuron_memory > spi_engine) + shared `int8_memory_access` instance in `rtl/spi_neuron_top.v`
|
||||
- [x] Testbenches: `sim/spi_slave_tb.v` (4 tests), `sim/spi_engine_tb.v` (10 tests, synthetic RAM), `sim/spi_neuron_top_tb.v` (end-to-end, **real** `psram_model.v`, no synthetic mock — RESET/READ_CONFIG/WRITE_RAM/READ_RAM/SET_BASE/START/STATUS/READ_OUTPUT all exercised purely over simulated SPI)
|
||||
|
||||
**Real-toolchain verification (Yosys + nextpnr-ecp5 + ecppack, 2026-09-02):**
|
||||
|
||||
- `spi_slave.v` alone: PASS, Fmax 403.23 MHz.
|
||||
- `spi_engine.v` alone: PASS, Fmax 191.31 MHz. Neither uses any DSP.
|
||||
- `spi_neuron_top.v` (full integration: SPI + arbiter + neuron_memory
|
||||
+ PSRAM chain), N_NEURONS=1: **FAIL at 80 MHz** — Fmax ~52.58 MHz
|
||||
(PARALLEL=8) / ~55.85 MHz (PARALLEL=2, the benchmark's own
|
||||
80 MHz-passing config in isolation). The critical path in both
|
||||
cases is entirely inside `neuron_parallel.v`'s saturation
|
||||
comparator (`> 127`, `rtl/neuron_parallel.v:127`) — zero
|
||||
contribution from the new SPI/arbiter logic — but its routed delay
|
||||
is ~57% worse than in the isolated benchmark (17.91 ns vs.
|
||||
11.38 ns) due to placement/routing congestion once the SPI +
|
||||
PSRAM logic shares the fabric with it, not resource exhaustion
|
||||
(DSP utilization only 2%). This means the current auto-placed
|
||||
full system would need to run around 50-56 MHz to stay within
|
||||
timing margin at speed grade -8, not the 80 MHz target — a
|
||||
system-level floorplanning/pipelining problem, out of scope here
|
||||
and left for Phase 7 (Optimization: "pipeline depth", "FPGA
|
||||
resource utilization"). It does not affect functional correctness
|
||||
(verified independently, in simulation, against real PSRAM
|
||||
timing) or synthesizability (0 CHECK-pass problems, no latches).
|
||||
|
||||
## Phase 5 — Multi-Layer Network
|
||||
|
||||
|
||||
Reference in New Issue
Block a user