feat: two-flash programming architecture, FPGA_DATA_READY, real JTAG/config pinout

Establishes the real ESP32<->ECP5 programming architecture: flash #1
(neural-network data, existing V1 subsystem, ball reserved not yet
wired into V2) stays separate from flash #2 (boot bitstream, MSPI
auto-boot, CFG[2:0]=[0,1,0]); ESP32 talks JTAG only (bit-banged, no
hardware JTAG-master peripheral on S3/C6), updating flash #2 through
the ECP5's own internal sysCONFIG-to-SPI bridge, never driving the
flash pins directly -- zero bus contention, confirmed against the real
Lattice hardware checklist and sysCONFIG user guide.

Adds real, verified ball assignments (official Lattice CABGA381 CSV +
Project Trellis iodb.json) for JTAG, PROGRAMN/INITN/DONE, CFG[2:0],
and the MSPI dedicated pins -- all written to docs/pinouts.md.

Implements FPGA_DATA_READY as real RTL: a system-idle detector
(dependency_manager's any_pending OR neural_director's !queue_empty OR
any active slot), sticky on the busy->idle edge, self-clearing on new
work -- not a per-neuron completion pulse, which was confirmed too
fine-grained. Bit-exact regression re-verified at N_SLOTS=4 and 8
(zero cycle-count change), new explicit data_ready assertion check
added to the D-Stress testbench (PASS both configs), and a fresh
Yosys+nextpnr-ecp5 placement check (0 errors, data_ready placed at G3).

Also fixes a real, independently-found bug while editing an adjacent
file: nms_neural_multiprocessor_sdram_unified.v's own sdram_a port was
still [11:0] (12 bits), stale from before the 64MB/13-bit memory
upgrade. Not exercised by the real board-level top (which wires SDRAM
directly, bypassing this wrapper) but WAS silently truncating A12 in
every D-Stress simulation this session, including today's earlier
ERR-0029 verification runs. Assessed impact: all D-Stress test
addresses used this session decode to rows under 4096 (bit 12 never
actually needed), so no false-positive PASS is believed to have
resulted -- but the full 64MB space was never actually exercised
through this wrapper. Fixed; re-verified bit-exact with identical
cycle counts.

