Files
FPGA-Neural-Datasheet/files/docs/datasheet/en/chapters/03-datapath.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

167 lines
7.3 KiB
TeX

\chapter{Compute datapath}
\label{ch:datapath}
\section{INT8/INT32 arithmetic chain}
The elementary datapath implements the typical sequence of a quantized neuron:
\begin{center}
\begin{tikzpicture}[font=\scriptsize,node distance=3mm,start chain=going right,
every node/.style={fnblock,minimum width=15mm,minimum height=8mm,on chain}]
\node[fnblockT]{INT8\\$\times$\,INT8};
\node{INT16\\product};
\node{sign-ext\\INT32};
\node[fnblockD]{accumulate\\INT32};
\node{$+$ bias};
\node[fnblockA]{activation};
\node[fnblockT]{sat. INT8};
\foreach \i [count=\j from 2] in {1,...,6}
\draw[fnarrow] (chain-\i) -- (chain-\j);
\end{tikzpicture}
\end{center}
Each INT8$\times$INT8 product fits in 16~bits; it is sign-extended to 32~bits before
accumulation, so the accumulator does not overflow on long vectors. Bias and activation
operate at 32~bits; only the final output is saturated to INT8.
\section{\texttt{mac\_unit} --- multiply-accumulator}
The \code{mac\_unit} module is purely combinational and parametric on \code{DATA\_WIDTH}
and \code{ACC\_WIDTH}. It computes:
\[
\mathrm{acc\_out} = \mathrm{acc\_in} + \mathrm{signext}_{ACC}(x \cdot w)
\]
The product has width $2\times$\code{DATA\_WIDTH} and is sign-extended by replicating
the most significant bit. On ECP5 the multiplication maps onto a \code{MULT18X18D} DSP
block.
\begin{lstlisting}[caption={\texttt{rtl/mac\_unit.v} --- arithmetic core},label={lst:macunit}]
localparam PROD_WIDTH = 2 * DATA_WIDTH;
wire signed [PROD_WIDTH-1:0] product = x * w;
wire signed [ACC_WIDTH-1:0] product_ext =
{{(ACC_WIDTH-PROD_WIDTH){product[PROD_WIDTH-1]}}, product};
assign acc_out = acc_in + product_ext;
\end{lstlisting}
\section{\texttt{mac8} --- parallel MAC and balanced adder tree}
\code{mac8} instantiates \code{PARALLEL} \code{mac\_unit} units that generate
\code{PARALLEL} independent products, then sums them with a \emph{balanced binary
adder tree}. Compared to the linear reduction
$((((p_0{+}p_1){+}p_2){+}p_3){+}\dots)$, of depth $O(\text{PARALLEL})$, the tree has
depth $O(\log_2 \text{PARALLEL})$, drastically reducing the combinational path.
\begin{center}
\begin{tikzpicture}[font=\scriptsize,level distance=11mm,
every node/.style={fnreg,minimum width=8mm},
level 1/.style={sibling distance=30mm},
level 2/.style={sibling distance=15mm},
level 3/.style={sibling distance=8mm},
edge from parent/.style={fnarrowT,draw}]
\node[fnblockD]{sum}
child {node[fnblockT]{$+$}
child {node[fnblockT]{$+$}
child {node{$p_0$}} child {node{$p_1$}}}
child {node[fnblockT]{$+$}
child {node{$p_2$}} child {node{$p_3$}}}}
child {node[fnblockT]{$+$}
child {node[fnblockT]{$+$}
child {node{$p_4$}} child {node{$p_5$}}}
child {node[fnblockT]{$+$}
child {node{$p_6$}} child {node{$p_7$}}}};
\end{tikzpicture}
\end{center}
\begin{center}\footnotesize\itshape\color{fnGrey}
Example with PARALLEL=8: 3 levels. PARALLEL=16 $\to$ 4 levels; PARALLEL=32 $\to$ 5
levels.\end{center}
\begin{fnnote}[PARALLEL as a power of two]
The tree is designed for \code{PARALLEL} as a power of two (8, 16, 32\ldots). This is
also the value used in all project configurations.
\end{fnnote}
\section{\texttt{neuron\_parallel} --- neuron FSM}
\code{neuron\_parallel} processes \code{N\_INPUTS} inputs in groups of \code{PARALLEL},
maintaining the accumulator from one group to the next. At the end it adds the bias,
applies the activation and saturates to INT8. The number of groups is
$\text{GROUPS}=\text{N\_INPUTS}/\text{PARALLEL}$.
\begin{center}
\begin{tikzpicture}[font=\scriptsize,node distance=4mm,start chain=going below,
every node/.style={on chain,fnblock,minimum width=46mm}]
\node[fnblockA]{\code{start}};
\node{group 0 $\to$ accumulate};
\node{group 1 $\to$ accumulate};
\node[draw=none,fill=none]{\vdots};
\node{group GROUPS$-$1 $\to$ accumulate};
\node{$+$ bias};
\node[fnblockA]{activation (ACT\_RELU / ACT\_NONE)};
\node[fnblockT]{INT8 saturation};
\node[fnblockD]{\code{done}, \code{y}};
\foreach \i [count=\j from 2] in {1,...,8}
\draw[fnarrow] (chain-\i) -- (chain-\j);
\end{tikzpicture}
\end{center}
\subsection{Parameter guard (elaboration-time)}
If \code{PARALLEL} does not exactly divide \code{N\_INPUTS} two failures occur, both
confirmed empirically in \code{sim/parameter\_sweep\_tb.v}:
\begin{itemize}
\item integer division truncates \code{GROUPS} and the excess inputs are never read
$\to$ \textbf{wrong} result, with no error and no warning;
\item if \code{PARALLEL > N\_INPUTS}, \code{GROUPS=0} and the terminal condition is
never satisfied $\to$ the neuron \textbf{hangs} (busy high, done never asserted).
\end{itemize}
The solution does not modify the validated datapath: a \code{generate} block
instantiates a deliberately undefined module when
$\text{N\_INPUTS} \bmod \text{PARALLEL}\neq0$, forcing an error at \emph{elaboration}
both in simulation and in synthesis. For valid configurations the branch is never
elaborated.
\begin{lstlisting}[caption={\texttt{rtl/neuron\_parallel.v} --- parameter guard}]
generate
if (N_INPUTS == 0 || N_INPUTS % PARALLEL != 0) begin : PARAMETER_ERROR
neuron_parallel_requires_N_INPUTS_multiple_of_PARALLEL
invalid_parameter_combination();
end
endgenerate
\end{lstlisting}
\begin{fnnote}[Edge case \texttt{N\_INPUTS=0} (fixed 2026-09-04)]
The original condition (\code{N\_INPUTS \% PARALLEL != 0}) does not catch
\code{N\_INPUTS=0}, since $0 \bmod \text{PARALLEL}=0$ for any \code{PARALLEL}: the module
elaborated successfully (both in simulation and in real Yosys synthesis) while leaving
\code{x\_bus}/\code{w\_bus} undriven and \code{start} silently ineffective. Found during
the re-certification campaign (\code{docs/validation/bugs.md}, BUG-002) and fixed by
extending the guard as above --- \code{N\_INPUTS=0} now fails elaboration exactly like the
other degenerate cases.
\end{fnnote}
\section{Activation functions}
\code{neuron\_parallel} accepts a 2-bit \code{activation} port. The default is
\code{ACT\_RELU}, the only behavior that existed before the port was introduced, so
every pre-existing caller remains unchanged.
\begin{tabularx}{\textwidth}{L{2.6cm} C{1.4cm} Y}
\toprule
\rowh \thd{Encoding} & \thd{Value} & \thd{Behavior} \\
\midrule
\code{ACT\_NONE} & \code{2'd0} & Linear: no clamp to zero, bilateral saturation to the INT8 range $[-128,+127]$. \\
\rowa \code{ACT\_RELU} & \code{2'd1} & $\max(0,x)$, then positive saturation to $+127$ (default; also the fallback for reserved encodings). \\
\bottomrule
\end{tabularx}
\section{INT8 saturation}
After bias and activation, the 32-bit accumulator is reduced to INT8:
\[
y=\begin{cases}
+127 & \text{if } \mathrm{final\_acc} > 127\\
-128 & \text{if } \mathrm{final\_acc} < -128 \ \text{(ACT\_NONE only)}\\
0 & \text{if } \mathrm{final\_acc}\le 0 \ \text{(ACT\_RELU only)}\\
\mathrm{final\_acc}[7:0] & \text{otherwise}
\end{cases}
\]
\section{\texttt{layer} --- neurons in parallel}
\code{layer} instantiates \code{N\_NEURONS} neurons that share the input vector
\code{x\_bus} but have distinct weights and bias; \code{busy} is the OR and \code{done}
the AND of the neurons' signals. It is the module used in the datapath benchmarks
(ch.~\ref{ch:impl}), where all neurons work simultaneously. The addressing convention
is neuron-major: the weights of neuron $n$ occupy
\code{weights\_bus[n*N\_INPUTS*DATA\_WIDTH +: N\_INPUTS*DATA\_WIDTH]}.