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

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

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

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

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

24 KiB
Raw Permalink Blame History

Registro bug — campagna di ri-certificazione FPGA-Neural

Formato per ogni voce: severità, sintomo, causa radice, evidenza (file:riga / comando/log citabile), stato, test di regressione che lo blocca (se risolto) o che lo riprodurrebbe (se aperto). Aggiornato incrementalmente man mano che avanzano gli aspetti C.1C.14.

Severità: CRITICA (corrompe dati/hang in scenari raggiungibili), MEDIA (comportamento scorretto in casi limite plausibili ma rari), BASSA (difetto reale ma senza impatto funzionale pratico), INFO (non un bug: gap di copertura, ambiguità documentale/naming).


Aperti

(nessuno — tutti i bug della campagna sono stati corretti e verificati, vedi "Risolti" sotto)


Risolti

BUG-001 (INFO) — sim/top.v non compila contro l'RTL corrente

  • Sintomo: iverilog fallisce con parameter FRAC_BITS not found in top.dut.
  • Causa radice: sim/top.v è un residuo della versione Q8.8 a virgola fissa del progetto, mai aggiornato dopo la conversione a INT8 puro (Fase 6, vedi docs/validation/00-inventario.md §0.2).
  • Evidenza: iverilog -g2012 -o /tmp/topcheck.out rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v sim/top.v → 2 errori di elaborazione.
  • Impatto: nessuno sulla regressione (il file non è referenziato da alcun testbench o tool) — era dead code, non un difetto funzionale del design.
  • Fix applicato: file rimosso (git rm sim/top.v) — confermato non referenziato da alcun testbench o tool (tools/run_regression.py lo esclude esplicitamente dal proprio elenco sorgenti anche prima della rimozione).
  • Stato: RISOLTO — file eliminato, nessun test di regressione necessario (non c'era comportamento da preservare).

BUG-002 (MEDIA, CONFERMATO su sim + sintesi reale) — N_INPUTS=0 bypassa il guard, start viene silenziosamente ignorato

  • Sintomo confermato (non più un'ipotesi — vedi docs/validation/01-datapath.md §1.4 per la narrativa completa, incl. un falso positivo iniziale nella mia stessa metodologia di test, corretto e ridocumentato per trasparenza): con neuron_parallel #(.N_INPUTS(0), .PARALLEL(P)), il guard elaboration-time (rtl/neuron_parallel.v:71, if (N_INPUTS % PARALLEL != 0)) non scatta (0 % P == 0 per ogni P), il modulo elabora con successo (sia in simulazione Icarus sia in sintesi reale Yosys, 0 problemi CHECK). A runtime: start viene accettato ma busy non si alza mai e done non pulsa mai — non l'hang "busy resta alto per sempre" descritto nel commento originale del guard (righe 55-58), un sintomo diverso, osservato per la prima volta in questa campagna.
  • Causa radice, confermata (non più ipotesi): x_bus/w_bus sono dichiarati [DATA_WIDTH*N_INPUTS-1:0], che per N_INPUTS=0 diventa [-1:0] — un range che non collassa a larghezza zero: sia Icarus sia Yosys lo trattano come un vettore reale a 2 bit (larghezza = |MSB-LSB|+1 = 2), lasciato non pilotato. Confermato dai warning di Yosys: Wire ...x_bus[1] is used but has no driver (×2, per x_bus e w_bus).
  • Evidenza:
    • iverilog -g2012 -o /tmp/n0proper.out rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v sim/neuron_parallel_bug002_n_inputs_zero_tb.v && vvp /tmp/n0proper.out → conferma il sintomo, ogni volta.
    • yosys -p "synth_ecp5 -json /tmp/n0.json -top n0_synth_wrap" rtl/neuron_parallel.v rtl/mac8.v rtl/mac_unit.v <wrapper>0 problemi CHECK, 4 warning "no driver" su x_bus/w_bus[1:0].
    • Test di regressione permanente: sim/neuron_parallel_bug002_n_inputs_zero_tb.v.
  • Impatto pratico: N_INPUTS è un parametro Verilog fissato in fase di sintesi (non un registro configurabile via SPI a runtime) — per essere raggiunto, qualcuno deve deliberatamente istanziare il modulo con N_INPUTS=0, cosa che non ha senso semantico per un layer reale. Rischio quindi basso in pratica (nessun percorso runtime/host-controllato può innescarlo), ma è un buco reale e confermato nella protezione, non solo teorico.
  • Fix applicato (rtl/neuron_parallel.v, guard di elaborazione): esteso a if (N_INPUTS == 0 || N_INPUTS % PARALLEL != 0)N_INPUTS=0 ora fa fallire l'elaborazione con lo stesso errore Unknown module type: neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL dei due test negativi già esistenti, invece di elaborare con successo e produrre start silenziosamente ignorato.
  • Test di regressione: sim/neuron_parallel_bug002_n_inputs_zero_tb.v, riscritto per asserire il fallimento di compilazione (stesso pattern di neuron_parallel_guard_negative_*_tb.v), aggiunto a EXPECTED_COMPILE_FAIL in tools/run_regression.py. Verificato: iverilog -g2012 -o /tmp/out rtl/neuron_parallel.v sim/neuron_parallel_bug002_n_inputs_zero_tb.v → errore di elaborazione atteso, exit code 3.
  • Stato: RISOLTO, verificato.

BUG-003 (MEDIA, CONFERMATO ma NON pienamente caratterizzato) — n_inputs_real=0 a runtime, comportamento incoerente tra ripetizioni

  • Sintomo: con N_INPUTS=32, PARALLEL=8 validi a compile-time (nessun problema di larghezza [-1:0], a differenza di BUG-002), impostando n_inputs_real=0 a runtime (lo stesso percorso raggiungibile dall'host via SET_BASE sel=7) il comportamento osservato varia tra ripetizioni quasi identiche dello stesso test: a volte start viene accettato ma busy/done non si muovono mai più (hang), a volte l'operazione completa normalmente ma processa l'INTERA larghezza di build invece di zero elementi (limite ignorato silenziosamente, stessa classe di BUG-004). Vedi docs/validation/ 02-runtime-width.md §2.3 per la registrazione completa di ogni singola ripetizione e dei suoi risultati, riportati senza scartare quelli "scomodi".
  • Causa radice: non isolata con certezza entro il tempo ragionevole per questa campagna. Analisi aritmetica plausibile (non confermata come spiegazione completa): per questa build GROUP_INDEX_WIDTH=2 bit, quindi groups_real[1:0]-1 per groups_real=0 avvolge al valore 3 (raggiungibile da un contatore a 2 bit, a differenza del contatore a 1 bit di BUG-002) — spiegherebbe l'esito "limite ignorato" come esito aritmeticamente atteso, ma non spiega perché in alcune ripetizioni compaia invece un hang vero. Esclusi esplicitamente: propagazione di X in simulazione (verificato inizializzando ogni registro prima di qualunque reset, il comportamento non cambia), e una dipendenza semplice dall'ordine delle chiamate (una sequenza valida→zero non blocca; una sequenza zero→zero→zero blocca dalla seconda chiamata in poi, non dalla prima — non un pattern semplice "prima volta sicura, poi no").
  • Impatto pratico: come BUG-002, n_inputs_real=0 non ha senso semantico per una rete reale, ma a differenza di BUG-002 questo valore è raggiungibile a runtime da un host via SPI senza bisogno di una nuova sintesi — un host con un bug che calcola erroneamente n_inputs_real=0 per un caso limite (es. un layer con zero neuroni in una topologia degenere) potrebbe innescarlo, con un esito imprevedibile tra hang e risultato silenziosamente sbagliato.
  • Fix applicato (rtl/neuron_parallel.v, dentro if (start && !busy)): finishing <= (n_inputs_real == 16'h0); (era finishing <= 0;) — riusa il percorso di completamento "finishing" già corretto ed esistente nel modulo invece di introdurre nuova logica per il caso degenere, stessa convenzione già usata altrove nel progetto. Per zero input reali il risultato matematico è y = activation(bias): con bias=0 e ACT_RELU nel test di regressione, expect_y=0.
  • Nota onestà: il meccanismo esatto per cui il comportamento pre-fix variava tra ripetizioni (hang vs. risultato sbagliato silenzioso) non è stato isolato bit-per-bit neppure in fase di correzione — il fix è un early-out esplicito che bypassa l'intero percorso ambiguo, verificato corretto e deterministico sul nuovo comportamento, non una spiegazione a posteriori del vecchio meccanismo.
  • Test di regressione: sim/neuron_parallel_bug003_n_inputs_real_zero_tb.v TEST 4, riscritto da osservazione ad asserzione hard (done entro 8 cicli, y===0). Verificato: n_inputs_real=0 completa in 1 ciclo, y=0 — TUTTI I TEST PASSED (incluse le TEST 1-3 di non-regressione sulla regione "poison").
  • Stato: RISOLTO, verificato (il meccanismo esatto del comportamento PRE-fix resta non isolato per intero, per trasparenza, ma non è più rilevante: il nuovo percorso è deterministico e verificato indipendentemente).

BUG-004 (MEDIA, CONFERMATO scorretto, NON pienamente caratterizzato) — n_neurons_real=0 non blocca, ma non fa nemmeno quello che ci si aspetterebbe in modo coerente

  • Sintomo: a rtl/neuron_memory.v, con n_neurons_real=0, l'operazione completa sempre normalmente (mai un hang, a differenza di BUG-002/003) — ma il numero di cicli impiegato non è coerente tra build diverse: per N_NEURONS=2 (NEURON_INDEX_WIDTH=1 bit) impiega esattamente lo stesso numero di cicli di n_neurons_real=2 (114=114, suggerendo che il limite venga ignorato e processi tutto), mentre per N_NEURONS=3 (NEURON_INDEX_WIDTH=2 bit) impiega 196 cicli — più della build completa a 3 neuroni (155), un terzo valore che non corrisponde né a "zero neuroni" né a "tutti i neuroni". In ogni caso testato: nessun errore, nessun timeout — un host che chiede zero neuroni riceve sempre un completamento dall'aspetto normale ma su un conteggio/dato diverso da quanto richiesto, e il conteggio esatto varia con N_NEURONS.
  • Causa radice: non isolata bit-per-bit (a differenza di BUG-002). Ipotesi coerente con BUG-003: l'aritmetica di avvolgimento (neuron_index == n_neurons_real[W-1:0]-1) per n_neurons_real=0 produce un valore di terminazione che, per coincidenza di larghezza, corrisponde al conteggio pieno invece che a "termina subito". Vedi docs/validation/02-runtime-width.md §2.5.
  • Impatto pratico: come BUG-002/003, richiede che l'host imposti deliberatamente (o per bug proprio) n_neurons_real=0 — non raggiungibile da un input esterno arbitrario, ma raggiungibile da un bug nel software host senza bisogno di ricompilare il bitstream.
  • Fix applicato (rtl/neuron_memory.v, tre punti coordinati, non uno solo): il pattern vulnerabile esisteva in TRE punti distinti, non uno — scoperto durante la correzione stessa (un primo tentativo di guard solo al dispatch STATE_IDLESTATE_READ_X è stato riconosciuto insufficiente perché copriva un solo dei tre punti d'ingresso nello stato vulnerabile STATE_READ_W, che viene rientrato indipendentemente una volta per neurone nel loop). Fix corretto (single-point-of-truth sulle CONDIZIONI di terminazione, non sui punti di dispatch): STATE_READ_X e STATE_READ_W guadagnano entrambe un prefisso n_inputs_real == 16'h0 ||/n_neurons_real == 16'h0 || sulla propria condizione di terminazione, e il punto di transizione X→W guadagna un ramo esplicito if (n_neurons_real == 16'h0) begin busy<=0; done<=1; state<=STATE_IDLE; end.
  • Test di regressione: sim/neuron_memory_bug004_n_neurons_real_zero_tb.v TEST 4, riscritto per asserire done entro il baseline a piena larghezza (cyc_full, stabilito dinamicamente da TEST 3). Verificato: n_neurons_real=0 completa in 32 cicli contro 155 per il build completo a 3 neuroni (X viene ancora letto una volta, condiviso tra neuroni, ma nessun calcolo per-neurone viene eseguito) — TUTTI I TEST PASSED.
  • Stato: RISOLTO, verificato (stessa nota di onestà di BUG-003: il meccanismo esatto del comportamento PRE-fix — 114 vs 196 cicli a seconda di N_NEURONS — non è stato isolato bit-per-bit, ma il nuovo percorso è deterministico e verificato indipendentemente).

BUG-005 (CRITICA, CONFERMATO) — RUN_NETWORK(0) esegue 256 layer fasulli leggendo dati arbitrari come descrittori

  • Sintomo: rtl/layer_sequencer.v documenta run_num_layers come "1..N_LAYERS" ma non esiste alcun guard, né a tempo di elaborazione né a runtime, che lo imponga. layer_idx (riga 121) è un registro a 8 bit PIENO (non ristretto come il group_index a 1 bit di BUG-002) — per run_num_layers=0, la condizione di terminazione layer_idx == num_layers_reg-1 (riga 303) avvolge a layer_idx==255, un valore che il contatore RAGGIUNGE naturalmente contando da 0. Risultato confermato empiricamente: RUN_NETWORK(0) non si blocca — esegue tutti e 256 gli indici di layer possibili (21761 cicli in simulazione) prima di terminare, ciascuno leggendo 11 byte di "descrittore" da table_base + layer_idx×11 — ben oltre la vera tabella descrittori (dimensionata per il build reale, tipicamente poche decine di byte) — e interpretando dati PSRAM arbitrari (pesi, altri dati di rete, o memoria non inizializzata) come indirizzi/parametri di layer validi, eseguendo run reali di neuron_memory con quei parametri e scrivendo i risultati nei buffer ping-pong a indirizzi derivati da quei dati arbitrari.
  • Causa radice: nessun guard su run_num_layers, né a tempo di elaborazione (come invece esiste per N_INPUTS%PARALLEL in neuron_parallel.v) né a runtime (come invece esiste, sia pure incompleto, per n_inputs_real/n_neurons_real, BUG-003/004).
  • Evidenza: sim/layer_sequencer_bug005_zero_layers_tb.viverilog -g2012 -o /tmp/ls0.out rtl/layer_sequencer.v sim/layer_sequencer_bug005_zero_layers_tb.v && vvp /tmp/ls0.outdut.layer_idx termina a 255, non a 0.
  • Impatto pratico: più severo di BUG-002/003/004 — raggiungibile con un singolo opcode SPI documentato (RUN_NETWORK, num_layers=0) senza bisogno di ricompilare il bitstream né di impostare un valore "runtime" degenere in un percorso secondario; il rischio non è solo un risultato sbagliato o un hang, ma scritture reali in PSRAM a indirizzi non controllati, derivati da dati che non erano mai stati pensati per essere interpretati come indirizzi.
  • Fix applicato (rtl/layer_sequencer.v, ST_IDLE): run_num_layers==0 è ora un no-op esplicito e immediato — seq_done pulsa senza mai entrare in ST_READ_DESC, stessa convenzione già usata da spi_engine.v per WRITE_RAM/READ_RAM con len==0 (accetta il comando, non fa nulla, nessun errore riportato).
  • Test di regressione: sim/layer_sequencer_bug005_zero_layers_tb.v, riscritto da osservazione ad asserzione hard (seq_done entro 5 cicli, layer_idx===0). Verificato: run_num_layers=0 completa in 1 ciclo con layer_idx rimasto a 0 (era 21761 cicli, layer_idx terminato a 255, prima del fix) — PASS.
  • Stato: RISOLTO, verificato.

BUG-006 (BASSA, stessa causa radice di BUG-005, protezione incidentale) — num_neurons_graph=0 in graph_engine.v

  • Sintomo/causa radice: identica struttura a BUG-005 — neuron_idx (rtl/graph_engine.v:159) è un registro a 16 bit pieni, num_neurons_graph=0 fa avvolgere la condizione di terminazione a un valore (65535) che il contatore raggiunge naturalmente. Nessun guard esplicito su num_neurons_graph.
  • Differenza da BUG-005: graph_engine ha già un guard runtime per-edge (src_id>=out_id/out_id>=N_TOTALerr) che, come effetto collaterale non progettato per questo scopo, cattura la maggior parte dei pattern di dati spazzatura molto rapidamente — verificato con un pattern non banale: err a 58 cicli, non 65536. layer_sequencer.v non ha alcuna protezione equivalente.
  • Evidenza: sim/graph_engine_bug006_zero_neurons_probe_tb.v — finestra di 5000 cicli, non fatto girare a completamento (limite dichiarato, vedi docs/validation/06-graph-engine.md §6.2).
  • Impatto pratico: basso ma non nullo — la protezione PRE-fix era incidentale, non garantita per ogni possibile contenuto PSRAM. Il buco strutturale era reale.
  • Fix applicato (rtl/graph_engine.v, ST_COPY_IN_WAIT, alla transizione di fine copia input): num_neurons_graph==0 è ora un no-op esplicito e immediato — done pulsa subito dopo il completamento della copia input, senza mai entrare in ST_DESC_RD/il loop descrittori, stessa convenzione del fix BUG-005.
  • Test di regressione: sim/graph_engine_bug006_zero_neurons_probe_tb.v, riscritto da osservazione a asserzione hard (done entro 30 cicli, nessun err, neuron_idx===0). Verificato: num_neurons_graph=0 completa in 13 cicli senza err, neuron_idx rimasto a 0 (era 58 cicli tramite l'intercettazione incidentale del guard src_id/out_id, prima del fix) — PASS.
  • Stato: RISOLTO, verificato.

BUG-007 (CRITICA, CONFERMATO end-to-end via SPI reale) — SET_NET_TYPE durante un RUN_NETWORK in corso blocca permanentemente il motore in esecuzione

  • Sintomo: rtl/spi_engine.v, stato ST_SET_NET_TYPE, accetta net_type <= rx_byte incondizionatamente su qualunque rx_valid, senza alcun controllo su graph_busy/seq_busy. rtl/spi_neuron_top.v (righe 394-397) instrada la Porta C dell'arbitro tra graph_engine e layer_sequencer in modo puramente combinazionale sul valore CORRENTE di net_type — non agganciato a quale motore ha effettivamente avviato il run in corso. Il commento alla riga 390 dichiara i due motori "mutually exclusive by construction", ma quella costruzione impedisce solo che entrambi vengano avviati insieme — non dice nulla su una scrittura di net_type che arriva a metà di un run già avviato.
  • Confermato end-to-end su SPI reale (non solo per ispezione): avviato un RUN_NETWORK in modalità grafo (lo stesso grafo valido già certificato in spi_neuron_top_graph_tb.v), poi immediatamente — prima che completi — inviato SET_NET_TYPE(dense) via SPI. Risultato: STATUS.busy resta bloccato a 1 per 400+ letture consecutive, ~2.35ms di tempo simulato (contro i ~12-25µs normali per quel grafo) — un hang permanente, non un rallentamento. Le transazioni SPI stesse (incl. il SET_NET_TYPE avversariale) completano regolarmente; è specificamente il motore grafo a restare bloccato in attesa di un ram_ready che non arriverà mai più tramite il percorso ormai scollegato dal mux.
  • Evidenza: sim/spi_neuron_top_bug007_mid_run_net_type_tb.v — riproduce l'hang in modo deterministico e ripetibile su SPI reale (non solo un accesso interno).
  • Impatto pratico: il più severo finora insieme a BUG-005 — raggiungibile con due soli opcode SPI documentati emessi in sequenza ravvicinata (RUN_NETWORK seguito da SET_NET_TYPE prima del completamento), uno scenario host plausibile (es. un host che prepara la configurazione per il prossimo run senza attendere la fine del precedente, o una race a livello applicativo tra due richieste). Blocca l'inferenza in corso finché l'host non se ne accorge (nessun timeout hardware, nessun errore riportato — solo STATUS.busy che non si abbassa mai).
  • Recupero verificato: un RESET inviato durante l'hang riporta il sistema a uno stato pienamente funzionante — verificato con una successiva operazione dense legittima completata normalmente (2 cicli di polling, esito corretto). Non è un blocco permanente, ma un host che si limita a fare polling di STATUS senza un timeout e un RESET di ripiego resterebbe bloccato indefinitamente comunque, dato che l'hardware non segnala mai da solo che qualcosa è andato storto.
  • Fix applicato (rtl/spi_engine.v, ST_SET_NET_TYPE): net_type <= rx_byte ora condizionato a if (!graph_busy && !seq_busy) — la scrittura viene silenziosamente rifiutata (comando accettato via SPI come prima, ma senza effetto) mentre un run è in corso, invece di rimappare il mux dell'arbitro a metà esecuzione.
  • Test di regressione: sim/spi_neuron_top_bug007_mid_run_net_type_tb.v, riscritto per asserire end-to-end su SPI reale sia (a) che il run in corso completi normalmente nonostante lo SET_NET_TYPE avversariale a metà esecuzione, sia (b) che la scrittura sia stata VERAMENTE rifiutata e non parzialmente applicata (una successiva RUN_NETWORK senza re-inviare SET_NET_TYPE(graph) completa comunque correttamente). Verificato: il run grafo completa con out_base[0]=126 dopo 1 solo polling nonostante lo SET_NET_TYPE(dense) avversariale; net_type confermato ancora GRAPH internamente — PASS su entrambi i controlli.
  • Stato: RISOLTO, verificato end-to-end su SPI reale.

Verifica post-fix su entrambi i piani (§A.4)

Dopo l'applicazione di tutti e 7 i fix (RTL: rtl/neuron_parallel.v, rtl/neuron_memory.v, rtl/layer_sequencer.v, rtl/graph_engine.v, rtl/spi_engine.v; rimozione: sim/top.v):

  • Regressione Icarus completa (python3 tools/run_regression.py): 44 testbench, 43 PASS, 0 FAIL/ERROR, 1 BENCHMARK (nessun verdetto per progetto, invariato). Nessuna regressione sui 37 test già certificati pre-fix.
  • Sintesi reale (yosys synth_ecp5, sistema completo spi_neuron_top con sottosistema flash, PARALLEL=8, stessi file/comando già validati in Fase 15/F7 di WORKLOG.md): 0 problemi CHECK, 1 warning atteso/preesistente (tri-state limitato in psram_controller.v, invariato). TRELLIS_FF: 4900 (era 4855 — +45, coerente con la nuova logica di guard/controllo introdotta dai fix, nessuna crescita anomala).
  • Place&route reale (nextpnr-ecp5 --45k --package CABGA381 --speed 8 --freq 80 --lpf synth/ecp5/spi_neuron_top.lpf): 0 errori di vincolo, 0 pin non vincolati, "Program finished normally". Fmax: 68.65 MHz (era 67.91 MHz — leggermente meglio, entro il rumore di piazzamento già documentato in Fase 7/WORKLOG.md, non una regressione). Percorso critico verificato esplicitamente strutturalmente identico a prima del fix: u_neuron_memory.u_neuron.group_indexu_mac8 → catena di riporto CCU2C dell'accumulatore (rtl/mac8.v) — nessun modulo toccato dai fix (che sono tutti aggiunte al percorso di controllo, non al datapath MAC/accumulatore) compare nel percorso critico. Margine sull'oscillatore reale 16MHz: 4.29× (invariato). Log: synth/ecp5/post_fix_verify/yosys.log, synth/ecp5/post_fix_verify/nextpnr.log.

Non-bug (falsi positivi trovati e chiusi durante l'analisi)

Voci che sono sembrate anomalie a un primo controllo automatico ma si sono rivelate corrette per progetto una volta letto il codice/intento — riportate per trasparenza sul processo, non perché siano difetti.

  • neuron_parallel_guard_negative_{degenerate,nonmultiple}_tb.v "falliscono a compilare": comportamento corretto e intenzionale (test negativi, la mancata compilazione è il PASS). Vedi docs/validation/00-inventario.md §0.5.
  • graph_engine_bandwidth_tb.v "nessun verdetto PASS/FAIL": è un benchmark per progetto, non un test di correttezza. Vedi §0.3/§0.5 dell'inventario.
  • C.1 (falso "hang" iniziale, neuron_parallel config nota-buona): controllo tardivo e singolo di done (impulso di un solo ciclo) in uno script bespoke — non un problema dell'RTL. Vedi docs/validation/01-datapath.md §1.4.
  • C.2 (falso "hang" per n_inputs_real=17): stesso tipo di errore in un secondo script bespoke diverso da quello già provato — corretto riusando lo schema affidabile. Vedi docs/validation/02-runtime-width.md §2.2.
  • C.3 (falsi mismatch su 2048 controlli + un falso fallimento di round-trip): nel nuovo sim/int8_memory_access_bytelane_tb.v, un controllo dei segnali nello stesso passo di simulazione del loro aggiornamento non-bloccante (leggeva il valore dell'iterazione precedente), e uno stub di memoria comportamentale che ignorava le byte-lane mem_lb_n/mem_ub_n durante la scrittura. Entrambi difetti della testbench, non dell'RTL — vedi docs/validation/03-memoria.md §3.1.
  • C.4 (mem_arbiter mai concedeva nulla nella mia prima testbench): assegnazioni bloccanti per ritirare le richieste dei "perdenti" nello stesso fronte di clock che doveva concedere la richiesta — race reale con il blocco sincrono del DUT. Diagnosticato con dut.owner mai uscito da SEL_NONE. Corretto passando ad assegnazioni non bloccanti. Vedi docs/validation/04-arbiter.md §4.1.