Begins the V2 Neural Multiprocessor / Dataflow architecture per docs/v2-description.md, per explicit user request to freeze V1 and start V2 development, copying from V1 what's needed. Scaffold: - hardware/v1/: byte-exact, read-only copy of the current V1 codebase (rtl, testbenches, tools, constraints, a representative subset of synthesis results, and reference docs) -- verified identical via diff/cmp against the live top-level tree before being made filesystem-read-only. The live top-level tree is untouched and remains the project's "production" V1 (see hardware/v1/README.md and hardware/v2/logs/decisions.log DEC-0001 for why copy-not-move). - hardware/v2/: mandatory structure (rtl/sim/constraints/synthesis/ reports/scripts/logs/docs) plus the full logging system required by the spec (development/architecture/simulation/synthesis/timing/ benchmark/decisions/experiments/errors.log). M1 -- Neural Processor (hardware/v2/rtl/neural_processor.v): - 8-stage pipelined perceptron unit (P_IN=8): input align, 8 multipliers, 3-level adder tree, accumulator, bias+activation, INT8 saturation. Genuine 1-tile/cycle throughput, not just a wider combinational datapath. - 7-state FSM (NP_IDLE..NP_ERROR per docs/v2-description.md §6, with 4 baseline states merged into NP_WAIT_OPERANDS -- see decisions.log DEC-0002); valid/ready/data/last stream interfaces per §7. - Bit-exact vs the frozen hardware/v1/rtl/neuron_parallel.v + mac8.v + mac_unit.v: 7/7 tests pass (hardware/v2/sim/tb_neural_processor.v), covering regular/mixed-sign/extreme-INT8 vectors, both activations, a zero-idle-gap back-to-back-tiles throughput check, and an 8-tile job -- verified with Verilator (see below for why). - Real synthesis + place&route (Yosys + nextpnr-ecp5): 0 CHECK problems, Fmax 183.12 MHz at ACC_WIDTH=32 (PASS at 80MHz, ~3x V1's isolated PARALLEL=8 Fmax of 61.71 MHz) and 176.21 MHz at ACC_WIDTH=24 (a user-requested comparison experiment, also bit-exact-verified; see experiments.log EXP-0001/EXP-0002 and benchmark.log). Three real bugs found and resolved during M1 development (full diagnostic record in errors.log): - Two independent, reproducible Icarus Verilog v13.0 scheduling defects (ERR-0001, ERR-0002) that silently produced wrong simulation results for standard sequential Verilog -- confirmed via Verilator 5.050 giving correct results on the same minimal repros. Verilator is now the trusted simulator for hardware/v2/ (decisions.log DEC-0004); Icarus's affected protocol-violation check was removed from the RTL and deferred architecturally to the Neural Director (DEC-0003) rather than chased further. - One real RTL bug (ERR-0003): last0 wasn't gated like valid0, letting a "last tile" tag leak into the pipeline ahead of its actual valid tile on back-to-back jobs. Fixed and verified. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
553 lines
27 KiB
Markdown
553 lines
27 KiB
Markdown
# FPGA-Neural — Hardware Design Document
|
||
|
||
Status: draft, pre-schematic. Component choices below are researched
|
||
against current distributor listings (2026-09-02) but not yet
|
||
ordered/prototyped. No PCB layout exists yet.
|
||
|
||
Goal: a board carrying the project's actual target device
|
||
(`LFE5U-45F-8BG381C`) plus the parallel PSRAM the current RTL
|
||
(`rtl/psram_controller.v`) is written for, so real hardware exists
|
||
to run everything already synthesized/benchmarked in this repo.
|
||
|
||
---
|
||
|
||
## 1. Why a new board (not `basic-ecp5-pcb`)
|
||
|
||
A reference ECP5 dev board (Matt Venn's `basic-ecp5-pcb`,
|
||
OSHWA-approved, in this workspace at `../basic-ecp5-pcb`) exists and
|
||
is a useful source of **proven power/config circuitry** — but it
|
||
carries the wrong chip for this project and has no RAM at all:
|
||
|
||
| | `basic-ecp5-pcb` | This project's target |
|
||
|---|---|---|
|
||
| Device | `LFE5U-45F-6BG256C` | `LFE5U-45F-8BG381C` |
|
||
| Package | 256-ball CABGA | 381-ball CABGA |
|
||
| Speed grade | -6 (slowest ECP5 grade) | -8 (fastest ECP5 grade) |
|
||
| RAM | none (6 PMODs, no memory chip) | parallel PSRAM required |
|
||
|
||
Same die (`LFE5U-45F`, same 44K LUT / 72 DSP), different package and
|
||
a materially slower speed grade. All Fmax numbers measured so far in
|
||
this repo (`docs/FPGA-NeuralNetwork-Engine.md` §15 "Phase 7 — Optimization") target
|
||
the -8 grade; they do not directly transfer to a -6 part.
|
||
|
||
**What we reuse from it anyway:** the power tree and bitstream-config
|
||
approach (§4, §5) are package-independent and already validated on
|
||
real, shipped hardware — no reason to redesign those from scratch.
|
||
|
||
---
|
||
|
||
## 2. I/O pin budget (real ball data, CABGA381)
|
||
|
||
Extracted from Lattice's own ECP5-45 pinout table (`../basic-ecp5-pcb/docs/ECP5Upinouts.ods`,
|
||
sheet `ECP5U45Pinout`, `CABGA381` column) — not estimated. **Caveat added
|
||
2026-09-03**: that spreadsheet is no longer present in this environment, so
|
||
these per-bank counts could not be re-verified against it. §7's actual pin
|
||
assignment instead uses Project Trellis's own device database directly
|
||
(same data `nextpnr-ecp5` uses) and gets somewhat lower generic-I/O counts
|
||
per bank (e.g. 32/33 usable in banks 2/3 vs. 35/36 here) — Trellis's
|
||
`packages`/`pio_metadata` only enumerates *programmable* I/O, not every ball
|
||
a full datasheet table would list as "usable I/O" (some datasheet-usable
|
||
balls may not appear as ordinary PIO sites in Trellis's model). The totals
|
||
below are kept for historical reference; §7 is the verified source for
|
||
actual pin placement:
|
||
|
||
| Bank | Usable I/O balls |
|
||
|---|---|
|
||
| 0 | 29 |
|
||
| 1 | 35 |
|
||
| 2 | 35 |
|
||
| 3 | 36 |
|
||
| 6 | 36 |
|
||
| 7 | 35 |
|
||
| 8 | 22 |
|
||
| 40 (config-related) | 4 |
|
||
| **Total usable** | **~232** |
|
||
| Power/ground/NC (remaining of 381 balls) | 149 |
|
||
|
||
Signal budget this design actually needs:
|
||
|
||
| Function | Pins |
|
||
|---|---|
|
||
| PSRAM (`psram_a` 22b worst case, `psram_dq` 16b, `ce_n/oe_n/we_n/lb_n/ub_n/zz_n` 6b) | up to 44 (real usage likely less — see §3, address lines can be trimmed to match actual chip density) |
|
||
| Application SPI (`sclk/mosi/miso/cs_n`) | 4 |
|
||
| `clk`, `rst` | 2 |
|
||
| Host attention (`irq_n`, `data_ready_n`, added 2026-09-03 — §7) | 2 |
|
||
| Flash runtime SPI bus (`flash_sclk`/`flash_mosi`/`flash_miso`/`flash_cs_n` — fully independent, all ordinary GPIO, no config-primitive shared; implemented Phases F1-F7) | 4 |
|
||
| JTAG (recommended, for bring-up/debug) | 4 |
|
||
| **Total** | **~60** |
|
||
|
||
~60 of ~232 usable I/O used (57 actually placed and place&route-verified
|
||
in the flash-subsystem synthesis, `synth/ecp5/spi_neuron_top_flash/
|
||
nextpnr.log`) — **plenty of headroom** (~170+ spare
|
||
pins) for LEDs, buttons, a debug PMOD-style header, or a second SPI
|
||
host, without any pin-count pressure. This board does not need to be
|
||
pin-constrained the way a 256-ball/PMOD-only design would.
|
||
|
||
---
|
||
|
||
## 3. PSRAM subsystem (the piece `basic-ecp5-pcb` doesn't have)
|
||
|
||
`rtl/psram_controller.v` implements an **asynchronous parallel**
|
||
interface with **page-mode burst reads** — address bus, 16-bit data
|
||
bus, `ce_n`/`oe_n`/`we_n` and byte-lane `lb_n`/`ub_n`, plus `zz_n` —
|
||
and its timing already hardcodes a **70 ns random-access
|
||
latency** assumption (`ACCESS_CYCLES = ceil(70ns × CLK_FREQ_MHZ /
|
||
1000)`). This is a classic async-SRAM-style bus, not QSPI — most
|
||
"PSRAM" sold today (including what's on typical ESP32 boards) is
|
||
serial/QSPI and **will not** plug into this controller without a
|
||
rewrite.
|
||
|
||
**Page mode (2026-09-03, `psram_page_mode_tb.v`):** the ISSI part is
|
||
"asynchronous/**page mode**", meaning sequential reads inside the
|
||
same 16-word page (address bits above `A[3]` unchanged) don't need
|
||
the full 70 ns each — only `tAPA`/`tPC` = **20 ns**, once
|
||
CE#/OE# are already asserted. Earlier revisions of this controller
|
||
did not use that mode at all — every access, sequential or not, paid
|
||
the full random-access latency. The controller now:
|
||
- Enables page mode on the chip itself at power-up, via the
|
||
datasheet's software configuration-register sequence (2 dummy
|
||
reads + 2 writes at the top address, CR = `0x00F0`) — page mode is
|
||
**off by default** on the real chip, so this step is load-bearing,
|
||
not cosmetic.
|
||
- Keeps CE#/OE# asserted after a read completes (`STATE_PAGE_OPEN`)
|
||
instead of closing every single-word transaction; a following read
|
||
in the same page only pays `tAPA`; a following read in a
|
||
*different* page still avoids a CE# toggle but pays a full `tAA`
|
||
for that one word (matches the datasheet: "any change in addresses
|
||
A[4] or higher initiates a new tAA access time").
|
||
- Byte-enable (`lb_n`/`ub_n`) changes do **not** close the page.
|
||
`int8_memory_access.v` alternates them on nearly every access
|
||
(byte-granular reads over the 16-bit bus), so treating that as a
|
||
close condition — the first implementation attempt — made the real
|
||
workload *slower*, not faster (measured regression, corrected
|
||
before this was documented as done: see
|
||
`docs/FPGA-Neural-Datapatch-Benchmark.md` for before/after
|
||
numbers). Only a WRITE, or holding CE# low for close to the
|
||
8 µs `tCEM` refresh limit, closes the page.
|
||
- `sim/psram_model.v` (the timing-strict simulation model used by
|
||
every PSRAM-backed testbench) was extended with its own
|
||
independent `tAPA`/`tAA` continuation check, so a passing
|
||
regression run is a real timing proof, not just a data-match.
|
||
|
||
**Recommended part: ISSI IS66WVE4M16EBLL-70BLI**
|
||
- 64 Mbit (4M × 16), parallel pseudo-SRAM, async, **70 ns
|
||
access** — matches the controller's timing assumption exactly, no
|
||
RTL change needed.
|
||
- TSOP-44/48 package — hand-solderable-adjacent, real distributor
|
||
listings (DigiKey, Mouser) at time of writing.
|
||
- **Address bus (2026-09-02: full addressing, all 22 chip lines
|
||
wired):** the chip is 4M×16 words (8 MB total), needing a
|
||
real 22-bit word address, A0–A21. `ADDR_WIDTH` is now **23**
|
||
bits across every module (`rtl/neuron_memory.v`,
|
||
`rtl/psram_controller.v`, etc. — bumped from the earlier 22-bit/
|
||
4 MiB default specifically to reach the full chip).
|
||
`int8_memory_access.v` right-shifts the 23-bit **byte** address by
|
||
1 (`addr >> 1`) into a 22-bit **word** address before it reaches
|
||
`psram_controller` — that 22-bit word address maps exactly onto
|
||
the chip's real A0–A21, with nothing left unconnected. Full
|
||
8 MB is addressable today, not deferred.
|
||
|
||
**Fallback: ISSI IS61WV6416DBLL / IS61WV102416BLL** (true async
|
||
SRAM, not pseudo-SRAM) — electrically drop-in on the same
|
||
`ce_n/oe_n/we_n/lb_n/ub_n` signals, no internal refresh (so `zz_n`
|
||
can just be tied inactive), faster than needed (~10 ns), useful
|
||
if the ISSI PSRAM specifically is out of stock. Smaller density
|
||
(1–16 Mbit depending on exact part) — fine for this
|
||
project's current memory footprint (weights/biases/activations for
|
||
the networks exercised so far are well under 1 MB).
|
||
|
||
Real part numbers, not yet ordered — verify current stock/pricing
|
||
before BOM lock.
|
||
|
||
---
|
||
|
||
## 4. Clock
|
||
|
||
`basic-ecp5-pcb` uses a fixed **16 MHz** MEMS oscillator
|
||
(SiTime SiT2001B family) — no crystal driver on the ECP5, the clock
|
||
input must come from an oscillator IC into a `PCLK` pad.
|
||
|
||
**Recommendation: keep 16 MHz**, same SiT2001B family (or
|
||
SiT1602/SiT8008, same vendor, also in stock). Rationale, not just
|
||
"reuse what worked":
|
||
|
||
- No PLL exists anywhere in this project's RTL yet — `CLK_FREQ_MHZ`
|
||
is a **timing parameter**, not a clock generator. Whatever
|
||
oscillator is fitted drives `clk` directly.
|
||
- Every Fmax measured so far for the *full* integrated system
|
||
(`spi_neuron_top`, Phase 5) sits at 39.5–45 MHz across a
|
||
seed sweep (`docs/FPGA-NeuralNetwork-Engine.md` §15 "Phase 7 — Optimization") —
|
||
confirmed structural, not placement luck. 16 MHz sits well
|
||
under that with real margin.
|
||
- **`CLK_FREQ_MHZ` must be set to match whatever oscillator is
|
||
actually fitted** (16, if this recommendation is taken) — it feeds
|
||
the PSRAM access-timing formulas directly (§3); using the RTL's
|
||
default of 80 with a 16 MHz real clock would under-time the
|
||
PSRAM by 5×.
|
||
|
||
A higher oscillator (e.g. 25 or 32 MHz) is possible with margin
|
||
to spare, but revisit once the Phase 7 timing-closure work
|
||
(`docs/FPGA-NeuralNetwork-Engine.md`) lands rather than guessing a
|
||
number now.
|
||
|
||
---
|
||
|
||
## 5. Power
|
||
|
||
Reuse `basic-ecp5-pcb`'s proven three-rail tree as-is (same device
|
||
family, same rail requirements regardless of package):
|
||
|
||
| Rail | Value | Part | Load | Status |
|
||
|---|---|---|---|---|
|
||
| Core | 1.1 V | TLV62568 (buck) | ≥600 mA | Confirmed in production, DigiKey/Mouser listed |
|
||
| I/O | 3.3 V | TLV62568 (buck) | 1 A (all banks + PSRAM + PMODs share this) | Confirmed in production |
|
||
| Auxiliary | 2.5 V | TLV73325 (LDO) | 10 mA | Confirmed in production |
|
||
|
||
Decoupling: one cap per I/O bank minimum, per Lattice's ECP5
|
||
Hardware Checklist (referenced by `basic-ecp5-pcb`, not re-derived
|
||
here).
|
||
|
||
---
|
||
|
||
## 6. Configuration (bitstream load)
|
||
|
||
Reuse `basic-ecp5-pcb`'s SPI-FLASH-boot approach:
|
||
|
||
- **W25Q128JV** SPI NOR flash (16 MB) — confirmed in production,
|
||
multiple package options (WSON, SOIC) currently listed.
|
||
- ECP5 reads its bitstream from this flash at power-on (`sysCONFIG`
|
||
SPI master mode); no external programmer needed for normal
|
||
power-up, only for the initial flash write.
|
||
|
||
**Lessons reused from `basic-ecp5-pcb`'s errata (do not re-discover
|
||
these the hard way):**
|
||
|
||
- Config-mode select pins should tie directly to GND, not through a
|
||
10 k resistor — the ECP5 test point is ~1 V, too close to
|
||
the 3.3 V bank's input threshold through a resistor divider.
|
||
- Not every SPI flash that claims QSPI actually has a usable QE
|
||
(quad-enable) bit in practice — `basic-ecp5-pcb` hit this with an
|
||
IS25LP016D and switched to the W25Q12x family instead. Stick with
|
||
W25Q128JV rather than substituting on price alone.
|
||
- **The dedicated config-SPI clock pin cannot be reused as a general
|
||
input** post-configuration without extra board-level workaround
|
||
(`basic-ecp5-pcb` needed a bodge wire to let a Raspberry Pi talk
|
||
SPI to the FPGA over the *same* physical pin used for flash boot).
|
||
**This project's application SPI** (`spi_neuron_top`'s
|
||
`sclk`/`mosi`/`miso`/`cs_n`, the host-facing protocol in
|
||
`docs/FPGA-NeuralNetwork-Engine.md` §8.1) **must land on separate,
|
||
ordinary I/O pins — never the config-SPI pins** — precisely to
|
||
avoid needing that same workaround.
|
||
|
||
---
|
||
|
||
## 7. Signal map — real `.lpf`, place&route-verified (2026-09-03)
|
||
|
||
**Source of truth changed from the plan in §2**: the `../basic-ecp5-pcb/docs/ECP5Upinouts.ods`
|
||
spreadsheet referenced there is no longer present in this environment. The
|
||
assignment below instead comes directly from **Project Trellis's own device
|
||
database** (`prjtrellis`'s `database/ECP5/LFE5U-45F/iodb.json`) — the same
|
||
data `nextpnr-ecp5` itself uses — joining each `CABGA381` ball to its bank
|
||
and any dual function via `pio_metadata`. This is real data, not invented
|
||
numbers, and it has been **verified by an actual `nextpnr-ecp5` place&route
|
||
run**, not just picked by eye:
|
||
|
||
```
|
||
nextpnr-ecp5 --45k --package CABGA381 --speed 8 --freq 80 \
|
||
--json synth/ecp5/spi_neuron_top_graph/top.json \
|
||
--lpf synth/ecp5/spi_neuron_top.lpf \
|
||
--textcfg <out>.config
|
||
```
|
||
|
||
Result: **0 constraint errors**, full route completes, `Program finished
|
||
normally` (log: `synth/ecp5/spi_neuron_top_graph/nextpnr_constrained.log`).
|
||
Fmax with this real, fixed pinout: **54.58 MHz** at the time this pinout
|
||
was first verified (FAIL at the 80 MHz target; the critical path was
|
||
then the saturation-comparator carry chain in `rtl/neuron_parallel.v`,
|
||
unrelated to pin placement). **Superseded 2026-09-03** by the timing-closure
|
||
work in `WORKLOG.md` ("Timing closure di `neuron_parallel`"): with the same
|
||
real pinout, Fmax is now **75.30 MHz** (up from 55.59 MHz
|
||
unconstrained originally) — the pin assignment itself did not need to
|
||
change, only the RTL critical path did. **Further updated 2026-09-03**
|
||
after adding the `irq_n`/`data_ready_n` host attention pins (2 more
|
||
signals, same bank 7, no other ball reassigned): re-verified with a fresh
|
||
place&route run, **0 constraint errors**, Fmax 73.88 MHz (within the
|
||
same noise band already characterized for this pin count in
|
||
`WORKLOG.md`'s timing-closure seed sweep — not a regression). TRELLIS_IO
|
||
usage: 53/245 (21%) — still confirms the §2 headroom estimate.
|
||
|
||
**Further updated 2026-09-04** after the flash subsystem (Phases F1-F6,
|
||
`WORKLOG.md`) added 3 real pins (`flash_mosi`, `flash_miso`, `flash_cs_n`
|
||
— bank 7, generated additively by `tools/pinout/gen_lpf.py`, confirmed via
|
||
`git diff` on `synth/ecp5/spi_neuron_top.lpf` to leave every existing ball
|
||
unchanged). Full-system real Yosys+nextpnr-ecp5 synthesis (`synth/ecp5/
|
||
spi_neuron_top_flash/nextpnr.log`), **0 constraint errors**, full route
|
||
completes, Fmax **66.68 MHz**. TRELLIS_IO usage: 56/245 (23%).
|
||
|
||
**Superseded again the same day (Phase F7)**: the flash bus's SCLK
|
||
originally reused the boot `CCLK` pad via the ECP5 `USRMCLK` primitive
|
||
(no dedicated pin) — dropped after the user pointed out this made the
|
||
"exclusive flash bus" claim electrically misleading (SCLK still
|
||
depended on the same pad as the config engine) and it carried an
|
||
unresolved verification gap (`USRMCLKTS` pad-enable timing never
|
||
checked against the primary Lattice sysCONFIG Usage Guide,
|
||
FPGA-TN-02039, absent from this project's document set). `flash_sclk`
|
||
is now a 4th ordinary GPIO ball (`E3`, bank 7), added purely additively
|
||
(`git diff` confirms only the new line, no existing ball moved) — the
|
||
flash bus is now 4 independent wires (`sclk`/`mosi`/`miso`/`cs_n`), zero
|
||
pins shared with any ECP5 config primitive. Re-verified with a fresh
|
||
full-system synthesis: **0 constraint errors**, Fmax **67.91 MHz**
|
||
(slightly better than 66.68, placement noise, not a regression),
|
||
critical path confirmed unchanged (`neuron_parallel` accumulator carry
|
||
chain, no flash module involved), `USRMCLK` utilisation now **0/1
|
||
(0%)** — direct confirmation the primitive is no longer used at all.
|
||
TRELLIS_IO usage: 57/245 (23%). See `WORKLOG.md`'s Phase F7 entry.
|
||
|
||
**Cross-checked against the real Lattice datasheet (2026-09-03, user-
|
||
provided `FPGA-DS-02012-3-4-ECP5-ECP5G-Family-Data-Sheet.pdf`)**: its §4.3.2
|
||
"LFE5U" Pin Information Summary table gives, for LFE5U-45 / 381caBGA, GPIO
|
||
counts per bank of 27/33/32/32/–/33/32/13 (banks 0/1/2/3/4/6/7/8) — this
|
||
matches the Trellis-derived counts used above **exactly on 6 of 7 relevant
|
||
banks**, off by exactly 1 ball on bank 3 (33 in Trellis's model vs 32 in the
|
||
datasheet, immaterial here since only 33 of that bank's balls were even
|
||
candidates and none of the 51 actually assigned came from the contested
|
||
one). Strong independent confirmation that using Trellis's device database
|
||
in place of the no-longer-available spreadsheet was the right call, not a
|
||
shortcut that introduced drift.
|
||
|
||
**What the datasheet does NOT have, confirmed by reading all of it (115
|
||
pages, §4 "Pinout Information" in full)**: any ball-by-ball table. §4.1 is
|
||
purely functional signal descriptions (no ball numbers at all) and §4.3 is
|
||
only the summary-count table quoted above — Lattice ships the actual
|
||
per-ball assignment as a separate resource (spreadsheet/pinout file, e.g.
|
||
the `.ods` originally referenced, or Diamond/Radiant's own device
|
||
database), not inside this PDF. This means the config-SPI (`PROGRAMN`,
|
||
`INITN`, `DONE`, `CCLK`, `CFG[2:0]` — the datasheet's "Miscellaneous
|
||
Dedicated Pins", counted at 7 for this package) and JTAG (`TCK`/`TMS`/
|
||
`TDI`/`TDO` — its "TAP", counted at 4) ball numbers are still not pinned
|
||
down to specific balls here.
|
||
|
||
**That gap does not block anything in this repo, though**: those pins are
|
||
dedicated/fixed-function silicon, not part of any user netlist — `rtl/
|
||
spi_neuron_top.v` has no TCK/TMS/TDI/TDO/PROGRAMN/etc. ports, so `nextpnr-
|
||
ecp5` never needs a `LOCATE` for them (confirmed by the 0-error run above,
|
||
which never mentions them) and no `.lpf` entry is possible or necessary for
|
||
them regardless. Their exact ball numbers only matter for **PCB schematic
|
||
capture** — routing a JTAG header connector and the SPI config-flash chip —
|
||
which is the user's own separate, in-progress KiCad work (untracked
|
||
`FPGA-Neural/` directory at the repo root), not an RTL/synthesis
|
||
deliverable. Downgraded accordingly in the checklist below.
|
||
|
||
**Placement rationale** (die-edge geometry from Trellis's `globals.json`,
|
||
confirmed by joining ball → (col,row) → bank): banks 2 (die col=90, row
|
||
11–32) and 3 (col=90, row 35–68) sit contiguously along the chip's **right**
|
||
edge — used together for the whole 44+1-signal PSRAM bus, exactly the "one
|
||
or two adjacent banks" the appendix asks for. Bank 7 (col=0, row 11–32, the
|
||
**left** edge, physically opposite the PSRAM bus) holds the application SPI
|
||
+ clock/reset, deliberately on the opposite side from PSRAM to keep the two
|
||
buses from crossing. `clk` is pinned to `H5` (`GR_PCLK7_0`), a dedicated
|
||
global-clock pad in bank 7. Plain (no dual-function) balls were preferred
|
||
first within each bank; where a bank ran out of plain balls (bank 2, for
|
||
part of `psram_dq`), the next-best dual-function ball was used as ordinary
|
||
GPIO — flagged individually below, and confirmed by the actual nextpnr run
|
||
above to be perfectly usable as such (including the one `VREF1_2` ball).
|
||
|
||
`psram_a[22]` is a real synthesized port bit (`ADDR_WIDTH`=23 sizes the
|
||
*byte* address everywhere in this design) that is always 0 in practice — see
|
||
§3: `int8_memory_access.v` shifts the byte address right by 1 before it
|
||
reaches the PSRAM, so only 22 bits (`psram_a[21:0]`) ever carry real
|
||
address information, matching the chip's actual 4M-word (2²²) capacity. It
|
||
still needs a physical pin for the tool, given a spare ball, and is a
|
||
no-connect on the actual board.
|
||
|
||
**Clock / reset:**
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `clk` | H5 | 7 | `GR_PCLK7_0` — dedicated global clock pad |
|
||
| `rst` | B4 | 7 | |
|
||
|
||
**Application SPI:**
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `cs_n` | B3 | 7 | |
|
||
| `miso` | A3 | 7 | |
|
||
| `mosi` | C5 | 7 | |
|
||
| `sclk` | B5 | 7 | |
|
||
|
||
**Host attention pins** (added 2026-09-03, active-low, level, driven
|
||
from already-registered sticky bits — see `rtl/spi_neuron_top.v` for
|
||
the exact rationale):
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `data_ready_n` | C3 | 7 | low while a result is waiting to be read (mirrors STATUS.bit1, clear-on-STATUS-read) |
|
||
| `irq_n` | C4 | 7 | low while `graph_engine`'s load-time guard has tripped (mirrors STATUS.bit2 / §7 of the network-engine spec); clears only on RESET or a fresh graph `run_start`, NOT on a plain STATUS read |
|
||
|
||
**Flash subsystem — runtime SPI to the onboard W25Q128JV** (added
|
||
2026-09-04, Phases F1-F6, made fully independent in Phase F7 same day —
|
||
`rtl/spi_flash_master.v`, `WORKLOG.md`; all 4 signals ordinary GPIO,
|
||
generated the same additive way as every other row here, confirmed by
|
||
`git diff` against the pre-flash `.lpf` to leave every existing ball
|
||
unchanged):
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `flash_cs_n` | E4 | 7 | |
|
||
| `flash_miso` | D5 | 7 | |
|
||
| `flash_mosi` | D3 | 7 | |
|
||
| `flash_sclk` | E3 | 7 | added Phase F7 — see below |
|
||
|
||
**Phase F7 (2026-09-04): `flash_sclk` is now a real, independent GPIO
|
||
ball, not a CCLK/`USRMCLK` reuse.** An earlier version drove SCLK
|
||
through the ECP5 `USRMCLK` primitive, reclaiming the same physical CCLK
|
||
net already used for bitstream boot, to save one pin. Dropped: it made
|
||
the "exclusive flash bus" claim electrically misleading (SCLK still
|
||
depended on the config engine's own pad) and carried an unresolved
|
||
verification gap (`USRMCLKTS` pad-enable timing never checked against
|
||
the primary Lattice sysCONFIG Usage Guide, FPGA-TN-02039, absent from
|
||
this project's document set). Re-synthesized full system: `USRMCLK`
|
||
utilisation now 0/1 (0%), directly confirming the primitive is no
|
||
longer used at all. See §6/§9 for the board-level wiring implication
|
||
(the flash chip's DI/DO/CS/CLK pins must be dual-wired to both the
|
||
dedicated sysCONFIG pins and these 4 ordinary balls — a board-level
|
||
duplication inherent to using one physical chip for both boot and
|
||
runtime persistence, not something Phase F7 changed).
|
||
|
||
**PSRAM address:**
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `psram_a[0]` | E16 | 2 | |
|
||
| `psram_a[1]` | F16 | 2 | |
|
||
| `psram_a[2]` | D18 | 2 | |
|
||
| `psram_a[3]` | E17 | 2 | |
|
||
| `psram_a[4]` | E18 | 2 | |
|
||
| `psram_a[5]` | F18 | 2 | |
|
||
| `psram_a[6]` | F17 | 2 | |
|
||
| `psram_a[7]` | G16 | 2 | |
|
||
| `psram_a[8]` | G18 | 2 | |
|
||
| `psram_a[9]` | H16 | 2 | |
|
||
| `psram_a[10]` | H17 | 2 | |
|
||
| `psram_a[11]` | H18 | 2 | |
|
||
| `psram_a[12]` | J16 | 2 | |
|
||
| `psram_a[13]` | J17 | 2 | |
|
||
| `psram_a[14]` | C20 | 2 | |
|
||
| `psram_a[15]` | D19 | 2 | |
|
||
| `psram_a[16]` | E19 | 2 | |
|
||
| `psram_a[17]` | E20 | 2 | |
|
||
| `psram_a[18]` | F19 | 2 | |
|
||
| `psram_a[19]` | F20 | 2 | |
|
||
| `psram_a[20]` | G20 | 2 | |
|
||
| `psram_a[21]` | H20 | 2 | |
|
||
| `psram_a[22]` | P18 | 3 | always 0 (23-bit byte `ADDR_WIDTH` / 22-bit real word address, §3) — NC on the board |
|
||
|
||
**PSRAM data:**
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `psram_dq[0]` | K18 | 2 | |
|
||
| `psram_dq[1]` | C18 | 2 | dual-function ball (URC_GPLL0T_IN), used here as plain GPIO |
|
||
| `psram_dq[2]` | D17 | 2 | dual-function ball (URC_GPLL0C_IN), used here as plain GPIO |
|
||
| `psram_dq[3]` | D20 | 2 | dual-function ball (VREF1_2), used here as plain GPIO |
|
||
| `psram_dq[4]` | G19 | 2 | dual-function ball (GR_PCLK2_1), used here as plain GPIO |
|
||
| `psram_dq[5]` | J18 | 2 | dual-function ball (GR_PCLK2_0), used here as plain GPIO |
|
||
| `psram_dq[6]` | J19 | 2 | dual-function ball (PCLKT2_1), used here as plain GPIO |
|
||
| `psram_dq[7]` | J20 | 2 | dual-function ball (PCLKT2_0), used here as plain GPIO |
|
||
| `psram_dq[8]` | K19 | 2 | dual-function ball (PCLKC2_1), used here as plain GPIO |
|
||
| `psram_dq[9]` | K20 | 2 | dual-function ball (PCLKC2_0), used here as plain GPIO |
|
||
| `psram_dq[10]` | L17 | 3 | |
|
||
| `psram_dq[11]` | M18 | 3 | |
|
||
| `psram_dq[12]` | M17 | 3 | |
|
||
| `psram_dq[13]` | N16 | 3 | |
|
||
| `psram_dq[14]` | N18 | 3 | |
|
||
| `psram_dq[15]` | P17 | 3 | |
|
||
|
||
**PSRAM control:**
|
||
|
||
| Signal | Ball | Bank | Note |
|
||
|---|---|---|---|
|
||
| `psram_ce_n` | N17 | 3 | |
|
||
| `psram_lb_n` | T16 | 3 | |
|
||
| `psram_oe_n` | R16 | 3 | |
|
||
| `psram_ub_n` | N19 | 3 | |
|
||
| `psram_we_n` | R17 | 3 | |
|
||
| `psram_zz_n` | N20 | 3 | |
|
||
|
||
---
|
||
|
||
## 8. Bill of materials (draft)
|
||
|
||
| Ref | Part | Function | Availability |
|
||
|---|---|---|---|
|
||
| U1 | LFE5U-45F-8BG381C | FPGA | Already the project's confirmed target (see main docs, price/stock table) |
|
||
| U2 | ISSI IS66WVE4M16EBLL-70BLI | Parallel PSRAM, 64Mb, 70ns | Verified listed, DigiKey/Mouser |
|
||
| U3, U4 | TLV62568 | Buck converter, core + IO rails | Confirmed in production |
|
||
| U5 | TLV73325 | LDO, 2.5V aux rail | Confirmed in production |
|
||
| U6 | W25Q128JV | SPI NOR flash, config | Confirmed in production, multiple packages |
|
||
| Y1 | SiT2001B, 16 MHz | System clock oscillator | Confirmed in production |
|
||
|
||
Not yet specified: exact package/footprint per part, decoupling cap
|
||
values, JTAG header, PSRAM address-bus trim if a smaller/cheaper
|
||
density than 4M×16 turns out to be sufficient once real network
|
||
sizes are decided.
|
||
|
||
---
|
||
|
||
## 9. Open items before schematic capture
|
||
|
||
- [x] `ADDR_WIDTH` set to 23 (full 8 MB) across all RTL modules
|
||
and testbenches, matching the recommended part's real capacity
|
||
(2026-09-02) — see §3.
|
||
- [ ] If the fallback true-SRAM part in §3 is used instead (smaller
|
||
density), decide whether to shrink `ADDR_WIDTH` back down to
|
||
match it or keep 23 with the extra range simply unused.
|
||
- [x] Real `.lpf` pin assignment for `clk`/`rst`/application SPI/PSRAM
|
||
(§7, `synth/ecp5/spi_neuron_top.lpf`), place&route-verified
|
||
(2026-09-03) — 0 constraint errors; cross-checked against the
|
||
real Lattice datasheet (§7, matches on 6/7 banks exactly). Fmax
|
||
54.58 MHz when this pinout was first verified, 75.30 MHz
|
||
after the `neuron_parallel` timing-closure work, 73.88 MHz
|
||
after adding the host-attention pins, 66.68 MHz after adding
|
||
the flash subsystem, and **67.91 MHz** for the current full
|
||
system after making the flash SPI bus fully independent (Phase F7,
|
||
§7, 2026-09-04) — see §7 for the full history and why each change is
|
||
pin-placement noise, not a regression.
|
||
**Config-SPI and JTAG ball numbers are still not pinned down**
|
||
(§7) — confirmed by reading the full real datasheet that it has
|
||
no per-ball table at all (only functional descriptions and
|
||
summary counts: TAP=4, misc dedicated=7 for this package), so
|
||
the actual per-ball assignment remains a separate Lattice
|
||
resource not available in this environment. **This does not
|
||
block any RTL/synthesis work**: those are dedicated/fixed-
|
||
function pins with no corresponding port in `rtl/spi_neuron_top.v`,
|
||
so no `.lpf` entry is possible or needed for them, and every
|
||
place&route run above already completes with 0 errors without
|
||
them. They only matter for **PCB schematic capture** (JTAG
|
||
header + config-flash routing) — the user's own separate,
|
||
in-progress KiCad work, not something this repo's synthesis
|
||
flow needs to resolve.
|
||
**Distinct from this open item** (do not conflate the two): the
|
||
flash subsystem's own runtime SPI pins (`flash_sclk`, `flash_mosi`,
|
||
`flash_miso`, `flash_cs_n` — Phases F1-F6, made fully independent
|
||
in Phase F7, `WORKLOG.md`) **are** real, pinned, place&route-
|
||
verified ordinary GPIO on bank 7 (`flash_sclk`=E3, `flash_mosi`=D3,
|
||
`flash_miso`=D5, `flash_cs_n`=E4), generated the same way as every
|
||
other signal in this table — **no pin shared with any ECP5 config
|
||
primitive** (Phase F7 removed the earlier `USRMCLK`/CCLK reuse for
|
||
SCLK; `USRMCLK` utilisation in the current full-system synthesis
|
||
is 0/1, confirming it). §5's PCB-level implication: the
|
||
W25Q128JV's DI/DO/CS/CLK pins must be wired to *both* the dedicated
|
||
sysCONFIG pins (for boot) *and* these 4 ordinary GPIO balls (for
|
||
runtime access after configuration completes) — a board-level
|
||
duplication inherent to using one physical chip for both roles,
|
||
not yet reflected in a schematic since none exists yet (see the
|
||
KiCad item below).
|
||
- [ ] Confirm PSRAM/SPI signal integrity at whatever clock is
|
||
actually fitted (§4) — no signal integrity analysis done yet
|
||
- [ ] JTAG header footprint choice (blocked on the JTAG ball question
|
||
above)
|
||
- [ ] KiCad (or other) schematic capture — none exists yet for this
|
||
device/package combination
|