Files
FPGA-Neural/docs/validation/bugs.md
T
micheleandClaude Sonnet 5 6db44efcfd test: certify graph_engine gather/guard (C.6), find related BUG-006
Gather/padding/src_id<out_id guard certified via existing solid
pre-session tests (graph_engine_tb.v checks act_buffer contents via
hierarchical reference, not just final output; graph_engine_guard_tb.v
covers 4 adversarial cases incl. recovery).

BUG-006 (LOW severity): num_neurons_graph=0 shares BUG-005's exact
root cause (neuron_idx is a full 16-bit register, no guard), but
graph_engine's existing per-edge src_id<out_id guard incidentally
catches most garbage-data patterns fast (err at cycle 58 for a
non-trivial test pattern, vs. layer_sequencer's 21761-cycle full run
in BUG-005) -- not a designed protection for this case, so not closed
as a non-issue, but lower severity given the observed practical risk.
Not run to full 65536-iteration completion (impractical for this
campaign's time budget) -- limitation stated explicitly.

Full regression: 40/40 real tests pass, 1 new observational test.

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

204 lines
14 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
### BUG-001 (INFO, non ancora classificato) — `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) — è dead code, non un difetto funzionale del design.
- **Stato**: aperto, non corretto per policy §E (analisi separata dalla correzione). Azione
proposta: rimuovere il file o aggiornarlo, decisione da confermare con l'utente.
### 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 proposto** (non applicato — analisi separata dalla correzione, §E del prompt di
certificazione): estendere il guard a `if (N_INPUTS == 0 || N_INPUTS % PARALLEL != 0)`.
- **Stato**: **APERTO, confermato, non corretto.**
### 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.
- **Stato**: **APERTO, confermato come comportamento scorretto in ogni caso osservato, ma
meccanismo esatto NON isolato** — dichiarato esplicitamente come limite di questa verifica
(§A.5), non presentato come pienamente compreso. Richiederebbe un'indagine dedicata
(probabilmente a livello gate/timing reale, non solo comportamentale) per chiudere con
certezza il meccanismo, non solo il sintomo.
### 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.
- **Stato**: **APERTO, confermato, causa esatta non isolata** (stesso limite dichiarato di
BUG-003).
### 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 proposto** (non applicato — analisi separata dalla correzione): guard a runtime in
`layer_sequencer.v` analogo a quello di `neuron_parallel.v`, es. rifiutare
`run_num_layers==0` prima di avviare la sequenza (riportando un errore osservabile invece
di procedere).
- **Stato**: **APERTO, confermato, causa isolata con certezza** (a differenza di
BUG-003/004, qui il meccanismo esatto è stato individuato precisamente, non solo il
sintomo).
### 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 osservata è incidentale, non
garantita per ogni possibile contenuto PSRAM. Il buco strutturale è reale.
- **Stato**: **APERTO**, severità inferiore a BUG-005 per la protezione incidentale
osservata, non pienamente verificato su ogni pattern di dati possibile.
---
## Risolti
(nessuno ancora in questa campagna — la Fase 0 è analisi/inventario, non correzione)
---
## 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.