Files
FPGA-Neural/docs/latex/architecture.tex
T
micheleandClaude Sonnet 5 cc6cfe168e docs: update LaTeX docs for N16 real timing closure + honest ESP32-S3 projection (EXP-0097)
architecture.tex: document the real MAC-pipeline fix (Stage 1a/1b
split) and its real result on both N8 (WNS 0.000ns -> +0.108ns) and
N16 (now closed, WNS=+0.269ns) -- N16 is a real, verified candidate
for a future board revision, N8 remains the configuration in current
physical fabrication.

tests_timing.tex: extended P&R history table, real N8/N16 signoff
tables (post-fix), the new N16 critical path (Director queue logic,
still route-dominated) and the negative result of a second directive
attempt (confirms current result is the best found). Added a new,
clearly-labeled PROJECTION (not a measurement) for N16 vs ESP32-S3 --
30-68x range, built from real measured scaling factors (same core
count as the original ECP5 comparison, real clock ratio, real 2x INT8
packing factor) -- more grounded than the old, superseded ~55-85x
figure, with the same DDR3-bandwidth-bound honest caveat carried
forward.

datasheet.tex: N8/N16 side-by-side timing/utilization/performance
tables, open items and revision history updated to reflect physical
fabrication status and the N16 candidate decision.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
2026-09-22 00:59:46 +02:00

253 lines
12 KiB
TeX

\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, secondo un'architettura sistolica a
\emph{broadcast dei pesi condiviso} (\emph{shared-weight broadcast}).
\textbf{Stato reale a due configurazioni (Capitolo~\ref{chap:tests})}:
\textbf{N=8} (\texttt{n8\_system\_ddr3\_top.v}, 2 gruppi da 4 PE) \`e
la configurazione attualmente in fabbricazione fisica sulla scheda
reale. \textbf{N=16} (\texttt{n16\_system\_ddr3\_top.v}, 4 gruppi da 4
PE) \`e ora, a seguito di una correzione reale del datapath MAC
(\S\ref{sec:mac-pipeline}), \textbf{funzionalmente verificata E con
timing reale chiuso} su un branch di sviluppo dedicato
(\texttt{n16-timing-closure}), non ancora promossa alla scheda fisica
in produzione --- una decisione hardware reale, non RTL, ancora da
prendere.
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: \`e la parte pi\`u stabile ed efficiente
del design. A N=16 (\S\ref{sec:mac-pipeline}) \`e per\`o diventato,
per la prima volta, il vero collo di bottiglia di temporizzazione ---
non del throughput di calcolo (\S\ref{sec:bottleneck}), ma della
temporizzazione fisica del place-and-route.
\subsection{Correzione reale: pipeline aggiuntiva nel datapath MAC (per N=16)}
\label{sec:mac-pipeline}
A N=16, il margine di temporizzazione reale --- gi\`a estremamente
sottile a N=2 (+0.0999962\,ns) --- \`e stato eroso oltre lo zero dalla
maggiore congestione fisica complessiva del die (WNS reale misurato:
$-0.338$\,ns, dopo un arbitro gerarchico e un tuning delle direttive
di place-and-route, Capitolo~\ref{chap:tests}). Il percorso critico
reale, tracciato (non ipotizzato) fino al livello del singolo
registro, va dall'uscita del moltiplicatore DSP48E1 (gi\`a
ri-temporizzata automaticamente da Vivado) attraverso la logica di
``spacchettamento'' dei due prodotti INT8 impacchettati (uno shift e
una somma di riporto condizionale, dominata da primitive CARRY4) fino
al registro che cattura il risultato --- tutto in un solo ciclo di
clock.
\textbf{Correzione reale applicata}: lo stadio originale \`e stato
spezzato in due stadi di pipeline reali distinti --- il primo registra
il prodotto grezzo del DSP48E1 senza alcuna logica intermedia; il
secondo esegue lo spacchettamento (matematica identica, invariata bit
per bit) a partire dal valore gi\`a registrato. Il costo reale: un solo
ciclo di clock aggiuntivo di latenza per operazione, senza alcun
impatto sul throughput (l'interfaccia a maniglia valid/ready non
assume mai una latenza fissa). Verificato bit-esatto in isolamento
(18/18 PASS contro 2 core reali di riferimento) e funzionalmente
sull'intero sistema N=16 su DDR3 reale (32/32 PASS).
\textbf{Risultato reale}: con questa sola correzione, il timing di
N=16 \textbf{chiude realmente} (WNS $=+0.269$\,ns, WHS $=+0.026$\,ns,
0 endpoint falliti) --- si veda il Capitolo~\ref{chap:tests} per i
numeri completi. La stessa correzione, applicata anche a N=8, migliora
pure il suo margine (da 0.000\,ns esatto a +0.108\,ns) senza alcuna
regressione funzionale in nessuna delle due configurazioni.
\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 esplorate e stato reale attuale}
Lo stesso RTL sistolico, tramite il parametro reale \texttt{N\_GROUPS},
\`e stato realmente sintetizzato e verificato a pi\`u configurazioni:
\begin{itemize}
\item \textbf{N=4} (1 gruppo, 4 PE, 32 DSP48E1) --- funzionante,
margine di timing quasi nullo (non ancora rifinito con la
correzione di \S\ref{sec:mac-pipeline}).
\item \textbf{N=8} (\texttt{n8\_system\_ddr3\_top.v}) --- \textbf{la
configurazione fisicamente in fabbricazione sulla scheda reale
attuale.} Timing chiuso, WNS $=+0.108$\,ns con la correzione MAC.
\item \textbf{N=16} (\texttt{n16\_system\_ddr3\_top.v}) --- dopo la
correzione di \S\ref{sec:mac-pipeline}, \textbf{funzionalmente
verificato E con timing reale chiuso} (WNS $=+0.269$\,ns), su un
branch di sviluppo reale (\texttt{n16-timing-closure}) separato
dalla scheda in produzione. \`E ora un candidato reale, verificato,
per una futura revisione della scheda --- non ancora promosso alla
produzione fisica corrente, una decisione hardware reale ancora da
prendere con l'utente.
\end{itemize}
N=8 resta, ad oggi, la configurazione realmente fabbricata. N=16 non
\`e pi\`u un limite architetturale reale (come inizialmente sembrava),
ma una reale, verificata alternativa a parallelismo doppio, la cui
adozione fisica dipende ora da una scelta dell'utente, non da un
vincolo tecnico residuo.