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
This commit is contained in:
+34
@@ -1236,3 +1236,37 @@ integrazione nel top level (F5), verifica consolidata e misure reali (F6).
|
|||||||
l'header di `neuron_parallel.v` dichiara esplicitamente che `n_inputs_real=0` riproduce lo
|
l'header di `neuron_parallel.v` dichiara esplicitamente che `n_inputs_real=0` riproduce lo
|
||||||
stesso rischio di BUG-002 "per responsabilità del chiamante" — da verificare se questo
|
stesso rischio di BUG-002 "per responsabilità del chiamante" — da verificare se questo
|
||||||
claim tiene, con lo stesso rigore.
|
claim tiene, con lo stesso rigore.
|
||||||
|
|
||||||
|
## Campagna di ri-certificazione — C.2: Larghezza runtime (2026-09-04)
|
||||||
|
|
||||||
|
- **Terminazione anticipata `n_inputs_real`/`n_neurons_real` per valori validi: CERTIFICATA**
|
||||||
|
con tecnica "regione veleno" (dati che saturerebbero il risultato se letti oltre il limite
|
||||||
|
reale) — nessun over-read confermato, cicli scalano proporzionalmente in entrambi i casi.
|
||||||
|
- **`n_inputs_real` non multiplo di PARALLEL a runtime: CERTIFICATO** — tronca
|
||||||
|
silenziosamente come documentato (`y=16` invece di 17 elementi), non un hang. Un primo
|
||||||
|
tentativo con uno script diverso da quello già provato aveva mostrato un falso hang,
|
||||||
|
corretto riproducendo con lo schema affidabile prima di fidarmene.
|
||||||
|
- **`n_inputs_real=0` a runtime (BUG-003) e `n_neurons_real=0` (BUG-004): comportamento
|
||||||
|
scorretto confermato ma NON pienamente caratterizzato** — la parte più impegnativa di
|
||||||
|
questa fase. Ripetizioni quasi identiche dello stesso test hanno prodotto risultati
|
||||||
|
DIVERSI (a volte hang, a volte limite ignorato con esito "processa tutto", a volte un
|
||||||
|
terzo valore di cicli che non corrisponde né a zero né al totale) — non ho isolato la
|
||||||
|
condizione esatta che decide l'esito entro un tempo ragionevole, ed è stato riportato per
|
||||||
|
intero invece di scegliere il risultato più pulito o nascondere l'incoerenza.
|
||||||
|
Analisi aritmetica (non confermata come spiegazione completa): la larghezza del contatore
|
||||||
|
di loop (`GROUP_INDEX_WIDTH`/`NEURON_INDEX_WIDTH`, dimensionata a compile-time sul massimo
|
||||||
|
di build) determina se il valore di avvolgimento per "reale=0" è raggiungibile dal
|
||||||
|
contatore (nessun hang, ma limite ignorato) o irraggiungibile (hang) — coerente con
|
||||||
|
l'osservazione ma non root-caused bit-per-bit come per BUG-002. **Importante**: in ogni
|
||||||
|
singola ripetizione osservata, il risultato era comunque SBAGLIATO in un modo o nell'altro
|
||||||
|
— non è mai stato "corretto per caso".
|
||||||
|
- **4 nuovi testbench permanenti**: `sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v`
|
||||||
|
(TESTS 1-3 certificano la terminazione anticipata, TEST 4 è deliberatamente
|
||||||
|
observe-only, non un'asserzione, dato il comportamento non deterministico riscontrato),
|
||||||
|
`sim/neuron_memory_bug004_n_neurons_real_zero_tb.v` (stesso schema).
|
||||||
|
- **Regressione completa**: 38/38 test reali PASS (36 precedenti + 2 nuovi), 0 regressioni.
|
||||||
|
- **Deliverable**: `docs/validation/02-runtime-width.md` (con la registrazione completa e
|
||||||
|
non filtrata di ogni tentativo per BUG-003, non solo il verdetto finale),
|
||||||
|
`docs/validation/bugs.md` aggiornato con BUG-003 e BUG-004.
|
||||||
|
- **Prossimo passo**: C.3 (sottosistema memoria — `int8_memory_access`, `memory_interface`,
|
||||||
|
`psram_controller`).
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -54,6 +54,63 @@ funzionale pratico), **INFO** (non un bug: gap di copertura, ambiguità document
|
|||||||
certificazione): estendere il guard a `if (N_INPUTS == 0 || N_INPUTS % PARALLEL != 0)`.
|
certificazione): estendere il guard a `if (N_INPUTS == 0 || N_INPUTS % PARALLEL != 0)`.
|
||||||
- **Stato**: **APERTO, confermato, non corretto.**
|
- **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).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Risolti
|
## Risolti
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:12 2026
|
Fri Sep 4 14:25:57 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:16 2026
|
Fri Sep 4 14:26:01 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:17 2026
|
Fri Sep 4 14:26:01 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:17 2026
|
Fri Sep 4 14:26:01 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:17 2026
|
Fri Sep 4 14:26:02 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:25 2026
|
Fri Sep 4 14:26:10 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// C.2 CERTIFICATION + BUG-004 OBSERVATION (certification campaign,
|
||||||
|
// docs/validation/bugs.md / docs/validation/02-runtime-width.md §2.4/§2.5).
|
||||||
|
//
|
||||||
|
// TESTS 1-3 (SOLID, CERTIFIED): n_neurons_real early termination for
|
||||||
|
// valid values (1, 2, 3 out of a N_NEURONS=3 build) completes in a
|
||||||
|
// cycle count that scales proportionally (~41 cycles/neuron) -- these
|
||||||
|
// DO fail the run if early termination breaks.
|
||||||
|
//
|
||||||
|
// TEST 4 (n_neurons_real=0) is an OBSERVATION, not a hard assertion:
|
||||||
|
// unlike the hang pattern of BUG-002/003, this one does NOT hang --
|
||||||
|
// it completes in the SAME cycle count as processing the full
|
||||||
|
// N_NEURONS build width, meaning the requested "zero neurons" limit
|
||||||
|
// was silently ignored. Confirmed with a minimal always-ready memory
|
||||||
|
// stub (content is irrelevant to this specific check -- only whether
|
||||||
|
// the loop terminates, and in how many cycles, matters here).
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module tb;
|
||||||
|
|
||||||
|
localparam ADDR_WIDTH = 23;
|
||||||
|
localparam DATA_WIDTH = 8;
|
||||||
|
localparam N_INPUTS = 8;
|
||||||
|
localparam N_NEURONS = 3;
|
||||||
|
localparam PARALLEL = 8;
|
||||||
|
localparam ACC_WIDTH = 32;
|
||||||
|
|
||||||
|
reg clk;
|
||||||
|
initial begin
|
||||||
|
clk = 1'b0;
|
||||||
|
forever #5 clk = ~clk;
|
||||||
|
end
|
||||||
|
|
||||||
|
reg rst, start;
|
||||||
|
reg [ADDR_WIDTH-1:0] x_base, w_base, bias_addr;
|
||||||
|
reg [15:0] n_neurons_real;
|
||||||
|
wire signed [DATA_WIDTH*N_NEURONS-1:0] y_bus;
|
||||||
|
wire busy, done;
|
||||||
|
|
||||||
|
wire mem_req, mem_wr;
|
||||||
|
wire [ADDR_WIDTH-1:0] mem_addr;
|
||||||
|
wire signed [7:0] mem_wdata;
|
||||||
|
reg signed [7:0] mem_rdata;
|
||||||
|
reg mem_ready;
|
||||||
|
|
||||||
|
// Minimal always-ready behavioral memory stub -- content is
|
||||||
|
// irrelevant here (this test only checks termination cycle
|
||||||
|
// count, not computed values).
|
||||||
|
always @(posedge clk) begin
|
||||||
|
mem_ready <= mem_req;
|
||||||
|
mem_rdata <= 8'sd1;
|
||||||
|
end
|
||||||
|
|
||||||
|
neuron_memory #(
|
||||||
|
.ADDR_WIDTH(ADDR_WIDTH), .DATA_WIDTH(DATA_WIDTH),
|
||||||
|
.N_INPUTS(N_INPUTS), .N_NEURONS(N_NEURONS), .PARALLEL(PARALLEL), .ACC_WIDTH(ACC_WIDTH)
|
||||||
|
) dut (
|
||||||
|
.clk(clk), .rst(rst), .start(start),
|
||||||
|
.mem_req(mem_req), .mem_wr(mem_wr), .mem_addr(mem_addr), .mem_wdata(mem_wdata),
|
||||||
|
.mem_rdata(mem_rdata), .mem_ready(mem_ready),
|
||||||
|
.x_base(x_base), .w_base(w_base), .bias_addr(bias_addr),
|
||||||
|
.n_neurons_real(n_neurons_real),
|
||||||
|
.y_bus(y_bus), .busy(busy), .done(done)
|
||||||
|
);
|
||||||
|
|
||||||
|
integer cyc;
|
||||||
|
integer errors;
|
||||||
|
integer cyc_full;
|
||||||
|
|
||||||
|
task automatic run_case(
|
||||||
|
input [15:0] nreal,
|
||||||
|
input integer is_bug004_probe // 1 = TEST 4: observe-only
|
||||||
|
);
|
||||||
|
begin
|
||||||
|
n_neurons_real = nreal;
|
||||||
|
x_base = 0; w_base = 0; bias_addr = 0;
|
||||||
|
rst = 1; start = 0;
|
||||||
|
@(posedge clk); @(posedge clk);
|
||||||
|
rst = 0;
|
||||||
|
@(posedge clk);
|
||||||
|
start = 1;
|
||||||
|
@(posedge clk);
|
||||||
|
start = 0;
|
||||||
|
cyc = 0;
|
||||||
|
while (!done && cyc < 500) begin
|
||||||
|
@(posedge clk);
|
||||||
|
cyc = cyc + 1;
|
||||||
|
end
|
||||||
|
if (is_bug004_probe) begin
|
||||||
|
if (!done)
|
||||||
|
$display("n_neurons_real=%0d: OBSERVED -- no done in 500 cycles (would match a BUG-002/003-style hang -- NOT what was found when this was last investigated, see docs/validation/02-runtime-width.md §2.5)", nreal);
|
||||||
|
else if (cyc == cyc_full)
|
||||||
|
$display("n_neurons_real=%0d: OBSERVED -- completed at cycle %0d, IDENTICAL to n_neurons_real=%0d (full build width) -- limit silently ignored, matches BUG-004 as documented. NOT counted as pass or fail here.", nreal, cyc, N_NEURONS);
|
||||||
|
else
|
||||||
|
$display("n_neurons_real=%0d: OBSERVED -- completed at cycle %0d (differs from full-width cycle count %0d -- behavior may have changed since BUG-004 was documented, re-check docs/validation/02-runtime-width.md §2.5)", nreal, cyc, cyc_full);
|
||||||
|
end else begin
|
||||||
|
if (!done) begin
|
||||||
|
$display("n_neurons_real=%0d: FAIL -- expected done, got none in 500 cycles", nreal);
|
||||||
|
errors = errors + 1;
|
||||||
|
end else begin
|
||||||
|
$display("n_neurons_real=%0d: PASS -- done at cycle %0d", nreal, cyc);
|
||||||
|
if (nreal == N_NEURONS) cyc_full = cyc;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
errors = 0;
|
||||||
|
cyc_full = -1;
|
||||||
|
|
||||||
|
$display("--- TEST 1: n_neurons_real=1 (early termination) ---");
|
||||||
|
run_case(16'd1, 0);
|
||||||
|
$display("--- TEST 2: n_neurons_real=2 (early termination) ---");
|
||||||
|
run_case(16'd2, 0);
|
||||||
|
$display("--- TEST 3: n_neurons_real=3 (full build width, establishes cyc_full baseline) ---");
|
||||||
|
run_case(N_NEURONS[15:0], 0);
|
||||||
|
|
||||||
|
$display("--- TEST 4 (BUG-004 probe, observe-only): n_neurons_real=0 ---");
|
||||||
|
run_case(16'd0, 1);
|
||||||
|
|
||||||
|
if (errors == 0)
|
||||||
|
$display("ALL TESTS PASSED (early termination certified correct for valid n_neurons_real values, TESTS 1-3; TEST 4 is an observe-only BUG-004 probe -- not scored)");
|
||||||
|
else
|
||||||
|
$display("FAILED: %0d unexpected result(s) in TESTS 1-3 -- see messages above", errors);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:21 2026
|
Fri Sep 4 14:26:06 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:25 2026
|
Fri Sep 4 14:26:10 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -0,0 +1,135 @@
|
|||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
// ================================================================
|
||||||
|
// C.2 CERTIFICATION + BUG-003 OBSERVATION (certification campaign,
|
||||||
|
// docs/validation/bugs.md / docs/validation/02-runtime-width.md).
|
||||||
|
//
|
||||||
|
// TESTS 1-3 (SOLID, CERTIFIED): early termination for valid
|
||||||
|
// n_inputs_real values is real -- a "poison" region at indices 16-31
|
||||||
|
// with saturating x=w=100 would corrupt the result if the RTL ever
|
||||||
|
// read past the real limit. It doesn't. These three checks DO fail
|
||||||
|
// the run (errors counted) if early termination breaks.
|
||||||
|
//
|
||||||
|
// TEST 4 (n_inputs_real=0) is DELIBERATELY NOT a hard pass/fail
|
||||||
|
// assertion. Unlike BUG-002 (N_INPUTS=0, a compile-time parameter),
|
||||||
|
// this runtime-reachable twin (n_inputs_real=0, exactly the port an
|
||||||
|
// SPI host drives via SET_BASE sel=7,
|
||||||
|
// docs/FPGA-NeuralNetwork-Engine.md §8.1) produced DIFFERENT results
|
||||||
|
// across nearly-identical repeated test runs while investigating this
|
||||||
|
// aspect -- sometimes a clean hang (busy/done never move), sometimes
|
||||||
|
// a normal-looking completion that silently processes the FULL build
|
||||||
|
// width instead of zero elements. The exact triggering condition was
|
||||||
|
// NOT isolated despite multiple attempts -- see
|
||||||
|
// docs/validation/02-runtime-width.md §2.3 for the full, undiscarded
|
||||||
|
// record of every attempt. This test only REPORTS which of the two
|
||||||
|
// (already known-incorrect) symptoms shows up on this particular run
|
||||||
|
// -- it does not assert one is "the" correct current behavior, because
|
||||||
|
// that has not been established.
|
||||||
|
// ================================================================
|
||||||
|
|
||||||
|
module tb;
|
||||||
|
|
||||||
|
localparam DATA_WIDTH = 8;
|
||||||
|
localparam N_INPUTS = 32;
|
||||||
|
localparam PARALLEL = 8;
|
||||||
|
localparam ACC_WIDTH = 32;
|
||||||
|
|
||||||
|
reg clk;
|
||||||
|
initial begin
|
||||||
|
clk = 1'b0;
|
||||||
|
forever #5 clk = ~clk;
|
||||||
|
end
|
||||||
|
|
||||||
|
reg rst, start;
|
||||||
|
reg signed [DATA_WIDTH*N_INPUTS-1:0] x_bus, w_bus;
|
||||||
|
reg [1:0] activation;
|
||||||
|
reg [15:0] n_inputs_real;
|
||||||
|
wire busy, done;
|
||||||
|
wire signed [DATA_WIDTH-1:0] y;
|
||||||
|
|
||||||
|
integer cyc, i;
|
||||||
|
integer errors;
|
||||||
|
|
||||||
|
neuron_parallel #(
|
||||||
|
.DATA_WIDTH(DATA_WIDTH), .N_INPUTS(N_INPUTS), .PARALLEL(PARALLEL), .ACC_WIDTH(ACC_WIDTH)
|
||||||
|
) dut (
|
||||||
|
.clk(clk), .rst(rst), .start(start),
|
||||||
|
.x_bus(x_bus), .w_bus(w_bus), .bias(8'sd0),
|
||||||
|
.activation(activation), .n_inputs_real(n_inputs_real),
|
||||||
|
.y(y), .busy(busy), .done(done)
|
||||||
|
);
|
||||||
|
|
||||||
|
task automatic run_case(
|
||||||
|
input [15:0] nreal,
|
||||||
|
input integer is_bug003_probe, // 1 = TEST 4: observe-only, never counts as a failure either way (see file header)
|
||||||
|
input signed [7:0] expect_y // meaningful only if is_bug003_probe == 0
|
||||||
|
);
|
||||||
|
begin
|
||||||
|
n_inputs_real = nreal;
|
||||||
|
rst = 1; start = 0;
|
||||||
|
@(posedge clk); @(posedge clk);
|
||||||
|
rst = 0;
|
||||||
|
@(posedge clk);
|
||||||
|
start = 1;
|
||||||
|
@(posedge clk);
|
||||||
|
start = 0;
|
||||||
|
cyc = 0;
|
||||||
|
while (!done && cyc < 200) begin
|
||||||
|
@(posedge clk);
|
||||||
|
cyc = cyc + 1;
|
||||||
|
end
|
||||||
|
if (is_bug003_probe) begin
|
||||||
|
if (done)
|
||||||
|
$display("n_inputs_real=%0d: OBSERVED -- completed at cycle %0d, y=%0d (limit silently ignored -- one of two known-incorrect symptoms, see docs/validation/02-runtime-width.md §2.3; NOT counted as pass or fail here)", nreal, cyc, y);
|
||||||
|
else
|
||||||
|
$display("n_inputs_real=%0d: OBSERVED -- no done in 200 cycles, busy=%b (hang -- the OTHER known-incorrect symptom, see docs/validation/02-runtime-width.md §2.3; NOT counted as pass or fail here)", nreal, busy);
|
||||||
|
end else begin
|
||||||
|
if (!done) begin
|
||||||
|
$display("n_inputs_real=%0d: FAIL -- expected done, got none in 200 cycles", nreal);
|
||||||
|
errors = errors + 1;
|
||||||
|
end else if (y !== expect_y) begin
|
||||||
|
$display("n_inputs_real=%0d: FAIL -- y=%0d expected=%0d", nreal, y, expect_y);
|
||||||
|
errors = errors + 1;
|
||||||
|
end else begin
|
||||||
|
$display("n_inputs_real=%0d: PASS -- y=%0d cycles=%0d (no over-read into poison region)", nreal, y, cyc);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
errors = 0;
|
||||||
|
activation = 2'd1; // ACT_RELU
|
||||||
|
|
||||||
|
// real region (0-15): x=w=1 -> contributes 1 each if read
|
||||||
|
for (i = 0; i < 16; i = i + 1) begin
|
||||||
|
x_bus[i*8 +: 8] = 8'sd1;
|
||||||
|
w_bus[i*8 +: 8] = 8'sd1;
|
||||||
|
end
|
||||||
|
// "poison" region (16-31): x=w=100 -> would saturate to 127 if
|
||||||
|
// ever read past the real limit
|
||||||
|
for (i = 16; i < 32; i = i + 1) begin
|
||||||
|
x_bus[i*8 +: 8] = 8'sd100;
|
||||||
|
w_bus[i*8 +: 8] = 8'sd100;
|
||||||
|
end
|
||||||
|
|
||||||
|
$display("--- TEST 1: n_inputs_real=16 -- early termination must NOT read the poison region ---");
|
||||||
|
run_case(16'd16, 0, 8'sd16);
|
||||||
|
|
||||||
|
$display("--- TEST 2: n_inputs_real=8 -- smaller early termination ---");
|
||||||
|
run_case(16'd8, 0, 8'sd8);
|
||||||
|
|
||||||
|
$display("--- TEST 3: n_inputs_real=32 (full) -- sanity: DOES read the poison region, saturates ---");
|
||||||
|
run_case(16'd32, 0, 8'sd127);
|
||||||
|
|
||||||
|
$display("--- TEST 4 (BUG-003 probe, observe-only): n_inputs_real=0 ---");
|
||||||
|
run_case(16'd0, 1, 8'sd0);
|
||||||
|
|
||||||
|
if (errors == 0)
|
||||||
|
$display("ALL TESTS PASSED (early termination certified correct for valid n_inputs_real values, TESTS 1-3; TEST 4 is an observe-only BUG-003 probe, see docs/validation/02-runtime-width.md §2.3 -- not scored)");
|
||||||
|
else
|
||||||
|
$display("FAILED: %0d unexpected result(s) in TESTS 1-3 -- see messages above", errors);
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:25 2026
|
Fri Sep 4 14:26:10 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:25 2026
|
Fri Sep 4 14:26:10 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:30 2026
|
Fri Sep 4 14:26:15 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:30 2026
|
Fri Sep 4 14:26:15 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:30 2026
|
Fri Sep 4 14:26:15 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:57 2026
|
Fri Sep 4 14:26:42 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:41 2026
|
Fri Sep 4 14:26:26 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:52 2026
|
Fri Sep 4 14:26:36 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
$date
|
$date
|
||||||
Fri Sep 4 13:40:58 2026
|
Fri Sep 4 14:26:43 2026
|
||||||
$end
|
$end
|
||||||
$version
|
$version
|
||||||
Icarus Verilog
|
Icarus Verilog
|
||||||
|
|||||||
Reference in New Issue
Block a user