Files
micheleandClaude Sonnet 5 dc0b331d3e 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
2026-09-05 14:06:53 +02:00

204 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.