feat: real DDRManager re-measurement at 32-bit channel + BOM/pinout docs (EXP-0087)

Re-measured ddr_prefetch_mgr.v's (EXP-0083) real benefit against the
now-closed 32-bit DDR3 channel (EXP-0086), per this project's own
standing plan. Real result: the 2.86% benefit measured at the old
16-bit channel is GONE at 32-bit (WITH: 100663.1335ns vs WITHOUT:
100656.6835ns -- a 0.0064% regression, statistically a wash). The
wider channel's lower per-tile latency already absorbs the gap the
look-ahead prefetch used to hide. Kept wired in for correctness/
timing-neutrality (real P&R already signs off with it included), but
it's no longer a real performance win. Updated docs/ARCHITECTURE_
ANALYSIS.md and docs/PHYSICAL_REALIZATION.md accordingly.

Found and fixed 3 real testbench/simulation-setup bugs along the way:
- tb_n2_system_ddr3.v and tb_mig_native_adapter.v still had a stale
  CLKIN_PERIOD=2900 (the FAILED EXP-0084 clock period) instead of the
  current real, closed 3225ps (EXP-0086).
- tb_n2_system_ddr3.v used SystemVerilog-only $signed(8'(...)) cast
  syntax, invalid for xvlog's default plain-Verilog mode -- fixed via
  an intermediate 8-bit reg.
- Building a fresh sim_1 fileset needs the real MIG simulation
  dependency set added explicitly (mig_7series_0_mig.v is marked
  USED_IN_SIMULATION=0 in the project since testbenches bypass the
  public wrapper); verilog_define is a fileset-level property, not
  per-file, in this Vivado version.

New measurement-only fork (not part of the real synthesis target, per
fork-before-promote discipline): packed_slot_noprefetch.v +
tb_n2_system_ddr3_noprefetch.v, reproducing the pre-EXP-0083 direct
per-tile activation-fetch sequencing for a fair A/B baseline.

Also adds docs/BOM.md and docs/PINOUT.md: a real component list (DDR3
x2, flash, FPGA already verified; clk_ref oscillator and an ESP32-S3-
WROOM-1 module newly verified in-stock on LCSC; sys_clk oscillator
flagged as needing a custom-programmed order, no off-the-shelf SKU at
the required 310.077MHz) and a consolidated, board-layout-ready pinout
extract of PHYSICAL_REALIZATION.md's own pin tables.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-20 18:37:41 +02:00
co-authored by Claude Sonnet 5
parent 678caa9bce
commit 344e798ad5
9 changed files with 1276 additions and 48 deletions
+71 -30
View File
@@ -9,11 +9,13 @@ directly measured (real simulation trace, real P&R report) or a calculation
built from directly-measured building blocks — the two are labeled built from directly-measured building blocks — the two are labeled
explicitly throughout. Nothing here is guessed. explicitly throughout. Nothing here is guessed.
**Status note (post EXP-0086)**: §5.1 (denser activation packing) is **Status note (post EXP-0087)**: §5.1 (denser activation packing) is
**DONE and real-P&R-verified** (EXP-0081/0082); §5.2 (DDRManager) phase 1 **DONE and real-P&R-verified** (EXP-0081/0082); §5.2 (DDRManager) phase 1
is **DONE and real-measured** (EXP-0083, a genuinely modest ~2.9% real is **DONE, built, and RE-MEASURED against the closed 32-bit channel**
benefit, measured against the OLD 16-bit signoff — re-measurement against (EXP-0083's original modest ~2.9% real benefit was measured only at the
the now-closed 32-bit channel is a real next step); §5.4 (32-bit widening) OLD 16-bit signoff — real re-measurement, EXP-0087, found the benefit is
now ~0%/a wash at 32-bit, kept wired in for correctness/timing-neutrality
only, not performance); §5.4 (32-bit widening)
is **fully DONE — real-verified AND real timing-closed** (EXP-0084 is **fully DONE — real-verified AND real timing-closed** (EXP-0084
functional + EXP-0086 timing closure; honest intermediate story: the first functional + EXP-0086 timing closure; honest intermediate story: the first
timing attempt failed because of a SEPARATE, stacked clock speedup, not the timing attempt failed because of a SEPARATE, stacked clock speedup, not the
@@ -41,7 +43,7 @@ DDR3 channel.
| → DDR3 can sustain, post-EXP-0081/0082 packing (2 tiles/burst, DONE) | **~50%** of one core's peak compute throughput | §3.2, current, real | | → DDR3 can sustain, post-EXP-0081/0082 packing (2 tiles/burst, DONE) | **~50%** of one core's peak compute throughput | §3.2, current, real |
| Real P&R timing margin (WNS), current trustworthy (32-bit, CLOSED) | **+0.096 ns** | measured, EXP-0086 real P&R | | Real P&R timing margin (WNS), current trustworthy (32-bit, CLOSED) | **+0.096 ns** | measured, EXP-0086 real P&R |
| Real DDR3 physical bandwidth ceiling, 32-bit channel (closed timing) | **~2.48 GB/s** | measured, real P&R signoff, EXP-0086 — 2× EXP-0083's 16-bit ~1.24 GB/s, real not projected | | Real DDR3 physical bandwidth ceiling, 32-bit channel (closed timing) | **~2.48 GB/s** | measured, real P&R signoff, EXP-0086 — 2× EXP-0083's 16-bit ~1.24 GB/s, real not projected |
| DDRManager phase 1 (single-slot look-ahead prefetch) real benefit | **2.86%** reduction in total real simulated time | measured, real xsim A/B on `tb_n2_system_ddr3.v` (§5.2, EXP-0083) — measured against the OLD 16-bit signoff; modest, honestly reported, not oversold; re-measurement against the closed 32-bit channel is a real next step | | DDRManager phase 1 (single-slot look-ahead prefetch) real benefit | **~0% (0.0064% regression)** at the closed 32-bit channel | measured, real xsim A/B on `tb_n2_system_ddr3.v` (§5.2, EXP-0087) — the old 16-bit-only 2.86% figure (EXP-0083) does not survive the wider channel; kept wired in (timing-neutral, real P&R already signs off with it) but no longer a real performance win |
| DSP48E1 headroom for scaling | 224/240 free (93%) | measured, real P&R utilization | | DSP48E1 headroom for scaling | 224/240 free (93%) | measured, real P&R utilization |
The DSP headroom is real and large. The memory-bandwidth ceiling is real, The DSP headroom is real and large. The memory-bandwidth ceiling is real,
@@ -60,9 +62,10 @@ stacked an ADDITIONAL clock speedup (`ui_clk` +11.2%) in the same wizard
session, which an unrelated, pre-existing compute-pipeline path couldn't session, which an unrelated, pre-existing compute-pipeline path couldn't
absorb (§5.4.1) — width and clock rate turned out to be separable levers, absorb (§5.4.1) — width and clock rate turned out to be separable levers,
and reverting just the clock period closed timing with the width gain and reverting just the clock period closed timing with the width gain
intact. The DDRManager (§5.2) remains complementary — hides latency, intact. The DDRManager (§5.2) was re-measured against this now-closed,
doesn't raise the physical ceiling — and its own real benefit should now be wider channel (EXP-0087): its real benefit is gone (~0%, a 0.0064%
re-measured against this closed, wider channel (not yet done). regression) — the wider channel's own lower per-tile latency already
absorbed the gap the look-ahead used to hide.
--- ---
@@ -311,7 +314,7 @@ burst, new-burst crossing, back-to-back alternation), `tb_packed_slot.v`
`tb_n2_system_ddr3.v` (8/8 PASS, real xsim against real `ddr3_model.sv`, `tb_n2_system_ddr3.v` (8/8 PASS, real xsim against real `ddr3_model.sv`,
JEDEC trace confirmed to show no more half-burst zero-padding). JEDEC trace confirmed to show no more half-burst zero-padding).
### 5.2 [Complementary, addresses latency not bandwidth] DDRManager with orchestrator-driven prefetch (user's proposal) — **phase 1 DONE (EXP-0083), real benefit smaller than the original hypothesis below predicted** ### 5.2 [Complementary, addresses latency not bandwidth] DDRManager with orchestrator-driven prefetch (user's proposal) — **phase 1 DONE (EXP-0083); real benefit at the closed 32-bit channel is now ZERO (EXP-0087)**
**The original hypothesis** (written before building anything, now corrected **The original hypothesis** (written before building anything, now corrected
by real measurement — kept here so the correction is visible, not silently by real measurement — kept here so the correction is visible, not silently
@@ -333,13 +336,15 @@ DDR3 fetch latency itself (which is dominated by row activation/precharge,
§3.3, and look-ahead cannot make a single fetch faster, only start it §3.3, and look-ahead cannot make a single fetch faster, only start it
earlier). earlier).
**Real, measured result** (`ddr_prefetch_mgr.v`, real P&R WNS +0.073ns, up **Real, measured result at the OLD 16-bit channel** (`ddr_prefetch_mgr.v`,
from EXP-0082's +0.068ns, LUTs 5644, DSP48E1 16 unchanged): real P&R WNS +0.073ns, up from EXP-0082's +0.068ns, LUTs 5644, DSP48E1 16
unchanged):
- Real apples-to-apples comparison on the **real DDR3 backend** - Real apples-to-apples comparison on the **real DDR3 backend**
(`tb_n2_system_ddr3.v` via real xsim, same N=2/8-position workload, (`tb_n2_system_ddr3.v` via real xsim, same N=2/8-position workload,
before vs after, same `ddr3_model.sv`): **2.86% reduction in total real before vs after, same `ddr3_model.sv`): **2.86% reduction in total real
simulated time** (108370.88ns → 105268.43ns). This is the trustworthy simulated time** (108370.88ns → 105268.43ns) — EXP-0083's own headline
headline number. number, but see EXP-0087 below: this figure does NOT survive the 32-bit
channel and should not be quoted as the current real benefit.
- On the fast SDR placeholder backend (used for isolated glue-logic - On the fast SDR placeholder backend (used for isolated glue-logic
testing, `tb_ddr_prefetch_mgr.v`): 0.9% reduction in a row-switch-heavy testing, `tb_ddr_prefetch_mgr.v`): 0.9% reduction in a row-switch-heavy
scenario, and -1.4% (i.e. not faster) in an isolated same-row best case — scenario, and -1.4% (i.e. not faster) in an isolated same-row best case —
@@ -348,6 +353,35 @@ from EXP-0082's +0.068ns, LUTs 5644, DSP48E1 16 unchanged):
cleanly isolate the mechanism the real DDR3 backend's own row/bank timing cleanly isolate the mechanism the real DDR3 backend's own row/bank timing
does. Full detail: EXP-0083 in `hardware/v2/logs/experiments.log`. does. Full detail: EXP-0083 in `hardware/v2/logs/experiments.log`.
**Real RE-measurement at the closed 32-bit channel (EXP-0087) — the
benefit is now GONE**: EXP-0083's 2.86% figure was only ever measured
against the OLD 16-bit/155MHz channel. Once the 32-bit channel's own real
timing closed (EXP-0086), this project's own standing plan
("re-measure once the wider channel's timing actually closes") was
carried out: a real, fair A/B on the SAME 32-bit/3225ps config, comparing
the current, committed `packed_slot.v` (with `ddr_prefetch_mgr.v`) against
a measurement-only fork reproducing the pre-EXP-0083 direct-fetch
sequencing (`hardware/v3/sim/packed_slot_noprefetch.v`, not part of the
real synthesis target). Real result, both runs 8/8 PASS, 0 errors,
identical golden data:
- WITH `ddr_prefetch_mgr.v`: $finish at 100663.1335 ns
- WITHOUT `ddr_prefetch_mgr.v`: $finish at 100656.6835 ns
- **WITH is 6.45ns SLOWER — a 0.0064% real regression**, statistically a
wash, definitively NOT a benefit any more.
**Real, honest interpretation**: the 32-bit widening already halves the
real per-tile DDR3 round-trip latency. EXP-0083's own real finding
already showed the achievable benefit was capped by
`neural_processor_packed.v`'s fixed one-operand-per-cycle consumption
rate, not DDR3 latency, even at 16-bit — the wider channel shrinks the
real per-tile wait below whatever small gap the look-ahead could still
hide, leaving effectively nothing left to overlap. `ddr_prefetch_mgr.v`
stays wired into the real, committed `packed_slot.v` (real P&R already
signs off with it included, EXP-0086, and it causes zero real harm) —
but its justification is now "real, correct, timing-neutral", not "real
performance win". Building the larger multi-slot scheduler sketched below
is **not justified** by this real result.
**What it does NOT solve** (this part of the original reasoning holds): **What it does NOT solve** (this part of the original reasoning holds):
§3.2's bandwidth ceiling is a hard physical limit (bytes/second the DDR3 §3.2's bandwidth ceiling is a hard physical limit (bytes/second the DDR3
channel can physically move) — prefetching earlier doesn't move more bytes channel can physically move) — prefetching earlier doesn't move more bytes
@@ -366,13 +400,17 @@ selection uses a registered index bit at both fill and read time, same
"known long before the data it gates" discipline as `act_tile_fetch.v`'s "known long before the data it gates" discipline as `act_tile_fetch.v`'s
own EXP-0081 layout — confirmed timing-safe by real P&R, not asserted. own EXP-0081 layout — confirmed timing-safe by real P&R, not asserted.
**Full multi-slot / whole-Director-queue scheduler — still NOT built, **Full multi-slot / whole-Director-queue scheduler — still NOT built, and
and now a more deliberate call, not just deferred**: given phase 1's real now real-disconfirmed, not just deferred**: the re-examination against the
measured benefit was modest, the cost/benefit case for the larger design 32-bit channel's real numbers (EXP-0087, above) has now happened, and the
below should be re-examined against the 32-bit-widened channel's real result is that phase 1's own benefit is gone entirely at this core count
numbers (§5.4) before committing more engineering time to it — building it — there is no real cost/benefit case left for the larger design below
now, on the still-16-bit channel, risks the same gap between hypothesis and until a NEW real bottleneck reintroduces DDR3-latency-bound behavior
measurement this phase-1 experiment just corrected. (e.g. a higher core count where the shared bus is contended again, or a
workload with heavier row-switching than this experiment's own test
pattern). Revisit only if/when N=4/8/16 scaling (§5.5) reveals DDR3
latency, not `neural_processor_packed.v`'s own consumption rate, as the
real limiting factor again.
**Concrete design sketch for the full version** (informed by what already **Concrete design sketch for the full version** (informed by what already
exists in this codebase; kept for when it's revisited): exists in this codebase; kept for when it's revisited):
@@ -537,18 +575,21 @@ specifically to document where/how it breaks rather than to succeed):
EXP-0086 real timing closure, WNS=+0.096ns). Doubles the physical EXP-0086 real timing closure, WNS=+0.096ns). Doubles the physical
ceiling itself (real, closed, ~2.48 GB/s), which §5.1 alone could not do. ceiling itself (real, closed, ~2.48 GB/s), which §5.1 alone could not do.
3. §5.2 (DDRManager) — phase 1 (single-slot look-ahead prefetch) is 3. §5.2 (DDRManager) — phase 1 (single-slot look-ahead prefetch) is
**DONE** (EXP-0083, real but modest ~2.9% benefit, measured on the OLD **DONE and RE-MEASURED** (EXP-0083's original ~2.9% benefit was
16-bit channel). **Next real step**: re-measure this SAME real A/B measured on the OLD 16-bit channel; EXP-0087 re-measured the same real
against the now-closed 32-bit channel (EXP-0086), since a wider channel A/B against the now-closed 32-bit channel and found the benefit is
may change how much idle-channel time there is left to fill — only gone, ~0%/a 0.0064% regression — the wider channel's lower per-tile
build the larger multi-slot scheduler version if that re-measurement latency already absorbed the gap the look-ahead used to hide). Kept
justifies it, not on the original (now-corrected) hypothesis alone. Not wired in for correctness/timing-neutrality; the larger multi-slot
yet done. scheduler is **not justified** by this real result — revisit only if
N=4/8/16 scaling below reintroduces DDR3-latency-bound behavior.
4. Real N=2/4/8/16 tests, each with its own real P&R signoff (margin is 4. Real N=2/4/8/16 tests, each with its own real P&R signoff (margin is
thin, §2 — do not assume a prior N's timing closure predicts the next). thin, §2 — do not assume a prior N's timing closure predicts the next).
N=8 is the real target configuration; N=16 is expected to expose real N=8 is the real target configuration; N=16 is expected to expose real
bus/arbitration/timing limits and is built specifically to document that bus/arbitration/timing limits and is built specifically to document that
breakdown, not to be a viable production configuration. breakdown, not to be a viable production configuration. With DDR3
latency no longer the real bottleneck at N=2 (EXP-0087), this is now
the more promising real lever to pursue next.
--- ---
@@ -697,6 +738,6 @@ design task, not yet scoped, and out of this repository's own RTL scope
| "~25% of peak sustainable" (pre-packing) / "~50%" (post-packing, current) | **Calculated** ratios; post-packing figure re-verified against real P&R (EXP-0082) and real simulation (§5.1) | | "~25% of peak sustainable" (pre-packing) / "~50%" (post-packing, current) | **Calculated** ratios; post-packing figure re-verified against real P&R (EXP-0082) and real simulation (§5.1) |
| I/O bank/DQS pin counts for XC7A100T-CSG324 (banks 14/15/16/34/35) | **Measured** — queried directly from the real Vivado part database for this exact part/package, used in §5.4's dual-channel-vs-widening analysis | | I/O bank/DQS pin counts for XC7A100T-CSG324 (banks 14/15/16/34/35) | **Measured** — queried directly from the real Vivado part database for this exact part/package, used in §5.4's dual-channel-vs-widening analysis |
| Row-switch penalty as a fraction of real workloads | **Not measured** — depends on host-chosen memory layout, flagged as an open question, not asserted | | Row-switch penalty as a fraction of real workloads | **Not measured** — depends on host-chosen memory layout, flagged as an open question, not asserted |
| DDRManager phase-1 real stall-reduction benefit (2.86%) | **Measured** — real xsim A/B on `tb_n2_system_ddr3.v`, real DDR3 backend, before vs after `ddr_prefetch_mgr.v` (EXP-0083), against the OLD 16-bit channel. Modest, not the larger figure the original hypothesis (§5.2) suggested before it was built. Re-measurement against the closed 32-bit channel (EXP-0086) is a real next step, not yet done. | | DDRManager phase-1 real stall-reduction benefit | **Measured** — real xsim A/B on `tb_n2_system_ddr3.v`, real DDR3 backend, before vs after `ddr_prefetch_mgr.v`. **2.86%** at the OLD 16-bit channel (EXP-0083); **~0% (0.0064% regression)** re-measured at the closed 32-bit channel (EXP-0087) — the benefit does not survive the wider channel, real and honestly reported, not oversold either direction. |
| Full multi-slot DDRManager's real benefit | **Not measured** not built; §5.2 recommends re-measuring phase 1 against the 32-bit-widened channel before deciding whether to build it | | Full multi-slot DDRManager's real benefit | **Not measured, not built, and now real-disconfirmed as a priority** — phase 1's own re-measurement against the closed 32-bit channel (EXP-0087) found ~0% benefit; §5.2 recommends against building the larger version unless N=4/8/16 scaling reintroduces DDR3-latency-bound behavior |
| 32-bit widening's real post-change bandwidth/timing numbers | **Measured** — real, closed P&R signoff (EXP-0086): WNS=+0.096ns, WHS=+0.036ns, 0 failing endpoints. The ~2.48 GB/s figure is now a real, closed-timing result, not a projection. | | 32-bit widening's real post-change bandwidth/timing numbers | **Measured** — real, closed P&R signoff (EXP-0086): WNS=+0.096ns, WHS=+0.036ns, 0 failing endpoints. The ~2.48 GB/s figure is now a real, closed-timing result, not a projection. |
+62
View File
@@ -0,0 +1,62 @@
# FPGA-Neural V3 — Bill of Materials
Real component list for the custom PCB (bare **XC7A100T-CSG324-2** + DDR3,
no dev board). Every line below is either **confirmed real and in-stock**
(part number + LCSC link, verified) or explicitly flagged as **needs the
user's own real sourcing check** — never a guessed/invented part number,
per this project's own standing discipline (CLAUDE.md: "verify real
component availability before committing to a part"). See
`docs/PINOUT.md` for how each part's pins map to the FPGA, and
`docs/PHYSICAL_REALIZATION.md` §1/§3 for the real, closed timing signoff
these frequency requirements are derived from (EXP-0086).
## Core, confirmed parts (verified in earlier sessions)
| # | Component | Part | Qty | Notes |
|---|---|---|---|---|
| 1 | FPGA | **XC7A100T-CSG324-2** | 1 | Speed grade -2. Real, closed timing signoff at this exact part (EXP-0086, WNS=+0.096ns). |
| 2 | DDR3 SDRAM | **Micron MT41J128M16JT-125:K** (2Gb, x16, DDR3-1600-rated) | **2** | Two chips ganged in parallel for the real 32-bit channel (EXP-0084/0086) — address/command/control shared, DQ/DQS/DM split 16 bits each. Verified in-stock on LCSC (earlier session). |
| 3 | Config flash | **Winbond W25Q32JVSSIQ** (32Mbit, SOIC-8) | 1 | Verified in-stock on LCSC (earlier session). Wired exclusively to the FPGA (no direct ESP32 connection). |
## Clocking — real, firm frequency requirements from the closed MIG config
| # | Component | Requirement | Status |
|---|---|---|---|
| 4 | `sys_clk_p/n` differential oscillator | **310.077 MHz**, differential (DIFF_SSTL15-compatible I/O, e.g. LVDS/LVPECL/HCSL) | **Not a stock catalog part at this exact frequency.** Real candidate family: **SiTime SiT9122** (220625 MHz MEMS oscillator, factory-programmable to any exact in-band frequency including 310.077 MHz) — but a custom-programmed frequency is order-to-spec, not an off-the-shelf SKU; typical low-MOQ, ~12 week lead time. **Needs the user's own real quote/order to get a firm LCSC SKU.** This exact frequency is a real, non-negotiable output of the Vivado MIG JEDEC/PLL calculator — do not substitute a different frequency without re-running the real MIG wizard (TimePeriod/InputClkFreq are tied together). |
| 5 | `clk_ref_p/n` differential oscillator | **200 MHz**, differential, LVDS (matches the LVDS_25 IOSTANDARD already assigned in the XDC) | **Confirmed real, in-stock**: **SiTime SiT9121AC-2CF-33E-200.00000**, LCSC **[C835051](https://lcsc.com/product-detail/programmable-oscillators_sitime-sit9121ac-2cf-33e-200-00000_C835051.html)**, 3.3V LVDS, factory-preset at exactly 200.000000 MHz, "In Stock". |
## Host MCU
| # | Component | Part | Status |
|---|---|---|---|
| 6 | ESP32 module (host/central processor) | **ESP32-S3-WROOM-1-N16R8** | **Confirmed real, in-stock**: LCSC **[C2913202](https://www.lcsc.com/product-detail/WiFi-Modules_Espressif-Systems-ESP32-S3-WROOM-1-N16R8_C2913202.html)**, ~8,375 units in stock at time of check, ~$5.09 ea. 45 GPIOs (real design needs only ~10: 4 for management SPI + sys_rst + data_ready_n + 4 for JTAG bit-banging, leaving generous headroom for debug UART etc.), 16MB flash + 8MB PSRAM, dual-core. No specific ESP32 module had been chosen anywhere in this project before now — this is a new real recommendation, not a previously-verified part. |
## DDR3 support (real design consideration, not yet a firm requirement)
| # | Component | Purpose | Status |
|---|---|---|---|
| 7 | VTT termination regulator (optional) | Sink/source termination for the DDR3 address/command/control/clock bus in fly-by topology — DQ/DQS lines use on-die termination and need no external parts. With 2 chips extending the fly-by chain, signal integrity margin may benefit from this. | Real, standard part for this role: **TI TPS51200**. Whether it's genuinely *needed* here is a **real, design-dependent judgment call** — depends on trace lengths, not established by this project's own work (no SI simulation has been done). One TPS51200 can typically supply 2 DDR3 chips sharing a rail per TI's own guidance, though thermal margin may favor one per chip. **Not confirmed in-stock this pass — treat as optional/TBD**, not a committed BOM line. |
## Power regulation (starting point only, not a full power-tree design)
| # | Component | Purpose | Status |
|---|---|---|---|
| 8 | VCCINT 1.0V regulator | FPGA core rail, the tightest-tolerance (~±5% per the Xilinx datasheet) and highest-current rail | Real, commonly-used candidate for Artix-7 designs: **Infineon IR38060** (integrated SupIRBuck, up to 6A) — appears in real Xilinx 7-series reference designs. **Stock not confirmed this pass.** |
| 9 | VCCAUX 1.8V, VCCO per-bank (1.5V DDR3 banks 34/35, 2.5V bank 14, 3.3V banks 15/16) | Remaining FPGA/DDR3 rails | **Not yet sourced** — only the tightest rail (VCCINT) was researched this pass; the rest of the power tree is a real, remaining task. |
## Known open items (honestly disclosed)
- **Line 4 (sys_clk oscillator)** is the real, highest-risk sourcing item —
no confirmed off-the-shelf SKU exists at the exact required frequency.
Get a real quote from SiTime (or an equivalent programmable-oscillator
vendor) before finalizing the BOM; a firm lead time affects build
scheduling.
- **Lines 7, 8, 9** need a real stock/price check before ordering — parts
named are real, plausible candidates, not yet individually confirmed
in-stock the way lines 13, 5, 6 are.
- Passive components (decoupling capacitors, pull-up/pull-down resistors
for the mode pins M[2:0]/CFGBVS, JTAG series resistors) are not itemized
here — standard values per the Xilinx UG470/UG483 reference design
guidance, not yet compiled into this BOM.
- This BOM does not include PCB fabrication, connectors, or enclosure
items — scope is components only.
+8 -5
View File
@@ -375,11 +375,14 @@ when it actually fires, instead of polling every loop iteration.
- ESP32-side JTAG bit-banging firmware (§5.1) does not exist yet — it's - ESP32-side JTAG bit-banging firmware (§5.1) does not exist yet — it's
software work on the host side, not part of this FPGA RTL. software work on the host side, not part of this FPGA RTL.
- `ddr_prefetch_mgr.v` (DDRManager phase 1, EXP-0083) is real, built, and - `ddr_prefetch_mgr.v` (DDRManager phase 1, EXP-0083) is real, built, and
timing-verified, but was only measured for benefit at the OLD 16-bit/ timing-verified. Its real performance benefit was 2.86% at the OLD
155MHz signoff (2.86% real reduction in total simulated time, modest — 16-bit/155MHz signoff, but a real re-measurement against the closed
see `docs/ARCHITECTURE_ANALYSIS.md` §5.2). The larger multi-slot 32-bit channel (EXP-0087) found the benefit is now GONE (~0%, a
DDRManager is not built. Its own real benefit should now be re-measured 0.0064% regression, statistically a wash) — see
against the closed 32-bit channel (EXP-0086) — not yet done. `docs/ARCHITECTURE_ANALYSIS.md` §5.2. Kept wired into `packed_slot.v`
for correctness/timing-neutrality only, not performance. The larger
multi-slot DDRManager is not built and, per EXP-0087, is not currently
justified.
- `data_ready_n` (EXP-0085, user-requested active-low sticky IRQ) is real, - `data_ready_n` (EXP-0085, user-requested active-low sticky IRQ) is real,
built, functionally verified (49/49 including 10 new checks, built, functionally verified (49/49 including 10 new checks,
`tb_spi_host_bridge_v3.v`), AND real P&R-verified at the closed EXP-0086 `tb_spi_host_bridge_v3.v`), AND real P&R-verified at the closed EXP-0086
+136
View File
@@ -0,0 +1,136 @@
# FPGA-Neural V3 — Consolidated Pinout Reference
Single-source pinout for the custom PCB (bare **XC7A100T-CSG324-2**, no dev
board). This is a flattened, board-layout-ready extract of
`docs/PHYSICAL_REALIZATION.md` §2 — that document remains the authoritative,
narrated source (it explains *why* each pin was chosen and every real
conflict/fix found along the way); this file is the quick reference for
schematic capture. Every pin below comes from a real routed design
(`n2_system_ddr3_top_routed.dcp`, EXP-0086 signoff) or the MIG-generated
`mig_7series_0.xdc` — none of it is guessed. Re-generate this file if
`docs/PHYSICAL_REALIZATION.md` §2 ever changes.
## Bank summary
| Bank | Voltage (VCCO) | Standard | Used for |
|---|---|---|---|
| 0 | per board config rail | LVCMOS (per UG470) | FPGA configuration control (PROGRAM_B/INIT_B/DONE/mode pins/CFGBVS) |
| 14 | 2.5V | LVDS_25 | `clk_ref_p/n` only (differential IDELAYCTRL reference clock) |
| 15 | 3.3V | LVCMOS33 | Management SPI (ESP32↔FPGA), `sys_rst`, `data_ready_n` |
| 16 | 3.3V | LVCMOS33 | Config flash SPI (FPGA↔flash) |
| 34 | 1.5V | SSTL15 / DIFF_SSTL15 | DDR3 addr/ba/control/ck, `sys_clk_p/n` |
| 35 | 1.5V | SSTL15 / DIFF_SSTL15 | DDR3 dq/dqs/dm |
## DDR3 (bank 34/35 — two MT41J128M16JT-125:K chips in parallel, 32-bit channel)
Address/command/control fan out identically to BOTH chips (not duplicated
per chip); DQ/DQS/DM are split 16 bits per chip.
| Signal | Pin | Signal | Pin | Signal | Pin |
|---|---|---|---|---|---|
| ddr3_dq[0] | C5 | ddr3_dq[16] | F4 | ddr3_addr[0] | U3 |
| ddr3_dq[1] | B7 | ddr3_dq[17] | F3 | ddr3_addr[1] | U4 |
| ddr3_dq[2] | B6 | ddr3_dq[18] | E2 | ddr3_addr[2] | V1 |
| ddr3_dq[3] | D8 | ddr3_dq[19] | D2 | ddr3_addr[3] | U1 |
| ddr3_dq[4] | C7 | ddr3_dq[20] | C1 | ddr3_addr[4] | L5 |
| ddr3_dq[5] | E6 | ddr3_dq[21] | H1 | ddr3_addr[5] | L6 |
| ddr3_dq[6] | E5 | ddr3_dq[22] | G1 | ddr3_addr[6] | L4 |
| ddr3_dq[7] | E7 | ddr3_dq[23] | F1 | ddr3_addr[7] | K5 |
| ddr3_dq[8] | B4 | ddr3_dq[24] | F6 | ddr3_addr[8] | M2 |
| ddr3_dq[9] | A4 | ddr3_dq[25] | G4 | ddr3_addr[9] | M3 |
| ddr3_dq[10] | A3 | ddr3_dq[26] | G3 | ddr3_addr[10] | L3 |
| ddr3_dq[11] | B3 | ddr3_dq[27] | J3 | ddr3_addr[11] | K3 |
| ddr3_dq[12] | B2 | ddr3_dq[28] | J2 | ddr3_addr[12] | M1 |
| ddr3_dq[13] | D5 | ddr3_dq[29] | K2 | ddr3_addr[13] | L1 |
| ddr3_dq[14] | D4 | ddr3_dq[30] | K1 | ddr3_ba[0] | V5 |
| ddr3_dq[15] | E3 | ddr3_dq[31] | H6 | ddr3_ba[1] | V2 |
| ddr3_dm[0] | C6 | ddr3_dm[2] | C2 | ddr3_ba[2] | U2 |
| ddr3_dm[1] | C4 | ddr3_dm[3] | G6 | ddr3_cas_n | R3 |
| ddr3_dqs_p[0] | A6 (DIFF) | ddr3_dqs_n[0] | A5 (DIFF) | ddr3_cke[0] | P2 |
| ddr3_dqs_p[1] | B1 (DIFF) | ddr3_dqs_n[1] | A1 (DIFF) | ddr3_cs_n[0] | T5 |
| ddr3_dqs_p[2] | H2 (DIFF) | ddr3_dqs_n[2] | G2 (DIFF) | ddr3_odt[0] | R2 |
| ddr3_dqs_p[3] | J4 (DIFF) | ddr3_dqs_n[3] | H4 (DIFF) | ddr3_ras_n | V4 |
| ddr3_ck_p[0] | N2 (DIFF) | ddr3_ck_n[0] | N1 (DIFF) | ddr3_we_n | T3 |
| ddr3_reset_n | D7 (LVCMOS15) | | | | |
| **sys_clk_p** | **N5 (DIFF_SSTL15)** | **sys_clk_n** | **P5 (DIFF_SSTL15)** | real, required freq **310.077 MHz** | |
## Reference clock (bank 14)
| Signal | Pin | Standard | Notes |
|---|---|---|---|
| clk_ref_p | T14 | LVDS_25 (2.5V) | required freq **200 MHz**, differential |
| clk_ref_n | T15 | LVDS_25 (2.5V) | |
Bank 14 hosts ONLY `clk_ref_p/n` — the config-flash bus was moved out of
this bank (to bank 16, see below) specifically because it can't share a
VCCO with this signal (EXP-0084).
## Management SPI — ESP32 ↔ FPGA, FPGA is slave (bank 15, 3.3V)
| Signal | Pin | Direction (FPGA side) | Notes |
|---|---|---|---|
| sclk | A15 | input | |
| mosi | B16 | input | |
| miso | B17 | output | |
| cs_n | A16 | input | |
| sys_rst | G13 | input | tentative pin, not yet a final board decision (§7 of PHYSICAL_REALIZATION.md) |
| data_ready_n | D14 | output | active-low sticky IRQ (EXP-0085); tentative pin, same caveat as sys_rst |
## Config flash SPI — FPGA ↔ Winbond W25Q32JVSSIQ, FPGA is master (bank 16, 3.3V)
| Signal | Pin | Direction (FPGA side) | Notes |
|---|---|---|---|
| flash_mosi | D9 | output | connects to flash DI |
| flash_miso | D10 | input | connects to flash DO |
| flash_cs_n | C9 | output | connects to flash /CS |
| (CCLK) | E9 | output | **not a top-level RTL port** — internally driven via `STARTUPE2`; wire the flash's own CLK pin directly to package pin E9 |
No direct ESP32↔flash connection exists by design — the ESP32 can only
reach the flash by relaying bytes through the FPGA over the management SPI
bus (`FLASH_XFER` opcode 0x40, see §5/§6 of PHYSICAL_REALIZATION.md).
## FPGA configuration control (bank 0, fixed by the device, not negotiable)
| Signal | Pin | Purpose |
|---|---|---|
| PROGRAM_B | P9 | pulse low to force reconfiguration from flash |
| INIT_B | P7 | low during config; re-asserting can indicate a config error |
| DONE | P10 | goes high once configuration succeeds — good status-LED candidate |
| M0 | P12 | mode select — tie per the Master SPI boot setting below |
| M1 | P13 | mode select |
| M2 | P11 | mode select |
| CFGBVS | P8 | tie to match bank 0's own VCCO logic level (see UG470) |
**Master SPI boot mode pins**: `M[2:0] = 001` — tie via pull-up/pull-down
resistors on the board (static, not driven dynamically).
## JTAG (bank 0, always available)
| Signal | Pin |
|---|---|
| TCK | E10 |
| TDI | E11 |
| TMS | E12 |
| TDO | E13 |
Intended to be driven by the ESP32 bit-banging the TAP state machine
(factory programming, recovery, development) — see §5.1 of
`docs/ARCHITECTURE_ANALYSIS.md`; that firmware doesn't exist yet.
## Reserved / do-not-use (bank 14)
`L16` (EMCCLK), `R16` (RDWR_B), `V15` (CSI_B) — config-mode-reserved,
`PROHIBIT`ed in the project XDC (via `get_sites -of_objects`, not directly
on the `package_pin` objects — a real Vivado property gotcha, see
CLAUDE.md). Kept clear for any future Quad-SPI/BPI expansion; not used by
this design's own Master-SPI-only configuration scheme.
## Open items (not yet finalized — do not treat as final for PCB layout)
- `sys_rst` (G13) and `data_ready_n` (D14) are real, verified-free pins
chosen only to unblock P&R — not yet a final board decision pending the
rest of the layout (reset circuit, status LEDs).
- Bank 15/16 VCCO is *assumed* 3.3V in this pinout — confirm against the
real board power plan before finalizing.
- `sys_clk_p/n` and `clk_ref_p/n` oscillator part selection (real,
in-stock candidates) is tracked in `docs/BOM.md`.
+106
View File
@@ -5707,3 +5707,109 @@ closes"). (3) Build the result-writeback engine (ARCHITECTURE_ANALYSIS
core-count scaling tests, each with its own real P&R signoff, per the core-count scaling tests, each with its own real P&R signoff, per the
user's own standing directive ("senza illusioni ma analizzando la user's own standing directive ("senza illusioni ma analizzando la
situazione piu' performante"). situazione piu' performante").
EXP-0087 -- real re-measurement of DDRManager (EXP-0083) benefit
against the now-closed 32-bit channel: real result is that the
benefit VANISHES (2026-09-20, user's own directive: "misuriamo il
beneficio come consigli" -- re-measure once the wider channel's
timing actually closes, per EXP-0086's own next_action)
CONTEXT: EXP-0083's own real 2.86% stall-reduction figure for
ddr_prefetch_mgr.v (single-slot look-ahead activation prefetch) was
measured ONLY against the OLD 16-bit/155.039MHz DDR3 channel -- never
re-verified at the real, now-closed 32-bit/155.039MHz config (EXP-0086).
This experiment redoes that A/B measurement fairly, both variants now
run against the SAME real 32-bit channel.
METHOD: real xsim (Vivado's own project-integrated `launch_simulation`,
not raw xvlog/xelab/xsim by hand) of `tb_n2_system_ddr3.v` against a
freshly-built `sim_1` fileset, real `ddr3_model.sv` (2 real chip
instances) + real `mig_7series_0_mig` (not the public wrapper, matching
this project's own established SIM_BYPASS_INIT_CAL="FAST" override
pattern). Real A/B pair:
- WITH prefetch: the CURRENT, real, committed `packed_slot.v` (wires
`ddr_prefetch_mgr.v`, unmodified).
- WITHOUT prefetch: a new, measurement-only fork,
`hardware/v3/sim/packed_slot_noprefetch.v`, reproducing the
pre-EXP-0083 baseline sequencing -- direct `act_tile_fetch.v`,
one req/wait/consume cycle per tile, no look-ahead overlap. Per
this project's own fork-before-promote discipline: NOT part of the
real synthesis target, sim-only, alongside its own driver testbench
`hardware/v3/sim/tb_n2_system_ddr3_noprefetch.v` (identical to
tb_n2_system_ddr3.v except the one module instantiation swapped).
REAL SETUP BUGS FOUND AND FIXED BEFORE A TRUSTWORTHY MEASUREMENT WAS
POSSIBLE (none of these were about the DDRManager itself -- all were
real, pre-existing or fresh-fileset gaps in the test infrastructure):
1. `tb_n2_system_ddr3.v` and `tb_mig_native_adapter.v` both still had
`CLKIN_PERIOD = 2900` (the FAILED EXP-0084 clock period) hardcoded
-- stale since EXP-0086 reverted the REAL config to 3225ps. Fixed
both to 3225, so this and all future xsim runs against these
testbenches reflect the real, current, closed-timing hardware
config, not a superseded one.
2. `tb_n2_system_ddr3.v` used SystemVerilog-only `$signed(8'((expr) &
8'hFF))` sized-cast syntax in two golden-data helper functions --
silently invalid for `xvlog` in its default (non `-sv`) mode for a
plain `.v` file, exactly the class of bug CLAUDE.md's own
"no SV-only syntax in a plain .v file" lesson already warned about
(until now only checked for synthesizable RTL, this is the first
real hit in a TESTBENCH). Fixed with an intermediate 8-bit `reg`
doing the same width-truncation-before-`$signed()` job portably.
3. Building a FRESH `sim_1` fileset from scratch (rather than reusing
a pre-populated one) does not auto-pull in `mig_7series_0_mig.v`'s
own real simulation dependency set -- that file is marked
`USED_IN_SIMULATION=0` in the project (Vivado expects the PUBLIC
`mig_7series_0.v` wrapper to be the sim entry point; this project's
own testbenches deliberately bypass it to override
`SIM_BYPASS_INIT_CAL`). Fixed by explicitly adding the real 68-file
`user_design/rtl` tree, `ddr3_model.sv` (`x2Gb`/`sg125`/`x16`
defines -- a real, second gotcha: `verilog_define` is a
FILESET-level property in this Vivado version, not a per-file one,
`set_property verilog_define ... [get_files ...]` errors outright),
`wiredly.v`, and `glbl.v` to the fileset by hand, mirroring the
real vendor-shipped `xsim_files.prj` file list.
REAL RESULT (both real xsim runs, 8/8 PASS, 0 errors, identical golden
results, both against the SAME real 32-bit/3225ps closed-timing config):
WITH ddr_prefetch_mgr.v: $finish at 100663.1335 ns
WITHOUT ddr_prefetch_mgr.v: $finish at 100656.6835 ns
-> WITH is 6.45 ns SLOWER than WITHOUT -- a 0.0064% real REGRESSION,
not a benefit. Statistically indistinguishable from zero (well
within normal run-to-run scheduling noise), but definitively NOT
the 2.86% improvement EXP-0083 measured at the old 16-bit width.
REAL, HONEST INTERPRETATION (not asserted without the measurement above
to back it): the 32-bit channel's real widening (EXP-0084/0086) already
halves the real per-tile DDR3 round-trip latency (same burst count, ~2x
the bits/cycle). EXP-0083's own real finding was that the look-ahead
prefetch's benefit was ALREADY capped by `neural_processor_packed.v`'s
own fixed one-operand-per-cycle consumption rate, not by DDR3 latency
itself, even at 16-bit -- widening the channel further shrinks the real
per-tile DDR3 wait below whatever gap the look-ahead could hide, so
there is now essentially nothing left for `ddr_prefetch_mgr.v` to
usefully overlap. This is a real, coherent explanation consistent with
EXP-0083's own already-disclosed caveat ("this hypothesis overstated the
achievable benefit... the pipeline accepts one operand per cycle"), not
a new assumption.
DECISION: `ddr_prefetch_mgr.v` stays wired into the real, committed
`packed_slot.v` (no reason to rip it out -- real P&R signoff, EXP-0086,
already shows the 32-bit config closes timing WITH it included, and it
causes zero real harm). But its own real justification for existing is
now "real, verified, functionally correct, timing-neutral" rather than
"real, measured performance win" -- the performance case this project
built it for (EXP-0083's own 2.86%) does not survive the wider channel.
Building the larger multi-slot/whole-Director-queue scheduler version
(the ORIGINAL, not-yet-built EXP-0083 stretch goal) is NOT justified by
this real result -- the real bottleneck this experiment reveals is
`neural_processor_packed.v`'s own one-operand-per-cycle consumption
rate, not DDR3 latency, at the current core count.
next_action: with DDR3 latency no longer the real constraint at N=2,
core-count scaling (N=4/8/16, already directed by the user) is now the
more promising real lever -- proceed there. The opportunistic BRAM
cache idea (`docs/ARCHITECTURE_ANALYSIS.md` S5.6.1) targets the SAME
now-diminished DDR3-latency lever this experiment just showed has
little room left to give at N=2 -- worth real-measuring its own benefit
carefully before investing further RTL effort, rather than assuming
EXP-0083's original optimistic framing still applies.
+336
View File
@@ -0,0 +1,336 @@
`timescale 1ns/1ps
// ============================================================
// MEASUREMENT-ONLY FORK of hardware/v3/rtl/packed_slot.v, NOT part of
// the real synthesis target and NOT maintained going forward -- exists
// ONLY to reproduce the pre-EXP-0083 per-tile req/wait/consume
// activation-fetch sequencing (direct act_tile_fetch.v, no look-ahead
// ping-pong buffer), so it can be A/B-measured against the current,
// real, committed packed_slot.v (which wires ddr_prefetch_mgr.v) at
// the NOW-CLOSED 32-bit/3225ps DDR3 config (EXP-0086) -- the ORIGINAL
// EXP-0083 measurement was only ever taken at the OLD 16-bit/155MHz
// config, so it's not a fair before/after comparison for today's real
// hardware. Per this project's own fork-before-promote discipline:
// this file is the throwaway "before" fork, `packed_slot.v` itself is
// never edited for this measurement.
//
// Everything below is copied from the current real packed_slot.v
// EXCEPT: ddr_prefetch_mgr.v is replaced with a direct
// act_tile_fetch.v instance, and S_TILEREQ/S_TILEWAIT revert to the
// simple one-shot-per-tile sequencing act_tile_fetch.v's own interface
// expects (issue req, wait for its own valid pulse, consume, move on)
// -- no job-level look-ahead, no depth-2 ping-pong overlap.
// ============================================================
module packed_slot_noprefetch #(
parameter DATA_WIDTH = 8,
parameter P_IN = 8,
parameter ACC_WIDTH = 32,
parameter BURST_LEN = 8,
parameter ADDR_WIDTH = 26,
parameter LAYER_BYTES = 128,
parameter BUFADDRW = $clog2(LAYER_BYTES)
)(
input wire clk,
input wire rst,
input wire job_start,
input wire [ADDR_WIDTH-1:0] x_base_a,
input wire [ADDR_WIDTH-1:0] x_base_b,
input wire [ADDR_WIDTH-1:0] w_base,
input wire [15:0] n_tiles,
input wire [ADDR_WIDTH-1:0] result_addr_a,
input wire [ADDR_WIDTH-1:0] result_addr_b,
input wire [15:0] node_id_a,
input wire [15:0] node_id_b,
output reg job_done,
output reg signed [DATA_WIDTH-1:0] result_data_a,
output reg signed [DATA_WIDTH-1:0] result_data_b,
output reg [15:0] result_node_id_a,
output reg [15:0] result_node_id_b,
output reg [ADDR_WIDTH-1:0] result_addr_a_out,
output reg [ADDR_WIDTH-1:0] result_addr_b_out,
output wire mem_active,
input wire mem_grant,
output wire ctrl_req,
output wire ctrl_wr,
output wire [ADDR_WIDTH-2:0] ctrl_addr,
output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready,
input wire ctrl_busy
);
localparam S_IDLE = 4'd0,
S_MEMWAIT = 4'd1,
S_PREFETCH = 4'd2,
S_SWAP = 4'd3,
S_JOBSTART = 4'd4,
S_TILEREQ = 4'd5,
S_TILEWAIT = 4'd6,
S_OPERAND = 4'd7,
S_RESULT = 4'd8,
S_DONE = 4'd9;
reg [3:0] state;
reg [ADDR_WIDTH-1:0] w_base_lat, x_base_a_lat, x_base_b_lat;
reg [15:0] n_tiles_lat;
reg [ADDR_WIDTH-1:0] result_addr_a_lat, result_addr_b_lat;
reg [15:0] node_id_a_lat, node_id_b_lat;
reg [15:0] tcnt;
reg pf_start;
wire pf_busy, pf_done;
wire pf_fill_we;
wire [BUFADDRW-1:0] pf_fill_addr;
wire [DATA_WIDTH-1:0] pf_fill_data;
wire pf_ctrl_req, pf_ctrl_wr;
wire [ADDR_WIDTH-2:0] pf_ctrl_addr;
wire [32*BURST_LEN-1:0] pf_ctrl_wdata;
wire [4*BURST_LEN-1:0] pf_ctrl_wmask;
layer_prefetch_ctrl #(
.DATA_WIDTH(DATA_WIDTH), .LAYER_BYTES(LAYER_BYTES), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
) u_pf (
.clk(clk), .rst(rst),
.start(pf_start), .layer_base(w_base_lat[ADDR_WIDTH-2:0]), .busy(pf_busy), .done(pf_done),
.fill_we(pf_fill_we), .fill_addr(pf_fill_addr), .fill_data(pf_fill_data),
.ctrl_req(pf_ctrl_req), .ctrl_wr(pf_ctrl_wr), .ctrl_addr(pf_ctrl_addr),
.ctrl_wdata(pf_ctrl_wdata), .ctrl_wmask(pf_ctrl_wmask),
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
);
// ---- pre-EXP-0083 baseline: direct act_tile_fetch.v, one req per
// tile, no look-ahead ----
reg act_req;
wire act_valid;
wire signed [DATA_WIDTH*P_IN-1:0] act_data_a_w, act_data_b_w;
wire act_mem_active;
wire act_ctrl_req, act_ctrl_wr;
wire [ADDR_WIDTH-2:0] act_ctrl_addr;
wire [32*BURST_LEN-1:0] act_ctrl_wdata;
wire [4*BURST_LEN-1:0] act_ctrl_wmask;
act_tile_fetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
) u_act (
.clk(clk), .rst(rst),
.req(act_req), .base_a(x_base_a_lat[ADDR_WIDTH-2:0]), .base_b(x_base_b_lat[ADDR_WIDTH-2:0]),
.tcnt(tcnt),
.valid(act_valid), .data_a(act_data_a_w), .data_b(act_data_b_w),
.mem_active(act_mem_active), .mem_grant(mem_grant),
.ctrl_req(act_ctrl_req), .ctrl_wr(act_ctrl_wr), .ctrl_addr(act_ctrl_addr),
.ctrl_wdata(act_ctrl_wdata), .ctrl_wmask(act_ctrl_wmask),
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
);
assign ctrl_req = act_mem_active ? act_ctrl_req : pf_ctrl_req;
assign ctrl_wr = act_mem_active ? act_ctrl_wr : pf_ctrl_wr;
assign ctrl_addr = act_mem_active ? act_ctrl_addr : pf_ctrl_addr;
assign ctrl_wdata = act_mem_active ? act_ctrl_wdata : pf_ctrl_wdata;
assign ctrl_wmask = act_mem_active ? act_ctrl_wmask : pf_ctrl_wmask;
assign mem_active = (state == S_MEMWAIT) || (state == S_PREFETCH) || act_mem_active;
wire [BUFADDRW-1:0] lwb_rd_addr;
wire [DATA_WIDTH-1:0] lwb_rd_data;
reg consume_done;
layer_weight_buffer #(.DATA_WIDTH(DATA_WIDTH), .LAYER_DEPTH(LAYER_BYTES)) u_lwb (
.clk(clk), .rst(rst),
.fill_we(pf_fill_we), .fill_addr(pf_fill_addr), .fill_data(pf_fill_data), .fill_done(pf_done),
.rd_addr(lwb_rd_addr), .rd_data(lwb_rd_data), .consume_done(consume_done),
.active_sel(), .swapped()
);
reg tile_req;
reg [BUFADDRW-1:0] tile_base;
reg tile_seen;
reg act_seen;
wire tile_valid;
wire [DATA_WIDTH*P_IN-1:0] tile_data;
weight_tile_gather #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BUFADDRW(BUFADDRW)
) u_gather (
.clk(clk), .rst(rst),
.tile_req(tile_req), .tile_base(tile_base),
.tile_valid(tile_valid), .tile_data(tile_data),
.rd_addr(lwb_rd_addr), .rd_data(lwb_rd_data)
);
reg job_valid_np;
wire job_ready_np;
reg [1:0] job_activation;
reg signed [DATA_WIDTH-1:0] job_bias;
reg operand_valid;
wire operand_ready;
reg signed [DATA_WIDTH*P_IN-1:0] input_data_a_r, input_data_b_r;
reg [DATA_WIDTH*P_IN-1:0] weight_data_r;
reg tile_last;
wire result_valid_np;
reg result_ready;
wire signed [DATA_WIDTH-1:0] result_data_a_np, result_data_b_np;
wire [15:0] result_node_id_a_np, result_node_id_b_np;
wire [3:0] np_state;
wire np_error;
neural_processor_packed #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH)
) u_np (
.clk(clk), .rst(rst),
.job_valid(job_valid_np), .job_ready(job_ready_np),
.job_node_id_a(node_id_a_lat), .job_node_id_b(node_id_b_lat),
.job_bias(job_bias), .job_activation(job_activation),
.operand_valid(operand_valid), .operand_ready(operand_ready),
.input_data_a(input_data_a_r), .input_data_b(input_data_b_r),
.weight_data(weight_data_r), .tile_last(tile_last),
.result_valid(result_valid_np), .result_ready(result_ready),
.result_data_a(result_data_a_np), .result_data_b(result_data_b_np),
.result_node_id_a(result_node_id_a_np), .result_node_id_b(result_node_id_b_np),
.np_state(np_state), .np_error(np_error)
);
localparam ACT_RELU = 2'd1;
always @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
job_done <= 1'b0;
pf_start <= 1'b0;
consume_done <= 1'b0;
tile_req <= 1'b0;
act_req <= 1'b0;
tile_seen <= 1'b0;
act_seen <= 1'b0;
job_valid_np <= 1'b0;
operand_valid<= 1'b0;
tile_last <= 1'b0;
result_ready <= 1'b0;
job_bias <= {DATA_WIDTH{1'b0}};
job_activation <= ACT_RELU;
tcnt <= 16'd0;
end else begin
job_done <= 1'b0;
pf_start <= 1'b0;
consume_done <= 1'b0;
tile_req <= 1'b0;
act_req <= 1'b0;
case (state)
S_IDLE: begin
if (job_start) begin
w_base_lat <= w_base;
x_base_a_lat <= x_base_a;
x_base_b_lat <= x_base_b;
n_tiles_lat <= n_tiles;
result_addr_a_lat <= result_addr_a;
result_addr_b_lat <= result_addr_b;
node_id_a_lat <= node_id_a;
node_id_b_lat <= node_id_b;
job_bias <= {DATA_WIDTH{1'b0}};
job_activation <= ACT_RELU;
state <= S_MEMWAIT;
end
end
S_MEMWAIT: begin
if (mem_grant) begin
pf_start <= 1'b1;
state <= S_PREFETCH;
end
end
S_PREFETCH: begin
if (pf_done) begin
consume_done <= 1'b1;
state <= S_SWAP;
end
end
S_SWAP: begin
job_valid_np <= 1'b1;
state <= S_JOBSTART;
end
S_JOBSTART: begin
if (job_valid_np && job_ready_np) begin
job_valid_np <= 1'b0;
tcnt <= 16'd0;
state <= S_TILEREQ;
end
end
// pre-EXP-0083 baseline: issue ONE tile request per
// iteration, wait for its own valid pulse -- no
// look-ahead overlap with the previous tile's consume.
S_TILEREQ: begin
tile_req <= 1'b1;
tile_base <= tcnt[BUFADDRW-1:0]*P_IN[BUFADDRW-1:0];
tile_seen <= 1'b0;
act_seen <= 1'b0;
act_req <= 1'b1;
state <= S_TILEWAIT;
end
// pre-EXP-0083 real join: weight_tile_gather.v's tile_valid
// and act_tile_fetch.v's act_valid are BOTH one-cycle pulses
// (act_tile_fetch.v has no level-hold the way ddr_prefetch_
// mgr.v's ddrpf_tile_valid does) -- each needs its own seen
// latch since they don't arrive on the same cycle in general.
S_TILEWAIT: begin
if (tile_valid) begin
weight_data_r <= tile_data;
tile_seen <= 1'b1;
end
if (act_valid) begin
input_data_a_r <= act_data_a_w;
input_data_b_r <= act_data_b_w;
act_seen <= 1'b1;
end
if ((tile_valid || tile_seen) && (act_valid || act_seen)) begin
tile_last <= (tcnt == n_tiles_lat - 16'd1);
operand_valid <= 1'b1;
state <= S_OPERAND;
end
end
S_OPERAND: begin
if (operand_valid && operand_ready) begin
operand_valid <= 1'b0;
tile_last <= 1'b0;
if (tcnt == n_tiles_lat - 16'd1) begin
result_ready <= 1'b1;
state <= S_RESULT;
end else begin
tcnt <= tcnt + 16'd1;
state <= S_TILEREQ;
end
end
end
S_RESULT: begin
if (result_valid_np) begin
result_data_a <= result_data_a_np;
result_data_b <= result_data_b_np;
result_node_id_a <= result_node_id_a_np;
result_node_id_b <= result_node_id_b_np;
result_addr_a_out <= result_addr_a_lat;
result_addr_b_out <= result_addr_b_lat;
result_ready <= 1'b0;
job_done <= 1'b1;
state <= S_IDLE;
end
end
default: state <= S_IDLE;
endcase
end
end
endmodule
+5 -1
View File
@@ -30,9 +30,13 @@
// 16-bit dq / 2-bit dqs / 2-bit dm slice -- confirmed by reading the // 16-bit dq / 2-bit dqs / 2-bit dm slice -- confirmed by reading the
// real regenerated sim_tb_top.v (not assumed), replicated exactly // real regenerated sim_tb_top.v (not assumed), replicated exactly
// below for DQ_WIDTH=32 (NUM_COMP=2). // below for DQ_WIDTH=32 (NUM_COMP=2).
//
// EXP-0086 UPDATE: CLKIN_PERIOD reverted 2900->3225ps, matching the
// real, current, timing-CLOSED MIG config (WNS=+0.096ns) -- 2900ps was
// the FAILED intermediate attempt (WNS=-0.618ns, EXP-0084).
// ============================================================ // ============================================================
module tb; module tb;
localparam CLKIN_PERIOD = 2900; // ps, matches this project's real MIG config (EXP-0084) localparam CLKIN_PERIOD = 3225; // ps, matches this project's real, CLOSED MIG config (EXP-0086)
localparam REFCLK_FREQ = 200.0; // MHz localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ)); localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps localparam RESET_PERIOD = 200000; // ps
+31 -12
View File
@@ -27,18 +27,24 @@
// variant), real ddr3_model.sv, real WireDelay pass-through -- same // variant), real ddr3_model.sv, real WireDelay pass-through -- same
// proven instantiation pattern as tb_mig_native_adapter.v. // proven instantiation pattern as tb_mig_native_adapter.v.
// //
// EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- CLKIN_PERIOD // EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- dq/dqs/dm pin
// 2900ps, dq/dqs/dm pin widths doubled (two MT41J128M16 chips ganged // widths doubled (two MT41J128M16 chips ganged in parallel), TWO real
// in parallel), TWO real ddr3_model.sv components instantiated (one // ddr3_model.sv components instantiated (one per chip, exact real
// per chip, exact real pattern confirmed against the real regenerated // pattern confirmed against the real regenerated sim_tb_top.v), both
// sim_tb_top.v), both sys_clk and clk_ref are now real differential // sys_clk and clk_ref are now real differential pairs on the inner
// pairs on the inner mig_7series_0_mig module (the user's own wizard // mig_7series_0_mig module (the user's own wizard choice), and the
// choice), and the weight/activation preload tasks rewritten for the // weight/activation preload tasks rewritten for the new
// new BYTES_PER_BURST=4*BURST_LEN / 4-tiles-per-burst real layouts // BYTES_PER_BURST=4*BURST_LEN / 4-tiles-per-burst real layouts (same
// (same rewrite already verified in tb_packed_slot.v). // rewrite already verified in tb_packed_slot.v).
//
// EXP-0086 UPDATE: CLKIN_PERIOD reverted 2900->3225ps, matching the
// real, current, timing-CLOSED MIG config (WNS=+0.096ns, EXP-0086) --
// the 2900ps value was the FAILED intermediate attempt (WNS=-0.618ns,
// EXP-0084) and must not be simulated as if it were the real, current
// hardware.
// ============================================================ // ============================================================
module tb; module tb;
localparam CLKIN_PERIOD = 2900; // ps, this project's real MIG config (EXP-0084) localparam CLKIN_PERIOD = 3225; // ps, this project's real, CLOSED MIG config (EXP-0086)
localparam REFCLK_FREQ = 200.0; // MHz localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ)); localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps localparam RESET_PERIOD = 200000; // ps
@@ -228,11 +234,24 @@ module tb;
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid) .app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid)
); );
// EXP-0086 fix: the original `8'(expr)` SystemVerilog sized-cast
// syntax was silently never valid plain Verilog (CLAUDE.md's own
// "no SV-only syntax in a plain .v file" lesson) -- xvlog in default
// (non `-sv`) mode rejects it outright. An intermediate 8-bit reg
// does the same width-truncation-before-$signed() job portably.
function automatic signed [7:0] weight_byte(input integer li, input integer t); function automatic signed [7:0] weight_byte(input integer li, input integer t);
weight_byte = $signed(8'((li*17 + t*29 + 13) & 8'hFF)); reg [7:0] tmp;
begin
tmp = (li*17 + t*29 + 13) & 8'hFF;
weight_byte = $signed(tmp);
end
endfunction endfunction
function automatic signed [7:0] input_byte(input integer li, input integer pos, input integer t); function automatic signed [7:0] input_byte(input integer li, input integer pos, input integer t);
input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF)); reg [7:0] tmp;
begin
tmp = (li*11 + pos*41 + t*7 + 3) & 8'hFF;
input_byte = $signed(tmp);
end
endfunction endfunction
task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data); task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
@@ -0,0 +1,521 @@
`timescale 1ps/100fs
// ============================================================
// MILESTONE: the full N=2 multi-core system (EXP-0066/0067, real
// neural_director_packed.v + 2 real packed_slot.v instances + real
// sdram_arbiter_n.v) running against REAL DDR3 (mig_native_adapter.v,
// EXP-0068, verified against MIG's own ddr3_model.sv) instead of the
// SDR SDRAM placeholder used everywhere until now.
//
// Runs entirely in the ui_clk domain (MIG's own generated clock is
// now this whole system's clock, per mig_native_adapter.v's own
// documented convention). Everything downstream of the memory
// backend (Director, packed_slot, weight-reuse path, packed core) is
// UNCHANGED, byte-for-byte, from EXP-0066/0067 -- only the physical
// memory backend is swapped, isolating that as the one variable
// under test.
//
// EXP-0079 UPDATE: activations are now fetched via a REAL act_tile_
// fetch.v inside each packed_slot.v instance (real DDR3 reads, same
// physical bus each slot already uses for weights) -- no more stand-
// in. This test now preloads real activation data into the SAME real
// DDR3 model too (preload_ddr3_activations), on top of the weight
// preload that was already here.
//
// Uses mig_7series_0_mig_sim (SIM_BYPASS_INIT_CAL="FAST" default,
// EXP-0068's own real vendor-shipped fast-calibration simulation
// variant), real ddr3_model.sv, real WireDelay pass-through -- same
// proven instantiation pattern as tb_mig_native_adapter.v.
//
// EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- dq/dqs/dm pin
// widths doubled (two MT41J128M16 chips ganged in parallel), TWO real
// ddr3_model.sv components instantiated (one per chip, exact real
// pattern confirmed against the real regenerated sim_tb_top.v), both
// sys_clk and clk_ref are now real differential pairs on the inner
// mig_7series_0_mig module (the user's own wizard choice), and the
// weight/activation preload tasks rewritten for the new
// BYTES_PER_BURST=4*BURST_LEN / 4-tiles-per-burst real layouts (same
// rewrite already verified in tb_packed_slot.v).
//
// EXP-0086 UPDATE: CLKIN_PERIOD reverted 2900->3225ps, matching the
// real, current, timing-CLOSED MIG config (WNS=+0.096ns, EXP-0086) --
// the 2900ps value was the FAILED intermediate attempt (WNS=-0.618ns,
// EXP-0084) and must not be simulated as if it were the real, current
// hardware.
// ============================================================
module tb;
localparam CLKIN_PERIOD = 3225; // ps, this project's real, CLOSED MIG config (EXP-0086)
localparam REFCLK_FREQ = 200.0; // MHz
localparam real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ));
localparam RESET_PERIOD = 200000; // ps
localparam DATA_WIDTH = 8;
localparam P_IN = 8;
localparam ACC_WIDTH = 32;
localparam ADDR_WIDTH = 26; // this project's byte-address convention (Director/packed_slot)
localparam MIG_ADDR_WIDTH = 25; // word-address convention (BURST_LEN=8) at the arbiter/adapter
localparam BURST_LEN = 8;
localparam N_INPUTS = 128;
localparam N_TILES = N_INPUTS/P_IN;
localparam LAYER_BYTES = N_INPUTS;
localparam WORDS_PER_LAYER = LAYER_BYTES/2;
localparam N_SLOTS = 2;
localparam QUEUE_DEPTH = 8;
localparam L = 2; // layers (kept small -- real DDR3 calibration + JEDEC timing already
localparam M = 4; // costs real simulated time; this is an integration check, not a
// repeat of EXP-0066's own fuller correctness sweep)
// ---- clock/reset (mirrors tb_mig_native_adapter.v's own proven pattern) ----
reg sys_rst_n;
wire sys_rst = sys_rst_n;
reg sys_clk_i = 1'b0;
always #(CLKIN_PERIOD/2.0) sys_clk_i = ~sys_clk_i;
wire sys_clk_p = sys_clk_i;
wire sys_clk_n = ~sys_clk_i;
reg clk_ref_i = 1'b0;
always #REFCLK_PERIOD clk_ref_i = ~clk_ref_i;
wire clk_ref_p = clk_ref_i;
wire clk_ref_n = ~clk_ref_i;
initial begin
sys_rst_n = 1'b0;
#RESET_PERIOD sys_rst_n = 1'b1;
end
// ---- real DDR3 pins + model (identical to tb_mig_native_adapter.v) ----
wire ddr3_reset_n;
wire [31:0] ddr3_dq_fpga;
wire [3:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga;
wire [13:0] ddr3_addr_fpga;
wire [2:0] ddr3_ba_fpga;
wire ddr3_ras_n_fpga, ddr3_cas_n_fpga, ddr3_we_n_fpga;
wire [0:0] ddr3_cke_fpga, ddr3_ck_p_fpga, ddr3_ck_n_fpga, ddr3_cs_n_fpga;
wire [3:0] ddr3_dm_fpga;
wire [0:0] ddr3_odt_fpga;
wire [31:0] ddr3_dq_sdram;
reg [13:0] ddr3_addr_sdram;
reg [2:0] ddr3_ba_sdram;
reg ddr3_ras_n_sdram, ddr3_cas_n_sdram, ddr3_we_n_sdram;
wire [0:0] ddr3_cs_n_sdram;
wire [0:0] ddr3_odt_sdram;
reg [0:0] ddr3_cke_sdram;
wire [3:0] ddr3_dm_sdram;
wire [3:0] ddr3_dqs_p_sdram, ddr3_dqs_n_sdram;
reg [0:0] ddr3_ck_p_sdram, ddr3_ck_n_sdram;
reg [0:0] ddr3_cs_n_sdram_tmp;
reg [3:0] ddr3_dm_sdram_tmp;
reg [0:0] ddr3_odt_sdram_tmp;
always @(*) begin
ddr3_ck_p_sdram <= ddr3_ck_p_fpga;
ddr3_ck_n_sdram <= ddr3_ck_n_fpga;
ddr3_addr_sdram <= ddr3_addr_fpga;
ddr3_ba_sdram <= ddr3_ba_fpga;
ddr3_ras_n_sdram <= ddr3_ras_n_fpga;
ddr3_cas_n_sdram <= ddr3_cas_n_fpga;
ddr3_we_n_sdram <= ddr3_we_n_fpga;
ddr3_cke_sdram <= ddr3_cke_fpga;
end
always @(*) ddr3_cs_n_sdram_tmp <= ddr3_cs_n_fpga;
assign ddr3_cs_n_sdram = ddr3_cs_n_sdram_tmp;
always @(*) ddr3_dm_sdram_tmp <= ddr3_dm_fpga;
assign ddr3_dm_sdram = ddr3_dm_sdram_tmp;
always @(*) ddr3_odt_sdram_tmp <= ddr3_odt_fpga;
assign ddr3_odt_sdram = ddr3_odt_sdram_tmp;
genvar dqwd;
generate
for (dqwd = 0; dqwd < 32; dqwd = dqwd + 1) begin : dq_delay
WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dq (
.A(ddr3_dq_fpga[dqwd]), .B(ddr3_dq_sdram[dqwd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete)
);
end
endgenerate
genvar dqswd;
generate
for (dqswd = 0; dqswd < 4; dqswd = dqswd + 1) begin : dqs_delay
WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dqs_p (
.A(ddr3_dqs_p_fpga[dqswd]), .B(ddr3_dqs_p_sdram[dqswd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete)
);
WireDelay #(.Delay_g(0.00), .Delay_rd(0.00), .ERR_INSERT("OFF")) u_delay_dqs_n (
.A(ddr3_dqs_n_fpga[dqswd]), .B(ddr3_dqs_n_sdram[dqswd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete)
);
end
endgenerate
genvar ci;
generate
for (ci = 0; ci < 2; ci = ci + 1) begin : gen_mem
ddr3_model u_comp_ddr3 (
.rst_n(ddr3_reset_n), .ck(ddr3_ck_p_sdram), .ck_n(ddr3_ck_n_sdram),
.cke(ddr3_cke_sdram[0]), .cs_n(ddr3_cs_n_sdram[0]),
.ras_n(ddr3_ras_n_sdram), .cas_n(ddr3_cas_n_sdram), .we_n(ddr3_we_n_sdram),
.dm_tdqs(ddr3_dm_sdram[2*ci +: 2]), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram),
.dq(ddr3_dq_sdram[16*ci +: 16]),
.dqs(ddr3_dqs_p_sdram[2*ci +: 2]), .dqs_n(ddr3_dqs_n_sdram[2*ci +: 2]),
.tdqs_n(), .odt(ddr3_odt_sdram[0])
);
end
endgenerate
wire [27:0] app_addr;
wire [2:0] app_cmd;
wire app_en, app_rdy;
wire [127:0] app_wdf_data;
wire app_wdf_end;
wire [15:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy;
wire [127:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst, init_calib_complete;
mig_7series_0_mig #(
.SIM_BYPASS_INIT_CAL("FAST")
) u_mig (
.ddr3_dq(ddr3_dq_fpga), .ddr3_dqs_n(ddr3_dqs_n_fpga), .ddr3_dqs_p(ddr3_dqs_p_fpga),
.ddr3_addr(ddr3_addr_fpga), .ddr3_ba(ddr3_ba_fpga),
.ddr3_ras_n(ddr3_ras_n_fpga), .ddr3_cas_n(ddr3_cas_n_fpga), .ddr3_we_n(ddr3_we_n_fpga),
.ddr3_reset_n(ddr3_reset_n),
.ddr3_ck_p(ddr3_ck_p_fpga), .ddr3_ck_n(ddr3_ck_n_fpga),
.ddr3_cke(ddr3_cke_fpga), .ddr3_cs_n(ddr3_cs_n_fpga),
.ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga),
.sys_clk_p(sys_clk_p), .sys_clk_n(sys_clk_n), .clk_ref_p(clk_ref_p), .clk_ref_n(clk_ref_n),
.app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en),
.app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren),
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end),
.app_rd_data_valid(app_rd_data_valid), .app_rdy(app_rdy), .app_wdf_rdy(app_wdf_rdy),
.app_sr_req(1'b0), .app_ref_req(1'b0), .app_zq_req(1'b0),
.app_sr_active(), .app_ref_ack(), .app_zq_ack(),
.ui_clk(ui_clk), .ui_clk_sync_rst(ui_clk_sync_rst),
.init_calib_complete(init_calib_complete),
.device_temp(),
.sys_rst(sys_rst)
);
// ---- preload path: direct access to mig_native_adapter.v,
// bypassing the arbiter, exactly like every prior testbench's own
// "pre_active" mux (EXP-0057 onward) -- used only before job
// submission begins. ----
reg pre_active;
reg pre_req, pre_wr;
reg [MIG_ADDR_WIDTH-1:0] pre_addr;
reg [32*BURST_LEN-1:0] pre_wdata;
wire adp_req, adp_wr;
wire [MIG_ADDR_WIDTH-1:0] adp_addr;
wire [32*BURST_LEN-1:0] adp_wdata;
wire [4*BURST_LEN-1:0] adp_wmask;
wire [32*BURST_LEN-1:0] adp_rdata;
wire adp_ready, adp_busy;
wire arb_ctrl_req_o, arb_ctrl_wr_o;
wire [MIG_ADDR_WIDTH-1:0] arb_ctrl_addr_o;
wire [32*BURST_LEN-1:0] arb_ctrl_wdata_o;
wire [4*BURST_LEN-1:0] arb_ctrl_wmask_o;
assign adp_req = pre_active ? pre_req : arb_ctrl_req_o;
assign adp_wr = pre_active ? pre_wr : arb_ctrl_wr_o;
assign adp_addr = pre_active ? pre_addr : arb_ctrl_addr_o;
assign adp_wdata = pre_active ? pre_wdata : arb_ctrl_wdata_o;
assign adp_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : arb_ctrl_wmask_o;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MIG_ADDR_WIDTH)) u_adapter (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.req(adp_req), .wr(adp_wr), .addr(adp_addr), .wdata(adp_wdata), .wmask(adp_wmask),
.rdata(adp_rdata), .ready(adp_ready), .busy(adp_busy),
.app_addr(app_addr), .app_cmd(app_cmd), .app_en(app_en), .app_rdy(app_rdy),
.app_wdf_data(app_wdf_data), .app_wdf_end(app_wdf_end), .app_wdf_mask(app_wdf_mask),
.app_wdf_wren(app_wdf_wren), .app_wdf_rdy(app_wdf_rdy),
.app_rd_data(app_rd_data), .app_rd_data_end(app_rd_data_end), .app_rd_data_valid(app_rd_data_valid)
);
// EXP-0086 fix: the original `8'(expr)` SystemVerilog sized-cast
// syntax was silently never valid plain Verilog (CLAUDE.md's own
// "no SV-only syntax in a plain .v file" lesson) -- xvlog in default
// (non `-sv`) mode rejects it outright. An intermediate 8-bit reg
// does the same width-truncation-before-$signed() job portably.
function automatic signed [7:0] weight_byte(input integer li, input integer t);
reg [7:0] tmp;
begin
tmp = (li*17 + t*29 + 13) & 8'hFF;
weight_byte = $signed(tmp);
end
endfunction
function automatic signed [7:0] input_byte(input integer li, input integer pos, input integer t);
reg [7:0] tmp;
begin
tmp = (li*11 + pos*41 + t*7 + 3) & 8'hFF;
input_byte = $signed(tmp);
end
endfunction
task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin
@(posedge ui_clk); while (adp_busy) @(posedge ui_clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@(posedge ui_clk); pre_req = 1'b0;
while (!adp_ready) @(posedge ui_clk);
end
endtask
// EXP-0084: BYTES_PER_BURST = 4*BURST_LEN (32 bytes/burst, up from
// 16) -- 4 consecutive weight bytes pack into each 32-bit word now.
task automatic preload_sdram_layers;
integer li, bi, wb, tt;
reg [32*BURST_LEN-1:0] burst_data;
begin
for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < (LAYER_BYTES/(4*BURST_LEN)); bi = bi + 1) begin
for (wb = 0; wb < BURST_LEN; wb = wb + 1) begin
tt = bi*(4*BURST_LEN) + wb*4;
burst_data[wb*32 +: 32] = {weight_byte(li, tt+3), weight_byte(li, tt+2),
weight_byte(li, tt+1), weight_byte(li, tt)};
end
sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data);
end
end
end
endtask
// ---- real activation preload (EXP-0084 layout: FOUR consecutive
// tiles share one BURST_LEN=8-word (256-bit) burst -- tile parity
// 0/1/2/3 -> quarters [63:0]/[127:64]/[191:128]/[255:192], see
// act_tile_fetch.v's own header). ----
localparam [MIG_ADDR_WIDTH-1:0] ACT_MEM_BASE = 25'h10000;
function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos);
act_x_base = {{(ADDR_WIDTH-MIG_ADDR_WIDTH){1'b0}}, ACT_MEM_BASE} + (li*M + pos) * ((N_TILES/4)*BURST_LEN);
endfunction
task automatic preload_ddr3_activations;
integer li, pos, tq, qi;
reg [32*BURST_LEN-1:0] burst_data;
reg [ADDR_WIDTH-1:0] base;
begin
for (li = 0; li < L; li = li + 1) begin
for (pos = 0; pos < M; pos = pos + 1) begin
base = act_x_base(li, pos);
for (tq = 0; tq < N_TILES/4; tq = tq + 1) begin
burst_data = {(32*BURST_LEN){1'b0}};
for (qi = 0; qi < 4; qi = qi + 1)
burst_data[qi*64 +: 64] = {input_byte(li, pos, (4*tq+qi)*P_IN + 7), input_byte(li, pos, (4*tq+qi)*P_IN + 6),
input_byte(li, pos, (4*tq+qi)*P_IN + 5), input_byte(li, pos, (4*tq+qi)*P_IN + 4),
input_byte(li, pos, (4*tq+qi)*P_IN + 3), input_byte(li, pos, (4*tq+qi)*P_IN + 2),
input_byte(li, pos, (4*tq+qi)*P_IN + 1), input_byte(li, pos, (4*tq+qi)*P_IN + 0)};
sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + tq*BURST_LEN, burst_data);
end
end
end
end
endtask
// ---- neural_director_packed.v ----
reg job_in_valid;
wire job_in_ready;
reg [ADDR_WIDTH-1:0] job_in_x_base, job_in_w_base, job_in_result_addr;
reg [15:0] job_in_n_tiles, job_in_node_id;
wire [N_SLOTS-1:0] slot_job_start;
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_x_base_a, slot_x_base_b, slot_w_base;
wire [ADDR_WIDTH*N_SLOTS-1:0] slot_result_addr_a, slot_result_addr_b;
wire [16*N_SLOTS-1:0] slot_n_tiles, slot_node_id_a, slot_node_id_b;
wire [N_SLOTS-1:0] slot_job_done;
wire job_out_done;
wire [$clog2(N_SLOTS)-1:0] job_out_slot;
wire [3:0] dir_state;
wire dir_error;
neural_director_packed #(
.ADDR_WIDTH(ADDR_WIDTH), .N_SLOTS(N_SLOTS), .QUEUE_DEPTH(QUEUE_DEPTH)
) u_dir (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.job_in_valid(job_in_valid), .job_in_ready(job_in_ready),
.job_in_x_base(job_in_x_base), .job_in_w_base(job_in_w_base),
.job_in_n_tiles(job_in_n_tiles), .job_in_result_addr(job_in_result_addr),
.job_in_node_id(job_in_node_id),
.slot_job_start(slot_job_start),
.slot_x_base_a(slot_x_base_a), .slot_x_base_b(slot_x_base_b),
.slot_w_base(slot_w_base), .slot_n_tiles(slot_n_tiles),
.slot_result_addr_a(slot_result_addr_a), .slot_result_addr_b(slot_result_addr_b),
.slot_node_id_a(slot_node_id_a), .slot_node_id_b(slot_node_id_b),
.slot_job_done(slot_job_done),
.job_out_done(job_out_done), .job_out_slot(job_out_slot),
.dir_state(dir_state), .dir_error(dir_error)
);
// ---- 2 real packed_slot.v instances + real N-way arbiter (NUM_REQ=2) ----
wire [1:0] mem_active, mem_grant;
wire [1:0] s_ctrl_req, s_ctrl_wr;
wire [1:0] s_ctrl_ready, s_ctrl_busy;
wire [MIG_ADDR_WIDTH*2-1:0] s_ctrl_addr_flat;
wire [32*BURST_LEN*2-1:0] s_ctrl_wdata_flat, s_ctrl_rdata_flat;
wire [4*BURST_LEN*2-1:0] s_ctrl_wmask_flat;
sdram_arbiter_n #(.NUM_REQ(2), .ADDR_WIDTH(MIG_ADDR_WIDTH), .BURST_LEN(BURST_LEN)) u_arb (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.req_active(mem_active), .req_grant(mem_grant),
.req_req(s_ctrl_req), .req_wr(s_ctrl_wr), .req_addr(s_ctrl_addr_flat),
.req_wdata(s_ctrl_wdata_flat), .req_wmask(s_ctrl_wmask_flat),
.req_rdata(s_ctrl_rdata_flat), .req_ready(s_ctrl_ready), .req_busy(s_ctrl_busy),
.ctrl_req(arb_ctrl_req_o), .ctrl_wr(arb_ctrl_wr_o), .ctrl_addr(arb_ctrl_addr_o),
.ctrl_wdata(arb_ctrl_wdata_o), .ctrl_wmask(arb_ctrl_wmask_o),
.ctrl_rdata(adp_rdata), .ctrl_ready(adp_ready), .ctrl_busy(adp_busy)
);
genvar gi;
generate
for (gi = 0; gi < N_SLOTS; gi = gi + 1) begin : GEN_SLOT
wire signed [DATA_WIDTH-1:0] res_a, res_b;
wire [15:0] res_nid_a, res_nid_b;
wire [ADDR_WIDTH-1:0] res_addr_a_out, res_addr_b_out;
packed_slot_noprefetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH),
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH), .LAYER_BYTES(LAYER_BYTES)
) u_slot (
.clk(ui_clk), .rst(ui_clk_sync_rst),
.job_start(slot_job_start[gi]),
.x_base_a(slot_x_base_a[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.x_base_b(slot_x_base_b[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.w_base(slot_w_base[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.n_tiles(slot_n_tiles[gi*16 +: 16]),
.result_addr_a(slot_result_addr_a[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.result_addr_b(slot_result_addr_b[gi*ADDR_WIDTH +: ADDR_WIDTH]),
.node_id_a(slot_node_id_a[gi*16 +: 16]), .node_id_b(slot_node_id_b[gi*16 +: 16]),
.job_done(slot_job_done[gi]),
.result_data_a(res_a), .result_data_b(res_b),
.result_node_id_a(res_nid_a), .result_node_id_b(res_nid_b),
.result_addr_a_out(res_addr_a_out), .result_addr_b_out(res_addr_b_out),
.mem_active(mem_active[gi]), .mem_grant(mem_grant[gi]),
.ctrl_req(s_ctrl_req[gi]), .ctrl_wr(s_ctrl_wr[gi]),
.ctrl_addr(s_ctrl_addr_flat[gi*MIG_ADDR_WIDTH +: MIG_ADDR_WIDTH]),
.ctrl_wdata(s_ctrl_wdata_flat[gi*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(s_ctrl_wmask_flat[gi*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(s_ctrl_rdata_flat[gi*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(s_ctrl_ready[gi]), .ctrl_busy(s_ctrl_busy[gi])
);
end
endgenerate
integer errors, tests, completions, n_expected, si;
reg [15:0] expect_node [0:31];
reg signed [7:0] expect_val [0:31];
function automatic signed [7:0] golden_result(input integer li, input integer pos);
integer t, acc;
reg signed [7:0] r;
begin
acc = 0;
for (t = 0; t < N_INPUTS; t = t + 1)
acc = acc + (input_byte(li, pos, t) * weight_byte(li, t));
if (acc <= 0) r = 0; else if (acc > 127) r = 8'sd127; else r = acc[7:0];
golden_result = r;
end
endfunction
task automatic check_completion(input integer slot, input [15:0] nid, input signed [7:0] val);
integer idx, found;
begin
found = 0;
for (idx = 0; idx < n_expected; idx = idx + 1) begin
if (expect_node[idx] === nid && !found) begin
found = 1;
tests = tests + 1;
if (expect_val[idx] !== val) begin
$display("FAIL slot=%0d node_id=%0d: got=%0d expected=%0d", slot, nid, $signed(val), $signed(expect_val[idx]));
errors = errors + 1;
end else begin
$display("PASS slot=%0d node_id=%0d: result=%0d", slot, nid, $signed(val));
end
end
end
end
endtask
always @(posedge ui_clk) begin
if (!ui_clk_sync_rst) begin
for (si = 0; si < N_SLOTS; si = si + 1) begin
if (slot_job_done[si]) begin
completions = completions + 2;
case (si)
0: begin
check_completion(0, GEN_SLOT[0].u_slot.result_node_id_a, GEN_SLOT[0].u_slot.result_data_a);
check_completion(0, GEN_SLOT[0].u_slot.result_node_id_b, GEN_SLOT[0].u_slot.result_data_b);
end
1: begin
check_completion(1, GEN_SLOT[1].u_slot.result_node_id_a, GEN_SLOT[1].u_slot.result_data_a);
check_completion(1, GEN_SLOT[1].u_slot.result_node_id_b, GEN_SLOT[1].u_slot.result_data_b);
end
endcase
end
end
end
end
task automatic submit_job(
input [ADDR_WIDTH-1:0] xb, input [ADDR_WIDTH-1:0] wb,
input [15:0] nt, input [ADDR_WIDTH-1:0] resaddr, input [15:0] nid
);
begin
@(posedge ui_clk);
job_in_x_base = xb; job_in_w_base = wb; job_in_n_tiles = nt;
job_in_result_addr = resaddr; job_in_node_id = nid;
job_in_valid = 1'b1;
while (!job_in_ready) @(posedge ui_clk);
@(posedge ui_clk);
job_in_valid = 1'b0;
end
endtask
integer li_i, pp_i, wd;
initial begin
errors = 0; tests = 0; completions = 0; n_expected = 0;
pre_active = 1'b1; pre_req = 0; pre_wr = 0; pre_addr = 0; pre_wdata = 0;
job_in_valid = 0; job_in_x_base = 0; job_in_w_base = 0;
job_in_n_tiles = 0; job_in_result_addr = 0; job_in_node_id = 0;
$display("=== waiting for real DDR3 init_calib_complete ===");
wait (init_calib_complete);
$display("=== calibration done at time %0t ===", $time);
repeat (10) @(posedge ui_clk);
$display("=== preload SDRAM with %0d resident-filter weight sets ===", L);
preload_sdram_layers;
$display("=== preload SDRAM with real activation data (EXP-0079) ===");
preload_ddr3_activations;
@(posedge ui_clk);
pre_active = 1'b0;
repeat (5) @(posedge ui_clk);
$display("=== N=2 system on REAL DDR3: submitting %0d layers x %0d positions ===", L, M);
for (li_i = 0; li_i < L; li_i = li_i + 1) begin
for (pp_i = 0; pp_i < M; pp_i = pp_i + 1) begin
submit_job(act_x_base(li_i, pp_i), li_i*WORDS_PER_LAYER, N_TILES[15:0],
26'h9000 + li_i*10 + pp_i, (li_i*M + pp_i));
expect_node[n_expected] = (li_i*M + pp_i);
expect_val[n_expected] = golden_result(li_i, pp_i);
n_expected = n_expected + 1;
end
end
wd = 0;
while (completions < n_expected && wd < 200000) begin
@(posedge ui_clk);
wd = wd + 1;
end
if (completions < n_expected) begin
$display("FAIL: only %0d/%0d position-results completed within watchdog", completions, n_expected);
errors = errors + 1;
end
$display("=== %0d/%0d tests, %0d errors, %0d/%0d positions completed ===", tests-errors, tests, errors, completions, n_expected);
if (errors == 0 && completions == n_expected) $display("ALL TESTS PASSED (tb_n2_system_ddr3, REAL DDR3)");
$finish;
end
endmodule