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:
@@ -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.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.
|
||||
@@ -0,0 +1,162 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,163 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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).
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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) |
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 |
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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) |
|
||||
@@ -0,0 +1,34 @@
|
||||
# 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).
|
||||
@@ -0,0 +1,30 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,27 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,26 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,143 @@
|
||||
# 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).
|
||||
@@ -0,0 +1,122 @@
|
||||
# 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).
|
||||
@@ -0,0 +1,328 @@
|
||||
# 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