From 14c8d87194a57b1db6956a17cf4f30f1d63cc2b8 Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Fri, 4 Sep 2026 14:27:14 +0200 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v --- WORKLOG.md | 34 ++++ docs/validation/02-runtime-width.md | 163 ++++++++++++++++++ docs/validation/bugs.md | 57 ++++++ sim/int8_memory_access.vcd | 2 +- sim/int8_psram_integration.vcd | 2 +- sim/layer.vcd | 2 +- sim/layer_sequencer.vcd | 2 +- sim/memory_interface.vcd | 2 +- sim/neuron_memory.vcd | 2 +- ...ron_memory_bug004_n_neurons_real_zero_tb.v | 131 ++++++++++++++ sim/neuron_memory_multi.vcd | 2 +- sim/neuron_parallel.vcd | 2 +- ...on_parallel_bug003_n_inputs_real_zero_tb.v | 135 +++++++++++++++ sim/parameter_sweep.vcd | 2 +- sim/parametric.vcd | 2 +- sim/psram_controller.vcd | 2 +- sim/psram_page_mode.vcd | 2 +- sim/spi_engine.vcd | 2 +- sim/spi_neuron_top.vcd | 2 +- sim/spi_neuron_top_graph.vcd | 2 +- sim/spi_neuron_top_runnetwork.vcd | 2 +- sim/spi_slave.vcd | 2 +- 22 files changed, 537 insertions(+), 17 deletions(-) create mode 100644 docs/validation/02-runtime-width.md create mode 100644 sim/neuron_memory_bug004_n_neurons_real_zero_tb.v create mode 100644 sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v diff --git a/WORKLOG.md b/WORKLOG.md index d3afce5..8b24cf3 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -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 stesso rischio di BUG-002 "per responsabilità del chiamante" — da verificare se questo 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`). diff --git a/docs/validation/02-runtime-width.md b/docs/validation/02-runtime-width.md new file mode 100644 index 0000000..b6c6ef2 --- /dev/null +++ b/docs/validation/02-runtime-width.md @@ -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. diff --git a/docs/validation/bugs.md b/docs/validation/bugs.md index 93e46ad..becefee 100644 --- a/docs/validation/bugs.md +++ b/docs/validation/bugs.md @@ -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)`. - **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 diff --git a/sim/int8_memory_access.vcd b/sim/int8_memory_access.vcd index 5e45fca..98fc464 100644 --- a/sim/int8_memory_access.vcd +++ b/sim/int8_memory_access.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:12 2026 + Fri Sep 4 14:25:57 2026 $end $version Icarus Verilog diff --git a/sim/int8_psram_integration.vcd b/sim/int8_psram_integration.vcd index 9a6d70c..7bc5428 100644 --- a/sim/int8_psram_integration.vcd +++ b/sim/int8_psram_integration.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:16 2026 + Fri Sep 4 14:26:01 2026 $end $version Icarus Verilog diff --git a/sim/layer.vcd b/sim/layer.vcd index 9177e2c..57e9780 100644 --- a/sim/layer.vcd +++ b/sim/layer.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:17 2026 + Fri Sep 4 14:26:01 2026 $end $version Icarus Verilog diff --git a/sim/layer_sequencer.vcd b/sim/layer_sequencer.vcd index a11ebd1..1112cbf 100644 --- a/sim/layer_sequencer.vcd +++ b/sim/layer_sequencer.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:17 2026 + Fri Sep 4 14:26:01 2026 $end $version Icarus Verilog diff --git a/sim/memory_interface.vcd b/sim/memory_interface.vcd index e1b2486..90c567e 100644 --- a/sim/memory_interface.vcd +++ b/sim/memory_interface.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:17 2026 + Fri Sep 4 14:26:02 2026 $end $version Icarus Verilog diff --git a/sim/neuron_memory.vcd b/sim/neuron_memory.vcd index ff039de..48e9888 100644 --- a/sim/neuron_memory.vcd +++ b/sim/neuron_memory.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:25 2026 + Fri Sep 4 14:26:10 2026 $end $version Icarus Verilog diff --git a/sim/neuron_memory_bug004_n_neurons_real_zero_tb.v b/sim/neuron_memory_bug004_n_neurons_real_zero_tb.v new file mode 100644 index 0000000..766c2c2 --- /dev/null +++ b/sim/neuron_memory_bug004_n_neurons_real_zero_tb.v @@ -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 diff --git a/sim/neuron_memory_multi.vcd b/sim/neuron_memory_multi.vcd index b98b411..f9810e1 100644 --- a/sim/neuron_memory_multi.vcd +++ b/sim/neuron_memory_multi.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:21 2026 + Fri Sep 4 14:26:06 2026 $end $version Icarus Verilog diff --git a/sim/neuron_parallel.vcd b/sim/neuron_parallel.vcd index edec9ae..c8231a4 100644 --- a/sim/neuron_parallel.vcd +++ b/sim/neuron_parallel.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:25 2026 + Fri Sep 4 14:26:10 2026 $end $version Icarus Verilog diff --git a/sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v b/sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v new file mode 100644 index 0000000..215aade --- /dev/null +++ b/sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v @@ -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 diff --git a/sim/parameter_sweep.vcd b/sim/parameter_sweep.vcd index cddea43..3a73cd7 100644 --- a/sim/parameter_sweep.vcd +++ b/sim/parameter_sweep.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:25 2026 + Fri Sep 4 14:26:10 2026 $end $version Icarus Verilog diff --git a/sim/parametric.vcd b/sim/parametric.vcd index 2fabb29..8858a0e 100644 --- a/sim/parametric.vcd +++ b/sim/parametric.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:25 2026 + Fri Sep 4 14:26:10 2026 $end $version Icarus Verilog diff --git a/sim/psram_controller.vcd b/sim/psram_controller.vcd index 7007fcf..d6a3f2d 100644 --- a/sim/psram_controller.vcd +++ b/sim/psram_controller.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:30 2026 + Fri Sep 4 14:26:15 2026 $end $version Icarus Verilog diff --git a/sim/psram_page_mode.vcd b/sim/psram_page_mode.vcd index 7888755..4788142 100644 --- a/sim/psram_page_mode.vcd +++ b/sim/psram_page_mode.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:30 2026 + Fri Sep 4 14:26:15 2026 $end $version Icarus Verilog diff --git a/sim/spi_engine.vcd b/sim/spi_engine.vcd index 7d43a55..48f1506 100644 --- a/sim/spi_engine.vcd +++ b/sim/spi_engine.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:30 2026 + Fri Sep 4 14:26:15 2026 $end $version Icarus Verilog diff --git a/sim/spi_neuron_top.vcd b/sim/spi_neuron_top.vcd index ff98760..7b6b440 100644 --- a/sim/spi_neuron_top.vcd +++ b/sim/spi_neuron_top.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:57 2026 + Fri Sep 4 14:26:42 2026 $end $version Icarus Verilog diff --git a/sim/spi_neuron_top_graph.vcd b/sim/spi_neuron_top_graph.vcd index d8ee447..3dd93cc 100644 --- a/sim/spi_neuron_top_graph.vcd +++ b/sim/spi_neuron_top_graph.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:41 2026 + Fri Sep 4 14:26:26 2026 $end $version Icarus Verilog diff --git a/sim/spi_neuron_top_runnetwork.vcd b/sim/spi_neuron_top_runnetwork.vcd index 6e42ab9..8cd601f 100644 --- a/sim/spi_neuron_top_runnetwork.vcd +++ b/sim/spi_neuron_top_runnetwork.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:52 2026 + Fri Sep 4 14:26:36 2026 $end $version Icarus Verilog diff --git a/sim/spi_slave.vcd b/sim/spi_slave.vcd index 0e4cd7c..8c8d28d 100644 --- a/sim/spi_slave.vcd +++ b/sim/spi_slave.vcd @@ -1,5 +1,5 @@ $date - Fri Sep 4 13:40:58 2026 + Fri Sep 4 14:26:43 2026 $end $version Icarus Verilog