167 lines
7.3 KiB
TeX
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]}.
|