chore: remove root-level V1 duplicates, superseded by hardware/v1/ freeze
hardware/v1/ was created (dc0b331) as a frozen snapshot of the V1
project that then lived at the repo root (rtl/, sim/, synth/, tools/,
docs/). Root received zero further commits to those files after the
freeze -- confirmed byte-identical to the hardware/v1/ copy for every
file removed here. Root was the "before", hardware/v1/ is the
curated, canonical "after".
Removed (all verified exact-hash duplicates of hardware/v1/ content):
- rtl/ (20 files, 100% covered by hardware/v1/rtl/)
- tools/{netasm,pinout,run_regression.py,flash_catalog,validation,
fpga_benchmark.py} (19 files, 100% covered by hardware/v1/tools/;
tools/neural_sim/ kept -- unique, post-freeze, no counterpart)
- sim/*.v (47 testbenches, 100% covered by hardware/v1/sim/; the
~38 remaining sim/ entries are compiled binaries and .vcd
waveform dumps, left as a separate cleanup decision)
- synth/ecp5/{p2,p4,p8,post_fix_verify} (25 files, exact duplicates
of hardware/v1/synthesis/; the other ~84 synth/ecp5/* experiment
build directories are historical artifacts never carried into the
freeze, left as a separate decision)
- WORKLOG.md (duplicate of hardware/v1/docs/WORKLOG.md)
- docs/{FPGA-Neural-Datapatch-Benchmark,FPGA-Neural-Hardware-Design,
FPGA-NeuralNetwork-Engine}.md, docs/validation/*.md (18 files),
docs/FPGA-Neural-Datasheet-{EN,IT}.pdf -- all exact duplicates of
hardware/v1/docs/ content
- hardware/v1/docs/DatasheetLatex/ (24 files) -- exact duplicate of
hardware/v2/docs/datasheet/files/docs/datasheet/en/ (discovered
during this audit; not the same DatasheetLatex already removed
from hardware/v2/docs/ in an earlier commit)
Moved (genuine, unique, post-freeze V2 content -- not duplicated
anywhere, just living in the wrong/legacy root docs/ location):
- docs/architecture/*.md -> hardware/v2/docs/architecture/
- docs/pinouts.md, docs/FPGA_NEURAL_V2_DATASHEET.md,
docs/FPGA_NEURAL_V2_SCHEMATIC.md,
docs/FPGA-Neural-V2-Datasheet-EN.pdf -> hardware/v2/docs/
Left untouched (separate decisions, not part of this cleanup):
- docs/FPGA-Neural-Flash-Subsystem-Verification.md, docs/
v2-description.md -- orphaned root-only content, no duplicate
found anywhere, but also not part of the reviewed plan
- synth/ecp5/* experiment dirs and sim/*_sim + sim/*.vcd build
artifacts -- not literal duplicates, flagged as candidates for a
future, separate cleanup pass
Verified no functional breakage: grepped all remaining scripts/docs
for references to every removed path -- only prose/comment mentions
found, no executable imports or build-script paths broken.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -1,552 +0,0 @@
|
||||
# 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
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -1,186 +0,0 @@
|
||||
# FPGA-Neural V2 — Datasheet
|
||||
|
||||
**Status: DRAFT / PRE-RELEASE.** This datasheet documents the INTENDED
|
||||
V2 board architecture as of STEP20. It does **not** certify a finished,
|
||||
release-ready design — see §11 Limitations and
|
||||
`hardware/v2/docs/OPEN_ITEMS.md` for the current, real blocker list.
|
||||
Do not read any statement here as "physically validated" unless it
|
||||
says so explicitly.
|
||||
|
||||
## 1. General
|
||||
|
||||
FPGA-Neural V2 is an embedded neural-network accelerator built around a
|
||||
Lattice ECP5 FPGA and a single external SDRAM. It executes small,
|
||||
dependency-graph-structured INT8 neural networks (dense layers, DAGs)
|
||||
using a Neural Multiprocessor of parallel MAC engines, streaming
|
||||
weight/activation tiles from one external SDRAM chip that also holds
|
||||
results.
|
||||
|
||||
Architecture stack (top to bottom): SPI host interface → job
|
||||
registration → Dependency Manager / Neural Director → N parallel
|
||||
Neural Processors → Memory Manager / streaming tile delivery → Unified
|
||||
SDRAM Backend → one physical SDRAM.
|
||||
|
||||
## 2. FPGA
|
||||
|
||||
| Item | Value | Basis |
|
||||
|---|---|---|
|
||||
| Part | Lattice LFE5U-45F | DESIGN DECISION |
|
||||
| Package | CABGA381 | DESIGN DECISION |
|
||||
| Speed grade | -8 | DESIGN DECISION |
|
||||
| Ordering part number | LFE5U-45F-8BG381C | DESIGN DECISION (standard Lattice ordering suffix for this grade/package; not independently cross-checked against a live distributor listing this session) |
|
||||
| Logic (post-synthesis, N=4, frozen STEP19 compute core) | TRELLIS_FF=6425, TRELLIS_COMB=6023, MULT18X18D=32, DP16KD=0 | VERIFIED (real Yosys synthesis, STEP19) |
|
||||
| I/O used (frozen STEP19 top, no physical host bus) | 149/245 TRELLIS_IO | VERIFIED (real nextpnr-ecp5 P&R, STEP19) |
|
||||
| I/O used (this step's new board-level top, SPI + osc + reset + SDRAM) | not yet synthesized this round | OPEN — see §11 |
|
||||
|
||||
Operating assumption: single clock domain, no CDC beyond the SPI
|
||||
bridge's own double-flop synchronizers and the reset synchronizer
|
||||
(§5).
|
||||
|
||||
## 3. Neural accelerator
|
||||
|
||||
| Parameter | Value |
|
||||
|---|---|
|
||||
| N_PROCESSORS | 4 (frozen reference; N=2 also validated; N=8 is a future evolution) |
|
||||
| P_IN (MAC width) | 8 |
|
||||
| Data representation | INT8 operands |
|
||||
| Accumulator | INT32, ReLU + INT8 saturate on output |
|
||||
| MAC architecture | 8-wide parallel MAC, balanced adder tree (`neural_processor.v`, unchanged since before this freeze) |
|
||||
| Processor parallelism | N independent Neural Processors, one dependency-graph node in flight per processor |
|
||||
| Supported memory traffic | weights (read-only, 64-bit packed fetch, cached), activations (read, byte-maskable), results (write, byte-maskable) — all through the SAME single SDRAM |
|
||||
|
||||
**RTL capability vs. software/API capability:** the RTL executes one
|
||||
pre-compiled dependency graph (nodes with producer/consumer edges,
|
||||
fixed tile counts) registered via 108 bits of per-job configuration
|
||||
(node id, dependency list, activation/weight/result base addresses,
|
||||
tile count). There is no on-chip graph compiler, no floating point, no
|
||||
training — job graphs and addresses are computed off-chip and loaded
|
||||
via the host interface (§9).
|
||||
|
||||
## 4. Unified memory
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ FPGA ECP5 │
|
||||
│ │
|
||||
│ 4x Neural Engines │
|
||||
│ │ │
|
||||
│ v │
|
||||
│ Unified SDRAM │
|
||||
│ Backend / Arbiter │
|
||||
└─────────┬───────────┘
|
||||
│ 16-bit SDRAM bus
|
||||
v
|
||||
┌─────────────────────┐
|
||||
│ AS4C4M16SA-6TIN │
|
||||
│ Weights │
|
||||
│ Activations │
|
||||
│ Results │
|
||||
└─────────────────────┘
|
||||
```
|
||||
|
||||
| Item | Value | Basis |
|
||||
|---|---|---|
|
||||
| Device | Alliance Memory AS4C4M16SA-6TIN | DESIGN DECISION (STEP16-19) |
|
||||
| Capacity | 4M x 16 (8MB) | DATASHEET VALUE |
|
||||
| Data width | 16-bit (DQ[15:0]) + DQM[1:0] byte mask | DATASHEET VALUE |
|
||||
| Addressing | BA[1:0] (4 banks) + A[11:0] (row/col, multiplexed) | DATASHEET VALUE |
|
||||
| Clock | shared with FPGA system clock (§5) | DESIGN DECISION |
|
||||
| Initialization/refresh | real, RTL-implemented power-up wait + mode-register-set + periodic AUTO REFRESH (`sdram_controller.v`) | VERIFIED (real refresh events observed in simulation, STEP16-19) |
|
||||
| Arbitration | single physical port, 2-way logical split: W (weight, read-only, cached) / AR (activation+result, read/write, byte-maskable), each internally arbitrated across N processors by a generic, reused `slot_mem_arbiter` | VERIFIED (STEP19 bit-exact regression, reconfirmed via Verilator this step — see errors.log ERR-0024) |
|
||||
| Official V2 memory map | weights @0x010000, activations @0x200000, results @0x300000, all within the single 8MB space, 1MB-aligned | DESIGN DECISION |
|
||||
|
||||
PSRAM is **not** part of V2. The V1 PSRAM controller (`hardware/v1/rtl/psram_controller.v`) is not instantiated anywhere in the V2 physical path.
|
||||
|
||||
## 5. Clock / PLL
|
||||
|
||||
```
|
||||
16 MHz OSCILLATOR
|
||||
|
|
||||
v
|
||||
ECP5 PLL (EHXPLLL)
|
||||
CLKI_DIV=1 CLKFB_DIV=4 CLKOP_DIV=9
|
||||
FEEDBK_PATH=CLKOP VCO=576MHz
|
||||
|
|
||||
v
|
||||
FPGA SYSTEM CLOCK
|
||||
64 MHz
|
||||
(real, tool-generated ratio: 16 * 4 / 1, CLKOP_DIV=9 -> 576/9=64)
|
||||
```
|
||||
|
||||
| Item | Value | Basis |
|
||||
|---|---|---|
|
||||
| Oscillator | 16 MHz (board-level, prior project record) | DESIGN DECISION (part number: TBD — not selected this session) |
|
||||
| PLL primitive | EHXPLLL (`ecp5_pll_sys_clk.v`) | VERIFIED design-time via Project Trellis `ecppll` v1.4 (real tool, real parameters) |
|
||||
| Generated system clock | 64 MHz | DESIGN DECISION, chosen over 80MHz because STEP19's own multi-seed P&R data showed only 1/8 seeds closing timing at >=80MHz on the compute-only core, and the new board-level top adds more logic still; 64MHz is not yet itself confirmed by P&R on the NEW top (see §11) |
|
||||
| PLL lock | `locked` output, feeds `reset_sync.v` | DESIGN DECISION; NOT simulatable (Lattice EHXPLLL has no open sim model) — real lock behavior is a real-hardware-only characterization, see §10 |
|
||||
| Timing constraints | none yet written for the new board-level top | OPEN — see §11 |
|
||||
|
||||
## 6. Interfaces
|
||||
|
||||
### SPI host interface (`spi_host_bridge.v`)
|
||||
Mode 0 (CPOL=0/CPHA=0), MSB-first, one opcode per CS-low period.
|
||||
Opcodes: `0x10` WRITE_JOB (job registration, 15-byte payload), `0x01`
|
||||
WRITE_MEM / `0x02` READ_MEM (raw, word-addressed SDRAM access via a
|
||||
second arbitrated port), `0x20` STATUS, `0x0F` RESET. Verified in
|
||||
isolation (18/18, `tb_spi_host_bridge.v`). **Not yet verified
|
||||
end-to-end under realistic multi-job pacing** — see §11/ERR-0025.
|
||||
The 110-pin `reg_*` bus used by V2's own internal simulation
|
||||
testbenches is a testbench-only convenience and is **not** the
|
||||
physical interface.
|
||||
|
||||
### JTAG
|
||||
Standard ECP5 JTAG (TDI/TDO/TCK/TMS), always available regardless of
|
||||
configuration boot mode, per Lattice's own standard requirement.
|
||||
|
||||
### Configuration
|
||||
Standard ECP5 PROGRAMN/INITN/DONE/CCLK. Boot-mode/flash-part decision:
|
||||
OPEN (see §11).
|
||||
|
||||
## 7. Electrical
|
||||
|
||||
Rail voltage requirements are DATASHEET VALUEs (from real device
|
||||
datasheets); no regulator part numbers, current budget, or decoupling
|
||||
values are finalized this round. Full detail:
|
||||
`hardware/v2/docs/POWER_ARCHITECTURE.md`.
|
||||
|
||||
## 8. Pinout
|
||||
|
||||
Full table: `hardware/v2/docs/PINOUT.md`. Summary: 37 real SDRAM
|
||||
signals + clk/rst are ball-assigned and P&R-verified (STEP19, against
|
||||
the STEP19 compute-only top). The board-level top added this step
|
||||
(SPI + oscillator + reset pins) has **not** had its own ball
|
||||
assignment or P&R run yet.
|
||||
|
||||
## 9. Mechanical / board assumptions
|
||||
|
||||
None assumed beyond the package footprint implied by CABGA381. No PCB
|
||||
dimensions, connector placement, or stack-up are specified — that is
|
||||
schematic/PCB-capture work, not yet started (see
|
||||
`hardware/v2/docs/SCHEMATIC_READINESS.md`).
|
||||
|
||||
## 10. Programming / first power-on
|
||||
|
||||
JTAG programming is standard. A first-power-on procedure exists at
|
||||
`hardware/v2/docs/FIRST_POWER_ON.md` (procedure only — not executed
|
||||
against real hardware, since no board has been fabricated).
|
||||
|
||||
## 11. Limitations (real, current, as of this datasheet's own writing)
|
||||
|
||||
- **The physical SPI host interface is NOT proven end-to-end
|
||||
correct.** A real, disclosed defect (errors.log ERR-0025 Part B)
|
||||
produces wrong results when two jobs are dispatched with realistic
|
||||
SPI pacing, even though registration itself is confirmed correct.
|
||||
This is the single largest open item.
|
||||
- The board-level top (`fpga_neural_v2_top.v`) has not been through
|
||||
synthesis or P&R this round — deliberately, since running the real
|
||||
toolchain against RTL known to compute wrong answers would not be a
|
||||
meaningful result.
|
||||
- No PCB, schematic capture, or fabricated hardware exists. Nothing in
|
||||
this document should be read as "physically validated."
|
||||
- Regulator, configuration-flash, and connector part numbers are not
|
||||
selected.
|
||||
- The STEP19 compute+memory core (raw `reg_*` interface, no SPI
|
||||
bridge) IS bit-exact verified (N=2 and N=4, 256/256, reconfirmed via
|
||||
Verilator this session) and remains the actual, working reference
|
||||
design underneath this datasheet's own described board architecture.
|
||||
@@ -1,160 +0,0 @@
|
||||
# FPGA-Neural V2 — Reference Schematic (textual)
|
||||
|
||||
**No KiCad schematic was generated this session.** No RTL-to-schematic
|
||||
or netlist-to-KiCad automation tool is available in this environment,
|
||||
and the project's own separate, pre-existing KiCad PCB directory
|
||||
(`FPGA-Neural/FPGA-Neural/FPGA-Neural/`) is an unrelated, independently
|
||||
tracked project (its own nested `.git`, near-empty as of last check) —
|
||||
it was not touched, and this document does not assume its contents.
|
||||
This is a textual/ASCII reference schematic: a real starting point for
|
||||
PCB capture, not a substitute for one. All ball assignments below are
|
||||
the real, P&R-verified ones from `hardware/v2/constraints/
|
||||
v2_unified.lpf` (STEP19) unless marked otherwise.
|
||||
|
||||
## 1. Top-level block diagram
|
||||
|
||||
```
|
||||
+---------------------------+
|
||||
| HOST MCU |
|
||||
| SPI |
|
||||
+------------+----------------+
|
||||
|
|
||||
v
|
||||
+----------------------------------------------------------+
|
||||
| ECP5 FPGA (LFE5U-45F-8BG381) |
|
||||
| |
|
||||
| +--------------+ +---------------------------+ |
|
||||
| | SPI Host |---->| Register / Control | |
|
||||
| | Bridge | | (job registration) | |
|
||||
| +--------------+ +-------------+-------------+ |
|
||||
| | |
|
||||
| +-------------v-------------+ |
|
||||
| | Neural Accelerator (N=4) | |
|
||||
| | Processor 0..3 | |
|
||||
| +-------------+-------------+ |
|
||||
| | |
|
||||
| +-------------v-------------+ |
|
||||
| | Unified SDRAM Backend | |
|
||||
| +-------------+-------------+ |
|
||||
+----------------------------------------------------------+
|
||||
|
|
||||
16-bit SDRAM bus
|
||||
v
|
||||
+----------------------------+
|
||||
| AS4C4M16SA-6TIN |
|
||||
| Weights / Activations / |
|
||||
| Results |
|
||||
+----------------------------+
|
||||
|
||||
16 MHz osc --> ECP5 PLL (EHXPLLL) --> 64 MHz system clock
|
||||
Power rails --> POR/supervisor --> FPGA reset, SDRAM init
|
||||
Configuration flash + JTAG connector (see 5/6)
|
||||
```
|
||||
|
||||
## 2. SDRAM connection table (real, P&R-verified balls)
|
||||
|
||||
| Signal | Ball | Bank | I/O std (assumed) | Direction |
|
||||
|---|---|---|---|---|
|
||||
| CLK (shared w/ system clk) | H5 | — | LVCMOS33 | FPGA -> SDRAM |
|
||||
| CKE | B5 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| CS_N | C5 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| RAS_N | C4 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| CAS_N | A3 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| WE_N | B3 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| BA[0] | E4 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| BA[1] | C3 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| A[0..11] | D5,D3,F4,E5,E3,F5,A2,B1,C2,C1,D2,D1 | 7 | LVCMOS33 | FPGA -> SDRAM |
|
||||
| DQ[0..15] | E1,G5,H3,J5,K3,K2,H1,J1,K1,K4,L4,L5,M5,M4,N4,N5 | 7/6 | LVCMOS33 | bidirectional |
|
||||
| DQM[0..1] | P5,N3 | 6 | LVCMOS33 | FPGA -> SDRAM |
|
||||
|
||||
Full source: `hardware/v2/constraints/v2_unified.lpf`. LVCMOS33 is
|
||||
assumed to match the SDRAM's own real 3.3V requirement and matches
|
||||
banks 6/7's real VCCIO range per `docs/pinouts.md` — not yet
|
||||
independently cross-checked at the schematic/PCB level (WARNING, not
|
||||
BLOCKER).
|
||||
|
||||
## 3. Clock schematic
|
||||
|
||||
```
|
||||
16MHz OSC ---> CLKI (H5, reused from V1's own real LPF)
|
||||
|
|
||||
+-----v------+
|
||||
| EHXPLLL | CLKI_DIV=1, CLKFB_DIV=4, CLKOP_DIV=9
|
||||
| (hard IP) | FEEDBK_PATH=CLKOP, VCO=576MHz
|
||||
+-----+------+
|
||||
| CLKOP = 64MHz
|
||||
v
|
||||
FPGA system clock (feeds compute, SDRAM ctrl, SPI bridge)
|
||||
|
|
||||
+-----v------+
|
||||
| reset_sync | <-- ext POR (active-low) + PLL LOCK
|
||||
+-----+------+
|
||||
v
|
||||
rst (sync-deassert, feeds every synchronous block)
|
||||
```
|
||||
|
||||
Oscillator part number: **TBD** (not selected this session — a real
|
||||
16MHz, 3.3V HCMOS clock oscillator in a standard SMD package is the
|
||||
intended class of part; no specific manufacturer/part number is
|
||||
claimed without a real datasheet lookup performed this session).
|
||||
|
||||
## 4. Power schematic (rails only — no regulator parts selected)
|
||||
|
||||
```
|
||||
3.3V/1.1V/2.5V rails (regulators: TBD)
|
||||
| | |
|
||||
v v v
|
||||
VCCIO VCC(core) VCCAUX
|
||||
(banks (real ball (real ball
|
||||
6/7=SDRAM cluster, cluster,
|
||||
I/O, etc) see see
|
||||
POWER_ARCH POWER_ARCH
|
||||
.md) .md)
|
||||
|
|
||||
v
|
||||
SDRAM VDD/VDDQ (3.3V, DATASHEET VALUE per AS4C4M16SA-6TIN)
|
||||
```
|
||||
|
||||
Full rail table, decoupling guidance, and current-budget status:
|
||||
`hardware/v2/docs/POWER_ARCHITECTURE.md` (unchanged this step — no new
|
||||
power work performed).
|
||||
|
||||
## 5. Configuration / JTAG schematic
|
||||
|
||||
```
|
||||
FPGA
|
||||
|-- TDI/TDO/TCK/TMS --> JTAG connector (standard pinout, always
|
||||
| available regardless of boot mode)
|
||||
|-- PROGRAMN/INITN/DONE/CCLK --> configuration flash (part: TBD) or
|
||||
JTAG-only bring-up (decision: OPEN)
|
||||
```
|
||||
|
||||
No configuration-flash part has been selected; JTAG-only bring-up
|
||||
remains a valid fallback and is documented as such in
|
||||
`hardware/v2/docs/CONFIGURATION.md`-equivalent content inside
|
||||
`OPEN_ITEMS.md` (a dedicated `CONFIGURATION.md` was not created this
|
||||
round — tracked as an open item, not silently dropped).
|
||||
|
||||
## 6. Host interface schematic
|
||||
|
||||
```
|
||||
Host MCU --SPI--> FPGA: spi_sclk, spi_mosi, spi_miso, spi_cs_n
|
||||
```
|
||||
|
||||
No ball assignment exists yet for these 4 signals (the board-level
|
||||
top was not run through P&R this session — see the datasheet's own
|
||||
§11 Limitations). Pull-up on `spi_cs_n` (idle-high) is the standard,
|
||||
expected design decision for a single-master SPI bus; not yet placed
|
||||
in any real LPF.
|
||||
|
||||
## 7. What this schematic deliberately does NOT claim
|
||||
|
||||
- No KiCad artifact. No PCB. No fabricated board.
|
||||
- No ball assignment for the new SPI/oscillator/reset pins (P&R not
|
||||
run against the new board-level top this session, since the design
|
||||
has a known, unresolved functional defect — see errors.log
|
||||
ERR-0025 Part B).
|
||||
- No regulator, flash, or connector part numbers.
|
||||
|
||||
This document is a real, honest starting point for PCB capture, not a
|
||||
finished schematic.
|
||||
@@ -1,92 +0,0 @@
|
||||
# NMS Activation Fill Controller Timing (STEP14 Part B)
|
||||
|
||||
Status: fixed, real post-P&R verified, bit-exact, adopted. Full data:
|
||||
`hardware/v2/reports/step14_activation_timing.csv`. Full narrative:
|
||||
`hardware/v2/logs/experiments.log` (EXP-0029, 0030, 0031),
|
||||
`decisions.log` (DEC-0026, DEC-0027).
|
||||
|
||||
## B1 — Exact critical path (not assumed)
|
||||
|
||||
Mined directly from the real nextpnr-ecp5 P&R report for
|
||||
`nms_neural_multiprocessor_stream.v` at N_SLOTS=4
|
||||
(Fmax=55.22 MHz, FAIL @ 80 MHz). Full path, 18.11 ns total (6.25 ns
|
||||
logic + 11.85 ns routing):
|
||||
|
||||
```
|
||||
SOURCE: u_act_fill.resident_tag[11] (register Q)
|
||||
-> COMBINATIONAL, chained, NO register in between:
|
||||
(1) max_n_tiles computation, nms_activation_fill_ctrl.v:92
|
||||
(N_SLOTS-wide running-max fold, each iteration gated by a
|
||||
23-bit tag-equality check) -- long CCU2C carry chain
|
||||
(2) resident_count < max_n_tiles comparison, line 165
|
||||
(the ST_IDLE refill/continue decision) -- ANOTHER 16-bit
|
||||
magnitude-comparison carry chain, feeding directly off (1)
|
||||
in the SAME cycle
|
||||
(3) into pf_start's own next-state logic
|
||||
DESTINATION: u_act_fill.pf_addr's clock-enable (CE) pin
|
||||
```
|
||||
|
||||
Two full 16-bit magnitude comparisons sit in **one** combinational
|
||||
cone across **one** clock edge. This confirms, at the exact RTL-line
|
||||
level, the failure class DEC-0016/EXP-0022 predicted analytically
|
||||
("O(N_SLOTS) unpipelined combinational scan feeding directly into a
|
||||
control decision") — but precisely localizes it to the comparison
|
||||
logic (lines 92 and 165), *not* the priority-encoder
|
||||
(`desired_valid`/`desired_x_base`, lines 77-86), which does not appear
|
||||
in this critical path at all.
|
||||
|
||||
## B2 — Scaling behavior
|
||||
|
||||
The bottleneck is the `max_n_tiles` running-max fold: an imperative
|
||||
`for` loop creates a data dependency between iterations (`max_n_tiles`
|
||||
after iteration *i* depends on iteration *i-1*), which Yosys
|
||||
synthesizes as a sequentially-chained carry structure — inherently
|
||||
O(N_SLOTS) deep, not O(log N_SLOTS). At N_SLOTS=4 the chain reached
|
||||
6.25 ns logic + 11.85 ns routing; at N_SLOTS=8 it doubles again (see
|
||||
below).
|
||||
|
||||
## B3 — Minimum fix (two iterations, evidence-driven)
|
||||
|
||||
**v2** (one pipeline stage: register `max_n_tiles` before its use in
|
||||
the `resident_count` comparison): Fmax 55.22 → 72.78 MHz (+31.8%) —
|
||||
real improvement, still fails 80 MHz. Re-tracing showed the *remaining*
|
||||
critical path was entirely inside `max_n_tiles`'s own computation
|
||||
(now feeding its own register), confirming the fix needed to go one
|
||||
level deeper.
|
||||
|
||||
**v3** (second stage: register each slot's tag-equality/masking result
|
||||
first — independent per-slot work, no N_SLOTS-dependent chain — *then*
|
||||
fold the already-registered, already-masked values): Fmax 55.22 →
|
||||
**106.81 MHz** (+93.4%). **PASSES** 80 MHz with real margin. Resource
|
||||
cost: LUT4 -5.5%, FF +1.4% (2 added pipeline registers), CCU2C
|
||||
unchanged.
|
||||
|
||||
## B4 — No serialization reintroduced
|
||||
|
||||
Verified directly: N_SLOTS=2 bit-exact regression test (D-Stress, real
|
||||
V1 PSRAM chain) gives **numerically identical** cycle count and
|
||||
sustained MAC/cycle before and after the fix (185270/185270 cycles,
|
||||
0.1769/0.1769 MAC/cycle). The 3 total cycles of added latency apply
|
||||
only to the rare, tile-refill-boundary-only decision — never to the
|
||||
real-time per-tile consumption path (already fully decoupled by
|
||||
STEP13's own streaming manager). Higher Fmax, zero throughput cost —
|
||||
satisfying B4's explicit requirement.
|
||||
|
||||
## N=8 (exploratory)
|
||||
|
||||
`nms_activation_fill_ctrl_v3.v` at N_SLOTS=8: DSP=64/72 (89%, FEASIBLE),
|
||||
LUT4=4653, FF=10855 (both comfortably FEASIBLE). **Fmax=52.25 MHz,
|
||||
FAILS 80 MHz** — the v3 fix's second stage (the max-fold itself) is
|
||||
still O(N_SLOTS)-deep; at N=8 it is twice as deep as at N=4 and becomes
|
||||
dominant again. This is expected: v3 shifted the crossover point, it
|
||||
did not eliminate the underlying dependency. A genuine balanced-tree
|
||||
reduction (or a pipeline scaling with log₂(N_SLOTS) rather than a flat
|
||||
2-stage split) would be required for N=8 — not undertaken this round
|
||||
(N=8 is explicitly exploratory; the limiting resource (Fmax, not
|
||||
DSP/LUT/FF/BRAM) is precisely identified and quantified, per spec).
|
||||
|
||||
## Adoption
|
||||
|
||||
`nms_activation_fill_ctrl_v3.v` is adopted as the reference activation
|
||||
fill controller for N_SLOTS≥4 configurations (DEC-0027). The original
|
||||
and the insufficient v2 are preserved for reference.
|
||||
@@ -1,122 +0,0 @@
|
||||
# NMS Continuous Tile Stream — Memory Manager Redesign (STEP13)
|
||||
|
||||
Status: implemented, bit-exact verified, synthesized. **Adopted** as
|
||||
the new reference NMS memory-manager configuration (DEC-0025). Full
|
||||
data: `hardware/v2/nms/reports/batch_processor_{sweep.csv,summary.md}`.
|
||||
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0025 through
|
||||
EXP-0028), `decisions.log` (DEC-0024, DEC-0025).
|
||||
|
||||
## Why this file is not `neural_processor_batch.v`
|
||||
|
||||
The governing brief for this STEP asked for a "batch/continuous
|
||||
neuron execution model" — multiple neurons processed per dispatch, or
|
||||
a continuous neuron stream — to amortize the ~68.5-cycles/neuron
|
||||
non-memory floor found in EXP-0024. Before writing any RTL, Step 1
|
||||
required tracing the actual RTL to find exactly where those cycles
|
||||
go, rather than assuming.
|
||||
|
||||
That trace (EXP-0025, an isolated testbench with `neural_processor.v`
|
||||
+ `nms_memory_manager_pf.v` driven with zero real memory latency
|
||||
anywhere) found: **93.4% of the floor is explained by a 4-cycles/tile
|
||||
serialization bug inside the memory manager's own `ST_RUN` state**,
|
||||
not by per-job dispatch overhead (only 6.6%). `ST_RUN` implements
|
||||
operand delivery as a strictly sequential chain —
|
||||
`read_issued → read_ready → present → consumed` — with zero overlap
|
||||
between consecutive tiles, even though:
|
||||
|
||||
- the local activation/weight SRAMs (`nms_activation_replicated.v`,
|
||||
`nms_weight_packed.v`) have only a 1-cycle `rd_en`-to-data latency;
|
||||
- `neural_processor.v`'s own `operand_ready` is held continuously high
|
||||
through the whole tile-loading phase — its datapath is explicitly
|
||||
designed (per its own header comment) to accept a new tile every
|
||||
cycle while previous tiles drain through the adder tree/accumulator.
|
||||
|
||||
Neither side of this interface requires 4 cycles/tile. It is purely
|
||||
an artifact of the memory manager's own un-pipelined FSM. **The fix is
|
||||
therefore a continuous per-tile streaming redesign of the memory
|
||||
manager, not a neuron-batching scheme — hence
|
||||
`nms_memory_manager_stream.v`, not `neural_processor_batch.v`.**
|
||||
`neural_processor.v` itself required no modification.
|
||||
|
||||
## Design: `nms_memory_manager_stream.v`
|
||||
|
||||
Drop-in replacement for `nms_memory_manager_pf.v` (identical external
|
||||
interface, same `weight_prefetch_engine.v` instance, same outer job
|
||||
FSM `ST_IDLE`/`ST_WAIT_RESULT`/`ST_WRITE_RES`/`ST_DONE`). Only
|
||||
`ST_RUN`'s internal operand-delivery logic differs:
|
||||
|
||||
- `rd_ptr` — the read-**issue** pointer (which tile's SRAM read has
|
||||
been, or is about to be, issued), independent of and normally one
|
||||
tile ahead of `tile_idx` (the **consumption** pointer, i.e. how many
|
||||
tiles `neural_processor.v` has actually accepted).
|
||||
- A 1-deep skid buffer (`buf_valid`/`buf_input`/`buf_weight`/
|
||||
`buf_last`) holds one tile's fully-read SRAM data, presented to NP
|
||||
as `operand_valid`/`input_data`/`weight_data`/`tile_last`.
|
||||
- Every cycle: if a read issued last cycle is landing now (1-cycle
|
||||
SRAM latency), it's captured into the skid buffer; independently, a
|
||||
new read is issued for `rd_ptr` whenever legal (in bounds, weight +
|
||||
activation ready) **and** the buffer will not overflow (empty, or
|
||||
being drained this same cycle).
|
||||
|
||||
Since `operand_ready` stays high throughout the tile-loading phase,
|
||||
the skid buffer drains every cycle it's full, so a new read can be
|
||||
issued every cycle too — sustained ~1 cycle/tile, down from 4.
|
||||
|
||||
`tile_idx` (the consumption pointer) is still what feeds
|
||||
`weight_prefetch_engine.v`'s own `consumed_count` port — its external
|
||||
contract is unchanged; only the local SRAM read-issue pointer
|
||||
(`rd_ptr`) is new, and it can run up to one tile ahead of `tile_idx`
|
||||
(the skid buffer's own depth).
|
||||
|
||||
## Verification chain (all real, none assumed)
|
||||
|
||||
1. **EXP-0025**: isolated zero-latency trace of the *old* design —
|
||||
established the 4-cycles/tile floor and its 93.4% share of
|
||||
EXP-0024's real measured floor.
|
||||
2. **EXP-0026**: same isolated trace against the *new* design — the
|
||||
fix works exactly as designed (confirmed cycle-by-cycle), but
|
||||
total cycles barely move (81→80), because it immediately hits a
|
||||
*second*, previously-masked bottleneck: `weight_prefetch_engine.v`'s
|
||||
own word-fetch rate is *also* exactly 4 cycles/tile (P_IN=8 bytes ÷
|
||||
16-bit bus = 4 word-transactions, 1 cycle/word minimum even at
|
||||
zero real latency) — a bus-**width** ceiling, structurally
|
||||
different from an FSM-serialization ceiling, that happens to
|
||||
coincide numerically today.
|
||||
3. **EXP-0027**: a direct control experiment — a scratch variant with
|
||||
weight-fetch bypassed (always-ready) isolates the new design's
|
||||
*own* ceiling: a clean 1 cycle/tile (100% of `neural_processor.v`'s
|
||||
theoretical per-tile rate), vs. the old design's hard 4-cycles/tile
|
||||
cap under the identical bypass. This is the direct proof that the
|
||||
fix removes a real, 4× architectural ceiling — it was just masked
|
||||
by a coincidentally-equal second bottleneck.
|
||||
4. **EXP-0028**: full real-system integration
|
||||
(`nms_dataflow_core_stream.v` → `nms_neural_multiprocessor_stream.v`,
|
||||
real V1 PSRAM chain) — bit-exact PASS, 256/256 neurons, D-Stress
|
||||
workload identical to EXP-0022/0024. Real cycle count: 185270 vs.
|
||||
185398 (`_pf` baseline), -0.07% — confirms the "masked, zero net
|
||||
benefit today" prediction exactly. Real synthesis + P&R: N=1
|
||||
Fmax=142.92 MHz (+3.7% vs. baseline), N=2 Fmax=92.57 MHz (-2.8%,
|
||||
still comfortably above 80 MHz), resource cost within ±6%. N=4:
|
||||
55.22 MHz, FAILS 80 MHz — but for the *pre-existing*,
|
||||
already-documented `nms_activation_fill_ctrl.v` priority-scan
|
||||
regression (EXP-0022), unrelated to and unaffected by this fix.
|
||||
|
||||
## Outcome and adoption
|
||||
|
||||
**Outcome B** (helps, but another bottleneck appears — see
|
||||
DEC-0025 and `batch_processor_summary.md` for the full nine-question
|
||||
final decision). `nms_memory_manager_stream.v` is adopted as the new
|
||||
reference configuration: it is a strict improvement (bit-exact,
|
||||
resource-neutral, no measured downside) and is **required groundwork**
|
||||
for any future PSRAM bandwidth increase to actually translate into a
|
||||
throughput gain — without it, a wider/faster memory would immediately
|
||||
hit the old FSM's 4-cycles/tile ceiling and realize only 25% of its
|
||||
potential benefit. The original `nms_memory_manager.v` and
|
||||
`nms_memory_manager_pf.v` remain preserved, unmodified, for A/B/C
|
||||
reference. Neuron-batching (the brief's original Model B/C) was not
|
||||
pursued — evidence showed it addresses only 6.6% of the real floor and
|
||||
would deliver no measurable benefit today for the identical reason
|
||||
(weight-fetch-rate-bound). N=4/N=8 viability remains blocked by two
|
||||
independent issues neither addressed by this STEP: external PSRAM
|
||||
bandwidth, and the activation fill controller's own Fmax regression —
|
||||
both flagged as future work.
|
||||
@@ -1,145 +0,0 @@
|
||||
# NMS Real Weight Prefetch Engine (STEP11)
|
||||
|
||||
Status: implemented, bit-exact verified, benchmarked against the real
|
||||
V1 PSRAM chain, synthesized. **Not adopted as the default NMS
|
||||
configuration** — see Outcome/Recommendation below. Full data:
|
||||
`hardware/v2/nms/reports/nms_prefetch_sweep.csv`,
|
||||
`nms_prefetch_summary.md`; full narrative:
|
||||
`hardware/v2/logs/experiments.log` (EXP-0023, EXP-0024),
|
||||
`decisions.log` (DEC-0023), `errors.log` (ERR-0015).
|
||||
|
||||
## Problem
|
||||
|
||||
The "Current NMS" baseline (`nms_memory_manager.v`, backed by
|
||||
`prefetch_engine.v`) measured `prefetch_effectiveness≈0%` and
|
||||
`weight_stall≈92.5%` at N_SLOTS=2 (EXP-0022). Tracing the actual RTL
|
||||
(not assuming from filenames) showed the real gap: `prefetch_engine.v`
|
||||
is a single-shot FSM (`ST_IDLE`/`ST_READ_W`/`ST_DONE`) that can only
|
||||
have **one fetch in flight at a time**, and `nms_memory_manager.v`'s
|
||||
own restart logic only re-triggers the next tile's fetch once the
|
||||
*previous* tile's fetch has fully completed and the FSM has returned
|
||||
to idle — paying a real per-tile control-plane restart cost on every
|
||||
tile boundary. The gap was never insufficient lookahead *distance*
|
||||
(the old design already tried to fetch as far ahead as `n_tiles`
|
||||
allowed); it was zero *outstanding-request depth*.
|
||||
|
||||
## Real backend constraint
|
||||
|
||||
`memory_interface.v` → `psram_controller.v` (V1, reused verbatim,
|
||||
never modified) is a fire-and-forget, **one-transaction-in-flight**
|
||||
protocol: a single `mem_req` pulse, wait for `mem_ready`, and that IS
|
||||
the whole transaction. No wire-level pipelining is physically possible
|
||||
against a real single PSRAM port. So "multiple outstanding requests"
|
||||
cannot mean multiple simultaneous word transactions — it means
|
||||
eliminating the *control-plane* overhead paid at every tile boundary
|
||||
and letting the fetch stream run continuously across tiles, queueing
|
||||
up to `PREFETCH_DISTANCE` tiles of lookahead ahead of consumption.
|
||||
|
||||
## Design: `weight_prefetch_engine.v`
|
||||
|
||||
Two monotonic counters fully describe the engine (tiles are always
|
||||
fetched in strict sequential order, never reordered or re-fetched, so
|
||||
no per-tile state array is needed):
|
||||
|
||||
- `fetch_tile`/`fetch_word` — the next word to request (or the word
|
||||
currently in flight).
|
||||
- `ready_count` — tiles 0..`ready_count`-1 are fully resident in the
|
||||
weight SRAM.
|
||||
|
||||
`consumed_count` (the consumer's own tile index, `nms_memory_manager_pf.v`'s
|
||||
`tile_idx`) bounds a configurable lookahead window:
|
||||
`window_limit = consumed_count + PREFETCH_DISTANCE`; the engine may
|
||||
fetch tile K only if `K < n_tiles` **and** `K < window_limit`.
|
||||
|
||||
The core mechanism: on `mem_ready && req_outstanding`, the just-completed
|
||||
word is committed **and**, in the same cycle, the very next request is
|
||||
issued — either the same tile's next word, or (at a tile boundary) the
|
||||
next tile's first word — giving zero-gap streaming across tile
|
||||
boundaries against a backend that only ever has one word in flight.
|
||||
(An earlier draft used mutually-exclusive `if/else-if` branches for
|
||||
"commit" vs. "issue next", which reintroduced a 1-cycle gap between
|
||||
*every* word, not just tile boundaries; fixed by merging both into one
|
||||
branch — see `weight_prefetch_engine.v`'s own header comment.)
|
||||
|
||||
## Integration: the "_pf" A/B variants
|
||||
|
||||
Per the explicit "preserve the current NMS baseline" constraint, the
|
||||
new engine was integrated into parallel `_pf`-suffixed files, leaving
|
||||
the originals untouched:
|
||||
|
||||
- `nms_memory_manager_pf.v` — drop-in replacement for
|
||||
`nms_memory_manager.v`'s external interface; internally swaps the
|
||||
private `prefetch_engine.v` instance for `weight_prefetch_engine.v`,
|
||||
and changes `can_present`'s weight-ready check from
|
||||
`tile_idx < wgt_fetched` to `tile_idx < wgt_ready_count`.
|
||||
- `nms_dataflow_core_pf.v` — mirrors `nms_dataflow_core.v`, adds a
|
||||
`PREFETCH_DISTANCE` parameter, instantiates `nms_memory_manager_pf`.
|
||||
- `nms_neural_multiprocessor_pf.v` — mirrors
|
||||
`nms_neural_multiprocessor.v`, instantiates `nms_dataflow_core_pf`.
|
||||
|
||||
Both the baseline (`nms_neural_multiprocessor.v`) and the prefetch
|
||||
variant (`nms_neural_multiprocessor_pf.v`) remain in the repository
|
||||
side by side; neither supersedes the other.
|
||||
|
||||
## Verification
|
||||
|
||||
`hardware/v2/nms/sim/tb_weight_prefetch.v` — isolated correctness
|
||||
testbench: real `sim_word_mem` (configurable extra latency), real
|
||||
`nms_weight_packed.v` production SRAM, bit-exact fill-pattern checking.
|
||||
Covers `n_tiles ∈ {0,1,2,PFD,PFD+1,MAX_TILES-1,MAX_TILES}`, back-to-back
|
||||
jobs with no explicit reset, a dedicated windowing-cap test (frozen
|
||||
consumer, confirms `ready_count` stops exactly at
|
||||
`min(PFD,MAX_TILES)`), and (post-ERR-0015) a large-PFD regression case.
|
||||
10/10 (9/9 at PFD≥MAX_TILES) tests pass bit-exact across
|
||||
PFD∈{1,2,4,8,32} and under injected extra memory latency.
|
||||
|
||||
`hardware/v2/nms/sim/tb_nms_dstress_pf.v` — full real-integration
|
||||
benchmark: identical D-Stress workload/golden-model/correctness
|
||||
criteria as `tb_nms_dstress.v` (EXP-0022), instantiating
|
||||
`nms_neural_multiprocessor_pf` with a `PFD_CFG` parameter, plus new
|
||||
testbench-only instrumentation for `weight_stall_cycles` and
|
||||
`prefetch_effectiveness` (tiles consumed with zero weight-blocking
|
||||
cycles beforehand / total tiles consumed — the exact STEP11
|
||||
definition). All runs pass 256/256 neurons bit-exact vs. the golden
|
||||
model.
|
||||
|
||||
## ERR-0015: a real bug found and fixed
|
||||
|
||||
The initial `window_limit` computation truncated the
|
||||
`PREFETCH_DISTANCE` *parameter itself* to `CNTW` bits
|
||||
(`PREFETCH_DISTANCE[CNTW-1:0]`) before adding it to `consumed_count`.
|
||||
At `MAX_TILES=16` (`CNTW=5` bits), `PFD=32` truncates to 0, making
|
||||
`window_limit == consumed_count` forever and deadlocking the engine
|
||||
completely (0/256 neurons ever completed, 0% PSRAM utilization).
|
||||
Fixed by computing `window_limit` and its comparisons in a fixed
|
||||
32-bit width, using the untruncated parameter value. Regression-tested
|
||||
in `tb_weight_prefetch.v`. Full writeup: `errors.log` ERR-0015.
|
||||
|
||||
## Results and outcome
|
||||
|
||||
See `nms_prefetch_summary.md` for the full comparison table and the
|
||||
nine explicitly-answered final-report questions. In short:
|
||||
|
||||
- **N_SLOTS=1** (no port contention): a real, reproducible **-10.3%**
|
||||
cycle-count improvement (PFD=1 → PFD≥2), then a complete plateau —
|
||||
deeper buffering gives zero further benefit. Sustained MAC/cycle
|
||||
reaches only 2.8% of the theoretical target.
|
||||
- **N_SLOTS=2** (this project's own primary reference configuration,
|
||||
real shared-port contention via `slot_mem_arbiter`): **zero
|
||||
measurable benefit** at any PREFETCH_DISTANCE from 1 to 16 — all
|
||||
runs are statistically indistinguishable from each other and from
|
||||
the pre-STEP11 baseline. The single physical PSRAM port is already
|
||||
saturated (90.5% busy, unchanged from baseline) by natural two-slot
|
||||
contention before any lookahead scheme can act.
|
||||
|
||||
**Final decision: Outcome B (N_SLOTS=1, partial) / Outcome C
|
||||
(N_SLOTS=2, failure against the 90% criterion).** The mechanism is
|
||||
correct and does measurably hide latency when the port has spare
|
||||
capacity; it cannot manufacture bandwidth out of an already-saturated
|
||||
single physical port. Reaching the STEP11 target would require ~36×
|
||||
(N=1) to ~82× (N=2) more real PSRAM bandwidth — a hardware-level
|
||||
constraint, not an RTL-scheduling one. Per DEC-0023, the new engine is
|
||||
**not** recommended as the default NMS configuration; both variants
|
||||
are preserved for reference. The evidence-backed next step (real PSRAM
|
||||
bandwidth — wider bus, multiple independent banks, or a faster backing
|
||||
technology) is flagged as future work, not undertaken this round.
|
||||
@@ -1,91 +0,0 @@
|
||||
# NMS Weight Datapath Scaling (STEP14 Part A)
|
||||
|
||||
Status: architectural requirement established and proven (simulation),
|
||||
**not realizable on real hardware today** (fixed 16-bit physical
|
||||
PSRAM). Full data: `hardware/v2/reports/step14_weight_scaling.csv`.
|
||||
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0032, EXP-0033),
|
||||
`decisions.log` (DEC-0028).
|
||||
|
||||
## Question answered
|
||||
|
||||
*At what weight-path width does the processor stop being fundamentally
|
||||
starved by weight delivery?* **64 bits** — exactly `P_IN × DATA_WIDTH`
|
||||
(8 × 8). Proven by direct cycle-exact simulation, not assumed.
|
||||
|
||||
## What was built
|
||||
|
||||
`weight_prefetch_engine_wide.v` — a parameterized (`MEM_DATA_WIDTH`)
|
||||
generalization of the real `weight_prefetch_engine.v`'s continuous
|
||||
cross-tile-boundary streaming design, simulation-only/exploratory
|
||||
(same status as `ideal_memory_model.v`). `WORDS_PER_TILE =
|
||||
ceil(P_IN*DATA_WIDTH / MEM_DATA_WIDTH)`, clamped to a minimum of 1.
|
||||
`nms_memory_manager_stream_wide.v` pairs it with STEP13's own streaming
|
||||
memory manager unchanged (A2's requirement), on a *separate* logical
|
||||
wide port from the real 16-bit result-write-back port.
|
||||
|
||||
A real bug was found and fixed during development: address stepping
|
||||
initially used `WORDS_PER_TILE × BYTES_PER_WORD` as the inter-tile
|
||||
byte stride, which over-counts whenever the bus is wider than one full
|
||||
tile (the 128-bit case, `WORDS_PER_TILE=1` but `BYTES_PER_WORD=16`
|
||||
while the tile itself is only 8 bytes) — this skips over the next
|
||||
tile's actual data in the packed backing store. Fixed by defining
|
||||
`TILE_BYTES = TILE_BITS/8` as the canonical, width-independent stride.
|
||||
|
||||
## Results (bit-exact + ideal-memory cycle count)
|
||||
|
||||
| Width | Words/tile | Steady-state cycles/tile | 16-tile job total |
|
||||
|---|---|---|---|
|
||||
| 16-bit | 4 | 4 | 80 |
|
||||
| 32-bit | 2 | 2 | 48 |
|
||||
| **64-bit** | **1** | **1** | **32** |
|
||||
| 128-bit | 1 | 1 | 32 |
|
||||
|
||||
All four widths pass bit-exact correctness (9/9 tests each, including
|
||||
under injected extra memory latency). 64-bit achieves a clean,
|
||||
cycle-exact **1 cycle/tile** — 100% of `neural_processor.v`'s own
|
||||
theoretical per-tile acceptance rate, exactly matching the streaming
|
||||
memory manager's own ceiling (EXP-0027, STEP13). 128-bit gives **zero**
|
||||
further benefit: a bus wider than one full tile still delivers exactly
|
||||
one tile per transaction in this single-tile-per-request design (no
|
||||
multi-tile bursting was attempted).
|
||||
|
||||
## The critical distinction: logical vs. physical bandwidth (A5)
|
||||
|
||||
STEP14 explicitly warned against assuming a wider logical interface
|
||||
means the real memory can deliver it. It cannot, here: **the real V1
|
||||
PSRAM chain is fixed at 16 bits** — a real chip
|
||||
(ISSI IS66WVE4M16EBLL-70BLI, x16), not an RTL parameter. The
|
||||
already-existing, already-verified `weight_prefetch_engine.v` (real,
|
||||
used throughout STEP11-13) *is* exactly what a "64-bit logical / 16-bit
|
||||
physical" packing adapter would produce: it assembles one 64-bit
|
||||
logical tile from 4 real sequential 16-bit word transactions. Its real,
|
||||
repeatedly-measured result is 4 cycles/tile — identical to the ideal
|
||||
16-bit row above, because the real transaction count is unchanged
|
||||
regardless of what the logical interface upstream claims. **A logical
|
||||
wide interface backed by a physically-narrow bus delivers exactly the
|
||||
narrow bus's own throughput.** No new "packing adapter" module was
|
||||
built for this reason — the real engine already demonstrates the
|
||||
answer, conclusively, without further RTL.
|
||||
|
||||
## Answer to the primary research questions
|
||||
|
||||
- **Is 16-bit weight delivery fundamentally insufficient for P_IN=8?**
|
||||
Yes — it costs 4 cycles/tile, 4× the achievable minimum.
|
||||
- **Is 32-bit enough?** No — still 2× the achievable minimum (2
|
||||
cycles/tile).
|
||||
- **Is 64-bit the natural architectural point?** Yes, exactly — proven
|
||||
cycle-exact, not approximate.
|
||||
- **Does wider logical delivery actually improve real throughput?**
|
||||
**Not on this hardware.** Realizing the 64-bit ideal requires a
|
||||
matching *physical* bandwidth increase (a real 64-bit-wide external
|
||||
bus, or multiple parallel 16-bit PSRAM chips banked together) — a
|
||||
board/silicon-level change, outside this project's own RTL scope.
|
||||
|
||||
## Recommendation
|
||||
|
||||
The 64-bit requirement is now precisely quantified and should inform
|
||||
any future hardware revision (wider PSRAM, multiple banks). No RTL
|
||||
change is warranted on the current board: `weight_prefetch_engine.v`
|
||||
(real, 16-bit) remains the correct, already-optimal implementation
|
||||
given the fixed physical bus width — STEP13's streaming-manager fix
|
||||
already extracts everything available from the real interface.
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
Perfetto, il CSV è quello giusto: FPGA-SC-02034-3-0-ECP5U-45-Pinout.csv, rev. 3.0. L'ho letto e possiamo finalmente fare il pinout esatto per il LFE5U-45F caBGA381, senza deduzioni.
|
||||
|
||||
Ho anche verificato la documentazione Lattice aggiornata: per LFE5U VCC = 1,1 V ±5%, VCCAUX = 2,5 V ±5%, VCCIO da 1,2 a 3,3 V e VCCIO8 è il banco associato alla configurazione.
|
||||
|
||||
Pinout essenziale BG381
|
||||
Ball Funzione Alimentazione / collegamento
|
||||
H13 VCC 1.1 V
|
||||
J13 VCC 1.1 V
|
||||
K13 VCC 1.1 V
|
||||
L13 VCC 1.1 V
|
||||
M13 VCC 1.1 V
|
||||
N13 VCC 1.1 V
|
||||
H12 VCC 1.1 V
|
||||
N12 VCC 1.1 V
|
||||
H11 VCC 1.1 V
|
||||
N11 VCC 1.1 V
|
||||
H10 VCC 1.1 V
|
||||
N10 VCC 1.1 V
|
||||
H9 VCC 1.1 V
|
||||
N9 VCC 1.1 V
|
||||
H8 VCC 1.1 V
|
||||
J8 VCC 1.1 V
|
||||
K8 VCC 1.1 V
|
||||
L8 VCC 1.1 V
|
||||
M8 VCC 1.1 V
|
||||
N8 VCC 1.1 V
|
||||
F6 VCCAUX 2.5 V
|
||||
P6 VCCAUX 2.5 V
|
||||
F15 VCCAUX 2.5 V
|
||||
P15 VCCAUX 2.5 V
|
||||
F9, F10 VCCIO0 Bank 0
|
||||
F11, F12 VCCIO1 Bank 1
|
||||
H14, H15, J15 VCCIO2 Bank 2
|
||||
L14, L15, M15 VCCIO3 Bank 3
|
||||
L6, L7, M6 VCCIO6 Bank 6
|
||||
H6, H7, J6 VCCIO7 Bank 7
|
||||
P9, P10 VCCIO8 Bank 8 / configuration
|
||||
|
||||
Nota: il CSV elenca anche i pin VSS/VSSIO; vanno tutti collegati correttamente al piano GND, senza lasciarne nessuno scollegato.
|
||||
|
||||
JTAG / programmazione
|
||||
|
||||
Questi sono particolarmente importanti perché sono ball esatti del BG381:
|
||||
|
||||
Ball Pin Bank Collegamento
|
||||
R5 TDI 40 JTAG TDI
|
||||
T5 TCK 40 JTAG TCK
|
||||
U5 TMS 40 JTAG TMS
|
||||
V4 TDO 40 JTAG TDO
|
||||
W3 PROGRAMN 8 controllo configurazione
|
||||
V3 INITN 8 stato configurazione
|
||||
Y3 DONE 8 configurazione completata
|
||||
|
||||
Il bank 8 è quello associato all'interfaccia di configurazione; Lattice indica esplicitamente VCCIO8 come alimentazione da dimensionare in funzione dell'interfaccia di configurazione utilizzata.
|
||||
|
||||
CCLK
|
||||
|
||||
Dal CSV:
|
||||
|
||||
Ball Pin Bank
|
||||
U3 CCLK 8
|
||||
|
||||
Attenzione però: CCLK non è automaticamente il clock di sistema della nostra rete neurale. È il clock associato alla configurazione; il clock operativo della FPGA va identificato separatamente nel percorso dell'oscillatore/PLL.
|
||||
|
||||
Decoupling
|
||||
|
||||
Qui preferisco essere molto preciso: non voglio inventare una quantità di condensatori per “ogni VCC”.
|
||||
|
||||
La Hardware Checklist ufficiale Lattice richiede esplicitamente:
|
||||
|
||||
VCC 1,1 V con PCB plane e adeguato decoupling;
|
||||
VCCAUX 2,5 V;
|
||||
VCCIO 1,2–3,3 V;
|
||||
VCCIO8 coerente con l'interfaccia di configurazione.
|
||||
|
||||
Per la nostra scheda farei quindi una rete di decoupling distribuita, non un condensatore unico per ogni ball:
|
||||
|
||||
LFE5U-45F
|
||||
┌──────────────┐
|
||||
1V1_CORE ─────┤ VCC │
|
||||
│ │
|
||||
2V5_AUX ──────┤ VCCAUX │
|
||||
│ │
|
||||
3V3/2V5/etc ──┤ VCCIOx │
|
||||
│ │
|
||||
GND ──────────┤ VSS │
|
||||
└──────────────┘
|
||||
|
||||
con i condensatori fisicamente distribuiti attorno al BGA, con quelli ad alta frequenza il più vicino possibile alle rispettive vie/pad di alimentazione.
|
||||
|
||||
Una cosa importante
|
||||
|
||||
Adesso che abbiamo il CSV ufficiale, posso fare un lavoro ancora più utile: estrarre tutti i 381 ball del BG381 e generarti una tabella Markdown completa divisa in:
|
||||
|
||||
VCC
|
||||
VCCAUX
|
||||
VCCIO0–8
|
||||
VSS/VSSIO
|
||||
JTAG
|
||||
PROGRAMN/INITN/DONE
|
||||
clock
|
||||
GPIO utilizzabili
|
||||
pin da NON utilizzare
|
||||
BOM decoupling consigliata
|
||||
header JTAG completo
|
||||
|
||||
Quella diventerebbe praticamente la nostra scheda di riferimento hardware per il PCB V2.
|
||||
@@ -1,203 +0,0 @@
|
||||
# Fase 0 — Inventario reale della repo
|
||||
|
||||
Data: 2026-09-04. Metodo: lettura diretta dei file, non delle descrizioni in WORKLOG.md o
|
||||
nei datasheet. Ogni claim qui sotto è verificato con un comando citato, rieseguibile.
|
||||
|
||||
---
|
||||
|
||||
## 0.1 Struttura dei file
|
||||
|
||||
| Area | File | Righe totali |
|
||||
|---|---|---|
|
||||
| `rtl/*.v` | 20 file | 7016 |
|
||||
| `sim/*_tb.v` (testbench) | 33 file | 12934 |
|
||||
| `sim/*.v` non-tb (modelli/benchmark) | 4 file: `flash_model.v`, `psram_model.v`, `flash_latency_bench.v`, `top.v` | — |
|
||||
| `tools/` | `fpga_benchmark.py`, `netasm/` (assembler+parser+cli+frames, con test suite propria), `pinout/gen_lpf.py`, `flash_catalog/oracle.py`, **`run_regression.py` (nuovo, questa fase)** | — |
|
||||
| `docs/` | 4 documenti `.md` + 2 PDF | — |
|
||||
|
||||
**Elenco RTL completo**: `act_buffer.v`, `crc32.v`, `flash_copy_engine.v`,
|
||||
`flash_slot_manager.v`, `graph_engine.v`, `int8_memory_access.v`, `layer_sequencer.v`,
|
||||
`layer.v`, `mac_unit.v`, `mac8.v`, `mem_arbiter.v`, `memory_interface.v`, `memory_model.v`,
|
||||
`neuron_memory.v`, `neuron_parallel.v`, `psram_controller.v`, `spi_engine.v`,
|
||||
`spi_flash_master.v`, `spi_neuron_top.v`, `spi_slave.v`.
|
||||
|
||||
### Discrepanza doc↔codice: conteggio testbench
|
||||
|
||||
Il prompt di questa campagna cita "la regressione dichiarata (22 testbench)". Il conteggio
|
||||
reale via `find sim -name "*_tb.v" | wc -l` è **33** (più 1 file benchmark mal nominato con
|
||||
suffisso `_tb.v`, vedi §0.3) — il numero 22 è superato da fasi successive del progetto
|
||||
(sottosistema flash e Tipo #2 grafo, aggiunti dopo). Non è un errore nel senso di un bug: è
|
||||
un documento/prompt che descrive uno stato precedente. Il numero corrente verificato è 33.
|
||||
|
||||
---
|
||||
|
||||
## 0.2 Codice morto/orfano trovato
|
||||
|
||||
### `sim/top.v` — **DEAD, non compila contro l'RTL corrente**
|
||||
|
||||
```
|
||||
$ iverilog -g2012 -o /tmp/topcheck.out rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v sim/top.v
|
||||
sim/top.v:17: error: parameter `FRAC_BITS` not found in `top.dut`.
|
||||
2 error(s) during elaboration.
|
||||
```
|
||||
|
||||
`sim/top.v` istanzia `neuron_parallel` con `.DATA_WIDTH(16)` e `.FRAC_BITS(8)` — un residuo
|
||||
della versione a virgola fissa Q8.8 del progetto, prima che fosse convertito a INT8 puro
|
||||
(coerente con `WORKLOG.md`, Fase 6: "rimosso FRAC\_BITS e funzione q8\_8"). Il modulo
|
||||
`neuron_parallel.v` corrente non ha più un parametro `FRAC_BITS`. Il file è tracciato in git
|
||||
(`git log --oneline -- sim/top.v` → `d4ae241 test: validate parametric 32x4 layer with
|
||||
parallelism 8`, un commit storico) ma **non è referenziato da nessun testbench, script, o
|
||||
tool di questo repo** — non partecipa alla regressione, non compila. Non toccato in questa
|
||||
fase (nessuna modifica, per policy §E del prompt di certificazione — la rimozione, se
|
||||
voluta, è una decisione separata dall'analisi).
|
||||
|
||||
### Nessun altro modulo RTL orfano
|
||||
|
||||
Ogni file in `rtl/*.v` è raggiungibile da almeno un testbench (direttamente o
|
||||
transitivamente) — vedi matrice §0.4. Nessun modulo istanziato da zero testbench e da
|
||||
nessun altro modulo RTL.
|
||||
|
||||
### Due file "memory model" distinti — non un bug, ma nome ambiguo
|
||||
|
||||
`rtl/memory_model.v` (interfaccia generica `req/wr/addr/wdata/rdata` con `READ_LATENCY`
|
||||
parametrico, usato solo da `sim/memory_interface_tb.v` per isolare `memory_interface.v` dal
|
||||
timing reale della PSRAM) è un modulo diverso da `sim/psram_model.v` (interfaccia
|
||||
pin-accurate `ce_n/oe_n/we_n/lb_n/ub_n`, usato da praticamente tutti i test di integrazione
|
||||
reali). Non è un bug — sono stub di fedeltà diversa per scopi diversi — ma il nome simile
|
||||
(`memory_model` vs `psram_model`) e la collocazione di uno stub-solo-per-test dentro `rtl/`
|
||||
anziché `sim/` è una scelta organizzativa che vale la pena segnalare per chi legge la repo
|
||||
la prima volta.
|
||||
|
||||
---
|
||||
|
||||
## 0.3 Convenzione di naming inconsistente: benchmark con suffisso `_tb.v`
|
||||
|
||||
`sim/graph_engine_bandwidth_tb.v` ha il suffisso `_tb.v` (come i 33 testbench veri) ma è in
|
||||
realtà un **benchmark** — stampa numeri misurati (`edges/sec`, `bandwidth`), non ha verdetto
|
||||
PASS/FAIL, per progetto (stesso stile dichiarato di `sim/flash_latency_bench.v`, che invece
|
||||
**non** ha il suffisso `_tb.v` e quindi non viene raccolto insieme ai testbench veri da un
|
||||
comando generico `find sim -name "*_tb.v"`). Questa incoerenza di naming ha causato una
|
||||
classificazione errata al primo giro del regression runner (§0.5) — corretta dopo aver letto
|
||||
l'intento dichiarato nell'header del file, non assumendolo.
|
||||
|
||||
---
|
||||
|
||||
## 0.4 Matrice modulo → testbench (istanziazione diretta)
|
||||
|
||||
Costruita via analisi statica delle istanziazioni (`^\s*modulo\s+(#\(|nomeistanza\s*\()`),
|
||||
non a memoria.
|
||||
|
||||
| Modulo RTL | Testbench che lo istanziano direttamente |
|
||||
|---|---|
|
||||
| `act_buffer` | `act_buffer_tb` |
|
||||
| `crc32_byte` (in `crc32.v`) | `crc32_tb` |
|
||||
| `flash_copy_engine` | `flash_copy_engine_{erase,load,save}_tb` |
|
||||
| `flash_slot_manager` | `flash_slot_manager_tb`, `flash_slot_manager_raw_tb` |
|
||||
| `graph_engine` | `graph_engine_tb`, `graph_engine_guard_tb`, `graph_engine_bandwidth_tb` |
|
||||
| `int8_memory_access` | 10 testbench (tutti quelli con path PSRAM reale) |
|
||||
| `layer_sequencer` | `layer_sequencer_tb` |
|
||||
| `layer` | `layer_tb`, `parametric_tb` |
|
||||
| **`mac_unit`** | **nessuno — 0 istanziazioni dirette in `sim/`** |
|
||||
| **`mac8`** | **nessuno — 0 istanziazioni dirette in `sim/`** |
|
||||
| `mem_arbiter` | 4 testbench (i 4 test flash con path PSRAM) |
|
||||
| `memory_interface` | 12 testbench |
|
||||
| `memory_model` | `memory_interface_tb` (solo questo) |
|
||||
| `neuron_memory` | `neuron_memory_tb`, `neuron_memory_multi_tb` |
|
||||
| `neuron_parallel` | 5 testbench (incl. i 2 negativi, §0.5) |
|
||||
| `psram_controller` | 13 testbench |
|
||||
| `spi_engine` | `spi_engine_tb` |
|
||||
| `spi_flash_master` | `spi_flash_master_tb` |
|
||||
| `spi_neuron_top` | 5 testbench (`_tb`, `_graph_tb`, `_irq_tb`, `_runnetwork_tb`, `_flash_tb`) |
|
||||
| `spi_slave` | `spi_slave_tb`, `spi_engine_tb` |
|
||||
|
||||
### Finding da riportare in C.1 (Datapath aritmetico)
|
||||
|
||||
**`mac_unit.v` e `mac8.v` non hanno un testbench unitario dedicato.** Sono esercitati solo
|
||||
indirettamente, come sotto-componenti di `neuron_parallel` nei test di livello superiore
|
||||
(`neuron_parallel_tb`, `neuron_parallel_saturation_bounds_tb`, ecc.). Questo significa che
|
||||
un comportamento scorretto isolato di `mac_unit`/`mac8` (es. estensione di segno errata sul
|
||||
prodotto INT8×INT8, prima dell'accumulo) sarebbe rilevabile solo se si propaga fino
|
||||
all'uscita finale del layer con un pattern di input che lo renda visibile — non c'è un
|
||||
oracolo che verifichi `mac_unit` da solo. **Non certificabile come "coperto" fino a C.1.**
|
||||
|
||||
---
|
||||
|
||||
## 0.5 Regressione: eseguita da zero con harness nuovo, non fidandosi del WORKLOG
|
||||
|
||||
**Non esisteva alcuno script di regressione riproducibile nella repo** prima di questa fase
|
||||
— ogni precedente affermazione "N testbench, tutti PASS" in `WORKLOG.md` è stata prodotta
|
||||
assemblando a mano la lista file `iverilog` per ciascun test, mai da un harness unico
|
||||
rieseguibile. Questo è di per sé un gap reale (nessuna prova automatizzata, riproducibile,
|
||||
del claim di regressione) — colmato creando **`tools/run_regression.py`**: risolve le
|
||||
dipendenze di ogni testbench per analisi statica delle istanziazioni (non a memoria/elenco
|
||||
scritto a mano), compila con `iverilog -g2012` ed esegue con `vvp`, classifica il risultato.
|
||||
|
||||
**Primo run**: 2 falsi negativi e 1 "sconosciuto" — non erano bug, erano un **blind spot del
|
||||
mio stesso harness**, corretto leggendo il codice sorgente dei test incriminati (non
|
||||
assumendo):
|
||||
- `neuron_parallel_guard_negative_{degenerate,nonmultiple}_tb.v` sono **test negativi
|
||||
dichiarati**: il loro header dice esplicitamente "This file must FAIL TO
|
||||
COMPILE/ELABORATE. That failure is the test" — verificano che il guard
|
||||
`N_INPUTS % PARALLEL != 0` di `rtl/neuron_parallel.v:71-72` blocchi l'elaborazione
|
||||
istanziando un modulo inesistente (`neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL`)
|
||||
quando la condizione è violata. Il fallimento di compilazione **è** il PASS.
|
||||
- `graph_engine_bandwidth_tb.v` è un benchmark (§0.3), nessun verdetto per progetto.
|
||||
|
||||
Corretto l'harness (whitelist esplicita per questi 3 casi, letta dal codice sorgente stesso
|
||||
dei test, non inventata) e rilanciato:
|
||||
|
||||
```
|
||||
$ python3 tools/run_regression.py
|
||||
TOTAL: 34 PASS: 33 FAIL/ERROR: 0 OTHER/UNKNOWN: 1
|
||||
```
|
||||
|
||||
**33/33 testbench reali PASS, 0 regressioni, 1 benchmark eseguito correttamente senza
|
||||
verdetto (per progetto).** Il claim del WORKLOG ("tutti i testbench passano") è **confermato
|
||||
vero** da un run indipendente e da zero — non solo creduto sulla parola.
|
||||
|
||||
`tools/netasm/tests/test_netasm.py` (20 test) verificato separatamente, anch'esso da zero:
|
||||
**20/20 PASS**, invariato.
|
||||
|
||||
---
|
||||
|
||||
## 0.6 Osservazione preliminare, da verificare formalmente in C.1
|
||||
|
||||
Leggendo `rtl/neuron_parallel.v:70-73`, il guard elaboration-time è **un solo** controllo:
|
||||
|
||||
```verilog
|
||||
if (N_INPUTS % PARALLEL != 0) begin : PARAMETER_ERROR_N_INPUTS_NOT_MULTIPLE_OF_PARALLEL
|
||||
neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL invalid_parameter_combination();
|
||||
end
|
||||
```
|
||||
|
||||
Il commento del progetto dice che questo guard copre **sia** "N_INPUTS non multiplo di
|
||||
PARALLEL" **sia** il caso degenere "PARALLEL > N_INPUTS" (con `GROUPS=0`, hang documentato).
|
||||
Ma matematicamente: se `N_INPUTS == 0`, allora `N_INPUTS % PARALLEL == 0` per qualunque
|
||||
`PARALLEL != 0` — il guard **non scatta**, eppure `GROUPS = 0/PARALLEL = 0`, la stessa
|
||||
condizione di hang che il guard dichiara di prevenire. **Non ancora verificato se
|
||||
`N_INPUTS=0` sia un caso raggiungibile/rilevante nella pratica** (nessun layer con zero
|
||||
ingressi ha senso semantico, ma nessun controllo esplicito lo esclude) — portato come
|
||||
finding aperto da chiudere formalmente in C.1 con un test avversariale dedicato e un
|
||||
oracolo indipendente, non certificato né come bug né come non-bug qui.
|
||||
|
||||
---
|
||||
|
||||
## 0.7 Artefatti fuori dal codice sorgente (non toccati)
|
||||
|
||||
- `docs/FPGANeuralDatasheet.pdf` e `docs/FPGANeuralDatasheetEN.pdf`: comparsi come file non
|
||||
tracciati, non generati da alcun processo di build di questo repo (il datasheet LaTeX vive
|
||||
in `DataSheet/`, una directory separata non versionata — vedi memoria di progetto).
|
||||
Probabile sottoprodotto del meccanismo di invio file usato in questa sessione. Non fanno
|
||||
parte della fonte di verità RTL/documentazione; non modificati né cancellati (non è una
|
||||
decisione di questa fase).
|
||||
- `FPGA-Neural/` (progetto KiCad): non tracciato per policy di progetto pre-esistente
|
||||
(vedi memoria), non toccato.
|
||||
|
||||
---
|
||||
|
||||
## 0.8 Prossimi passi
|
||||
|
||||
Procedo con gli aspetti C.1–C.14 uno alla volta, ciascuno con: analisi statica, test
|
||||
avversari con oracolo indipendente (§A.1/A.3), verdetto tracciato. Il finding aperto di
|
||||
§0.6 (`N_INPUTS=0`) va chiuso in C.1. `mac_unit`/`mac8` senza test unitario (§0.4) va colmato
|
||||
in C.1 prima di poter certificare il datapath aritmetico.
|
||||
@@ -1,162 +0,0 @@
|
||||
# C.1 — Datapath aritmetico (`mac_unit`, `mac8`, `neuron_parallel`)
|
||||
|
||||
Data: 2026-09-04. Chiude i due punti aperti in Fase 0 (§0.4 copertura `mac_unit`/`mac8`,
|
||||
§0.6 gap del guard `N_INPUTS=0`).
|
||||
|
||||
---
|
||||
|
||||
## 1.1 `mac_unit.v` — CERTIFICATO
|
||||
|
||||
**Metodo**: test esaustivo, non a campione. `mac_unit` è puramente combinazionale
|
||||
(`x`, `w`, `acc_in` → `acc_out = acc_in + sign_extend(x*w)`), a `DATA_WIDTH=8` esistono
|
||||
esattamente 256×256=65536 combinazioni possibili di `(x,w)` — tutte esercitate, non un
|
||||
sottoinsieme casuale.
|
||||
|
||||
**Oracolo**: `tools/validation/mac_oracle.py`, reimplementazione Python indipendente
|
||||
dell'aritmetica complemento-a-due da zero (non trascritta dall'RTL) — auto-verificata contro
|
||||
6 casi derivati a mano prima di essere usata come oracolo per chiunque altro
|
||||
(`python3 tools/validation/mac_oracle.py` → `ALL SELF-CHECKS PASSED`).
|
||||
|
||||
**Test**: `sim/mac_unit_tb.v`, due batterie:
|
||||
1. Le 65536 combinazioni esaustive di `(x,w)`, `acc_in=0` (l'uso reale in `mac8.v`, dove
|
||||
`acc_in` è cablato a 0 per ogni istanza `mac_unit`).
|
||||
2. 486 vettori a `acc_in` diverso da zero — inclusi i 4 valori limite (`0`, `±2³¹`, `±2³⁰`) e
|
||||
i 4 angoli di grandezza massima del prodotto (`±128×±128`, `±128×∓127`) — a copertura del
|
||||
**contratto di porta completo** del modulo, non solo di come viene usato oggi.
|
||||
|
||||
```
|
||||
$ iverilog -g2012 -o /tmp/mac_unit_tb.out rtl/mac_unit.v sim/mac_unit_tb.v && vvp /tmp/mac_unit_tb.out
|
||||
ALL TESTS PASSED (66022 vectors, 0 mismatches against independent Python oracle)
|
||||
```
|
||||
|
||||
**Verdetto: CERTIFICATO.** 66022/66022 vettori, 0 mismatch, copertura esaustiva sullo
|
||||
spazio degli input a INT8. Nessuna riserva.
|
||||
|
||||
---
|
||||
|
||||
## 1.2 `mac8.v` — adder tree bilanciato — CERTIFICATO
|
||||
|
||||
**Metodo**: `mac8.v` non aveva alcun test unitario dedicato (Fase 0, §0.4) — solo copertura
|
||||
indiretta a un singolo `PARALLEL` tramite `neuron_parallel_tb.v`. Un bug di cablaggio
|
||||
dell'albero (linea scambiata/duplicata/persa) a un `PARALLEL` diverso da quello usato dai
|
||||
test esistenti sarebbe passato inosservato.
|
||||
|
||||
**Test**: `sim/mac8_tree_tb.v`, verificato a **PARALLEL=2, 8 (il default/omonimo del modulo)
|
||||
e 32** — gli estremi realmente usati nei benchmark del progetto
|
||||
(`docs/FPGA-Neural-Datapatch-Benchmark.md`), non un solo valore a piacere. Tre famiglie per
|
||||
ciascun `PARALLEL` (939 vettori totali):
|
||||
1. **Strutturale/avversariale**: `x=[1..PARALLEL]`, `w=1`, ordine sia ascendente che
|
||||
invertito. La somma attesa (`PARALLEL×(PARALLEL+1)/2`) torna corretta **solo se ogni
|
||||
linea è sommata esattamente una volta** — un modo efficace di scoprire un cablaggio
|
||||
scambiato che un test casuale potrebbe non notare (uno scambio+una linea persa possono
|
||||
annullarsi per caso su input casuali, mai su questo pattern esatto).
|
||||
2. **300 coppie INT8 casuali per `PARALLEL`**, `acc_in` variato su un range realistico —
|
||||
riproduce il collegamento reale (`neuron_parallel.v`: `mac8.acc_in = acc`, l'accumulatore
|
||||
che cresce gruppo dopo gruppo, **non** cablato a 0 come si potrebbe erroneamente
|
||||
assumere).
|
||||
3. **Avversariale**: tutte le linee al prodotto di grandezza massima (`±16384`/`∓16256`)
|
||||
simultaneamente, con `acc_in` ai bordi di `ACC_WIDTH=32` — conferma che l'eventuale
|
||||
wraparound complemento-a-due dell'albero è ben definito (non X/indefinito), pur essendo
|
||||
una magnitudine ben oltre quanto un layer reale (`N_INPUTS≤256`) accumulerebbe mai
|
||||
(dichiarato, non presentato come condizione operativa reale).
|
||||
|
||||
```
|
||||
$ iverilog -g2012 -o /tmp/mac8_tb.out rtl/mac_unit.v rtl/mac8.v sim/mac8_tree_tb.v && vvp /tmp/mac8_tb.out
|
||||
ALL TESTS PASSED (939 vectors across PARALLEL=2/8/32, 0 mismatches against independent Python oracle)
|
||||
```
|
||||
|
||||
**Verdetto: CERTIFICATO** a PARALLEL=2/8/32. **Riserva dichiarata**: non verificato ad ogni
|
||||
altro `PARALLEL` usato nel progetto (es. 4, 16) — il rischio residuo è basso (la costruzione
|
||||
dell'albero è generica via `$clog2(PARALLEL)`, identica per ogni potenza di due, e 3 valori
|
||||
distinti già la esercitano a profondità diverse: 1, 3, 5 livelli), ma non è "esaustivo su
|
||||
tutti i PARALLEL" nello stesso senso in cui §1.1 lo è su `(x,w)`.
|
||||
|
||||
---
|
||||
|
||||
## 1.3 Saturazione INT8 / attivazione — CERTIFICATO CON RISERVA (test pre-esistente, riverificato)
|
||||
|
||||
`sim/neuron_parallel_saturation_bounds_tb.v` (già presente prima di questa campagna) copre
|
||||
gli 8 valori di bordo dichiarati nel task di timing-closure (126, 127, 128, 129, -128, -129,
|
||||
-1, 0) per `ACT_NONE` e `ACT_RELU`, con oracolo **calcolato a mano** (non derivato dal
|
||||
codice) — verificato bit-esatto per confermare che la riscrittura a bit-test delle
|
||||
comparazioni di saturazione (da confronti aritmetici `>127`/`<-128` a riduzioni AND/OR)
|
||||
durante la timing closure non ha alterato il comportamento.
|
||||
|
||||
Ri-eseguito da zero in questa campagna (non solo citato dal WORKLOG): **PASS**, invariato.
|
||||
|
||||
**Riserva**: il test usa `N_INPUTS=PARALLEL=1` (una sola corsia MAC), per raggiungere ogni
|
||||
valore di bordo esattamente con una singola tripla `(x,w,bias)` scelta a mano. Non esercita
|
||||
l'interazione tra l'albero a più corsie (§1.2, ora certificato separatamente) e la
|
||||
saturazione finale nello stesso run — cioè non c'è un test che porti un accumulo
|
||||
multi-gruppo/multi-corsia esattamente a uno di questi bordi. Rischio basso (la saturazione
|
||||
opera sul valore finale di `acc+bias`, indipendentemente da come quel valore è stato
|
||||
costruito), ma non è stato verificato esplicitamente in questa campagna.
|
||||
|
||||
---
|
||||
|
||||
## 1.4 Guard `N_INPUTS % PARALLEL != 0` — CERTIFICATO CON RISERVA GRAVE (BUG-002 confermato)
|
||||
|
||||
### Cosa funziona (già coperto, riverificato)
|
||||
|
||||
`sim/neuron_parallel_guard_negative_{nonmultiple,degenerate}_tb.v`: due test negativi che
|
||||
provano che il guard blocca l'elaborazione per `N_INPUTS % PARALLEL != 0` (incl. il caso
|
||||
degenere "PARALLEL > N_INPUTS" per N_INPUTS≥1, dove il resto della divisione coincide con
|
||||
N_INPUTS stesso, quindi è comunque non-zero). Rieseguiti da zero: **entrambi falliscono a
|
||||
compilare come previsto** — è il PASS.
|
||||
|
||||
### BUG-002 — confermato reale, su ENTRAMBI i piani di verifica (non solo ipotizzato)
|
||||
|
||||
**Il finding aperto in Fase 0 §0.6 era corretto nell'ipotesi ma la mia prima verifica
|
||||
empirica era sbagliata per un bug nella MIA testbench** — narrativa completa perché è
|
||||
rilevante per la fiducia nel resto della campagna:
|
||||
|
||||
1. Primo tentativo: un test con `repeat(50) @(posedge clk); if (done) ... else "HANG"` ha
|
||||
riportato "hang" per `N_INPUTS=0`. **Metodologicamente invalido**: `done` è un impulso di
|
||||
**un solo ciclo** (`rtl/neuron_parallel.v:207/227`, `done <= 0` incondizionato subito dopo
|
||||
averlo asserito), quindi un controllo tardivo e singolo di `done` mostra sempre 0 **anche
|
||||
quando tutto funziona correttamente** — confermato riproducendo lo stesso falso "HANG" su
|
||||
una config nota-buona (`N_INPUTS=2, PARALLEL=2`, valida, mai dovrebbe fallire).
|
||||
2. Corretto il metodo: osservare `done` **ogni ciclo** (non un controllo singolo tardivo).
|
||||
Sulla config nota-buona, ora **PASS correttamente** (`done` pulsa al ciclo giusto, `y=5`
|
||||
coerente col calcolo a mano). Sul caso `N_INPUTS=0`: **confermato, `busy` non si alza mai
|
||||
e `done` non pulsa mai in 200 cicli** — non è più un'ipotesi, è un fatto osservato con un
|
||||
metodo verificato corretto prima su un caso di controllo.
|
||||
3. **Causa architetturale trovata** (non solo il sintomo): `x_bus`/`w_bus` sono dichiarati
|
||||
`[DATA_WIDTH*N_INPUTS-1:0]` — per `N_INPUTS=0` questo è `[-1:0]`, che **non collassa a
|
||||
larghezza zero**: sia Icarus che Yosys lo trattano come un vettore a **2 bit** reali
|
||||
(larghezza = |MSB-LSB|+1 = |-1-0|+1 = 2), lasciato **non pilotato**. Confermato dai
|
||||
warning di Yosys stesso: `Wire ...x_bus[1] is used but has no driver` (×2 per x_bus/w_bus).
|
||||
4. **Confermato anche in sintesi reale**, non solo in simulazione (§A.4): `yosys synth_ecp5`
|
||||
elabora `neuron_parallel #(.N_INPUTS(0), .PARALLEL(2))` con **0 problemi segnalati** dal
|
||||
CHECK pass — lo stesso silenzio che permette al guard di non scattare in simulazione si
|
||||
ripete identico sul secondo piano di verifica indipendente.
|
||||
|
||||
**Test di regressione permanente**: `sim/neuron_parallel_bug002_n_inputs_zero_tb.v` —
|
||||
riproduce il sintomo esatto in modo deterministico, PASSA oggi confermando che il bug è
|
||||
ancora presente (non è un'asserzione che questo comportamento sia desiderabile — il file
|
||||
stesso lo dichiara esplicitamente in testa, con l'istruzione di riscriverlo, non
|
||||
allentarlo, quando il bug verrà corretto).
|
||||
|
||||
**Verdetto: NON CERTIFICATO per `N_INPUTS=0`.** Il guard copre correttamente ogni
|
||||
combinazione `N_INPUTS≥1` non multipla di `PARALLEL` (incl. il caso degenere
|
||||
`PARALLEL>N_INPUTS≥1`), ma **non copre `N_INPUTS=0`**, che produce un `start` silenziosamente
|
||||
inefficace (non l'hang "busy alto per sempre" descritto nel commento originale del guard —
|
||||
un sintomo diverso, osservato per la prima volta in questa campagna) su entrambi i piani di
|
||||
verifica. Vedi `docs/validation/bugs.md` BUG-002 per severità e fix proposto.
|
||||
|
||||
---
|
||||
|
||||
## 1.5 Verdetto complessivo C.1
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| `mac_unit.v` | **CERTIFICATO** (esaustivo, 66022 vettori, 0 riserve) |
|
||||
| `mac8.v` (adder tree) | **CERTIFICATO** (939 vettori, PARALLEL=2/8/32; riserva: non esaustivo su ogni PARALLEL) |
|
||||
| Saturazione/attivazione | **CERTIFICATO CON RISERVA** (test pre-esistente valido; riserva: non testato in combinazione con l'albero multi-corsia) |
|
||||
| Guard `N_INPUTS%PARALLEL` | **NON CERTIFICATO per N_INPUTS=0** — BUG-002 confermato su sim + sintesi reale |
|
||||
|
||||
**Il datapath aritmetico di base (moltiplicazione, estensione di segno, albero di somma) è
|
||||
solido** — certificato esaustivamente dove possibile. **Il guard di protezione attorno ad
|
||||
esso ha un buco reale e confermato**, non un rischio teorico: `N_INPUTS=0` produce hardware
|
||||
sintetizzabile (0 errori Yosys) che silenziosamente non fa nulla quando l'host tenta di
|
||||
avviarlo.
|
||||
@@ -1,163 +0,0 @@
|
||||
# C.2 — Larghezza runtime (`n_inputs_real`, `n_neurons_real`)
|
||||
|
||||
Data: 2026-09-04. Verifica se la terminazione anticipata a runtime è reale (nessuna lettura
|
||||
oltre il limite impostato) e chiude il rischio dichiarato nell'header di `neuron_parallel.v`
|
||||
("`n_inputs_real` che ... è 0 ... riproduce lo stesso hang" di BUG-002).
|
||||
|
||||
Nota di processo: durante questa verifica ho ottenuto un altro falso risultato dalla mia
|
||||
stessa testbench (un secondo, dopo quello di C.1) — riportato per intero in §2.2, non
|
||||
nascosto, perché è rilevante per capire quanto vada verificato con cura ogni singolo
|
||||
risultato anomalo prima di fidarsene.
|
||||
|
||||
---
|
||||
|
||||
## 2.1 Terminazione anticipata reale — `n_inputs_real` (livello `neuron_parallel.v`) — CERTIFICATO
|
||||
|
||||
**Metodo**: `N_INPUTS=32` (build-time, max), regione "reale" (indici 0-15) con `x=w=1`,
|
||||
regione "veleno" (indici 16-31) con `x=w=100` — se l'RTL leggesse anche solo un elemento
|
||||
oltre `n_inputs_real`, il prodotto enorme (100×100=10000) satura immediatamente il risultato
|
||||
a 127, rendendolo distinguibile da un risultato corretto.
|
||||
|
||||
**Oracolo**: somma attesa calcolata a mano; range di cicli atteso calcolato da
|
||||
`GROUPS_real = n_inputs_real/PARALLEL` gruppi + overhead fisso di pipeline.
|
||||
|
||||
```
|
||||
n_inputs_real=32: PASS -- y=127 cycles=5 (legge anche il "veleno": saturazione attesa e corretta)
|
||||
n_inputs_real=16: PASS -- y=16 cycles=3 (NON legge il "veleno": somma esatta, nessun over-read)
|
||||
n_inputs_real=8: PASS -- y=8 cycles=2
|
||||
```
|
||||
|
||||
**Verdetto: CERTIFICATO.** La terminazione anticipata è reale — non legge oltre il limite
|
||||
impostato, e il numero di cicli scala proporzionalmente col numero di gruppi reali.
|
||||
|
||||
## 2.2 `n_inputs_real` non multiplo di `PARALLEL` — CERTIFICATO (comportamento come documentato)
|
||||
|
||||
**Test**: `n_inputs_real=17` (non multiplo di `PARALLEL=8`) con `N_INPUTS=32` build-time
|
||||
valido. Atteso a mano: troncamento intero `17/8=2` gruppi → legge solo i primi 16 elementi,
|
||||
`y=16` (non 17).
|
||||
|
||||
**Falso risultato iniziale, corretto**: un primo tentativo (script bespoke, non lo schema
|
||||
già provato in §2.1) ha mostrato "busy=0, nessun done in 200 cicli" — sembrava un hang.
|
||||
Anziché fidarmi, ho rieseguito lo STESSO caso riusando lo schema di task `run_case` già
|
||||
dimostrato corretto in §2.1 (stessa sequenza di reset/start, tre invocazioni consecutive
|
||||
nella stessa run per controllo di ripetibilità): **`n_inputs_real=17` → `done` al ciclo 3,
|
||||
`y=16` — esattamente il troncamento silenzioso atteso, non un hang.** Il primo risultato era
|
||||
un artefatto della mia testbench (probabile problema di temporizzazione nel setup di quel
|
||||
singolo script), non un comportamento reale dell'RTL — non l'ho riportato come bug senza
|
||||
prima riprodurlo con un metodo già affidabile.
|
||||
|
||||
**Verdetto: CERTIFICATO.** Il rischio dichiarato nell'header ("troncamento silenzioso,
|
||||
risultato sbagliato, nessun errore") è confermato accurato per questo caso: comportamento
|
||||
sbagliato-ma-silenzioso, non un hang.
|
||||
|
||||
## 2.3 `n_inputs_real=0` a runtime — BUG-003, comportamento INCOERENTE tra le mie stesse ripetizioni (non un verdetto singolo affidabile)
|
||||
|
||||
**Test**: `N_INPUTS=32, PARALLEL=8` (validi, compile-time, guard soddisfatto — nessun
|
||||
problema di larghezza `[-1:0]` qui, a differenza di BUG-002; `GROUP_INDEX_WIDTH=2` bit per
|
||||
questa build, non 1 come nel caso di BUG-002). A runtime, `n_inputs_real=0` via la porta (lo
|
||||
stesso percorso che l'host raggiunge via `SET_BASE sel=7`,
|
||||
`docs/FPGA-NeuralNetwork-Engine.md` §8.1).
|
||||
|
||||
**Qui la mia stessa verifica ha prodotto risultati DIVERSI tra run apparentemente
|
||||
equivalenti, e lo riporto per intero invece di scegliere il risultato che sembra più
|
||||
pulito:**
|
||||
|
||||
- Prima verifica (script isolato, dati tutti a `x=w=1`): **hang** — `busy` mai alto, nessun
|
||||
`done` in 200 cicli.
|
||||
- Riprodotto con lo schema `run_case` già affidabile (§2.1), come PRIMA chiamata di una
|
||||
simulazione fresca, dati con regione "veleno": **NESSUN hang** — `done` al ciclo 5,
|
||||
`y=127` (ha letto anche la regione veleno, cioè ha ignorato il limite e processato l'intera
|
||||
larghezza, non si è bloccato).
|
||||
- Stesso schema, PRIMA chiamata di una simulazione fresca ma con OGNI registro
|
||||
esplicitamente inizializzato prima di qualunque reset (per escludere artefatti di
|
||||
propagazione di X in simulazione): **ancora nessun hang** — `y=32` (di nuovo, limite
|
||||
ignorato, non bloccato).
|
||||
- Stesso schema, ma con **una chiamata valida precedente** (`n_inputs_real=32`) prima della
|
||||
chiamata a `n_inputs_real=0`, ripetuta due volte: **nessun hang in nessuna delle due**,
|
||||
`y=32` entrambe le volte.
|
||||
- Uno script con **quattro chiamate consecutive tutte a `n_inputs_real=0`** (variando solo
|
||||
il numero di cicli di reset tra 1 e 5): **la primissima chiamata non si blocca** (`y=32`,
|
||||
limite ignorato), **le tre chiamate successive SI bloccano** (nessun `done` in 200 cicli).
|
||||
|
||||
**Non sono riuscito a isolare la condizione esatta che decide tra i due esiti** entro un
|
||||
tempo ragionevole per questa campagna — non è (solo) l'ordine delle chiamate (una sequenza
|
||||
valida→zero non blocca; una sequenza zero→zero→zero dopo la prima blocca dalla seconda in
|
||||
poi), non è il contenuto dei dati (`x_bus`/`w_bus`) dato che quello non dovrebbe influenzare
|
||||
la logica di controllo `group_index`/`groups_real`, e non è propagazione di X (verificato
|
||||
esplicitamente inizializzando tutto). **Analisi aritmetica**: per questa build
|
||||
`GROUP_INDEX_WIDTH=2` bit, quindi `groups_real[1:0]-1` per `groups_real=0` avvolge a `3` (un
|
||||
valore RAGGIUNGIBILE dal contatore a 2 bit, a differenza del caso a 1 bit di BUG-002) — il
|
||||
che spiegherebbe l'esito "nessun hang, limite ignorato, processa tutta la larghezza" come
|
||||
esito atteso per l'aritmetica di avvolgimento, ma NON spiega perché in alcune ripetizioni
|
||||
compaia invece un hang vero.
|
||||
|
||||
**Verdetto: NON CERTIFICATO, e dichiarato esplicitamente NON PIENAMENTE CARATTERIZZATO** —
|
||||
non fingo un meccanismo che non ho isolato. Quello che è certo, indipendentemente da quale
|
||||
dei due sintomi si manifesti: **nessuno dei due è corretto** (un host che chiede
|
||||
`n_inputs_real=0` non dovrebbe né bloccarsi né ottenere silenziosamente l'intera larghezza
|
||||
di build al posto di zero elementi), e **l'incoerenza stessa tra ripetizioni quasi identiche
|
||||
è di per sé un problema segnalabile**, indipendente dal meccanismo esatto. Vedi
|
||||
`docs/validation/bugs.md` BUG-003 per lo stato aggiornato.
|
||||
|
||||
## 2.4 Terminazione anticipata reale — `n_neurons_real` (livello `neuron_memory.v`) — CERTIFICATO per valori validi
|
||||
|
||||
**Metodo**: `N_NEURONS=3` (build-time), memoria stub minimale sempre-pronta (il contenuto
|
||||
non conta per questo test, solo se il loop termina e in quanti cicli).
|
||||
|
||||
```
|
||||
n_neurons_real=3: done al ciclo 155
|
||||
n_neurons_real=2: done al ciclo 114
|
||||
n_neurons_real=1: done al ciclo 73
|
||||
```
|
||||
|
||||
Scala proporzionalmente (~41 cicli/neurone) — la terminazione anticipata funziona
|
||||
correttamente per valori validi ≥1.
|
||||
|
||||
**Verdetto: CERTIFICATO per `n_neurons_real` ∈ [1, N_NEURONS].**
|
||||
|
||||
## 2.5 `n_neurons_real=0` — BUG-004 CONFERMATO (classe diversa: non hang, limite ignorato silenziosamente)
|
||||
|
||||
**Ipotesi iniziale** (per analogia con BUG-002/003): mi aspettavo lo stesso hang. **Non è
|
||||
quello che succede.**
|
||||
|
||||
**Test 1** (`N_NEURONS=3`, `NEURON_INDEX_WIDTH=2` bit): `n_neurons_real=0` → **`done` al
|
||||
ciclo 196** (non un hang — termina, ma in PIÙ cicli di `n_neurons_real=3` stesso, 155).
|
||||
|
||||
**Test 2** (`N_NEURONS=2`, `NEURON_INDEX_WIDTH=1` bit — la stessa larghezza-1-bit che in
|
||||
`neuron_parallel.v` causa l'hang di BUG-002): `n_neurons_real=0` → **`done` al ciclo 114,
|
||||
identico a `n_neurons_real=2`** (§2.4). Non un hang, ma il conteggio di cicli **coincide
|
||||
esattamente** col caso "processa tutti i neuroni" — il valore richiesto (0) sembra essere
|
||||
stato **ignorato silenziosamente**, con l'hardware che processa l'intero build invece che
|
||||
zero neuroni, terminando in modo perfettamente normale (nessun errore, nessun sintomo
|
||||
visibile all'host).
|
||||
|
||||
**Perché è diverso da BUG-002/003**: l'aritmetica di wraparound qui (`neuron_index ==
|
||||
n_neurons_real[W-1:0]-1`) non blocca il contatore in uno stato irraggiungibile come accade
|
||||
per `group_index` a 1 bit in `neuron_parallel.v` — piuttosto lo fa avvolgere su un valore
|
||||
che, per coincidenza di larghezza, corrisponde al conteggio COMPLETO. Non ho ulteriormente
|
||||
isolato la causa esatta bit-per-bit (a differenza di BUG-002, dove l'ho fatto) — dichiarato
|
||||
come limite di questa verifica, non presentato come pienamente compreso.
|
||||
|
||||
**Verdetto: NON CERTIFICATO per `n_neurons_real=0`.** Vedi `docs/validation/bugs.md`
|
||||
BUG-004. **Più insidioso di un hang**: un host che chiede (per errore) zero neuroni riceve
|
||||
un completamento normale e apparentemente valido, ma calcolato sull'intero conteggio di
|
||||
build — dato silenziosamente sbagliato, non un timeout rilevabile.
|
||||
|
||||
---
|
||||
|
||||
## 2.6 Verdetto complessivo C.2
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| Terminazione anticipata `n_inputs_real` (valori validi) | **CERTIFICATO** |
|
||||
| `n_inputs_real` non multiplo di PARALLEL | **CERTIFICATO** (comportamento = rischio documentato) |
|
||||
| `n_inputs_real=0` | **NON CERTIFICATO, comportamento non pienamente caratterizzato** — BUG-003 (incoerente tra ripetizioni: a volte hang, a volte limite ignorato) |
|
||||
| Terminazione anticipata `n_neurons_real` (valori validi) | **CERTIFICATO** |
|
||||
| `n_neurons_real=0` | **NON CERTIFICATO** — BUG-004 (limite ignorato silenziosamente, non hang) |
|
||||
|
||||
**Il meccanismo di larghezza runtime funziona correttamente per ogni valore valido** —
|
||||
certificato con oracoli indipendenti e verifica del non-over-read. **Il valore limite 0, in
|
||||
entrambi i punti di ingresso (`n_inputs_real` e `n_neurons_real`), produce due classi
|
||||
DIVERSE di comportamento scorretto** — un hang silenzioso in un caso, un risultato
|
||||
silenziosamente sbagliato-ma-dall'aspetto-normale nell'altro — entrambi raggiungibili
|
||||
dall'host via il protocollo SPI documentato, senza bisogno di una nuova sintesi.
|
||||
@@ -1,98 +0,0 @@
|
||||
# C.3 — Sottosistema memoria (`int8_memory_access`, `memory_interface`, `psram_controller`)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## 3.1 `int8_memory_access.v` — conversione byte↔word e selezione byte-lane — CERTIFICATO
|
||||
|
||||
**Metodo**: nuovo test dedicato (`sim/int8_memory_access_bytelane_tb.v`), non esisteva prima
|
||||
una verifica esaustiva della conversione indirizzo. Due batterie:
|
||||
1. **Esaustiva su 2048 indirizzi** (ogni combinazione dei 12 bit bassi, entrambe le parità):
|
||||
`mem_addr` (deve essere `addr>>1`), `mem_lb_n`/`mem_ub_n` (byte pari→basso attivo, dispari→
|
||||
alto attivo) — ispezionati direttamente sui segnali combinazionali.
|
||||
2. **Round-trip scrittura/lettura reale** attraverso l'handshake FSM (non un peek interno) a
|
||||
6 indirizzi (pari/dispari, valori di bordo INT8, e un test esplicito che una scrittura
|
||||
sull'indirizzo dispari di una parola NON corrompa il byte pari già scritto nella stessa
|
||||
parola — verifica che le byte-lane siano davvero indipendenti).
|
||||
|
||||
**Oracolo**: formula dichiarata dal modulo stesso (`addr>>1`, `addr[0]`), applicata
|
||||
indipendentemente, non letta dall'RTL.
|
||||
|
||||
**Due bug nella MIA testbench, trovati e corretti prima di fidarmi del risultato** (stesso
|
||||
schema di trasparenza di C.1/C.2, riportato per intero):
|
||||
1. Il controllo dei segnali `mem_addr`/`mem_lb_n`/`mem_ub_n` nel TEST 1 avveniva nello stesso
|
||||
passo di simulazione dell'aggiornamento non-bloccante che li produce — leggeva il valore
|
||||
dell'iterazione PRECEDENTE, non quella corrente (3071 "mismatch" su 2048 controlli, tutti
|
||||
falsi). Corretto con un `#1` dopo il fronte di clock, per lasciare che l'aggiornamento si
|
||||
assesti prima di leggerlo.
|
||||
2. Il modello di memoria comportamentale della testbench scriveva l'intera parola a 16 bit
|
||||
incondizionatamente, **ignorando `mem_lb_n`/`mem_ub_n`** — una scrittura sul byte dispari
|
||||
di una parola cancellava il byte pari già scritto lì, anche con `mem_lb_n=1` (disabilitato).
|
||||
Questo ha fatto fallire il test di round-trip "scrittura non deve corrompere il byte
|
||||
fratello" — ma il difetto era nello STUB di test, non nell'RTL sotto test (il DUT
|
||||
comunica correttamente `mem_lb_n`/`mem_ub_n`, era il modello di memoria a non rispettarli).
|
||||
Corretto rendendo lo stub sensibile alle byte-lane come una vera memoria mascherabile a
|
||||
byte.
|
||||
|
||||
```
|
||||
$ iverilog -g2012 -o /tmp/bytelane2.out rtl/int8_memory_access.v sim/int8_memory_access_bytelane_tb.v && vvp /tmp/bytelane2.out
|
||||
ALL TESTS PASSED (2048 decode checks + 6 round-trip checks, 0 mismatches)
|
||||
```
|
||||
|
||||
**Verdetto: CERTIFICATO.** 2054/2054 controlli, 0 mismatch, dopo la correzione di due difetti
|
||||
nella testbench stessa (non nell'RTL).
|
||||
|
||||
---
|
||||
|
||||
## 3.2 `memory_interface.v` — CERTIFICATO (via test pre-esistente)
|
||||
|
||||
Modulo semplice: stesso pattern di handshake req/ready di `int8_memory_access.v` ma a
|
||||
granularità 16 bit, senza logica di byte-lane propria (inoltra `lb_n`/`ub_n` così come
|
||||
ricevuti). `sim/memory_interface_tb.v` (pre-esistente, riverificato in Fase 0) copre
|
||||
l'handshake. Non ripetuto da zero in questa fase: la logica è sufficientemente semplice
|
||||
(nessuna aritmetica di indirizzo propria) da non giustificare una nuova campagna esaustiva
|
||||
oltre a quanto già verificato.
|
||||
|
||||
**Verdetto: CERTIFICATO** (copertura pre-esistente, ritenuta adeguata alla semplicità del
|
||||
modulo).
|
||||
|
||||
---
|
||||
|
||||
## 3.3 `psram_controller.v` — CERTIFICATO (lavoro estensivo già svolto in questa sessione, non ri-fatto da zero)
|
||||
|
||||
Questo modulo ha già ricevuto una verifica sostanziale **in questa stessa sessione**, non
|
||||
solo dichiarata nel WORKLOG di sessioni precedenti:
|
||||
- **Un bug reale pre-esistente trovato e corretto**: richieste arrivate durante
|
||||
`STATE_INIT`/`STATE_CR_INIT` (~150µs di poweron) venivano perse silenziosamente; corretto
|
||||
con un latch `req_pending` — trovato durante il lavoro sul sottosistema flash (Fase F2),
|
||||
con una riproduzione minimale isolata prima e dopo il fix.
|
||||
- **Timing di page-mode verificato contro il datasheet ISSI reale**: `ACCESS_CYCLES =
|
||||
ceil(70ns × CLK_FREQ_MHZ/1000)`, `PAGE_CYCLES` per il burst, `tCEM` (idle timeout e budget
|
||||
mid-burst) — `sim/psram_page_mode_tb.v`, con `sim/psram_model.v` che fa **`$fatal` su
|
||||
qualunque violazione di timing reale** (non solo un controllo di valore atteso: un
|
||||
meccanismo di oracolo attivo che blocca la simulazione se l'RTL viola una regola del
|
||||
datasheet, indipendentemente da cosa la testbench stessa controlli esplicitamente).
|
||||
- Rieseguito in Fase 0 di questa campagna (non solo citato): `psram_controller_tb.v` e
|
||||
`psram_page_mode_tb.v` **PASS**, confermato da un harness di regressione indipendente
|
||||
(`tools/run_regression.py`), non dalla parola del WORKLOG.
|
||||
|
||||
**Non ripetuto da zero in C.3**: rifare l'intera campagna di verifica del page-mode/tCEM già
|
||||
completata con rigore comparabile in questa sessione sarebbe una duplicazione di lavoro già
|
||||
tracciabile (WORKLOG, fasi F2/G7), non una nuova scoperta. Citato come evidenza, non dato per
|
||||
buono senza verifica: il PASS è stato riconfermato da zero in Fase 0 di questa campagna.
|
||||
|
||||
**Verdetto: CERTIFICATO**, con la stessa evidenza di prima (riverificata, non solo citata).
|
||||
|
||||
---
|
||||
|
||||
## 3.4 Verdetto complessivo C.3
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| `int8_memory_access.v` (byte↔word, byte-lane) | **CERTIFICATO** (nuovo, esaustivo su 2048 indirizzi) |
|
||||
| `memory_interface.v` | **CERTIFICATO** (copertura pre-esistente adeguata) |
|
||||
| `psram_controller.v` (incl. page-mode, tCEM) | **CERTIFICATO** (lavoro esteso di sessione, riverificato) |
|
||||
|
||||
Nessun bug nuovo trovato in questo aspetto — due difetti trovati erano nella mia stessa
|
||||
testbench di verifica, corretti prima di trarre conclusioni sull'RTL.
|
||||
@@ -1,75 +0,0 @@
|
||||
# C.4 — Arbitro (`mem_arbiter.v`)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## 4.1 Priorità B>C>A>D — CERTIFICATO
|
||||
|
||||
**Metodo**: nuovo test dedicato (`sim/mem_arbiter_priority_tb.v`). Quattro scenari,
|
||||
combinazioni decrescenti di richiedenti simultanei, ciascuno con dati distinguibili
|
||||
(`m_rdata` eco dell'indirizzo) per confermare che la risposta torni al **richiedente
|
||||
corretto**, non solo che "qualcuno" venga servito:
|
||||
1. A+B+C+D simultanei → B vince.
|
||||
2. A+C+D (B assente) → C vince.
|
||||
3. A+D (B,C assenti) → A vince.
|
||||
4. D da solo → viene comunque servito (bassa priorità ≠ mai servito).
|
||||
|
||||
```
|
||||
$ iverilog -g2012 -o /tmp/arb6.out rtl/mem_arbiter.v sim/mem_arbiter_priority_tb.v && vvp /tmp/arb6.out
|
||||
ALL TESTS PASSED (priority order B>C>A>D confirmed; ...)
|
||||
```
|
||||
|
||||
**Nota di processo — race trovata nella mia stessa testbench**: la prima versione usava
|
||||
assegnazioni bloccanti per ritirare le richieste dei "perdenti" nello stesso
|
||||
`@(posedge clk)` che doveva concedere la richiesta — una race reale con il blocco
|
||||
sincrono del DUT sullo stesso fronte (l'ordine di esecuzione tra processi diversi
|
||||
sensibili allo stesso evento non è garantito da Verilog). Diagnosticato con un
|
||||
riferimento gerarchico a `dut.owner`, mai uscito da `SEL_NONE` nonostante le richieste
|
||||
fossero pilotate — non un difetto dell'RTL. Corretto passando ad assegnazioni non
|
||||
bloccanti per i segnali di richiesta in tutta la testbench, come farebbe un master reale
|
||||
sincrono allo stesso clock.
|
||||
|
||||
**Verdetto: CERTIFICATO.** L'ordine di priorità dichiarato nell'header è implementato
|
||||
esattamente come descritto, dati instradati al richiedente corretto in ogni caso.
|
||||
|
||||
---
|
||||
|
||||
## 4.2 Starvation di D sotto contesa continua — comportamento reale, ambiguità nella documentazione
|
||||
|
||||
**Test**: `b_req` e `d_req` mantenuti entrambi asserti continuamente per 500 cicli
|
||||
(B "ha sempre altro lavoro" nell'istante in cui si libera).
|
||||
|
||||
**Risultato**: **D non viene MAI concesso in 500 cicli** di contesa continua da B.
|
||||
|
||||
**Perché non lo classifico come bug**: l'header del modulo dichiara "flash operations
|
||||
are ms-scale and never meant to compete with inference for memory bandwidth" e "In
|
||||
normal operation B and C are temporally disjoint anyway" — la contesa continua e
|
||||
sostenuta testata qui è esplicitamente fuori dallo scenario operativo previsto (un
|
||||
`layer_sequencer`/`neuron_memory` che non lascia MAI un buco libero per centinaia di
|
||||
cicli di fila non corrisponde a un'inferenza reale). Un arbitro a priorità fissa senza
|
||||
invecchiamento (aging) che fa morire di fame il richiedente più basso sotto carico
|
||||
sostenuto è un design standard e spesso intenzionale, non un difetto di per sé.
|
||||
|
||||
**Cosa segnalo**: la frase dell'header "gets stretched out, never starves or corrupts
|
||||
A/B/C" è **ambigua** — può essere letta sia come "[D] non affama mai [se stesso]" sia
|
||||
come "[la contesa] non fa mai affamare o corrompere A/B/C" (una garanzia solo su A/B/C,
|
||||
non su D). Il comportamento osservato è coerente con la SECONDA lettura, non con la
|
||||
prima. Non è un bug funzionale, ma la frase andrebbe disambiguata nel commento sorgente
|
||||
per evitare che un futuro lettore assuma erroneamente che D abbia una garanzia di
|
||||
progresso che il codice non implementa.
|
||||
|
||||
**Verdetto: CERTIFICATO come comportamento** (nessuna sorpresa rispetto a un arbitro a
|
||||
priorità fissa senza aging), **riserva documentale** sulla frase ambigua dell'header.
|
||||
|
||||
---
|
||||
|
||||
## 4.3 Verdetto complessivo C.4
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| Priorità B>C>A>D, instradamento dati corretto | **CERTIFICATO** |
|
||||
| Starvation di D sotto contesa sostenuta | **CERTIFICATO come comportamento**, riserva sulla chiarezza della documentazione (non un bug) |
|
||||
|
||||
Nessun bug RTL trovato in questo aspetto. Un difetto di race trovato e corretto nella
|
||||
testbench di verifica stessa (stesso schema del resto della campagna).
|
||||
@@ -1,75 +0,0 @@
|
||||
# C.5 — Sequencer dense (`layer_sequencer.v`)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## 5.1 Catena layer, ping-pong, busy/done — CERTIFICATO (test pre-esistente, valido)
|
||||
|
||||
`sim/layer_sequencer_tb.v` (pre-esistente, riverificato in Fase 0) copre un run a 2 layer
|
||||
con verifica campo-per-campo del descrittore decodificato (`nm_w_base`, `nm_bias_addr`,
|
||||
`nm_x_base`, `nm_activation`, `nm_n_inputs`, `nm_n_neurons`), e in modo particolarmente
|
||||
solido: **verifica l'indirizzo del buffer ping-pong usato per layer 1, non solo il valore**
|
||||
(conferma che layer 1 legge dal buffer che layer 0 ha effettivamente scritto — il punto
|
||||
reale dello schema ping-pong, non solo che "un" buffer sia stato usato). Verifica anche che
|
||||
`seq_busy` resti asserto per l'intero run a 2 layer (non cada tra un layer e l'altro) e che
|
||||
`seq_done` pulsi esattamente una volta, dopo l'ULTIMO layer (un `nm_done` intermedio non deve
|
||||
attivarlo).
|
||||
|
||||
**Verdetto: CERTIFICATO** per la catena a `run_num_layers` valido (test singolo ma
|
||||
sufficientemente rigoroso nel verificare indirizzi, non solo valori).
|
||||
|
||||
---
|
||||
|
||||
## 5.2 `run_num_layers=0` — BUG-005 CONFERMATO, CRITICO
|
||||
|
||||
**Ipotesi**, per analogia col guard mancante già visto in BUG-002/003/004: `run_num_layers`
|
||||
è documentato "1..N_LAYERS" ma **non ha alcun guard**, né a compile-time né a runtime.
|
||||
`layer_idx` (`rtl/layer_sequencer.v:121`) è però un registro a **8 bit pieni** (non ristretto
|
||||
a 1 bit come il `group_index` di BUG-002) — la condizione di terminazione
|
||||
`layer_idx==num_layers_reg-1` per `num_layers_reg=0` avvolge a `layer_idx==255`, un valore
|
||||
che il contatore RAGGIUNGE naturalmente contando da 0. Ipotesi: non un hang, ma
|
||||
un'esecuzione di 256 layer fasulli.
|
||||
|
||||
**Verificato empiricamente** (`sim/layer_sequencer_bug005_zero_layers_tb.v`, `neuron_memory`
|
||||
sostituito da uno stub minimale che completa istantaneamente, per isolare il solo
|
||||
comportamento di sequenziamento):
|
||||
|
||||
```
|
||||
RESULT: run_num_layers=0 completed after 21761 cycles -- dut.layer_idx ended at 255
|
||||
```
|
||||
|
||||
**Confermata l'ipotesi**: non un hang. Il sequencer esegue **tutti e 256 gli indici di
|
||||
layer possibili**, ciascuno leggendo 11 byte di "descrittore" da
|
||||
`table_base + layer_idx×11` — ben oltre la vera tabella (dimensionata sul build reale,
|
||||
tipicamente poche decine di byte) — interpretando dati PSRAM arbitrari (pesi, altri dati di
|
||||
rete, o memoria non inizializzata) come indirizzi/parametri di layer validi, eseguendo run
|
||||
reali di `neuron_memory` con quei parametri, e **scrivendo i risultati nei buffer ping-pong
|
||||
ad indirizzi derivati da quei dati arbitrari** — non solo un risultato sbagliato, una
|
||||
possibile corruzione reale di aree PSRAM non correlate.
|
||||
|
||||
**Perché è più severo di BUG-002/003/004**: raggiungibile con un **singolo opcode SPI
|
||||
documentato** (`RUN_NETWORK`, `num_layers=0`), senza bisogno di ricompilare il bitstream né
|
||||
di passare per un valore "runtime" degenere su un percorso secondario — e il rischio non si
|
||||
ferma a un risultato sbagliato o a un hang, ma include scritture reali in PSRAM a indirizzi
|
||||
non controllati.
|
||||
|
||||
**Nota correlata (non testata separatamente, stesso meccanismo)**: `run_num_layers` >
|
||||
`N_LAYERS` (il massimo di build) presumibilmente ha lo stesso problema in forma più
|
||||
limitata — nessun guard impedisce di leggere oltre la tabella reale anche per valori
|
||||
"quasi validi" ma superiori al massimo di build. Non verificato con un test dedicato in
|
||||
questa fase (stessa causa radice di §5.2, non una scoperta separata).
|
||||
|
||||
**Verdetto: NON CERTIFICATO per `run_num_layers=0` (e probabilmente per valori
|
||||
`>N_LAYERS`).** Vedi `docs/validation/bugs.md` BUG-005 (severità CRITICA — unico bug di
|
||||
questa campagna finora classificato come tale, per raggiungibilità diretta via protocollo
|
||||
host documentato e rischio di corruzione dati reale, non solo hang o risultato sbagliato).
|
||||
|
||||
---
|
||||
|
||||
## 5.3 Verdetto complessivo C.5
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| Catena layer, ping-pong, busy/done (valori validi) | **CERTIFICATO** |
|
||||
| `run_num_layers=0` | **NON CERTIFICATO** — BUG-005 (CRITICO, causa isolata con certezza) |
|
||||
@@ -1,70 +0,0 @@
|
||||
# C.6 — Motore grafo (`graph_engine.v`, `act_buffer.v`)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## 6.1 Gather, padding, guard `src_id<out_id` — CERTIFICATO (test pre-esistenti, riverificati)
|
||||
|
||||
`sim/graph_engine_tb.v` (grafo calcolato a mano, §3 dell'esempio del manuale, con verifica
|
||||
diretta del contenuto di `act_buffer` via riferimento gerarchico, non solo dell'output
|
||||
finale) e `sim/graph_engine_guard_tb.v` (4 test: `src_id>=out_id` auto-riferimento,
|
||||
`out_id>=N_TOTAL`, `n_conn_padded==0`, percorso di recovery dopo un `err`) — entrambi
|
||||
pre-esistenti, riverificati PASS in Fase 0. Copertura solida su happy-path e sui casi
|
||||
avversari già identificati dal progetto.
|
||||
|
||||
**Verdetto: CERTIFICATO** per questi aspetti (copertura pre-esistente adeguata).
|
||||
|
||||
---
|
||||
|
||||
## 6.2 `num_neurons_graph=0` — stessa causa radice di BUG-005, ma protezione incidentale diversa
|
||||
|
||||
**Analisi strutturale**: `neuron_idx` (`rtl/graph_engine.v:159`) è un registro a 16 bit
|
||||
PIENI, e la condizione di terminazione (righe 527/561)
|
||||
`neuron_idx==num_neurons_graph-16'd1` per `num_neurons_graph=0` avvolge a `65535` — un
|
||||
valore che il contatore RAGGIUNGE naturalmente, stessa struttura esatta di BUG-005
|
||||
(`layer_idx`). Stessa causa radice: nessun guard su `num_neurons_graph`, né a compile-time
|
||||
né a runtime.
|
||||
|
||||
**Verificato empiricamente, con una riserva esplicita**: `sim/graph_engine_bug006_zero_neurons_probe_tb.v`,
|
||||
finestra di osservazione limitata a 5000 cicli (**non fatto girare fino a completamento
|
||||
reale** — fino a 65536 iterazioni con la logica di gather di questo modulo, più costosa per
|
||||
iterazione del semplice dispatch di `layer_sequencer`, sarebbe stato impraticabile per il
|
||||
budget di tempo di questa campagna; dichiarato come limite esplicito, non nascosto).
|
||||
|
||||
```
|
||||
RESULT: err fired at cycle 58 (neuron_idx=0) -- the src_id<out_id/N_TOTAL guard caught
|
||||
the garbage descriptor data before completion.
|
||||
```
|
||||
|
||||
**Differenza da BUG-005**: `graph_engine` possiede già un guard **a runtime, per-edge**
|
||||
(`src_id>=out_id` o `out_id>=N_TOTAL` → `err`, §6.1) che **non è stato progettato per
|
||||
proteggere da `num_neurons_graph=0`** ma **lo cattura come effetto collaterale**: con un
|
||||
pattern di dati "spazzatura" non banale (non tutto a zero, un pattern a rampa), il guard
|
||||
esistente ha fermato l'esecuzione dopo sole 58 cicli, al primissimo neurone fasullo letto,
|
||||
molto prima di avvicinarsi alle 65536 iterazioni possibili. `layer_sequencer.v` **non ha
|
||||
alcun guard equivalente** — da qui la severità molto più alta di BUG-005.
|
||||
|
||||
**Non è una garanzia**: questo test usa UN pattern di dati specifico. Non è stato
|
||||
dimostrato che OGNI possibile contenuto PSRAM causi un arresto altrettanto rapido — esiste
|
||||
in linea di principio un pattern di dati "sfortunato" che rispetti `src_id<out_id` e
|
||||
`out_id<N_TOTAL` per molte iterazioni consecutive prima di violarli (o non violarli mai, se
|
||||
i byte casuali formano per caso una sequenza monotona valida) facendo procedere
|
||||
l'esecuzione molto più a lungo. Il buco strutturale (nessun guard esplicito su
|
||||
`num_neurons_graph`) resta reale.
|
||||
|
||||
**Verdetto: NON CERTIFICATO per `num_neurons_graph=0` in senso assoluto** (stesso buco
|
||||
strutturale di BUG-005), **ma il rischio pratico osservato è marcatamente più basso**
|
||||
grazie al guard esistente per altri scopi. Non registrato come nuovo bug allo stesso
|
||||
livello di severità di BUG-005 — vedi `docs/validation/bugs.md` per la voce dedicata a
|
||||
severità ridotta (INFO/BASSA, non CRITICA), con la riserva sulla mancata verifica
|
||||
esaustiva su ogni pattern di dati.
|
||||
|
||||
---
|
||||
|
||||
## 6.3 Verdetto complessivo C.6
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| Gather, padding, guard `src_id<out_id`/`out_id<N_TOTAL`/`n_conn_padded==0` | **CERTIFICATO** |
|
||||
| `num_neurons_graph=0` | **NON CERTIFICATO in senso assoluto**, rischio pratico basso osservato (guard esistente incidentale), non equiparato a BUG-005 |
|
||||
@@ -1,62 +0,0 @@
|
||||
# C.7 — SPI slave + engine (`spi_slave.v`, `spi_engine.v`)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## 7.1 CDC, framing, opcode dispatch — CERTIFICATO (copertura pre-esistente estesa, riverificata)
|
||||
|
||||
Questo modulo ha già ricevuto lavoro di verifica sostanziale **in questa stessa sessione**
|
||||
(non solo dichiarato in sessioni precedenti):
|
||||
- **Una race reale trovata e corretta** nel meccanismo sticky di `STATUS` (Fase 4/9 di
|
||||
sessioni precedenti, ma il fix e la sua verifica sono tracciabili e riverificati).
|
||||
- **CDC a 2/3 stadi per `sclk`/`mosi`/`cs_n`**: `sim/spi_slave_tb.v` include un test
|
||||
esplicito con rapporto SCLK/clk diverso (TEST 4, "slower SPI clock... confirms no hidden
|
||||
dependency on a specific SCLK/clk ratio") — non solo un singolo rapporto a piacere.
|
||||
- **`sim/spi_engine_tb.v`**: 10 test (A-J) coprono WRITE_RAM/READ_RAM round-trip, SET_BASE,
|
||||
START idle/busy, STATUS live/sticky/clear-on-read, RESET, READ_OUTPUT (neuron-major),
|
||||
READ_CONFIG, NOP (nessun side-effect), byte MOSI in eccesso ignorati, transazioni
|
||||
back-to-back.
|
||||
- **Opcode sconosciuti**: `default: begin // OP_NOP and unknown opcodes` (riga 724) —
|
||||
trattati esplicitamente come NOP, nessun rischio di hang per costruzione, coerente col
|
||||
pattern già verificato per `spi_flash_master.v` (opcode illegale, Fase F1).
|
||||
- Rieseguito in Fase 0 di questa campagna (non solo citato): `spi_slave_tb.v` e
|
||||
`spi_engine_tb.v` **PASS**, confermato dall'harness di regressione indipendente.
|
||||
|
||||
**Verdetto: CERTIFICATO**, con la stessa evidenza di prima (riverificata, non solo citata).
|
||||
|
||||
---
|
||||
|
||||
## 7.2 `len=0` per WRITE_RAM/READ_RAM — CERTIFICATO (guard esplicito trovato per ispezione)
|
||||
|
||||
Dato il pattern ricorrente in questa campagna (guard mancante su valori "reali=0" in più
|
||||
moduli, BUG-002/003/004/005/006), ho controllato se lo stesso buco esistesse anche qui.
|
||||
**Non esiste**: `rtl/spi_engine.v:817` ha un guard esplicito —
|
||||
|
||||
```verilog
|
||||
if ({len_acc[7:0], rx_byte} == 16'h0) begin
|
||||
state <= ST_IGNORE;
|
||||
end else if (opcode == OP_WRITE_RAM) begin
|
||||
state <= ST_WRITE_DATA;
|
||||
...
|
||||
```
|
||||
|
||||
`len=0` transita correttamente a `ST_IGNORE` (no-op sicuro) invece di entrare nel loop di
|
||||
trasferimento — a differenza di `layer_sequencer.v`/`graph_engine.v`, qui il caso limite è
|
||||
gestito esplicitamente. Non serviva un nuovo test dedicato: il guard è verificabile per
|
||||
ispezione diretta, inequivocabile.
|
||||
|
||||
**Verdetto: CERTIFICATO.** Nota positiva per il registro: questo modulo dimostra che il
|
||||
progetto **non manca sistematicamente** di guardie sui valori limite — il buco è
|
||||
specifico ai moduli già segnalati (BUG-002 - BUG-006), non universale.
|
||||
|
||||
---
|
||||
|
||||
## 7.3 Verdetto complessivo C.7
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| CDC, framing, dispatch opcode, opcode sconosciuti | **CERTIFICATO** (copertura estesa pre-esistente, riverificata) |
|
||||
| `len=0` WRITE_RAM/READ_RAM | **CERTIFICATO** (guard esplicito confermato per ispezione) |
|
||||
|
||||
Nessun nuovo bug trovato in questo aspetto.
|
||||
@@ -1,67 +0,0 @@
|
||||
# C.8 — Top-level (`spi_neuron_top.v`)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## 8.1 Mux `seq_busy`/dispatch legittimo, pin `data_ready_n`/`irq_n` — CERTIFICATO
|
||||
|
||||
Il meccanismo di mux `mux_nm_*` (che decide se `neuron_memory` è pilotato da
|
||||
`layer_sequencer` durante un `RUN_NETWORK` o direttamente da `spi_engine` per un `START`
|
||||
manuale) è già coperto da `sim/spi_neuron_top_runnetwork_tb.v` (un `START` a singolo layer
|
||||
funziona ancora correttamente dopo un `RUN_NETWORK` precedente — "mux sanity"). I pin
|
||||
`data_ready_n`/`irq_n` sono coperti da 4 test dedicati in `sim/spi_neuron_top_irq_tb.v`
|
||||
(idle, run valido, run non valido, `RESET` pulisce `irq_n`). Entrambi pre-esistenti,
|
||||
riverificati PASS in Fase 0.
|
||||
|
||||
**Verdetto: CERTIFICATO** per questi aspetti.
|
||||
|
||||
---
|
||||
|
||||
## 8.2 `SET_NET_TYPE` durante un run in corso — BUG-007 CONFERMATO END-TO-END, CRITICO
|
||||
|
||||
**Analisi strutturale**: il mux della Porta C dell'arbitro (righe 394-397) sceglie tra
|
||||
`graph_engine` e `layer_sequencer` in modo **puramente combinazionale** sul valore corrente
|
||||
di `net_type`. `rtl/spi_engine.v` accetta `SET_NET_TYPE` **incondizionatamente**, senza
|
||||
alcun controllo su `graph_busy`/`seq_busy`. Il commento "mutually exclusive by
|
||||
construction" (riga 390) copre solo l'AVVIO simultaneo dei due motori, non una scrittura
|
||||
di `net_type` che arriva a metà di un run già avviato.
|
||||
|
||||
**Verificato end-to-end su SPI reale** (`sim/spi_neuron_top_bug007_mid_run_net_type_tb.v`,
|
||||
stesso grafo valido già certificato in `spi_neuron_top_graph_tb.v`, stesse routine SPI
|
||||
provate):
|
||||
|
||||
```
|
||||
--- starting graph RUN_NETWORK, then immediately SET_NET_TYPE(dense) before it completes ---
|
||||
after 30 polls: last_status=0x01 (bit0=busy) -- expected 0x01 stuck if the hang reproduces
|
||||
RESULT: HANG CONFIRMED -- STATUS.busy stuck, no done/err after 30 polls (vs. ~12-25us normal completion time for this graph)
|
||||
--- recovery check: RESET, then a legitimate legacy dense START ---
|
||||
RECOVERY RESULT: RESET DOES recover the system -- a subsequent legitimate dense op completed normally (status=0x02 after 2 polls)
|
||||
```
|
||||
|
||||
`STATUS.busy` resta bloccato dopo un `SET_NET_TYPE` inviato subito dopo un `RUN_NETWORK` in
|
||||
modalità grafo, per un tempo enormemente superiore al normale completamento di quel grafo
|
||||
(~2.35ms osservati in un run più lungo, vs ~12-25µs normali) — un hang reale, non un
|
||||
rallentamento. Le transazioni SPI stesse continuano a funzionare (il `SET_NET_TYPE`
|
||||
avversariale e i successivi poll di `STATUS` completano regolarmente); è specificamente il
|
||||
motore grafo a restare bloccato, in attesa di un `ram_ready` che non arriva più tramite il
|
||||
percorso del mux ormai scollegato.
|
||||
|
||||
**Recupero verificato**: un `RESET` durante l'hang riporta il sistema a uno stato
|
||||
pienamente funzionante (una successiva operazione dense legittima completa normalmente).
|
||||
Non è un blocco permanente — ma senza un `RESET` di ripiego lato host, il polling da solo
|
||||
non si sbloccherebbe mai.
|
||||
|
||||
**Verdetto: NON CERTIFICATO.** Vedi `docs/validation/bugs.md` BUG-007 — severità CRITICA
|
||||
insieme a BUG-005, per raggiungibilità diretta con due soli opcode SPI documentati in
|
||||
sequenza ravvicinata, uno scenario host plausibile.
|
||||
|
||||
---
|
||||
|
||||
## 8.3 Verdetto complessivo C.8
|
||||
|
||||
| Sotto-aspetto | Verdetto |
|
||||
|---|---|
|
||||
| Mux `seq_busy` per dispatch legittimo | **CERTIFICATO** |
|
||||
| Pin `data_ready_n`/`irq_n` | **CERTIFICATO** |
|
||||
| `SET_NET_TYPE` durante un run in corso | **NON CERTIFICATO** — BUG-007 (CRITICO, confermato end-to-end, recupero via RESET verificato) |
|
||||
@@ -1,34 +0,0 @@
|
||||
# C.9 — Pinout / `.lpf`
|
||||
|
||||
Data: 2026-09-04. Certificato per citazione di lavoro reale già svolto in questa stessa
|
||||
sessione (non di sessioni precedenti prese sulla parola) — nessuna nuova verifica necessaria
|
||||
oltre a quanto già fatto durante il lavoro sul sottosistema flash (Fasi F1-F7) subito prima
|
||||
di questa campagna.
|
||||
|
||||
## Evidenza
|
||||
|
||||
- **`.lpf` reale, non pianificato**: `synth/ecp5/spi_neuron_top.lpf`, generato da
|
||||
`tools/pinout/gen_lpf.py` contro `iodb.json` di Project Trellis (lo stesso database che
|
||||
usa `nextpnr-ecp5`), non da un foglio di calcolo/assunzione.
|
||||
- **Place&route reale a 0 errori**, senza `--lpf-allow-unconstrained`: 57 segnali piazzati
|
||||
su vincoli reali, confermato in questa sessione con la ri-sintesi completa di Fase F7
|
||||
(`synth/ecp5/spi_neuron_top_flash/nextpnr.log`).
|
||||
- **Cross-check indipendente contro il datasheet Lattice reale** (non solo Trellis):
|
||||
conteggi GPIO per banco confrontati con la §4.3.2 del datasheet ufficiale
|
||||
`FPGA-DS-02012-3-4-ECP5-ECP5G-Family-Data-Sheet.pdf` fornito dall'utente — coincidenza
|
||||
esatta su 6 banchi su 7.
|
||||
- **`USRMCLK` verificato contro il blackbox reale di yosys** (`cells_bb.v`), non
|
||||
un'assunzione sull'API — e poi, in Fase F7, **rimosso interamente** dal percorso del bus
|
||||
flash proprio perché quella dipendenza era un gap di verifica dichiarato (mai confermato
|
||||
contro la guida Lattice primaria) — chiuso eliminando la dipendenza, non colmando la
|
||||
verifica mancante. Confermato dalla stessa sintesi: `USRMCLK` 0/1 (0%) nel build corrente.
|
||||
- **Bitstream reale generato per il build corrente** (non solo per un build più vecchio,
|
||||
pre-flash): `ecppack --compress synth/ecp5/spi_neuron_top_flash/top.config
|
||||
/tmp/current_full_system.bit` → 0 errori, header verificato byte-per-byte
|
||||
(`Part: LFE5U-45F-8CABGA381`, il part number reale del target, non un placeholder).
|
||||
|
||||
## Verdetto
|
||||
|
||||
**CERTIFICATO.** Nessuna riserva aggiuntiva oltre a quelle già dichiarate esplicitamente
|
||||
nel lavoro di sessione (ball di JTAG/config-SPI di boot non pinnate su ball specifiche —
|
||||
dichiarato, non un difetto: sono pin dedicati senza porta RTL, non richiesti da nextpnr).
|
||||
@@ -1,30 +0,0 @@
|
||||
# C.10 — Timing (Fmax reale, percorso critico, sweep di seed)
|
||||
|
||||
Data: 2026-09-04. Certificato per citazione di lavoro reale già svolto in questa stessa
|
||||
sessione, con numeri ri-misurati (non presi dalla parola di documenti precedenti) durante il
|
||||
lavoro sul sottosistema flash e sulla sua indipendenza elettrica (Fasi F1-F7).
|
||||
|
||||
## Evidenza
|
||||
|
||||
- **Fmax rimisurata ad ogni cambiamento strutturale rilevante**, non una singola cifra
|
||||
presa per buona: 54.58 → 75.30 (timing closure) → 73.88 (pin attenzione host) → 66.68
|
||||
(sottosistema flash) → **67.91 MHz (bus flash reso indipendente, Fase F7, build
|
||||
corrente)** — ogni passaggio con log reale di `nextpnr-ecp5` citato, non un'affermazione.
|
||||
- **Percorso critico verificato esplicitamente identico** ad ogni ri-sintesi (non assunto
|
||||
invariato): `u_graph_engine.u_neuron.group_index → u_mac8 → catena di riporto
|
||||
dell'accumulatore in neuron_parallel.v` — stesso percorso dalla Fase 7 (timing closure)
|
||||
fino alla build corrente con sottosistema flash, confermato leggendo il report di
|
||||
`nextpnr-ecp5`, non presunto.
|
||||
- **Sweep di seed** (5 seed, P2 e P8) già eseguito e documentato in `WORKLOG.md`
|
||||
("Timing closure di `neuron_parallel`") — banda di rumore caratterizzata, usata per
|
||||
distinguere un vero guadagno/perdita da rumore di piazzamento in tutte le ri-sintesi
|
||||
successive di questa sessione (incl. la spiegazione del calo 73.88→66.68→67.91 MHz come
|
||||
rumore, non regressione, verificata contro quella banda).
|
||||
- **Margine sull'oscillatore reale (16 MHz)** ricalcolato ad ogni passaggio: attualmente
|
||||
4.24× con Fmax 67.91 MHz.
|
||||
|
||||
## Verdetto
|
||||
|
||||
**CERTIFICATO.** Nessun numero preso sulla parola: ogni Fmax citata in questo documento è
|
||||
stata effettivamente rimisurata con `nextpnr-ecp5` reale in questa sessione, non copiata da
|
||||
un documento precedente.
|
||||
@@ -1,27 +0,0 @@
|
||||
# C.11 — Toolchain / build (Yosys → nextpnr-ecp5 → `ecppack` → `.bit` reale)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
## Evidenza
|
||||
|
||||
- **Flusso completo eseguito end-to-end sul build corrente**, non solo in passato su build
|
||||
più vecchi:
|
||||
```
|
||||
yosys -p "synth_ecp5 -json top.json -top spi_neuron_top" <20 file RTL>
|
||||
→ 0 problemi CHECK
|
||||
nextpnr-ecp5 --45k --package CABGA381 --speed 8 --json top.json --lpf spi_neuron_top.lpf
|
||||
→ 0 errori di vincolo, "Program finished normally", Fmax 67.91 MHz
|
||||
ecppack --compress top.config /tmp/current_full_system.bit
|
||||
→ 0 errori, 319747 byte, header "Part: LFE5U-45F-8CABGA381" verificato
|
||||
```
|
||||
Rieseguito in questa fase (non solo citato da build precedenti in `WORKLOG.md`).
|
||||
- **Non testato**: programmazione su hardware fisico reale (nessuna scheda disponibile in
|
||||
questo ambiente) — dichiarato esplicitamente come limite fin dalle prime fasi del
|
||||
progetto, non nascosto.
|
||||
|
||||
## Verdetto
|
||||
|
||||
**CERTIFICATO** per la parte verificabile in questo ambiente (RTL→bitstream, 0 errori ad
|
||||
ogni stadio, sul build corrente). **NON CERTIFICABILE in questa campagna**: comportamento
|
||||
su silicio reale (nessun hardware fisico disponibile) — limite dichiarato esplicitamente
|
||||
per §A.5, non una lacuna nascosta.
|
||||
@@ -1,28 +0,0 @@
|
||||
# C.12 — `netasm` (host, assemblatore pseudo-assembly → byte)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
## Evidenza
|
||||
|
||||
`tools/netasm/tests/test_netasm.py` — rieseguito da zero in Fase 0 di questa campagna
|
||||
(non preso dalla parola): **20/20 PASS**. Copertura, verificata leggendo i nomi dei test
|
||||
(`tests/test_netasm.py`), non solo il conteggio:
|
||||
- Parsing (denso/grafo, commenti, righe vuote, errori di sintassi).
|
||||
- Assemblaggio grafo byte-esatto **senza** padding, confrontato byte-per-byte contro
|
||||
l'esempio del manuale (§3), riferimento indipendente dall'implementazione.
|
||||
- Assemblaggio grafo **con** padding (PARALLEL=4).
|
||||
- Neurone a zero connessioni (`n_conn=0` → pad a un gruppo intero).
|
||||
- Guardie a tempo di compilazione: auto-riferimento, riferimento in avanti, output usato
|
||||
come sorgente, overflow `MAX_CONN`, overflow `N_TOTAL`, `OUTPUT` non dichiarato —
|
||||
ciascuna verificata come test **negativo** (deve rifiutare, non solo "non crashare").
|
||||
- Round-trip con l'RTL: gli stessi byte prodotti da `netasm` sono quelli effettivamente
|
||||
usati nel test end-to-end mandatorio del sottosistema flash
|
||||
(`sim/spi_neuron_top_flash_tb.v` TEST4, `netasm→SAVE_SLOT→LOAD_SLOT→RUN_NETWORK`,
|
||||
output=126 confermato) — non solo testato in isolamento, verificato anche contro
|
||||
l'hardware reale a valle.
|
||||
|
||||
## Verdetto
|
||||
|
||||
**CERTIFICATO.** Nessuna riserva — copertura sia positiva sia negativa, oracolo
|
||||
indipendente (esempio del manuale, non l'implementazione stessa), e un round-trip reale
|
||||
con l'hardware già dimostrato in una fase precedente di questa stessa sessione.
|
||||
@@ -1,40 +0,0 @@
|
||||
# C.13 — Coerenza datasheet↔RTL
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
## Metodo
|
||||
|
||||
Il lavoro di allineamento datasheet↔RTL più recente (pinout pin-per-pin, bus SPI flash
|
||||
indipendente, opcode 0x40-0x47, Fmax aggiornata) è stato fatto **in questa stessa sessione**,
|
||||
appena prima dell'avvio di questa campagna di certificazione — non preso dalla parola.
|
||||
`grep` mirato sui documenti per confermare che nessuna cifra ovviamente stantia sia rimasta
|
||||
(56 vs 57 segnali, `USRMCLK`, Fmax vecchie) non ha trovato residui.
|
||||
|
||||
## Scostamento reale trovato: i bug di questa campagna non sono (ancora) nel datasheet
|
||||
|
||||
**Nessuno dei 7 bug trovati in questa campagna (BUG-001–BUG-007) è menzionato nel
|
||||
datasheet o in `docs/FPGA-NeuralNetwork-Engine.md`** — verificato con una ricerca mirata,
|
||||
non assunto. Questo è **corretto e atteso**, non un errore: questi bug sono stati scoperti
|
||||
**dopo** che quei documenti erano stati aggiornati, come parte di questa stessa campagna di
|
||||
ri-certificazione. Lo segnalo qui esplicitamente perché la regola del prompt di
|
||||
certificazione ("dove un documento dice una cosa e il codice ne dice un'altra, vince il
|
||||
codice, e lo scostamento va segnalato") si applica anche al **tempo**: al momento in cui
|
||||
scrivo, il datasheet descrive un comportamento più sicuro di quello che l'RTL
|
||||
effettivamente ha per `N_INPUTS=0`, `n_inputs_real=0`, `n_neurons_real=0`,
|
||||
`run_num_layers=0`, `num_neurons_graph=0`, e `SET_NET_TYPE` durante un run — nessuno di
|
||||
questi casi limite è menzionato come rischio in nessun documento pubblico del progetto.
|
||||
|
||||
**Non corretto in questa fase** (per policy §E — l'aggiornamento della documentazione è
|
||||
un'azione separata dall'analisi, e questa campagna è ancora in corso): raccomando di
|
||||
aggiornare `docs/FPGA-NeuralNetwork-Engine.md` (che già documenta il rischio di
|
||||
backpressure di `WRITE_RAM`/`READ_RAM`, lo stesso stile di sezione andrebbe usato qui) e il
|
||||
datasheet una volta che la campagna di certificazione è completa e i bug hanno uno stato
|
||||
definitivo (o corretti, o dichiarati come rischio noto permanente).
|
||||
|
||||
## Verdetto
|
||||
|
||||
**CERTIFICATO per l'allineamento sulle cifre/pinout/opcode** (nessun residuo stantio
|
||||
trovato). **NON CERTIFICATO per la documentazione dei rischi**: i 7 bug di questa campagna
|
||||
non sono ancora riflessi in nessun documento pubblico — scostamento reale, dichiarato qui,
|
||||
non nascosto, con l'azione di correzione esplicitamente rimandata a dopo il completamento
|
||||
della campagna.
|
||||
@@ -1,26 +0,0 @@
|
||||
# C.14 — Lavori in corso (page-mode PSRAM, sottosistema flash)
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
## Stato reale, non dichiarato
|
||||
|
||||
Entrambi gli elementi che il prompt di certificazione elenca come "lavori in corso" sono in
|
||||
realtà **completi**, verificato per lo stato reale della repo (non per quanto dichiarato):
|
||||
|
||||
- **Page-mode PSRAM**: `sim/psram_page_mode_tb.v` esiste, copre `ACCESS_CYCLES`, `PAGE_CYCLES`,
|
||||
`tCEM` (idle timeout e budget mid-burst), con `psram_model.v` che fa `$fatal` su
|
||||
violazione di timing reale. Rieseguito PASS in Fase 0 (§C.3).
|
||||
- **Sottosistema flash**: `rtl/spi_flash_master.v`, `flash_copy_engine.v`,
|
||||
`flash_slot_manager.v` esistono, 8 opcode SPI (0x40-0x47) integrati in
|
||||
`spi_neuron_top.v`, bus SPI reso indipendente in Fase F7. 33 testbench del progetto
|
||||
includono 9 dedicati al sottosistema flash, tutti PASS in Fase 0.
|
||||
|
||||
**Nessun residuo "in corso" trovato**: non ci sono moduli RTL a metà, TODO irrisolti nel
|
||||
codice, o funzionalità dichiarate ma non implementate per questi due elementi.
|
||||
|
||||
## Verdetto
|
||||
|
||||
**CERTIFICATO come COMPLETO**, non "in corso" — il prompt di certificazione descriveva
|
||||
questi elementi come potenzialmente incompleti, ma lo stato reale della repo (verificato,
|
||||
non assunto) li mostra completi e testati, coerentemente con quanto già stabilito nella
|
||||
documentazione. Nessuno scostamento trovato qui.
|
||||
@@ -1,143 +0,0 @@
|
||||
# CERTIFICATO FPGA-Neural — Campagna di ri-certificazione 2026-09-04 (aggiornato post-fix)
|
||||
|
||||
Metodo per ogni aspetto: analisi statica del codice reale (non di descrizioni), test con
|
||||
oracolo indipendente (Python, calcolo a mano, o citazione datasheet), verifica su entrambi
|
||||
i piani (simulazione Icarus + sintesi reale Yosys/nextpnr-ecp5 dove applicabile). Dettagli,
|
||||
comandi esatti e log per capitolo in `docs/validation/00-inventario.md` e
|
||||
`docs/validation/01-datapath.md` … `D-trasversali.md`. Dettagli dei fix e delle relative
|
||||
verifiche in `docs/validation/bugs.md`.
|
||||
|
||||
Questo documento è la revisione POST-FIX del verdetto iniziale (commit `77e74db`, fase di
|
||||
sola analisi). Il verdetto iniziale resta leggibile nella storia git per trasparenza sul
|
||||
processo — questo documento lo sostituisce come stato corrente del progetto.
|
||||
|
||||
---
|
||||
|
||||
## Verdetto complessivo
|
||||
|
||||
**Il datapath aritmetico di base è solido e certificato esaustivamente dove possibile**
|
||||
(`mac_unit.v`: 65536/65536 combinazioni INT8 esaustive, 0 mismatch). **Il resto del design
|
||||
— controllo, sequenziamento, arbitraggio — è funzionalmente corretto sul percorso felice**,
|
||||
confermato da una regressione di 43 testbench reali tutti PASS (harness indipendente creato
|
||||
in questa campagna).
|
||||
|
||||
**La campagna aveva trovato 7 bug reali, concentrati tutti in un unico pattern sistemico**:
|
||||
valori limite "reale=0" (`N_INPUTS`, `n_inputs_real`, `n_neurons_real`, `run_num_layers`,
|
||||
`num_neurons_graph`) e una scrittura di configurazione (`SET_NET_TYPE`) non protetta durante
|
||||
un'operazione in corso. Due di questi (BUG-005, BUG-007) erano CRITICI: raggiungibili con
|
||||
opcode SPI documentati in condizioni plausibili, con rischio di corruzione dati reale in
|
||||
PSRAM o hang dell'inferenza in corso.
|
||||
|
||||
**Tutti e 7 i bug sono ora stati corretti in RTL e verificati indipendentemente**, ciascuno
|
||||
con il proprio testbench di regressione riscritto per ASSERIRE (non solo osservare) il
|
||||
comportamento corretto — dettagli completi, evidenza per-bug ed esiti dei test in
|
||||
`docs/validation/bugs.md`. Il fix è stato applicato come commit separato dall'analisi
|
||||
originale, per policy della campagna (§E del mandato). La regressione completa (43/43 test
|
||||
reali PASS) e una nuova sintesi/place&route reale (Yosys + nextpnr-ecp5, 0 errori, Fmax
|
||||
invariato entro il rumore di piazzamento) confermano che nessuno dei fix ha introdotto
|
||||
regressioni sul percorso felice né sul timing.
|
||||
|
||||
**Il progetto è ora certificabile con riserve residue minori** (elencate sotto — nessuna di
|
||||
severità CRITICA o MEDIA rimane aperta), a differenza del verdetto iniziale che richiedeva
|
||||
riserve esplicite bloccanti su BUG-005/007 prima di un uso in produzione con host non
|
||||
completamente fidato.
|
||||
|
||||
---
|
||||
|
||||
## Tabella per aspetto (aggiornata post-fix)
|
||||
|
||||
| Aspetto | Verdetto | Capitolo |
|
||||
|---|---|---|
|
||||
| Fase 0 — Inventario | CERTIFICATO (fotografia reale, non presunta) | `00-inventario.md` |
|
||||
| C.1 — Datapath aritmetico (`mac_unit`, `mac8`) | CERTIFICATO (guard `N_INPUTS=0` corretto e verificato — BUG-002 risolto) | `01-datapath.md`, `bugs.md` |
|
||||
| C.2 — Larghezza runtime | CERTIFICATO (BUG-003, BUG-004 risolti e verificati) | `02-runtime-width.md`, `bugs.md` |
|
||||
| C.3 — Sottosistema memoria | CERTIFICATO | `03-memoria.md` |
|
||||
| C.4 — Arbitro | CERTIFICATO (riserva documentale non-bug su starvation di D) | `04-arbiter.md` |
|
||||
| C.5 — Sequencer dense | CERTIFICATO (BUG-005, ex-CRITICO, risolto e verificato) | `05-layer-sequencer.md`, `bugs.md` |
|
||||
| C.6 — Motore grafo | CERTIFICATO (BUG-006 risolto e verificato) | `06-graph-engine.md`, `bugs.md` |
|
||||
| C.7 — SPI slave + engine | CERTIFICATO | `07-spi.md` |
|
||||
| C.8 — Top-level | CERTIFICATO (BUG-007, ex-CRITICO, risolto e verificato end-to-end su SPI reale) | `08-top-level.md`, `bugs.md` |
|
||||
| C.9 — Pinout / `.lpf` | CERTIFICATO | `09-pinout.md` |
|
||||
| C.10 — Timing | CERTIFICATO (Fmax post-fix 68.65 MHz, invariato entro rumore di piazzamento rispetto a 67.91 MHz pre-fix) | `10-timing.md`, `bugs.md` |
|
||||
| C.11 — Toolchain / build | CERTIFICATO (silicio reale: NON CERTIFICABILE, nessun hardware disponibile) | `11-toolchain.md` |
|
||||
| C.12 — `netasm` | CERTIFICATO | `12-netasm.md` |
|
||||
| C.13 — Coerenza datasheet↔RTL | AGGIORNATO POST-FIX (i 7 bug e i relativi fix sono ora riflessi nel datasheet) | `13-coerenza-datasheet.md` |
|
||||
| C.14 — Lavori in corso | CERTIFICATO (risultano completi, non "in corso") | `14-lavori-in-corso.md` |
|
||||
| D.1 — CDC | CERTIFICATO | `D-trasversali.md` §D.1 |
|
||||
| D.2 — Reset | CERTIFICATO (fatto: sincrono ovunque, non async) | `D-trasversali.md` §D.2 |
|
||||
| D.3 — FSM | CERTIFICATO CON RISERVE (nessuna analisi di raggiungibilità esaustiva oltre il pattern "reale=0" già trovato e corretto) | `D-trasversali.md` §D.3 |
|
||||
| D.4 — Larghezze/overflow | CERTIFICATO CON RISERVE | `D-trasversali.md` §D.4 |
|
||||
| D.5 — Lint | CERTIFICATO (0 latch accidentali, 1 warning noto/atteso, invariato post-fix) | `D-trasversali.md` §D.5 |
|
||||
| D.6 — Determinismo | CERTIFICATO CON RISERVE (non verificato con campagna dedicata) | `D-trasversali.md` §D.6 |
|
||||
|
||||
---
|
||||
|
||||
## Registro bug — riepilogo (dettagli completi in `bugs.md`)
|
||||
|
||||
| ID | Severità | Sintomo | Raggiungibilità | Stato |
|
||||
|---|---|---|---|---|
|
||||
| BUG-001 | INFO | `sim/top.v` non compilava (dead code, residuo pre-INT8) | N/A (non nella regressione) | **RISOLTO** — file rimosso |
|
||||
| BUG-002 | MEDIA | `N_INPUTS=0` bypassava il guard compile-time, `start` ignorato | Richiede una nuova sintesi | **RISOLTO** — guard esteso, verificato (fallimento di compilazione atteso) |
|
||||
| BUG-003 | MEDIA | `n_inputs_real=0` a runtime, comportamento incoerente tra ripetizioni (hang o limite ignorato) | Runtime, via SPI (`SET_BASE` sel 7) | **RISOLTO** — early-out esplicito, verificato (1 ciclo, y=0) |
|
||||
| BUG-004 | BASSA | `n_neurons_real=0`, limite ignorato silenziosamente, conteggio cicli incoerente tra build | Runtime, via SPI (`SET_BASE` sel 8) | **RISOLTO** — guard su 3 punti d'ingresso, verificato (32 vs 155 cicli) |
|
||||
| BUG-005 | **CRITICA** | `RUN_NETWORK(0)` eseguiva 256 layer fasulli, scriveva PSRAM a indirizzi arbitrari | Un solo opcode SPI documentato | **RISOLTO** — no-op immediato, verificato (1 ciclo, layer_idx=0) |
|
||||
| BUG-006 | BASSA | Stessa causa di BUG-005 in `graph_engine`, mitigata incidentalmente da un guard esistente | Un solo opcode SPI, rischio pratico basso osservato | **RISOLTO** — no-op immediato, verificato (13 cicli, no err) |
|
||||
| BUG-007 | **CRITICA** | `SET_NET_TYPE` durante un run bloccava il motore in corso | Due opcode SPI documentati in sequenza ravvicinata | **RISOLTO** — scrittura rifiutata mentre busy, verificato end-to-end su SPI reale |
|
||||
|
||||
Tutti i fix e le rispettive verifiche sono in un commit separato dall'analisi originale
|
||||
(policy §E). Regressione completa post-fix: 44 testbench, 43 PASS, 0 FAIL/ERROR, 1
|
||||
BENCHMARK (nessun verdetto per progetto, invariato).
|
||||
|
||||
---
|
||||
|
||||
## Riserve aperte residue (onestà sui limiti, §A.5)
|
||||
|
||||
Nessuna riserva CRITICA o MEDIA rimane aperta. Riserve residue, tutte già dichiarate nel
|
||||
verdetto iniziale e non toccate dalla campagna di fix (fuori scope, o limiti strutturali
|
||||
della metodologia):
|
||||
|
||||
1. **BUG-003/004 (nota storica)**: il meccanismo esatto del comportamento PRE-fix (perché
|
||||
variava tra hang e limite ignorato) non è stato isolato bit-per-bit nemmeno durante la
|
||||
correzione — il fix bypassa l'intero percorso ambiguo con un early-out esplicito,
|
||||
verificato deterministico sul NUOVO comportamento. Non rilevante per la sicurezza
|
||||
dell'RTL corrente, ma dichiarato per trasparenza sul processo.
|
||||
2. **C.11**: comportamento su silicio reale non verificabile in questo ambiente (nessun
|
||||
hardware fisico) — limite dichiarato dall'inizio del progetto, non di questa campagna.
|
||||
3. **D.3 (FSM)**: nessuna analisi di raggiungibilità esaustiva di OGNI FSM del progetto — i
|
||||
6 bug di FSM trovati (BUG-002-007) sono stati scoperti e corretti seguendo un pattern
|
||||
(valori limite "reale=0"), non da un'analisi sistematica di ogni possibile stato.
|
||||
**Potrebbero esisterne altri non ancora scoperti**, in particolare in moduli non ancora
|
||||
sottoposti a test avversariali mirati su valori limite (es. `psram_controller.v`,
|
||||
`spi_slave.v`).
|
||||
4. **D.6 (determinismo)**: nessuna campagna dedicata di run ripetuti/seed multipli.
|
||||
5. **Verifica elettrica/analogica reale** (setup/hold, rise/fall, signal integrity): mai in
|
||||
scope per una campagna basata su simulazione comportamentale + sintesi digitale — limite
|
||||
strutturale della metodologia, dichiarato fin dall'inizio del progetto (§A.5).
|
||||
|
||||
---
|
||||
|
||||
## Verifica dei fix (dual-plane, §A.4)
|
||||
|
||||
- **Simulazione**: ciascuno dei 6 bug RTL (BUG-002 – BUG-007) ha un testbench di
|
||||
regressione dedicato, riscritto dopo il fix per ASSERIRE il comportamento corretto
|
||||
(non solo osservarlo, come durante la fase di scoperta) — vedi `docs/validation/bugs.md`
|
||||
per il dettaglio di ogni asserzione e il relativo esito.
|
||||
- **Sintesi reale**: `yosys synth_ecp5` sul sistema completo (`spi_neuron_top` con
|
||||
sottosistema flash, PARALLEL=8) — 0 problemi CHECK, 1 warning atteso/preesistente
|
||||
(invariato). `nextpnr-ecp5` reale — 0 errori di vincolo, 0 pin non vincolati, Fmax 68.65
|
||||
MHz (invariato entro il rumore di piazzamento rispetto ai 67.91 MHz pre-fix), percorso
|
||||
critico strutturalmente identico (accumulatore MAC in `neuron_parallel.v`/`mac8.v`, non
|
||||
toccato dai fix). Log: `synth/ecp5/post_fix_verify/`.
|
||||
- **Regressione**: `python3 tools/run_regression.py` — 44 testbench, 43 PASS, 0
|
||||
FAIL/ERROR, 1 BENCHMARK (per progetto, invariato).
|
||||
|
||||
---
|
||||
|
||||
## Stato lavori residui
|
||||
|
||||
1. ~~BUG-005 e BUG-007 (CRITICI)~~ — **RISOLTI**.
|
||||
2. ~~BUG-002/003/004 (MEDIA/BASSA)~~ — **RISOLTI**.
|
||||
3. ~~BUG-006~~ — **RISOLTO**.
|
||||
4. ~~BUG-001~~ — **RISOLTO** (file rimosso).
|
||||
5. ~~Aggiornamento datasheet/documentazione (C.13)~~ — completato in questo stesso ciclo di
|
||||
lavoro (markdown + LaTeX IT/EN).
|
||||
@@ -1,122 +0,0 @@
|
||||
# D — Analisi trasversali
|
||||
|
||||
Data: 2026-09-04.
|
||||
|
||||
---
|
||||
|
||||
## D.1 CDC (clock domain crossing)
|
||||
|
||||
`spi_slave.v` è l'unico vero attraversamento di dominio di clock del progetto (`sclk`
|
||||
esterno asincrono → `clk` di sistema): sincronizzatore a 2/3 stadi, verificato con un test
|
||||
esplicito a rapporto SCLK/clk variabile (`sim/spi_slave_tb.v` TEST 4, §C.7). Nessun altro
|
||||
segnale multi-bit attraversa domini di clock diversi senza passare prima per questo
|
||||
sincronizzatore a livello di bit singolo (i segnali multi-bit, es. gli indirizzi SPI, sono
|
||||
ricostruiti byte-per-byte SUL lato `clk` dopo la sincronizzazione bit-a-bit, non
|
||||
attraversano il confine come bus paralleli).
|
||||
|
||||
**Verdetto: CERTIFICATO** (evidenza da C.7, non ripetuta qui).
|
||||
|
||||
---
|
||||
|
||||
## D.2 Reset
|
||||
|
||||
**Trovato per ispezione su tutti i 20 file RTL** (non assunto): `grep -l "posedge rst"
|
||||
rtl/*.v` → **nessun risultato**. L'intero progetto usa reset **esclusivamente sincrono**
|
||||
(`always @(posedge clk) if (rst) ... else ...`), mai `always @(posedge clk or posedge rst)`.
|
||||
Questo è **diverso** da quanto la formulazione "async assert / sync deassert" del prompt di
|
||||
certificazione presuppone — non è un difetto (reset sincrono è una scelta di design comune
|
||||
e spesso preferita su FPGA, evita i problemi di recovery/removal timing tipici del reset
|
||||
asincrono), ma va segnalato come fatto reale, non l'assunzione implicita nel prompt.
|
||||
|
||||
Nessuno stato illegale dopo reset a metà operazione trovato nei moduli testati in questa
|
||||
campagna (C.1-C.8) — ogni reset osservato riporta correttamente FSM/accumulatori/flag a
|
||||
zero, confermato empiricamente nei test di regressione (33+ testbench, incl. reset a metà
|
||||
run in `flash_slot_manager_tb.v`'s test di power-loss simulato, §sessioni precedenti).
|
||||
|
||||
**Verdetto: CERTIFICATO come "reset sincrono coerente in tutto il progetto"** (fatto
|
||||
verificato per ispezione esaustiva, non campione).
|
||||
|
||||
---
|
||||
|
||||
## D.3 FSM (stati irraggiungibili, deadlock, default sicuro)
|
||||
|
||||
Non è stata fatta un'analisi di raggiungibilità formale di ogni FSM del progetto (fuori
|
||||
scope per il tempo di questa campagna) — ma **6 bug reali trovati in questa campagna
|
||||
(BUG-002-007) sono ESATTAMENTE difetti di FSM**: contatori che avvolgono su un valore
|
||||
raggiungibile invece di essere bloccati da una guardia, e un mux non agganciato allo stato
|
||||
del motore che sta effettivamente pilotando. Questo non è una copertura esaustiva, ma è una
|
||||
verifica reale e concreta della categoria "deadlock/stato scorretto", con risultati
|
||||
concreti (non un "nessun problema trovato" vuoto).
|
||||
|
||||
Ogni `case` osservato nei moduli letti in questa campagna ha un ramo `default` che
|
||||
riporta lo stato a IDLE/SEL_NONE (verificato in `mem_arbiter.v`, `int8_memory_access.v`,
|
||||
`neuron_parallel.v` — nessuno stato `case` privo di default trovato nei moduli ispezionati).
|
||||
|
||||
**Verdetto: CERTIFICATO CON RISERVA** — i difetti di FSM effettivamente presenti (BUG-002-007)
|
||||
sono stati trovati e documentati, ma non è stata fatta un'analisi di raggiungibilità
|
||||
esaustiva di OGNI FSM del progetto: potrebbero esisterne altri non ancora scoperti nei
|
||||
moduli non ancora sottoposti a test avversariali mirati sui valori limite (es. `spi_slave.v`
|
||||
stesso, `psram_controller.v` oltre a quanto già verificato in sessioni precedenti).
|
||||
|
||||
---
|
||||
|
||||
## D.4 Larghezze e overflow
|
||||
|
||||
**Un bug reale di questa classe era già stato trovato e corretto in una fase precedente di
|
||||
questa stessa sessione** (non solo teoria): `FLASH_SPACE_BYTES = 24'h100_0000` (16MB=2^24)
|
||||
troncava silenziosamente a 0 in 24 bit, catturato dal warning di iverilog stesso
|
||||
("Numeric constant truncated"), corretto allargando a 25 bit — citato per completezza, non
|
||||
riscoperto qui.
|
||||
|
||||
**In questa campagna**: la causa radice di BUG-002 è ESATTAMENTE un problema di larghezza
|
||||
(`[DATA_WIDTH*N_INPUTS-1:0]` con `N_INPUTS=0` diventa `[-1:0]`, che sia Icarus sia Yosys
|
||||
trattano come 2 bit reali invece di larghezza zero) — un secondo caso reale della stessa
|
||||
categoria, trovato con evidenza su entrambi i piani di verifica (non solo simulazione).
|
||||
|
||||
**Verdetto: CERTIFICATO CON RISERVA** — due casi reali di questa categoria trovati e
|
||||
documentati (uno in sessione precedente, uno in questa campagna), nessuna garanzia che sia
|
||||
l'unico rimasto.
|
||||
|
||||
---
|
||||
|
||||
## D.5 Lint
|
||||
|
||||
**Eseguito in questa fase** (non solo il CHECK pass isolato per modulo già visto durante
|
||||
tutta la sessione): sintesi Yosys dell'intero sistema (`spi_neuron_top` + tutti i 19 moduli
|
||||
RTL che istanzia), con `proc; opt_clean; check`, filtrando esplicitamente ogni messaggio
|
||||
`warning`/`latch`/`error`/`width mismatch`/`multiple driver`:
|
||||
|
||||
```
|
||||
Warnings: 1 unique messages, 1 total
|
||||
rtl/psram_controller.v:191: Warning: Yosys has only limited support for tri-state logic
|
||||
[...25× "No latch inferred for signal ..." -- CONFERME, non warning: ogni blocco
|
||||
combinazionale controllato NON ha inferito un latch accidentale, incl. l'intero albero
|
||||
binario di mac8.v e la funzione next_crc di crc32.v]
|
||||
```
|
||||
|
||||
**Un solo warning reale**, lo stesso già noto e documentato ripetutamente in
|
||||
`WORKLOG.md` fin dalla Fase 15 (bus dati PSRAM bidirezionale, comportamento tri-state
|
||||
atteso e corretto per un bus dati esterno, non un difetto). **Zero latch inferiti
|
||||
accidentalmente** in tutto il progetto, confermato esplicitamente segnale per segnale, non
|
||||
solo per assenza di un warning generico.
|
||||
|
||||
**Verdetto: CERTIFICATO.** Nessun warning reale non spiegato, nessun latch accidentale in
|
||||
tutto il progetto.
|
||||
|
||||
---
|
||||
|
||||
## D.6 Determinismo
|
||||
|
||||
Non eseguita una campagna dedicata di run ripetuti a confronto bit-esatto in questa fase
|
||||
(fuori scope per il tempo disponibile) — ma **evidenza indiretta forte** raccolta durante
|
||||
tutta questa campagna: ogni test rieseguito più volte durante il debug (es. i tentativi
|
||||
multipli su BUG-003 in C.2, il test di regressione completo rieseguito ad ogni fase C.1-C.8)
|
||||
ha prodotto **risultati identici a parità di stimolo** — l'unica "incoerenza" osservata
|
||||
(BUG-003) è stata tracciata a **stimoli testbench effettivamente diversi tra i tentativi**
|
||||
(pattern di reset diverso, sequenza di chiamate diversa), non a un comportamento
|
||||
non-deterministico del design a parità di stimolo esatto — confermato ripetendo lo stesso
|
||||
identico stimolo più volte con risultato stabile.
|
||||
|
||||
**Verdetto: CERTIFICATO CON RISERVA** — nessuna evidenza di non-determinismo reale trovata,
|
||||
ma non verificato con una campagna dedicata (es. seed multipli su tutti i testbench,
|
||||
confronto bit-esatto sistematico).
|
||||
@@ -1,328 +0,0 @@
|
||||
# Registro bug — campagna di ri-certificazione FPGA-Neural
|
||||
|
||||
Formato per ogni voce: severità, sintomo, causa radice, evidenza (file:riga / comando/log
|
||||
citabile), stato, test di regressione che lo blocca (se risolto) o che lo riprodurrebbe (se
|
||||
aperto). Aggiornato incrementalmente man mano che avanzano gli aspetti C.1–C.14.
|
||||
|
||||
Severità: **CRITICA** (corrompe dati/hang in scenari raggiungibili), **MEDIA** (comportamento
|
||||
scorretto in casi limite plausibili ma rari), **BASSA** (difetto reale ma senza impatto
|
||||
funzionale pratico), **INFO** (non un bug: gap di copertura, ambiguità documentale/naming).
|
||||
|
||||
---
|
||||
|
||||
## Aperti
|
||||
|
||||
(nessuno — tutti i bug della campagna sono stati corretti e verificati, vedi "Risolti" sotto)
|
||||
|
||||
---
|
||||
|
||||
## Risolti
|
||||
|
||||
### BUG-001 (INFO) — `sim/top.v` non compila contro l'RTL corrente
|
||||
|
||||
- **Sintomo**: `iverilog` fallisce con `parameter FRAC_BITS not found in top.dut`.
|
||||
- **Causa radice**: `sim/top.v` è un residuo della versione Q8.8 a virgola fissa del
|
||||
progetto, mai aggiornato dopo la conversione a INT8 puro (Fase 6, vedi
|
||||
`docs/validation/00-inventario.md` §0.2).
|
||||
- **Evidenza**: `iverilog -g2012 -o /tmp/topcheck.out rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v sim/top.v` → 2 errori di elaborazione.
|
||||
- **Impatto**: nessuno sulla regressione (il file non è referenziato da alcun testbench o
|
||||
tool) — era dead code, non un difetto funzionale del design.
|
||||
- **Fix applicato**: file rimosso (`git rm sim/top.v`) — confermato non referenziato da alcun
|
||||
testbench o tool (`tools/run_regression.py` lo esclude esplicitamente dal proprio elenco
|
||||
sorgenti anche prima della rimozione).
|
||||
- **Stato**: **RISOLTO** — file eliminato, nessun test di regressione necessario (non c'era
|
||||
comportamento da preservare).
|
||||
|
||||
### BUG-002 (MEDIA, CONFERMATO su sim + sintesi reale) — `N_INPUTS=0` bypassa il guard, `start` viene silenziosamente ignorato
|
||||
|
||||
- **Sintomo confermato** (non più un'ipotesi — vedi `docs/validation/01-datapath.md` §1.4 per
|
||||
la narrativa completa, incl. un falso positivo iniziale nella mia stessa metodologia di
|
||||
test, corretto e ridocumentato per trasparenza): con
|
||||
`neuron_parallel #(.N_INPUTS(0), .PARALLEL(P))`, il guard elaboration-time
|
||||
(`rtl/neuron_parallel.v:71`, `if (N_INPUTS % PARALLEL != 0)`) **non scatta** (`0 % P == 0`
|
||||
per ogni `P`), il modulo **elabora con successo** (sia in simulazione Icarus sia in sintesi
|
||||
reale Yosys, 0 problemi CHECK). A runtime: `start` viene accettato ma **`busy` non si alza
|
||||
mai e `done` non pulsa mai** — non l'hang "busy resta alto per sempre" descritto nel
|
||||
commento originale del guard (righe 55-58), un sintomo diverso, osservato per la prima
|
||||
volta in questa campagna.
|
||||
- **Causa radice, confermata (non più ipotesi)**: `x_bus`/`w_bus` sono dichiarati
|
||||
`[DATA_WIDTH*N_INPUTS-1:0]`, che per `N_INPUTS=0` diventa `[-1:0]` — un range che **non
|
||||
collassa a larghezza zero**: sia Icarus sia Yosys lo trattano come un vettore reale a
|
||||
**2 bit** (larghezza = |MSB-LSB|+1 = 2), lasciato non pilotato. Confermato dai warning di
|
||||
Yosys: `Wire ...x_bus[1] is used but has no driver` (×2, per x_bus e w_bus).
|
||||
- **Evidenza**:
|
||||
- `iverilog -g2012 -o /tmp/n0proper.out rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v sim/neuron_parallel_bug002_n_inputs_zero_tb.v && vvp /tmp/n0proper.out` → conferma il sintomo, ogni volta.
|
||||
- `yosys -p "synth_ecp5 -json /tmp/n0.json -top n0_synth_wrap" rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v <wrapper>` → **0 problemi CHECK**, 4 warning "no driver" su x_bus/w_bus[1:0].
|
||||
- Test di regressione permanente: `sim/neuron_parallel_bug002_n_inputs_zero_tb.v`.
|
||||
- **Impatto pratico**: `N_INPUTS` è un parametro Verilog fissato in fase di sintesi (non un
|
||||
registro configurabile via SPI a runtime) — per essere raggiunto, qualcuno deve
|
||||
deliberatamente istanziare il modulo con `N_INPUTS=0`, cosa che non ha senso semantico per
|
||||
un layer reale. Rischio quindi basso in pratica (nessun percorso runtime/host-controllato
|
||||
può innescarlo), ma è un buco reale e confermato nella protezione, non solo teorico.
|
||||
- **Fix applicato** (`rtl/neuron_parallel.v`, guard di elaborazione): esteso a
|
||||
`if (N_INPUTS == 0 || N_INPUTS % PARALLEL != 0)` — `N_INPUTS=0` ora fa fallire
|
||||
l'elaborazione con lo stesso errore `Unknown module type:
|
||||
neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL` dei due test negativi già
|
||||
esistenti, invece di elaborare con successo e produrre `start` silenziosamente ignorato.
|
||||
- **Test di regressione**: `sim/neuron_parallel_bug002_n_inputs_zero_tb.v`, riscritto per
|
||||
asserire il fallimento di compilazione (stesso pattern di
|
||||
`neuron_parallel_guard_negative_*_tb.v`), aggiunto a `EXPECTED_COMPILE_FAIL` in
|
||||
`tools/run_regression.py`. Verificato: `iverilog -g2012 -o /tmp/out rtl/neuron_parallel.v sim/neuron_parallel_bug002_n_inputs_zero_tb.v` → errore di elaborazione atteso, exit code 3.
|
||||
- **Stato**: **RISOLTO, verificato.**
|
||||
|
||||
### BUG-003 (MEDIA, CONFERMATO ma NON pienamente caratterizzato) — `n_inputs_real=0` a runtime, comportamento incoerente tra ripetizioni
|
||||
|
||||
- **Sintomo**: con `N_INPUTS=32, PARALLEL=8` validi a compile-time (nessun problema di
|
||||
larghezza `[-1:0]`, a differenza di BUG-002), impostando `n_inputs_real=0` a runtime (lo
|
||||
stesso percorso raggiungibile dall'host via `SET_BASE sel=7`) il comportamento osservato
|
||||
**varia tra ripetizioni quasi identiche dello stesso test**: a volte `start` viene
|
||||
accettato ma `busy`/`done` non si muovono mai più (hang), a volte l'operazione completa
|
||||
normalmente ma processa l'INTERA larghezza di build invece di zero elementi (limite
|
||||
ignorato silenziosamente, stessa classe di BUG-004). Vedi `docs/validation/
|
||||
02-runtime-width.md` §2.3 per la registrazione completa di ogni singola ripetizione e dei
|
||||
suoi risultati, riportati senza scartare quelli "scomodi".
|
||||
- **Causa radice**: **non isolata con certezza** entro il tempo ragionevole per questa
|
||||
campagna. Analisi aritmetica plausibile (non confermata come spiegazione completa): per
|
||||
questa build `GROUP_INDEX_WIDTH=2` bit, quindi `groups_real[1:0]-1` per `groups_real=0`
|
||||
avvolge al valore 3 (raggiungibile da un contatore a 2 bit, a differenza del contatore a
|
||||
1 bit di BUG-002) — spiegherebbe l'esito "limite ignorato" come esito aritmeticamente
|
||||
atteso, ma non spiega perché in alcune ripetizioni compaia invece un hang vero. Esclusi
|
||||
esplicitamente: propagazione di X in simulazione (verificato inizializzando ogni registro
|
||||
prima di qualunque reset, il comportamento non cambia), e una dipendenza semplice
|
||||
dall'ordine delle chiamate (una sequenza valida→zero non blocca; una sequenza
|
||||
zero→zero→zero blocca dalla seconda chiamata in poi, non dalla prima — non un pattern
|
||||
semplice "prima volta sicura, poi no").
|
||||
- **Impatto pratico**: come BUG-002, `n_inputs_real=0` non ha senso semantico per una rete
|
||||
reale, ma a differenza di BUG-002 questo valore **è raggiungibile a runtime da un host via
|
||||
SPI** senza bisogno di una nuova sintesi — un host con un bug che calcola erroneamente
|
||||
`n_inputs_real=0` per un caso limite (es. un layer con zero neuroni in una topologia
|
||||
degenere) potrebbe innescarlo, con un esito imprevedibile tra hang e risultato
|
||||
silenziosamente sbagliato.
|
||||
- **Fix applicato** (`rtl/neuron_parallel.v`, dentro `if (start && !busy)`):
|
||||
`finishing <= (n_inputs_real == 16'h0);` (era `finishing <= 0;`) — riusa il percorso di
|
||||
completamento "finishing" già corretto ed esistente nel modulo invece di introdurre nuova
|
||||
logica per il caso degenere, stessa convenzione già usata altrove nel progetto. Per zero
|
||||
input reali il risultato matematico è `y = activation(bias)`: con `bias=0` e ACT_RELU nel
|
||||
test di regressione, `expect_y=0`.
|
||||
- **Nota onestà**: il meccanismo esatto per cui il comportamento pre-fix variava tra
|
||||
ripetizioni (hang vs. risultato sbagliato silenzioso) **non è stato isolato bit-per-bit**
|
||||
neppure in fase di correzione — il fix è un early-out esplicito che bypassa
|
||||
l'intero percorso ambiguo, verificato corretto e deterministico sul nuovo comportamento,
|
||||
non una spiegazione a posteriori del vecchio meccanismo.
|
||||
- **Test di regressione**: `sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v` TEST 4,
|
||||
riscritto da osservazione ad asserzione hard (done entro 8 cicli, `y===0`). Verificato:
|
||||
`n_inputs_real=0` completa in **1 ciclo**, `y=0` — TUTTI I TEST PASSED (incluse le TEST
|
||||
1-3 di non-regressione sulla regione "poison").
|
||||
- **Stato**: **RISOLTO, verificato** (il meccanismo esatto del comportamento PRE-fix resta
|
||||
non isolato per intero, per trasparenza, ma non è più rilevante: il nuovo percorso è
|
||||
deterministico e verificato indipendentemente).
|
||||
|
||||
### BUG-004 (MEDIA, CONFERMATO scorretto, NON pienamente caratterizzato) — `n_neurons_real=0` non blocca, ma non fa nemmeno quello che ci si aspetterebbe in modo coerente
|
||||
|
||||
- **Sintomo**: a `rtl/neuron_memory.v`, con `n_neurons_real=0`, l'operazione **completa
|
||||
sempre normalmente** (mai un hang, a differenza di BUG-002/003) — ma il numero di cicli
|
||||
impiegato **non è coerente tra build diverse**: per `N_NEURONS=2` (`NEURON_INDEX_WIDTH=1`
|
||||
bit) impiega **esattamente** lo stesso numero di cicli di `n_neurons_real=2` (114=114,
|
||||
suggerendo che il limite venga ignorato e processi tutto), mentre per `N_NEURONS=3`
|
||||
(`NEURON_INDEX_WIDTH=2` bit) impiega **196 cicli — più della build completa a 3 neuroni
|
||||
(155)**, un terzo valore che non corrisponde né a "zero neuroni" né a "tutti i neuroni".
|
||||
In ogni caso testato: nessun errore, nessun timeout — un host che chiede zero neuroni
|
||||
riceve sempre un completamento dall'aspetto normale ma su un conteggio/dato diverso da
|
||||
quanto richiesto, e il conteggio esatto varia con `N_NEURONS`.
|
||||
- **Causa radice**: non isolata bit-per-bit (a differenza di BUG-002). Ipotesi coerente con
|
||||
BUG-003: l'aritmetica di avvolgimento (`neuron_index == n_neurons_real[W-1:0]-1`) per
|
||||
`n_neurons_real=0` produce un valore di terminazione che, per coincidenza di larghezza,
|
||||
corrisponde al conteggio pieno invece che a "termina subito".
|
||||
Vedi `docs/validation/02-runtime-width.md` §2.5.
|
||||
- **Impatto pratico**: come BUG-002/003, richiede che l'host imposti deliberatamente (o per
|
||||
bug proprio) `n_neurons_real=0` — non raggiungibile da un input esterno arbitrario, ma
|
||||
raggiungibile da un bug nel software host senza bisogno di ricompilare il bitstream.
|
||||
- **Fix applicato** (`rtl/neuron_memory.v`, tre punti coordinati, non uno solo): il pattern
|
||||
vulnerabile esisteva in TRE punti distinti, non uno — scoperto durante la correzione stessa
|
||||
(un primo tentativo di guard solo al dispatch `STATE_IDLE`→`STATE_READ_X` è stato
|
||||
riconosciuto insufficiente perché copriva un solo dei tre punti d'ingresso nello stato
|
||||
vulnerabile `STATE_READ_W`, che viene rientrato indipendentemente una volta per neurone nel
|
||||
loop). Fix corretto (single-point-of-truth sulle CONDIZIONI di terminazione, non sui punti
|
||||
di dispatch): `STATE_READ_X` e `STATE_READ_W` guadagnano entrambe un prefisso
|
||||
`n_inputs_real == 16'h0 ||`/`n_neurons_real == 16'h0 ||` sulla propria condizione di
|
||||
terminazione, e il punto di transizione X→W guadagna un ramo esplicito
|
||||
`if (n_neurons_real == 16'h0) begin busy<=0; done<=1; state<=STATE_IDLE; end`.
|
||||
- **Test di regressione**: `sim/neuron_memory_bug004_n_neurons_real_zero_tb.v` TEST 4,
|
||||
riscritto per asserire `done` entro il baseline a piena larghezza (`cyc_full`, stabilito
|
||||
dinamicamente da TEST 3). Verificato: `n_neurons_real=0` completa in **32 cicli** contro
|
||||
**155** per il build completo a 3 neuroni (X viene ancora letto una volta, condiviso tra
|
||||
neuroni, ma nessun calcolo per-neurone viene eseguito) — TUTTI I TEST PASSED.
|
||||
- **Stato**: **RISOLTO, verificato** (stessa nota di onestà di BUG-003: il meccanismo esatto
|
||||
del comportamento PRE-fix — 114 vs 196 cicli a seconda di `N_NEURONS` — non è stato isolato
|
||||
bit-per-bit, ma il nuovo percorso è deterministico e verificato indipendentemente).
|
||||
|
||||
### BUG-005 (CRITICA, CONFERMATO) — `RUN_NETWORK(0)` esegue 256 layer fasulli leggendo dati arbitrari come descrittori
|
||||
|
||||
- **Sintomo**: `rtl/layer_sequencer.v` documenta `run_num_layers` come "1..N_LAYERS" ma
|
||||
**non esiste alcun guard**, né a tempo di elaborazione né a runtime, che lo imponga.
|
||||
`layer_idx` (riga 121) è un registro a 8 bit PIENO (non ristretto come il
|
||||
`group_index` a 1 bit di BUG-002) — per `run_num_layers=0`, la condizione di
|
||||
terminazione `layer_idx == num_layers_reg-1` (riga 303) avvolge a `layer_idx==255`, un
|
||||
valore che il contatore RAGGIUNGE naturalmente contando da 0. Risultato confermato
|
||||
empiricamente: **`RUN_NETWORK(0)` non si blocca — esegue tutti e 256 gli indici di
|
||||
layer possibili** (21761 cicli in simulazione) prima di terminare, ciascuno leggendo 11
|
||||
byte di "descrittore" da `table_base + layer_idx×11` — ben oltre la vera tabella
|
||||
descrittori (dimensionata per il build reale, tipicamente poche decine di byte) — e
|
||||
interpretando dati PSRAM arbitrari (pesi, altri dati di rete, o memoria non
|
||||
inizializzata) come indirizzi/parametri di layer validi, eseguendo run reali di
|
||||
`neuron_memory` con quei parametri e **scrivendo i risultati nei buffer ping-pong a
|
||||
indirizzi derivati da quei dati arbitrari**.
|
||||
- **Causa radice**: nessun guard su `run_num_layers`, né a tempo di elaborazione (come
|
||||
invece esiste per `N_INPUTS%PARALLEL` in `neuron_parallel.v`) né a runtime (come invece
|
||||
esiste, sia pure incompleto, per `n_inputs_real`/`n_neurons_real`, BUG-003/004).
|
||||
- **Evidenza**: `sim/layer_sequencer_bug005_zero_layers_tb.v` — `iverilog -g2012 -o /tmp/ls0.out rtl/layer_sequencer.v sim/layer_sequencer_bug005_zero_layers_tb.v && vvp /tmp/ls0.out` →
|
||||
`dut.layer_idx` termina a 255, non a 0.
|
||||
- **Impatto pratico**: **più severo di BUG-002/003/004** — raggiungibile con un singolo
|
||||
opcode SPI documentato (`RUN_NETWORK`, `num_layers=0`) senza bisogno di ricompilare il
|
||||
bitstream né di impostare un valore "runtime" degenere in un percorso secondario; il
|
||||
rischio non è solo un risultato sbagliato o un hang, ma **scritture reali in PSRAM a
|
||||
indirizzi non controllati**, derivati da dati che non erano mai stati pensati per essere
|
||||
interpretati come indirizzi.
|
||||
- **Fix applicato** (`rtl/layer_sequencer.v`, `ST_IDLE`): `run_num_layers==0` è ora un
|
||||
no-op esplicito e immediato — `seq_done` pulsa senza mai entrare in `ST_READ_DESC`,
|
||||
stessa convenzione già usata da `spi_engine.v` per `WRITE_RAM`/`READ_RAM` con `len==0`
|
||||
(accetta il comando, non fa nulla, nessun errore riportato).
|
||||
- **Test di regressione**: `sim/layer_sequencer_bug005_zero_layers_tb.v`, riscritto da
|
||||
osservazione ad asserzione hard (seq_done entro 5 cicli, `layer_idx===0`). Verificato:
|
||||
`run_num_layers=0` completa in **1 ciclo** con `layer_idx` rimasto a 0 (era 21761 cicli,
|
||||
`layer_idx` terminato a 255, prima del fix) — PASS.
|
||||
- **Stato**: **RISOLTO, verificato.**
|
||||
|
||||
### BUG-006 (BASSA, stessa causa radice di BUG-005, protezione incidentale) — `num_neurons_graph=0` in `graph_engine.v`
|
||||
|
||||
- **Sintomo/causa radice**: identica struttura a BUG-005 — `neuron_idx`
|
||||
(`rtl/graph_engine.v:159`) è un registro a 16 bit pieni, `num_neurons_graph=0` fa
|
||||
avvolgere la condizione di terminazione a un valore (65535) che il contatore raggiunge
|
||||
naturalmente. Nessun guard esplicito su `num_neurons_graph`.
|
||||
- **Differenza da BUG-005**: `graph_engine` ha già un guard runtime per-edge
|
||||
(`src_id>=out_id`/`out_id>=N_TOTAL` → `err`) che, **come effetto collaterale non
|
||||
progettato per questo scopo**, cattura la maggior parte dei pattern di dati spazzatura
|
||||
molto rapidamente — verificato con un pattern non banale: `err` a 58 cicli, non 65536.
|
||||
`layer_sequencer.v` non ha alcuna protezione equivalente.
|
||||
- **Evidenza**: `sim/graph_engine_bug006_zero_neurons_probe_tb.v` — finestra di 5000 cicli,
|
||||
non fatto girare a completamento (limite dichiarato, vedi
|
||||
`docs/validation/06-graph-engine.md` §6.2).
|
||||
- **Impatto pratico**: basso ma non nullo — la protezione PRE-fix era incidentale, non
|
||||
garantita per ogni possibile contenuto PSRAM. Il buco strutturale era reale.
|
||||
- **Fix applicato** (`rtl/graph_engine.v`, `ST_COPY_IN_WAIT`, alla transizione di fine copia
|
||||
input): `num_neurons_graph==0` è ora un no-op esplicito e immediato — `done` pulsa subito
|
||||
dopo il completamento della copia input, senza mai entrare in `ST_DESC_RD`/il loop
|
||||
descrittori, stessa convenzione del fix BUG-005.
|
||||
- **Test di regressione**: `sim/graph_engine_bug006_zero_neurons_probe_tb.v`, riscritto da
|
||||
osservazione a asserzione hard (`done` entro 30 cicli, nessun `err`, `neuron_idx===0`).
|
||||
Verificato: `num_neurons_graph=0` completa in **13 cicli** senza `err`, `neuron_idx` rimasto
|
||||
a 0 (era 58 cicli tramite l'intercettazione incidentale del guard src_id/out_id, prima del
|
||||
fix) — PASS.
|
||||
- **Stato**: **RISOLTO, verificato.**
|
||||
|
||||
### BUG-007 (CRITICA, CONFERMATO end-to-end via SPI reale) — `SET_NET_TYPE` durante un `RUN_NETWORK` in corso blocca permanentemente il motore in esecuzione
|
||||
|
||||
- **Sintomo**: `rtl/spi_engine.v`, stato `ST_SET_NET_TYPE`, accetta
|
||||
`net_type <= rx_byte` **incondizionatamente** su qualunque `rx_valid`, senza alcun
|
||||
controllo su `graph_busy`/`seq_busy`. `rtl/spi_neuron_top.v` (righe 394-397) instrada la
|
||||
Porta C dell'arbitro tra `graph_engine` e `layer_sequencer` in modo **puramente
|
||||
combinazionale** sul valore CORRENTE di `net_type` — non agganciato a quale motore ha
|
||||
effettivamente avviato il run in corso. Il commento alla riga 390 dichiara i due motori
|
||||
"mutually exclusive by construction", ma quella costruzione impedisce solo che **entrambi
|
||||
vengano avviati insieme** — non dice nulla su una scrittura di `net_type` che arriva a
|
||||
metà di un run già avviato.
|
||||
- **Confermato end-to-end su SPI reale** (non solo per ispezione): avviato un
|
||||
`RUN_NETWORK` in modalità grafo (lo stesso grafo valido già certificato in
|
||||
`spi_neuron_top_graph_tb.v`), poi immediatamente — prima che completi — inviato
|
||||
`SET_NET_TYPE(dense)` via SPI. Risultato: **`STATUS.busy` resta bloccato a 1 per 400+
|
||||
letture consecutive, ~2.35ms di tempo simulato** (contro i ~12-25µs normali per quel
|
||||
grafo) — un hang permanente, non un rallentamento. Le transazioni SPI stesse (incl. il
|
||||
`SET_NET_TYPE` avversariale) completano regolarmente; è specificamente il motore grafo
|
||||
a restare bloccato in attesa di un `ram_ready` che non arriverà mai più tramite il
|
||||
percorso ormai scollegato dal mux.
|
||||
- **Evidenza**: `sim/spi_neuron_top_bug007_mid_run_net_type_tb.v` — riproduce l'hang in
|
||||
modo deterministico e ripetibile su SPI reale (non solo un accesso interno).
|
||||
- **Impatto pratico**: **il più severo finora insieme a BUG-005** — raggiungibile con due
|
||||
soli opcode SPI documentati emessi in sequenza ravvicinata (`RUN_NETWORK` seguito da
|
||||
`SET_NET_TYPE` prima del completamento), uno scenario host plausibile (es. un host che
|
||||
prepara la configurazione per il prossimo run senza attendere la fine del precedente,
|
||||
o una race a livello applicativo tra due richieste). Blocca l'inferenza in corso finché
|
||||
l'host non se ne accorge (nessun timeout hardware, nessun errore riportato — solo
|
||||
`STATUS.busy` che non si abbassa mai).
|
||||
- **Recupero verificato**: un `RESET` inviato durante l'hang **riporta il sistema a uno
|
||||
stato pienamente funzionante** — verificato con una successiva operazione dense legittima
|
||||
completata normalmente (2 cicli di polling, esito corretto). **Non è un blocco
|
||||
permanente**, ma un host che si limita a fare polling di `STATUS` senza un timeout e un
|
||||
`RESET` di ripiego resterebbe bloccato indefinitamente comunque, dato che l'hardware non
|
||||
segnala mai da solo che qualcosa è andato storto.
|
||||
- **Fix applicato** (`rtl/spi_engine.v`, `ST_SET_NET_TYPE`): `net_type <= rx_byte` ora
|
||||
condizionato a `if (!graph_busy && !seq_busy)` — la scrittura viene silenziosamente
|
||||
rifiutata (comando accettato via SPI come prima, ma senza effetto) mentre un run è in
|
||||
corso, invece di rimappare il mux dell'arbitro a metà esecuzione.
|
||||
- **Test di regressione**: `sim/spi_neuron_top_bug007_mid_run_net_type_tb.v`, riscritto per
|
||||
asserire end-to-end su SPI reale sia (a) che il run in corso completi normalmente
|
||||
nonostante lo `SET_NET_TYPE` avversariale a metà esecuzione, sia (b) che la scrittura sia
|
||||
stata VERAMENTE rifiutata e non parzialmente applicata (una successiva `RUN_NETWORK` senza
|
||||
re-inviare `SET_NET_TYPE(graph)` completa comunque correttamente). Verificato: il run grafo
|
||||
completa con `out_base[0]=126` dopo 1 solo polling nonostante lo `SET_NET_TYPE(dense)`
|
||||
avversariale; `net_type` confermato ancora `GRAPH` internamente — PASS su entrambi i
|
||||
controlli.
|
||||
- **Stato**: **RISOLTO, verificato end-to-end su SPI reale.**
|
||||
|
||||
---
|
||||
|
||||
## Verifica post-fix su entrambi i piani (§A.4)
|
||||
|
||||
Dopo l'applicazione di tutti e 7 i fix (RTL: `rtl/neuron_parallel.v`, `rtl/neuron_memory.v`,
|
||||
`rtl/layer_sequencer.v`, `rtl/graph_engine.v`, `rtl/spi_engine.v`; rimozione:
|
||||
`sim/top.v`):
|
||||
|
||||
- **Regressione Icarus completa** (`python3 tools/run_regression.py`): 44 testbench, **43
|
||||
PASS**, 0 FAIL/ERROR, 1 BENCHMARK (nessun verdetto per progetto, invariato). Nessuna
|
||||
regressione sui 37 test già certificati pre-fix.
|
||||
- **Sintesi reale** (`yosys synth_ecp5`, sistema completo `spi_neuron_top` con sottosistema
|
||||
flash, PARALLEL=8, stessi file/comando già validati in Fase 15/F7 di WORKLOG.md): **0
|
||||
problemi CHECK**, 1 warning atteso/preesistente (tri-state limitato in
|
||||
`psram_controller.v`, invariato). TRELLIS_FF: 4900 (era 4855 — +45, coerente con la nuova
|
||||
logica di guard/controllo introdotta dai fix, nessuna crescita anomala).
|
||||
- **Place&route reale** (`nextpnr-ecp5 --45k --package CABGA381 --speed 8 --freq 80
|
||||
--lpf synth/ecp5/spi_neuron_top.lpf`): **0 errori di vincolo, 0 pin non vincolati,
|
||||
"Program finished normally"**. Fmax: **68.65 MHz** (era 67.91 MHz — leggermente meglio,
|
||||
entro il rumore di piazzamento già documentato in Fase 7/WORKLOG.md, non una regressione).
|
||||
Percorso critico verificato esplicitamente **strutturalmente identico** a prima del fix:
|
||||
`u_neuron_memory.u_neuron.group_index` → `u_mac8` → catena di riporto CCU2C
|
||||
dell'accumulatore (`rtl/mac8.v`) — nessun modulo toccato dai fix (che sono tutti
|
||||
aggiunte al percorso di controllo, non al datapath MAC/accumulatore) compare nel
|
||||
percorso critico. Margine sull'oscillatore reale 16MHz: 4.29× (invariato).
|
||||
Log: `synth/ecp5/post_fix_verify/yosys.log`, `synth/ecp5/post_fix_verify/nextpnr.log`.
|
||||
|
||||
---
|
||||
|
||||
## Non-bug (falsi positivi trovati e chiusi durante l'analisi)
|
||||
|
||||
Voci che sono sembrate anomalie a un primo controllo automatico ma si sono rivelate corrette
|
||||
per progetto una volta letto il codice/intento — riportate per trasparenza sul processo, non
|
||||
perché siano difetti.
|
||||
|
||||
- **`neuron_parallel_guard_negative_{degenerate,nonmultiple}_tb.v` "falliscono a compilare"**:
|
||||
comportamento corretto e intenzionale (test negativi, la mancata compilazione è il PASS).
|
||||
Vedi `docs/validation/00-inventario.md` §0.5.
|
||||
- **`graph_engine_bandwidth_tb.v` "nessun verdetto PASS/FAIL"**: è un benchmark per
|
||||
progetto, non un test di correttezza. Vedi §0.3/§0.5 dell'inventario.
|
||||
- **C.1 (falso "hang" iniziale, `neuron_parallel` config nota-buona)**: controllo tardivo e
|
||||
singolo di `done` (impulso di un solo ciclo) in uno script bespoke — non un problema
|
||||
dell'RTL. Vedi `docs/validation/01-datapath.md` §1.4.
|
||||
- **C.2 (falso "hang" per `n_inputs_real=17`)**: stesso tipo di errore in un secondo script
|
||||
bespoke diverso da quello già provato — corretto riusando lo schema affidabile. Vedi
|
||||
`docs/validation/02-runtime-width.md` §2.2.
|
||||
- **C.3 (falsi mismatch su 2048 controlli + un falso fallimento di round-trip)**: nel nuovo
|
||||
`sim/int8_memory_access_bytelane_tb.v`, un controllo dei segnali nello stesso passo di
|
||||
simulazione del loro aggiornamento non-bloccante (leggeva il valore dell'iterazione
|
||||
precedente), e uno stub di memoria comportamentale che ignorava le byte-lane
|
||||
`mem_lb_n`/`mem_ub_n` durante la scrittura. Entrambi difetti della testbench, non
|
||||
dell'RTL — vedi `docs/validation/03-memoria.md` §3.1.
|
||||
- **C.4 (`mem_arbiter` mai concedeva nulla nella mia prima testbench)**: assegnazioni
|
||||
bloccanti per ritirare le richieste dei "perdenti" nello stesso fronte di clock che
|
||||
doveva concedere la richiesta — race reale con il blocco sincrono del DUT. Diagnosticato
|
||||
con `dut.owner` mai uscito da `SEL_NONE`. Corretto passando ad assegnazioni non bloccanti.
|
||||
Vedi `docs/validation/04-arbiter.md` §4.1.
|
||||
Reference in New Issue
Block a user