Builds tools/run_regression.py (no reproducible regression script existed before -- every prior "N testbenches, all pass" claim was assembled by hand). Resolves each testbench's dependencies by static analysis of instantiation sites, not from memory, then compiles+runs everything fresh. Independently re-verifies the 33-testbench regression clean (0 real failures) after fixing two false negatives in the harness itself (two tests are deliberate compile-time-failure negative tests, one file is a benchmark with no pass/fail verdict by design -- confirmed by reading each file's own header, not assumed). Findings recorded in docs/validation/: sim/top.v is dead code (references a removed FRAC_BITS parameter from the pre-INT8 Q8.8 era); mac_unit.v/ mac8.v have no dedicated unit testbench, only indirect coverage; the N_INPUTS%PARALLEL elaboration guard does not mathematically cover N_INPUTS=0 (open finding, not yet confirmed reachable -- BUG-002). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
204 lines
10 KiB
Markdown
204 lines
10 KiB
Markdown
# 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.
|