Files
FPGA-Neural/docs/validation/02-runtime-width.md
T
micheleandClaude Sonnet 5 14c8d87194 test: certify runtime width early-termination (C.2), document BUG-003/004
n_inputs_real/n_neurons_real early termination for valid values is
certified real: a "poison" region (data that would saturate the result
if read past the claimed limit) confirms no over-read, cycle counts
scale proportionally. n_inputs_real non-multiple-of-PARALLEL at runtime
matches the documented silent-truncation risk exactly.

n_inputs_real=0 / n_neurons_real=0 (BUG-003/004): confirmed incorrect
behavior in every repetition, but the exact triggering mechanism was
NOT fully isolated -- nearly-identical repeated tests produced
different symptoms (clean hang vs. silently processing the full build
width vs. a third cycle count matching neither). Reported in full,
including the inconsistency itself, rather than picking the cleanest
result. The two new permanent testbenches reflect this honestly: the
solid early-termination checks are hard assertions, the n_*_real=0
probe is deliberately observe-only given the non-deterministic result.

Full regression: 38/38 real tests pass.

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

164 lines
9.7 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.
# 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.