docs: extract V2 datasheet into submodule
This commit is contained in:
Submodule
+1
Submodule hardware/v2/docs/datasheet added at 07f8ec0460
@@ -1,21 +0,0 @@
|
||||
# LaTeX build artifacts (latexmk et al.) -- sources and rendered PDFs
|
||||
# are versioned, intermediate build byproducts are not.
|
||||
*.aux
|
||||
*.log
|
||||
*.out
|
||||
*.toc
|
||||
*.lof
|
||||
*.lot
|
||||
*.fls
|
||||
*.fdb_latexmk
|
||||
*.synctex.gz
|
||||
*.bbl
|
||||
*.bcf
|
||||
*.blg
|
||||
*.run.xml
|
||||
*.nav
|
||||
*.snm
|
||||
*.vrb
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
Binary file not shown.
@@ -1,134 +0,0 @@
|
||||
% ======================================================================
|
||||
% FPGA-Neural V2 -- Neural Multiprocessor / Dataflow Machine
|
||||
% Datasheet / Technical reference manual
|
||||
% Repository: github.com/manvalan/FPGA-Neural
|
||||
% ======================================================================
|
||||
\documentclass[11pt,a4paper,openany]{report}
|
||||
|
||||
\newcommand{\datasheetrev}{B2}
|
||||
\newcommand{\datasheetdate}{September 2026}
|
||||
|
||||
\input{preamble}
|
||||
|
||||
\begin{document}
|
||||
\sloppy
|
||||
|
||||
% ======================================================================
|
||||
% TITLE PAGE
|
||||
% ======================================================================
|
||||
\begin{titlepage}
|
||||
\thispagestyle{empty}
|
||||
\begin{tikzpicture}[remember picture,overlay]
|
||||
\fill[fnDark] (current page.north west) rectangle
|
||||
([yshift=-4.3cm]current page.north east);
|
||||
\fill[fnTeal] ([yshift=-4.3cm]current page.north west) rectangle
|
||||
([yshift=-4.55cm]current page.north east);
|
||||
\node[anchor=north west,text=white,font=\Huge\bfseries]
|
||||
at ([xshift=2.2cm,yshift=-1.15cm]current page.north west)
|
||||
{FPGA\,--\,Neural~V2};
|
||||
\node[anchor=north west,text=fnLight,font=\large]
|
||||
at ([xshift=2.25cm,yshift=-2.15cm]current page.north west)
|
||||
{Neural Multiprocessor / Dataflow Machine};
|
||||
\node[anchor=north west,text=fnLight2,font=\normalsize]
|
||||
at ([xshift=2.25cm,yshift=-2.85cm]current page.north west)
|
||||
{N\_SLOTS-way concurrent INT8 accelerator -- Datasheet and reference manual};
|
||||
\node[anchor=north east,text=white,font=\ttfamily\small]
|
||||
at ([xshift=-2.2cm,yshift=-3.55cm]current page.north east)
|
||||
{Rev.~\datasheetrev~~\textbullet~~\datasheetdate};
|
||||
\end{tikzpicture}
|
||||
|
||||
\vspace*{5.0cm}
|
||||
|
||||
% --- compact block diagram on the title page ---
|
||||
\begin{center}
|
||||
\resizebox{\textwidth}{!}{%
|
||||
\begin{tikzpicture}[node distance=7mm and 10mm]
|
||||
\node[fnblockD,minimum width=26mm] (host) {HOST\\{\scriptsize graph loader}};
|
||||
\node[fnblockT,right=14mm of host,minimum width=30mm] (dm) {Dependency\\Manager};
|
||||
\node[fnblockT,right=14mm of dm,minimum width=28mm] (dir) {Neural\\Director};
|
||||
\node[fnblock,right=14mm of dir,minimum width=34mm] (slots) {N\_SLOTS $\times$ (Memory\\Manager $+$ Neural Proc.)};
|
||||
\node[fnblock,right=10mm of slots,minimum width=20mm] (ram) {SDRAM\\{\scriptsize 64\,MB}};
|
||||
\draw[fnbus] (host) -- (dm);
|
||||
\draw[fnbus] (dm) -- (dir);
|
||||
\draw[fnbus] (dir) -- (slots);
|
||||
\draw[fnbus] (slots) -- node[fnlbl,above]{16-bit word} (ram);
|
||||
\node[below=1mm of slots,font=\scriptsize\itshape,text=fnGrey]
|
||||
{computation entirely on-chip, dependency graph resolved autonomously};
|
||||
\end{tikzpicture}%
|
||||
}
|
||||
\end{center}
|
||||
|
||||
\vfill
|
||||
\begin{center}
|
||||
\begin{tikzpicture}
|
||||
\node[draw=fnRule,rounded corners=3pt,inner sep=10pt,fill=fnLight,text width=15.5cm]{
|
||||
\footnotesize
|
||||
\textbf{\color{fnDark}Reference target device:} Lattice ECP5 \code{LFE5U-45F-8BG381C}
|
||||
(speed grade $-8$, CABGA381) --- identical device and board as V1.\\[2pt]
|
||||
\textbf{\color{fnDark}Production configuration:} INT8/INT32, \code{P\_IN}=8,
|
||||
\code{N\_SLOTS}=4 (real 8/8-seed timing closure at 64\,MHz --- see
|
||||
ch.~\ref{ch:hw}), single unified SDR SDRAM (Alliance Memory
|
||||
\code{AS4C32M16SB-7BIN}, 64\,MB), real board-level pinout and KiCad
|
||||
schematic/BOM.\\[2pt]
|
||||
\textbf{\color{fnDark}Status:} RTL verified in real Verilator simulation and real
|
||||
synthesis + place\&route (Yosys + nextpnr-ecp5). Full benchmark campaign, two
|
||||
post-campaign memory optimizations, an alternative memory-subsystem
|
||||
redesign that became the current architecture (the Neural Memory System,
|
||||
ch.~\ref{ch:nms}), and a real, board-level schematic/BOM verification pass
|
||||
(ch.~\ref{ch:hw}) all complete and measured. Document describing the
|
||||
project as of \datasheetdate.
|
||||
};
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\vspace{0.6cm}
|
||||
{\footnotesize\color{fnGrey}\raggedright
|
||||
Project author: Michele Bigi \textbullet{} MIKILAB / manvalan.\\
|
||||
This datasheet documents V2 of the RTL code, documentation and benchmarks
|
||||
present in the repository \texttt{github.com/manvalan/FPGA-Neural}. V1 remains
|
||||
frozen and unmodified as the project's golden functional/performance
|
||||
reference; its own datasheet previously lived alongside this one in this
|
||||
repository and was consolidated out of the working tree as part of a
|
||||
2026-09-09 documentation cleanup (recoverable from git history).\par}
|
||||
\end{titlepage}
|
||||
|
||||
% ======================================================================
|
||||
% "FEATURES" PAGE (datasheet style)
|
||||
% ======================================================================
|
||||
\input{chapters/00-features}
|
||||
|
||||
% ======================================================================
|
||||
% PINOUT SUMMARY (real, board-verified ball assignment)
|
||||
% ======================================================================
|
||||
\newpage
|
||||
\input{chapters/00b-pinout}
|
||||
|
||||
% ======================================================================
|
||||
% TABLE OF CONTENTS
|
||||
% ======================================================================
|
||||
\newpage
|
||||
\pagenumbering{roman}
|
||||
{\color{fnDark}\tableofcontents}
|
||||
\newpage
|
||||
\pagenumbering{arabic}
|
||||
|
||||
% ======================================================================
|
||||
% CHAPTERS
|
||||
% ======================================================================
|
||||
\include{chapters/01-overview}
|
||||
\include{chapters/02-architecture}
|
||||
\include{chapters/03-datapath}
|
||||
\include{chapters/04-parameters}
|
||||
\include{chapters/05-memory}
|
||||
\include{chapters/06-scheduling}
|
||||
\include{chapters/07-hostinterface}
|
||||
\include{chapters/08-toplevel}
|
||||
\include{chapters/09-implementation}
|
||||
\include{chapters/10-hardware}
|
||||
\include{chapters/11-registers}
|
||||
\include{chapters/12-roadmap}
|
||||
\include{chapters/13-nms}
|
||||
|
||||
\appendix
|
||||
\include{chapters/A-modules}
|
||||
|
||||
\end{document}
|
||||
@@ -1,144 +0,0 @@
|
||||
\thispagestyle{plain}
|
||||
\noindent
|
||||
\begin{tikzpicture}
|
||||
\node[fill=fnDark,text=white,rounded corners=2pt,inner sep=6pt,
|
||||
minimum width=\textwidth,anchor=west]
|
||||
{\large\bfseries FPGA-Neural V2 --- General description and features};
|
||||
\end{tikzpicture}
|
||||
|
||||
\vspace{6pt}
|
||||
\noindent
|
||||
{\small FPGA-Neural V2 is a \textbf{neural multiprocessor / dataflow machine},
|
||||
the evolution of the V1 sequential accelerator (documented separately, frozen
|
||||
and unmodified as the project's golden reference). Where V1 executes one
|
||||
neuron at a time under host-driven SPI control, V2 registers a
|
||||
\textbf{dependency graph of neurons} and keeps \code{N\_SLOTS} independent
|
||||
Neural Processor $+$ Memory Manager pairs busy concurrently, resolving data
|
||||
dependencies and hiding memory latency in hardware, without host
|
||||
intervention once a graph is loaded. Computation (INT8 MAC, ReLU,
|
||||
saturation) is bit-exact identical to V1's own datapath; what changed is
|
||||
everything \emph{around} it, including, mid-project, the external memory
|
||||
device itself (\S\ref{sec:sdram-mem-addendum}).}
|
||||
|
||||
\vspace{8pt}
|
||||
\begin{multicols}{2}
|
||||
{\color{fnDark}\large\bfseries Features}\\[2pt]
|
||||
{\footnotesize
|
||||
\begin{itemize}[leftmargin=1.1em]
|
||||
\item \textbf{Dependency-graph scheduling}: nodes are registered with an
|
||||
explicit producer list; a node becomes eligible for execution only
|
||||
once every producer it depends on has genuinely completed --- verified
|
||||
for 1-hop shared-producer/multi-consumer graphs and 2-hop transitive
|
||||
(diamond) graphs.
|
||||
\item \code{N\_SLOTS}=4 independent \textbf{Neural Processor + Memory
|
||||
Manager} pairs (production baseline), each running the identical
|
||||
8-stage INT8 pipeline inherited from V1.
|
||||
\item \textbf{Single unified SDRAM}: one external SDR SDRAM device serves
|
||||
weights, activations, AND results through one arbitrated backend
|
||||
(\code{sdram\_unified\_backend.v}) --- no PSRAM, no second physical
|
||||
memory device, in the current, frozen hardware path.
|
||||
\item \textbf{Real physical host transport}: a placed, ball-assigned SPI
|
||||
Mode~0 slave (\code{spi\_host\_bridge.v}) plus a real
|
||||
\code{FPGA\_DATA\_READY} completion pin --- both verified on real
|
||||
\code{nextpnr-ecp5} place\&route, not just in simulation.
|
||||
\item \textbf{Real, board-level verification}: a real KiCad schematic
|
||||
capture, a real exported BOM, and real component selections
|
||||
(regulators, oscillator, configuration flash) all cross-checked
|
||||
against this datasheet --- not merely a simulated design.
|
||||
\item \textbf{Real, measured} characterization at every step: Verilator
|
||||
RTL simulation, Yosys synthesis, real \code{nextpnr-ecp5}
|
||||
place\&route --- no theoretical number reported without a matching
|
||||
real measurement.
|
||||
\end{itemize}}
|
||||
|
||||
\columnbreak
|
||||
|
||||
{\color{fnDark}\large\bfseries Honest, measured limitations}\\[2pt]
|
||||
{\footnotesize
|
||||
\begin{itemize}[leftmargin=1.1em]
|
||||
\item \code{N\_SLOTS}=8 is \textbf{functionally correct but not
|
||||
timing-closed}: only 3/8 tested placement seeds pass 64\,MHz ---
|
||||
deferred, not production-frozen (\S\ref{sec:clock-closure-current}).
|
||||
\item \textbf{Hold-time closure is a genuine, disclosed tool-chain
|
||||
limitation}: no \code{pytrellis}/vendor static-timing-analysis path
|
||||
is available in this environment to check min-delay/hold, only
|
||||
setup (\S\ref{sec:clock-closure-current}).
|
||||
\item \textbf{FPGA dynamic power/current draw is not measured}: no ECP5
|
||||
power estimator is available in this toolchain; regulator sizing
|
||||
uses datasheet-based engineering margin, not a computed budget
|
||||
(\S\ref{sec:power-addendum}).
|
||||
\item Fixed, lowest-index-priority arbitration (Director and memory
|
||||
arbiter alike) is not fairness-balanced --- a real, measured
|
||||
per-slot workload imbalance exists under sustained contention.
|
||||
\end{itemize}}
|
||||
|
||||
\vspace{4pt}
|
||||
{\color{fnDark}\large\bfseries Target \& toolchain}\\[2pt]
|
||||
{\footnotesize
|
||||
\begin{itemize}[leftmargin=1.1em]
|
||||
\item FPGA: Lattice ECP5 \code{LFE5U-45F-8BG381C} ($-8$, commercial grade,
|
||||
381-ball caBGA, 0.8\,mm pitch) --- same target device as V1.
|
||||
\item SDRAM: Alliance Memory \code{AS4C32M16SB-7BIN} (512\,Mbit/64\,MB,
|
||||
4M$\times$16, 54-ball FBGA).
|
||||
\item Synthesis: Yosys; place\&route: real \code{nextpnr-ecp5} 0.11.1.
|
||||
\item Simulation: Verilator 5.050 (\code{--binary --timing}) --- adopted
|
||||
for V2 after two independent Icarus Verilog v13.0 scheduling
|
||||
defects were found and reproduced on minimal repros (V1's own
|
||||
certification, performed separately, was unaffected).
|
||||
\end{itemize}}
|
||||
\end{multicols}
|
||||
|
||||
\vspace{2pt}
|
||||
% --- key parameter table ---
|
||||
\noindent
|
||||
{\small\color{fnDark}\bfseries Key parameters (production configuration,
|
||||
real measured data)}
|
||||
\vspace{2pt}
|
||||
|
||||
\noindent
|
||||
\begin{tabularx}{\textwidth}{L{3.6cm}L{3.6cm}Y}
|
||||
\toprule
|
||||
\rowh \thd{Quantity} & \thd{Value} & \thd{Notes} \\
|
||||
\midrule
|
||||
Data precision & INT8 (signed) & \code{DATA\_WIDTH}=8, identical to V1 \\
|
||||
\rowa Accumulator & INT32 (signed) & \code{ACC\_WIDTH}=32 \\
|
||||
Dot-product width & 8 & \code{P\_IN}=8 parallel MAC lanes per neuron \\
|
||||
\rowa Production concurrency & \code{N\_SLOTS}=4 & real, 8/8-seed timing closure; see \S\ref{sec:clock-closure-current} \\
|
||||
System clock & 64\,MHz & 16\,MHz oscillator $\to$ \code{EHXPLLL} PLL; 80\,MHz confirmed NO-GO (genuine regenerated PLL, 0/8 seeds) \\
|
||||
\rowa Fmax, \code{N\_SLOTS}=4 (real P\&R, 8 seeds) & worst 64.55\,MHz / best 72.37\,MHz & production baseline, 8/8 PASS \\
|
||||
D-Stress regression (256 neurons) & 49,927 cycles, 256/256 bit-exact & 780\,\textmu s wall-clock @ 64\,MHz \\
|
||||
\rowa SPI host clock, verified & 12\,MHz recommended (12.8\,MHz hard CDC edge) & simulation-verified, real margin below the deterministic edge \\
|
||||
Address space & 26~bit (byte), single SDRAM & \code{ADDR\_WIDTH}=26 \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\vspace{8pt}
|
||||
\noindent
|
||||
{\small\color{fnDark}\bfseries System block diagram}
|
||||
\begin{center}
|
||||
\resizebox{\textwidth}{!}{%
|
||||
\begin{tikzpicture}[node distance=6mm and 9mm,font=\footnotesize]
|
||||
\node[fnblockD,minimum width=24mm,minimum height=15mm] (host){HOST (SPI)\\{\scriptsize registers a node graph}};
|
||||
\node[fnblockT,right=14mm of host,minimum width=30mm,minimum height=13mm] (dm){Dependency\\Manager};
|
||||
\node[fnblockT,right=14mm of dm,minimum width=28mm,minimum height=13mm] (dir){Neural\\Director};
|
||||
\node[fnreg,fill=white,right=14mm of dir,minimum width=30mm,minimum height=20mm] (slots){
|
||||
\begin{tabular}{c}
|
||||
N\_SLOTS=4 $\times$ \\
|
||||
Memory Manager \\
|
||||
$+$ Neural Processor
|
||||
\end{tabular}};
|
||||
\node[fnblock,right=14mm of slots,minimum width=26mm,minimum height=15mm] (ram){SDRAM 64\,MB\\{\scriptsize unified backend}};
|
||||
\draw[fnbus] (host) -- (dm);
|
||||
\draw[fnbus] (dm) -- node[fnlbl,above]{ready node} (dir);
|
||||
\draw[fnbus] (dir) -- (slots);
|
||||
\draw[fnbus] (slots) -- node[fnlbl,above]{W / AR ports} (ram);
|
||||
\draw[fnarrowT] (slots.south) |- ++(0,-4mm) -| node[fnlbl,below]{producer done} (dm.south);
|
||||
\end{tikzpicture}%
|
||||
}
|
||||
\end{center}
|
||||
\begin{center}\footnotesize\itshape\color{fnGrey}
|
||||
A slot's completion feeds back to the Director (frees the slot) and to the
|
||||
Dependency Manager (wakes up any node waiting on it) --- closing the
|
||||
dataflow loop entirely on-chip. \code{FPGA\_DATA\_READY} (ball G3) goes high
|
||||
once every registered node has both resolved and dispatched
|
||||
(\S\ref{sec:host-addendum}).\end{center}
|
||||
@@ -1,67 +0,0 @@
|
||||
\thispagestyle{plain}
|
||||
\noindent
|
||||
\begin{tikzpicture}
|
||||
\node[fill=fnDark,text=white,rounded corners=2pt,inner sep=6pt,
|
||||
minimum width=\textwidth,anchor=west]
|
||||
{\large\bfseries Pinout summary --- real, board-verified};
|
||||
\end{tikzpicture}
|
||||
|
||||
\vspace{6pt}
|
||||
\noindent
|
||||
{\footnotesize
|
||||
V2's top-level module, \code{fpga\_neural\_v2\_top.v}, has a complete,
|
||||
real ball assignment: every signal --- SDRAM bus, SPI host transport,
|
||||
clock/reset, \code{FPGA\_DATA\_READY}, JTAG, configuration mode straps,
|
||||
and the boot flash's dedicated MSPI pins --- carries a real CABGA381 ball
|
||||
site, sourced from the official Lattice pinout CSV (rev 3.0) and
|
||||
cross-checked against Project Trellis's own \code{iodb.json}. This
|
||||
supersedes an earlier V2 milestone in which the board-level top had been
|
||||
placed only \textbf{unconstrained}; a full, constrained \code{.lpf} now
|
||||
exists (\code{hardware/v2/constraints/v2\_board\_top.lpf}) and every
|
||||
Fmax number in this datasheet (\S\ref{sec:clock-closure-current}) is
|
||||
measured against it.
|
||||
}
|
||||
|
||||
\vspace{6pt}
|
||||
\begin{fnnote}[What is real]
|
||||
Every ball in the summary table below is placed, P\&R-confirmed, and
|
||||
cross-checked against a real, exported KiCad schematic and BOM
|
||||
(\S\ref{sec:schematic-capture}--\ref{sec:bom}) --- not a simulation-only
|
||||
placeholder. No PSRAM signals exist anywhere in this revision: the
|
||||
single external memory is SDR SDRAM (\S\ref{sec:sdram-mem-addendum}).
|
||||
\end{fnnote}
|
||||
|
||||
\begin{fnwarn}[What remains open]
|
||||
FPGA dynamic power/current draw has not been measured post-implementation
|
||||
(no ECP5 power estimator is available in this toolchain), so exact
|
||||
decoupling/regulator sizing uses datasheet-based engineering margin, not
|
||||
a computed budget. Hold-time closure is a genuine tool-chain limitation
|
||||
(no min-delay analysis path available) --- setup timing is fully
|
||||
verified. See ch.~\ref{ch:hw} for the complete, disclosed list.
|
||||
\end{fnwarn}
|
||||
|
||||
\vspace{6pt}
|
||||
\noindent
|
||||
{\small\color{fnDark}\bfseries Ball summary (see ch.~\ref{ch:hw} for the
|
||||
complete, per-signal table)}
|
||||
\vspace{2pt}
|
||||
|
||||
\noindent
|
||||
\begin{tabularx}{\textwidth}{L{3.4cm}L{2.4cm}Y}
|
||||
\toprule
|
||||
\rowh \thd{Interface} & \thd{Ball count} & \thd{Notes} \\
|
||||
\midrule
|
||||
SDRAM bus (A[0:12], BA[0:1], DQ[0:15], DQM[0:1], CKE/CS\#/RAS\#/CAS\#/WE\#) & 35 & Bank 6/7, real, P\&R-confirmed \\
|
||||
\rowa SPI host transport (\code{sclk}/\code{mosi}/\code{miso}/\code{cs\_n}) & 4 & Bank 6/7, plain GPIO \\
|
||||
\code{FPGA\_DATA\_READY}, \code{osc\_clk}, \code{ext\_rst\_n}, \code{sdram\_clk}, \code{pll\_locked} & 5 & Bank 6/7 \\
|
||||
\rowa JTAG (TCK/TMS/TDI/TDO) & 4 & Bank 40, to ESP32 \\
|
||||
Config control (PROGRAMN/INITN/DONE) + CFG[2:0] straps & 6 & Bank 8 \\
|
||||
\rowa Boot-flash dedicated MSPI (CSSPIN/MCLK/D0/D1) & 4 & Bank 8, dual-function \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\vspace{4pt}
|
||||
\noindent
|
||||
{\footnotesize\color{fnGrey}
|
||||
Complete per-signal ball tables and the real KiCad schematic/BOM: ch.~\ref{ch:hw}.
|
||||
Logical (not physical) register-level port list: ch.~\ref{ch:regs}.\par}
|
||||
@@ -1,99 +0,0 @@
|
||||
\chapter{Overview and design philosophy}
|
||||
\label{ch:overview}
|
||||
|
||||
\section{From sequential accelerator to dataflow machine}
|
||||
V1 is, structurally, a single pipeline: one neuron computes at a time,
|
||||
driven by the host over SPI, one MAC group at a time, one layer at a
|
||||
time. It is fast for what it is (the V1 datasheet's own ``ECP5
|
||||
implementation'' chapter documents its real Fmax/timing-closure history),
|
||||
but it cannot keep
|
||||
more than one computational unit genuinely busy at once, and it has no
|
||||
notion of a dependency graph --- the host sequences everything.
|
||||
|
||||
V2 keeps V1's own proven INT8 datapath (bit-exact, byte-for-byte reused
|
||||
math) but wraps it in a fundamentally different control architecture:
|
||||
a \textbf{Dependency Manager} tracks a graph of neuron ``jobs'', each
|
||||
with an explicit list of producer nodes it depends on; a \textbf{Neural
|
||||
Director} dispatches every node whose dependencies have resolved to
|
||||
whichever of \code{N\_SLOTS} concurrent (Memory Manager $+$ Neural
|
||||
Processor) pairs is free; a slot's completion feeds back to wake up any
|
||||
node that was waiting on it. Once a graph is loaded, the whole system
|
||||
runs autonomously --- no per-neuron host intervention.
|
||||
|
||||
\section{What did NOT change}
|
||||
\begin{itemize}
|
||||
\item The INT8$\times$INT8$\to$INT32 MAC math, the balanced adder tree,
|
||||
ReLU/linear activation with saturation --- \code{neural\_processor.v}
|
||||
is a direct, bit-exact-verified port of V1's own
|
||||
\code{neuron\_parallel.v}/\code{mac8.v}/\code{mac\_unit.v}.
|
||||
\item V1's own PSRAM backend files (\code{memory\_interface.v},
|
||||
\code{psram\_controller.v}) remain byte-for-byte, unmodified
|
||||
copies throughout the repository --- V1 itself, as a tree
|
||||
(\code{hardware/v1/}), is frozen and was never touched.
|
||||
\textbf{Not currently part of V2's physical board}, however: the
|
||||
project has since replaced external memory with a single SDR
|
||||
SDRAM device (\S\ref{sec:sdram-mem-addendum}); the PSRAM-era
|
||||
chapters that follow document real, correctly-measured work for
|
||||
the architecture it was measured on, not the current board.
|
||||
\item The target device (Lattice ECP5 \code{LFE5U-45F-8BG381C}) and the
|
||||
real-toolchain-only measurement discipline: every number in this
|
||||
datasheet is labelled \textsc{Theoretical}, \textsc{Simulated},
|
||||
\textsc{Post-P\&R measured}, or \textsc{Derived}, and no result was
|
||||
invented to make V2 look better than it measured (§\ref{ch:impl2}).
|
||||
\end{itemize}
|
||||
|
||||
\section{What DID change}
|
||||
\begin{itemize}
|
||||
\item \textbf{Concurrency}: from one active neuron to \code{N\_SLOTS}
|
||||
independent Neural Processor instances, each fed by its own Memory
|
||||
Manager.
|
||||
\item \textbf{Scheduling}: from host-sequenced SPI opcodes to an on-chip
|
||||
dependency graph, resolved autonomously.
|
||||
\item \textbf{Memory backend granularity}: from byte-at-a-time fetches
|
||||
(through \code{int8\_memory\_access.v}, still frozen V1 but no
|
||||
longer instantiated in V2's own datapath) to word-level bursts
|
||||
talking to \code{memory\_interface.v} directly --- a real, measured
|
||||
2.24--2.37$\times$ speedup (ch.~\ref{ch:mem}).
|
||||
\item \textbf{Memory traffic pattern}: a new shared on-chip
|
||||
\textbf{activation cache} eliminates redundant re-fetching of an
|
||||
input vector shared by many neurons of the same layer --- a
|
||||
further real 1.66--2.00$\times$ cycle reduction, at a real, honestly
|
||||
reported Fmax cost (ch.~\ref{ch:mem}).
|
||||
\end{itemize}
|
||||
|
||||
\section{The central, measured finding}
|
||||
The single most important result of this project's own benchmark
|
||||
campaign is that \textbf{V2 is memory-bound, not compute-bound}: the
|
||||
real compute-to-memory-wait ratio is on the order of 1:170--1:220, and
|
||||
the one physical PSRAM port saturates at $\approx$90\% utilization
|
||||
regardless of \code{N\_SLOTS}$\ge$2. Real parallel scaling from
|
||||
\code{N\_SLOTS}=1 to \code{N\_SLOTS}=8 is essentially flat for
|
||||
large/sustained workloads (1.05--1.06$\times$), and once real,
|
||||
place\&route-measured Fmax degradation from added routing congestion is
|
||||
also accounted for, \code{N\_SLOTS}=4 measures as \emph{slower} in real
|
||||
wall-clock time than \code{N\_SLOTS}=1 for the largest workload tested
|
||||
--- more hardware parallelism made that specific configuration worse,
|
||||
not better, because the bottleneck was never compute. This finding
|
||||
directly shaped both post-campaign optimizations in ch.~\ref{ch:mem}.
|
||||
|
||||
\begin{fnwarn}[Architecture changed since this finding: SDRAM, not PSRAM]
|
||||
This memory-bound finding was measured on the PSRAM-era architecture
|
||||
described above. The project has since replaced PSRAM with a single
|
||||
SDR SDRAM device (\S\ref{sec:sdram-mem-addendum}) and closed on
|
||||
\textbf{\code{N\_SLOTS}=4 as the production configuration} --- chosen
|
||||
primarily because it is the largest slot count that reliably closes
|
||||
real timing (8/8 seeds @ 64\,MHz, ch.~\ref{ch:hw}
|
||||
\S\ref{sec:clock-closure-current}), not from a re-run of this specific
|
||||
utilization/scaling study. Whether the SDRAM backend's own
|
||||
utilization/saturation ratio matches the PSRAM-era $\approx$90\% figure
|
||||
above has \textbf{not been independently re-measured} --- disclosed as
|
||||
an open item, not assumed to carry over.
|
||||
\end{fnwarn}
|
||||
|
||||
\begin{fnnote}[Reproducibility]
|
||||
Every real number in this datasheet traces to a specific, append-only
|
||||
log entry (\code{EXP-\textit{NNNN}}, \code{DEC-\textit{NNNN}},
|
||||
\code{ERR-\textit{NNNN}}) in \code{hardware/v2/logs/}, a specific git
|
||||
commit, and an exact toolchain command --- the same discipline applied
|
||||
throughout V1's own development.
|
||||
\end{fnnote}
|
||||
@@ -1,141 +0,0 @@
|
||||
\chapter{Architecture}
|
||||
\label{ch:arch}
|
||||
|
||||
\begin{fnnote}[Scheduling core unchanged; memory backend and slot count
|
||||
have]
|
||||
\code{dependency\_manager.v} and \code{neural\_director.v} (this
|
||||
chapter's own subject) are identical between the PSRAM-era milestone
|
||||
described below and the current, real SDRAM board --- the scheduling
|
||||
logic itself did not change. What changed since is the memory backend
|
||||
(single SDR SDRAM, not PSRAM, \S\ref{sec:sdram-mem-addendum}), the
|
||||
absence of the shared \textbf{Activation Cache} module from the current
|
||||
physical top (ch.~\ref{ch:toplevel}), and the production slot count
|
||||
(\code{N\_SLOTS}=4, not 2).
|
||||
\end{fnnote}
|
||||
|
||||
\section{Module map}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=7mm and 11mm,font=\footnotesize]
|
||||
\node[fnblockD,minimum width=34mm,minimum height=13mm] (dm){\textbf{Dependency Manager}\\{\scriptsize node table, wake-up}};
|
||||
\node[fnblockT,right=13mm of dm,minimum width=32mm,minimum height=13mm] (dir){\textbf{Neural Director}\\{\scriptsize first-free dispatch}};
|
||||
\node[fnreg,fill=white,right=13mm of dir,minimum width=34mm,minimum height=20mm] (mm0){
|
||||
\begin{tabular}{c}\textbf{Memory Manager} 0\\ $+$ \textbf{Neural Processor} 0\end{tabular}};
|
||||
\node[fnreg,fill=white,below=3mm of mm0,minimum width=34mm,minimum height=20mm] (mm1){
|
||||
\begin{tabular}{c}\textbf{Memory Manager} 1\\ $+$ \textbf{Neural Processor} 1\end{tabular}};
|
||||
\node[fnblockA,below=9mm of dir,minimum width=32mm,minimum height=13mm] (cache){\textbf{Activation Cache}\\{\scriptsize shared, single-tag}};
|
||||
\node[fnblock,right=13mm of mm0,minimum width=26mm,minimum height=13mm] (arb){\textbf{Slot Memory}\\\textbf{Arbiter}};
|
||||
\node[fnblockD,below right=9mm and 13mm of arb,minimum width=30mm,minimum height=13mm] (psram){\textbf{Real V1 PSRAM chain}\\{\scriptsize memory\_interface $\to$ psram\_controller}};
|
||||
\draw[fnbus] (dm) -- node[fnlbl,above]{ready\_valid/ready} (dir);
|
||||
\draw[fnbus] (dir) -- (mm0);
|
||||
\draw[fnbus] (dir) -- (mm1);
|
||||
\draw[fnarrowT] (mm0.south) |- (cache.east);
|
||||
\draw[fnarrowT] (mm1.west) -- (cache.east);
|
||||
\draw[fnarrowT] (cache.north) |- node[fnlbl,above,pos=0.3]{producer\_done} (dm.south);
|
||||
\draw[fnbus] (mm0) -- (arb);
|
||||
\draw[fnbus] (mm1) -- (arb);
|
||||
\draw[fnbus] (cache.south) |- (arb.west);
|
||||
\draw[fnbus] (arb) -- (psram);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\begin{center}\footnotesize\itshape\color{fnGrey}
|
||||
PSRAM-era diagram, N\_SLOTS=2 shown; the architecture is parametric in
|
||||
N\_SLOTS. Every arrow is a real signal path verified in Verilator
|
||||
simulation and real Yosys/nextpnr-ecp5 synthesis. The current, real
|
||||
board (N\_SLOTS=4, single SDRAM, no Activation Cache module) is shown
|
||||
in ch.~\ref{ch:toplevel}'s own hierarchy listing.\end{center}
|
||||
|
||||
\section{Dependency Manager}
|
||||
Holds a table of \code{N\_NODES} job descriptors, each tracking: node
|
||||
id, state (\code{EMPTY}/\code{WAITING}/\code{READY}/\code{DISPATCHED}),
|
||||
required-dependency count, resolved-dependency count, up to
|
||||
\code{MAX\_DEPS} producer node ids, and the job descriptor fields
|
||||
(\code{x\_base}, \code{w\_base}, \code{n\_tiles}, \code{result\_addr}). A
|
||||
node with zero required dependencies is immediately \code{READY} on
|
||||
registration. When a producer completes, \emph{every} \code{WAITING}
|
||||
node listing it among its own producers gets its resolved-dependency
|
||||
count incremented --- a single producer can satisfy several waiting
|
||||
consumers (shared-producer/multi-consumer), and a node depending on
|
||||
several producers accumulates resolution across separate events
|
||||
(multiple dependencies). Verified for both 1-hop and 2-hop transitive
|
||||
(diamond) graphs. Ready nodes are handed to the Neural Director one at a
|
||||
time over a backpressure-safe valid/ready interface.
|
||||
|
||||
\begin{fnwarn}[No slot reclamation]
|
||||
\code{ST\_DISPATCHED} is terminal: node table slots are never reused
|
||||
once dispatched. A long-running system that keeps registering new
|
||||
nodes without limit will eventually exhaust \code{N\_NODES} --- this is
|
||||
a real, measured consequence (a benchmark testbench hit exactly this
|
||||
deadlock via node-id wraparound before \code{N\_NODES} was sized
|
||||
generously enough). Slot reclamation is explicitly deferred, not
|
||||
forgotten.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Neural Director}
|
||||
Dispatches ready job descriptors to whichever of \code{N\_SLOTS} Memory
|
||||
Manager instances is currently free --- \textbf{first-free} scheduling: a
|
||||
fixed, lowest-index-wins priority scan, not load-balanced. Slot-busy
|
||||
tracking and completion detection are always-active, independent of
|
||||
whatever the allocate/scan control state happens to be that cycle (the
|
||||
same ``don't gate a per-unit event behind one shared FSM state''
|
||||
principle applied throughout this design). A completed slot's node id
|
||||
is tracked (\code{slot\_node\_id}) so its completion can be resolved back
|
||||
to a \code{producer\_done} event for the Dependency Manager, closing the
|
||||
wake-up loop without any external glue logic.
|
||||
|
||||
\begin{fnnote}[Measured scheduling imbalance]
|
||||
Real per-slot data (\code{N\_SLOTS}=4, a 128-neuron workload) shows
|
||||
slots 0/1 delivering 1008 real tiles each while slots 2/3 deliver only
|
||||
16 each, despite all four slots reporting near-100\% ``busy''
|
||||
utilization --- direct, measured evidence that fixed lowest-index
|
||||
priority does not distribute load evenly once the shared PSRAM port is
|
||||
the real constraint. See ch.~\ref{ch:impl2}.
|
||||
\end{fnnote}
|
||||
|
||||
\section{Memory Manager + Neural Processor (per slot)}
|
||||
Each slot pairs one \code{memory\_manager.v} instance with one
|
||||
\code{neural\_processor.v} instance. The Memory Manager double-buffers
|
||||
tile fetches (compute tile $N$ while prefetching tile $N{+}1$) and
|
||||
presents the Neural Processor with a simple ``data available''
|
||||
interface (\code{operand\_valid/ready}, \code{tile\_last}) --- the
|
||||
processor never sees PSRAM request/wait cycles directly. A tile's
|
||||
activation half is requested from the shared Activation Cache; its
|
||||
weight half is fetched directly (weights are per-neuron, never shared,
|
||||
so caching them would not help). A bank is presentable to the processor
|
||||
only once \emph{both} halves have arrived
|
||||
(\code{bank\_ready = bank\_x\_ready \& bank\_w\_ready}).
|
||||
|
||||
\section{Activation Cache}
|
||||
\label{sec:archcache}
|
||||
A single shared instance (not one per slot) serving every Memory
|
||||
Manager's activation-fetch requests. Single-tag design: one cached
|
||||
\code{x\_base} at a time, filled tile-by-tile on first use, served
|
||||
directly from an on-chip buffer on every subsequent request for the same
|
||||
vector --- no PSRAM access on a hit. A request for a different
|
||||
\code{x\_base} invalidates the cache and restarts filling from tile~0;
|
||||
this is always \emph{correct} (never serves stale data) but can thrash
|
||||
under interleaved, genuinely-different-\code{x\_base} concurrent
|
||||
traffic --- an honestly documented limitation, not exercised by this
|
||||
project's own realistic dense-layer workloads (where many neurons of one
|
||||
layer share one input vector, dispatched together). Full detail,
|
||||
including the real Fmax cost this module introduces, in
|
||||
ch.~\ref{ch:mem}.
|
||||
|
||||
\section{Slot Memory Arbiter}
|
||||
Funnels \code{N\_SLOTS}$+$1 independent backend ports (one per Memory
|
||||
Manager's weight/write-back traffic, plus one for the Activation
|
||||
Cache's own traffic) down to the one real physical PSRAM port. Fixed
|
||||
lowest-index priority, same convention as the Director. Every incoming
|
||||
request is latched into a per-port pending register regardless of
|
||||
arbiter state --- a byte-level backend protocol quirk discovered by real
|
||||
simulation (a fire-and-forget single-cycle request pulse can arrive
|
||||
while the shared bus is owned by another port; a naive ``grant only
|
||||
while live'' arbiter would silently drop it) made this latch a
|
||||
correctness requirement, not an optimization.
|
||||
|
||||
\section{Real, unmodified V1 PSRAM backend}
|
||||
\code{memory\_interface.v} and \code{psram\_controller.v} are reused
|
||||
byte-for-byte from the frozen \code{hardware/v1/} tree. The controller's
|
||||
own real page-mode support (fast same-page continuation vs.\ a slower
|
||||
cold access) was already implemented in V1 and is exploited more
|
||||
effectively by V2's word-level burst rewrite (ch.~\ref{ch:mem}) --- no
|
||||
change to the controller itself was needed or made.
|
||||
@@ -1,95 +0,0 @@
|
||||
\chapter{Compute datapath}
|
||||
\label{ch:datapath}
|
||||
|
||||
\section{Bit-exact reuse of V1's arithmetic}
|
||||
\code{neural\_processor.v} implements the identical INT8/INT32 arithmetic
|
||||
chain as V1's own \code{neuron\_parallel.v}/\code{mac8.v}/\code{mac\_unit.v}
|
||||
--- verified bit-exact against V1's own modules, instantiated side-by-side
|
||||
in the same testbench, across 7 test cases including extreme INT8 values,
|
||||
back-to-back zero-gap tiles, and multi-tile jobs. What changed is the
|
||||
\emph{pipelining}, not the math.
|
||||
|
||||
\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}
|
||||
|
||||
\section{8-stage pipeline}
|
||||
\code{neural\_processor.v} is fully pipelined, throughput-oriented (one
|
||||
new tile accepted per cycle in steady state, given a continuous operand
|
||||
stream):
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[font=\scriptsize,node distance=2.5mm,start chain=going below,
|
||||
every node/.style={on chain,fnblock,minimum width=64mm}]
|
||||
\node[fnblockA]{Stage 0 --- input alignment / register (\code{x0}, \code{w0})};
|
||||
\node{Stage 1 --- per-lane multiply (\code{P\_IN}$\times$\code{MULT18X18D})};
|
||||
\node{Stages 2..$(1{+}\log_2\text{P\_IN})$ --- balanced adder tree};
|
||||
\node[fnblockD]{accumulate (gated by job state, cleared at job start)};
|
||||
\node{$+$bias, activation select};
|
||||
\node[fnblockT]{INT8 saturation / output register};
|
||||
\foreach \i [count=\j from 2] in {1,...,5}
|
||||
\draw[fnarrow] (chain-\i) -- (chain-\j);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
With \code{P\_IN}=8 the adder tree has 3 levels, giving an 8-stage pipeline
|
||||
overall. \code{tile\_last} is gated identically to \code{valid} at every
|
||||
stage (\code{last0 <= (operand\_valid \&\& operand\_ready) ? tile\_last :
|
||||
1'b0;}) --- an early draft left it ungated, letting a ``last'' tag
|
||||
propagate one cycle ahead of its own valid/data pair on jobs where
|
||||
\code{tile\_last} was asserted before \code{operand\_ready} rose (legal
|
||||
valid-before-ready producer behavior); found and fixed via a
|
||||
cycle-by-cycle dump of the pipeline's own internal valid/last signals,
|
||||
re-verified against the full 7-test regression.
|
||||
|
||||
\section{Accumulator width: 24 vs.\ 32 bits}
|
||||
A real, 6-seed placement sweep (reusing already-synthesized netlists,
|
||||
real \code{nextpnr-ecp5} place\&route only) resolved an earlier
|
||||
single-seed measurement that had suggested \code{ACC\_WIDTH}=32 was
|
||||
marginally faster:
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{2.6cm} C{2.0cm} C{2.0cm} C{2.0cm} C{1.6cm}}
|
||||
\toprule
|
||||
\rowh \thd{ACC\_WIDTH} & \thd{mean Fmax} & \thd{min} & \thd{max} & \thd{stdev} \\
|
||||
\midrule
|
||||
32 & 170.12~MHz & 145.73 & 183.96 & 14.16 \\
|
||||
\rowa 24 & \textbf{180.71~MHz} & 175.16 & 185.49 & \textbf{4.21} \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\begin{center}\footnotesize\itshape\color{fnGrey}
|
||||
Real place\&route, P\_IN=8, 6 seeds each (default plus 5 explicit).\end{center}
|
||||
|
||||
\begin{fnnote}[Why a single seed misled]
|
||||
Over 6 real placement seeds, \code{ACC\_WIDTH}=24 has both a higher mean
|
||||
Fmax ($+$6.2\%) and a much tighter seed-to-seed spread ($\approx$3.4$\times$
|
||||
tighter) than \code{ACC\_WIDTH}=32 --- combined with fewer LUT/FF/CCU2C at
|
||||
24 bits and identical bit-exact correctness, \code{ACC\_WIDTH}=24 is
|
||||
recommended for any new P\_IN=8 INT8 configuration, where product
|
||||
magnitudes never need more than 24 bits of accumulator headroom.
|
||||
\end{fnnote}
|
||||
|
||||
\section{Activation and saturation}
|
||||
Identical encoding and bit-test logic to V1 (bilateral saturation for
|
||||
\code{ACT\_NONE}, positive-only for \code{ACT\_RELU}, both INT8-range).
|
||||
Every job dispatched by \code{dataflow\_core.v} currently hardcodes
|
||||
\code{job\_bias=0}, \code{job\_activation=ACT\_RELU} --- a documented
|
||||
simplification carried through every milestone since M4/M5, not yet
|
||||
exposed per-node by the Dependency Manager's own job descriptor.
|
||||
|
||||
\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: bilateral saturation to $[-128,+127]$. \\
|
||||
\rowa \code{ACT\_RELU} & \code{2'd1} & $\max(0,x)$, positive saturation to $+127$ (used by every V2 job today). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
@@ -1,73 +0,0 @@
|
||||
\chapter{Parameters and configurability}
|
||||
\label{ch:param}
|
||||
|
||||
\section{Build parameters (synthesis-time)}
|
||||
\begin{fnwarn}[Current, real board parameters (\code{fpga\_neural\_v2\_top.v})]
|
||||
The table below reflects the real, current SDRAM-architecture top
|
||||
level. The PSRAM-era \S\S\ref{ch:mem} chapters below this one describe
|
||||
an earlier, real, correctly-measured milestone with different defaults
|
||||
(notably \code{ADDR\_WIDTH}=23 and a PSRAM data-bus parameter) ---
|
||||
superseded, not deleted, since that data remains accurate for the
|
||||
architecture it was measured on.
|
||||
\end{fnwarn}
|
||||
\begin{tabularx}{\textwidth}{L{3.0cm} C{1.8cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Parameter} & \thd{Default} & \thd{Meaning} \\
|
||||
\midrule
|
||||
\code{DATA\_WIDTH} & 8 & Data width (INT8), unchanged from V1. \\
|
||||
\rowa \code{ACC\_WIDTH} & 32 & Accumulator width. \\
|
||||
\code{P\_IN} & 8 & Parallel MAC lanes per neuron per tile; must be even (word-level burst constraint, ch.~\ref{ch:mem}). \\
|
||||
\rowa \code{ADDR\_WIDTH} & 26 & Byte-address width (widened 23$\to$26 for the 64\,MB SDRAM device, DEC-0039). \\
|
||||
\code{N\_SLOTS} & 4 & Concurrent Memory Manager$+$Neural Processor pairs. \textbf{Production configuration} --- real 8/8-seed timing closure at 64\,MHz (ch.~\ref{ch:hw} \S\ref{sec:clock-closure-current}). \\
|
||||
\rowa \code{N\_NODES} & 16 & Dependency Manager node-table depth. Sized to the largest node-id range a graph will ever use; never reclaimed (ch.~\ref{ch:arch}). \\
|
||||
\code{MAX\_DEPS} & 4 & Maximum producers a single node can list. \\
|
||||
\rowa \code{QUEUE\_DEPTH} & 8 & Neural Director's own ready-job FIFO depth. \\
|
||||
\code{MAX\_TILES} & 16 & Longest activation/weight tile run a job can request. \\
|
||||
\rowa \code{CLK\_FREQ\_MHZ} & 64 & Real system clock, generated by \code{ecp5\_pll\_sys\_clk.v} from the 16\,MHz oscillator; 80\,MHz confirmed NO-GO (ch.~\ref{ch:hw} \S\ref{sec:clock-closure-current}). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\begin{fnwarn}[\texttt{N\_SLOTS} is a real, measured trade-off, not a free parameter]
|
||||
\code{N\_SLOTS}=4 is the production default: the largest slot count
|
||||
that reliably closes real timing at 64\,MHz on every tested placement
|
||||
seed (8/8). \code{N\_SLOTS}=8 is functionally correct (bit-exact) but
|
||||
only 3/8 seeds close timing --- deferred, not production-frozen. Do
|
||||
not simply raise \code{N\_SLOTS} without re-running the real 8-seed
|
||||
\code{nextpnr-ecp5} matrix.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Word-alignment constraint (post word-burst rewrite)}
|
||||
Since the memory backend now moves 16-bit words rather than bytes
|
||||
(ch.~\ref{ch:mem}), every tile base address the system computes
|
||||
(\code{x\_base + tile\_idx*P\_IN}, and equivalently for weights) must
|
||||
land on an even byte address. \code{P\_IN} even and \code{x\_base}/
|
||||
\code{w\_base} themselves even together guarantee this for every tile of
|
||||
every job --- true of every address this project's own testbenches use,
|
||||
and a trivial constraint for any real loader/host to satisfy.
|
||||
|
||||
\section{Characterized configurations (PSRAM-era; see ch.~\ref{ch:hw}
|
||||
\S\ref{sec:clock-closure-current} for the current SDRAM-architecture
|
||||
numbers)}
|
||||
\begin{tabularx}{\textwidth}{C{1.6cm} C{2.6cm} C{2.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{N\_SLOTS} & \thd{Fmax, word-burst only} & \thd{Fmax, $+$activation cache} & \thd{Notes} \\
|
||||
\midrule
|
||||
1 & 152.44~MHz & 131.79~MHz & Best real wall-clock speedup (3.86$\times$ vs baseline); no arbitration contention possible. \\
|
||||
\rowa 2 & 133.58~MHz & \textbf{87.72~MHz} & \textbf{Recommended default} --- real 2.45$\times$ speedup vs baseline, still comfortably above 80\,MHz. \\
|
||||
4 & 112.07~MHz & 65.01~MHz (\FAIL) & No additional real throughput; fails 80\,MHz with the cache active. Not recommended. \\
|
||||
\rowa 8 & 92.63~MHz (dataflow\_core only, no real PSRAM chain) & not re-measured & Real DSP ceiling for P\_IN=8 (64/72 MULT18X18D); a resource ceiling, not a useful operating point. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Build versus runtime}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[font=\footnotesize,node distance=6mm]
|
||||
\node[fnblockD,minimum width=54mm,minimum height=17mm](b){\textbf{BUILD (synthesis)}\\[2pt]
|
||||
{\scriptsize N\_SLOTS, N\_NODES, MAX\_DEPS,}\\{\scriptsize QUEUE\_DEPTH, P\_IN, DATA\_WIDTH, ACC\_WIDTH}\\{\scriptsize $\Rightarrow$ machine ceiling}};
|
||||
\node[fnblockT,right=16mm of b,minimum width=54mm,minimum height=17mm](r){\textbf{RUNTIME (node registration)}\\[2pt]
|
||||
{\scriptsize reg\_node\_id, reg\_required, reg\_producer\_ids,}\\{\scriptsize reg\_x\_base/w\_base/n\_tiles/result\_addr}\\{\scriptsize $\Rightarrow$ the actual dependency graph}};
|
||||
\draw[fnbus] (b) -- node[fnlbl,above]{$\le$} (r);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
Full field-level description of the runtime (node registration)
|
||||
interface: ch.~\ref{ch:regs}.
|
||||
@@ -1,189 +0,0 @@
|
||||
\chapter{Memory subsystem}
|
||||
\label{ch:mem}
|
||||
|
||||
\section{Baseline: reused byte-level V1 backend}
|
||||
V2's first working milestones connected each Memory Manager's own
|
||||
\code{prefetch\_engine.v} to the real, unmodified V1 chain
|
||||
\code{int8\_memory\_access.v} $\to$ \code{memory\_interface.v} $\to$
|
||||
\code{psram\_controller.v}, fetching one INT8 byte per transaction ---
|
||||
exactly the contract V1's own \code{neuron\_memory.v} already used
|
||||
against the same backend. This was correct and fully verified (bit-exact
|
||||
end-to-end through the real PSRAM chain), but it was not the fastest
|
||||
possible use of that chain.
|
||||
|
||||
\section{Optimization \#1 --- word-level burst reads}
|
||||
\label{sec:burst}
|
||||
Direct inspection of \code{int8\_memory\_access.v} shows it already
|
||||
converts every 8-bit logical request into a \textbf{full 16-bit PSRAM
|
||||
word access} internally (\code{mem\_addr <= addr >> 1}, one byte lane
|
||||
selected via \code{lb\_n}/\code{ub\_n}) --- so a byte-at-a-time fetch was
|
||||
already paying for two bytes of real PSRAM bandwidth per transaction
|
||||
while using only one, and paying \code{int8\_memory\_access.v}'s own
|
||||
request/wait round-trip twice for every real word instead of once.
|
||||
|
||||
\code{prefetch\_engine.v} (weights) and \code{activation\_cache.v}
|
||||
(activations, \S\ref{sec:cache}) now talk directly to
|
||||
\code{memory\_interface.v}'s own 16-bit word interface, \textbf{skipping
|
||||
\code{int8\_memory\_access.v} entirely}. Both files remain frozen,
|
||||
byte-for-byte unmodified V1 --- V2 simply chooses to reuse the lower
|
||||
(word-level) layer of the same frozen stack instead of the byte-splitting
|
||||
layer on top of it, the same precedent already set by
|
||||
\code{slot\_mem\_arbiter.v} not reusing V1's own \code{mem\_arbiter.v}
|
||||
verbatim.
|
||||
|
||||
\begin{fnnote}[Real, measured result --- single job, real PSRAM]
|
||||
\begin{tabularx}{\textwidth}{C{2.2cm} C{2.4cm} C{2.4cm} C{1.6cm}}
|
||||
\toprule
|
||||
\rowh \thd{n\_tiles} & \thd{cycles, before} & \thd{cycles, after} & \thd{$\Delta$} \\
|
||||
\midrule
|
||||
1 & 166 & 84 & $-49\%$ \\
|
||||
\rowa 3 & 446 & 204 & $-54\%$ \\
|
||||
5 & 728 & 322 & $-56\%$ \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
Real Verilator simulation, real V1 PSRAM chain, all results still
|
||||
bit-exact.
|
||||
\end{fnnote}
|
||||
|
||||
Combined real wall-clock effect (256-neuron sustained workload, cycles
|
||||
$\div$ real POST-P\&R Fmax): a \textbf{2.24--2.37$\times$} speedup across
|
||||
every \code{N\_SLOTS} tested, at a negligible real Fmax cost
|
||||
(unchanged at \code{N\_SLOTS}=1; $-6.2\%$ at \code{N\_SLOTS}=2; $-1.2\%$
|
||||
at \code{N\_SLOTS}=4).
|
||||
|
||||
\begin{fnwarn}[Why not just pipeline more requests instead?]
|
||||
\code{int8\_memory\_access.v}'s own \code{STATE\_IDLE} only samples a
|
||||
new \code{req} once back in \code{STATE\_IDLE} after the previous
|
||||
transaction's \code{mem\_ready} --- it fundamentally does not support
|
||||
request pipelining. No wrapper built \emph{on top of} it can avoid
|
||||
paying its round-trip cost twice per word; only bypassing it (talking to
|
||||
\code{memory\_interface.v} directly) actually removes the redundancy.
|
||||
This is why the fix reaches one layer lower in the stack rather than
|
||||
adding queuing logic in front of the existing byte-level port.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Optimization \#2 --- shared activation cache}
|
||||
\label{sec:cache}
|
||||
In the realistic dense-layer workloads this project benchmarks, many
|
||||
neurons of the same layer share the \emph{exact same} activation vector.
|
||||
Before this optimization, each of \code{N\_SLOTS} Memory Manager
|
||||
instances re-fetched that identical vector from PSRAM independently ---
|
||||
real, measured, redundant traffic on the one shared PSRAM port.
|
||||
\code{activation\_cache.v} (a new, single shared instance per
|
||||
\code{dataflow\_core}, not one per slot) fetches a given \code{x\_base}
|
||||
vector once, tile by tile on first use, and serves every subsequent
|
||||
request for the same vector directly from an on-chip buffer.
|
||||
|
||||
\begin{fnnote}[Real, measured result --- 256-neuron sustained workload, D-Stress]
|
||||
\begin{tabularx}{\textwidth}{C{1.4cm} C{2.4cm} C{2.4cm} C{2.0cm} C{2.0cm}}
|
||||
\toprule
|
||||
\rowh \thd{N\_SLOTS} & \thd{cycles, burst only} & \thd{cycles, $+$cache} & \thd{Fmax, burst} & \thd{Fmax, $+$cache} \\
|
||||
\midrule
|
||||
1 & 348682 & 174610 & 152.44 & 131.79 \\
|
||||
\rowa 2 & 307602 & 185428 & 133.58 & \textbf{87.72} \\
|
||||
4 & 307346 & 184795 & 112.07 & 65.01 (\FAIL) \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
A further real 1.66--2.00$\times$ cycle reduction on top of optimization~\#1,
|
||||
$\approx$4$\times$ combined vs.\ the original byte-level baseline.
|
||||
\end{fnnote}
|
||||
|
||||
\begin{fnwarn}[Real, measured Fmax cost --- read this before raising N\_SLOTS]
|
||||
The shared cache's real Fmax cost is \textbf{much steeper} than
|
||||
optimization~\#1's: a single central resource with \code{N\_SLOTS}
|
||||
request ports, a broadcast-capable hit-check evaluated combinationally
|
||||
every cycle for every port, and a shared \code{tile\_store} array create
|
||||
a genuine fan-in/routing hot spot that grows with \code{N\_SLOTS}.
|
||||
\code{N\_SLOTS}=2 (recommended) still passes 80\,MHz (87.72\,MHz, margin
|
||||
down from $+$67\% to $+$9.7\%); \code{N\_SLOTS}=4 \textbf{fails outright}
|
||||
(65.01\,MHz). This is the central input to ch.~\ref{ch:roadmap}'s own
|
||||
open work item on cache pipelining.
|
||||
\end{fnwarn}
|
||||
|
||||
Combined real wall-clock speedup vs.\ the original byte-level baseline
|
||||
(both optimizations together): \code{N\_SLOTS}=1 \textbf{3.86$\times$};
|
||||
\code{N\_SLOTS}=2 \textbf{2.45$\times$} (the recommended configuration);
|
||||
\code{N\_SLOTS}=4 2.29$\times$ but a real \emph{regression} versus
|
||||
optimization~\#1 alone, since its own Fmax now fails 80\,MHz.
|
||||
|
||||
\subsection{Design notes}
|
||||
Single-tag, tile-granular: a request tag mismatch invalidates the cache
|
||||
and restarts filling from tile~0 for the new \code{x\_base} --- always
|
||||
correct, never serves stale data, but can thrash under interleaved,
|
||||
genuinely-different-\code{x\_base} concurrent traffic (not exercised by
|
||||
this project's own dense-layer workloads, where sharing is real and
|
||||
sustained). Requests are latched per-slot on arrival (the same
|
||||
``queue, don't drop'' idiom used by the arbiter, \S\ref{sec:archcache}
|
||||
of ch.~\ref{ch:arch}) and served with a broadcast ack the cycle a
|
||||
matching tile becomes valid, so multiple slots pending on the same,
|
||||
about-to-arrive tile are all served the same cycle.
|
||||
|
||||
\begin{fnnote}[Two real bugs found and fixed during implementation]
|
||||
(1)~A target-bank/pending-bank race: a later handoff could queue a new
|
||||
cache request (targeting a different double-buffer bank) in the same
|
||||
cycle an earlier request was still awaiting its own ack, and
|
||||
non-blocking-assignment ``last write wins'' semantics silently
|
||||
misattributed which bank the earlier request's data landed in --- the
|
||||
same bug class already found once for the weight-side
|
||||
\code{pf\_target\_bank} register, fixed with the identical two-register
|
||||
(pending/target) staging pattern. (2)~A zero-width Verilog replication
|
||||
at \code{N\_SLOTS}=1 (\code{\{\$clog2(1)\{1'b0\}\}} $=$ \code{\{0\{...\}\}},
|
||||
illegal outside a concatenation), the same class already found once in
|
||||
\code{neural\_director.v} and fixed with the same width-agnostic
|
||||
\code{'0} literal. Both found via real simulation, not by inspection.
|
||||
\end{fnnote}
|
||||
|
||||
\section{Real PSRAM chain (unmodified V1)}
|
||||
\code{memory\_interface.v} and \code{psram\_controller.v} are byte-for-byte
|
||||
identical to V1's own copies throughout this chapter --- the real
|
||||
page-mode support they already implement (fast same-page continuation,
|
||||
slower cold access) is exploited more effectively by the word-level
|
||||
rewrite, not changed. The real ISSI \code{IS66WVE4M16EBLL-70BLI} chip and
|
||||
its board wiring are unchanged from V1 (ch.~\ref{ch:hw}).
|
||||
|
||||
\section{SDRAM upgrade addendum (2026-09-07) --- current, authoritative
|
||||
memory architecture}
|
||||
\label{sec:sdram-mem-addendum}
|
||||
\begin{fnwarn}[Superseded architecture]
|
||||
The PSRAM-based chain described above (\S\S\ref{sec:burst}--\ref{sec:cache})
|
||||
belongs to an earlier V2 milestone. The project has since closed on a
|
||||
single-external-memory architecture (real \code{decisions.log} DEC-0034):
|
||||
\textbf{one SDR SDRAM device, one \code{sdram\_controller.v} instance},
|
||||
serving weights, activations, AND results through
|
||||
\code{sdram\_unified\_backend.v}'s two logical ports (W: 64-bit weight
|
||||
read; AR: 16-bit, byte-maskable activation-read/result-write), arbitrated
|
||||
2-way priority (W wins when both pending). No PSRAM, no second physical
|
||||
memory device, in the current, frozen hardware path.
|
||||
\end{fnwarn}
|
||||
|
||||
The device itself was upgraded mid-project from an 8\,MB part
|
||||
(\code{AS4C4M16SA-6TIN}) to the current \textbf{AS4C32M16SB-7BIN,
|
||||
64\,MB (512\,Mbit), 54-ball FBGA} --- both the row/column/bank geometry
|
||||
(\code{sdram\_controller.v}'s \code{ROW\_BITS}/\code{COL\_BITS}/
|
||||
\code{BANK\_BITS} parameters, now 13/10/2) and the SPI host protocol's
|
||||
own address-field width (23$\to$26-bit byte address; WRITE\_JOB payload
|
||||
grew 15$\to$18 bytes) changed accordingly. Full electrical/pinout data
|
||||
and the complete FPGA$\leftrightarrow$SDRAM ball mapping are in
|
||||
ch.~\ref{ch:hw}, \S\ref{sec:sdram-addendum} (kept in one place to avoid
|
||||
two copies of the same real data).
|
||||
|
||||
\subsection{Real, measured clock closure}
|
||||
\textbf{N\_SLOTS=4 @ 64\,MHz is the frozen production configuration}:
|
||||
real \code{nextpnr-ecp5} P\&R, 8/8 tested seeds PASS. \textbf{N\_SLOTS=8
|
||||
@ 64\,MHz is deferred}, not production-frozen: 3/8 seeds PASS in the
|
||||
final, current RTL state. 80\,MHz was tested with a genuinely
|
||||
regenerated PLL (not merely a \code{--freq} flag) and is \textbf{not
|
||||
achievable} at either processor count --- the achievable Fmax is a
|
||||
property of the routed fabric, confirmed identical between the
|
||||
64\,MHz- and 80\,MHz-targeted netlists. Bit-exact functional
|
||||
correctness (D-Stress, 256/256 neurons vs.\ golden model) is unaffected
|
||||
at every configuration tested.
|
||||
|
||||
\begin{fnnote}[Single source of truth for exact numbers]
|
||||
The exact per-seed Fmax/WNS table, its full revision history (three
|
||||
successive real critical-path fixes: ERR-0027, ERR-0028, ERR-0029, plus
|
||||
a later fan-out fix, DEC-0042), and the SDRAM directed boundary-test
|
||||
result (21/21 PASS, both 64\,MHz and 166\,MHz) are kept in one place to
|
||||
avoid two copies of the same real data --- see ch.~\ref{ch:hw}
|
||||
\S\ref{sec:clock-closure-current} and \S\ref{sec:sdram-addendum}.
|
||||
\end{fnnote}
|
||||
@@ -1,66 +0,0 @@
|
||||
\chapter{Dataflow scheduling}
|
||||
\label{ch:sched}
|
||||
|
||||
\section{Node lifecycle}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[font=\scriptsize,node distance=16mm,>=Stealth]
|
||||
\node[fnstate](e){EMPTY};
|
||||
\node[fnstate,right=of e](w){WAITING};
|
||||
\node[fnstate,right=of w](r){READY};
|
||||
\node[fnstate,right=of r](d){DISPATCHED};
|
||||
\draw[fnarrow] (e) -- node[fnlbl,above]{register, deps$>$0} (w);
|
||||
\draw[fnarrow] (e) to[bend left=25] node[fnlbl,above]{register, deps$=$0} (r);
|
||||
\draw[fnarrow] (w) -- node[fnlbl,above]{all producers done} (r);
|
||||
\draw[fnarrow] (r) -- node[fnlbl,above]{Director accepts} (d);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\code{DISPATCHED} is terminal (\S\ref{ch:arch}): a real, honest
|
||||
consequence, not an oversight --- see the roadmap (ch.~\ref{ch:roadmap})
|
||||
for the deferred slot-reclamation work item.
|
||||
|
||||
\section{Verified graph topologies}
|
||||
\begin{tabularx}{\textwidth}{L{3.4cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Topology} & \thd{What it proves} \\
|
||||
\midrule
|
||||
Shared producer, 2 consumers & One node's completion resolves the dependency count of \emph{two} different waiting nodes independently. \\
|
||||
\rowa Multiple producers, 1 consumer & A node with \code{required}$>$1 only becomes \code{READY} once \emph{every} listed producer has completed, tracked across separate wake-up events. \\
|
||||
2-hop transitive diamond ($A,B$ independent; $C$ dep-$A$; $D$ dep-$B$; $E$ dep-$C,D$) & Correct cascading wake-up two hops deep --- $E$ does not fire until $C$ and $D$ have \emph{themselves} genuinely completed, not merely been marked ready. \\
|
||||
\rowa Mixed-depth fan-in (node depending on both a root and a 1-hop descendant) & Dependency resolution does not assume a uniform graph depth. \\
|
||||
Multilayer (8 layer-1 neurons, random INT8 data, feeding 2 layer-2 neurons reading their real shared result bytes) & Real cross-node \emph{data} forwarding through real PSRAM --- layer-2's golden values are computed from the real bytes layer-1 actually wrote, not from an independent expectation. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
All topologies above were exercised with the real, full
|
||||
\code{neural\_multiprocessor.v} (real V1 PSRAM chain, real
|
||||
\code{slot\_mem\_arbiter.v}) and verified bit-exact against a software
|
||||
golden model.
|
||||
|
||||
\section{First-free dispatch}
|
||||
The Neural Director's own scheduling policy is deliberately the simplest
|
||||
one that is provably correct: a fixed, lowest-index priority scan over
|
||||
currently-free slots. Round-robin, least-loaded, or any fairness-aware
|
||||
alternative was explicitly deferred until real measured data showed
|
||||
whether it mattered (\S\ref{sec:fairness}).
|
||||
|
||||
\section{Measured scheduling behavior}
|
||||
\label{sec:fairness}
|
||||
Real per-slot data (\code{N\_SLOTS}=4, a 128-neuron dense-layer
|
||||
workload) shows a striking imbalance: slots~0 and~1 each deliver 1008
|
||||
real tiles, while slots~2 and~3 deliver only 16 each --- despite all four
|
||||
slots reporting near-100\% ``busy'' utilization. The cause is not
|
||||
unfairness in isolation: once the shared PSRAM port is saturated
|
||||
(ch.~\ref{ch:mem}), there is rarely a moment where the low-index slots
|
||||
are simultaneously busy \emph{and} the high-index slots have nothing to
|
||||
do, so the fixed low-index-first scan keeps re-selecting the same two
|
||||
slots. This is a real, measured limitation of the current scheduler,
|
||||
carried into ch.~\ref{ch:roadmap} as an open item rather than patched
|
||||
without first measuring whether it is worth the added complexity for
|
||||
real workloads.
|
||||
|
||||
\section{Correctness guarantees (measured, not assumed)}
|
||||
Across the full final benchmark campaign (6 workloads $\times$ 4
|
||||
\code{N\_SLOTS} configurations, re-verified after both memory
|
||||
optimizations): \textbf{zero} lost jobs, \textbf{zero} duplicated jobs
|
||||
(\code{jobs\_allocated == jobs\_completed == neurons\_completed} exactly,
|
||||
every run), \textbf{zero} deadlocks, \textbf{zero} timeouts, correct
|
||||
multi-hop dependency wake-up in every topology tested.
|
||||
@@ -1,138 +0,0 @@
|
||||
\chapter{Host / graph-loader interface}
|
||||
\label{ch:host}
|
||||
|
||||
\begin{fnwarn}[Scope of this chapter]
|
||||
V1's own host interface is a real, placed, physically-verified SPI Mode~0
|
||||
slave (ch.~7 of the V1 datasheet). V2's equivalent --- a node-registration
|
||||
bus into \code{neural\_multiprocessor.v} --- has, in this revision, been
|
||||
exercised exclusively from Verilator testbenches and unconstrained
|
||||
synthesis top-levels. This chapter describes the \textbf{logical}
|
||||
protocol only; no real host-side driver (SPI or otherwise) has been
|
||||
built or placed yet. See ch.~\ref{ch:roadmap}.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Node registration protocol}
|
||||
A simple valid/ready producer interface, backpressure-safe: the loader
|
||||
holds \code{reg\_valid} and the node's own fields until \code{reg\_ready}
|
||||
is observed high on the same cycle, exactly like registering into any
|
||||
FIFO. \code{reg\_ready} for a given \code{reg\_node\_id} is asserted
|
||||
whenever that node's own table slot is \code{EMPTY} (\S\ref{ch:sched}).
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{3.2cm} C{1.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Field} & \thd{Width} & \thd{Meaning} \\
|
||||
\midrule
|
||||
\code{reg\_node\_id} & $\lceil\log_2\text{N\_NODES}\rceil$ & This node's own id --- doubles as its table slot index. \\
|
||||
\rowa \code{reg\_required} & $\lceil\log_2(\text{MAX\_DEPS}{+}1)\rceil$ & How many of \code{reg\_producer\_ids} are meaningful (0 $\Rightarrow$ immediately \code{READY}). \\
|
||||
\code{reg\_producer\_ids} & \code{MAX\_DEPS}$\times\lceil\log_2\text{N\_NODES}\rceil$ & Packed array of producer node ids this node depends on. \\
|
||||
\rowa \code{reg\_x\_base} & \code{ADDR\_WIDTH} & Base byte address of this node's activation vector. \\
|
||||
\code{reg\_w\_base} & \code{ADDR\_WIDTH} & Base byte address of this node's weight vector. \\
|
||||
\rowa \code{reg\_n\_tiles} & 16 & Number of P\_IN-wide tiles to accumulate. \\
|
||||
\code{reg\_result\_addr} & \code{ADDR\_WIDTH} & Byte address the computed INT8 result is written to. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\begin{fnnote}[A node id is a real, finite resource]
|
||||
Because dispatched node table slots are never reclaimed
|
||||
(\S\ref{ch:sched}), a loader driving many independent jobs over a long
|
||||
session must use a fresh \code{reg\_node\_id} for each one, within
|
||||
\code{N\_NODES}. Reusing a value before the system has been reset will
|
||||
simply be refused (\code{reg\_ready} stays low for an occupied,
|
||||
non-\code{EMPTY} node id) --- it will not corrupt anything, but it will
|
||||
also not register.
|
||||
\end{fnnote}
|
||||
|
||||
\section{Result readback}
|
||||
The computed INT8 result is written to \code{reg\_result\_addr} through
|
||||
the same real PSRAM chain every other memory access uses --- there is no
|
||||
separate result-readback port; the host/loader reads the result byte
|
||||
back from PSRAM directly, the same convention every V2 testbench in this
|
||||
project uses for verification.
|
||||
|
||||
\section{What a real host driver would still need to add}
|
||||
\begin{itemize}
|
||||
\item Per-job \code{bias}/\code{activation} selection, currently
|
||||
hardcoded to \code{bias=0}/\code{ACT\_RELU} for every job
|
||||
(\S\ref{ch:datapath}).
|
||||
\end{itemize}
|
||||
|
||||
\section{Addendum (2026-09-07) --- real physical transport and
|
||||
completion signal, both now closed}
|
||||
\label{sec:host-addendum}
|
||||
\begin{fnwarn}[Supersedes the two items removed from the list above]
|
||||
Both real gaps this chapter used to list are closed. This section is
|
||||
the current, real state.
|
||||
\end{fnwarn}
|
||||
|
||||
\textbf{Physical transport}: \code{spi\_host\_bridge.v}, a real SPI
|
||||
Mode~0 slave, is the board's actual node-registration transport ---
|
||||
real ball assignments (\code{spi\_sclk}/\code{spi\_mosi}/
|
||||
\code{spi\_miso}/\code{spi\_cs\_n}) verified, real place\&route (see
|
||||
ch.~\ref{ch:hw}). WRITE\_JOB carries the full table from
|
||||
\S\ref{ch:host} above as an 18-byte payload (grew from 15 after the
|
||||
64MB memory upgrade widened every address field from 3 to 4 bytes ---
|
||||
\code{decisions.log} DEC-0039). \textbf{Maximum verified operating
|
||||
clock: 12\,MHz recommended} (exact deterministic CDC edge at
|
||||
12.8\,MHz $=$ 64\,MHz/5, triple-flop synchronizer) --- see
|
||||
ch.~\ref{ch:hw} \S\ref{sec:spi-max-verified} for the full sweep.
|
||||
|
||||
\textbf{Completion notification}: \code{FPGA\_DATA\_READY}, a real
|
||||
output pin (ball \code{G3}, bank~7), closes the exact gap this
|
||||
chapter used to flag. It is a system-idle detector, not a per-job
|
||||
pulse --- deliberately, since ``the whole graph has an answer'' and
|
||||
``one neuron finished'' are different questions and only the former is
|
||||
useful to a host waiting on a result:
|
||||
\[
|
||||
\text{sys\_busy} = \big(\textstyle\bigvee \text{job\_active}\big)
|
||||
\;\lor\; \lnot\text{queue\_empty} \;\lor\; \text{any\_pending}
|
||||
\]
|
||||
where \code{job\_active} is per-slot (already real, \S\ref{ch:sched}),
|
||||
\code{queue\_empty} is \code{neural\_director.v}'s own dispatch-queue
|
||||
occupancy, and \code{any\_pending} tracks whether any node is
|
||||
currently registered but not yet dispatched (\code{WAITING} or
|
||||
\code{READY} --- \code{DISPATCHED} nodes are tracked by the two
|
||||
signals above instead, not here).
|
||||
|
||||
\begin{fnwarn}[Updated 2026-09-07 --- \code{any\_pending} implementation changed]
|
||||
The first real implementation computed \code{any\_pending} as a
|
||||
combinational OR-reduce over \code{dependency\_manager}'s own
|
||||
\code{node\_state[0:N\_NODES-1]} array every cycle. A real 8-seed
|
||||
\code{nextpnr-ecp5} P\&R sweep later showed this adding genuine
|
||||
fan-out onto \code{node\_state} --- a signal that also sits on this
|
||||
project's own worst real critical path
|
||||
(\code{neural\_director.job\_out\_slot} $\to$
|
||||
\code{dependency\_manager.node\_resolved}/\code{node\_state}), costing
|
||||
real Fmax margin (traced to a real 62.47\,MHz failing seed at
|
||||
N\_SLOTS=4 \S64\,MHz --- see ch.~\ref{ch:hw} \S\ref{sec:clock-closure-current}).
|
||||
Replaced with a synchronous up/down
|
||||
counter: \code{pending\_count} increments on a node's own registration
|
||||
acceptance (\code{reg\_valid\&\®\_ready}) and decrements on its own
|
||||
dispatch acceptance (\code{ready\_valid\&\&ready\_ready}); \code{any\_pending
|
||||
= (pending\_count != 0)}. Mathematically identical to the original
|
||||
OR-reduce (nodes are never reclaimed mid-run, ch.~\ref{ch:sched}), but
|
||||
reads one small registered counter instead of scanning a 16-wide array
|
||||
every cycle --- zero added fan-out on the congested signal. Recovered
|
||||
the last failing N\_SLOTS=4 seed (62.47 $\to$ 64.55\,MHz), closing
|
||||
8/8. See \code{decisions.log} DEC-0042.
|
||||
\end{fnwarn}
|
||||
|
||||
\code{FPGA\_DATA\_READY} is a sticky
|
||||
register: set on the \code{sys\_busy} $1\to0$ edge, cleared the
|
||||
instant \code{sys\_busy} goes high again --- self-clearing, no host
|
||||
acknowledgement command needed.
|
||||
|
||||
\begin{fnnote}[Real, disclosed assumption]
|
||||
This is correct only if the host finishes registering every node of a
|
||||
graph before the first one completes. Realistic for this
|
||||
architecture's own real timing (SPI registration: microseconds;
|
||||
per-neuron compute: $\sim$195 real measured cycles, \S\ref{ch:impl2})
|
||||
but not proven for every conceivable host registration pattern --- a
|
||||
host that deliberately staggers registration across a long enough gap
|
||||
could observe a premature \code{FPGA\_DATA\_READY} pulse after only
|
||||
the first node completes.
|
||||
\end{fnnote}
|
||||
|
||||
Bit-exact regression re-verified with an explicit assertion on this
|
||||
signal (N\_SLOTS=4 and 8, both PASS, see \code{decisions.log}
|
||||
DEC-0041) and a real \code{nextpnr-ecp5} placement check (0 errors,
|
||||
\code{data\_ready} placed at \code{G3}).
|
||||
@@ -1,64 +0,0 @@
|
||||
\chapter{Top-level module}
|
||||
\label{ch:toplevel}
|
||||
|
||||
\begin{fnwarn}[Real, board-level top --- not the PSRAM-era compute core]
|
||||
This chapter describes \code{fpga\_neural\_v2\_top.v}, the module that
|
||||
is actually placed\&routed against real balls
|
||||
(\code{hardware/v2/constraints/v2\_board\_top.lpf}) and whose Fmax
|
||||
numbers appear throughout this datasheet. It supersedes an earlier
|
||||
milestone's \code{neural\_multiprocessor.v} top level, which drove
|
||||
V1's own PSRAM chain directly and is retained in the repository for
|
||||
regression purposes (\code{tb\_nms\_dstress\_sdram\_unified.v}'s own
|
||||
wrapper, \S\ref{sec:sdram-mem-addendum}) but is not the physical top.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{\texttt{fpga\_neural\_v2\_top.v}}
|
||||
The real, board-level top: a PLL/reset front-end, a real SPI host
|
||||
bridge, the compute/scheduling core, and a single unified SDRAM
|
||||
backend --- 18 physical ports, every one ball-assigned.
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{3.4cm} C{1.2cm} C{1.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Port} & \thd{Dir} & \thd{Width} & \thd{Function} \\
|
||||
\midrule
|
||||
\code{osc\_clk} & IN & 1 & 16\,MHz board oscillator (ball H5). \\
|
||||
\rowa \code{ext\_rst\_n} & IN & 1 & External POR/supervisor, active-low (ball B4). \\
|
||||
\code{spi\_sclk}, \code{spi\_mosi}, \code{spi\_cs\_n} & IN & 1 each & Physical SPI host transport (ch.~\ref{ch:host}). \\
|
||||
\rowa \code{spi\_miso} & OUT & 1 & SPI host transport, response direction. \\
|
||||
\code{sdram\_clk} & OUT & 1 & SDRAM chip's own \code{CLK} pin --- a real board-level output, not internal-only routing (found missing during this session's own schematic review; ball J4). \\
|
||||
\rowa \code{sdram\_cke}, \code{sdram\_cs\_n}, \code{sdram\_ras\_n}, \code{sdram\_cas\_n}, \code{sdram\_we\_n} & OUT & 1 each & SDRAM control lines. \\
|
||||
\code{sdram\_ba} & OUT & 2 & SDRAM bank address. \\
|
||||
\rowa \code{sdram\_a} & OUT & 13 & SDRAM row/column address (widened 12$\to$13 bits for the 64\,MB device, DEC-0039). \\
|
||||
\code{sdram\_dq} & INOUT & 16 & SDRAM bidirectional data bus. \\
|
||||
\rowa \code{sdram\_dqm} & OUT & 2 & SDRAM byte mask. \\
|
||||
\code{data\_ready} & OUT & 1 & \code{FPGA\_DATA\_READY}, system-idle completion flag (ball G3, \S\ref{sec:host-addendum}). \\
|
||||
\rowa \code{pll\_locked} & OUT & 1 & PLL lock status, bring-up/debug (ball L1). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Internal hierarchy}
|
||||
\noindent\code{fpga\_neural\_v2\_top.v}
|
||||
\begin{itemize}[leftmargin=2.4em]
|
||||
\footnotesize
|
||||
\item \code{u\_pll} : \code{ecp5\_pll\_sys\_clk.v} (real \code{EHXPLLL} primitive, 16$\to$64\,MHz)
|
||||
\item \code{u\_reset\_sync} : \code{reset\_sync.v} (async assert, sync deassert, gated by \code{ext\_rst\_n} AND \code{pll\_locked})
|
||||
\item \code{u\_spi\_bridge} : \code{spi\_host\_bridge.v} (real SPI Mode~0 slave, triple-flop CDC)
|
||||
\item \code{u\_dataflow\_core} : \code{nms\_dataflow\_core\_sdram.v}
|
||||
\begin{itemize}
|
||||
\item \code{u\_dep\_mgr} : \code{dependency\_manager.v}
|
||||
\item \code{u\_director} : \code{neural\_director.v}
|
||||
\item \code{GEN\_SLOT[0..N\_SLOTS-1]}: \code{nms\_memory\_manager\_stream\_wide.v} $+$ \code{neural\_processor.v}
|
||||
\end{itemize}
|
||||
\item \code{u\_arbiter\_w}, \code{u\_arbiter\_ar} : \code{slot\_mem\_arbiter.v} (one per logical SDRAM port, W and AR)
|
||||
\item \code{u\_sdram\_backend} : \code{sdram\_unified\_backend.v} $\to$ \code{sdram\_controller.v} (single physical SDRAM)
|
||||
\end{itemize}
|
||||
|
||||
\begin{fnnote}[No shared activation cache in this datapath]
|
||||
The PSRAM-era shared activation cache (\code{activation\_cache.v},
|
||||
ch.~\ref{ch:mem} \S\ref{sec:cache}) is not part of the current SDRAM
|
||||
top-level's instantiation tree --- \code{nms\_memory\_manager\_stream\_wide.v}
|
||||
handles per-slot activation/weight/result streaming directly against
|
||||
the unified SDRAM backend. The PSRAM-era module remains real, correct,
|
||||
and documented for the architecture it was measured on
|
||||
(ch.~\ref{ch:mem}), but is not reused here.
|
||||
\end{fnnote}
|
||||
@@ -1,257 +0,0 @@
|
||||
\chapter[ECP5 implementation \& benchmarks]{ECP5 implementation, real benchmark campaign \& measured results}
|
||||
\label{ch:impl2}
|
||||
|
||||
\section{Flow and verification discipline}
|
||||
Every number in this chapter is labelled \textsc{Theoretical},
|
||||
\textsc{Simulated}, \textsc{Post-P\&R measured}, or \textsc{Derived}
|
||||
(a combination of two real measurements, e.g.\ cycles $\div$ real Fmax).
|
||||
No result is invented, approximated to look better, or reported without
|
||||
a matching real measurement.
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{5.0cm} C{3.0cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Verification stage} & \thd{Outcome} & \thd{Covers} \\
|
||||
\midrule
|
||||
RTL simulation (Verilator 5.050) & \PASS & bit-exact correctness vs.\ a software golden model \\
|
||||
\rowa ECP5 synthesis (Yosys \code{synth\_ecp5}) & \PASS, 0 problems & synthesizability, resource mapping \\
|
||||
Place\&route (real \code{nextpnr-ecp5}) & \PASS at \code{N\_SLOTS}$\le$2 & LUT/FF/DSP, real timing \\
|
||||
\rowa Full benchmark campaign & 24/24 bit-exact & 6 workloads $\times$ 4 \code{N\_SLOTS} configurations \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\begin{fnnote}[Verilator, not Icarus, for V2]
|
||||
Two independent Icarus Verilog v13.0 scheduling defects were found and
|
||||
reproduced on minimal repros during V2's own M1 milestone (a
|
||||
task/scope-entry desync and a spurious condition evaluation, both
|
||||
edge-parity dependent) --- Verilator gives correct results on the same
|
||||
repros. V1's own certification (performed separately, with Icarus) was
|
||||
unaffected, since its own testbenches already avoided the trigger
|
||||
pattern by convention; this is flagged honestly, not glossed over.
|
||||
\end{fnnote}
|
||||
|
||||
\section{V1 vs.\ V2 --- final comparison}
|
||||
Both systems full-system (not isolated modules), same
|
||||
PARALLEL/P\_IN=8, same real, unmodified V1 PSRAM chain.
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{3.6cm} C{2.6cm} C{2.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Metric} & \thd{V1} & \thd{V2 (N\_SLOTS=2)} & \thd{Class} \\
|
||||
\midrule
|
||||
Fmax & 68.65~MHz (\FAIL) & \textbf{87.72~MHz} (\PASS) & Post-P\&R \\
|
||||
\rowa LUT (Total LUT4s) & 8907 & 4359 & Post-P\&R \\
|
||||
FF (Total DFFs) & 4900 & 3924 & Post-P\&R \\
|
||||
\rowa DSP (MULT18X18D) & 16 & 16 & Post-P\&R \\
|
||||
BRAM (DP16KD) & 2 & 0 & Post-P\&R \\
|
||||
\rowa cycles/neuron (1 neuron, 8 inputs, real PSRAM) & 209 & 166 & Simulated \\
|
||||
Real wall-clock speedup vs.\ V1 & 1.00$\times$ & \textbf{2.6$\times$} & Derived \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\begin{center}\footnotesize\itshape\color{fnGrey}
|
||||
V1's own figures are its already-certified, frozen baseline (not
|
||||
re-measured this session); V2's figures are real, current measurements
|
||||
including both post-campaign optimizations.\end{center}
|
||||
|
||||
\begin{fnnote}[Where the win comes from --- and where it does not]
|
||||
V2's advantage comes from a faster pipeline and a higher achievable
|
||||
clock, \textbf{not} primarily from the multi-processor concurrency the
|
||||
architecture was built to add. That concurrency's own real payoff, given
|
||||
the single-PSRAM-port memory subsystem, is much smaller than a naive
|
||||
\code{N\_SLOTS}$\times$\code{P\_IN} calculation would suggest ---
|
||||
\S\ref{sec:scaling}.
|
||||
\end{fnnote}
|
||||
|
||||
\section{Real \texttt{N\_SLOTS} sweep --- Fmax and resources}
|
||||
Full system, real place\&route, both memory optimizations active
|
||||
(word-burst \S\ref{sec:burstimpl} $+$ activation cache
|
||||
\S\ref{sec:cacheimpl}).
|
||||
|
||||
\begin{tabularx}{\textwidth}{C{1.6cm} C{2.2cm} C{1.6cm} C{1.6cm} C{1.6cm} C{1.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{N\_SLOTS} & \thd{Fmax} & \thd{LUT4} & \thd{FF} & \thd{DSP} & \thd{BRAM} & \thd{80\,MHz} \\
|
||||
\midrule
|
||||
1 & 131.79~MHz & 2760 & 2405 & 8/72 & 0 & \PASS \\
|
||||
\rowa 2 & \textbf{87.72~MHz} & 4359 & 3924 & 16/72 & 0 & \PASS (\textbf{recommended}) \\
|
||||
4 & 65.01~MHz & 9158 & 7986 & 32/72 & 0 & \FAIL \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\subsection{Fmax versus N\_SLOTS}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
width=0.68\textwidth,height=6.0cm,
|
||||
xlabel={\footnotesize N\_SLOTS}, ylabel={\footnotesize Fmax [MHz]},
|
||||
xtick={1,2,4}, ymin=50,ymax=145, ytick={50,65,80,95,110,125,140},
|
||||
tick label style={font=\scriptsize}, label style={font=\footnotesize},
|
||||
grid=major, grid style={fnRule!40},
|
||||
legend style={font=\scriptsize,at={(0.5,-0.28)},anchor=north,legend columns=2}]
|
||||
\addplot[fnBlue,mark=square*,thick,mark options={fill=fnBlue}]
|
||||
coordinates {(1,152.44)(2,133.58)(4,112.07)};
|
||||
\addlegendentry{word-burst only}
|
||||
\addplot[fnRed,mark=*,thick,mark options={fill=fnRed}]
|
||||
coordinates {(1,131.79)(2,87.72)(4,65.01)};
|
||||
\addlegendentry{$+$ activation cache}
|
||||
\draw[fnAmber,dashed,thick] (axis cs:1,80)--(axis cs:4,80);
|
||||
\node[font=\scriptsize,text=fnAmber] at (axis cs:3.3,74){80 MHz target};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\begin{center}\footnotesize\itshape\color{fnGrey}
|
||||
The activation cache's own real Fmax cost grows much faster with
|
||||
N\_SLOTS than the arbiter-widening cost alone --- a single shared
|
||||
resource with N\_SLOTS request ports and an unpipelined,
|
||||
broadcast-capable hit-check.\end{center}
|
||||
|
||||
\section{Real parallel scaling}
|
||||
\label{sec:scaling}
|
||||
Not assumed --- computed from real cycle counts, largest workload
|
||||
(256 independent neurons sharing one input vector).
|
||||
|
||||
\begin{tabularx}{\textwidth}{C{1.6cm} C{2.6cm} C{2.0cm} C{2.6cm} C{2.0cm}}
|
||||
\toprule
|
||||
\rowh \thd{N\_SLOTS} & \thd{Speedup(N)} & \thd{Efficiency} & \thd{PSRAM utilization} & \thd{Real wall-clock speedup vs.\ N=1} \\
|
||||
\midrule
|
||||
1 & 1.00$\times$ & 100\% & 55.5--71.8\% & 1.00$\times$ \\
|
||||
\rowa 2 & 1.06$\times$ & 53\% & $\approx$90\% & 0.99$\times$ (a wash) \\
|
||||
4 & 1.06$\times$ & 27\% & $\approx$90\% & 0.79$\times$ (\emph{slower}) \\
|
||||
8 & 1.06$\times$ & 13\% & $\approx$90\% & --- \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\begin{center}\footnotesize\itshape\color{fnGrey}
|
||||
Pre-optimization figures, isolating the real scaling behavior from the
|
||||
two memory optimizations' own effect (\S\ref{sec:burstimpl}--\ref{sec:cacheimpl}).\end{center}
|
||||
|
||||
\begin{fnwarn}[The central, measured finding]
|
||||
Real cycle-count speedup from \code{N\_SLOTS}=1 to \code{N\_SLOTS}=8 is
|
||||
essentially flat (1.05--1.06$\times$) for sustained, memory-bound
|
||||
workloads --- the single shared PSRAM port saturates at
|
||||
$\approx$90\% utilization regardless of \code{N\_SLOTS}$\ge$2. Once real
|
||||
Fmax degradation is also folded in, \code{N\_SLOTS}=4 measures
|
||||
\emph{slower} in real wall-clock time than \code{N\_SLOTS}=1. More
|
||||
hardware parallelism made this workload class worse, not better,
|
||||
because the bottleneck was never compute.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Memory optimization \#1 --- word-level burst reads}
|
||||
\label{sec:burstimpl}
|
||||
See ch.~\ref{ch:mem}, \S\ref{sec:burst}, for the full rationale. Real,
|
||||
measured single-job cycle reduction: $-$49\% (1 tile), $-$54\% (3
|
||||
tiles), $-$56\% (5 tiles). Combined real wall-clock speedup on the
|
||||
256-neuron sustained workload: 2.24--2.37$\times$ across every
|
||||
\code{N\_SLOTS} tested, at negligible real Fmax cost.
|
||||
|
||||
\section{Memory optimization \#2 --- shared activation cache}
|
||||
\label{sec:cacheimpl}
|
||||
See ch.~\ref{ch:mem}, \S\ref{sec:cache}. A further real 1.66--2.00$\times$
|
||||
cycle reduction on top of optimization~\#1, at a real, steep Fmax cost
|
||||
that makes \code{N\_SLOTS}=4 fail 80\,MHz outright.
|
||||
|
||||
\begin{tabularx}{\textwidth}{C{1.6cm} C{2.4cm} C{2.4cm} C{2.4cm}}
|
||||
\toprule
|
||||
\rowh \thd{N\_SLOTS} & \thd{Wall-clock, baseline} & \thd{Wall-clock, final} & \thd{Total real speedup} \\
|
||||
\midrule
|
||||
1 & 5118.1~$\mu$s & 1324.9~$\mu$s & \textbf{3.86$\times$} \\
|
||||
\rowa 2 & 5169.5~$\mu$s & 2113.9~$\mu$s & \textbf{2.45$\times$} (recommended) \\
|
||||
4 & 6498.7~$\mu$s & 2842.6~$\mu$s (Fmax fails) & 2.29$\times$ but a real regression vs.\ \#1 alone \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Bottleneck analysis}
|
||||
\begin{tabularx}{\textwidth}{L{2.8cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Candidate} & \thd{Verdict, with real evidence} \\
|
||||
\midrule
|
||||
\textbf{Memory (PSRAM port)} & \textbf{The real bottleneck.} $\approx$90\% utilization at N\_SLOTS$\ge$2; real compute-to-memory-wait ratio on the order of 1:170--1:220. \\
|
||||
\rowa Compute (Neural Processor) & Not the bottleneck --- the pipeline is idle most of the time waiting for data. \\
|
||||
Arbiter overhead & Real but small: a mandatory 1-cycle pending-latch (correctness, not choice) plus modest Fmax cost ($-$6\% at N\_SLOTS=2, word-burst alone). \\
|
||||
\rowa Director/dependency logic & Not the bottleneck --- zero lost/duplicated jobs, no queueing backlog observed; a real \emph{fairness} issue exists (\S\ref{ch:sched}) but does not limit throughput. \\
|
||||
DSP/LUT/FF availability & Not the bottleneck at N\_SLOTS$\le$4 --- all well under budget; DSP would eventually bind at N\_SLOTS=9 (64/72), never reached in practice since the memory bottleneck dominates first. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Limitations, honestly stated (PSRAM-era campaign above)}
|
||||
\begin{itemize}
|
||||
\item V1's own memory-utilization/stall figures were not re-measured
|
||||
this session (V1 is frozen); only its already-certified numbers
|
||||
are used for comparison.
|
||||
\item No clean per-cycle split between ``processor computing'' and
|
||||
``processor waiting for memory'' exists in the current
|
||||
instrumentation --- reported figures use tile-delivery-rate
|
||||
proxies, not an exact split.
|
||||
\item Power/energy: \textbf{NOT MEASURED} --- no ECP5 power estimator
|
||||
(\code{ecppower}, \code{icepower}, or equivalent) is available in
|
||||
this project's toolchain; no value is invented in its place.
|
||||
\item \code{N\_SLOTS}=8 was not re-measured full-system (real PSRAM
|
||||
chain) after either memory optimization --- only
|
||||
\code{dataflow\_core.v} alone, pre-optimization (92.63~MHz).
|
||||
\end{itemize}
|
||||
|
||||
\section{SDRAM-era benchmark addendum (2026-09-07) --- current,
|
||||
authoritative results}
|
||||
\label{sec:impl-sdram-addendum}
|
||||
\begin{fnwarn}[Supersedes the PSRAM/\code{N\_SLOTS}$\le$2-era campaign
|
||||
above for the current hardware baseline]
|
||||
Every section above (V1 vs.\ V2 comparison, \code{N\_SLOTS} sweep,
|
||||
parallel scaling, memory optimizations \#1/\#2, bottleneck analysis)
|
||||
describes an earlier V2 milestone built on V1's own PSRAM chain,
|
||||
recommending \code{N\_SLOTS}=2. The project has since replaced external
|
||||
memory with a single SDR SDRAM device (ch.~\ref{ch:mem}
|
||||
\S\ref{sec:sdram-mem-addendum}) and closed on
|
||||
\textbf{\code{N\_SLOTS}=4 as the production configuration}. This
|
||||
section is the current, real, measured state; the PSRAM-era numbers
|
||||
above remain real and correctly measured for the architecture they
|
||||
describe, but do not apply to the current board.
|
||||
\end{fnwarn}
|
||||
|
||||
\subsection{Real resource utilization (\code{N\_SLOTS}=4, SDRAM
|
||||
architecture, post real critical-path fixes)}
|
||||
\begin{tabularx}{\textwidth}{L{4.2cm} C{2.4cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Resource} & \thd{Count} & \thd{Notes} \\
|
||||
\midrule
|
||||
TRELLIS\_COMB (LUT4-equiv) & 7,175 / 43,848 (16.4\%) & Real Yosys synthesis, most recent measurement (post-ERR-0029) \\
|
||||
\rowa MULT18X18D & 32 / 72 (44.4\%) & Exactly $4\times8$ (\code{N\_SLOTS}$\times$\code{P\_IN}), confirmed --- the ERR-0027 fix removed a spurious 33rd multiplier \\
|
||||
DP16KD (block RAM) & 0 / 108 & All small SRAMs synthesize to distributed RAM \\
|
||||
\rowa EHXPLLL & 1 & Real \code{EHXPLLL} primitive, \code{ecppll}-derived parameters \\
|
||||
TRELLIS\_FF & $\ge$6,322 (last individually re-quoted figure) & Real, same SDRAM architecture, pre-dates the ERR-0027/0028/0029 restructuring; not independently re-synthesized standalone since --- disclosed as a lower-bound reference, not re-invented as exact \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\subsection{Real, current clock closure and functional regression}
|
||||
See ch.~\ref{ch:hw} \S\ref{sec:clock-closure-current} for the complete
|
||||
per-seed Fmax/WNS table (single source of truth, not duplicated here):
|
||||
\textbf{\code{N\_SLOTS}=4 @ 64\,MHz, 8/8 seeds PASS} (worst 64.55\,MHz,
|
||||
best 72.37\,MHz); \code{N\_SLOTS}=8 deferred (3/8); 80\,MHz confirmed
|
||||
NO-GO at either processor count with a genuinely regenerated PLL.
|
||||
|
||||
D-Stress functional regression (256 neurons, 256/256 bit-exact vs.\
|
||||
golden model): \textbf{49,927 cycles} at \code{N\_SLOTS}=4 ---
|
||||
\textbf{780\,\textmu s} real wall-clock at the P\&R-verified 64\,MHz
|
||||
system clock ($49{,}927 / 64{,}000{,}000$, \textsc{Derived}). SDRAM
|
||||
directed boundary verification (ch.~\ref{ch:hw}
|
||||
\S\ref{sec:sdram-addendum}): 21/21 PASS, zero bugs found, both 64\,MHz
|
||||
and 166\,MHz.
|
||||
|
||||
\subsection{Real SPI host protocol throughput}
|
||||
Board-level smoke test (\code{tb\_fpga\_neural\_v2\_top\_smoke.v}, 11/11
|
||||
PASS): single job 99--100 cycles/job; back-to-back 88--100 cycles/job;
|
||||
steady-state throughput unaffected by inter-job gap (100\,ns/5\,\textmu
|
||||
s/50\,\textmu s tested). Maximum verified SPI host clock: \textbf{12\,MHz
|
||||
recommended} (exact deterministic CDC edge at 12.8\,MHz $=$ 64\,MHz/5)
|
||||
--- see ch.~\ref{ch:hw} \S\ref{sec:spi-max-verified} for the full sweep.
|
||||
|
||||
\subsection{Limitations, honestly stated (current SDRAM architecture)}
|
||||
\begin{itemize}
|
||||
\item Power/energy: \textbf{NOT MEASURED} --- no ECP5 power estimator
|
||||
is available in this toolchain (unchanged from the PSRAM-era
|
||||
disclosure above).
|
||||
\item Hold-time closure: \textbf{OPEN --- tool-chain limitation}, not a
|
||||
real defect; see ch.~\ref{ch:hw} \S\ref{sec:hw-open-items} for
|
||||
the complete, consolidated open-items list.
|
||||
\item \code{N\_SLOTS}=8 is functionally correct but not
|
||||
timing-closed on every tested seed --- deferred by explicit
|
||||
project direction, not attempted further this pass.
|
||||
\item No embedded-host (ESP32-class) physical baseline exists; all
|
||||
host-side numbers above are protocol-level simulation, not
|
||||
measured on real silicon.
|
||||
\end{itemize}
|
||||
@@ -1,564 +0,0 @@
|
||||
\chapter{Hardware and board}
|
||||
\label{ch:hw}
|
||||
|
||||
\section{Board summary}
|
||||
V2 targets Lattice ECP5 \code{LFE5U-45F-8BG381C} ($-8$, commercial
|
||||
grade, 381-ball caBGA, 0.8\,mm pitch, real package geometry
|
||||
17$\times$17$\times$1.76\,mm) --- the same die/package family as V1,
|
||||
but the board around it has diverged substantially: V2 replaces V1's
|
||||
PSRAM with a single external SDR SDRAM device (\S\ref{sec:sdram-addendum}),
|
||||
adds a real, placed SPI host transport and \code{FPGA\_DATA\_READY}
|
||||
completion pin (ch.~\ref{ch:host}), and has a real, exported KiCad
|
||||
schematic capture and BOM (\S\ref{sec:schematic-capture}--\ref{sec:bom}).
|
||||
Every top-level signal of \code{fpga\_neural\_v2\_top.v} carries a real
|
||||
ball assignment in \code{hardware/v2/constraints/v2\_board\_top.lpf} ---
|
||||
no unconstrained/placeholder pins remain in this revision.
|
||||
|
||||
\begin{fnnote}[V1's own PSRAM chain: retained in RTL, not on this board]
|
||||
\code{psram\_controller.v}/\code{memory\_interface.v} remain byte-for-byte
|
||||
identical to V1's own copies in the repository (frozen golden reference),
|
||||
but are \textbf{not instantiated anywhere in V2's real physical top}
|
||||
--- confirmed by inspection (\code{grep -ri psram hardware/v2/} returns
|
||||
nothing outside historical commentary). V1's own PSRAM ball assignment
|
||||
therefore does not apply to this board.
|
||||
\end{fnnote}
|
||||
|
||||
\section{SDRAM upgrade addendum (2026-09-07) --- current, authoritative
|
||||
board state}
|
||||
\label{sec:sdram-addendum}
|
||||
\begin{fnwarn}[Real, closed architectural decision]
|
||||
An earlier V2 milestone reused V1's own PSRAM chain, placed
|
||||
unconstrained. The project has since made a closed architectural
|
||||
decision (real \code{decisions.log} DEC-0034) to replace external
|
||||
memory with a single SDR SDRAM device, and has since upgraded that
|
||||
device's capacity (8\,MB $\to$ 64\,MB) and re-verified real,
|
||||
constrained place\&route timing end to end. This section is the
|
||||
current, real, measured state.
|
||||
\end{fnwarn}
|
||||
|
||||
\subsection{Memory device}
|
||||
\textbf{Alliance Memory AS4C32M16SB-7BIN} --- 512\,Mbit (64\,MByte) SDR
|
||||
SDRAM, organized 4 banks $\times$ 8M words $\times$ 16 bits, 54-ball
|
||||
FBGA package (8$\times$8$\times$1.2\,mm max), $-40$ to $85^{\circ}$C
|
||||
industrial, $-7$ speed grade (143\,MHz max). VDD/VDDQ 3.3\,V $\pm$0.3\,V.
|
||||
Single-ended \code{CLK} --- \textbf{no \code{CLK\_N}}, this is SDR, not
|
||||
DDR, SDRAM. Real distributor availability confirmed: DigiKey product
|
||||
11613071, 568 units in stock, \$31.12/unit (qty 1), 16-week
|
||||
manufacturer lead time.
|
||||
|
||||
\subsection{Complete AS4C32M16SB-7BIN ball assignment}
|
||||
From the manufacturer's own \code{-7BIN}-specific datasheet (Alliance
|
||||
Memory, Rev.\,1.4, June 2024, Figure~1.1 --- the real TFBGA ball
|
||||
diagram, not inferred from the TSOP-II \code{-7TIN} pinout).
|
||||
|
||||
\begin{fnnote}[Address / Bank]
|
||||
A0=H7, A1=H8, A2=J8, A3=J7, A4=J3, A5=J2, A6=H3, A7=H2, A8=H1, A9=G3,
|
||||
A10/AP=H9, A11=G2, A12=G1, BA0=G7, BA1=G8.
|
||||
\end{fnnote}
|
||||
\begin{fnnote}[Data / Masks]
|
||||
DQ0=A8, DQ1=B9, DQ2=B8, DQ3=C9, DQ4=C8, DQ5=D9, DQ6=D8, DQ7=E9, DQ8=E1,
|
||||
DQ9=D2, DQ10=D1, DQ11=C2, DQ12=C1, DQ13=B2, DQ14=B1, DQ15=A2, LDQM=E8,
|
||||
UDQM=F1.
|
||||
\end{fnnote}
|
||||
\begin{fnnote}[Control / Power]
|
||||
CLK=F2, CKE=F3, CS\#=G9, RAS\#=F8, CAS\#=F7, WE\#=F9. VDD=\{A9,E7,J9\},
|
||||
VSS=\{A1,E3,J1\}, VDDQ=\{A7,B3,C7,D3\}, VSSQ=\{A3,B7,C3,D7\}, NC=E2.
|
||||
\end{fnnote}
|
||||
|
||||
\subsection{FPGA $\leftrightarrow$ SDRAM mapping (real, LPF-verified)}
|
||||
From \code{hardware/v2/constraints/v2\_board\_top.lpf} (45/45 unique
|
||||
FPGA balls, no duplicates, LFE5U-45F-8BG381 rev.\,3.0 CSV-verified).
|
||||
|
||||
\begin{fnnote}[FPGA ball $\to$ SDRAM ball, by signal group]
|
||||
\code{sdram\_a[0..12]}: D5,D3,F4,E5,E3,F5,A2,B1,C2,C1,D2,D1,F1 $\to$
|
||||
A0..A12 (H7,H8,J8,J7,J3,J2,H3,H2,H1,G3,H9,G2,G1). \code{sdram\_ba[0:1]}:
|
||||
E4,C3 $\to$ BA0,BA1 (G7,G8). \code{sdram\_dq[0..15]}:
|
||||
E1,G5,H3,J5,K3,K2,H1,J1,K1,K4,L4,L5,M5,M4,N4,N5 $\to$ DQ0..DQ15.
|
||||
\code{sdram\_dqm[0:1]}: P5,N3 $\to$ LDQM,UDQM. Control:
|
||||
\code{sdram\_cke/cs\_n/ras\_n/cas\_n/we\_n}: B5,C5,C4,A3,B3 $\to$
|
||||
CKE,CS\#,RAS\#,CAS\#,WE\#.
|
||||
\end{fnnote}
|
||||
|
||||
\subsection{Real, measured clock closure (nextpnr-ecp5, 8 seeds/config)}
|
||||
\label{sec:clock-closure-current}
|
||||
\begin{fnwarn}[Updated 2026-09-07 --- supersedes the ERR-0029-era numbers below]
|
||||
Flash \#1 (\S\ref{sec:prog-addendum}, since removed) briefly regressed
|
||||
N\_SLOTS=4 from 8/8 to 3/8 while it was integrated; that integration
|
||||
was reverted, prioritizing clock frequency over on-board flash
|
||||
persistence. A further real fix (DEC-0042, replacing a combinational
|
||||
fan-out with a synchronous counter) closed N\_SLOTS=4 back to 8/8 on
|
||||
the flash-free design --- the numbers below are the CURRENT, real,
|
||||
final state.
|
||||
\end{fnwarn}
|
||||
\begin{tabularx}{\textwidth}{L{4.0cm} C{1.6cm} C{2.2cm} X}
|
||||
\toprule
|
||||
\rowh \thd{Configuration} & \thd{Pass} & \thd{Worst / Best Fmax} & \thd{Notes} \\
|
||||
\midrule
|
||||
N\_SLOTS=4 @ 64\,MHz & \textbf{8/8} & 64.55 / 72.37\,MHz & \textbf{Production baseline, GO} \\
|
||||
\rowa N\_SLOTS=8 @ 64\,MHz & 3/8 & --- & Out of current scope, not pursued further \\
|
||||
N\_SLOTS=4/8 @ 80\,MHz & 0/8 & --- & NO-GO, genuine \code{ecppll}-regenerated PLL (re-confirmed pre-revert; not re-tested post-revert, expected unchanged) \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
Root cause of the last N\_SLOTS=4 failure (seed1, real critical-path
|
||||
trace): \code{neural\_director.job\_out\_slot} $\to$
|
||||
\code{dependency\_manager.node\_resolved}/\code{node\_state}, a
|
||||
producer-completion broadcast crossing physically distant regions of
|
||||
the die (75--84\% routing, not a serial logic chain --- already a
|
||||
flat, parallel 64-way compare, so the ERR-0027/0028/0029 restructuring
|
||||
fix class does not apply here). The real contributor found: this
|
||||
chapter's own \code{FPGA\_DATA\_READY} support (\S\ref{sec:host-addendum})
|
||||
read \code{node\_state[0:N\_NODES-1]} combinationally every cycle,
|
||||
adding real fan-out onto that same congested signal. Fixed by
|
||||
replacing the OR-reduce with a synchronous up/down counter (see
|
||||
\S\ref{sec:host-addendum} for the exact formula) --- worst seed
|
||||
improved 62.47\,MHz $\to$ 64.55\,MHz, closing the last failing seed.
|
||||
See \code{decisions.log} DEC-0042 for full detail. A further
|
||||
pipelining fix on the same broadcast path is a real, identified,
|
||||
not-yet-attempted option if more margin is ever needed.
|
||||
|
||||
\subsection{Directed SDRAM boundary verification}
|
||||
A dedicated directed testbench (\code{tb\_sdram\_boundary.v}, 21 checks)
|
||||
covers every address/row/bank boundary the randomized D-Stress
|
||||
regression does not directly target: exact first/last address
|
||||
(\code{0x000000}/\code{0x3FFFFF}), the row-10/row-11 column boundary,
|
||||
all three inter-bank crossings, the real V2 memory-map boundaries
|
||||
(weights/activations/results base and last-word-before-next-region),
|
||||
and all four byte-mask combinations with distinct deterministic
|
||||
patterns. All 21 addresses are written first, then read back in
|
||||
\textbf{reversed} order with address-derived patterns, proving no
|
||||
write corrupts any neighbouring address. \textbf{Result: 21/21 PASS at
|
||||
both 64\,MHz and 166\,MHz --- no bug found}, closing the one directed
|
||||
boundary-test gap disclosed earlier in the project's own verification
|
||||
history.
|
||||
|
||||
\subsection{Verified SPI host operating clock}
|
||||
\label{sec:spi-max-verified}
|
||||
A dedicated sweep testbench (\code{tb\_spi\_freq\_sweep.v}) drives the
|
||||
real \code{fpga\_neural\_v2\_top} (not \code{spi\_host\_bridge} in
|
||||
isolation) at the real 64\,MHz system clock and sweeps the SPI bit
|
||||
rate across single-job, back-to-back, gapped, and raw
|
||||
\code{WRITE\_MEM}/\code{READ\_MEM} traffic. The breakpoint is
|
||||
\textbf{exact and deterministic}: PASS at every rate up to
|
||||
\textbf{12.8\,MHz (precisely 64\,MHz/5)}, FAIL (data corruption, then
|
||||
protocol FSM hang) at every rate at or above it --- the triple-flop CDC
|
||||
synchronizer plus edge-detect/FSM reaction in \code{spi\_host\_bridge.v}
|
||||
requires at least 5 full system-clock cycles per SPI bit period to
|
||||
reliably track \code{sclk}/\code{mosi}/\code{cs\_n} transitions, a real
|
||||
property of the CDC design (correct, standard practice), not a bug.
|
||||
\textbf{SPI\_MAX\_VERIFIED = 12\,MHz} is the recommended host operating
|
||||
point (real margin below the hard 12.8\,MHz edge, $\approx$6.7\%
|
||||
headroom). Board-level electrical limits (trace length, driver
|
||||
rise/fall time, ground bounce, real metastability risk) are
|
||||
\textbf{not} modeled by this deterministic simulation and remain to be
|
||||
confirmed empirically at bring-up.
|
||||
|
||||
\section{Power supply design (2026-09-07) --- verified against the real
|
||||
Lattice hardware checklist}
|
||||
\label{sec:power-addendum}
|
||||
\begin{fnwarn}[Real design data, not estimated]
|
||||
The actual rail topology, sized against the real, primary-source
|
||||
Lattice and TI documents below.
|
||||
\end{fnwarn}
|
||||
|
||||
\subsection{Rail topology}
|
||||
Three rails, one simplification from the original V1 reference design:
|
||||
\textbf{no separate buck regulator for the 3.3\,V I/O rail} --- the
|
||||
board's own external input is specified as \textbf{3.3\,V}, so
|
||||
\code{VCCIO}, the SDRAM (VDD/VDDQ, 3.3\,V per its own datasheet), and
|
||||
the flash (3.3\,V) are fed directly from the board input. A buck
|
||||
targeting 3.3\,V output from a 3.3\,V input would run at 100\% duty
|
||||
cycle permanently --- zero regulation margin, no benefit over a direct
|
||||
connection.
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{2.6cm} L{2.0cm} L{3.2cm} X}
|
||||
\toprule
|
||||
\rowh \thd{Rail} & \thd{Value} & \thd{Source} & \thd{Feeds} \\
|
||||
\midrule
|
||||
I/O & 3.3\,V & Direct board input & FPGA \code{VCCIO0--8}, SDRAM VDD/VDDQ, SPI flash, PMOD \\
|
||||
\rowa Core & 1.1\,V & TLV62568 (buck), from the 3.3\,V rail & FPGA \code{VCC} \\
|
||||
Auxiliary & 2.5\,V & TLV73325 (LDO), from the 3.3\,V rail & FPGA \code{VCCAUX} \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\subsection{Power-up sequencing --- real Lattice requirement, verified
|
||||
compliant}
|
||||
Per Lattice's own \emph{ECP5 and ECP5-5G Hardware Checklist}
|
||||
(FPGA-TN-02038-2.0, July 2024), \S4: \emph{``\code{VCCIO} supplies
|
||||
should be powered up before or together with the \code{VCC} and
|
||||
\code{VCCAUX} supplies.''} The same document's \S2 adds: all three
|
||||
monitored rails must rise \textbf{monotonically}, and the on-chip
|
||||
Power-On-Reset de-asserts only once \code{VCC}$\geq$0.9\,V,
|
||||
\code{VCCAUX}$\geq$2.0\,V, and \code{VCCIO8}$\geq$0.95\,V are all
|
||||
simultaneously satisfied --- device initialization waits for whichever
|
||||
of the three is slowest.
|
||||
|
||||
This board's topology satisfies the requirement \textbf{by construction},
|
||||
with no sequencer IC needed: \code{VCCIO} (3.3\,V) is a direct,
|
||||
unregulated connection to the board input, so it rises first/fastest,
|
||||
strictly before the two regulated rails (Core, Aux) can even begin
|
||||
their own soft-start ramps --- ``before or together with'' is met on
|
||||
every possible power-up transient, not just the typical case.
|
||||
|
||||
\subsection{Decoupling --- real Lattice-recommended values (not a
|
||||
generic ``one cap per pin'' guess)}
|
||||
Per FPGA-TN-02038-2.0 Table~3.1 (\S3.1), applied per-rail:
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{2.0cm} L{5.0cm} X}
|
||||
\toprule
|
||||
\rowh \thd{Rail} & \thd{Filter} & \thd{Notes} \\
|
||||
\midrule
|
||||
\code{VCC} & 10\,\textmu F $\times$3 (bulk) $+$ 100\,nF per pin & Core, 1.1\,V \\
|
||||
\rowa \code{VCCAUX} & 120\,$\Omega$ ferrite bead $+$ 10\,\textmu F $+$
|
||||
100\,nF per pin & 2.5\,V; \textbf{new part not in the earlier power
|
||||
tree draft} --- a ferrite bead in series was missing before this
|
||||
verification pass \\
|
||||
\code{VCCIO[0--8]} & 10\,\textmu F $+$ 100\,nF per pin (per bank in
|
||||
use) & 1\textmu F acceptable on unused banks; 22\,\textmu F (or a
|
||||
second 10\,\textmu F) on banks with heavy output loading \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
Capacitor selection, also per the same document: X5R/X7R dielectric
|
||||
(avoid Y5V/Z5U), voltage rating $\geq$80\% above the rail's maximum ---
|
||||
for the 3.3\,V rail this means a \textbf{6.3\,V minimum} rating, not
|
||||
the bare 3.3\,V-rated parts sometimes used to save cost. All ground
|
||||
pins tie to the board's ground plane (no star grounding on this
|
||||
family).
|
||||
|
||||
\subsection{Regulator component values (real, computed from datasheet
|
||||
constants)}
|
||||
\textbf{TLV62568} (core, 1.1\,V): input range 2.5--5.5\,V (3.3\,V
|
||||
input has full margin); feedback reference \code{VFB}$=0.6$\,V
|
||||
(typical, per TI SLVSD89B). Output set via
|
||||
$V_{OUT}=V_{FB}\left(1+\frac{R1}{R2}\right)$: choosing
|
||||
\textbf{R1$=$100\,k$\Omega$, R2$=$120\,k$\Omega$} gives
|
||||
$0.6\times(1+100/120)=1.1$\,V exactly. Per TI's own typical
|
||||
application circuit: C1$=$4.7\,\textmu F on \code{VIN}, L1$=$2.2\,
|
||||
\textmu H inductor, C2$=$10\,\textmu F on \code{VOUT}.
|
||||
|
||||
\textbf{TLV73325} (auxiliary, 2.5\,V fixed-output LDO): input range
|
||||
1.4--5.5\,V (per TI SBVS221, real datasheet), dropout 125\,mV at
|
||||
300\,mA --- far above this rail's $\sim$10\,mA real load, so dropout is
|
||||
not a concern at 3.3\,V input. Capacitor-free architecture (stable
|
||||
without external caps at the regulator itself); the 10\,\textmu F$+$
|
||||
100\,nF on \code{VCCAUX} above are the FPGA-side filter from
|
||||
FPGA-TN-02038, not regulator-stability caps, and are still required.
|
||||
|
||||
\begin{fnnote}[16\,MHz oscillator: frozen]
|
||||
\textbf{ECS Inc. International \code{ECS-3225MV-160-BN-TR}} --- a
|
||||
quartz crystal oscillator (XO, not a bare crystal; direct digital clock
|
||||
output, no external oscillator circuit needed), 3225 SMD package
|
||||
(3.2$\times$2.5\,mm, 4-pad, matching the real KiCad footprint for U5),
|
||||
3.3\,V supply (matches \code{osc\_clk}'s real \code{IO\_TYPE=LVCMOS33}
|
||||
ball H5 exactly, no level-shifting needed), $\pm$50\,ppm stability,
|
||||
$-40$ to $+85^{\circ}$C. One 100\,nF decoupling capacitor across
|
||||
\code{VDD}/\code{GND}, placed close to the supply pin. The exact
|
||||
terminal order-code suffix (stability/output-enable option letters)
|
||||
should be cross-checked against ECS's current published datasheet at
|
||||
BOM lock --- normal due diligence, not an open architectural question.
|
||||
\end{fnnote}
|
||||
|
||||
\subsection{Power tree}
|
||||
\begin{center}
|
||||
\begin{tikzpicture}[node distance=10mm and 14mm,font=\footnotesize]
|
||||
\node[fnblockD,minimum width=30mm,minimum height=11mm] (in){Board input\\3.3\,V};
|
||||
\node[fnblock,below left=14mm and -6mm of in,minimum width=32mm,minimum height=13mm] (u3){TLV62568 (buck)\\R1/R2 $\to$ 1.1\,V};
|
||||
\node[fnblockT,below right=14mm and -6mm of in,minimum width=32mm,minimum height=13mm] (u5){TLV73325 (LDO)\\fixed 2.5\,V};
|
||||
\node[fnblockA,right=30mm of in,minimum width=26mm,minimum height=11mm] (io){\code{VCCIO0--8}\\3.3\,V direct};
|
||||
\node[fnreg,below=8mm of io,minimum width=26mm] (sd){SDRAM VDD/VDDQ};
|
||||
\node[fnreg,below=6mm of sd,minimum width=26mm] (fl){SPI flash \code{VCC}};
|
||||
\node[fnblock,below=18mm of u3,minimum width=30mm,minimum height=11mm] (core){FPGA \code{VCC}\\1.1\,V core};
|
||||
\node[fnblockT,below=18mm of u5,minimum width=30mm,minimum height=11mm] (aux){FPGA \code{VCCAUX}\\2.5\,V};
|
||||
\draw[fnbus] (in) -- (io);
|
||||
\draw[fnarrow] (io) -- (sd);
|
||||
\draw[fnarrow] (io) -- (fl);
|
||||
\draw[fnbus] (in) -- (u3);
|
||||
\draw[fnbus] (in) -- (u5);
|
||||
\draw[fnbus] (u3) -- node[fnlbl,right]{10\textmu F$\times$3 + 100nF/pin} (core);
|
||||
\draw[fnbus] (u5) -- node[fnlbl,right]{120$\Omega$ FB + 10\textmu F + 100nF/pin} (aux);
|
||||
\end{tikzpicture}
|
||||
\end{center}
|
||||
\begin{center}
|
||||
{\scriptsize Power tree, direct 3.3\,V I/O rail (no redundant buck), verified against
|
||||
FPGA-TN-02038-2.0 \S3--4. Full schematic capture (BOM, connectors,
|
||||
FPGA--RAM/FLASH and PROG sections) pending separately.}
|
||||
\end{center}
|
||||
|
||||
\section{Programming architecture (updated 2026-09-07) --- single
|
||||
boot flash, ESP32 over JTAG only}
|
||||
\label{sec:prog-addendum}
|
||||
\begin{fnwarn}[Real, closed design -- superseded once, now final]
|
||||
Originally converged on a two-flash design (\S below described flash
|
||||
\#1 for neural-network data and flash \#2 for boot). Flash \#1 was
|
||||
fully implemented (real V1 subsystem instantiated, a new byte$\leftrightarrow$word
|
||||
adapter, a new SPI opcode, a dedicated testbench, 64/64 bytes verified
|
||||
bit-exact) and then \textbf{removed again}, per an explicit design
|
||||
decision: it measurably regressed N\_SLOTS=4's own real timing
|
||||
closure (8/8 $\to$ 3/8 PASS at 64\,MHz), and clock frequency was
|
||||
judged more valuable than on-board persistent weight storage --- the
|
||||
ESP32 can push weights fresh each session instead. Reverted cleanly
|
||||
via \code{git revert} (commit \code{59901a4}, fully recoverable from
|
||||
history if ever needed again). This section now describes the
|
||||
current, real, single-flash architecture. See \code{decisions.log}
|
||||
DEC-0041 (original two-flash design) and DEC-0042 (removal + the
|
||||
timing recovery that followed) for the complete history.
|
||||
\end{fnwarn}
|
||||
|
||||
\subsection{One physical flash chip: boot bitstream only}
|
||||
\textbf{Winbond \code{W25Q128JVPIM}} (128\,Mbit, WSON-8, 6$\times$5\,mm
|
||||
--- real BOM entry U9, \S\ref{sec:bom}). Connects exclusively to the
|
||||
ECP5's own dedicated sysCONFIG pins, Master SPI mode, auto-boots every
|
||||
power-up, zero ESP32 involvement in normal operation. No second flash
|
||||
device, no on-board neural-network
|
||||
weight persistence in the current design --- the host (ESP32) is
|
||||
responsible for pushing weight/activation data into SDRAM fresh each
|
||||
session via the real SPI application protocol
|
||||
(\S\ref{sec:host-addendum}).
|
||||
|
||||
\subsection{ESP32 $\leftrightarrow$ ECP5: JTAG only}
|
||||
Neither ESP32-S3 nor ESP32-C6 has a hardware JTAG \emph{master}
|
||||
peripheral (verified against Espressif's own documentation): their
|
||||
native ``USB Serial/JTAG Controller'' lets an external host debug the
|
||||
ESP32 itself --- the wrong direction for driving the ECP5. TCK/TMS/
|
||||
TDI/TDO are therefore bit-banged from ordinary ESP32 GPIO, standard
|
||||
practice. ESP32 updates flash~\#2 by commanding the ECP5's own
|
||||
internal sysCONFIG engine to bridge JTAG writes through to the
|
||||
external flash (real Lattice mechanism, FPGA-TN-02038-2.0 Figure~6.3,
|
||||
``Programming external Flash via JTAG'') --- ESP32 never drives
|
||||
flash~\#2's own SPI pins directly, zero bus contention by
|
||||
construction.
|
||||
|
||||
\subsection{Real ball assignments (CABGA381)}
|
||||
From the official Lattice pinout CSV (\code{FPGA-SC-02034-3-0-
|
||||
ECP5U-45-Pinout.csv} rev.\,3.0) cross-checked against Project
|
||||
Trellis's \code{iodb.json}.
|
||||
|
||||
\begin{fnnote}[JTAG (bank 40/TAP) --- to ESP32]
|
||||
TCK=T5, TMS=U5, TDI=R5, TDO=V4.
|
||||
\end{fnnote}
|
||||
\begin{fnnote}[Dedicated config (bank 8) --- to ESP32]
|
||||
PROGRAMN=W3, INITN=V3, DONE=Y3.
|
||||
\end{fnnote}
|
||||
\begin{fnnote}[CFG{[}2:0{]} (bank 8) --- board jumpers/0$\Omega$, NOT to ESP32]
|
||||
For MSPI, CFG[2:0]$=$[0,1,0] read MSB-first: CFG\_2(R4)$=$GND,
|
||||
CFG\_1(T4)$=$pull-up 1--10\,k$\Omega$ to VCCIO8, CFG\_0(U4)$=$GND.
|
||||
\end{fnnote}
|
||||
\begin{fnnote}[MSPI dedicated/dual-function pins to flash \#2 (bank 8) --- NOT to ESP32]
|
||||
MCLK/CCLK=U3, CSSPIN=R2 (dual w/ HOLDN/DI/BUSY/CEN), D0/MOSI=W2,
|
||||
D1/MISO=V2.
|
||||
\end{fnnote}
|
||||
|
||||
Confirmed real and safe (Lattice FPGA-TN-02039-2.3 sysCONFIG User
|
||||
Guide, \S6.1.2): once User Mode is reached, the MSPI dedicated pins
|
||||
tristate with a weak pull-up, so they never contend with another
|
||||
driver on the same net --- not load-bearing for the current
|
||||
single-flash architecture (nothing else shares these pins), but
|
||||
confirms the mechanism is real should a future revision ever add a
|
||||
second flash device sharing this same chip.
|
||||
|
||||
\section{Real KiCad schematic review (2026-09-07)}
|
||||
\label{sec:schematic-review}
|
||||
\begin{fnwarn}[Schematic capture reviewed against every real ball
|
||||
assignment established in this chapter]
|
||||
This section records an actual review pass of the KiCad schematic
|
||||
capture (sheet \code{FPGA-Neural/FPGA.kicad\_sch}) against the real
|
||||
ball tables above --- confirmed items and real, disclosed findings,
|
||||
not a generic checklist.
|
||||
\end{fnwarn}
|
||||
|
||||
\subsection{Confirmed correct}
|
||||
JTAG (TCK=T5, TDI=R5, TDO=V4, TMS=U5); the complete real SDRAM bus
|
||||
(A0--A12, all 16 DQ, BA0/BA1, LDQM/UDQM, CLK=F2, CKE=F3, CS\#=G9,
|
||||
RAS\#=F8); \code{TLV62568}'s real component values (L1=2.2\,\textmu H,
|
||||
R1=100\,k$\Omega$/R2=120\,k$\Omega$ feedback divider, C6=4.7\,\textmu F);
|
||||
\code{TLV73325}'s 2.5\,V output; the VCCAUX ferrite (180\,$\Omega$,
|
||||
matching the approved \code{CBG160808U181T}); \code{FPGA\_DATA\_READY}=G3,
|
||||
\code{FPGA\_RESET}=B4, \code{osc\_clk}=H5; CFG\_1's 10\,k$\Omega$
|
||||
pull-up (inside the required 1--10\,k$\Omega$ range).
|
||||
|
||||
\subsection{Real findings --- all resolved as of this pass}
|
||||
\begin{enumerate}
|
||||
\item \textbf{Boot-flash net-name mismatch}: \textbf{resolved}. The
|
||||
original capture had the flash chip's own pins labeled
|
||||
\code{FPGA\_SPI\_CS/SCLK/MOSI/MISO} while the ECP5's dedicated
|
||||
MSPI pins (CSSPIN/MCLK/D0/D1, ball R2/U3/W2/V2) were labeled
|
||||
\code{FGPA\_SPI\_CLK/MISO/MOSI/CS} --- a transposed
|
||||
\code{FGPA}/\code{FPGA} typo, and \code{SCLK} vs.\ \code{CLK}
|
||||
being two different label strings (KiCad nets are formed by
|
||||
exact label-text match, so auto-boot from flash would have
|
||||
silently failed). The corrected schematic now shows all eight
|
||||
labels as identical text, \code{FPGA\_SPI\_CS/SCLK/MOSI/MISO},
|
||||
on both the flash chip and the ECP5's dedicated pins --- verified
|
||||
by direct comparison of the two label sets in the updated
|
||||
capture (\S\ref{sec:schematic-capture}).
|
||||
\end{enumerate}
|
||||
|
||||
\begin{fnnote}[Checked and cleared]
|
||||
SDRAM CAS\#/WE\#: verified CAS\#=F7, WE\#=F9 in the real schematic ---
|
||||
matches this chapter exactly. The apparent swap in the original review
|
||||
was a misread of the schematic image, not a real error.
|
||||
\end{fnnote}
|
||||
|
||||
\subsection{Open items --- all resolved as of this pass}
|
||||
\begin{itemize}
|
||||
\item \code{TLV62568}'s EN pin: \textbf{resolved} --- \code{R3}=499\,k$\Omega$
|
||||
confirmed on EN (BOM, \S\ref{sec:bom}), matches TI's own
|
||||
reference circuit.
|
||||
\item The \code{+1V1} label near the VCCAUX ferrite (L2): \textbf{resolved,
|
||||
false alarm}. \code{TLV62568} (U1) itself outputs 1.1\,V (directly
|
||||
confirmed against the schematic, matches the R1/R2 divider calculation in
|
||||
\S\ref{sec:power-addendum}) --- the label belongs to U1's own
|
||||
real output net, merely placed nearby on the schematic page, not
|
||||
routed through the VCCAUX ferrite. VCCAUX remains 2.5\,V as
|
||||
required.
|
||||
\item JTAG pull-up array (R5--R12, 4.7\,k$\Omega$): TDI/TDO/TMS need a
|
||||
pull-up to VCCIO8, TCK needs a pull-\emph{down} to GND ---
|
||||
\textbf{resolved}: the real BOM (\S\ref{sec:bom}) confirms these
|
||||
are 8 \emph{discrete} 0402 parts, not a single bussed-array
|
||||
package, so each can carry its own correct polarity (still
|
||||
needs a final visual confirmation of the actual net-by-net
|
||||
wiring, but the package-level limitation is ruled out).
|
||||
\end{itemize}
|
||||
|
||||
\section{Real KiCad schematic capture (2026-09-07)}
|
||||
\label{sec:schematic-capture}
|
||||
\begin{fnwarn}[Source of these figures]
|
||||
Plotted directly from the real KiCad project
|
||||
(\code{FPGA-Neural/FPGA-Neural.kicad\_sch}, hierarchy: root
|
||||
\code{FPGA-Neural} $\to$ sheet \code{FPGA} $\to$ sheet
|
||||
\code{UnusedBank}) via \code{kicad-cli sch export pdf}, not a
|
||||
re-rendered screenshot --- what follows is the schematic exactly as
|
||||
it exists in the project file at commit time.
|
||||
\end{fnwarn}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=\textwidth,page=2]{images/fpga-neural-v2-schematic.pdf}
|
||||
\caption{Main sheet (\code{FPGA}): FPGA symbols U2A/U2F/U2G/U2H/U2I,
|
||||
regulators U1/U3, SDRAM U4, boot flash U9, 16\,MHz crystal U5, and the
|
||||
full real net/label set reviewed in \S\ref{sec:schematic-review}.}
|
||||
\end{figure}
|
||||
|
||||
\begin{figure}[htbp]
|
||||
\centering
|
||||
\includegraphics[width=\textwidth,page=3]{images/fpga-neural-v2-schematic.pdf}
|
||||
\caption{\code{UnusedBank} sheet: unused/reserved FPGA I/O bank, held
|
||||
for future expansion (\S\ref{sec:pcb-module}).}
|
||||
\end{figure}
|
||||
|
||||
\begin{fnnote}[Sheets present in the project but not reachable from
|
||||
the root hierarchy]
|
||||
\code{power.kicad\_sch}, \code{ram.kicad\_sch}, and
|
||||
\code{embeddedia.kicad\_sch} exist as files in the KiCad project
|
||||
directory but are not referenced by any sheet symbol in the current
|
||||
hierarchy (checked directly against the real \code{.kicad\_sch}
|
||||
sheet-reference fields) --- their content is already folded into the
|
||||
\code{FPGA} sheet above. Left as-is; not board-affecting, since KiCad
|
||||
only builds/plots what the root hierarchy actually reaches.
|
||||
\end{fnnote}
|
||||
|
||||
\section{Bill of Materials (real, KiCad-exported, 2026-09-07)}
|
||||
\label{sec:bom}
|
||||
\begin{fnwarn}[Real export, cross-checked against every value this
|
||||
chapter specifies]
|
||||
Regenerated directly from the real KiCad source
|
||||
(\code{kicad-cli sch export bom}, grouped by value+footprint) ---
|
||||
not the CSV snapshot the earlier review used. Every value
|
||||
matches exactly (feedback divider, inductor, ferrite, regulators,
|
||||
SDRAM). One real discrepancy found: see below.
|
||||
\end{fnwarn}
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{2.4cm} C{1.0cm} L{2.6cm} X}
|
||||
\toprule
|
||||
\rowh \thd{Ref} & \thd{Qty} & \thd{Value} & \thd{Footprint / Part} \\
|
||||
\midrule
|
||||
C2,C3,C9,C13,C15,C17,C19,C21,C23,C25,C27,C28,C30 & 13 & 100\,nF & 0402 \\
|
||||
\rowa C4 & 1 & 1\,\textmu F & 01005 (TLV73325 CIN) \\
|
||||
C5,C7,C8,C10,C11,C12,C14,C16,C18,C20,C22,C24,C26,C29 & 14 & 10\,\textmu F & 0603 \\
|
||||
\rowa C6 & 1 & 4.7\,\textmu F & 01005 (TLV62568 CIN) \\
|
||||
L1 & 1 & 2.2\,\textmu H & 0805, 1.7\,A/215\,m$\Omega$ \\
|
||||
\rowa L2 & 1 & 180\,$\Omega$ & 0603, \code{CBG160808U181T} (VCCAUX ferrite) \\
|
||||
R1 & 1 & 100\,k$\Omega$ & 0402 (TLV62568 FB) \\
|
||||
\rowa R2 & 1 & 120\,k$\Omega$ & 0402 (TLV62568 FB) \\
|
||||
R3 & 1 & 499\,k$\Omega$ & 0402 (TLV62568 EN, matches TI's own reference) \\
|
||||
\rowa R4 & 1 & 10\,k$\Omega$ & 0402 (CFG\_1 pull-up) \\
|
||||
R5--R12 & 8 & 4.7\,k$\Omega$ & 0402, discrete (JTAG/PROGRAMN/INITN/DONE/CSSPIN) \\
|
||||
\rowa U1 & 1 & \code{TLV62568DBV} & SOT-23-5 \\
|
||||
U2 & 1 & \code{LFE5U-45F-8BG381C} & 381-ball caBGA, 0.8\,mm pitch, 20$\times$20 array, 17$\times$17$\times$1.76\,mm body --- \textbf{grade now verified fixed, see below} \\
|
||||
\rowa U3 & 1 & \code{TLV73325PDBV} & SOT-23-5 \\
|
||||
U4 & 1 & \code{AS4C32M16SB-7BIN} & 54-ball TFBGA, 0.8\,mm pitch, 6$\times$9 array, 8$\times$8$\times$1.2\,mm (real footprint dims match the datasheet exactly) \\
|
||||
\rowa U5 & 1 & 16\,MHz & 3225-4Pin crystal \\
|
||||
U9 & 1 & \code{W25Q128JVPIM} & WSON-8, 6$\times$5\,mm (real Winbond DTR datasheet linked) \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\subsection{Discrepancy: FPGA grade --- resolved and now source-verified}
|
||||
U2 was originally captured as \code{LFE5U-45F-8BG381I} (industrial
|
||||
grade, real $T_J$ range $-40$ to $+100^{\circ}$C) --- every other
|
||||
reference in this project (LPF, this chapter, decisions.log) uses
|
||||
\textbf{\code{LFE5U-45F-8BG381C}} (commercial grade, real $T_J$ range
|
||||
$0$ to $+85^{\circ}$C; same ``$-8$'' speed grade in both --- the
|
||||
letter suffix changes only the characterized temperature range, not
|
||||
logic speed). \textbf{The commercial (C) grade is the intended
|
||||
part}, confirmed against every other reference. This BOM regeneration confirms the fix landed in the real KiCad
|
||||
source itself, not just as a stated intent: U2's \code{Value} field
|
||||
now reads \code{LFE5U-45F-8BG381C} exactly.
|
||||
|
||||
\subsubsection{New, real, minor finding: stale footprint library name}
|
||||
U2's \emph{footprint} field is
|
||||
\code{MIKILAB\_LFE5U\_45F\_8BG381I:BGA381C80P20X20\_1700X1700X176}
|
||||
--- the library name still carries the old \code{...8BG381I} suffix
|
||||
even though the symbol \code{Value} was corrected to \code{...381C}.
|
||||
\textbf{Not board-affecting}: caBGA381-C and caBGA381-I are the same
|
||||
physical package (identical ball grid/pitch/body, grade suffix is a
|
||||
temperature-characterization distinction only, confirmed above), so
|
||||
the pad geometry itself
|
||||
(\code{BGA381C80P20X20\_1700X1700X176} --- 381 balls, 0.8\,mm pitch,
|
||||
20$\times$20, 17$\times$17$\times$1.76\,mm) is correct regardless of
|
||||
which grade the library folder is named after. Purely a stale/misleading
|
||||
library name; worth renaming the library folder to
|
||||
\code{..\_8BG381C} at some point for consistency, but does not block
|
||||
fabrication.
|
||||
|
||||
\subsection{Open items resolved by this BOM}
|
||||
R3=499\,k$\Omega$ confirms TLV62568's EN is populated (matches TI's
|
||||
own reference circuit exactly). R5--R12 being 8 \emph{discrete} 0402
|
||||
parts (not a single multi-resistor array footprint) confirms the
|
||||
earlier ``bussed array can't mix pull-up/pull-down'' concern does not
|
||||
apply --- each resistor can go to its own correct rail. U5 confirms
|
||||
the 16\,MHz oscillator, previously missing from the capture, is now
|
||||
present.
|
||||
|
||||
\subsection{Resolved}
|
||||
TLV73325's EN pin: no dedicated resistor needed --- direct wire to
|
||||
+3.3\,V (VIN), always-enabled. Unlike TLV62568's own soft-start
|
||||
R3=499\,k$\Omega$ pull-up, a plain LDO has no equivalent timing
|
||||
requirement (per TI's own datasheet: ``active high, do not leave
|
||||
floating,'' no sequencing note); no dynamic enable/disable control
|
||||
exists elsewhere in this design.
|
||||
|
||||
\section{PCB module form factor (reserved)}
|
||||
\label{sec:pcb-module}
|
||||
Target: a castellated-edge SMD module, approximately
|
||||
\textbf{50\,mm $\times$ 25\,mm}, for mounting onto a carrier board ---
|
||||
dimensions and pin-out placeholder, real layout pending. This section
|
||||
will be filled in with the actual module outline, castellation pin
|
||||
map, and mechanical drawing once available.
|
||||
|
||||
\section{Verification status --- real, disclosed open items}
|
||||
\label{sec:hw-open-items}
|
||||
Everything above is real (simulated, synthesized, and/or place\&route
|
||||
measured); this section lists what is genuinely \textbf{not yet}
|
||||
verified, honestly, rather than silently omitted.
|
||||
|
||||
\begin{tabularx}{\textwidth}{L{4.4cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Item} & \thd{Status} \\
|
||||
\midrule
|
||||
Hold-time closure & \textbf{OPEN --- tool-chain limitation.} \code{nextpnr-ecp5}'s own timing report contains setup-side (posedge$\to$posedge max-delay) data only; no hold/min-delay analysis. No \code{pytrellis}-based min-delay pass or vendor (Lattice Diamond/Radiant) static timing analysis is available in this environment. Setup timing is fully verified (\S\ref{sec:clock-closure-current}). \\
|
||||
\rowa FPGA dynamic power/current draw & \textbf{OPEN --- not computable without post-implementation tools.} No ECP5 power estimator (\code{ecppower} or equivalent) is available in this toolchain. Regulator current ratings (\S\ref{sec:power-addendum}) are real, datasheet-supported engineering margin against this unknown, not a computed budget. \\
|
||||
N\_SLOTS=8 @ 64\,MHz & \textbf{Deferred, not production-frozen} --- functionally correct (bit-exact), 3/8 seeds pass timing closure. See \S\ref{sec:clock-closure-current}. \\
|
||||
\rowa Board-level SPI electrical limit & \textbf{OPEN --- requires real hardware.} \S\ref{sec:spi-max-verified}'s 12\,MHz recommendation is a simulation-verified logical limit; real trace length, driver rise/fall time, and metastability risk are not modeled by simulation. \\
|
||||
Embedded-host (ESP32-class) benchmark baseline & \textbf{OPEN --- no hardware available.} No comparison against a real ESP32 host exists; all host-side timing is protocol-level (ch.~\ref{ch:host}), not measured on real silicon. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
@@ -1,86 +0,0 @@
|
||||
\chapter{Register-level interface \& internal state encodings}
|
||||
\label{ch:regs}
|
||||
|
||||
\begin{fnwarn}[Real SPI opcode map exists; state encodings below are
|
||||
per-module reference]
|
||||
Ch.~\ref{ch:host} now documents V2's real, physical SPI opcode map
|
||||
(\code{WRITE\_JOB}/\code{WRITE\_MEM}/\code{READ\_MEM}/\code{STATUS}/
|
||||
\code{RESET}) --- this chapter's own node-registration field layout
|
||||
below remains the logical field reference (repeated here for quick
|
||||
reference). The \textbf{internal FSM state encodings} below are useful
|
||||
for simulation-level debug; \S\S\ref{ch:regs}'s Dependency
|
||||
Manager/Neural Director tables are shared by every V2 architecture
|
||||
(unchanged between the PSRAM-era and current SDRAM boards). The Memory
|
||||
Manager and Neural Processor tables were captured from the PSRAM-era
|
||||
\code{memory\_manager.v}/\code{neural\_processor.v} pairing (ch.~\ref{ch:arch})
|
||||
--- the current SDRAM board's \code{nms\_memory\_manager\_stream\_wide.v}
|
||||
implements the same functional handshake (prefetch $\to$ stream $\to$
|
||||
write-back $\to$ done) against the SDRAM backend instead of PSRAM, but
|
||||
its own internal state encoding was not re-transcribed into this table.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Node registration fields (quick reference)}
|
||||
See ch.~\ref{ch:host} for the full field-level description.
|
||||
\code{reg\_node\_id}, \code{reg\_required}, \code{reg\_producer\_ids},
|
||||
\code{reg\_x\_base}, \code{reg\_w\_base}, \code{reg\_n\_tiles},
|
||||
\code{reg\_result\_addr} --- valid/ready handshake, \code{reg\_ready}
|
||||
gated on the target node id's table slot being \code{EMPTY}.
|
||||
|
||||
\section{Dependency Manager node state (\texttt{node\_state})}
|
||||
\begin{tabularx}{\textwidth}{C{1.4cm} L{2.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Value} & \thd{Name} & \thd{Meaning} \\
|
||||
\midrule
|
||||
\op{2'd0} & \code{ST\_EMPTY} & Table slot free; \code{reg\_ready} asserted for this node id. \\
|
||||
\rowa \op{2'd1} & \code{ST\_WAITING} & Registered, at least one producer not yet resolved. \\
|
||||
\op{2'd2} & \code{ST\_READY} & All producers resolved; eligible for dispatch. \\
|
||||
\rowa \op{2'd3} & \code{ST\_DISPATCHED} & Handed to the Director; \textbf{terminal} (\S\ref{ch:sched}). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Neural Director state (\texttt{dir\_state})}
|
||||
\begin{tabularx}{\textwidth}{C{1.4cm} L{2.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Value} & \thd{Name} & \thd{Meaning} \\
|
||||
\midrule
|
||||
\op{4'd0} & \code{DIR\_IDLE} & Reset/startup. \\
|
||||
\rowa \op{4'd1} & \code{DIR\_SCAN\_READY} & Checking whether a queued job and a free slot both exist. \\
|
||||
\op{4'd2} & \code{DIR\_ALLOCATE} & Dispatching the head-of-queue job to the first free slot. \\
|
||||
\rowa \op{4'd3} & \code{DIR\_ERROR} & Recoverable only via reset (an isolated fault never blocks other slots). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Memory Manager state (\texttt{state})}
|
||||
\begin{tabularx}{\textwidth}{C{1.4cm} L{3.0cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Value} & \thd{Name} & \thd{Meaning} \\
|
||||
\midrule
|
||||
\op{3'd0} & \code{MM\_IDLE} & Waiting for \code{job\_start}. \\
|
||||
\rowa \op{3'd1} & \code{MM\_PREFETCH\_FIRST} & Waiting for tile~0's activation \emph{and} weight halves to both arrive. \\
|
||||
\op{3'd2} & \code{MM\_STREAM} & Presenting tiles to the Neural Processor, double-buffering the next one. \\
|
||||
\rowa \op{3'd3} & \code{MM\_WAIT\_RESULT} & Last tile handed off; waiting for the Neural Processor's own result. \\
|
||||
\op{3'd4} & \code{MM\_WRITE\_RESULT} & Issuing the real PSRAM word write for the INT8 result. \\
|
||||
\rowa \op{3'd5} & \code{MM\_DONE} & Waiting for the write's own \code{mem\_ready}; then pulses \code{job\_done}. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Neural Processor state (\texttt{np\_state})}
|
||||
\begin{tabularx}{\textwidth}{C{1.4cm} L{2.8cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Value} & \thd{Name} & \thd{Meaning} \\
|
||||
\midrule
|
||||
\op{4'd0} & \code{NP\_IDLE} & No job in flight. \\
|
||||
\rowa \op{4'd1} & \code{NP\_LOAD\_JOB} & Latching \code{job\_bias}/\code{job\_activation}, clearing the accumulator. \\
|
||||
\op{4'd2} & \code{NP\_WAIT\_OPERANDS} & Consuming tiles as they arrive (absorbs the per-tile MAC/accumulate/next-tile sequence). \\
|
||||
\rowa \op{4'd3} & \code{NP\_FINISH} & Draining the pipeline after \code{tile\_last}. \\
|
||||
\op{4'd4} & \code{NP\_WRITE\_RESULT} & Result available for the Memory Manager to consume. \\
|
||||
\rowa \op{4'd5} & \code{NP\_DONE} & Job complete. \\
|
||||
\op{4'd6} & \code{NP\_ERROR} & Reachable only via an unreachable \code{default} case --- isolated per-processor, never blocks other slots. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Slot Memory Arbiter owner encoding}
|
||||
\code{owner} is \code{0} for ``no port granted'', or (port index $+1$)
|
||||
for the currently-granted port --- indices \code{0..N\_SLOTS-1} are the
|
||||
per-slot Memory Managers' own weight/write-back traffic; index
|
||||
\code{N\_SLOTS} is the shared Activation Cache's own traffic.
|
||||
@@ -1,62 +0,0 @@
|
||||
\chapter{Roadmap and development status}
|
||||
\label{ch:roadmap}
|
||||
|
||||
\section{Milestones M1--M10}
|
||||
\begin{tabularx}{\textwidth}{C{1.0cm} L{3.6cm} C{1.3cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{M} & \thd{Title} & \thd{Status} & \thd{Content} \\
|
||||
\midrule
|
||||
1 & Neural Processor & \OK & Bit-exact 8-stage pipeline vs.\ V1, 7/7 tests; 183.12~MHz isolated. \\
|
||||
\rowa 2 & Processor Array & \OK & 1/2/4/8 processors, real concurrent-slot simulation; DSP (not LUT/FF) found to saturate first. \\
|
||||
3 & Buffers & \OK & \code{activation\_buffer}/\code{weight\_buffer}/\code{result\_buffer}, real \code{DP16KD} inference --- superseded in the real datapath by the Activation Cache (\S\ref{sec:roadmapcache}). \\
|
||||
\rowa 4 & Memory Manager & \OK & Double-buffered prefetch, real V1 PSRAM chain, 3 real RTL bugs found/fixed. \\
|
||||
5 & Neural Director & \OK & First-free dispatch, real backpressure, 4/4 tests. \\
|
||||
\rowa 6 & Dependency Manager & \OK & Multi-dependency/shared-producer wake-up, 4/4 tests. \\
|
||||
7 & Dataflow Core & \OK & Full M1--M6 integration, wake-up loop closed end-to-end. \\
|
||||
\rowa 8 & PSRAM integration & \OK & Real, shared PSRAM across concurrent slots; 1 real arbiter bug found/fixed (dropped request under contention). \\
|
||||
9 & Full benchmark & \OK & V1 vs.\ V2 comparison, every number classified. \\
|
||||
\rowa 10 & Optimization & \OK & N\_SLOTS ceiling (DSP), ACC\_WIDTH 6-seed sweep, real stall/utilization instrumentation. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Post-campaign: targeted optimizations}
|
||||
\label{sec:roadmapcache}
|
||||
Following M9/M10's own final benchmark campaign
|
||||
(\code{hardware/v2/docs/benchmarks/final-benchmark.md}), two concrete
|
||||
optimizations were implemented and measured against the real toolchain:
|
||||
\begin{enumerate}
|
||||
\item \textbf{Word-level burst reads} (ch.~\ref{ch:mem}, \S\ref{sec:burst}):
|
||||
real 2.24--2.37$\times$ wall-clock speedup, negligible Fmax cost.
|
||||
\item \textbf{Shared activation cache} (ch.~\ref{ch:mem},
|
||||
\S\ref{sec:cache}): a further real 1.66--2.00$\times$ cycle
|
||||
reduction, at a real, steep Fmax cost that makes
|
||||
\code{N\_SLOTS}=4 fail 80\,MHz outright.
|
||||
\end{enumerate}
|
||||
Combined: \textbf{2.45$\times$} real wall-clock speedup at
|
||||
\code{N\_SLOTS}=2 (recommended) over the pre-optimization baseline, which
|
||||
was itself already 2.6$\times$ faster than V1.
|
||||
|
||||
\section{Open work items (real, not hidden)}
|
||||
\begin{tabularx}{\textwidth}{L{3.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Item} & \thd{Why it is open} \\
|
||||
\midrule
|
||||
Activation cache pipelining & The concrete fix for \code{N\_SLOTS}=4's Fmax failure: register the hit-detection/broadcast logic to break its single-cycle combinational path. Not attempted this round --- \code{N\_SLOTS}=4 delivers no real throughput benefit anyway (memory-bound), so this protects \code{N\_SLOTS}=2's own margin rather than making 4 useful. \\
|
||||
\rowa Dependency Manager node-slot reclamation & \code{ST\_DISPATCHED} is terminal; a real long-running system will eventually exhaust \code{N\_NODES}. \\
|
||||
Scheduler fairness & Fixed lowest-index priority shows real, measured per-slot imbalance under sustained contention (ch.~\ref{ch:sched}); no fairness-aware alternative has been measured yet. \\
|
||||
\rowa Second physical PSRAM bank & The only real way to raise the memory-bandwidth ceiling itself, rather than use existing bandwidth more efficiently --- a board-level change, not attempted this round. \\
|
||||
Real host driver \& pinout & No physical transport or placed pin assignment exists for the node-registration bus (ch.~\ref{ch:host}, ch.~\ref{ch:hw}). \\
|
||||
\rowa Per-node bias/activation & Every job currently hardcodes \code{bias=0}/\code{ACT\_RELU}; not yet exposed by the Dependency Manager's own job descriptor. \\
|
||||
Power/energy characterization & No ECP5 power estimator available in this toolchain; honestly reported as \textsc{Not measured}, not invented. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\begin{fnnote}[Every claim in this datasheet traces to a log entry]
|
||||
\code{hardware/v2/logs/}: \code{development.log}, \code{simulation.log},
|
||||
\code{synthesis.log}, \code{timing.log}, \code{benchmark.log},
|
||||
\code{decisions.log} (\code{DEC-\textit{NNNN}}), \code{experiments.log}
|
||||
(\code{EXP-\textit{NNNN}}), \code{errors.log} (\code{ERR-\textit{NNNN}}).
|
||||
IDs are never reused, past results are never overwritten, even failed
|
||||
ones --- the same discipline V1's own \code{docs/validation/} campaign
|
||||
followed.
|
||||
\end{fnnote}
|
||||
@@ -1,281 +0,0 @@
|
||||
\chapter{The Neural Memory System (NMS)}
|
||||
\label{ch:nms}
|
||||
|
||||
\begin{fnnote}[Scope of this chapter]
|
||||
Chapters~\ref{ch:arch}--\ref{ch:impl2} document \textbf{Current V2}
|
||||
(\code{memory\_manager.v} + \code{activation\_cache.v}, DEC-0015/
|
||||
DEC-0016) as a complete, frozen, real-measured system in its own
|
||||
right. This chapter documents a \textbf{parallel, later evolution} ---
|
||||
the Neural Memory System (NMS) --- built to directly address Current
|
||||
V2's own central finding (\S\ref{sec:cache}'s own honest warning:
|
||||
real parallel scaling flat beyond \code{N\_SLOTS}=2, a single shared
|
||||
PSRAM port saturating regardless of on-chip organization). Both
|
||||
systems are real, both are independently synthesizable and
|
||||
simulatable, and both remain available: \textbf{Current V2 is not
|
||||
being retired by this chapter} --- \S\ref{sec:nmscompare}'s own real
|
||||
data shows the choice between them is configuration-dependent, not a
|
||||
strict win for either.
|
||||
\end{fnnote}
|
||||
|
||||
\begin{fnwarn}[This is the direct ancestor of the current, real board
|
||||
--- read this before the rest of the chapter]
|
||||
The \code{nms\_*}-prefixed modules introduced in this chapter
|
||||
(\code{nms\_dataflow\_core.v}, \code{nms\_neural\_multiprocessor.v},
|
||||
\ldots) are the \textbf{direct code ancestors} of the real, current
|
||||
board-level RTL documented in ch.~\ref{ch:hw}/\ref{ch:toplevel}
|
||||
(\code{nms\_dataflow\_core\_sdram.v}, \code{fpga\_neural\_v2\_top.v}).
|
||||
The project's own path was: Current V2 (PSRAM, ch.~\ref{ch:arch}) $\to$
|
||||
NMS (this chapter, still PSRAM, replicated on-chip SRAM) $\to$
|
||||
\textbf{single unified SDRAM} (ch.~\ref{ch:hw}
|
||||
\S\ref{sec:sdram-mem-addendum}, the current, real, shipped board). This
|
||||
chapter's own STEP9/10 recommendation below (``adopt NMS at
|
||||
\code{N\_SLOTS}$\le$2'') was itself superseded by that final SDRAM
|
||||
step, which changed the backing memory device and re-closed timing at
|
||||
\code{N\_SLOTS}=4 (ch.~\ref{ch:hw} \S\ref{sec:clock-closure-current}).
|
||||
Read this chapter as \textbf{real history explaining how the current
|
||||
architecture was reached}, not as a currently-open choice between three
|
||||
systems.
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Design goal}
|
||||
Current V2's own memory path is fundamentally an on-demand,
|
||||
per-request architecture: every tile fetch is a fresh transaction,
|
||||
arbitrated one at a time onto the shared PSRAM port, with the
|
||||
activation cache's own single shared instance introducing exactly the
|
||||
kind of centralized combinational hit-check that \S\ref{sec:cache}
|
||||
already flagged as a real Fmax risk at higher \code{N\_SLOTS}. The
|
||||
NMS instead asks: \emph{what is the minimum on-chip organization that
|
||||
lets the Neural Processor array run at close to its own compute rate,
|
||||
treating PSRAM purely as backing storage?} Following the project's own
|
||||
established discipline, this was answered with real, measured data at
|
||||
every step (a real bandwidth-requirement study, a real bank-contention
|
||||
sweep, real candidate synthesis) rather than assumed.
|
||||
|
||||
\section{STEP1 --- real bandwidth requirement study}
|
||||
\label{sec:nmsstep1}
|
||||
An idealized backing-store model (runtime-configurable latency and
|
||||
bandwidth, simulation-only, never synthesized) drove the real,
|
||||
unmodified \code{neural\_processor.v} directly, sweeping \code{N\_SLOTS}
|
||||
$\times$ \code{PREFETCH\_DEPTH} $\times$ latency $\times$ bandwidth (768
|
||||
real Verilator data points). Three real bugs in the study harness
|
||||
itself were found and fixed first (a registered-grant race, a
|
||||
single-transfer-at-a-time serialization cap, and a stale-value
|
||||
issuance throttle) before any result was trusted.
|
||||
|
||||
\begin{fnnote}[Real result: a hard, linear bandwidth floor]
|
||||
Minimum aggregate bandwidth for $\ge$90/95/99\% of compute-only
|
||||
throughput scales \textbf{exactly linearly} with \code{N\_SLOTS} at
|
||||
\textbf{16~bytes/cycle/slot} ($=2\times$\code{P\_IN}, the raw
|
||||
activation+weight demand of one \code{neural\_processor.v} at its own
|
||||
maximum pipelined rate) --- a hard floor, not a design margin.
|
||||
\code{PREFETCH\_DEPTH} (tiles of lookahead) needed to actually reach
|
||||
that floor scales with round-trip latency, independent of bandwidth:
|
||||
$\approx$4 tiles hides 0--1~cycle latency; $\approx$16 tiles is
|
||||
\emph{not yet enough} to hide 16~cycles (83.4\% measured, not 90\%+).
|
||||
\end{fnnote}
|
||||
|
||||
\section{STEP2 --- closed-form traffic model}
|
||||
Per slot at steady state: \textbf{weight} traffic is always
|
||||
\code{P\_IN}=8~B/cycle (never shared, no amortization possible ever);
|
||||
\textbf{activation} traffic is 8~B/cycle worst case (no sharing) down
|
||||
to $\approx$0 amortized (full sharing across a layer); \textbf{result}
|
||||
traffic is negligible ($1/n\_tiles$~B/cycle/slot). The 16~B/cycle/slot
|
||||
worst-case floor measured in STEP1 is exactly $8+8$ --- a clean
|
||||
cross-validation of the simulated result against the analytical model,
|
||||
not a coincidence.
|
||||
|
||||
\section{STEP3 --- real bank-contention sweep}
|
||||
\label{sec:nmsstep3}
|
||||
A second simulation harness measured whether banking the shared
|
||||
Activation SRAM (broadcast-on-same-address, round-robin arbitration on
|
||||
conflict) actually lets \code{N\_SLOTS} scale under a \emph{realistic}
|
||||
dispatch stagger (the Neural Director dispatches one job at a time,
|
||||
never simultaneously) --- the exact mechanism behind Current V2's own
|
||||
flat-scaling finding. Two real bugs (fixed-priority starvation causing
|
||||
an actual simulation hang; a testbench/DUT handshake mismatch) were
|
||||
found and fixed first.
|
||||
|
||||
\begin{fnnote}[Real result: banking recovers real parallel scaling]
|
||||
With \code{N\_BANKS}=\code{N\_SLOTS}, aggregate throughput scales
|
||||
\textbf{near-linearly} regardless of dispatch stagger (0--8 cycles
|
||||
tested): \code{N\_SLOTS}=1\,$\to$\,0.990, 2\,$\to$\,1.979 (1.999$\times$),
|
||||
4\,$\to$\,3.950 (3.990$\times$), 8\,$\to$\,7.869 (7.949$\times$)
|
||||
tiles/cycle. With \code{N\_BANKS}=1 (matching Current V2's own single
|
||||
shared port), utilization collapses under any nonzero stagger exactly
|
||||
as Current V2's own real benchmark showed (e.g.\ \code{N\_SLOTS}=2,
|
||||
stagger=1: 49.8\%) --- the first real, simulated confirmation in this
|
||||
project that \code{N=2>N=1} and \code{N=4>N=2} are achievable without
|
||||
the shared memory nullifying parallelism.
|
||||
\end{fnnote}
|
||||
|
||||
\section{STEP4--7 --- real candidate synthesis and selection}
|
||||
Two real, synthesizable candidates were built and bit-exact verified
|
||||
for \emph{each} SRAM, then compared on real Yosys+nextpnr-ecp5 data
|
||||
(never chosen a priori):
|
||||
|
||||
\textbf{Activation SRAM.} Candidate~A (\code{N\_SLOTS} private
|
||||
replicated copies, broadcast-write fill) vs.\ Candidate~B (banked +
|
||||
round-robin arbiter + 2-stage registered crossbar, deliberately
|
||||
pipelined per \S\ref{sec:cache}'s own Fmax lesson). Candidate~A won
|
||||
decisively: 2--4$\times$ higher real Fmax and $\approx$24$\times$
|
||||
fewer LUTs than Candidate~B at \code{N\_SLOTS}=8 (\code{MAX\_TILES}=16),
|
||||
for a real BRAM cost that stays cheap even at a much deeper, more
|
||||
realistic vector length (8~DP16KD, 7\% of the chip, at
|
||||
\code{MAX\_TILES}=256/\code{N\_SLOTS}=8) --- confirming the M3-era
|
||||
warning against assuming ``shallower depth $=$ less BRAM'': at
|
||||
\code{MAX\_TILES}=16 \emph{neither} candidate used any real BRAM at
|
||||
all (Yosys chose distributed LUT-RAM for both).
|
||||
|
||||
\textbf{Weight SRAM.} Candidate~W1 (one native-width memory per slot,
|
||||
mirroring \code{weight\_buffer.v}'s own M3-era structure) vs.\
|
||||
Candidate~W2 (per-MAC-lane packed narrow memories). At
|
||||
\code{MAX\_TILES}=256 both use \emph{identical} real DP16KD count
|
||||
(one full block's own native 16\,Kbit capacity per slot, either way),
|
||||
but packed uses $\approx$2$\times$ fewer LUTs/FFs at \code{N\_SLOTS}=8
|
||||
for the same BRAM cost --- the wide single memory's own byte-lane
|
||||
write-enable decode logic is exactly what per-lane packing avoids by
|
||||
construction.
|
||||
|
||||
\textbf{Selected}: replicated Activation SRAM + packed Weight SRAM.
|
||||
Combined real cost at \code{N\_SLOTS}=8/\code{MAX\_TILES}=256: 16
|
||||
DP16KD (14.8\% of the LFE5U-45F's 108 total) --- an honestly affordable
|
||||
real price for this project's own realistic workload sizes.
|
||||
|
||||
\section{STEP8 --- full integration}
|
||||
\code{nms\_dataflow\_core.v} mirrors \code{dataflow\_core.v}'s own
|
||||
scope exactly: the Dependency Manager and Neural Director are
|
||||
\textbf{reused verbatim}, unmodified --- only the memory cluster
|
||||
changed. Each slot's own \code{nms\_memory\_manager.v} is structurally
|
||||
simpler than \code{memory\_manager.v}: since the on-chip SRAMs now hold
|
||||
the \emph{entire} vector (not just 2 double-buffered banks), there is
|
||||
no more bank-swap logic --- a slot simply reads sequentially once its
|
||||
own weight-fetch progress and the shared activation controller's own
|
||||
resident count both exceed the tile index it needs.
|
||||
|
||||
\begin{fnwarn}[Four real bugs found at full integration scale]
|
||||
All four are the same root cause: a counter that must represent the
|
||||
\emph{value} \code{MAX\_TILES} itself (e.g.\ a 16-tile job with
|
||||
\code{MAX\_TILES}=16) needs one more bit than an address field
|
||||
indexing \code{0..MAX\_TILES$-$1} --- easy to miss because every test
|
||||
smaller than \code{MAX\_TILES} passes regardless. Found only once a
|
||||
real \code{n\_tiles}=\code{MAX\_TILES} job (this project's own
|
||||
realistic 16-tile neurons) was actually run: a truncated 16-bit
|
||||
compare that read 16 as 0 (hanging weight fetch entirely); an
|
||||
undersized counter wrapping 15$\to$0 instead of reaching 16 (an
|
||||
infinite re-fetch loop); a logic error comparing the wrong two signals
|
||||
introduced while fixing the first bug (deadlocking exactly the last
|
||||
tile of every job); and a top-level connecting wire left at the
|
||||
narrower width after both endpoint modules were widened (silently
|
||||
truncating the real value 16 back to 0 one wire short of the fix).
|
||||
Each was isolated via real cycle-by-cycle signal tracing, the same
|
||||
discipline used throughout this project.
|
||||
\end{fnwarn}
|
||||
|
||||
7/7 bit-exact tests pass at \code{N\_SLOTS}=2, including the exact
|
||||
scenario STEP3 modeled (two slots dispatched together on the identical
|
||||
\code{x\_base}, different never-shared weights) and a new
|
||||
multi-tile test that specifically catches bug class 2 above.
|
||||
|
||||
\section{STEP9--10 --- real end-to-end benchmark vs.\ Current V2}
|
||||
\label{sec:nmscompare}
|
||||
\code{nms\_neural\_multiprocessor.v} mirrors
|
||||
\code{neural\_multiprocessor.v}'s own real hardware-facing scope
|
||||
exactly (same real \code{slot\_mem\_arbiter.v}, same real,
|
||||
unmodified V1 PSRAM chain). The \textbf{identical} D-Stress workload
|
||||
(256 neurons, 16~inputs$\times$8 tiles, one shared input vector) used
|
||||
for every Current-V2 number in this datasheet was run through it,
|
||||
bit-exact against the same golden model.
|
||||
|
||||
\begin{fnnote}[Real, direct comparison --- same workload, same toolchain]
|
||||
\begin{tabularx}{\textwidth}{L{3.6cm} C{2.8cm} C{2.8cm} C{1.6cm}}
|
||||
\toprule
|
||||
\rowh \thd{Metric (\code{N\_SLOTS}=2)} & \thd{Current V2} & \thd{NMS} & \thd{$\Delta$} \\
|
||||
\midrule
|
||||
Fmax (real P\&R) & 87.72~MHz & \textbf{93.10~MHz} & $+$6.1\% \\
|
||||
\rowa LUT4 & 4359 & \textbf{1948} & $-$55.3\% \\
|
||||
CCU2C & 366 & 266 & $-$27.3\% \\
|
||||
\rowa TRELLIS\_FF & 3924 & 3522 & $-$10.2\% \\
|
||||
DSP / BRAM & 16 / 0 & 16 / 0 & $=$ \\
|
||||
\rowa D-Stress cycles & 185428 & 185645 & $+$0.1\% \\
|
||||
D-Stress wall-clock & 2113.9~$\mu$s & \textbf{1994.0~$\mu$s} & \textbf{$+$6.0\% faster} \\
|
||||
\rowa Effective MAC/s & 15.50~M & \textbf{16.43~M} & $+$6.0\% \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\begin{tabularx}{\textwidth}{L{3.6cm} C{2.8cm} C{2.8cm} C{1.6cm}}
|
||||
\toprule
|
||||
\rowh \thd{Metric (\code{N\_SLOTS}=4)} & \thd{Current V2} & \thd{NMS} & \thd{$\Delta$} \\
|
||||
\midrule
|
||||
Fmax (real P\&R) & 65.01~MHz (\FAIL) & 56.62~MHz (\FAIL) & $-$12.9pp \\
|
||||
\rowa D-Stress cycles & 184795 & 184764 & $-$0.02\% \\
|
||||
D-Stress wall-clock & 2842.6~$\mu$s & \textbf{3263.2~$\mu$s} & $-$12.9\% (NMS slower) \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
\end{fnnote}
|
||||
|
||||
Cycles are essentially flat between \code{N\_SLOTS}=2 and 4 for
|
||||
\emph{both} systems (185645$\to$184764 for NMS, $-$0.5\%) ---
|
||||
confirming STEP1's own analytical floor: a single real PSRAM port caps
|
||||
\emph{aggregate} throughput regardless of on-chip organization; NMS's
|
||||
banking work makes the on-chip side efficient, it cannot and does not
|
||||
remove the external bandwidth ceiling.
|
||||
|
||||
\begin{fnwarn}[Real critical path found at N\_SLOTS=4/8 --- not hidden]
|
||||
Real nextpnr-ecp5 critical-path tracing at \code{N\_SLOTS}=4 shows the
|
||||
worst path running through
|
||||
\code{nms\_activation\_fill\_ctrl.v}'s own combinational
|
||||
priority-scan/address logic (6.26\,ns logic $+$ 11.40\,ns routing) ---
|
||||
the \emph{same class} of unpipelined, \code{N\_SLOTS}-scaling
|
||||
combinational cost \S\ref{sec:cache} already documented for
|
||||
\code{activation\_cache.v}, reintroduced here in the module that
|
||||
decides \emph{which} shared tag to chase (a genuinely different piece
|
||||
from the replicated SRAM itself, which has no such problem in
|
||||
isolation). \code{N\_SLOTS}$\le$2 is unaffected and real, measured
|
||||
faster; \code{N\_SLOTS}$\ge$4 is a real, open regression, not
|
||||
recommended, until this scan is pipelined (\S\ref{sec:nmsfuture}).
|
||||
\end{fnwarn}
|
||||
|
||||
\section{Real per-metric detail, N\_SLOTS=2 (D-Stress)}
|
||||
\begin{tabularx}{\textwidth}{L{4.4cm} C{2.4cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{Metric} & \thd{Value} & \thd{Note} \\
|
||||
\midrule
|
||||
Processor utilization & 1.10\% & tiles(4096)/(2$\times$185645 cycles) --- consistent with the project's own 1:170--1:220 compute-to-memory-wait finding \\
|
||||
\rowa Memory (PSRAM port) utilization & 90.4\% & 167830/185645 busy cycles \\
|
||||
Memory stall (per slot) & 93.6\% & 92.5\% waiting on weight $+$ 1.1\% waiting on activation, measured directly \\
|
||||
\rowa Compute stall & $\equiv$ memory stall & the Neural Processor stalls \emph{only} on a missing operand in this design --- no separate compute-only stall source exists \\
|
||||
Weight-buffer hit rate & 0\% & confirmed empirically (2048 real fetches $=$ 2048 tiles/slot, zero reuse) --- weights are never shared, by design \\
|
||||
\rowa Activation-buffer hit rate & 99.61\% & only 16 real PSRAM fetches for 4096 tile-consumptions (256 neurons share one vector) \\
|
||||
Prefetch effectiveness & low ($\approx$0\%) & a real, honest gap: this revision fetches weight ``as fast as possible'' but with no bounded lookahead buffer (\code{PREFETCH\_DISTANCE}), so weight-fetch latency dominates stall almost entirely --- see \S\ref{sec:nmsfuture} \\
|
||||
\rowa Parallel efficiency (N=2 vs.\ N=1) & 48.1\% & real speedup $=$ cycles(1)/cycles(2) $=$ 178432/185645 $=$ 0.961$\times$ (N=2 needs \emph{more} cycles than N=1) --- the shared PSRAM port is still the bottleneck \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Recommendation}
|
||||
Adopt NMS at \code{N\_SLOTS}$\le$2 as a real, measured upgrade over
|
||||
Current V2 at its own already-recommended default: faster, smaller,
|
||||
higher Fmax margin, bit-exact, same workload. Do \textbf{not} adopt
|
||||
NMS at \code{N\_SLOTS}=4/8 yet --- Current V2 is really faster there
|
||||
until the fill-controller pipelining fix below is implemented and
|
||||
re-measured. Both systems remain in the repository; selecting between
|
||||
them is a real, configuration-dependent decision, not a blanket
|
||||
replacement.
|
||||
|
||||
\section{Open work (real, not hidden)}
|
||||
\label{sec:nmsfuture}
|
||||
\begin{itemize}
|
||||
\item \textbf{Pipeline \code{nms\_activation\_fill\_ctrl.v}'s own
|
||||
priority-scan/address logic} --- the concrete, identified fix
|
||||
for the \code{N\_SLOTS}=4/8 Fmax regression above.
|
||||
\item \textbf{Implement real bounded-lookahead weight prefetch}
|
||||
(\code{PREFETCH\_DISTANCE}, per STEP1's own findings) --- the
|
||||
current single-shot ``fetch as fast as possible'' weight path is
|
||||
why prefetch effectiveness measures low; STEP1's own data shows
|
||||
a real, achievable fix (depth scaled to real round-trip latency).
|
||||
\item Re-measure \code{N\_SLOTS}=1 and 8 D-Stress cycle counts for
|
||||
full parity with Current V2's own 4-point table (only 2 and 4
|
||||
measured this round, time-bounded).
|
||||
\item A fixed, smaller-\code{N\_BANKS} Activation SRAM variant was
|
||||
never revisited after full replication was selected --- BRAM
|
||||
cost was cheap enough at this project's real workload sizes that
|
||||
it was never worth reconsidering.
|
||||
\end{itemize}
|
||||
@@ -1,65 +0,0 @@
|
||||
\chapter{Module and file map}
|
||||
\label{ap:modules}
|
||||
|
||||
\section{V2 RTL (\texttt{hardware/v2/rtl/})}
|
||||
\begin{tabularx}{\textwidth}{L{4.2cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{File} & \thd{Role} \\
|
||||
\midrule
|
||||
\code{neural\_processor.v} & 8-stage INT8 pipeline (M1); bit-exact vs.\ V1. \\
|
||||
\rowa \code{neural\_processor\_array.v} & N-processor array used for the M2 concurrency sweep. \\
|
||||
\code{activation\_buffer.v}, \code{weight\_buffer.v}, \code{result\_buffer.v} & M3 BRAM-backed buffers; superseded in the real datapath by \code{activation\_cache.v}. \\
|
||||
\rowa \code{prefetch\_engine.v} & Weight-only, word-level burst fetch engine (M4, rewritten DEC-0015/DEC-0016). \\
|
||||
\code{memory\_manager.v} & Double-buffered per-slot tile manager; coordinates the Activation Cache (X) and \code{prefetch\_engine.v} (W). \\
|
||||
\rowa \code{neural\_director.v} & First-free job dispatch (M5). \\
|
||||
\code{dependency\_manager.v} & Node table, dependency counting, wake-up (M6). \\
|
||||
\rowa \code{dataflow\_core.v} & Full M1--M6 integration $+$ Activation Cache (M7, extended DEC-0016). \\
|
||||
\code{slot\_mem\_arbiter.v} & Generic N-port arbiter to the real PSRAM chain (M8). \\
|
||||
\rowa \code{activation\_cache.v} & Shared, single-tag activation cache (post-M10, DEC-0016). \\
|
||||
\code{neural\_multiprocessor.v} & Real hardware-facing top level (M8). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{NMS RTL (\texttt{hardware/v2/nms/rtl/}, ch.~\ref{ch:nms})}
|
||||
\begin{tabularx}{\textwidth}{L{4.6cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{File} & \thd{Role} \\
|
||||
\midrule
|
||||
\code{nms\_activation\_replicated.v} & Selected Activation SRAM: \code{N\_SLOTS} private full-vector copies, broadcast-write fill (DEC-0019). \\
|
||||
\rowa \code{nms\_activation\_fill\_ctrl.v} & Shared dedup/fetch controller backing it -- the real \code{N\_SLOTS}=4/8 Fmax bottleneck identified in ch.~\ref{ch:nms}. \\
|
||||
\code{nms\_weight\_packed.v} & Selected Weight SRAM: per-MAC-lane packed private copies (DEC-0020). \\
|
||||
\rowa \code{nms\_memory\_manager.v} & Per-slot job FSM, drop-in replacement for \code{memory\_manager.v}'s own external interface. \\
|
||||
\code{nms\_dataflow\_core.v} & Full NMS integration, mirrors \code{dataflow\_core.v}'s own scope (STEP8). \\
|
||||
\rowa \code{nms\_neural\_multiprocessor.v} & Real hardware-facing top level, mirrors \code{neural\_multiprocessor.v}'s own scope (STEP9). \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
Also reused verbatim, unmodified, in the NMS datapath:
|
||||
\code{neural\_processor.v}, \code{prefetch\_engine.v} (as a generic
|
||||
P\_IN-byte-tile fetch engine, not weight-specific despite its name),
|
||||
\code{dependency\_manager.v}, \code{neural\_director.v},
|
||||
\code{slot\_mem\_arbiter.v}.
|
||||
|
||||
\section{Reused, unmodified V1 (\texttt{hardware/v1/rtl/})}
|
||||
\begin{tabularx}{\textwidth}{L{4.2cm} Y}
|
||||
\toprule
|
||||
\rowh \thd{File} & \thd{Role in V2} \\
|
||||
\midrule
|
||||
\code{memory\_interface.v} & Word-level (16-bit) PSRAM backend port, now the direct target of both \code{prefetch\_engine.v} and \code{activation\_cache.v}. \\
|
||||
\rowa \code{psram\_controller.v} & Real PSRAM controller, page-mode support exploited more effectively by the word-burst rewrite. \\
|
||||
\code{int8\_memory\_access.v} & \textbf{No longer instantiated} in V2's datapath post-DEC-0015 --- file itself untouched. \\
|
||||
\bottomrule
|
||||
\end{tabularx}
|
||||
|
||||
\section{Simulation (\texttt{hardware/v2/sim/})}
|
||||
\code{tb\_neural\_processor.v}, \code{tb\_dataflow\_core.v},
|
||||
\code{tb\_memory\_manager.v}, \code{tb\_neural\_director.v},
|
||||
\code{tb\_dependency\_manager.v}, \code{tb\_neural\_multiprocessor.v},
|
||||
\code{tb\_benchmark\_suite.v} (the final campaign's own testbench,
|
||||
parametric in \code{N\_SLOTS\_CFG} via Verilator's own \code{-G} override).
|
||||
|
||||
\section{Documentation and logs (\texttt{hardware/v2/docs/}, \texttt{hardware/v2/logs/})}
|
||||
\code{ROADMAP.md}; \code{docs/benchmarks/final-benchmark.md} (the
|
||||
21-section pre-optimization campaign report); append-only logs
|
||||
(\code{development}, \code{simulation}, \code{synthesis}, \code{timing},
|
||||
\code{benchmark}, \code{decisions}, \code{experiments}, \code{errors})
|
||||
--- the primary source of every number in this datasheet.
|
||||
Binary file not shown.
@@ -1,184 +0,0 @@
|
||||
% ======================================================================
|
||||
% FPGA-Neural Datasheet -- preamble / stile
|
||||
% ======================================================================
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[utf8]{inputenc}
|
||||
\usepackage[english]{babel}
|
||||
\usepackage{helvet}
|
||||
\renewcommand{\familydefault}{\sfdefault}
|
||||
\usepackage{courier}
|
||||
\usepackage{microtype}
|
||||
|
||||
\usepackage[a4paper,top=2.4cm,bottom=2.3cm,left=2.2cm,right=2.2cm,headheight=15pt]{geometry}
|
||||
\usepackage[table]{xcolor}
|
||||
\usepackage{graphicx}
|
||||
\usepackage{booktabs}
|
||||
\usepackage{tabularx}
|
||||
\usepackage{longtable}
|
||||
\usepackage{array}
|
||||
\usepackage{ltablex}
|
||||
\keepXColumns
|
||||
\usepackage{multirow}
|
||||
\usepackage{multicol}
|
||||
\usepackage{enumitem}
|
||||
\usepackage{amsmath}
|
||||
\usepackage{amssymb}
|
||||
\usepackage{ragged2e}
|
||||
|
||||
% ---------- Palette ----------------------------------------------------
|
||||
\definecolor{fnDark}{HTML}{0B2E4F} % blu profondo (primario)
|
||||
\definecolor{fnBlue}{HTML}{15629B} % blu medio
|
||||
\definecolor{fnTeal}{HTML}{0E8F8A} % accento teal
|
||||
\definecolor{fnAmber}{HTML}{C9761B} % accento ambra
|
||||
\definecolor{fnRed}{HTML}{B22C34} % fail / warning
|
||||
\definecolor{fnGreen}{HTML}{2E7D32} % pass / ok
|
||||
\definecolor{fnGrey}{HTML}{5B6B78}
|
||||
\definecolor{fnLight}{HTML}{EEF3F7} % sfondo chiaro
|
||||
\definecolor{fnLight2}{HTML}{E2ECF3}
|
||||
\definecolor{fnRule}{HTML}{9FB4C4}
|
||||
\definecolor{codebg}{HTML}{F5F7F9}
|
||||
\definecolor{codekw}{HTML}{15629B}
|
||||
\definecolor{codecom}{HTML}{5B6B78}
|
||||
\definecolor{codestr}{HTML}{0E8F8A}
|
||||
|
||||
% ---------- Titoli -----------------------------------------------------
|
||||
\usepackage{titlesec}
|
||||
\titleformat{\chapter}[display]
|
||||
{\normalfont\bfseries\color{fnDark}}
|
||||
{\filright\Large\color{fnTeal}CHAPTER \thechapter}
|
||||
{6pt}
|
||||
{\Huge\filright}
|
||||
[\vspace{2pt}{\color{fnRule}\titlerule[1.3pt]}]
|
||||
\titlespacing*{\chapter}{0pt}{6pt}{18pt}
|
||||
|
||||
\titleformat{\section}
|
||||
{\normalfont\large\bfseries\color{fnDark}}{\thesection}{0.6em}{}
|
||||
\titleformat{\subsection}
|
||||
{\normalfont\bfseries\color{fnBlue}}{\thesubsection}{0.6em}{}
|
||||
\titleformat{\subsubsection}
|
||||
{\normalfont\bfseries\color{fnGrey}}{\thesubsubsection}{0.6em}{}
|
||||
\titlespacing*{\section}{0pt}{12pt}{4pt}
|
||||
|
||||
% ---------- Header / footer -------------------------------------------
|
||||
\usepackage{fancyhdr}
|
||||
\pagestyle{fancy}
|
||||
\fancyhf{}
|
||||
\renewcommand{\headrulewidth}{0.6pt}
|
||||
\renewcommand{\footrulewidth}{0.4pt}
|
||||
\renewcommand{\headrule}{\hbox to\headwidth{\color{fnRule}\leaders\hrule height \headrulewidth\hfill}}
|
||||
\renewcommand{\footrule}{\hbox to\headwidth{\color{fnRule}\leaders\hrule height \footrulewidth\hfill}}
|
||||
\renewcommand{\chaptermark}[1]{\markboth{#1}{}}
|
||||
\fancyhead[L]{\small\color{fnDark}\textbf{FPGA-Neural}}
|
||||
\fancyhead[R]{\footnotesize\color{fnGrey}\nouppercase{\leftmark}}
|
||||
\fancyfoot[L]{\small\color{fnGrey}Datasheet~\textbf{Rev.\,\datasheetrev}}
|
||||
\fancyfoot[C]{\small\color{fnGrey}manvalan/FPGA-Neural}
|
||||
\fancyfoot[R]{\small\color{fnGrey}\thepage}
|
||||
\fancypagestyle{plain}{\fancyhf{}%
|
||||
\fancyfoot[L]{\small\color{fnGrey}Datasheet~\textbf{Rev.\,\datasheetrev}}%
|
||||
\fancyfoot[C]{\small\color{fnGrey}manvalan/FPGA-Neural}%
|
||||
\fancyfoot[R]{\small\color{fnGrey}\thepage}%
|
||||
\renewcommand{\headrulewidth}{0pt}}
|
||||
|
||||
% ---------- tcolorbox --------------------------------------------------
|
||||
\usepackage[most]{tcolorbox}
|
||||
\tcbuselibrary{skins,breakable}
|
||||
|
||||
% Box "nota"
|
||||
\newtcolorbox{fnnote}[1][Note]{
|
||||
enhanced, breakable, colback=fnLight, colframe=fnTeal,
|
||||
boxrule=0.4pt, left=8pt, right=8pt, top=5pt, bottom=5pt,
|
||||
fonttitle=\bfseries\color{white}, coltitle=white,
|
||||
attach boxed title to top left={xshift=6pt,yshift=-3pt},
|
||||
boxed title style={colback=fnTeal,boxrule=0pt,arc=1pt}, title={#1}}
|
||||
|
||||
% Box "attenzione"
|
||||
\newtcolorbox{fnwarn}[1][Warning]{
|
||||
enhanced, breakable, colback=fnLight, colframe=fnAmber,
|
||||
boxrule=0.4pt, left=8pt, right=8pt, top=5pt, bottom=5pt,
|
||||
fonttitle=\bfseries\color{white}, coltitle=white,
|
||||
attach boxed title to top left={xshift=6pt,yshift=-3pt},
|
||||
boxed title style={colback=fnAmber,boxrule=0pt,arc=1pt}, title={#1}}
|
||||
|
||||
% Box "registro/parametro"
|
||||
\newtcolorbox{fnspec}[1][Specification]{
|
||||
enhanced, breakable, colback=white, colframe=fnBlue,
|
||||
boxrule=0.7pt, left=8pt, right=8pt, top=5pt, bottom=5pt, arc=1.5pt,
|
||||
fonttitle=\bfseries\color{white}, coltitle=white,
|
||||
attach boxed title to top left={xshift=6pt,yshift=-3pt},
|
||||
boxed title style={colback=fnBlue,boxrule=0pt,arc=1pt}, title={#1}}
|
||||
|
||||
% ---------- listings (Verilog) ----------------------------------------
|
||||
\usepackage{listings}
|
||||
\lstdefinestyle{verilog}{
|
||||
language=Verilog,
|
||||
backgroundcolor=\color{codebg},
|
||||
basicstyle=\ttfamily\scriptsize,
|
||||
keywordstyle=\color{codekw}\bfseries,
|
||||
commentstyle=\color{codecom}\itshape,
|
||||
stringstyle=\color{codestr},
|
||||
numbers=left, numberstyle=\tiny\color{fnGrey}, numbersep=7pt,
|
||||
showstringspaces=false, breaklines=true, frame=leftline,
|
||||
framerule=1.2pt, rulecolor=\color{fnTeal},
|
||||
xleftmargin=12pt, framexleftmargin=10pt, tabsize=2,
|
||||
morekeywords={logic,always_ff,always_comb,localparam,signed,genvar,generate,endgenerate}
|
||||
}
|
||||
\lstset{style=verilog}
|
||||
|
||||
% ---------- Tabelle ----------------------------------------------------
|
||||
\newcolumntype{L}[1]{>{\raggedright\arraybackslash}p{#1}}
|
||||
\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}}
|
||||
\newcolumntype{R}[1]{>{\raggedleft\arraybackslash}p{#1}}
|
||||
\newcolumntype{Y}{>{\raggedright\arraybackslash}X}
|
||||
\renewcommand{\arraystretch}{1.25}
|
||||
\arrayrulecolor{fnRule}
|
||||
|
||||
% intestazione tabella colorata
|
||||
\newcommand{\thd}[1]{\textbf{\color{white}#1}}
|
||||
\newcommand{\rowh}{\rowcolor{fnDark}}
|
||||
\newcommand{\rowa}{\rowcolor{fnLight}}
|
||||
|
||||
% ---------- Caption ----------------------------------------------------
|
||||
\usepackage{caption}
|
||||
\captionsetup{font=small,labelfont={bf,color=fnTeal},labelsep=period}
|
||||
|
||||
% ---------- TikZ / pgfplots -------------------------------------------
|
||||
\usepackage{tikz}
|
||||
\usetikzlibrary{arrows.meta,positioning,calc,shapes.geometric,shapes.misc,
|
||||
fit,backgrounds,chains,decorations.pathreplacing,decorations.markings,
|
||||
matrix,shadows.blur}
|
||||
\usepackage{pgfplots}
|
||||
\pgfplotsset{compat=1.17}
|
||||
\usepackage{tikz-timing}
|
||||
|
||||
% stili di blocco riusabili
|
||||
\tikzset{
|
||||
fnblock/.style={draw=fnBlue,fill=fnLight,rounded corners=2pt,
|
||||
minimum height=9mm,minimum width=24mm,align=center,font=\small,
|
||||
inner sep=4pt,line width=0.7pt},
|
||||
fnblockT/.style={fnblock,draw=fnTeal,fill=fnLight2},
|
||||
fnblockD/.style={fnblock,draw=fnDark,fill=fnDark,text=white},
|
||||
fnblockA/.style={fnblock,draw=fnAmber,fill=white},
|
||||
fnreg/.style={draw=fnGrey,fill=white,minimum height=8mm,align=center,
|
||||
font=\footnotesize,inner sep=3pt},
|
||||
fnstate/.style={draw=fnBlue,fill=fnLight,circle,minimum size=13mm,
|
||||
align=center,font=\scriptsize,line width=0.7pt},
|
||||
fnarrow/.style={-{Stealth[length=2.6mm]},line width=0.8pt,draw=fnDark},
|
||||
fnarrowT/.style={-{Stealth[length=2.6mm]},line width=0.8pt,draw=fnTeal},
|
||||
fnbus/.style={-{Stealth[length=3mm]},line width=1.6pt,draw=fnBlue},
|
||||
fnlbl/.style={font=\scriptsize\itshape,fill=white,inner sep=1pt,text=fnGrey}
|
||||
}
|
||||
|
||||
% ---------- varie ------------------------------------------------------
|
||||
\newcommand{\reg}[1]{\texttt{\textbf{#1}}}
|
||||
\newcommand{\sig}[1]{\texttt{#1}}
|
||||
\newcommand{\op}[1]{\texttt{\color{fnBlue}#1}}
|
||||
\newcommand{\PASS}{\textcolor{fnGreen}{\textbf{PASS}}}
|
||||
\newcommand{\FAIL}{\textcolor{fnRed}{\textbf{FAIL}}}
|
||||
\newcommand{\OK}{\textcolor{fnGreen}{\textbf{OK}}}
|
||||
\newcommand{\code}[1]{\texttt{#1}}
|
||||
|
||||
\usepackage{enumitem}
|
||||
\setlist{noitemsep,topsep=2pt,leftmargin=1.4em}
|
||||
|
||||
\usepackage[hidelinks,colorlinks=true,linkcolor=fnBlue,urlcolor=fnTeal,
|
||||
citecolor=fnBlue]{hyperref}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user