55c827bedf7cc3b7203e895512c650d089fe9255
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
55c827bedf |
feat: PSRAM page-mode reads + graph engine (Type #2) + real pinout/IRQ pins
PSRAM page-mode read burst support in psram_controller.v: enables the ISSI IS66WVE4M16EBLL-70BLI's page mode via its configuration-register software-access sequence at boot (disabled by default on the real chip), then keeps CE#/OE# asserted after a read so a same-page continuation only pays tAPA (20ns) instead of a full tAA (70ns) random access, with automatic tCEM-safe session closing. Only a WRITE closes the page -- byte-enable changes do not, since int8_memory_access.v alternates them on nearly every access and an early implementation attempt that treated them as a close condition measured a real regression (53.25->61.25 cycles/edge) before being corrected (53.25->37.53 cycles/edge, +42% gather bandwidth). sim/psram_model.v gained independent tAPA/tAA and tCEM enforcement (with a real Verilog same-timestep event-ordering race found and fixed via a #0 sync) so the regression proves real timing compliance, not just data correctness. New sim/psram_page_mode_tb.v; full 26-file regression suite re-run clean. Real nextpnr-ecp5 Fmax re-measured on the full spi_neuron_top system: 75.73MHz (P2, up from 55.59MHz) and 65.13MHz (P8) -- still under the 80MHz target but not regressed, with the critical path confirmed (not assumed) to remain entirely inside neuron_parallel's accumulate chain, never psram_controller. Also includes this session's other already-validated work: the graph engine (Type #2 sparse-graph network: act_buffer, graph_engine, netasm host assembler), real CABGA381 pinout (.lpf, place&route verified) and physical IRQ_N/DATA_READY_N pins, and Phase 7 timing closure logs -- all previously uncommitted, documented in WORKLOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LH3jPeJ3eFMfF2v8SQhpkk |
||
|
|
7e2711fa27 |
feat: widen ADDR_WIDTH to 23 bits for full 8MB PSRAM addressing
Bumps ADDR_WIDTH's default from 22 to 23 bits across every RTL module (neuron_memory, layer_sequencer, spi_engine, spi_neuron_top, mem_arbiter, int8_memory_access, memory_interface, psram_controller, memory_model) and every testbench that mirrors it, so the system's byte-address space reaches the full 8 MiB the recommended PSRAM part (ISSI IS66WVE4M16EBLL-70BLI, docs/FPGA-Neural-Hardware-Design.md §3) actually provides -- previously only 4 MiB (half the chip) was reachable, since int8_memory_access.v's byte->word address shift (addr >> 1) turned the old 22-bit byte address into only 21 real word bits, one short of the chip's real 22-bit word address (A0-A21). At 23 bits, that same shift lands exactly on all 22 chip address lines, so the whole part is usable now instead of deferred to a future widening. Also fixes a stray 22'd11-sized literal in layer_sequencer.v's descriptor-table address increment (numerically already safe via Verilog's zero-extension, but now correctly unsized so it always matches ADDR_WIDTH instead of silently assuming 22). Updated docs/FPGA-NeuralNetwork-Engine.md's SPI protocol address-field note (23 bits, top 1 reserved bit instead of 2) and docs/FPGA-Neural-Hardware-Design.md's PSRAM section (the "chip has one spare address line" framing is gone now that all 22 are wired and used). Full regression (all 11 ADDR_WIDTH-touching testbenches, plus a Yosys elaboration check of spi_neuron_top with the new default and no override) passes clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
f6edc01613 |
docs: Phase 7 placement-seed sweep + hardware design document
Phase 7 (docs/FPGA-NeuralNetwork-Engine.md): re-ran nextpnr-ecp5 on the already-synthesized Phase 5 spi_neuron_top netlists (top.json reused, only placement re-seeded) at --seed 1/2/3 for both P8 and P2. Both land in a tight band regardless of seed (P8: 39.5-40.6 MHz, 2.6% spread; P2: 42.5-45.0 MHz, 5.8% spread) -- confirms the Phase 5 timing shortfall is a real structural bottleneck, not placement noise, unlike the much smaller same-tier benchmark design (<2% utilization, huge placer freedom, genuinely noisy). Corrected the earlier "pipeline the saturate stage" candidate fix, which targeted Phase 4's critical path and not the one Phase 5's logic actually shifted to; block RAM for x_mem/w_mem remains the leading candidate, not yet implemented. New docs/FPGA-Neural-Hardware-Design.md: draft hardware design doc for a board carrying the project's actual target device (LFE5U-45F-8BG381C) plus the parallel PSRAM rtl/psram_controller.v is written for. Covers: why not the basic-ecp5-pcb reference board (wrong package/speed grade, no RAM), a real I/O pin budget from Lattice's own CABGA381 pinout table, a researched PSRAM part (ISSI IS66WVE4M16EBLL-70BLI -- 70ns access matches the controller's timing assumption exactly, with a note on the byte/word address shift in int8_memory_access.v so the chip's top address line is correctly left as spare headroom, not a wiring error), clock (16 MHz, no PLL exists yet so CLK_FREQ_MHZ must match whatever oscillator is fitted), power/config reusing the reference board's proven circuitry and errata (config-SPI pin can't double as the application SPI interface), and a BOM/open-items list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
a918c3f1e9 |
feat: configurable activation functions + runtime-configurable network topology
Two related Phase 5 additions, both threaded the same way (a new runtime field defaulting to the pre-existing behavior, settable per-layer via the descriptor table or per-run via SET_BASE): Configurable activation functions: - neuron_parallel.v gains a 2-bit `activation` port (ACT_NONE = linear + two-sided INT8 saturate, ACT_RELU = the original hardwired behavior, kept as the default so every pre-existing caller/testbench is unaffected), threaded through neuron_memory.v. - spi_engine.v: SET_BASE sel=6 (single-layer path); the descriptor table gains a 7th byte (multi-layer path). - Verified in neuron_parallel_tb.v (negative pass-through + negative saturation to -128) and end-to-end in spi_neuron_top_runnetwork_tb.v (a real negative accumulator that ACT_RELU would clamp to 0 comes through unclamped under ACT_NONE, over real SPI/RAM). Runtime network width (one bitstream, any topology up to its build-time max, entirely host-configured over SPI): - neuron_parallel.v gains n_inputs_real, bounding its MAC group loop (n_inputs_real/PARALLEL groups instead of the fixed build-time count). neuron_memory.v gains n_inputs_real/n_neurons_real, bounding its X/W RAM-read loop and its neuron loop. All default to the build-time max, so unconnected callers are unaffected. n_inputs_real must stay a multiple of PARALLEL (same constraint N_INPUTS itself is held to at elaboration time, now the caller's runtime responsibility). - spi_engine.v: SET_BASE sel=7/8 (single-layer path); the descriptor table grows to 11 bytes/layer (+n_inputs_real +n_neurons_real, multi-layer path) -- layer_sequencer.v also now copies only n_neurons_real bytes into the ping-pong buffer, not the full build width. - This is real early termination, not bookkeeping: no RAM zero-padding needed for the unused tail, and it measurably completes faster. neuron_parallel_tb.v TEST 7: 3 cycles vs 6 for a reduced-vs-full run, with garbage loaded into the skipped lanes to prove they're never read. neuron_memory_tb.v TEST 5: through the real PSRAM stack, 209 cycles vs 788. layer_sequencer_tb.v proves a reduced n_neurons_real shortens the ping-pong copy-out itself (bytes beyond the real count stay untouched, not just differing). docs/FPGA-NeuralNetwork-Engine.md: §8.1 opcode/SET_BASE table, new "Runtime network width" subsection, Phase 5 checklist, Current Status table, and the "Core architectural principle" statement updated to reflect that topology (not just trained parameters) is now host-configured at runtime up to a build-time ceiling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
233d6ff7fb |
feat: complete Phase 5 multi-layer network (RUN_NETWORK) + fix STATUS race
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. |
||
|
|
a2bd60e305 |
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 |
||
|
|
87efce3d9b |
docs: draft SPI protocol v1 for Phase 4 (opcodes, register map)
Phase 4 (SPI Interface) only had a high-level conceptual sequence (RESET/CONFIGURE/LOAD.../START/WAIT/READ) with no concrete opcodes, framing, or register map -- not enough to start RTL from. Added docs/FPGA-NeuralNetwork-Engine.md §8.1 with a concrete v1 draft: - SPI Mode 0, MSB-first, one opcode byte per CS-low transaction. - Explicit length field on WRITE_RAM/READ_RAM (chosen over CS-edge-delimited streaming: simpler controller, just a byte counter). - READ_CONFIG opcode exposing N_INPUTS/N_NEURONS/PARALLEL/ ADDR_WIDTH/DATA_WIDTH at runtime, so one host firmware build can target different bitstreams. - RESET kept as its own opcode (0x0F), distinct from NOP. - STATUS.done documented as required to be a STICKY, clear-on-read bit in the SPI register bank: neuron_memory.done is a one-cycle pulse that a slow SPI poll would almost certainly miss otherwise. Opcode values themselves are marked explicitly as draft/example, not frozen -- only the framing rules and the two decisions above are meant to stick going into Phase 4 RTL work. No RTL or testbench changes in this commit; design-only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
661363f637 |
feat: extend neuron_memory to support N_NEURONS>1 (Phase 3)
neuron_memory.v only handled a single neuron. Added an N_NEURONS parameter (default 1, fully backward compatible) and a memory-bound neuron loop: X is read once (shared layer input), and for each neuron in turn W and bias are re-read from PSRAM and fed to a single, reused neuron_parallel instance -- no change to the validated compute datapath (neuron_parallel/mac8/mac_unit). Addressing follows layer.v's neuron-major convention: neuron n's weights live at w_base + n*N_INPUTS bytes, its bias at bias_addr + n. Output changed from a single `y` port to a packed `y_bus` (DATA_WIDTH*N_NEURONS bits, neuron-major), matching layer.v's y_bus. - rtl/neuron_memory.v: N_NEURONS parameter, neuron_index/ w_group_base/bias_group_addr tracking, y_reg[] array assembled into y_bus, STATE_WAIT_N now loops back to STATE_READ_W for the next neuron instead of finishing after one. - sim/neuron_memory_tb.v: updated to the new y_bus port (N_NEURONS=1 explicit); all 5 existing tests still pass unchanged, confirming backward compatibility. - sim/neuron_memory_multi_tb.v: new end-to-end test (full memory_interface + psram_controller + psram_model stack) with N_NEURONS=3, validating per-neuron addressing and a single done pulse at the end of the sequence (scale, larger value, ReLU). - Full regression re-run: all existing testbenches still pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
1a6f0ba2ef |
fix: guard neuron_parallel against invalid N_INPUTS/PARALLEL combos
Both Phase 2 findings (docs/FPGA-NeuralNetwork-Engine.md) shared one root cause: GROUPS = N_INPUTS / PARALLEL is integer division. When N_INPUTS is not an exact multiple of PARALLEL, the remainder inputs were silently dropped from the accumulation (wrong result, no error); when PARALLEL > N_INPUTS, GROUPS = 0 and the controller's terminal condition was never met, hanging the neuron forever. Added a single elaboration-time guard to rtl/neuron_parallel.v: a `generate` block instantiates a deliberately undefined module when N_INPUTS % PARALLEL != 0, forcing a hard failure in both simulation and synthesis instead of a silent wrong answer or a deadlock. Valid configurations are unaffected (the branch is never elaborated). The validated datapath (mac8/mac_unit/accumulation/ReLU/saturation) is untouched -- this is authorized as a scoped exception to the "core is fixed, do not touch" project policy, for this guard only. - sim/neuron_parallel_guard_negative_nonmultiple_tb.v and sim/neuron_parallel_guard_negative_degenerate_tb.v: negative tests that must fail to elaborate; verified both fail with the expected "Unknown module type" error. - sim/parameter_sweep_tb.v: rewritten to valid-configs-only (the three configs that used to demonstrate truncation/hang no longer compile, by design); added PARALLEL=2 and PARALLEL=4 configs, the two best-performing values from docs/FPGA-Neural-Datapatch-Benchmark.md. - Full regression re-run after the RTL change: all existing testbenches still pass unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
9b9859a104 |
test: add Phase 2 parameter sweep for neuron_parallel, update roadmap
Roadmap Phase 2 asks to validate N_INPUTS/N_NEURONS/PARALLEL combinations, including non-exact-multiple configurations. Added sim/parameter_sweep_tb.v with 5 configs (two exact-multiple sanity checks, two non-exact-multiple, one degenerate PARALLEL>N_INPUTS), using a cycle-count watchdog instead of a blocking wait so a hanging config is reported rather than hanging the simulation. Findings (RTL unchanged, core datapath left untouched): - GROUPS = N_INPUTS / PARALLEL truncates: when N_INPUTS is not an exact multiple of PARALLEL, the remainder inputs are silently never summed (confirmed 30/8 -> 6 dropped, 20/16 -> 4 dropped). - PARALLEL > N_INPUTS gives GROUPS=0, and the controller's group_index == GROUPS-1 terminal condition is never met: the neuron hangs forever (confirmed via watchdog timeout). Documented both as findings under Phase 2 in docs/FPGA-NeuralNetwork-Engine.md for follow-up in Phase 3/7. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WQV3vS9TXaGDJ5cRfnfidt |
||
|
|
16769eaa4b | working with memory |