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
200 lines
9.2 KiB
TeX
200 lines
9.2 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. 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}).
|