feat(v2): scaffold hardware/v1 frozen baseline + M1 Neural Processor

Begins the V2 Neural Multiprocessor / Dataflow architecture per
docs/v2-description.md, per explicit user request to freeze V1 and
start V2 development, copying from V1 what's needed.

Scaffold:
- hardware/v1/: byte-exact, read-only copy of the current V1 codebase
  (rtl, testbenches, tools, constraints, a representative subset of
  synthesis results, and reference docs) -- verified identical via
  diff/cmp against the live top-level tree before being made
  filesystem-read-only. The live top-level tree is untouched and
  remains the project's "production" V1 (see hardware/v1/README.md
  and hardware/v2/logs/decisions.log DEC-0001 for why copy-not-move).
- hardware/v2/: mandatory structure (rtl/sim/constraints/synthesis/
  reports/scripts/logs/docs) plus the full logging system required by
  the spec (development/architecture/simulation/synthesis/timing/
  benchmark/decisions/experiments/errors.log).

M1 -- Neural Processor (hardware/v2/rtl/neural_processor.v):
- 8-stage pipelined perceptron unit (P_IN=8): input align, 8
  multipliers, 3-level adder tree, accumulator, bias+activation, INT8
  saturation. Genuine 1-tile/cycle throughput, not just a wider
  combinational datapath.
- 7-state FSM (NP_IDLE..NP_ERROR per docs/v2-description.md §6, with
  4 baseline states merged into NP_WAIT_OPERANDS -- see
  decisions.log DEC-0002); valid/ready/data/last stream interfaces
  per §7.
- Bit-exact vs the frozen hardware/v1/rtl/neuron_parallel.v + mac8.v
  + mac_unit.v: 7/7 tests pass (hardware/v2/sim/tb_neural_processor.v),
  covering regular/mixed-sign/extreme-INT8 vectors, both activations,
  a zero-idle-gap back-to-back-tiles throughput check, and an 8-tile
  job -- verified with Verilator (see below for why).
- Real synthesis + place&route (Yosys + nextpnr-ecp5): 0 CHECK
  problems, Fmax 183.12 MHz at ACC_WIDTH=32 (PASS at 80MHz, ~3x V1's
  isolated PARALLEL=8 Fmax of 61.71 MHz) and 176.21 MHz at ACC_WIDTH=24
  (a user-requested comparison experiment, also bit-exact-verified;
  see experiments.log EXP-0001/EXP-0002 and benchmark.log).

Three real bugs found and resolved during M1 development (full
diagnostic record in errors.log):
- Two independent, reproducible Icarus Verilog v13.0 scheduling
  defects (ERR-0001, ERR-0002) that silently produced wrong simulation
  results for standard sequential Verilog -- confirmed via Verilator
  5.050 giving correct results on the same minimal repros. Verilator
  is now the trusted simulator for hardware/v2/ (decisions.log
  DEC-0004); Icarus's affected protocol-violation check was removed
  from the RTL and deferred architecturally to the Neural Director
  (DEC-0003) rather than chased further.
- One real RTL bug (ERR-0003): last0 wasn't gated like valid0,
  letting a "last tile" tag leak into the pipeline ahead of its
  actual valid tile on back-to-back jobs. Fixed and verified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-05 14:06:53 +02:00
co-authored by Claude Sonnet 5
parent 07a48e401f
commit dc0b331d3e
161 changed files with 1151210 additions and 0 deletions
@@ -0,0 +1,203 @@
# 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.1C.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.