Files
FPGA-Neural/docs/latex/architecture.tex
T
micheleandClaude Sonnet 5 fc0130e2e2 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
2026-09-21 08:54:25 +02:00

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}).