See decisions.log DEC-0041 for full detail.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-07 12:46:00 +02:00
co-authored by Claude Sonnet 5
parent 81a9619214
commit 7d6311bce3
11 changed files with 390 additions and 9 deletions
+11 -3
View File
@@ -97,8 +97,16 @@ LOCATE COMP "sdram_dq[15]" SITE "N5"; IOBUF PORT "sdram_dq[15]" IO_TYPE=LVCMOS33
LOCATE COMP "sdram_dqm[0]" SITE "P5"; IOBUF PORT "sdram_dqm[0]" IO_TYPE=LVCMOS33;
LOCATE COMP "sdram_dqm[1]" SITE "N3"; IOBUF PORT "sdram_dqm[1]" IO_TYPE=LVCMOS33;
// All 16 top-level ports of fpga_neural_v2_top are now real-ball
// data_ready (FPGA_DATA_READY, to ESP32): free bank-7 ball, dual-
// function PCLKT7_1 not needed as a PLL input here, reused as plain
// GPIO output -- real ball, verified free (not in any other LOCATE
// COMP in this file) via the Trellis iodb.json + official CSV.
LOCATE COMP "data_ready" SITE "G3"; IOBUF PORT "data_ready" IO_TYPE=LVCMOS33;
// All 17 top-level ports of fpga_neural_v2_top are now real-ball
// assigned: osc_clk, ext_rst_n, spi_sclk, spi_mosi, spi_miso,
// spi_cs_n, sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n,
// sdram_we_n, sdram_ba[1:0], sdram_a[11:0], sdram_dq[15:0],
// sdram_dqm[1:0], pll_locked -- no placeholders remain in THIS LPF.
// sdram_we_n, sdram_ba[1:0], sdram_a[12:0], sdram_dq[15:0],
// sdram_dqm[1:0], data_ready, pll_locked -- no placeholders remain in
// THIS LPF. (sdram_a corrected from a stale [11:0] comment -- the
// real port has been [12:0] since the 64MB memory upgrade.)
@@ -117,3 +117,131 @@ N\_SLOTS=4/8 @ 80\,MHz & 0/8 & --- & NO-GO, genuine \code{ecppll}-regenerated PL
Real, measured after the ERR-0029 weight-cache hit-index optimization
(serial priority scan $\to$ flat one-hot compare); see
\code{hardware/v2/logs/errors.log} and \code{decisions.log} DEC-0040.
\section{Power supply design (2026-09-07) --- verified against the real
Lattice hardware checklist}
\label{sec:power-addendum}
\begin{fnwarn}[Supersedes the generic \S3 stub above]
The ``Power supply, oscillator, configuration'' section earlier in
this chapter only said ``unchanged from V1'' without real design data.
This section replaces that stub with the actual rail topology, sized
against the real, primary-source Lattice and TI documents below --- not
estimated.
\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}[Open item carried from \S3 above]
The 16\,MHz reference oscillator's exact manufacturer part number is
not yet specified in this document (only ``16\,MHz'' as a frequency
requirement) --- flagged, not invented, pending the schematic capture
the user is preparing separately.
\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}
+35
View File
@@ -62,6 +62,41 @@ U3 CCLK 8
Attenzione però: CCLK non è automaticamente il clock di sistema della nostra rete neurale. È il clock associato alla configurazione; il clock operativo della FPGA va identificato separatamente nel percorso dell'oscillatore/PLL.
CFG[2:0] (selezione modalità di boot)
Dal CSV, tutti banco 8:
Ball Pin Note
U4 CFG_0 CFGMDN0
T4 CFG_1 CFGMDN1
R4 CFG_2 CFGMDN2
Per boot automatico da flash #2 (MSPI): CFG[2:0]=[0,1,0] (letto CFG2,CFG1,CFG0) → CFG_2 a GND, CFG_1 a pull-up 110kΩ verso VCCIO8, CFG_0 a GND (dato reale, Lattice FPGA-TN-02039-2.3 §6.1.1, Tabella 6.3). Pin resi modificabili via jumper/resistori 0Ω, non hardwired fissi.
Pin dual-function MSPI verso flash #2 (boot)
Dal CSV, ball dual-function del banco 8, NON pin JTAG/dedicati separati — sono ball PIO ordinari con funzione secondaria sysCONFIG:
Ball Pin CSV Funzione MSPI
R2 PB15A: HOLDN/DI/BUSY/CSSPIN/CEN CSSPIN (chip select verso flash #2), + 4.7kΩ pull-up a VCCIO8
W2 PB11B: D0/MOSI/IO0 D0/MOSI verso flash #2
V2 PB11A: D1/MISO/IO1 D1/MISO verso flash #2
U3 CCLK (vedi sopra) MCLK verso flash #2, pull-up debole interna
Questi 4 ball (insieme a PROGRAMN/INITN/DONE sopra) collegano l'FPGA esclusivamente alla flash #2 (boot) — MAI alla flash #1 (dati rete neurale), che resta su un bus GPIO ordinario separato (sotto).
Flash #1 (dati rete neurale) — ball riservati, RTL non ancora presente in V2
**Importante**: i ball reali usati da V1 per questo stesso bus (`flash_sclk`=E3, `flash_mosi`=D3, `flash_miso`=D5, `flash_cs_n`=E4) sono **già occupati in V2** dal bus SDRAM (E3=sdram_a[4], D3=sdram_a[1], D5=sdram_a[0], E4=sdram_ba[0]) — non riutilizzabili, V2 ha una geometria pin diversa da V1. Individuati 4 ball liberi alternativi, stesso banco 7 (stessa tensione 3.3V del resto del bus SDRAM):
Ball Funzione dual (libera, riusabile come GPIO ordinario)
B2 VREF1_7 (non serve, nessuno standard I/O riferito a VREF in uso)
E2 PCLKC7_0 (ingresso PLL non usato, riusabile come GPIO)
F2 PCLKT7_0 (ingresso PLL non usato, riusabile come GPIO)
F3 PCLKC7_1 (ingresso PLL non usato, riusabile come GPIO)
Assegnazione proposta: `flash_sclk`=B2, `flash_mosi`=E2, `flash_miso`=F2, `flash_cs_n`=F3. **Non ancora nel LPF**: `flash_copy_engine.v`/`flash_slot_manager.v` (RTL V1 reale) non sono stati portati nel top-level di V2 — aggiungere un `LOCATE COMP` per questi segnali ora romperebbe la sintesi, dato che non esiste ancora una porta corrispondente in `fpga_neural_v2_top.v`. Riservati qui solo come ball, in attesa dell'integrazione RTL.
Decoupling
Qui preferisco essere molto preciso: non voglio inventare una quantità di condensatori per “ogni VCC”.
+127
View File
@@ -2275,3 +2275,130 @@ PASS, real positive margin on every seed). N_SLOTS=8 @ 64MHz OPEN,
improved but not closed (5/8 PASS). 80MHz NO-GO at either N_SLOTS,
reconfirmed on the fixed RTL. File changed: hardware/v2/nms/rtl/
sdram_unified_backend.v.
DEC-0041 -- Two-flash programming architecture (boot vs. neural-network
data), real JTAG/config pin assignment, and FPGA_DATA_READY
DATE: 2026-09-07
CONTEXT: user-driven design session covering how an external ESP32
controller programs and interacts with the ECP5. Converged, after
several false starts (see chat history for the discarded single-
shared-flash / SSPI-every-boot alternatives), on a clean architecture:
ARCHITECTURE:
- Flash #1 (neural-network weights/graph data): unchanged from V1's
own real subsystem (`flash_copy_engine.v`/`flash_slot_manager.v`,
not yet ported into V2's top-level RTL -- a real, separate, still-
open task, not attempted here). Uses 4 ordinary GPIO balls. V1's own
real balls (flash_sclk=E3, flash_mosi=D3, flash_miso=D5,
flash_cs_n=E4) are NOT reusable in V2 -- confirmed conflict, all 4
are already occupied by V2's own SDRAM bus (E3=sdram_a[4],
D3=sdram_a[1], D5=sdram_a[0], E4=sdram_ba[0]). New free balls found
and reserved (bank 7, same 3.3V domain): flash_sclk=B2,
flash_mosi=E2, flash_miso=F2, flash_cs_n=F3 -- NOT yet in the LPF,
since no corresponding RTL port exists yet in `fpga_neural_v2_top.v`
(adding a LOCATE COMP for a non-existent port would break synthesis).
- Flash #2 (boot bitstream): connects ONLY to the ECP5's dedicated
sysCONFIG pins, MSPI mode, CFG[2:0]=[0,1,0] (real ball assignment
and correct per-bit resistor population below). Auto-boots on every
power-up, no ESP32 involvement in normal operation.
- ESP32 connects to the ECP5 via JTAG only (bit-banged over ordinary
GPIO -- confirmed neither ESP32-S3 nor ESP32-C6 has a hardware JTAG
MASTER peripheral; their native "USB Serial/JTAG Controller" is for
debugging the ESP32 itself, wrong direction for this use). ESP32
updates flash #2's content 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 SPI pins
directly, zero bus contention.
- Confirmed real and safe (Lattice FPGA-TN-02039-2.3 sysCONFIG User
Guide, §6.1.2): after User Mode is reached, the MSPI dedicated pins
tristate with a weak pull-up, so a separate ordinary-GPIO consumer
on the same physical net (flash #1's own bus, if it were ever
shared -- not the case here, since flash #1/#2 are physically
separate chips) would not contend. Documented for completeness; not
load-bearing for this specific two-chip architecture.
REAL BALL ASSIGNMENTS (CABGA381, from the official Lattice CSV
FPGA-SC-02034-3-0-ECP5U-45-Pinout.csv rev 3.0 and cross-checked
against Project Trellis's iodb.json -- both already used elsewhere in
this project's own pinout work):
JTAG (bank 40/TAP, to ESP32): TCK=T5, TMS=U5, TDI=R5, TDO=V4
Dedicated config (bank 8, to ESP32): PROGRAMN=W3, INITN=V3, DONE=Y3
CFG[2:0] (bank 8, board jumpers/0R, NOT to ESP32): CFG_0=U4,
CFG_1=T4, CFG_2=R4 -- for MSPI, CFG[2:0]=[0,1,0] read MSB-first
means CFG_2=GND, CFG_1=pull-up 1-10k to VCCIO8, CFG_0=GND (an
earlier draft of this entry in docs/pinouts.md had CFG_0/CFG_1
swapped -- corrected there and here).
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.
All written into `hardware/v2/docs/pinouts.md`.
FPGA_DATA_READY (real RTL, not just a pin reservation): the user
wants a signal meaning "the whole registered graph finished, the
answer is ready" -- NOT per-neuron `job_done` (already existed,
wrong granularity) and NOT achievable without new logic (confirmed:
zero existing "graph complete" signal anywhere in the RTL, and the
host protocol never tells the FPGA how many nodes to expect, so a
"last node" flag approach -- first proposed, then rejected by the
user in favor of a hardware idle-detection approach -- was not used).
Implemented as a system-idle detector, built entirely from real,
already-existing internal signals, no protocol change:
sys_busy = (|job_active) || !neural_director's queue_empty ||
dependency_manager's any_pending (new: OR-reduce over
node_state[N_NODES] == ST_WAITING or ST_READY --
ST_DISPATCHED nodes are tracked downstream, not here)
data_ready: sticky register, set on the sys_busy 1->0 falling edge,
cleared the instant sys_busy goes high again (new work started) --
self-clearing, no host ACK needed.
ASSUMPTION, disclosed not hidden: correct only if the host finishes
registering every node of a graph before the first one completes.
Realistic for this architecture (SPI registration: microseconds;
per-neuron compute: ~195 real measured cycles) but not proven for
every conceivable host timing pattern.
Files changed: `hardware/v2/rtl/dependency_manager.v` (new
`any_pending` output), `hardware/v2/rtl/neural_director.v` (new
`queue_empty` output, wired from the existing internal `q_empty`),
`hardware/v2/nms/rtl/nms_dataflow_core_sdram.v` (aggregation logic,
new `data_ready` output), `hardware/v2/nms/rtl/
nms_neural_multiprocessor_sdram_unified.v` and `hardware/v2/nms/rtl/
fpga_neural_v2_top.v` (port passthrough). New real ball: `data_ready`
= G3 (bank 7, free, confirmed via Trellis iodb.json).
REAL BUG FOUND AND FIXED (unrelated to the above, found while editing
an adjacent file): `nms_neural_multiprocessor_sdram_unified.v`'s own
`sdram_a` port was still declared `[11:0]` (12 bits) -- a stale width
from before the 64MB memory upgrade (which needs 13 bits, A0-A12).
This wrapper is NOT instantiated by the real board-level top
(`fpga_neural_v2_top.v` instantiates `nms_dataflow_core_sdram` and
`sdram_unified_backend` directly, confirmed by inspection -- see that
file's own header comment), so real hardware was never affected. It
IS instantiated by `tb_nms_dstress_sdram_unified.v` (this session's
own primary bit-exact regression oracle for the whole 64MB/ERR-0029
verification work), whose own `sdram_a` wire was ALSO stuck at 12
bits -- meaning A12 was silently truncated in every D-Stress
simulation run this session, including the ERR-0029 verification
runs reported earlier today. Real, honest assessment of the impact:
all D-Stress test addresses used this session (0x010000, 0x200000,
0x300000) decode to row addresses well under 4096 (row bit 12 never
actually needed), so the truncation is assessed as NOT having
produced any false-positive PASS result -- but the full 64MB address
space was never actually exercised through this specific wrapper
path. Fixed both widths to `[12:0]`; re-ran the full D-Stress
regression (N_SLOTS=4: 49927 cycles, N_SLOTS=8: 49909 cycles, both
256/256 bit-exact vs golden, IDENTICAL cycle counts to before the
fix, confirming zero behavioral change for the address range actually
exercised) plus the real board-level smoke test
(`tb_fpga_neural_v2_top_smoke.v`, 11/11 PASS) and a fresh Yosys+
nextpnr-ecp5 placement check (0 errors, `data_ready` placed cleanly
at G3). Files changed: `hardware/v2/nms/rtl/
nms_neural_multiprocessor_sdram_unified.v`, `hardware/v2/nms/sim/
tb_nms_dstress_sdram_unified.v`.
STATUS: two-flash architecture and FPGA_DATA_READY CLOSED and
verified (bit-exact + real placement). Flash #1's RTL port
(`flash_copy_engine.v` integration into V2's top-level) remains a
real, separate, OPEN task -- ball positions reserved, not wired.
+6
View File
@@ -66,6 +66,11 @@ module fpga_neural_v2_top #(
inout wire [15:0] sdram_dq,
output wire [1:0] sdram_dqm,
// FPGA_DATA_READY: high once the whole registered graph has
// finished (system-idle sticky flag, self-clearing on new work) --
// see nms_dataflow_core_sdram.v for the full design comment.
output wire data_ready,
output wire pll_locked
);
@@ -141,6 +146,7 @@ module fpga_neural_v2_top #(
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.data_ready(data_ready),
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
+37 -2
View File
@@ -79,6 +79,10 @@ module nms_dataflow_core_sdram #(
input wire [15:0] reg_n_tiles,
input wire [ADDR_WIDTH-1:0] reg_result_addr,
// FPGA_DATA_READY: see the assignment site (below u_director) for
// the full design comment.
output wire data_ready,
output wire [N_SLOTS:0] slot_mem_req,
output wire [N_SLOTS:0] slot_mem_wr,
output wire [ADDR_WIDTH*(N_SLOTS+1)-1:0] slot_mem_addr,
@@ -110,6 +114,7 @@ module nms_dataflow_core_sdram #(
wire dm_producer_done_valid;
wire [NODE_IDW-1:0] dm_producer_done_node_id;
wire dm_any_pending;
dependency_manager #(
.N_NODES(N_NODES), .MAX_DEPS(MAX_DEPS), .ADDR_WIDTH(ADDR_WIDTH)
@@ -122,7 +127,8 @@ module nms_dataflow_core_sdram #(
.producer_done_valid(dm_producer_done_valid), .producer_done_node_id(dm_producer_done_node_id),
.ready_valid(dm_ready_valid), .ready_ready(dm_ready_ready), .ready_node_id(dm_ready_node_id),
.ready_x_base(dm_ready_x_base), .ready_w_base(dm_ready_w_base),
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr)
.ready_n_tiles(dm_ready_n_tiles), .ready_result_addr(dm_ready_result_addr),
.any_pending(dm_any_pending)
);
wire [15:0] dm_ready_node_id_ext = {{(16-NODE_IDW){1'b0}}, dm_ready_node_id};
@@ -135,6 +141,7 @@ module nms_dataflow_core_sdram #(
wire [$clog2(N_SLOTS)-1:0] dir_job_out_slot;
wire [3:0] dir_state;
wire dir_error;
wire dir_queue_empty;
neural_director #(
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
@@ -148,9 +155,37 @@ module nms_dataflow_core_sdram #(
.slot_n_tiles(dir_slot_n_tiles), .slot_result_addr(dir_slot_result_addr),
.slot_node_id(dir_slot_node_id), .slot_job_done(dir_slot_job_done),
.job_out_done(dir_job_out_done), .job_out_slot(dir_job_out_slot),
.dir_state(dir_state), .dir_error(dir_error)
.dir_state(dir_state), .dir_error(dir_error), .queue_empty(dir_queue_empty)
);
// ---- FPGA_DATA_READY: system-idle detection (see decisions.log
// for the full design rationale) ----
// sys_busy: true while ANY of {a slot is active, the director's
// dispatch queue is non-empty, dependency_manager has a node not
// yet dispatched} holds. data_ready is a sticky level that goes
// HIGH on the busy->idle falling edge (a graph just finished) and
// LOW again the instant any new work starts (registration or
// dispatch) -- self-clearing, no explicit host ACK needed. Correct
// ONLY if the host finishes registering every node of a graph
// before the first one completes (documented assumption, see
// decisions.log) -- registration (microseconds over SPI) is far
// faster than per-neuron compute (~195 real measured cycles) for
// every workload this project has characterized.
wire sys_busy = (|job_active) || (!dir_queue_empty) || dm_any_pending;
reg sys_busy_prev;
reg data_ready_reg;
always @(posedge clk) begin
if (rst) begin
sys_busy_prev <= 1'b0;
data_ready_reg <= 1'b0;
end else begin
sys_busy_prev <= sys_busy;
if (sys_busy) data_ready_reg <= 1'b0;
else if (sys_busy_prev) data_ready_reg <= 1'b1;
end
end
assign data_ready = data_ready_reg;
wire [15:0] completed_node_id_16 = dir_slot_node_id[dir_job_out_slot*16 +: 16];
assign dm_producer_done_valid = dir_job_out_done;
assign dm_producer_done_node_id = completed_node_id_16[NODE_IDW-1:0];
@@ -60,6 +60,9 @@ module nms_neural_multiprocessor_sdram_unified #(
input wire [15:0] reg_n_tiles,
input wire [ADDR_WIDTH-1:0] reg_result_addr,
// FPGA_DATA_READY: system-idle sticky flag, see nms_dataflow_core_sdram.v
output wire data_ready,
// ---- STEP19: ONE physical SDRAM interface, ALL traffic
// (weights + activations + results) ----
output wire sdram_cke,
@@ -68,7 +71,7 @@ module nms_neural_multiprocessor_sdram_unified #(
output wire sdram_cas_n,
output wire sdram_we_n,
output wire [1:0] sdram_ba,
output wire [11:0] sdram_a,
output wire [12:0] sdram_a,
inout wire [15:0] sdram_dq,
output wire [1:0] sdram_dqm
);
@@ -94,6 +97,7 @@ module nms_neural_multiprocessor_sdram_unified #(
.reg_required(reg_required), .reg_producer_ids(reg_producer_ids),
.reg_x_base(reg_x_base), .reg_w_base(reg_w_base), .reg_n_tiles(reg_n_tiles),
.reg_result_addr(reg_result_addr),
.data_ready(data_ready),
.slot_mem_req(slot_mem_req), .slot_mem_wr(slot_mem_wr), .slot_mem_addr(slot_mem_addr),
.slot_mem_wdata(slot_mem_wdata), .slot_mem_lb_n(slot_mem_lb_n), .slot_mem_ub_n(slot_mem_ub_n),
.slot_mem_rdata(slot_mem_rdata), .slot_mem_ready(slot_mem_ready),
@@ -111,7 +111,7 @@ module tb #(
// results ALL share this single bus/chip now -- no PSRAM anywhere.
wire sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n;
wire [1:0] sdram_ba;
wire [11:0] sdram_a;
wire [12:0] sdram_a;
wire [15:0] sdram_dq;
wire [1:0] sdram_dqm;
@@ -855,6 +855,19 @@ module tb #(
// non-overlapping 1MB-aligned regions in the single 8MB SDRAM.
run_dense_layer("D-Stress", 256, 16, 16'd400, 26'h200000, 26'h010000, 26'h300000, 1'b0);
// FPGA_DATA_READY check: the whole graph (256 nodes) just
// finished and no new work has been registered -- data_ready
// must be asserted (system-idle sticky flag, see
// nms_dataflow_core_sdram.v). A few idle cycles for the
// busy->idle edge to settle before sampling.
repeat (4) @(posedge clk);
if (u_nmp.data_ready !== 1'b1) begin
$display("FAIL data_ready: expected 1 after graph completion, got %b", u_nmp.data_ready);
errors = errors + 1;
end else begin
$display("PASS data_ready: correctly asserted after graph completion");
end
$display("========================================");
if (errors == 0)
$display("ALL %0d WORKLOAD SUITES PASSED (N_SLOTS_CFG=%0d, PFD_CFG=%0d, SINGLE SDRAM for weights+activations+results, no PSRAM)", tests, N_SLOTS_CFG, PFD_CFG);
+19 -1
View File
@@ -70,7 +70,14 @@ module dependency_manager #(
output reg [ADDR_WIDTH-1:0] ready_x_base,
output reg [ADDR_WIDTH-1:0] ready_w_base,
output reg [15:0] ready_n_tiles,
output reg [ADDR_WIDTH-1:0] ready_result_addr
output reg [ADDR_WIDTH-1:0] ready_result_addr,
// FPGA_DATA_READY support: high while at least one registered node
// has not yet been handed to the Director (ST_WAITING or ST_READY --
// ST_DISPATCHED is deliberately excluded, since dispatched work is
// tracked downstream by neural_director.v's own queue/slot state,
// not here -- see this file's own ST_DISPATCHED comment).
output wire any_pending
);
localparam ST_EMPTY = 2'd0;
@@ -111,6 +118,17 @@ module dependency_manager #(
end
end
// ---- FPGA_DATA_READY support (see any_pending port comment above) ----
reg any_pending_r;
integer pi;
always @(*) begin
any_pending_r = 1'b0;
for (pi = 0; pi < N_NODES; pi = pi + 1)
if (node_state[pi] == ST_WAITING || node_state[pi] == ST_READY)
any_pending_r = 1'b1;
end
assign any_pending = any_pending_r;
integer ni, di;
always @(posedge clk) begin
+8 -1
View File
@@ -73,7 +73,13 @@ module neural_director #(
output reg [$clog2(N_SLOTS)-1:0] job_out_slot,
output reg [3:0] dir_state,
output reg dir_error
output reg dir_error,
// FPGA_DATA_READY support: high when the dispatch queue is empty
// (no job waiting for a free slot) -- combined upstream with
// dependency_manager's any_pending and this module's own slot
// activity to detect true system-idle.
output wire queue_empty
);
localparam DIR_IDLE = 4'd0;
@@ -97,6 +103,7 @@ module neural_director #(
reg [Q_ADDR_WIDTH:0] q_count; // one extra bit: 0..QUEUE_DEPTH inclusive
wire q_empty = (q_count == 0);
assign queue_empty = q_empty;
wire q_full = (q_count == QUEUE_DEPTH[Q_ADDR_WIDTH:0]);
assign job_in_ready = !q_full;