docs: LaTeX chapters for hardware, chip architecture, and tests/timing

Three chapters (docs/latex/), sourced from the real project docs
(BOM.md, PHYSICAL_REALIZATION.md, ARCHITECTURE_ANALYSIS.md,
experiments.log) -- no invented numbers:

- hardware.tex: board components, DDR3/FPGA/flash/ESP32, real pinout,
  SPI protocol, boot procedure.
- architecture.tex: packed INT8 MAC core, N=8 hybrid systolic
  architecture (shared-weight broadcast), hierarchical arbiter,
  result writeback, why N=8 is the DDR3-bandwidth-bound sweet spot.
- tests_timing.tex: verification methodology, real functional test
  results table, full real P&R signoff history (N=2 through N=16),
  the definitive N=8 signoff (WNS=0.000ns), and an honestly-caveated
  ESP32-S3 comparison (real historical ECP5 measurement vs. the
  superseded, never-verified Artix-7 projection).

main.tex ties the three together as report chapters.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-21 08:54:25 +02:00
co-authored by Claude Sonnet 5
parent 264950923b
commit fc0130e2e2
4 changed files with 730 additions and 0 deletions
+199
View File
@@ -0,0 +1,199 @@
\chapter{Architettura: come funziona il chip}
\label{chap:architecture}
\section{Vista d'insieme}
Il design realizzato in FPGA \`e un acceleratore per reti neurali
quantizzate INT8, organizzato come un insieme di \textbf{elementi di
elaborazione} (Processing Element, PE) paralleli che condividono un
unico canale DDR3 reale. La configurazione attualmente definitiva
(Capitolo~\ref{chap:tests}) \`e \textbf{N=8}: 8 PE reali, organizzati
in \textbf{2 gruppi da 4 PE ciascuno}, secondo un'architettura
sistolica a \emph{broadcast dei pesi condiviso} (\emph{shared-weight
broadcast}). Il modulo top-level reale \`e
\texttt{hardware/v3/rtl/n8\_system\_ddr3\_top.v}.
L'intero design gira in un unico dominio di clock reale a
\textbf{155.039\,MHz} (\texttt{ui\_clk}, derivato dal MIG DDR3,
Capitolo~\ref{chap:hardware}).
\section{Il nucleo di calcolo: MAC INT8 impacchettati su DSP48E1}
L'unit\`a base di calcolo \`e \texttt{neural\_processor\_packed.v},
usata identicamente in ogni PE. Ogni core usa \textbf{8 DSP48E1},
ciascuno configurato per eseguire \textbf{due moltiplicazioni-accumulo
INT8 impacchettate per ciclo} (una corsia A e una corsia B, che
condividono lo stesso peso residente) --- una tecnica di packing reale
verificata sia a livello RTL sia con sintesi Xilinx effettiva:
\[
8~\text{DSP48E1} \times 2~\text{MAC/DSP} = 16~\text{MAC/ciclo per PE}
\]
Al clock reale di 155.039\,MHz, il picco teorico per singolo PE \`e:
\[
16~\text{MAC/ciclo} \times 155.039\times10^6~\text{cicli/s} = 2.48~\text{GMAC/s per PE (calcolo da dati misurati)}
\]
Questo nucleo \`e rimasto \emph{invariato} (stesso numero di DSP,
stessa struttura) dalla primissima sintesi reale del progetto fino
alla configurazione N=8 attuale: \`e la parte pi\`u stabile ed efficiente
del design, e non \`e mai stato il collo di bottiglia delle prestazioni
(si veda \S\ref{sec:bottleneck}).
\section{L'architettura sistolica a gruppi (N=8)}
\subsection{Motivazione}
Scalare il numero di PE aumentando semplicemente il numero di core
indipendenti significa aumentare linearmente anche il numero di
richiedenti sul canale DDR3 condiviso --- ogni PE, in un design
``piatto'', dovrebbe rifetchare autonomamente i pesi del layer anche
quando pi\`u PE elaborano lo stesso layer in parallelo, sprecando
banda DDR3 reale su dati identici gi\`a in transito per un altro PE.
L'architettura a gruppi risolve questo con un \textbf{unico fetch dei
pesi per gruppo}, condiviso via broadcast da tutti i PE del gruppo:
\begin{itemize}
\item Un gruppo (\texttt{systolic\_group.v}) possiede
\textbf{un solo} percorso reale di prefetch pesi
(\texttt{layer\_prefetch\_ctrl.v} + \texttt{layer\_weight\_buffer.v}
+ \texttt{weight\_tile\_gather.v} --- moduli riusati integralmente
e non modificati dal design N=2 originale).
\item I 4 PE del gruppo (\texttt{packed\_pe.v}, una versione di
\texttt{packed\_slot.v} privata del proprio percorso di fetch pesi)
ricevono il tile di peso corrente via un bus di broadcast, tramite
un meccanismo reale di sincronizzazione a barriera: il gruppo avanza
al tile successivo solo quando \emph{tutti e 4} i PE hanno
confermato (\texttt{pe\_tile\_ack}) il consumo del tile corrente.
\item Ogni PE elabora indipendentemente e in parallelo le proprie
posizioni di attivazione (nessuna propagazione di risultati tra PE
--- non \`e uno shift register sistolico letterale, ma un vero
broadcast condiviso: scelta esplicitamente confermata con l'utente
dopo un confronto diretto tra le due alternative).
\end{itemize}
Con questa scelta, il fetch pesi \`e ridotto di un fattore 4 per gruppo
rispetto a un design piatto con lo stesso numero di PE, mentre il
fetch delle attivazioni --- che non ha riuso possibile tra posizioni
diverse --- resta uno per PE, esattamente come nel design N=2 originale.
\subsection{Struttura a N=8}
\begin{itemize}
\item \textbf{2 gruppi} $\times$ \textbf{4 PE} = 8 PE reali totali.
\item \textbf{64 DSP48E1} totali (8 per PE $\times$ 8 PE).
\item Il modulo \texttt{neural\_director\_grouped.v} dispatcha
\textbf{ottetti} di job (8 posizioni che condividono la stessa base
dei pesi) a un gruppo libero --- estensione diretta della disciplina
di accoppiamento a coppie gi\`a usata dal Director originale N=2,
generalizzata a 8 posizioni.
\end{itemize}
Un fatto reale rilevante: il protocollo SPI verso l'host
\textbf{non cambia affatto} passando da N=2 a N=8 --- l'host continua
a sottomettere un job alla volta con la stessa struttura a 16 byte
(\S\ref{sec:spi-protocol}); \`e il Director interno a raggruppare
automaticamente gli 8 job pi\`u vecchi in coda quando condividono la
stessa base dei pesi.
\section{Arbitraggio gerarchico dell'accesso a DDR3}
Con 8 PE reali pi\`u 2 unit\`a di fetch pesi di gruppo pi\`u l'accesso
diretto dell'host, il numero di richiedenti reali sul singolo canale
DDR3 fisico \`e 11. Un arbitro piatto a selezione singola (usato
inizialmente per il tentativo a N=16) genera un multiplexer troppo
largo, con un impatto reale misurato sul timing dominato dal
\emph{routing} fisico (distanza tra le 20+ sorgenti sparse e il punto
centrale di arbitraggio), non dalla profondit\`a logica.
La soluzione reale adottata \`e un \textbf{arbitro gerarchico a due
livelli} (\texttt{sdram\_arbiter\_hier.v}):
\begin{itemize}
\item \textbf{Livello foglia} (uno per gruppo): un'istanza
dell'arbitro N-way gi\`a esistente (\texttt{sdram\_arbiter\_n.v},
riutilizzato \emph{senza modifiche}), che arbitra i 5 richiedenti
locali del gruppo (1 fetch pesi + 4 PE), fisicamente vicini tra
loro sul die.
\item \textbf{Livello superiore}: una seconda istanza dello stesso
arbitro, che arbitra tra i 2 gruppi e l'host, con un vero stadio
di pipeline (un ciclo di registro) tra i due livelli --- questo
\`e ci\`o che effettivamente permette al piazzatore/router di
Vivado di distribuire il problema su due finestre di clock fisiche
separate invece di forzare tutte le sorgenti a convergere in un
solo ciclo.
\item L'accesso dell'host \textbf{bypassa} il primo livello
(collegamento diretto al livello superiore) poich\'e non ha mai
rappresentato il collo di bottiglia reale.
\end{itemize}
Un vincolo di correttezza gi\`a stabilito nel progetto (dalla prima
versione dell'arbitro, 2026) \`e che un richiedente a impulso singolo
deve vedere il proprio \emph{grant} nello stesso ciclo in cui asserisce
per la prima volta il proprio segnale di attivit\`a --- altrimenti la
richiesta viene persa silenziosamente. L'arbitro gerarchico preserva
questa garanzia esattamente per ogni PE e per il fetch pesi di ogni
gruppo (il grant al livello foglia resta puramente combinatorio); solo
il transito effettivo del dato verso il controllore DDR3 fisico viene
pipeline-ato, con un costo reale di 1--2 cicli aggiuntivi di latenza
per transazione --- trascurabile rispetto alla latenza intrinseca di
un vero accesso DDR3 (decine di cicli).
\section{Scrittura dei risultati}
Ogni PE, al termine del proprio job, scrive il risultato direttamente
in DDR3 tramite \texttt{result\_writeback.v} (un'istanza per PE),
usando lo stesso canale di controllo gi\`a condiviso con il fetch
attivazioni. Questo elimina un vincolo reale di scalabilit\`a che
affliggeva le prime versioni del progetto: esporre i risultati come
pin fisici dedicati del package sarebbe stato insostenibile oltre
poche unit\`a di PE (a N=16 avrebbe richiesto centinaia di pin).
\section{Il vincolo reale: banda DDR3, non conteggio dei DSP}
\label{sec:bottleneck}
Un'analisi quantitativa reale (misurata, non stimata) condotta nelle
prime fasi del progetto ha stabilito che il sistema \`e
\textbf{limitato dalla banda DDR3, non dal numero di DSP}, gi\`a a
partire da un solo core:
\begin{itemize}
\item Banda DDR3 fisica reale misurata (canale a 16 bit, traccia
JEDEC reale): 1.24\,GB/s.
\item Banda richiesta da un singolo core al picco DSP teorico,
con il packing delle attivazioni ottimizzato (1 byte DDR3 mosso
per MAC utile): 2.48\,GB/s.
\end{itemize}
L'allargamento del canale fisico DDR3 da 16 a 32 bit (due chip
Micron in parallelo, Capitolo~\ref{chap:hardware}) raddoppia realmente
il tetto fisico a \textbf{2.48\,GB/s}, confermato da un vero
place-and-route con timing chiuso (\S~\ref{sec:pnr-history}), non da
una proiezione.
L'occupazione reale di DSP48E1 resta comunque bassa anche alla
configurazione N=8 (64/240, 26.7\%) --- conferma diretta che il
margine di scalabilit\`a residuo del chip \`e ampio sul lato
computazionale, ma \`e vincolato dal canale di memoria condiviso, non
dalla logica di calcolo.
\section{Configurazioni alternative esplorate}
Lo stesso RTL sistolico, tramite il parametro reale \texttt{N\_GROUPS},
\`e stato realmente sintetizzato e verificato anche a:
\begin{itemize}
\item \textbf{N=4} (1 gruppo, 4 PE, 32 DSP48E1) --- funzionante,
margine di timing quasi nullo.
\item \textbf{N=16} (4 gruppi, 16 PE, 128 DSP48E1) --- funzionalmente
verificato su DDR3 reale, ma con temporizzazione non ancora chiusa
(si veda \S~\ref{sec:pnr-history}) --- mantenuto come lavoro futuro
documentato, non abbandonato.
\end{itemize}
N=8 \`e stata scelta come configurazione reale definitiva perch\'e
\`e la pi\`u grande che chiude realmente il timing con margine
positivo (Capitolo~\ref{chap:tests}).
+290
View File
@@ -0,0 +1,290 @@
\chapter{Descrizione hardware}
\label{chap:hardware}
\section{Panoramica della scheda}
Il progetto realizza un acceleratore neurale su una scheda custom
(non una dev-board commerciale), basata su un componente FPGA nudo
Xilinx Artix-7, affiancato da memoria DDR3 reale, una flash di
configurazione dedicata e un microcontrollore ESP32 come processore
host/centrale. La scheda non utilizza moduli di sviluppo preassemblati:
ogni parte è stata scelta, verificata in reperibilità reale (LCSC) e
posizionata pin-per-pin a partire da un vero place-and-route Vivado,
non da valori stimati.
\begin{table}[h]
\centering
\begin{tabular}{lll}
\toprule
\textbf{Componente} & \textbf{Parte} & \textbf{Note} \\
\midrule
FPGA & XC7A100T-CSG324-2 & Speed grade $-2$ \\
DDR3 SDRAM & 2$\times$ Micron MT41J128M16JT-125:K & 2Gb, x16, canale fisico a 32 bit \\
Flash di configurazione & Winbond W25Q32JVSSIQ & 32Mbit, SOIC-8, esclusiva della FPGA \\
Host / processore centrale & ESP32-S3-WROOM-1-N16R8 & 16MB flash, 8MB PSRAM \\
\bottomrule
\end{tabular}
\caption{Componenti principali, tutti verificati realmente reperibili su LCSC.}
\end{table}
\section{FPGA: Xilinx XC7A100T-CSG324-2}
Il componente centrale è un Artix-7 XC7A100T, package CSG324 (324 BGA),
speed grade $-2$. La scelta dello speed grade $-2$ (corretta rispetto
a un'ipotesi iniziale $-1$) non comporta alcuna differenza di die,
package o footprint: offre solamente un margine di timing strettamente
migliore, confermato dai reali segni di place-and-route (Capitolo~\ref{chap:tests}).
Risorse rilevanti utilizzate dal design (target reale N=8,
Capitolo~\ref{chap:architecture}):
\begin{itemize}
\item 240 DSP48E1 totali disponibili; 64 utilizzati (26.7\%).
\item 63400 LUT disponibili; 12535 utilizzate (19.77\%).
\item 126800 registri disponibili; 19902 utilizzati (15.70\%).
\item Nessuna BRAM utilizzata.
\end{itemize}
\section{Memoria DDR3}
Il canale DDR3 è realizzato con due chip Micron
MT41J128M16JT-125:K (2Gb, x16, DDR3-1600) collegati in parallelo,
formando un canale fisico reale a \textbf{32 bit} (bus dati raddoppiato
rispetto alla configurazione iniziale a 16 bit di un solo chip). Le linee
di indirizzo/comando/controllo sono condivise e distribuite
identicamente a entrambi i chip; le linee DQ/DQS/DM sono invece divise
16 bit per chip.
Il canale DDR3 è pilotato dal MIG (Memory Interface Generator) di
Xilinx, generato realmente per questo esatto part number. I due
domini di clock reali coinvolti sono:
\begin{itemize}
\item \textbf{sys\_clk\_p/n} --- clock differenziale della PHY
DDR3, \textbf{310.078\,MHz} (periodo 3.225\,ns).
\item \textbf{ui\_clk / clk\_pll\_i} --- dominio di calcolo interno,
derivato dal PLL del MIG con rapporto 2:1 dal clock PHY,
\textbf{155.039\,MHz}. Tutta la logica neurale (Director, arbitraggio,
PE) gira in questo dominio.
\end{itemize}
Questa combinazione di frequenze non è una scelta libera: è l'unica
uscita reale del calcolatore JEDEC/PLL del wizard MIG di Vivado per
questo speed grade e questa larghezza di canale, e determina
direttamente il vincolo di temporizzazione usato in ogni place-and-route
reale del progetto (Capitolo~\ref{chap:tests}).
\subsection{Convenzione di indirizzamento in DDR3}
Pesi e attivazioni condividono lo stesso spazio di indirizzi DDR3,
indirizzato a parola. La parola nativa è a 32 bit
(\texttt{BURST\_LEN=8} per transazione, cio\`e 256 bit/burst).
\begin{itemize}
\item \textbf{Pesi}: l'insieme di pesi di un layer parte all'indirizzo
parola \texttt{layer\_index * WORDS\_PER\_LAYER}, letto in burst
sequenziali in un buffer on-chip una sola volta per job (riuso reale
attraverso pi\`u posizioni).
\item \textbf{Attivazioni}: quattro tile consecutivi (8 valori INT8
ciascuno) condividono un solo burst da 256 bit; la selezione del
quarto di burst \`e latenziata al momento della richiesta (non un
multiplexer indicizzato a runtime), preservando il margine di
temporizzazione.
\end{itemize}
\section{Flash di configurazione}
Una Winbond W25Q32JVSSIQ (32Mbit / 4MB, SOIC-8) ospita il bitstream
di configurazione (circa 30.5Mbit per un XC7A100T pieno) ed \`e
collegata \textbf{esclusivamente} alla FPGA --- l'ESP32 non ha alcun
collegamento elettrico diretto ad essa. L'unico percorso elettrico
dall'ESP32 alla flash \`e un relay software attraverso la FPGA
(opcode SPI \texttt{0x40 FLASH\_XFER}, \S\ref{sec:spi-protocol}).
\section{Host: ESP32-S3}
Il processore host \`e un modulo ESP32-S3-WROOM-1-N16R8 (16MB flash,
8MB PSRAM, dual-core), verificato realmente reperibile su LCSC. Comunica
con la FPGA tramite un bus SPI dedicato dove la \textbf{FPGA \`e
slave}, e --- tramite la FPGA come relay --- raggiunge la flash di
configurazione su un secondo bus SPI fisicamente distinto dove la
\textbf{FPGA \`e master}.
\section{Assegnazione dei pin (reale, dal design instradato)}
Tutti i pin elencati sotto provengono da un vero file di vincoli
generato da Vivado (XDC), non da valori ipotizzati.
\subsection{DDR3 (fissati dall'hardware PHY della FPGA)}
I pin DDR3 sono generati dal wizard MIG e non sono una scelta libera:
banchi \textbf{34/35}, standard \texttt{SSTL15}/\texttt{DIFF\_SSTL15}
(1.5V). Il clock di riferimento differenziale \texttt{clk\_ref\_p/n}
vive necessariamente nel \textbf{banco 14} (regola di piazzamento
UG586 del wizard per questo package), a \textbf{2.5V} (\texttt{LVDS\_25}) ---
un vincolo che ha richiesto lo spostamento del bus SPI della flash
(si veda sotto), poich\'e un banco pu\`o avere una sola tensione VCCO.
\subsection{SPI di gestione (ESP32 $\leftrightarrow$ FPGA, FPGA slave)}
Banco 15, colonna di bordo del package (tracce corte), \texttt{LVCMOS33}:
\begin{table}[h]
\centering
\begin{tabular}{lll}
\toprule
\textbf{Segnale} & \textbf{Pin} & \textbf{Direzione (lato FPGA)} \\
\midrule
sclk & A15 & input \\
mosi & B16 & input \\
miso & B17 & output \\
cs\_n & A16 & input \\
sys\_rst & G13 & input (pin provvisorio) \\
data\_ready\_n & D14 & output (IRQ sticky attivo-basso) \\
\bottomrule
\end{tabular}
\caption{Pinout del bus SPI di gestione, banco 15, VCCO assunto 3.3V.}
\end{table}
\subsection{SPI della flash di configurazione (FPGA $\leftrightarrow$ flash, FPGA master)}
Originariamente sui pin dedicati di configurazione Master-SPI della
FPGA (banco 14); spostati nel \textbf{banco 16} per risolvere un
conflitto reale di VCCO con \texttt{clk\_ref} (rilevato da un vero
fallimento di \texttt{place\_design}, non ipotizzato):
\begin{table}[h]
\centering
\begin{tabular}{llll}
\toprule
\textbf{Segnale} & \textbf{Pin} & \textbf{Direzione} & \textbf{Note} \\
\midrule
flash\_mosi & D9 & output & banco 16 (era K17, banco 14) \\
flash\_miso & D10 & input & banco 16 (era K18, banco 14) \\
flash\_cs\_n & C9 & output & banco 16 (era L13, banco 14) \\
(CCLK) & E9 & output & pilotato internamente via \texttt{STARTUPE2} \\
\bottomrule
\end{tabular}
\caption{Pinout del bus SPI della flash, banco 16, VCCO 3.3V.}
\end{table}
\subsection{Controllo di configurazione FPGA (banco 0, non negoziabile)}
\begin{table}[h]
\centering
\begin{tabular}{ll}
\toprule
\textbf{Segnale} & \textbf{Pin} \\
\midrule
PROGRAM\_B & P9 \\
INIT\_B & P7 \\
DONE & P10 \\
M0 / M1 / M2 & P12 / P13 / P11 \\
CFGBVS & P8 \\
\bottomrule
\end{tabular}
\end{table}
Per il boot autonomo da Master SPI: \texttt{M[2:0] = 001}.
\subsection{JTAG}
\begin{table}[h]
\centering
\begin{tabular}{ll}
\toprule
\textbf{Segnale} & \textbf{Pin} \\
\midrule
TCK & E10 \\
TDI & E11 \\
TMS & E12 \\
TDO & E13 \\
\bottomrule
\end{tabular}
\end{table}
Usato per la programmazione di fabbrica/recupero quando la flash \`e
vuota, e per debug --- pilotato via bit-banging da firmware ESP32
(non ancora implementato, lavoro software separato).
\section{Alimentazione (stato di avanzamento)}
La ricerca dei componenti di alimentazione \`e parziale e onestamente
dichiarata incompleta:
\begin{itemize}
\item \textbf{VCCINT} (1.0V, alimentazione core FPGA): candidato
reale identificato, Infineon IR38060 (SupIRBuck integrato, fino a
6A) --- \textbf{stock non confermato}.
\item \textbf{VTT} (terminazione DDR3, opzionale): candidato reale
TI TPS51200 --- la necessit\`a effettiva dipende dalla lunghezza
reale delle tracce sul layout, non ancora stabilita da questo
progetto (nessuna simulazione di signal integrity eseguita).
\item \textbf{VCCAUX (1.8V) e VCCO per banco} (1.5V banchi 34/35,
2.5V banco 14, 3.3V banchi 15/16): \textbf{non ancora ricercati}.
\end{itemize}
\section{Oscillatori di clock}
\begin{table}[h]
\centering
\begin{tabular}{lll}
\toprule
\textbf{Segnale} & \textbf{Frequenza richiesta} & \textbf{Stato} \\
\midrule
sys\_clk\_p/n & 310.077\,MHz differenziale & Nessun SKU a catalogo a questa
esatta frequenza; candidato reale SiTime SiT9122 (oscillatore MEMS
programmabile 220--625\,MHz) --- richiede un ordine a specifica, non
ancora confermato in stock. \\
clk\_ref\_p/n & 200\,MHz differenziale LVDS & \textbf{Confermato reale,
in stock}: SiTime SiT9121AC-2CF-33E-200.00000, LCSC C835051. \\
\bottomrule
\end{tabular}
\end{table}
\section{Protocollo SPI di gestione (per il firmware ESP32)}
\label{sec:spi-protocol}
Un byte di opcode (MSB-first) per transazione CS-basso, gestito da
\texttt{spi\_host\_bridge\_v3.v}:
\begin{table}[h]
\centering
\begin{tabular}{lp{2.3cm}p{3.5cm}p{5cm}}
\toprule
\textbf{Opcode} & \textbf{Nome} & \textbf{Payload} & \textbf{Scopo} \\
\midrule
0x00 & NOP & 0 byte & inerte \\
0x0F & RESET & 0 byte & soft-reset \\
0x10 & WRITE\_JOB & 16 byte & sottomette un job di inferenza \\
0x20 & STATUS & 0 $\to$ 1 byte & bit di stato (busy, ultimo job accettato) \\
0x01 & WRITE\_MEM & $4+2N$ byte & scrittura raw in DDR3 ($N$ parole) \\
0x02 & READ\_MEM & 6 $\to$ 2$N$ byte & lettura raw da DDR3 ($N$ parole) \\
0x30 & REG\_WRITE & 5 byte & scrive un registro di controllo \\
0x31 & REG\_READ & 1 $\to$ 4 byte & legge un registro di stato/ID \\
0x40 & FLASH\_XFER & $N \to N{+}2$ byte & passthrough grezzo verso la flash \\
\bottomrule
\end{tabular}
\caption{Riepilogo del protocollo SPI di gestione. Il layout completo dei campi \`e documentato nell'intestazione di \texttt{spi\_host\_bridge\_v3.v}.}
\end{table}
Un pin dedicato \texttt{data\_ready\_n} (attivo-basso, sticky) permette
all'ESP32 di essere interrupt-driven invece di eseguire polling
continuo su STATUS.
\section{Procedura di avvio (boot)}
Due percorsi reali, entrambi presenti sulla scheda per progetto:
\begin{enumerate}
\item \textbf{Primo avvio / recupero (JTAG, pilotato da ESP32)}:
su una scheda vergine la flash \`e vuota --- solo JTAG pu\`o
inizializzarla (vincolo reale, non aggirabile via SPI finch\'e
la FPGA non esegue gi\`a la logica che relaya i comandi).
\item \textbf{Avvio normale (Master SPI, autonomo)}: a ogni
accensione successiva la FPGA si auto-configura dalla flash
tramite il proprio hardware dedicato, senza intervento dell'ESP32.
\item \textbf{Aggiornamento firmware sul campo} (\texttt{FLASH\_XFER}):
a FPGA gi\`a in funzione, l'ESP32 pu\`o riscrivere la flash
relayando byte SPI-NOR grezzi attraverso la FPGA.
\end{enumerate}
+43
View File
@@ -0,0 +1,43 @@
\documentclass[11pt,a4paper]{report}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[italian]{babel}
\usepackage[margin=2.5cm]{geometry}
\usepackage{booktabs}
\usepackage{longtable}
\usepackage{array}
\usepackage{xcolor}
\usepackage{hyperref}
\usepackage{amsmath}
\usepackage{listings}
\usepackage{caption}
\hypersetup{
colorlinks=true,
linkcolor=blue!50!black,
urlcolor=blue!50!black,
citecolor=blue!50!black
}
\lstset{
basicstyle=\ttfamily\small,
breaklines=true,
frame=single,
columns=fullflexible
}
\title{FPGA-Neural \\ \large Acceleratore neurale su FPGA Xilinx Artix-7}
\author{}
\date{Stato al 2026-09-21 --- target reale definitivo: N=8}
\begin{document}
\maketitle
\tableofcontents
\input{hardware.tex}
\input{architecture.tex}
\input{tests_timing.tex}
\end{document}
+198
View File
@@ -0,0 +1,198 @@
\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}