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
10 KiB
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.vsono test negativi dichiarati: il loro header dice esplicitamente "This file must FAIL TO COMPILE/ELABORATE. That failure is the test" — verificano che il guardN_INPUTS % PARALLEL != 0dirtl/neuron_parallel.v:71-72blocchi 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:
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.pdfedocs/FPGANeuralDatasheetEN.pdf: comparsi come file non tracciati, non generati da alcun processo di build di questo repo (il datasheet LaTeX vive inDataSheet/, 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.