Files
FPGA-Neural/hardware/v1/docs/validation/bugs.md
T
micheleandClaude Sonnet 5 dc0b331d3e feat(v2): scaffold hardware/v1 frozen baseline + M1 Neural Processor
Begins the V2 Neural Multiprocessor / Dataflow architecture per
docs/v2-description.md, per explicit user request to freeze V1 and
start V2 development, copying from V1 what's needed.

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

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

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
2026-09-05 14:06:53 +02:00

329 lines
24 KiB
Markdown
Raw Blame History

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