Wires the already-present layer_sequencer.v into the SPI stack: - spi_engine.v: RUN_NETWORK opcode (0x23) + SET_BASE selectors for table_base/buf_a_base/buf_b_base; STATUS.busy/done extended to track the sequencer (seq_busy/seq_done) alongside neuron_memory directly, so done latches on the last layer only. - spi_neuron_top.v: instantiates layer_sequencer, muxes neuron_memory's control inputs between it (while seq_busy) and spi_engine's direct-drive path (legacy single-layer mode), wires the sequencer's own RAM master to mem_arbiter's Port C. Found and fixed a real race while writing the end-to-end test: STATUS's sticky/clear-on-read done bit read its value live/combinationally during transmission and cleared unconditionally on any STATUS read. A done_event landing mid-transmission of a STATUS response byte could be silently dropped -- the host would receive a stale byte while the sticky bit was cleared regardless, hanging any host polling STATUS in a loop. Present since Phase 4, not RUN_NETWORK-specific; only surfaced under this test's continuous polling. Fixed by latching a status_snapshot at opcode-accept time and gating the clear on what was actually transmitted. Tests: spi_engine_tb.v gains RUN_NETWORK/SET_BASE opcode tests (K/L); new layer_sequencer_tb.v unit-tests the sequencer FSM directly (descriptor table, ping-pong buffer addressing, byte-exact copy-out); new spi_neuron_top_runnetwork_tb.v drives a real 2-layer network over simulated SPI end to end (real neuron_memory + PSRAM, hand-computed expected output) and confirms the legacy single-layer path still works afterward. All existing testbenches still pass.
30 KiB
FPGA Neural Network Engine
Hardware Neural Network Engine based on FPGA + dedicated RAM.
The project implements a parametric hardware accelerator for neural networks, designed to be reusable across different embedded systems and applications.
The fundamental design principle is that the neural-network computation is performed entirely inside the FPGA, while the host system communicates with the engine through a simple hardware-independent interface such as SPI.
1. Project Goal
The goal of this project is to develop a reusable Neural Network Engine implemented in FPGA hardware.
The engine is composed of:
- FPGA;
- dedicated RAM connected to the FPGA;
- host interface, initially SPI and potentially Dual SPI.
The host system is not part of the neural-network computational datapath.
Possible host systems include:
- Linux SoCs;
- Raspberry-Pi-like systems;
- ESP32;
- microcontrollers;
- other embedded processors;
- development PCs.
The same Neural Network Engine architecture should therefore be usable in completely different systems.
HOST SYSTEM
┌─────────────────────────┐
│ │
│ Linux / ESP32 / MCU │
│ │
│ Configuration │
│ Training │
│ Control │
└────────────┬────────────┘
│
SPI / Dual SPI
│
▼
┌─────────────────────────┐
│ FPGA │
│ │
│ Neural Network Engine │
│ │
│ Compute / Control │
│ │
└────────────┬────────────┘
│
│
Dedicated RAM
2. Architectural Principle
The FPGA is the actual neural-network accelerator.
The RAM required by the neural network is physically associated with the FPGA and is not part of the host system memory.
The host only provides:
- configuration;
- network parameters;
- input data;
- control;
- result retrieval.
The neural-network calculations themselves are executed by the FPGA.
This separation is a fundamental architectural requirement.
3. Hardware Configuration vs Network Configuration
An important distinction is made between the hardware architecture of the accelerator and the parameters of the neural network.
3.1 FPGA hardware configuration
The physical architecture of the Neural Network Engine is defined when the FPGA design is synthesized and implemented.
Typical hardware parameters include:
N_INPUTS
N_NEURONS
N_LAYERS
PARALLEL
DATA_WIDTH
ACCUMULATOR_WIDTH
These parameters can therefore be Verilog/SystemVerilog parameters or equivalent synthesis-time configuration values.
For example:
N_INPUTS = 32
N_NEURONS = 4
PARALLEL = 8
defines a specific hardware implementation optimized for that architecture.
The resulting FPGA bitstream contains the corresponding datapath.
3.2 Neural-network configuration
Once the FPGA has been configured and initialized, the actual neural-network parameters can be loaded through the host interface.
These parameters may include:
- weights;
- biases;
- activation parameters;
- quantization parameters;
- network-specific constants.
These values are stored in the RAM associated with the FPGA.
Therefore:
FPGA BITSTREAM
│
│ defines hardware architecture
▼
┌─────────────────────┐
│ Neural Network │
│ Hardware Engine │
└──────────┬──────────┘
│
│ loads
▼
┌─────────────────────┐
│ Dedicated RAM │
│ │
│ weights │
│ biases │
│ parameters │
│ buffers │
└─────────────────────┘
This provides an important separation between hardware specialization and network data.
4. Application-Specific Neural Networks
The Neural Network Engine is not intended to implement one fixed neural network.
Instead, each application can define its own network.
For example:
Application A
16 inputs
8 neurons
1 output
Application B
32 inputs
16 neurons
4 outputs
Application C
64 inputs
multiple layers
custom parallelism
The FPGA hardware can then be generated specifically for the required architecture.
This allows the design to exploit the FPGA resources efficiently rather than implementing a completely generic and potentially inefficient neural-network processor.
5. Parametric Compute Engine
The current implementation contains a parametric neural-network layer.
A validated configuration is:
N_INPUTS = 32
N_NEURONS = 4
PARALLEL = 8
The datapath processes the inputs in parallel groups.
Conceptually:
32 inputs
│
├── 8 parallel MACs
│
├── 8 parallel MACs
│
├── 8 parallel MACs
│
└── 8 parallel MACs
│
▼
Accumulation
│
▼
Bias
│
▼
Activation
│
▼
Output
The architecture is intended to scale by changing the synthesis parameters.
6. Current Functional Validation
The 32 × 4 / PARALLEL = 8 configuration has been successfully simulated.
Test output:
========================================
PARAMETRIC LAYER TEST
N_INPUTS = 32
N_NEURONS = 4
PARALLEL = 8
========================================
PASS N0: 8192
PASS N1: 4096
PASS N2: 0 (ReLU)
PASS N3: 16384
========================================
PARAMETRIC TEST PASSED
========================================
Validated functionality:
- multiple inputs;
- multiple neurons;
- parallel MAC processing;
- accumulation across multiple input groups;
- bias handling;
- independent neuron outputs;
- ReLU activation;
- parametric layer architecture.
The corresponding test has been committed to the repository.
Commit:
test: validate parametric 32x4 layer with parallelism 8
7. FPGA Boot and Initialization
The FPGA is configured during system initialization using its normal FPGA configuration mechanism.
The FPGA bitstream defines the hardware architecture of the Neural Network Engine.
Conceptually:
Power-on
│
▼
FPGA configuration
│
│ bitstream
▼
Neural Network Engine available
│
▼
Host initialization
│
│ SPI
▼
Load network parameters
│
▼
Load weights / biases
│
▼
Engine ready
This means that the host does not dynamically construct the FPGA datapath during normal operation.
The datapath already exists in hardware.
The host configures the network data that the datapath operates on.
8. Host Interface
The primary external interface is intended to be:
SPI
with possible future support for:
Dual SPI
The interface must remain independent of the host operating system.
The same hardware protocol should therefore be usable from:
Linux
ESP32
MCU
PC
The host interface should provide access to:
- control registers;
- status;
- network configuration;
- RAM;
- input data;
- output data;
- start/stop control;
- completion status.
A conceptual command sequence is:
RESET
│
▼
CONFIGURE
│
▼
LOAD NETWORK PARAMETERS
│
▼
LOAD WEIGHTS
│
▼
LOAD BIASES
│
▼
LOAD INPUT
│
▼
START
│
▼
WAIT FOR DONE
│
▼
READ OUTPUT
8.1 SPI Protocol v1 (draft, 2026-09-02)
Concrete opcode-level draft of the section above, written before any Phase 4 RTL. Opcode values and the exact set of commands are illustrative/example at this stage, not frozen — the framing rules (MSB-first, explicit length, sticky STATUS.done) and the two decisions already made (explicit length field over CS-delimited streaming; a runtime READ_CONFIG command) are the parts intended to stick; the opcode table itself is expected to be revised as Phase 4 RTL work starts.
Physical layer: SPI Mode 0 (CPOL=0, CPHA=0), MSB-first, single SPI for v1 (Dual SPI is a future extension per §8, not addressed here). The FPGA is always SPI slave. One command per CS-low period; byte 0 of every transaction is the opcode.
Multi-byte fields are big-endian (most significant byte first).
Byte addresses are ADDR_WIDTH-bit (22 bits today, from
rtl/neuron_memory.v), carried in a 3-byte field with the top 2 bits
reserved as 0.
Length is explicit, not CS-edge-delimited: WRITE_RAM/READ_RAM
carry a 2-byte length field, so the SPI controller only needs a byte
counter, not CS-edge detection mid-transfer.
Opcode table
| Opcode | Name | Payload (host → FPGA) | Response (FPGA → host) | Function |
|---|---|---|---|---|
| 0x00 | NOP | — | — | No operation (idle/dummy clocking) |
| 0x01 | WRITE_RAM | addr(3B) + len(2B) + len data bytes |
— | Write a block into PSRAM (X, weights, bias, network params) |
| 0x02 | READ_RAM | addr(3B) + len(2B) | len data bytes |
Read a block back from PSRAM |
| 0x0F | RESET | — | — | Synchronous reset pulse to the compute engine (neuron_memory) and clears the STATUS latch below. Does not erase PSRAM contents. Kept as a distinct opcode from NOP. |
| 0x10 | SET_BASE | sel(1B) + addr(3B) | — | Sets x_base(sel=0) / w_base(sel=1) / bias_addr(sel=2) / table_base(sel=3) / buf_a_base(sel=4) / buf_b_base(sel=5) |
| 0x20 | START | — | — | Pulses start on neuron_memory directly (single-layer/manual path); ignored (no-op) if the engine is busy in any form (single-layer or a RUN_NETWORK job) |
| 0x21 | STATUS | — | 1 byte | bit0=busy (live, OR of the single-layer and RUN_NETWORK busy signals), bit1=done (sticky, clear-on-read; latches on the final layer's completion for a RUN_NETWORK job, not each intermediate layer), bits7:2 reserved=0 |
| 0x22 | READ_OUTPUT | — | N_NEURONS bytes |
y_bus, neuron-major (byte 0 = neuron 0) |
| 0x23 | RUN_NETWORK | num_layers(1B) | — | Phase 5: pulses layer_sequencer's run_start to chain num_layers (1..N_LAYERS) runs of neuron_memory using the descriptor table at table_base and the ping-pong buffers at buf_a_base/buf_b_base; layer 0 reads from x_base. Ignored (no-op) if the engine is already busy. |
| 0x30 | READ_CONFIG | — | 8 bytes | Hardware config record, see below |
Why STATUS.done is sticky / clear-on-read: in rtl/neuron_memory.v
done is a single-cycle pulse (asserted for exactly one clock in
STATE_WAIT_N, deasserted the next cycle). A host polling over SPI
— orders of magnitude slower than the FPGA clock — would almost
certainly miss a raw one-cycle pulse. The SPI register bank must
therefore latch done into a sticky bit on the pulse, and clear it
when the host issues STATUS (or RESET), not sample the raw
neuron_memory.done signal directly. busy has no such problem
(it is level-held for the whole computation) and can be read live.
READ_CONFIG payload (fixed 8 bytes, lets one host firmware build work across different bitstreams without recompiling):
| Byte(s) | Field | Source |
|---|---|---|
| 0 | ADDR_WIDTH (bits) |
neuron_memory.ADDR_WIDTH |
| 1–2 | N_INPUTS (16-bit BE) |
neuron_memory.N_INPUTS |
| 3 | N_NEURONS |
neuron_memory.N_NEURONS |
| 4 | PARALLEL |
neuron_memory.PARALLEL |
| 5 | DATA_WIDTH (bits) |
neuron_memory.DATA_WIDTH |
| 6–7 | protocol version (16-bit BE) | 0x0001 for this spec |
Example session (fills in the conceptual sequence above with concrete opcodes):
RESET -> 0x0F
READ_CONFIG -> 0x30 (host learns N_INPUTS/N_NEURONS/...)
WRITE_RAM (weights) -> 0x01 ...
WRITE_RAM (biases) -> 0x01 ...
SET_BASE (X/W/BIAS) -> 0x10 x3
WRITE_RAM (input X) -> 0x01 ...
START -> 0x20
poll STATUS -> 0x21 (until done bit set; clears on this read)
READ_OUTPUT -> 0x22
RUN_NETWORK (Phase 5) example session:
WRITE_RAM (layer descriptor table) -> 0x01 ... (N_LAYERS entries of w_base(3B)+bias_addr(3B), MSB-first)
WRITE_RAM (weights/biases per layer, X for layer 0) -> 0x01 ...
SET_BASE (X/TABLE/BUF_A/BUF_B) -> 0x10 x4
RUN_NETWORK(num_layers) -> 0x23 <num_layers>
poll STATUS -> 0x21 (until done bit set; clears on this read)
READ_OUTPUT -> 0x22 (final layer's y_bus)
Not yet decided / explicitly out of scope for v1: Dual SPI framing,
a CRC/checksum on transfers (SPI is assumed reliable for a
board-level trace in v1). Multi-layer sequencing itself (RUN_NETWORK,
opcode 0x23) is implemented per rtl/layer_sequencer.v and the table
above.
9. Dedicated FPGA RAM
The RAM is considered part of the Neural Network Engine.
It is not intended to be supplied by the host system.
Depending on the final architecture, RAM may contain:
Weights
Biases
Input buffers
Intermediate layer buffers
Output buffers
Network parameters
The memory architecture must be designed according to:
- number of parallel MAC units;
- data width;
- required bandwidth;
- number of layers;
- buffering requirements;
- FPGA block-RAM resources;
- possible external RAM requirements.
The preferred architecture is that the FPGA directly controls this memory.
10. Training
Training and inference are conceptually separated.
The first implementation does not require the FPGA to perform the complete training process.
Training can be performed externally:
PC / Linux / other host
│
│ training
▼
Network weights
│
│ SPI
▼
FPGA RAM
The FPGA then performs inference using the resulting parameters.
This approach greatly reduces the complexity of the initial hardware implementation.
However, the architecture should not prevent future implementation of hardware-assisted or fully hardware-based training.
11. Inference
During inference, the host only supplies input data and retrieves the result.
HOST
│
Input
│
│ SPI
▼
┌───────────────┐
│ FPGA │
│ │
│ Neural Network│
│ Engine │
│ │
└───────┬───────┘
│
Output
│
│ SPI
▼
HOST
The host is not involved in the individual MAC operations.
This provides:
- deterministic computation;
- reduced host workload;
- hardware parallelism;
- predictable latency;
- independence from the host CPU architecture.
12. Multi-Layer Architecture
The current implementation starts from a single parametrized layer.
The intended architecture is eventually:
Input
│
▼
┌──────────────┐
│ Layer 0 │
└──────┬───────┘
│
▼
┌──────────────┐
│ Layer 1 │
└──────┬───────┘
│
▼
┌──────────────┐
│ Layer 2 │
└──────┬───────┘
│
▼
Output
Intermediate data will be stored in FPGA-controlled memory buffers.
The number and size of layers should ultimately be part of the hardware generation process.
13. Reusability
The main purpose of the architecture is reuse.
A future project should be able to use the same general Neural Network Engine architecture with a different hardware configuration.
For example:
Project A
N_INPUTS = 32
N_NEURONS = 8
PARALLEL = 8
Project B
N_INPUTS = 64
N_NEURONS = 16
PARALLEL = 16
Project C
N_INPUTS = 128
N_NEURONS = 32
PARALLEL = 32
The HDL architecture remains conceptually the same while synthesis parameters generate an implementation appropriate for the target application.
14. Design Philosophy
The project should be considered a:
Reusable FPGA Neural Network Accelerator Platform
rather than a single neural-network implementation.
The application determines:
Input size
Network topology
Number of layers
Number of neurons
Parallelism
Numerical precision
Activation functions
Memory requirements
Performance requirements
The hardware generator then produces the corresponding FPGA implementation.
15. Development Roadmap
Phase 1 — Parametric Layer
- Parametric inputs
- Parametric neurons
- Parametric parallelism
- Accumulation
- Bias
- ReLU
- 32×4 / P=8 functional test
Phase 2 — Parameter Sweep
Validate multiple combinations of:
N_INPUTS
N_NEURONS
PARALLEL
including configurations where the number of inputs is not an exact multiple of the parallelism.
- Exact-multiple sanity configs (32×8, 64×32)
- Non-exact-multiple configs (30×8, 20×16)
- Degenerate config, PARALLEL > N_INPUTS (4×8)
- Sweep testbench:
sim/parameter_sweep_tb.v
Findings — FIXED (2026-09-02):
neuron_parallel.vcomputedGROUPS = N_INPUTS / PARALLELwith integer division. WhenN_INPUTSwas not an exact multiple ofPARALLEL, only the firstGROUPS * PARALLELinputs were ever read by the accumulator — the remainder was silently dropped (no error, no warning). Confirmed for 30×8 → only 24 of 30 inputs summed, and 20×16 → only 16 of 20 inputs summed.- If
PARALLEL > N_INPUTS,GROUPS = 0and the controller'sgroup_index == GROUPS-1terminal condition was never satisfied: the neuron enteredbusyand never asserteddone(confirmed hang, 500-cycle watchdog in the sweep bench). - Both share the same root cause (
N_INPUTS % PARALLEL != 0, degeneratePARALLEL > N_INPUTSincluded) and both are now rejected at elaboration time, in simulation and synthesis alike, by agenerateguard added toneuron_parallel.v(instantiates a deliberately undefined module when the parameter combination is invalid — zero cost, zero behavior change for any valid configuration). The validated datapath itself (mac8/mac_unit/accumulation/ReLU/saturation) was not modified. Seesim/neuron_parallel_guard_negative_nonmultiple_tb.vandsim/neuron_parallel_guard_negative_degenerate_tb.vfor the negative-test proof, andsim/parameter_sweep_tb.vfor the updated positive sweep (now valid-configs-only, includingPARALLEL=2andPARALLEL=4, the two best-performing values fromdocs/FPGA-Neural-Datapatch-Benchmark.md).
Phase 3 — Memory Architecture
Define:
-
weight memory;
-
bias memory;
-
input buffers;
-
output buffers;
-
intermediate buffers;
-
memory addressing;
-
bandwidth requirements.
-
neuron_memory.v: single-neuron memory integration (N_NEURONS=1) -
neuron_memory.v: multi-neuron memory integration (N_NEURONS>1) -
Intermediate/multi-layer buffers (deferred to Phase 5)
-
Bandwidth analysis against PSRAM timing (deferred to Phase 7)
Multi-neuron design (2026-09-02): neuron_memory.v now takes an
N_NEURONS parameter and loops over neurons in memory: X is read
once (shared input vector), and for each neuron in turn W and
bias are re-read from PSRAM and fed to a single, reused
neuron_parallel instance — memory-bound by design, one neuron
computed at a time, no change to the validated compute datapath.
Addressing follows the same neuron-major convention as layer.v:
neuron n's weights live at w_base + n*N_INPUTS bytes, its bias at
bias_addr + n. Output is now y_bus (packed, DATA_WIDTH*N_NEURONS
bits, neuron-major), replacing the old single-neuron y port.
Validated end to end through the full memory stack
(memory_interface + psram_controller + psram_model) in
sim/neuron_memory_multi_tb.v (N_NEURONS=3: scale, larger value,
ReLU). sim/neuron_memory_tb.v (N_NEURONS=1) still passes unchanged,
confirming backward compatibility.
Phase 4 — SPI Interface
Implement:
-
SPI controller;
-
register map;
-
RAM access;
-
configuration protocol;
-
input/output protocol;
-
status and control.
-
Protocol/opcode set drafted — see §8.1 SPI Protocol v1
-
SPI controller RTL —
rtl/spi_slave.v(physical layer: Mode 0, MSB-first, 3-stage CDC synchronizer for SCLK/MOSI/CS_N) -
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) -
RAM access passthrough RTL —
rtl/mem_arbiter.v(fixed-priority arbiter, neuron_memory > spi_engine) + sharedint8_memory_accessinstance inrtl/spi_neuron_top.v -
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, realpsram_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.valone: PASS, Fmax 403.23 MHz.spi_engine.valone: 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).
- 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
Phase 5 — Multi-Layer Network
Implement:
-
multiple layers;
-
intermediate buffers;
-
layer sequencing;
-
configurable activation functions.
-
layer_sequencer.v: chains up toN_LAYERSruns of a single, reusedneuron_memoryinstance, reading each layer'sw_base/bias_addrfrom a host-written descriptor table and ping-ponging each layer's output between two RAM buffers (layer 0 reads the externalx_base; layer k>0 reads the buffer layer k-1 wrote) -
mem_arbiter.v: extended to a third port (Port C, priority B > C > A) for the sequencer's own RAM master access -
spi_engine.v:RUN_NETWORKopcode (0x23) +SET_BASEselectors fortable_base/buf_a_base/buf_b_base;STATUS.busy/STATUS.doneextended to track the sequencer (seq_busy/seq_done) as well asneuron_memorydirectly, sodonelatches on the last layer only, not each intermediate one — see §8.1 -
spi_neuron_top.v: instantiates the sequencer and muxesneuron_memory's control inputs between it (whileseq_busy) andspi_engine's direct-drive path (legacy single-layer mode) -
Testbenches:
sim/layer_sequencer_tb.v(2-layer run: descriptor table, ping-pong addressing verified by address not just value, byte-exact output-buffer copy,seq_busyheld across the layer boundary,seq_donefires exactly once);sim/spi_engine_tb.vgained tests K/L (newSET_BASEselectors,RUN_NETWORKaccept/busy-ignore gating,STATUSsemantics) — both use a mockedneuron_memory, same pattern as Phase 4'sspi_engine_tb.v -
Configurable activation functions (not started —
neuron_parallel.vstill has ReLU hardwired) -
Real-toolchain (Yosys + nextpnr-ecp5) synthesis/Fmax check of the extended
spi_neuron_top.v -
sim/spi_neuron_top_runnetwork_tb.v: real end-to-end test,RUN_NETWORKdriven purely over simulated SPI against the realneuron_memory+ PSRAM chain (N_INPUTS=N_NEURONS=4, PARALLEL=2, 2 layers, hand-computed expected output verified via bothREAD_OUTPUTand aREAD_RAMofbuf_b_base, plusbuf_a_base's intermediate layer-0 output) — all PASS, and confirms the mux correctly handsneuron_memoryback to the legacy single-layerSTARTpath afterward
Bug found and fixed while writing the above test (2026-09-02): a real race
in the STATUS.done sticky/clear-on-read mechanism (§8.1), present since Phase 4
and not specific to RUN_NETWORK — it only needed continuous STATUS polling
racing a done transition to surface, which the new end-to-end test's
wait_done polling loop finally did. tx_byte's OP_STATUS case read
status_done_sticky/busy live/combinationally for the whole ST_RESP
window, while the sticky bit was cleared unconditionally on any STATUS read
(status_read_now). If done_event landed while a STATUS response byte was
already mid-transmission, the byte actually shifted out to the host could
still be the stale pre-done value while the engine simultaneously treated the
read as having delivered done and cleared it — silently dropping the
transition forever, hanging any host polling STATUS in a tight loop. Fixed in
rtl/spi_engine.v by latching a status_snapshot register once, at
OP_STATUS opcode-accept time, and gating the sticky clear on
status_snapshot[1] (i.e. only clear if the byte actually transmitted showed
done=1) instead of clearing unconditionally on every STATUS read. A
done_event that arrives too late for one snapshot is now reported on the
next poll instead of being lost. All existing testbenches (spi_slave_tb.v,
spi_engine_tb.v, spi_neuron_top_tb.v, layer_sequencer_tb.v) still pass
unchanged.
Phase 6 — Host Software
Develop host-side drivers for:
- Linux;
- ESP32.
The same FPGA protocol should be usable by both.
Phase 7 — Optimization
Evaluate:
- pipeline depth;
- MAC parallelism;
- memory bandwidth;
- numerical precision;
- FPGA resource utilization;
- latency;
- throughput.
Phase 8 — Optional Hardware Training
Investigate:
- backpropagation;
- gradient calculation;
- weight updates;
- hardware-assisted training.
16. Long-Term Vision
The final objective is to create a reusable hardware block that can be integrated into different future MIKILAB projects.
APPLICATION
│
┌─────────────┴─────────────┐
│ │
Linux ESP32
│ │
└─────────────┬─────────────┘
│
SPI / Dual SPI
│
▼
┌────────────────────────┐
│ FPGA │
│ │
│ Neural Network Engine │
│ │
│ ┌────────────────────┐ │
│ │ Control │ │
│ ├────────────────────┤ │
│ │ Input Interface │ │
│ ├────────────────────┤ │
│ │ NN Compute Core │ │
│ ├────────────────────┤ │
│ │ Activation │ │
│ ├────────────────────┤ │
│ │ Output Interface │ │
│ └────────────────────┘ │
│ │
│ Dedicated RAM │
│ │
└────────────────────────┘
The host platform can change without changing the fundamental Neural Network Engine architecture.
The FPGA becomes a dedicated neural-computation peripheral, analogous to other hardware accelerators, but optimized specifically for the neural-network topology required by each application.
17. Current Status
| Component | Status |
|---|---|
| Parametric neuron layer | OK Working |
| Parametric input count | OK |
| Parametric neuron count | OK |
| Parametric parallelism | OK |
| Accumulation | OK |
| Bias | OK |
| ReLU | OK |
| 32×4 / P=8 validation | OK |
| Dedicated RAM architecture | - Design |
| SPI interface | Planned |
| Dual SPI | Future |
| Multi-layer engine | - RTL + unit tests + real end-to-end (simulated SPI) done, not yet on real toolchain |
| Linux host driver | Planned |
| ESP32 host driver | Planned |
| Hardware training | Future |
Core architectural principle
The FPGA implements the neural-network machine.
The FPGA owns its RAM.
The host configures and uses the machine.
The network topology is specialized at FPGA build time, while its trained parameters are loaded into FPGA-local memory at initialization.
This separation is the foundation of the project.