\chapter{Verifica funzionale, timing e confronti} \label{chap:tests} \section{Metodologia di verifica} Il progetto segue una disciplina di verifica a due livelli, applicata sistematicamente a ogni modulo nuovo prima di fidarsi di un risultato di sintesi o di place-and-route: \begin{enumerate} \item \textbf{Verifica isolata} (Icarus Verilog / \texttt{iverilog}): ogni modulo nuovo \`e verificato da solo, con un modello di memoria comportamentale semplificato, prima di essere collegato al resto del sistema (``one variable at a time''). \item \textbf{Verifica funzionale reale su DDR3} (Vivado \texttt{xsim}): il sistema completo (o una sua configurazione reale, es. N=8) \`e simulato contro il modello DDR3 \emph{reale} fornito da Xilinx (\texttt{ddr3\_model.sv}, lo stesso modello usato per la certificazione del MIG), con calibrazione realistica e tracce di comando JEDEC reali --- non un modello di memoria semplificato. \end{enumerate} Solo dopo che entrambi i livelli passano si procede a un vero place-and-route in contesto (\texttt{synth\_design} + \texttt{opt\_design} + \texttt{place\_design} + \texttt{route\_design}), sulla parte reale XC7A100T-CSG324-2, mai fuori contesto e mai stimato. \section{Risultati della verifica funzionale (reali, non stimati)} \begin{table}[h] \centering \begin{tabular}{lll} \toprule \textbf{Modulo / testbench} & \textbf{Risultato} & \textbf{Note} \\ \midrule \texttt{tb\_mig\_native\_adapter.v} & 12/12 PASS & adattatore DDR3 nativo, contro il modello DDR3 reale \\ \texttt{tb\_n2\_system\_ddr3.v} & 8/8 PASS & sistema N=2 completo su DDR3 reale \\ \texttt{tb\_packed\_slot.v} & 9/9 PASS & include verifica read-after-write reale in DDR3 \\ \texttt{tb\_spi\_host\_bridge\_v3.v} & 49/49 PASS & protocollo SPI di gestione completo \\ \texttt{tb\_host\_mem\_bridge.v} & 16/16 PASS & percorso di accesso raw DDR3 dell'host \\ \texttt{tb\_systolic\_group.v} & 8/8 PASS & un gruppo sistolico isolato, 2 job consecutivi \\ \texttt{tb\_neural\_director\_grouped.v} & 4/4 PASS & dispatch a ottetti, stallo su mismatch, wraparound \\ \texttt{tb\_sdram\_arbiter\_hier.v} & 23/23 PASS & arbitro gerarchico, contesa cross-gruppo e host \\ \texttt{tb\_n16\_system\_ddr3.v} & 32/32 PASS & sistema N=16 completo, su DDR3 reale \\ \texttt{tb\_n8\_system\_ddr3.v} & \textbf{16/16 PASS} & \textbf{sistema N=8 completo, su DDR3 reale (target definitivo)} \\ \bottomrule \end{tabular} \caption{Sottoinsieme rappresentativo dei test funzionali reali eseguiti nel progetto. Ogni riga \`e un vero risultato di simulazione, non una stima.} \end{table} \section{Cronologia reale dei segni di timing (place-and-route)} \label{sec:pnr-history} Ogni riga della tabella seguente \`e un vero risultato di \texttt{report\_timing\_summary} dopo un vero \texttt{route\_design}, sulla stessa parte fisica (XC7A100T-CSG324-2) e sullo stesso dominio di clock reale (155.039\,MHz, \texttt{clk\_pll\_i}) --- mai una stima di sintesi fuori contesto. \begin{longtable}{p{1.3cm}p{3.7cm}p{0.9cm}p{2.1cm}p{1.3cm}p{1.3cm}} \toprule \textbf{Exp.} & \textbf{Cosa \`e cambiato} & \textbf{N} & \textbf{WNS (ns)} & \textbf{LUT} & \textbf{DSP48E1} \\ \midrule \endhead 0074 & prima vera P\&R in contesto: DDR3 + pin & 2 & +0.040 & 5140 & 16 \\ 0076 & + registri, + pin, + fix SPI & 2 & +0.056 & 5173 & 16 \\ 0078 & + bridge flash (STARTUPE2 reale) & 2 & +0.013 & 5213 & 16 \\ 0079 & + motore attivazioni reale & 2 & +0.030 & 5379 & 16 \\ 0082 & + packing attivazioni pi\`u denso & 2 & +0.068 & 5437 & 16 \\ 0083 & + DDRManager fase 1, canale 16 bit & 2 & +0.073 & 5644 & 16 \\ 0084 & canale DDR3 a 32 bit + clock pi\`u veloce (172.4\,MHz) & 2 & $-0.618$ \textbf{(FALLITO)} & 6418 & 16 \\ 0086 & canale a 32 bit, clock ripristinato a 155.039\,MHz & 2 & +0.096 \textbf{(CHIUSO)} & 6382 & 16 \\ 0088 & + motore di scrittura risultati in DDR3 & 2 & +0.100 \textbf{(CHIUSO)} & 6642 & 16 \\ 0093 & architettura sistolica, arbitro piatto a 21 vie & 16 & $-0.913$ \textbf{(FALLITO)} & 19751 & 128 \\ 0094 & + arbitro gerarchico a 2 livelli & 16 & $-0.646$ (ancora fallito) & 19936 & 128 \\ 0094 & + tuning delle direttive P\&R & 16 & $-0.338$ (ancora fallito) & 19936 & 128 \\ 0095 & curva N=4 (1 gruppo) & 4 & $-0.005$ (2 endpoint falliti) & 8794 & 32 \\ \textbf{0095/0096} & \textbf{\texttt{n8\_system\_ddr3\_top.v}, definitivo} & \textbf{8} & \textbf{0.000 (CHIUSO)} & \textbf{12535} & \textbf{64} \\ \bottomrule \caption{Cronologia reale dei segni di place-and-route, dal primo P\&R in contesto fino al target definitivo N=8.} \end{longtable} \subsection{Il segno definitivo: N=8} \begin{table}[h] \centering \begin{tabular}{ll} \toprule \textbf{Metrica} & \textbf{Valore reale} \\ \midrule Clock PHY DDR3 (sys\_clk\_p/n) & 310.078\,MHz (periodo 3.225\,ns) \\ Clock di calcolo (ui\_clk/clk\_pll\_i) & 155.039\,MHz \\ WNS (setup slack) & \textbf{0.000\,ns} --- chiuso, 0 endpoint falliti \\ WHS (hold slack) & +0.017\,ns \\ Endpoint falliti & 0 su 63212 (setup), 0 su 63209 (hold) \\ LUT utilizzate & 12535 / 63400 (19.77\%) \\ Registri utilizzati & 19902 / 126800 (15.70\%) \\ DSP48E1 utilizzati & 64 / 240 (26.7\%) \\ Parallelismo reale & 8 elementi di elaborazione paralleli \\ \bottomrule \end{tabular} \caption{Segno di temporizzazione reale, definitivo, per \texttt{n8\_system\_ddr3\_top.v} (EXP-0095/0096).} \end{table} \textbf{Nota importante, dichiarata onestamente}: il margine WNS=0.000\,ns \`e \emph{esattamente} zero --- reale e chiuso, ma senza alcuno slack di riserva. Qualunque futura modifica RTL a questo top-level o ai suoi moduli dipendenti richiede un nuovo, vero place-and-route (con la stessa sequenza di direttive: \texttt{opt\_design -directive Explore}, \texttt{place\_design -directive ExtraNetDelay\_high}, \texttt{phys\_opt\_design -directive AggressiveExplore}, \texttt{route\_design -directive AggressiveExplore}) prima di fidarsi nuovamente della temporizzazione. \subsection{Il collo di bottiglia reale trovato a N=16} Il percorso critico che impedisce la chiusura del timing a N=16 \`e stato tracciato realmente (non ipotizzato) fino all'interno del datapath MAC di \texttt{neural\_processor\_packed.v} --- lo stesso nucleo di calcolo descritto nel Capitolo~\ref{chap:architecture}, invariato dalla prima versione del progetto, che a N=2 chiudeva gi\`a con un margine estremamente sottile (+0.0999\,ns). A N=16 la maggiore occupazione complessiva del die (31\% LUT) aumenta la congestione di piazzamento a sufficienza da erodere quel margine gi\`a minimo --- un problema diffuso di congestione, non un singolo collo di bottiglia strutturale come quello, gi\`a risolto, dell'arbitro. \section{Confronto con un riferimento reale: ESP32-S3} \label{sec:esp32-comparison} Un confronto quantitativo con un microcontrollore ESP32-S3 esiste, ma va presentato con la dovuta cautela storica, per non presentare come attuale un dato ormai superato. \begin{itemize} \item \textbf{Dato reale, misurato (architettura precedente, ECP5)}: la versione precedente del progetto (v2, su FPGA Lattice ECP5, ora archiviata) ha misurato realmente uno speedup di \textbf{$\sim$9.5--14$\times$ rispetto a ESP32-S3} (baseline misurata) per un carico di lavoro tipo riconoscimento facciale (classe MobileFaceNet), con 16 core paralleli a 64--97\,MHz. \item \textbf{Proiezione non verificata (superata)}: da quel dato era stata derivata una proiezione di $\sim$55--85$\times$ su ESP32-S3 per l'architettura Artix-7, basata su un'ipotesi di \textbf{30 core paralleli} limitati solo dal conteggio dei DSP disponibili. Questa proiezione \textbf{non \`e mai stata verificata} con una misura reale a livello di sistema completo, ed \`e oggi superata dalla scoperta successiva (\S~\ref{sec:bottleneck}) che il sistema \`e limitato dalla banda DDR3, non dal conteggio dei DSP --- l'ipotesi dei 30 core non \`e pi\`u realistica alla luce di questo vincolo. \end{itemize} \textbf{Stato onesto attuale}: non esiste ancora una misura reale, diretta, di throughput aggregato (inferenze al secondo, o MAC/s sostenuti) della configurazione N=8 definitiva confrontata con un benchmark ESP32-S3 aggiornato. Il picco teorico calcolabile per N=8 \`e: \[ 8~\text{PE} \times 16~\text{MAC/ciclo} \times 155.039\times10^6~\text{cicli/s} = 19.84~\text{GMAC/s di picco teorico aggregato} \] ma questo \`e un limite superiore puramente computazionale: dato il vincolo reale di banda DDR3 (2.48\,GB/s fisici, condivisi tra tutti gli 8 PE), il throughput realmente sostenibile in un carico di lavoro reale sar\`a inferiore, nella stessa misura gi\`a documentata a N=1/2 (\S~\ref{sec:bottleneck}) --- una misura reale e diretta di questo throughput sostenuto a N=8, e un nuovo confronto onesto con un benchmark ESP32-S3 aggiornato, restano lavoro futuro non ancora eseguito. \section{Lezioni reali dal processo di verifica} Alcuni bug reali, trovati e corretti durante la verifica di questo progetto, meritano di essere documentati perch\'e generalizzabili: \begin{itemize} \item \textbf{Impulsi a colpo singolo persi al confine dell'arbitraggio}: un richiedente che genera un impulso di richiesta di un solo ciclo pu\`o essere perso se l'elemento che lo riceve non \`e ancora pronto a elaborarlo nello stesso ciclo --- causa originaria di un bug reale gi\`a nella primissima versione dell'arbitro, e riemerso (in una forma nuova, al confine tra i due livelli dell'arbitro gerarchico) durante lo sviluppo di N=16/N=8; risolto con un'aggancio (\emph{latch}) persistente per richiesta, non con un semplice registro. \item \textbf{Corse (race condition) nei testbench}: pilotare gli stimoli sullo stesso fronte di clock campionato dal modulo sotto test, specialmente in chiamate ravvicinate senza un ciclo di margine naturale, pu\`o causare doppie registrazioni silenziose --- risolto pilotando gli stimoli sul fronte opposto. \item \textbf{Casi limite di larghezza di bus}: espressioni come $\log_2(N)$ diventano zero (e quindi un intervallo di bit non valido) quando $N=1$, un caso non testato fino all'esplorazione della curva N=4/8/16 --- risolto con un valore minimo di 1 bit esplicito. \end{itemize}