diff --git a/docs/ARCHITECTURE_ANALYSIS.md b/docs/ARCHITECTURE_ANALYSIS.md index bdd79d3..d8be91b 100644 --- a/docs/ARCHITECTURE_ANALYSIS.md +++ b/docs/ARCHITECTURE_ANALYSIS.md @@ -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 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 -is **DONE and real-measured** (EXP-0083, a genuinely modest ~2.9% real -benefit, measured against the OLD 16-bit signoff — re-measurement against -the now-closed 32-bit channel is a real next step); §5.4 (32-bit widening) +is **DONE, built, and RE-MEASURED against the closed 32-bit channel** +(EXP-0083's original modest ~2.9% real benefit was measured only at the +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 functional + EXP-0086 timing closure; honest intermediate story: the first 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 | | 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 | -| 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 | 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 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 -intact. The DDRManager (§5.2) remains complementary — hides latency, -doesn't raise the physical ceiling — and its own real benefit should now be -re-measured against this closed, wider channel (not yet done). +intact. The DDRManager (§5.2) was re-measured against this now-closed, +wider channel (EXP-0087): its real benefit is gone (~0%, a 0.0064% +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`, 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 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 earlier). -**Real, measured result** (`ddr_prefetch_mgr.v`, real P&R WNS +0.073ns, up -from EXP-0082's +0.068ns, LUTs 5644, DSP48E1 16 unchanged): +**Real, measured result at the OLD 16-bit channel** (`ddr_prefetch_mgr.v`, +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** (`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 - simulated time** (108370.88ns → 105268.43ns). This is the trustworthy - headline number. + simulated time** (108370.88ns → 105268.43ns) — EXP-0083's own headline + 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 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 — @@ -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 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): §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 @@ -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 own EXP-0081 layout — confirmed timing-safe by real P&R, not asserted. -**Full multi-slot / whole-Director-queue scheduler — still NOT built, -and now a more deliberate call, not just deferred**: given phase 1's real -measured benefit was modest, the cost/benefit case for the larger design -below should be re-examined against the 32-bit-widened channel's real -numbers (§5.4) before committing more engineering time to it — building it -now, on the still-16-bit channel, risks the same gap between hypothesis and -measurement this phase-1 experiment just corrected. +**Full multi-slot / whole-Director-queue scheduler — still NOT built, and +now real-disconfirmed, not just deferred**: the re-examination against the +32-bit channel's real numbers (EXP-0087, above) has now happened, and the +result is that phase 1's own benefit is gone entirely at this core count +— there is no real cost/benefit case left for the larger design below +until a NEW real bottleneck reintroduces DDR3-latency-bound behavior +(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 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 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 - **DONE** (EXP-0083, real but modest ~2.9% benefit, measured on the OLD - 16-bit channel). **Next real step**: re-measure this SAME real A/B - against the now-closed 32-bit channel (EXP-0086), since a wider channel - may change how much idle-channel time there is left to fill — only - build the larger multi-slot scheduler version if that re-measurement - justifies it, not on the original (now-corrected) hypothesis alone. Not - yet done. + **DONE and RE-MEASURED** (EXP-0083's original ~2.9% benefit was + measured on the OLD 16-bit channel; EXP-0087 re-measured the same real + A/B against the now-closed 32-bit channel and found the benefit is + gone, ~0%/a 0.0064% regression — the wider channel's lower per-tile + latency already absorbed the gap the look-ahead used to hide). Kept + wired in for correctness/timing-neutrality; the larger multi-slot + 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 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 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) | | 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 | -| 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. | -| 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 | +| 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, 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. | diff --git a/docs/BOM.md b/docs/BOM.md new file mode 100644 index 0000000..a3b8cf7 --- /dev/null +++ b/docs/BOM.md @@ -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** (220–625 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, ~1–2 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 1–3, 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. diff --git a/docs/PHYSICAL_REALIZATION.md b/docs/PHYSICAL_REALIZATION.md index f657c50..0264cbf 100644 --- a/docs/PHYSICAL_REALIZATION.md +++ b/docs/PHYSICAL_REALIZATION.md @@ -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 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 - timing-verified, but was only measured for benefit at the OLD 16-bit/ - 155MHz signoff (2.86% real reduction in total simulated time, modest — - see `docs/ARCHITECTURE_ANALYSIS.md` §5.2). The larger multi-slot - DDRManager is not built. Its own real benefit should now be re-measured - against the closed 32-bit channel (EXP-0086) — not yet done. + timing-verified. Its real performance benefit was 2.86% at the OLD + 16-bit/155MHz signoff, but a real re-measurement against the closed + 32-bit channel (EXP-0087) found the benefit is now GONE (~0%, a + 0.0064% regression, statistically a wash) — see + `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, 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 diff --git a/docs/PINOUT.md b/docs/PINOUT.md new file mode 100644 index 0000000..58f4deb --- /dev/null +++ b/docs/PINOUT.md @@ -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`. diff --git a/hardware/v2/logs/experiments.log b/hardware/v2/logs/experiments.log index 59cdc1f..518263e 100644 --- a/hardware/v2/logs/experiments.log +++ b/hardware/v2/logs/experiments.log @@ -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 user's own standing directive ("senza illusioni ma analizzando la 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. diff --git a/hardware/v3/sim/packed_slot_noprefetch.v b/hardware/v3/sim/packed_slot_noprefetch.v new file mode 100644 index 0000000..a416739 --- /dev/null +++ b/hardware/v3/sim/packed_slot_noprefetch.v @@ -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 diff --git a/hardware/v3/sim/tb_mig_native_adapter.v b/hardware/v3/sim/tb_mig_native_adapter.v index 5c7fcca..524ef1c 100644 --- a/hardware/v3/sim/tb_mig_native_adapter.v +++ b/hardware/v3/sim/tb_mig_native_adapter.v @@ -30,9 +30,13 @@ // 16-bit dq / 2-bit dqs / 2-bit dm slice -- confirmed by reading the // real regenerated sim_tb_top.v (not assumed), replicated exactly // 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; - 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 real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ)); localparam RESET_PERIOD = 200000; // ps diff --git a/hardware/v3/sim/tb_n2_system_ddr3.v b/hardware/v3/sim/tb_n2_system_ddr3.v index bfbc224..4f1144a 100644 --- a/hardware/v3/sim/tb_n2_system_ddr3.v +++ b/hardware/v3/sim/tb_n2_system_ddr3.v @@ -27,18 +27,24 @@ // 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 -- CLKIN_PERIOD -// 2900ps, 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-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 = 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 real REFCLK_PERIOD = (1000000.0/(2*REFCLK_FREQ)); 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) ); + // 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); - 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 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 task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data); diff --git a/hardware/v3/sim/tb_n2_system_ddr3_noprefetch.v b/hardware/v3/sim/tb_n2_system_ddr3_noprefetch.v new file mode 100644 index 0000000..fe804de --- /dev/null +++ b/hardware/v3/sim/tb_n2_system_ddr3_noprefetch.v @@ -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