Files
FPGA-Neural-Datasheet/files/docs/datasheet/chapters/07b-programmazione.tex
T
micheleandClaude Sonnet 5 0e73eb4726 docs: bring datasheet/ into the main repo under hardware/v2/docs
Was a separate, untracked directory (DataSheet/) outside the repo.
Renamed to lowercase and moved in as hardware/v2/docs/datasheet/, with
its own .gitignore for LaTeX build byproducts (compiled PDFs stay
tracked, .aux/.log/.toc/etc do not). Now versioned and shares this
repo's own remote instead of living untracked on disk.

Content: IT+EN LaTeX chapter sources, reference manufacturer PDFs, and
compiled datasheet PDFs including the 2026-09-07 SDRAM upgrade
addendum (AS4C32M16SB-7BIN part/pinout/timing) in the v2-en chapters.

Note: hardware/v2/docs/DatasheetLatex/ (and the v1 sibling) is a
separate, already-tracked, differently-structured LaTeX document that
predates this move -- left untouched, not merged, since its chapter
set and content differ and merging was not requested.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
2026-09-07 05:09:14 +02:00

234 lines
11 KiB
TeX

\chapter[Programmazione della rete]{Programmazione della rete neurale}
\label{ch:prog}
Questo capitolo è la guida pratica alla codifica di una rete per FPGA-Neural: come si
dispone in memoria, quali registri si impostano e come si avvia, per entrambe le
topologie. Presuppone gli opcode SPI (cap.~\ref{ch:spi}) e i formati descrittore
(cap.~\ref{ch:seq}, \ref{ch:grafo}).
\section{Flusso generale}
Qualunque sia il tipo, il ciclo è lo stesso: l'host \emph{costruisce le strutture dati in
RAM}, imposta i \emph{registri base}, dichiara il \emph{tipo di rete}, \emph{avvia} e
\emph{rilegge} il risultato.
\begin{center}
\begin{tikzpicture}[font=\scriptsize,node distance=3mm,start chain=going below,
every node/.style={on chain,fnblock,minimum width=64mm}]
\node[fnblockA]{1. \op{RESET} --- azzera il motore e il latch STATUS};
\node{2. \op{SET\_NET\_TYPE} --- dense (\#1) o graph (\#2)};
\node{3. \op{WRITE\_RAM} --- tabelle, pesi/edge, bias, input X};
\node{4. \op{SET\_BASE} --- registri base (x, table, \ldots)};
\node[fnblockT]{5. \op{RUN\_NETWORK} --- dispatch su \code{net\_type}};
\node{6. \op{STATUS} in polling --- attende \code{done}};
\node[fnblockD]{7. \op{READ\_OUTPUT} / \op{READ\_RAM} --- risultato};
\foreach \i [count=\j from 2] in {1,...,6} \draw[fnarrow] (chain-\i)--(chain-\j);
\end{tikzpicture}
\end{center}
\section{Registri e opcode coinvolti}
Tutti i valori base si impostano con \op{SET\_BASE} \code{sel(1B)+addr(3B)}. Selettori:
\begin{tabularx}{\textwidth}{C{1.0cm} L{3.4cm} C{1.4cm} C{1.4cm} Y}
\toprule
\rowh \thd{sel} & \thd{Registro} & \thd{Tipo \#1} & \thd{Tipo \#2} & \thd{Uso} \\
\midrule
0 & \code{x\_base} & \checkmark & \checkmark & Base input $X$. \\
\rowa 3 & \code{table\_base} & \checkmark & \checkmark & Tabella descrittori. \\
4 & \code{buf\_a\_base} & \checkmark & \checkmark\textsuperscript{$\ast$} & Ping-pong A (\#1) / \code{out\_base} riuso (\#2). \\
\rowa 5 & \code{buf\_b\_base} & \checkmark & --- & Ping-pong B (\#1). \\
9 & \code{num\_neurons\_graph} & --- & \checkmark & Numero neuroni del grafo. \\
\rowa 10 & \code{n\_out} & --- & \checkmark & Numero id di uscita. \\
\bottomrule
\end{tabularx}
\begin{center}\footnotesize\itshape\color{fnGrey}
$\ast$ In Tipo \#2 i buffer ping-pong non servono: il selettore 4 è riusato come
\code{out\_base} (regione dove copiare le uscite). I selettori 1/2/6/7/8 riguardano solo
il percorso single-layer manuale (\op{START}), non \op{RUN\_NETWORK}.\end{center}
Per il Tipo \#1, i \code{w\_base}/\code{bias\_addr} \emph{per-layer} \textbf{non} si
impostano con \op{SET\_BASE}: sono campi della tabella descrittori. \op{SET\_NET\_TYPE}
default dopo \op{RESET} è \emph{dense}, quindi una rete \#1 funziona anche senza emetterlo.
% ======================================================================
\section{Tipo \#1 --- rete densa}
\subsection{Layout in memoria}
\begin{tabularx}{\textwidth}{L{3.4cm} Y}
\toprule
\rowh \thd{Struttura} & \thd{Formato} \\
\midrule
Input $X$ & \code{n\_inputs\_real} byte INT8 a \code{x\_base}. \\
\rowa Pesi (per layer) & Neuron-major: neurone $k$ a \code{w\_base + k*n\_inputs\_real}, \code{n\_neurons*n\_inputs} byte. \\
Bias (per layer) & Un byte INT8 per neurone a \code{bias\_addr}. \\
\rowa Tabella descrittori & \code{num\_layers} voci da 11 byte a \code{table\_base}. \\
Buffer A/B & Uscite intermedie ping-pong. \\
\bottomrule
\end{tabularx}
Descrittore (11 byte, MSB-first): \code{w\_base}(3) $|$ \code{bias\_addr}(3) $|$
\code{activation}(1) $|$ \code{n\_inputs\_real}(2) $|$ \code{n\_neurons\_real}(2).
\subsection{Esempio completo: rete $4\to4\to2$}
Layer~0: 4 input, 4 neuroni, ReLU. Layer~1: 4 input, 2 neuroni, lineare
(\code{PARALLEL}=2, quindi ogni \code{n\_inputs\_real} è multiplo di 2). Indirizzi scelti:
\code{table\_base}=\code{0x000000}, \code{x\_base}=\code{0x001000}, pesi/bias L0 a
\code{0x002000}/\code{0x002100}, L1 a \code{0x002200}/\code{0x002300}, buffer a
\code{0x003000}/\code{0x003100}.
\begin{lstlisting}[language=,caption={Tabella descrittori dense (22 byte)},basicstyle=\ttfamily\scriptsize]
Layer 0: 00 20 00 | 00 21 00 | 01 | 00 04 | 00 04
w_base bias_addr ReLU n_in=4 n_neu=4
Layer 1: 00 22 00 | 00 23 00 | 00 | 00 04 | 00 02
w_base bias_addr NONE n_in=4 n_neu=2
\end{lstlisting}
\begin{lstlisting}[language=,caption={Sessione SPI (dense)},basicstyle=\ttfamily\scriptsize]
0x0F RESET
0x11 01 SET_NET_TYPE = dense
0x01 000000 0016 <22 byte tabella> WRITE_RAM tabella
0x01 002000 0010 <16 byte pesi L0> WRITE_RAM pesi L0 (neuron-major)
0x01 002100 0004 <4 byte bias L0>
0x01 002200 0008 <8 byte pesi L1>
0x01 002300 0002 <2 byte bias L1>
0x01 001000 0004 <x0 x1 x2 x3> WRITE_RAM input X
0x10 00 001000 SET_BASE x_base
0x10 03 000000 SET_BASE table_base
0x10 04 003000 SET_BASE buf_a
0x10 05 003100 SET_BASE buf_b
0x23 02 RUN_NETWORK num_layers=2
0x21 ... poll STATUS finche' done=1
0x22 READ_OUTPUT -> 2 byte (layer finale)
\end{lstlisting}
\subsection{Pseudocodice host (dense)}
\begin{lstlisting}[language=,caption={Codifica e caricamento di una rete densa},basicstyle=\ttfamily\scriptsize]
def load_dense(layers, X): # layers in ordine di esecuzione
spi(RESET); spi(SET_NET_TYPE, DENSE)
table = b""
for L in layers: # L: pesi[n][k], bias[n], act, n_in, n_out
assert L.n_in % PARALLEL == 0
w = alloc(L.weights_neuron_major) # k lento, input veloce
b = alloc(L.bias)
table += u24(w)+u24(b)+u8(L.act)+u16(L.n_in)+u16(L.n_out)
write_ram(TABLE_BASE, table)
write_ram(X_BASE, X)
set_base(0, X_BASE); set_base(3, TABLE_BASE)
set_base(4, BUF_A); set_base(5, BUF_B)
spi(RUN_NETWORK, len(layers))
wait_status_done()
return read_output(layers[-1].n_out)
\end{lstlisting}
% ======================================================================
\section{Tipo \#2 --- rete a grafo}
\subsection{Layout in memoria}
\begin{tabularx}{\textwidth}{L{3.4cm} Y}
\toprule
\rowh \thd{Struttura} & \thd{Formato} \\
\midrule
Input $X$ & \code{N\_in} byte a \code{x\_base}; copiati in \code{act\_buf[0..N\_in-1]} all'avvio. \\
\rowa Tabella descrittori & \code{num\_neurons\_graph} voci da 11 byte a \code{table\_base}, in ordine di \code{out\_id} crescente. \\
Blocchi edge & Per neurone: \code{n\_conn} edge da 4 byte a \code{conn\_ptr}, con padding a multiplo di \code{PARALLEL} (edge peso 0). \\
\rowa Uscite & \code{n\_out} byte scritti a \code{out\_base} (=selettore 4). \\
\bottomrule
\end{tabularx}
Descrittore graph (11 byte): \code{conn\_ptr}(3) $|$ \code{n\_conn}(2) $|$ \code{out\_id}(2)
$|$ \code{activation}(1) $|$ \code{bias}(1) $|$ \code{reserved}(2). \quad
Edge (4 byte): \code{src\_id}(2) $|$ \code{weight}(1) $|$ \code{reserved}(1). \quad
Vincolo: \code{src\_id < out\_id} (DAG feed-forward).
\subsection{Esempio completo}
4 ingressi (id 0--3). Neurone n4 (\code{out\_id}=4, ReLU, bias=2) connesso agli id 0 e 1;
neurone n5 (\code{out\_id}=5, lineare, bias=0) connesso a n4 (id~4) e all'id~2; uscita = n5
(\code{n\_out}=1). \code{PARALLEL}=2, entrambi hanno 2 connessioni (nessun padding).
Indirizzi: \code{table\_base}=\code{0x000000}, edge a \code{0x000100}, \code{x\_base}=
\code{0x001000}, \code{out\_base}=\code{0x002000}.
\begin{lstlisting}[language=,caption={Descrittori + edge grafo},basicstyle=\ttfamily\scriptsize]
Descrittori (a 0x000000, 22 byte):
n4: 00 01 00 | 00 02 | 00 04 | 01 | 02 | 00 00
conn_ptr n_conn out_id ReLU bias rsv
n5: 00 01 08 | 00 02 | 00 05 | 00 | 00 | 00 00
conn_ptr n_conn out_id NONE bias rsv
Blocchi edge (a 0x000100, 4 byte/edge: src_id, weight, rsv):
n4 @0x000100: 00 00 05 00 (src=0, w=+5)
00 01 FD 00 (src=1, w=-3) ; -3 = 0xFD
n5 @0x000108: 00 04 02 00 (src=4, w=+2) ; id4 = uscita di n4
00 02 07 00 (src=2, w=+7)
\end{lstlisting}
\begin{lstlisting}[language=,caption={Sessione SPI (graph)},basicstyle=\ttfamily\scriptsize]
0x0F RESET
0x11 02 SET_NET_TYPE = graph
0x01 000000 0016 <22 byte tabella> WRITE_RAM descrittori
0x01 000100 0010 <16 byte edge> WRITE_RAM blocchi edge
0x01 001000 0004 <x0 x1 x2 x3> WRITE_RAM input X
0x10 00 001000 SET_BASE x_base
0x10 03 000000 SET_BASE table_base
0x10 04 002000 SET_BASE out_base (riuso sel 4)
0x10 09 000002 SET_BASE num_neurons_graph = 2
0x10 0A 000001 SET_BASE n_out = 1
0x23 00 RUN_NETWORK (dispatch a graph_engine)
0x21 ... poll STATUS (bit2=err se src_id>=out_id)
0x02 002000 0001 READ_RAM out_base -> 1 byte (uscita n5)
\end{lstlisting}
\subsection{Pseudocodice host (graph)}
\begin{lstlisting}[language=,caption={Codifica e caricamento di un grafo},basicstyle=\ttfamily\scriptsize]
def load_graph(neurons, X, n_out): # neurons ordinati per out_id crescente
spi(RESET); spi(SET_NET_TYPE, GRAPH)
edges = b""; table = b""
for N in neurons: # N: out_id, conns=[(src_id,w)...], act, bias
for (src,_) in N.conns:
assert src < N.out_id and src < N_TOTAL # regola DAG
conn_ptr = EDGE_BASE + len(edges)
padded = pad(N.conns, PARALLEL, fill=(0,0)) # edge peso 0
for (src,w) in padded:
edges += u16(src)+i8(w)+u8(0)
table += u24(conn_ptr)+u16(len(N.conns))+u16(N.out_id) \
+ u8(N.act)+i8(N.bias)+u16(0)
write_ram(TABLE_BASE, table); write_ram(EDGE_BASE, edges)
write_ram(X_BASE, X)
set_base(0, X_BASE); set_base(3, TABLE_BASE); set_base(4, OUT_BASE)
set_base(9, len(neurons)); set_base(10, n_out)
spi(RUN_NETWORK, 0) # payload ignorato in graph
wait_status_done()
return read_ram(OUT_BASE, n_out)
\end{lstlisting}
\subsection{Pseudo-assembly \texttt{netasm}}
La descrizione leggibile viene compilata dall'assemblatore host (\code{tools/netasm/})
esattamente nei byte delle tabelle e degli edge sopra. Esempio equivalente al grafo
dell'esempio:
\begin{lstlisting}[language=,caption={netasm: sorgente e byte generati},basicstyle=\ttfamily\scriptsize]
; --- sorgente ---
NET graph
INPUTS 4 ; id 0..3
NEURON n4 relu bias=2
CONN 0 w=5
CONN 1 w=-3
NEURON n5 none bias=0
CONN n4 w=2 ; riferimento simbolico -> id 4
CONN 2 w=7
OUTPUT n5
END
; --- l'assemblatore emette ---
; id assegnati: n4=4, n5=5 (garantito src_id < out_id)
; descrittori: 00 01 00 00 02 00 04 01 02 00 00
; 00 01 08 00 02 00 05 00 00 00 00
; edge: 00 00 05 00 00 01 FD 00 (n4)
; 00 04 02 00 00 02 07 00 (n5)
; registri: table_base, x_base, out_base, num_neurons=2, n_out=1
; validato a compile-time: src_id<out_id, N_TOTAL, padding a PARALLEL
\end{lstlisting}
\begin{fnnote}[Perche' due livelli di codifica]
Lo pseudocodice host e il \code{netasm} producono gli \emph{stessi byte}. Il primo è utile
quando la rete è generata a runtime (es. pesi da training); il secondo quando la topologia
è scritta a mano o versionata come sorgente. In entrambi i casi l'FPGA riceve solo tabelle
e dati via \op{WRITE\_RAM}: nessun interprete a bordo.
\end{fnnote}