feat: real 32-bit DDR3 channel widening - functionally complete, timing NOT yet closed (EXP-0084)

Real 32-bit DDR3 widening (2x MT41J128M16JT-125:K chips ganged in
parallel, user's own MIG wizard session). Full RTL adaptation across
the shared ctrl bus (16-bit word -> 32-bit word, BURST_LEN=8 unchanged,
burst payload 128->256 bits):

- mig_native_adapter.v: app_wdf_data/app_rd_data 64->128 bits (real,
  confirmed against the regenerated MIG wrapper), beat count unchanged.
- act_tile_fetch.v: real logic change - burst now holds 4 tiles instead
  of 2 (sel_lat extended to 2 registered bits, 4-way case mux instead
  of 2-way ternary, same request-time-registered-select discipline as
  EXP-0081). Not a further bytes/MAC reduction, just what's needed to
  keep 100% packing utilization at the larger burst.
- host_mem_bridge.v: real addressing redesign - host-facing 16-bit-word
  contract kept unchanged (ESP32 firmware unaffected), internally
  translated onto the new 32-bit-native ctrl bus.
- sdram_arbiter_n.v, layer_prefetch_ctrl.v, packed_slot.v,
  ddr_prefetch_mgr.v, n2_system_ddr3_top.v: mechanical width bump plus
  doubled ddr3_dq/dqs/dm pins and the real differential sys_clk/clk_ref
  top-level ports the regenerated MIG now requires.

New burst_mem_model32.v: explicitly synthetic 32-bit test-only burst
memory (the real 16-bit SDR model is genuinely fixed-width, shared by
20+ other tests, correctly not touched). Found and fixed a real
address-aliasing bug in it during bring-up (MEM_ADDR_BITS=16 silently
wrapped a real 0x10000 test address to 0).

Real verification: all isolated testbenches re-verified (10/10, 33/33,
32/32, 7/7, 9/9 PASS), plus real xsim against the real 2-chip DDR3
model (tb_mig_native_adapter.v 12/12 PASS, tb_n2_system_ddr3.v 8/8
PASS, both chips visibly returning different real data).

Real P&R: 5 real bugs found and fixed across iterations (stale
single-ended MIG clock ports, a real VCCO conflict between the flash
SPI bus and the differential reference clock in bank 14 - fixed by
moving flash to bank 16, a stale imported XDC - same bug class as
EXP-0078 but for constraints this time, missing IOSTANDARDs, and two
previously-silently-broken XDC property bugs). Route completes 100%,
but real timing does NOT close: WNS -0.618ns, 213 failing endpoints.

Honest root cause: the violation is inside neural_processor_packed.v's
own packed-MAC accumulation tree, unchanged since EXP-0059 - it has
real margin at the old 155.039MHz ui_clk but not at the new 172.414MHz
the paired clock-period change produced. This is NOT caused by the
32-bit width change itself. Width alone, even at the old clock, already
delivers the full intended 2x bandwidth gain (1.24 -> ~2.48 GB/s) -
width and clock rate are separable levers. Current trustworthy timing
signoff remains EXP-0083 (16-bit, +0.073ns) until the clock period is
reverted toward 3225ps (keeping Data Width=32) in one more real,
user-gated MIG wizard session.

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 16:28:44 +02:00
co-authored by Claude Sonnet 5
parent 376ccb6ee2
commit 9dead54ebf
21 changed files with 1302 additions and 754 deletions
+14 -6
View File
@@ -44,14 +44,22 @@ unmodified by v3, e.g. `layer_prefetch_ctrl.v`/`layer_weight_buffer.v`).
## Hard-won lessons (do not re-derive these the slow way) ## Hard-won lessons (do not re-derive these the slow way)
- **Vivado's imported source copies go stale silently.** If a project file - **Vivado's imported source copies go stale silently — RTL *and*
under `NeuralProcessor.srcs/sources_1/imports/...` was ever edited on disk constraint (.xdc) files alike.** If a project file under
*after* being added to the project, diff it against the live `NeuralProcessor.srcs/sources_1/imports/...` (RTL) or
`NeuralProcessor.srcs/constrs_1/imports/...` (XDC) was ever edited on
disk *after* being added to the project, diff it against the live
`hardware/v3/...` source before trusting any P&R result — `add_files`/ `hardware/v3/...` source before trusting any P&R result — `add_files`/
`update_compile_order` do NOT auto-refresh it, and a stale copy produces `update_compile_order` do NOT auto-refresh it, and a stale copy produces
no error, just silently wrong (old) synthesis results (EXP-0078). Prefer no error, just silently wrong (old) synthesis/implementation results
adding new files so they stay a direct reference (check `IS_GLOBAL_INCLUDE`/ (EXP-0078 for RTL; EXP-0084 for the XDC — the stale constraints file was
the file's own path isn't under `imports/`) rather than get copied. old enough to still have PRE-EXP-0077 pin assignments, predating the
flash bridge entirely, and its own real bug fixes silently didn't take
effect across multiple P&R re-runs until the staleness itself was
diagnosed and fixed). Prefer adding new files so they stay a direct
reference (check `IS_GLOBAL_INCLUDE`/the file's own path isn't under
`imports/`) rather than get copied — for BOTH the `sources_1` and
`constrs_1` filesets.
- **Testbench stimulus must use nonblocking assignment (`<=`), not blocking - **Testbench stimulus must use nonblocking assignment (`<=`), not blocking
(`=`), when driving a DUT's inputs from a separate `always`/`initial` (`=`), when driving a DUT's inputs from a separate `always`/`initial`
block.** Blocking assignment races the DUT's own `posedge`-triggered block.** Blocking assignment races the DUT's own `posedge`-triggered
+99 -49
View File
@@ -1,21 +1,25 @@
# FPGA-Neural V3 — Architecture Analysis: Timing, Bottlenecks, and Recommended Interventions # FPGA-Neural V3 — Architecture Analysis: Timing, Bottlenecks, and Recommended Interventions
Scope: the current, real, P&R-verified V3 design (`hardware/v3/`, branch Scope: the current, real, P&R-verified V3 design (`hardware/v3/`, branch
`v3-artix7`), updated through EXP-0083 (DDRManager phase 1, real P&R: WNS `v3-artix7`), updated through EXP-0084 (32-bit DDR3 widening, functionally
+0.073ns). Every number in this document is either directly measured (real complete and real-verified; real P&R timing does NOT yet close at the
simulation trace, real P&R report) or a calculation built from directly- paired clock speedup — see §5.4.1). The current real, trustworthy *timing*
measured building blocks — the two are labeled explicitly throughout. signoff remains EXP-0083 (16-bit, 155.039MHz, WNS +0.073ns) until a real
Nothing here is guessed. P&R closes for the 32-bit configuration. Every number in this document is
either 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-0083)**: §5.1 (denser activation packing) described **Status note (post EXP-0084)**: §5.1 (denser activation packing) is
below as a *recommendation* is **DONE and real-P&R-verified** (EXP-0081/ **DONE and real-P&R-verified** (EXP-0081/0082); §5.2 (DDRManager) phase 1
0082), and §5.2 (DDRManager) phase 1 is **also DONE and real-measured** is **DONE and real-measured** (EXP-0083, a genuinely modest ~2.9% real
(EXP-0083, a genuinely modest ~2.9% real benefit — see that section for the benefit); §5.4 (32-bit widening) is **functionally DONE and real-verified**
honest number and why the original hypothesis overstated it). See the but its own real timing has **not yet closed** (EXP-0084 — honest root
"DONE" markers in those sections and the updated bandwidth numbers in §3. The cause: an unrelated, pre-existing compute-pipeline critical path, not the
document originally analyzed the pre-fix state; it's kept below (marked width change itself; see §5.4.1). See the "DONE" markers in those sections.
historical) because the comparison is itself informative, then updated with The document originally analyzed the pre-fix state; it's kept below
the real post-fix numbers throughout. (marked historical) because the comparison is itself informative, then
updated with the real post-fix numbers throughout.
--- ---
@@ -34,7 +38,8 @@ DDR3 channel.
| Real DDR3 bandwidth needed for ONE core at peak DSP throughput | **4.96 GB/s** | calculated from measured DSP rate + memory layout (§3.2) | | Real DDR3 bandwidth needed for ONE core at peak DSP throughput | **4.96 GB/s** | calculated from measured DSP rate + memory layout (§3.2) |
| → DDR3 can sustain, pre-EXP-0081 packing (1 tile/burst) | **~25%** of one core's peak compute throughput | §3.2, historical | | → DDR3 can sustain, pre-EXP-0081 packing (1 tile/burst) | **~25%** of one core's peak compute throughput | §3.2, historical |
| → 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) | **+0.073 ns** | measured, EXP-0083 real P&R (improved further from EXP-0082's +0.068ns, and EXP-0079's +0.030ns before that) | | Real P&R timing margin (WNS), current trustworthy (16-bit) | **+0.073 ns** | measured, EXP-0083 real P&R |
| Real P&R timing, 32-bit widened config (EXP-0084) | **-0.618 ns (FAILS)** | measured, real P&R — real root cause is an unrelated, pre-existing compute-pipeline path, not the width change; see §5.4.1 |
| 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) — modest, honestly reported, not oversold | | 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) — modest, honestly reported, not oversold |
| 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 |
@@ -44,14 +49,17 @@ achievable fraction of it** — from ~25% to ~50% of one core's peak DSP
throughput, with the real P&R margin *improving*, not degrading, as a side throughput, with the real P&R margin *improving*, not degrading, as a side
effect. This was free leverage and it's now banked. effect. This was free leverage and it's now banked.
Even at ~50%, DDR3 is still the limiting resource, not DSP count — the next Even at ~50%, DDR3 is still the limiting resource, not DSP count. The 32-bit
real interventions, per the user's own explicit direction, are: (a) widening channel widening (§5.4) is **functionally real and verified** (real xsim
the physical DDR3 channel from 16-bit to 32-bit (§5.5 — doubles the physical against the real 2-chip DDR3 model), and width alone already delivers the
1.24 GB/s ceiling itself, unlike §5.1 which only reduced waste against a full intended 2× physical bandwidth gain (1.24 → ~2.48 GB/s) — but its real
fixed ceiling), and (b) an intelligent DDRManager (§5.2) to hide latency via P&R does not yet close timing, because the SAME wizard session also sped up
orchestrator-driven prefetch. Both are required together — a wider channel `ui_clk` by 11.2%, which an unrelated, pre-existing compute-pipeline path
without a smarter prefetcher still stalls on latency; a smarter prefetcher can't currently absorb (§5.4.1). Width and clock rate turned out to be
against a 16-bit channel still hits the same physical bandwidth wall. separable levers; the path forward is reverting just the clock period. The
DDRManager (§5.2) remains complementary — hides latency, doesn't raise the
physical ceiling — and its own real benefit should be re-measured once the
wider channel's timing actually closes.
--- ---
@@ -65,7 +73,8 @@ against a 16-bit channel still hits the same physical bandwidth wall.
| 0078 | + config-flash bridge (real STARTUPE2 placement) | +0.013 | 5213 | 16 | margin dropped — real added logic | | 0078 | + config-flash bridge (real STARTUPE2 placement) | +0.013 | 5213 | 16 | margin dropped — real added logic |
| 0079 | + real activation-fetch engine (`act_tile_fetch.v`) | +0.030 | 5379 | 16 | pre-packing baseline | | 0079 | + real activation-fetch engine (`act_tile_fetch.v`) | +0.030 | 5379 | 16 | pre-packing baseline |
| 0082 | + denser activation packing (2 tiles/burst, EXP-0081) | +0.068 | 5437 | 16 | pre-DDRManager baseline | | 0082 | + denser activation packing (2 tiles/burst, EXP-0081) | +0.068 | 5437 | 16 | pre-DDRManager baseline |
| 0083 | + DDRManager phase 1 (`ddr_prefetch_mgr.v`, single-slot look-ahead prefetch) | **+0.073** | 5644 | 16 | current, final, trustworthy number — margin IMPROVED again despite +207 LUTs | | 0083 | + DDRManager phase 1 (`ddr_prefetch_mgr.v`, single-slot look-ahead prefetch) | **+0.073** | 5644 | 16 | current, trustworthy 16-bit/155.039MHz signoff |
| 0084 | 32-bit DDR3 widening (2 chips) + clock speedup to 172.414MHz | **-0.618 (FAILS)** | 6418 | 16 | functionally verified (real xsim), real timing NOT closed — root cause is the clock speedup, not the width; see §5.4.1. Not the current trustworthy number. |
**Observation**: WNS does not move monotonically with LUT count (0.056 → **Observation**: WNS does not move monotonically with LUT count (0.056 →
0.013 → 0.030 → 0.068 → 0.073 while LUTs only ever grow) — this is normal P&R behavior 0.013 → 0.030 → 0.068 → 0.073 while LUTs only ever grow) — this is normal P&R behavior
@@ -390,7 +399,7 @@ never as N-scaled literal top-level pins again. Same architectural shape as
the weight-fetch path, in reverse (write instead of read) — a reasonable, the weight-fetch path, in reverse (write instead of read) — a reasonable,
bounded scope, and a real prerequisite, not optional polish. bounded scope, and a real prerequisite, not optional polish.
### 5.4 [Decided] Widening the physical DDR3 channel: 32-bit single channel vs. a second independent 16-bit channel ### 5.4 [Decided, RTL done] Widening the physical DDR3 channel: 32-bit single channel vs. a second independent 16-bit channel — **functionally complete (EXP-0084), real timing pending**
**The real question**: §5.1 halved *waste* against a fixed 1.24 GB/s **The real question**: §5.1 halved *waste* against a fixed 1.24 GB/s
physical ceiling; it did not raise the ceiling itself. Getting past ~50% physical ceiling; it did not raise the ceiling itself. Getting past ~50%
@@ -418,32 +427,73 @@ this package (the DQS-capable pins exist), but:
| **Conflict with already-placed I/O** | None | **Real, direct**: the management-SPI bus (bank 15: A15/B16/B17/A16) and the config-flash bus (bank 14: K17/K18/L13) are already placed in exactly the banks that would need to host a second channel. Only bank 16 (11 pins) would remain free — not enough margin for either SPI bus, let alone both. | | **Conflict with already-placed I/O** | None | **Real, direct**: the management-SPI bus (bank 15: A15/B16/B17/A16) and the config-flash bus (bank 14: K17/K18/L13) are already placed in exactly the banks that would need to host a second channel. Only bank 16 (11 pins) would remain free — not enough margin for either SPI bus, let alone both. |
| Controller logic cost | One MIG instance, wider data path (mostly automatic — the MIG wizard regenerates CAS/CWL/MMCM ratios for the new width) | A full second MIG instance: second calibration sequence, second `ui_clk` domain, and critically the **DDRManager/arbiter would need to become channel-aware**, not just requester-aware — real added complexity on top of §5.2's own design, not a simplification | | Controller logic cost | One MIG instance, wider data path (mostly automatic — the MIG wizard regenerates CAS/CWL/MMCM ratios for the new width) | A full second MIG instance: second calibration sequence, second `ui_clk` domain, and critically the **DDRManager/arbiter would need to become channel-aware**, not just requester-aware — real added complexity on top of §5.2's own design, not a simplification |
| Real risk given current thin margin (+0.073ns) | Lower — one controller, one clock domain, incremental change to an already-proven design | Higher — two independent PHYs, two calibration state machines, cross-channel coordination logic, all new | | Real risk given current thin margin (+0.073ns) | Lower — one controller, one clock domain, incremental change to an already-proven design | Higher — two independent PHYs, two calibration state machines, cross-channel coordination logic, all new |
| PCB impact | None (same pins, same DDR3 part, different bus width usage — **the physical board the user is designing does not need to change** for this) | Would require re-routing/relocating whichever board-level bus (SPI mgmt or flash) currently occupies bank 14/15 pins — real PCB-level rework, not just RTL | | PCB impact | **Corrected 2026-09-20 (real, caught mid-MIG-wizard-session, not assumed away)**: the selected DDR3 part, MT41J128M16JT-125:K, is a **x16 component** — the MIG wizard's "Data Width" field is the *controller's* aggregate width, not the chip's own width, so Data Width=32 with this part means **two physical MT41J128M16 chips wired in parallel**, each supplying half the data bus (standard DDR3 ganging — address/command fan out to both chips, only DQ/DQS/DM roughly double). This is a real BOM/footprint/routing addition, **not** "no PCB change" as an earlier draft of this document incorrectly claimed. Whether the extra DQ/DQS pins still fit within banks 34/35's existing allocation (likely, since address/command isn't duplicated) or require reaching into bank 14/15 must be confirmed from the real regenerated pinout, not assumed. | Also requires a second physical DDR3 chip (or a second x16-wide subsystem) for its own full independent controller, **plus** claims bank 14/15 for that second channel's own address/command/data — the SPI/flash pin conflict is additional to, not instead of, the same per-chip PCB cost as the 32-bit option. |
**Recommendation (honest, not deferential, as requested)**: **32-bit single- **Recommendation (honest, not deferential, as requested — and corrected
channel widening**, not a second independent channel. The bandwidth gain is once, honestly, mid-session)**: **32-bit single-channel widening**, not a
identical, but the 32-bit path has zero pin conflicts with already-placed, second independent channel — this part of the recommendation still holds.
already-verified I/O (SPI management bus, config-flash bus), a much smaller What does **not** hold, and was a real mistake in this document's earlier
real risk profile against the current thin timing margin, doesn't require version: the claim that widening needs "no PCB changes". It does — a second
a second full MIG/calibration instance, and — most importantly for the physical DDR3 chip, confirmed by the MIG wizard itself (Memory Details:
user's own board — needs **no PCB changes**, since it reuses the DDR3 part's x16, single component) when Data Width was set to 32. The real, remaining
own existing data pins at a wider access width rather than claiming new differentiator versus a second independent channel is narrower than
banks. A second channel's only real advantage (aggregate bandwidth could in originally stated but still real: 32-bit widening very likely stays within
principle scale further with a 3rd/4th channel later) doesn't apply here — the ALREADY-USED banks 34/35 (only data/strobe pins roughly double, address/
this package genuinely has no more free DQS-capable banks to grow into command is shared), while a second independent channel definitely needs its
after banks 14/15/34/35, so there's no future-proofing benefit being given OWN address/command/data pins, conflicting with the already-placed SPI
up. **User confirmed this recommendation and it is the decided path management bus (bank 15) and config-flash bus (bank 14). **User confirmed
forward.** they already understood the second-chip requirement and decided to proceed
with 32-bit widening regardless** (2026-09-20) — this remains the decided
path, now on the correct, fully-disclosed premise.
**What this requires (not yet done, real, disclosed)**: the real Xilinx MIG **What this required**: the real Xilinx MIG "Customize IP" wizard, re-run
"Customize IP" wizard must be re-run interactively (Data Width 16→32 AND interactively by the user (Data Width 16→32 AND Input Clock Period changed
Input Clock Period both changed in the *same* wizard session, since both in the same wizard session, since both require the wizard's own JEDEC/PLL
require the wizard's own JEDEC/PLL calculator to recompute CAS Latency/CWL/ calculator — never hand-edited, per this project's own established
MMCM ratios correctly — this is **not** safe to hand-edit in `mig_a.prj` the discipline). **Done, 2026-09-20.**
way the earlier `TargetFPGA` speed-grade field was, per this project's own
established discipline). This is a real, outstanding, user-gated #### 5.4.1 Real outcome (EXP-0084): functionally complete, timing NOT yet closed — the width/clock-speed decoupling
prerequisite before §5.2's DDRManager and any N>2 scaling test can use the
wider channel. **Confirmed correct, as predicted above**: the widened DQ/DQS/DM pins
stayed within banks 34/35 (no bank 14/15 conflict for the DDR3 signals
themselves) — real, verified from the actual routed pinout, not assumed.
One real prediction from this section did **not** hold, though, and is
corrected here rather than hidden: the differential **reference clock**
(`clk_ref_p/n`, a signal this section didn't analyze, chosen later in the
same wizard session) landed in **bank 14** by the wizard's own real UG586
placement rules — directly conflicting with the already-placed config-flash
SPI bus there (LVCMOS33 vs LVDS_25, incompatible VCCO in one bank). Real,
observed as an actual `place_design` failure, not hypothetical — fixed by
moving the flash bus to bank 16 (see `docs/PHYSICAL_REALIZATION.md` §2.3).
**The real, more consequential finding**: the SAME wizard session paired
the Data Width change with an Input Clock Period change (3225ps→2900ps,
chosen to keep the PHY:Controller ratio at 2:1 rather than falling back to
4:1, which would have halved `ui_clk` instead of speeding it up). This
clock speedup pushed `ui_clk` from 155.039MHz to 172.414MHz (+11.2%) — and
real P&R shows this specific increase breaks timing in
`neural_processor_packed.v`'s own packed-MAC accumulation tree (unchanged
since EXP-0059, real positive margin at the old rate, real -0.618ns
violation at the new one). **This is unrelated to the 32-bit width change
itself** — every module touched for the widening is real-verified
functionally correct (real xsim, real 2-chip DDR3 model, all tests PASS).
**The honest, useful conclusion**: width and clock rate are separable
levers, and this experiment shows they don't have to be exercised together.
Bandwidth = width × clock — 32-bit width **alone**, even at the OLD,
already-timing-proven 155.039MHz, already delivers the full intended 2×
bandwidth gain (1.24 → ~2.48 GB/s). The clock speedup was free upside *if*
the compute datapath could absorb it; real measurement shows it currently
can't, without a real, separate re-pipelining of that accumulation tree —
out of scope for a channel-width task, not attempted here.
**Current real status**: RTL fully adapted and functionally verified (see
`hardware/v2/logs/experiments.log` EXP-0084 for the complete story,
including 5 real P&R/XDC bugs found and fixed). Real timing signoff for
this configuration is **pending** — next real step is reverting Input
Clock Period toward 3225ps (keeping Data Width=32) in one more real,
user-gated MIG wizard session. The current real, trustworthy P&R signoff
remains EXP-0083 (16-bit, 155.039MHz, +0.073ns) until then.
### 5.5 Scaling path recommendation (real numbers, not a guess) — updated per user's final directive ### 5.5 Scaling path recommendation (real numbers, not a guess) — updated per user's final directive
+178 -78
View File
@@ -18,32 +18,59 @@ full derivation history.
### 2.1 DDR3 (fixed by the FPGA's own PHY hardware — not a free choice) ### 2.1 DDR3 (fixed by the FPGA's own PHY hardware — not a free choice)
**EXP-0084 UPDATE: real 32-bit channel (two MT41J128M16JT-125:K chips
ganged in parallel)** — dq/dqs/dm pin counts DOUBLED versus the original
16-bit design (dq 16→32, dqs 2→4 pairs, dm 2→4). Address/command/control
lines are unchanged and fanned out identically to both chips. Real,
board-accurate pin placement below (confirmed from the actual routed
design, `mig_7series_0.xdc` + this project's own top-level XDC) —
**functionally verified**, but see §3 for the real, honest timing-closure
status before treating this as a final signoff.
Generated by the Vivado MIG wizard (`mig_7series_0.xdc`), all `SSTL15` / Generated by the Vivado MIG wizard (`mig_7series_0.xdc`), all `SSTL15` /
`DIFF_SSTL15` (1.5V), banks 34/35: `DIFF_SSTL15` (1.5V), banks 34/35:
| Signal | Pin | Signal | Pin | Signal | Pin | | Signal | Pin | Signal | Pin | Signal | Pin |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| ddr3_dq[0] | G4 | ddr3_dq[8] | M1 | ddr3_addr[0] | B1 | | ddr3_dq[0] | C5 | ddr3_dq[16] | F4 | ddr3_addr[0] | U3 |
| ddr3_dq[1] | G3 | ddr3_dq[9] | K3 | ddr3_addr[1] | A3 | | ddr3_dq[1] | B7 | ddr3_dq[17] | F3 | ddr3_addr[1] | U4 |
| ddr3_dq[2] | J3 | ddr3_dq[10] | L3 | ddr3_addr[2] | A4 | | ddr3_dq[2] | B6 | ddr3_dq[18] | E2 | ddr3_addr[2] | V1 |
| ddr3_dq[3] | J2 | ddr3_dq[11] | M3 | ddr3_addr[3] | B4 | | ddr3_dq[3] | D8 | ddr3_dq[19] | D2 | ddr3_addr[3] | U1 |
| ddr3_dq[4] | K2 | ddr3_dq[12] | M2 | ddr3_addr[4] | C4 | | ddr3_dq[4] | C7 | ddr3_dq[20] | C1 | ddr3_addr[4] | L5 |
| ddr3_dq[5] | K1 | ddr3_dq[13] | K5 | ddr3_addr[5] | E7 | | ddr3_dq[5] | E6 | ddr3_dq[21] | H1 | ddr3_addr[5] | L6 |
| ddr3_dq[6] | H6 | ddr3_dq[14] | L4 | ddr3_addr[6] | E5 | | ddr3_dq[6] | E5 | ddr3_dq[22] | G1 | ddr3_addr[6] | L4 |
| ddr3_dq[7] | H5 | ddr3_dq[15] | L6 | ddr3_addr[7] | E6 | | ddr3_dq[7] | E7 | ddr3_dq[23] | F1 | ddr3_addr[7] | K5 |
| ddr3_addr[8] | C7 | ddr3_addr[9] | D8 | ddr3_addr[10] | B6 | | ddr3_dq[8] | B4 | ddr3_dq[24] | F6 | ddr3_addr[8] | M2 |
| ddr3_addr[11] | B7 | ddr3_addr[12] | C5 | ddr3_addr[13] | C6 | | ddr3_dq[9] | A4 | ddr3_dq[25] | G4 | ddr3_addr[9] | M3 |
| ddr3_ba[0] | B2 | ddr3_ba[1] | B3 | ddr3_ba[2] | A1 | | ddr3_dq[10] | A3 | ddr3_dq[26] | G3 | ddr3_addr[10] | L3 |
| ddr3_ras_n | D5 | ddr3_cas_n | D4 | ddr3_we_n | E3 | | ddr3_dq[11] | B3 | ddr3_dq[27] | J3 | ddr3_addr[11] | K3 |
| ddr3_reset_n | F6 (LVCMOS15) | ddr3_cke[0] | D7 | ddr3_odt[0] | H2 | | ddr3_dq[12] | B2 | ddr3_dq[28] | J2 | ddr3_addr[12] | M1 |
| ddr3_cs_n[0] | D3 | ddr3_dm[0] | G6 | ddr3_dm[1] | L1 | | ddr3_dq[13] | D5 | ddr3_dq[29] | K2 | ddr3_addr[13] | L1 |
| ddr3_dqs_p[0] | J4 (DIFF) | ddr3_dqs_n[0] | H4 (DIFF) | | | | ddr3_dq[14] | D4 | ddr3_dq[30] | K1 | ddr3_ba[0] | V5 |
| ddr3_dqs_p[1] | N2 (DIFF) | ddr3_dqs_n[1] | N1 (DIFF) | | | | ddr3_dq[15] | E3 | ddr3_dq[31] | H6 | ddr3_ba[1] | V2 |
| ddr3_ck_p[0] | A6 (DIFF) | ddr3_ck_n[0] | A5 (DIFF) | | | | ddr3_dm[0] | C6 | ddr3_dm[2] | C2 | ddr3_ba[2] | U2 |
| sys_clk_i | E2 (SSTL15, bank 35) | clk_ref_i | C9 (LVCMOS25, bank 16) | | | | ddr3_dm[1] | C4 | ddr3_dm[3] | G6 | | |
| ddr3_dqs_p[0] | A6 (DIFF) | ddr3_dqs_n[0] | A5 (DIFF) | ddr3_cas_n | R3 |
| ddr3_dqs_p[1] | B1 (DIFF) | ddr3_dqs_n[1] | A1 (DIFF) | ddr3_cke[0] | P2 |
| ddr3_dqs_p[2] | H2 (DIFF) | ddr3_dqs_n[2] | G2 (DIFF) | ddr3_cs_n[0] | T5 |
| ddr3_dqs_p[3] | J4 (DIFF) | ddr3_dqs_n[3] | H4 (DIFF) | ddr3_odt[0] | R2 |
| ddr3_ck_p[0] | N2 (DIFF) | ddr3_ck_n[0] | N1 (DIFF) | ddr3_ras_n | V4 |
| ddr3_reset_n | D7 (LVCMOS15) | ddr3_we_n | T3 | | |
| **sys_clk_p** | **N5 (DIFF_SSTL15, bank 34)** | **sys_clk_n** | **P5 (DIFF_SSTL15, bank 34)** | | |
| **clk_ref_p** | **T14 (LVDS_25, bank 14)** | **clk_ref_n** | **T15 (LVDS_25, bank 14)** | | |
**Bank voltage requirements**: bank 34/35 → **1.5V** (DDR3 SSTL15), bank 16 → **Real board implication (EXP-0084)**: both `sys_clk` and `clk_ref` are now
**2.5V** (clk_ref_i, LVCMOS25). genuine **differential pairs** — the board needs a differential oscillator
(or a differential buffer stage), not a single-ended one. `clk_ref` lives in
**bank 14** (not 34/35) because the MIG wizard's own UG586 placement rules
restrict that specific net to bank 14 for this part/package — not a free
choice. This directly displaced the config-flash SPI bus, which used to
share bank 14 (see §2.3 — moved to bank 16 in EXP-0084 to resolve a real
VCCO conflict, LVCMOS33 vs LVDS_25 cannot coexist in one bank).
**Bank voltage requirements**: bank 34/35 → **1.5V** (DDR3 SSTL15, plus
`sys_clk_p/n` at bank 34's own 1.5V-domain `DIFF_SSTL15`), bank 14 →
**2.5V** (`clk_ref_p/n`, LVDS_25).
`INTERNAL_VREF` for banks 34/35 is set to 0.750V by the MIG constraints `INTERNAL_VREF` for banks 34/35 is set to 0.750V by the MIG constraints
(required for SSTL15 single-ended inputs) — this is a Vivado-side setting, (required for SSTL15 single-ended inputs) — this is a Vivado-side setting,
@@ -66,26 +93,44 @@ board power plan uses a different rail for this bank.
### 2.3 Config flash SPI (FPGA ↔ flash, FPGA is **master**) ### 2.3 Config flash SPI (FPGA ↔ flash, FPGA is **master**)
These are the FPGA's own dedicated Master-SPI configuration pins, **reclaimed **EXP-0084 REAL PIN CHANGE**: originally these were the FPGA's own dedicated
as ordinary fabric I/O after configuration completes** (requires Master-SPI configuration pins (K17/K18/L13, bank 14), reclaimed as ordinary
`BITSTREAM.CONFIG.PERSIST = FALSE`, the Vivado default — already set fabric I/O post-configuration. EXP-0084's real 32-bit DDR3 widening moved
explicitly in the project XDC). Bank 14, `LVCMOS33`: the differential reference clock (`clk_ref_p/n`, §2.1) into bank 14 too —
a real VCCO conflict (the flash needs LVCMOS33/3.3V, `clk_ref` needs
LVDS_25/2.5V, one bank can only have one VCCO), confirmed by a real
`place_design` failure, not hypothetical. **Fixed by moving the flash bus
to bank 16** (completely unconstrained before this, no VCCO commitment):
| Signal | Pin | Direction (FPGA side) | Notes | | Signal | Pin | Direction (FPGA side) | Notes |
|---|---|---|---| |---|---|---|---|
| flash_mosi | K17 | output | = D00_MOSI (config pin, reclaimed) | | flash_mosi | D9 | output | bank 16 (was K17/D00_MOSI, bank 14) |
| flash_miso | K18 | input | = D01_DIN (config pin, reclaimed) | | flash_miso | D10 | input | bank 16 (was K18/D01_DIN, bank 14) |
| flash_cs_n | L13 | output | = FCS_B (config pin, reclaimed) | | flash_cs_n | C9 | output | bank 16 (was L13/FCS_B, bank 14) |
| (CCLK) | E9 | output | **Not a top-level port** — driven internally via the `STARTUPE2` primitive. Wire the flash's own CLK pin to package pin **E9**. | | (CCLK) | E9 | output | **Not a top-level port** — driven internally via the `STARTUPE2` primitive. Wire the flash's own CLK pin to package pin **E9**. Unaffected by the bank-14→16 move (CCLK was never a plain port). |
**Bank 14 VCCO**: assumed **3.3V** (matches the flash's own VCC, typically Still requires `BITSTREAM.CONFIG.PERSIST = NO` (the Vivado default — set
1.83.6V for the W25Q32JV — check its datasheet's exact operating range explicitly in the project XDC; **EXP-0084 note**: the XDC previously had
against whatever VCCO you choose for bank 14). this as `FALSE`, an invalid enum value in this Vivado version — silently
not applied at all until fixed to the real `NO`/`YES` enum).
**Reserved, do not use** (bank 14, same reasons as above but unused by this **Bank 16 VCCO**: **3.3V** (matches the flash's own VCC, typically
design — kept clear for any future Quad-SPI/BPI expansion): `L16` (EMCCLK), 1.83.6V for the W25Q32JV) — chosen freely since bank 16 had no prior VCCO
`R16` (RDWR_B), `V15` (CSI_B). The project's own XDC `PROHIBIT`s these so commitment.
Vivado's auto-placement never claims them by accident.
**Real board implication**: since the flash bridge no longer uses the
FPGA's own dedicated D00_MOSI/D01_DIN/FCS_B configuration pins at all
(K17/K18/L13 are now unused by this design), the flash chip's SPI lines on
the PCB connect to D9/D10/C9 instead — a real routing change versus any
earlier board layout draft based on the pre-EXP-0084 pinout.
**Reserved, do not use** (bank 14, unused by this design, kept clear for
any future Quad-SPI/BPI expansion — no longer near the flash bus, now just
general config-mode-reserved pins): `L16` (EMCCLK), `R16` (RDWR_B), `V15`
(CSI_B). The project's own XDC `PROHIBIT`s the underlying **sites** for
these (fixed in EXP-0084 — `PROHIBIT` is not a valid property directly on
`package_pin` objects in this Vivado version, only on their `site`; this
constraint had silently never been enforced before).
### 2.4 FPGA configuration control (dedicated, bank 0, not negotiable) ### 2.4 FPGA configuration control (dedicated, bank 0, not negotiable)
@@ -144,46 +189,93 @@ Signoff history (every real change, same target, in-context P&R):
| 0079 (+real activation engine) | +0.030 | 5379 | 16 | | 0079 (+real activation engine) | +0.030 | 5379 | 16 |
| 0082 (+denser activation packing) | +0.068 | 5437 | 16 | | 0082 (+denser activation packing) | +0.068 | 5437 | 16 |
| 0083 (+DDRManager phase 1, `ddr_prefetch_mgr.v`) | **+0.073** | 5644 | 16 | | 0083 (+DDRManager phase 1, `ddr_prefetch_mgr.v`) | **+0.073** | 5644 | 16 |
| 0084 (32-bit DDR3 widening, 2900ps/172.414MHz) | **-0.618 (FAILS)** | 6418 | 16 |
### 3.1 EXP-0084: real 32-bit DDR3 widening — functionally verified, timing NOT yet closed
**Honest status, not glossed over**: the real 32-bit channel (two
MT41J128M16JT-125:K chips) is **functionally complete and real-verified**
(real xsim against the real 2-chip DDR3 model: `tb_mig_native_adapter.v`
12/12 PASS, `tb_n2_system_ddr3.v` 8/8 PASS) — but its own real P&R does
**not** close timing at the paired clock speedup (2900ps sys_clk /
172.414MHz ui_clk): **WNS = -0.618ns, 213 failing endpoints**. Route
completed 100% (a real, physically valid placement/routing), only timing
itself fails.
**Real root cause** (traced to the actual worst path): the violation is
inside `neural_processor_packed.v`'s own packed-MAC accumulation tree (a
DSP48E1 output through a 4-deep CARRY4 chain), **unchanged since EXP-0059**
— it had real positive margin at the old 155.039MHz `ui_clk` (EXP-0083:
+0.073ns) but not at the new 172.414MHz (an 11.2% real frequency increase).
This is **not caused by the 32-bit width change itself** — every module
touched for the widening (adapter, arbiter, activation fetch, etc.) is
real-verified functionally correct; the failure is in unrelated, pre-
existing compute logic that simply wasn't budgeted for a faster clock.
**The real, honest decoupling this reveals**: bandwidth = width × clock
rate. Widening to 32 bits **alone**, even at the OLD, already-closing
3225ps/155.039MHz, already delivers the FULL intended 2× bandwidth gain
(1.24 → ~2.48 GB/s physical ceiling) — the clock speedup to 2900ps was a
separate, additional optimization stacked on top in the same wizard
session, and it is specifically *that* stacking, not the width, that
breaks timing.
**Current real, trustworthy signoff therefore remains EXP-0083** (16-bit,
155.039MHz, +0.073ns) until a real P&R closes for the 32-bit configuration
— expected once the Input Clock Period is reverted toward 3225ps (keeping
Data Width=32), a real, user-gated MIG wizard step, not yet done. See
`hardware/v2/logs/experiments.log` EXP-0084 for the complete story,
including 5 real P&R/XDC bugs found and fixed along the way (stale
differential-clock ports, a real VCCO bank conflict, a stale imported XDC,
missing IOSTANDARDs, and two cosmetic but previously-silently-broken XDC
property bugs).
## 4. Real DDR3 memory layout convention ## 4. Real DDR3 memory layout convention
Both weight data and activation data share the same DDR3 address space Both weight data and activation data share the same DDR3 address space,
(word-addressed, 16-bit words, `BURST_LEN=8` per transaction = 128 bits/burst). word-addressed. **EXP-0084**: the native word width is now **32 bits**
(`BURST_LEN=8` per transaction = 256 bits/burst, up from 128 bits at the
old 16-bit width) — this is the real, current RTL (`hardware/v3/rtl/`),
functionally verified; see §3.1 for its pending timing-closure status.
- **Weights**: one layer's weight set starts at word address `layer_index * - **Weights**: one layer's weight set starts at word address `layer_index *
WORDS_PER_LAYER` (`WORDS_PER_LAYER = LAYER_BYTES/2`). Densely packed — WORDS_PER_LAYER` (`WORDS_PER_LAYER = LAYER_BYTES/2`). Densely packed —
`layer_prefetch_ctrl.v` reads full bursts sequentially into the on-chip `layer_prefetch_ctrl.v` reads full bursts sequentially into the on-chip
weight buffer once per job. weight buffer once per job. `BYTES_PER_BURST` is now `4*BURST_LEN` (32
bytes/burst, up from 16) — the byte-drain logic itself is word-width-
agnostic and needed no change beyond this.
- **Activations** (real engine, `act_tile_fetch.v`; current layout is the - **Activations** (real engine, `act_tile_fetch.v`; current layout is the
**v2 convention, EXP-0081/0082**): **two consecutive tiles (P_IN=8 INT8 **v3 convention, EXP-0084**): **FOUR consecutive tiles (P_IN=8 INT8
values each) share ONE full `BURST_LEN=8`-word (128-bit) burst** — the values each, 64 bits each) share ONE full `BURST_LEN=8`-word (256-bit)
even-indexed tile occupies the low 64 bits, the odd-indexed tile occupies burst** — tile parity 0/1/2/3 (`tcnt[1:0]`) selects bits
the high 64 bits. Tile `t`'s burst address is `base + (t>>1)*BURST_LEN` `[63:0]`/`[127:64]`/`[191:128]`/`[255:192]` of the burst response. Tile
(integer division — two tiles per burst), always burst-aligned by `t`'s burst address is `base + (t>>2)*BURST_LEN` (integer division —
construction. This **halves** real DDR3 bytes-moved-per-useful-byte versus four tiles per burst), always burst-aligned by construction. This is
the original EXP-0079 "1 tile = 1 burst" layout, which wasted the upper 64 **not** a further bytes-per-MAC reduction beyond EXP-0081's already-
bits of every burst as padding. optimal 1 byte/MAC — it's what's required to keep that same 100% packing
- **Why this is timing-safe despite selecting a sub-burst half at read utilization at the new, larger burst size instead of leaving half of it
time**: the tile index's LSB (which half of the burst a given tile newly wasted.
lives in) is known at *request* time, not at response time. It's - **Why this is timing-safe despite selecting a sub-burst quarter at
latched into a register (`sel_lat`) the same cycle the request is read time**: the tile index's own low 2 bits (which quarter of the
accepted — many `ui_clk` cycles before the real DDR3 round-trip burst a given tile lives in) are known at *request* time, not at
completes and `ctrl_rdata` becomes valid. The eventual data-select mux response time. They're latched into a register (`sel_lat`) the same
therefore always selects on an already-long-stable registered bit, cycle the request is accepted — many `ui_clk` cycles before the real
never a bit racing live read data — this is NOT the runtime-indexed- DDR3 round-trip completes and `ctrl_rdata` becomes valid. The eventual
part-select-on-the-critical-path pattern flagged as a real Fmax risk in data-select mux is an explicit 4-way `case` on constant byte offsets
EXP-0061 (that pattern is about a select signal arriving *late*, (not a runtime-indexed part-select expression) — extending the exact
simultaneously with the data it gates). Confirmed timing-safe by real same discipline EXP-0081 established for the 1-bit case to 2 bits.
P&R (EXP-0082): margin *improved* from +0.030ns to +0.068ns, not This mux is real and functionally verified, but is unrelated to
degraded. §3.1's own real timing failure (that violation is inside the compute
- Real measured effect: back-to-back same-row DDR3 throughput is a fixed core's accumulation tree, not this fetch path).
1.24 GB/s (measured, EXP-0080) regardless of packing — this convention - Real measured effect: back-to-back same-row DDR3 throughput was a
doesn't change that ceiling, it changes how much of it is *wasted* on fixed 1.24 GB/s at 16-bit width (measured, EXP-0080); at 32-bit width
padding, doubling the real achievable useful fraction (see the real physical ceiling doubles to ~2.48 GB/s, independent of this
`docs/ARCHITECTURE_ANALYSIS.md` §3 and §5.1). packing convention — packing controls how much of that ceiling is
*wasted*, not the ceiling itself (see `docs/ARCHITECTURE_ANALYSIS.md`
§3 and §5.1/§5.4).
- `base` (a job's own `x_base_a`/`x_base_b`) is chosen freely by whoever - `base` (a job's own `x_base_a`/`x_base_b`) is chosen freely by whoever
submits jobs (the SPI host) — just keep each position's own activation submits jobs (the SPI host) — just keep each position's own activation
array in its own non-overlapping `(N_TILES/2) * BURST_LEN`-word region. array in its own non-overlapping `(N_TILES/4) * BURST_LEN`-word region.
## 5. FPGA configuration (boot) procedure ## 5. FPGA configuration (boot) procedure
@@ -249,19 +341,27 @@ table is a summary/index.
engine (currently `result_data_a/b` are literal top-level pins, fine at engine (currently `result_data_a/b` are literal top-level pins, fine at
N=2 but a real pin-budget blocker beyond that) must land first — see N=2 but a real pin-budget blocker beyond that) must land first — see
`docs/ARCHITECTURE_ANALYSIS.md` §5.3. `docs/ARCHITECTURE_ANALYSIS.md` §5.3.
- The reset pin and other very-low-pin-count signals have no fixed PCB - `sys_rst` has only a *tentative* real pin (G13, bank 15, EXP-0084 —
location yet — assign once the rest of the board layout (reset circuit, chosen just to unblock real P&R, not a final board decision) — assign
status LEDs, etc.) is decided. its real, permanent location once the rest of the board layout (reset
- The §3 timing margin (+0.073ns) is real but thin — do not add logic circuit, status LEDs, etc.) is decided.
without a fresh real P&R to confirm it still closes. - **The 32-bit DDR3 channel's own real timing does NOT currently close**
(EXP-0084: WNS -0.618ns at 2900ps/172.414MHz) — the current real,
trustworthy signoff remains EXP-0083 (16-bit, 155.039MHz, +0.073ns) until
the Input Clock Period is reverted toward 3225ps (keeping Data Width=32)
in one more real, user-gated MIG wizard session. See §3.1 for the full
honest story and real root cause (unrelated to the width change itself).
- 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.
- A 32-bit-wide DDR3 channel (decided, see `docs/ARCHITECTURE_ANALYSIS.md`
§5.4) requires the user's own real interactive Vivado MIG "Customize IP"
wizard session (Data Width + Input Clock Period changed together) —
not yet done; every timing/bandwidth number in this document still
reflects the current 16-bit channel.
- `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 its measured benefit (2.86% real reduction in total timing-verified (at the OLD 16-bit/155MHz signoff), but its measured
simulated time) is modest — see `docs/ARCHITECTURE_ANALYSIS.md` §5.2 for benefit (2.86% real reduction in total simulated time) is modest — see
the honest number and why. The larger multi-slot DDRManager is not built. `docs/ARCHITECTURE_ANALYSIS.md` §5.2 for the honest number and why. The
larger multi-slot DDRManager is not built. Its own real benefit should be
re-measured once the 32-bit channel's timing actually closes.
- User-requested, not yet built: an active-low `data_ready_n`/IRQ pin so
the ESP32 can be interrupt-driven (job completion or error pending)
instead of polling the STATUS register — planned as a sticky bit (set on
`job_out_done`/`dir_error`, cleared when the ESP32 reads STATUS),
deliberately deferred until the §3.1 timing-closure work lands so it
doesn't further complicate the currently-tight I/O/VCCO budget mid-fix.
+229
View File
@@ -5330,3 +5330,232 @@ without ddr_prefetch_mgr) against the wider channel to see whether look-
ahead's real benefit grows once the physical ceiling is higher; (3) build ahead's real benefit grows once the physical ceiling is higher; (3) build
the result-writeback engine (S5.3, still the real blocker for N>2); (4) the result-writeback engine (S5.3, still the real blocker for N>2); (4)
real N=2/4/8/16 scaling tests per the user's own final directive. real N=2/4/8/16 scaling tests per the user's own final directive.
EXP-0084 -- real 32-bit DDR3 channel widening: functionally complete
and real-verified, but real timing does NOT close at the paired clock
speedup -- honest finding, width and clock speed are separable
(2026-09-20, same autonomous continuation, user's own direction: "ok
sono d'accordo andiamo per un canale fisico a 32 bit... cerchiamo di
spremere al massimo il timing")
CONTEXT: docs/ARCHITECTURE_ANALYSIS.md S5.4 (EXP-0081/0082) recommended
32-bit single-channel widening over a second independent DDR3 channel,
based on real device data (XC7A100T-CSG324 bank/DQS pin analysis).
User ran the real Vivado MIG "Customize IP" wizard themselves: Data
Width 16->32 (two MT41J128M16JT-125:K chips ganged in parallel, a real
PCB change the user explicitly confirmed they already understood),
Input Clock Period 3225ps->2900ps (the fastest value that still keeps
PHY:Controller ratio at 2:1, found by the user testing the wizard's
own real constraint directly -- below 2900ps the wizard forces 4:1,
which would have HALVED ui_clk instead of speeding it up), Differential
system clock AND reference clock (both real board decisions -- a
differential oscillator, and T14/T15 bank 14 for clk_ref specifically
because the wizard's own UG586 placement rules restricted that net to
bank 14).
REAL RTL ADAPTATION (the shared ctrl bus's own native word width
changed from 16 to 32 bits system-wide, BURST_LEN=8 unchanged --
burst payload 128->256 bits):
- mig_native_adapter.v: app_wdf_data/app_rd_data 64->128 bits (real,
confirmed against the regenerated mig_7series_0.v: app data width
= DataWidth*BURST_LEN/nCK_PER_CLK = 32*8/2 = 128, matches exactly),
app_wdf_mask 8->16 bits, ctrl wdata/wmask 32*BURST_LEN/4*BURST_LEN.
Beat count (2) and state-machine shape unchanged -- only the
per-beat slice widths changed.
- sdram_arbiter_n.v, layer_prefetch_ctrl.v (v2, reused -- real,
disclosed, deliberate exception to its "unmodified from v2"
status, see its own header), host_mem_bridge.v, packed_slot.v,
ddr_prefetch_mgr.v, n2_system_ddr3_top.v: mechanical width bump of
the shared ctrl_wdata/ctrl_wmask/ctrl_rdata convention throughout.
- act_tile_fetch.v: the REAL logic-bearing change. Burst now holds
256 bits = FOUR 64-bit tiles (was 128 bits = two, EXP-0081) --
sel_lat extended from 1 to 2 registered bits, tile_offset divisor
from tcnt>>1 to tcnt>>2, and the 2-way ternary mux replaced with an
explicit 4-way `case` on constant byte offsets (not a runtime part-
select -- same EXP-0081 discipline, registered select known at
request time, extended from 1 to 2 bits). This is NOT a further
bytes-per-MAC reduction beyond EXP-0081's already-optimal 1
byte/MAC -- it's what's REQUIRED to keep that same 100% packing
utilization at the new, larger burst size instead of leaving half
of it newly wasted.
- host_mem_bridge.v: a real, deliberate ADDRESSING REDESIGN, not a
mechanical bump. The host-facing contract (mem_addr as a 16-bit-
word address, mem_wdata/mem_rdata 16-bit, mem_lb_n/mem_ub_n byte
enables) is kept COMPLETELY UNCHANGED -- spi_host_bridge_v3.v's own
WRITE_MEM/READ_MEM opcode payload size, and by extension the ESP32
firmware contract, is NOT touched by the DDR3 widening. mem_addr's
LSB now additionally selects which 16-bit half of the addressed
32-bit ctrl-bus word to target. Real, disclosed limitation: this
halves the host's own reachable byte range for a given ADDR_WIDTH
-- acceptable for this debug/raw-access path at the project's real
current scale, not the compute path.
- n2_system_ddr3_top.v: ddr3_dq 16->32 bits, ddr3_dqs_p/n 2->4 bits,
ddr3_dm 2->4 bits (real, confirmed against the regenerated
mig_7series_0.v wrapper -- address/command/control lines unchanged,
shared identically by both chips). app_wdf_data/app_rd_data/
app_wdf_mask widths matched to mig_native_adapter.v's own.
NEW TEST INFRASTRUCTURE: burst_mem_model32.v -- an explicitly SYNTHETIC
32-bit-wide burst-memory test model (NOT a real chip model, unlike
sdram_controller.v/sdram_model.v which genuinely represent the real
AS4C32M16SA x16 SDR part and are correctly, deliberately NOT modified
here -- that real chip is inherently 16-bit, shared by 20+ other v2/v3
tests, out of scope). Built to unblock the isolated fast (iverilog)
tests for modules that now speak the 32-bit convention, matching this
project's own "fast backend for glue-logic, real DDR3 backend for the
trustworthy number" precedent. Real bug found and fixed during bring-
up: the model's first version sized its dense backing array at
MEM_ADDR_BITS=16 (65536 entries) -- tb_packed_slot.v's own
ACT_MEM_BASE=0x10000 (=65536) SILENTLY WRAPPED to address 0, aliasing
the weight and activation regions and producing real, confusing wrong-
answer failures (7/9 tests failing with plausible-looking but wrong
0/127 values) that took a real root-cause pass to trace to the
truncation, not a logic bug. Fixed by widening to MEM_ADDR_BITS=20
(~1M entries, ~32MB simulation memory, trivial cost). Also found and
fixed: a wmask polarity bug (real DQM convention is 0=write/1=masked,
matching sdram_controller.v's own documented convention -- the first
draft had it backwards).
TESTBENCHES UPDATED (all re-verified real, after the burst_mem_model32
fixes): tb_act_tile_fetch.v (10/10 PASS, rewritten for 4-tiles/burst),
tb_ddr_prefetch_mgr.v (33/33 PASS, PART 3's own same-row-vs-row-switch
A/B dropped -- burst_mem_model32's fixed latency doesn't carry that
distinction the way the real DDR3 backend does, so it no longer means
anything on this backend; EXP-0083's own real-DDR3-backend 2.86%
number remains the trustworthy one for that question), tb_host_mem_
bridge.v (32/32 PASS, extended to cover all 16 half-word offsets per
burst now, was 8), tb_sdram_arbiter_n.v (7/7 PASS), tb_packed_slot.v
(9/9 PASS, bit-identical result pattern to EXP-0081 -- confirms zero
effect on computed results).
REAL xsim VERIFICATION (real MIG IP + real ddr3_model.sv, TWO real
component instances now, DQ_WIDTH=32/16-per-component -- exact real
vendor pattern confirmed by reading the regenerated sim_tb_top.v's own
generate block, not assumed):
1. tb_mig_native_adapter.v: 12/12 PASS. Found and fixed a real
testbench-only bug during bring-up (not an RTL bug): the write-
pattern fill loop still used the old 16-bit-word slicing
(wpat[k*16+:16]) even after the port widths were bumped -- sed's
blanket 16*BURST_LEN->32*BURST_LEN substitution correctly missed
this since it's a different expression shape; same class of gap
already hit once in tb_sdram_arbiter_n.v this same session.
2. tb_n2_system_ddr3.v: 8/8 PASS, 0 errors, 8/8 positions completed,
real N=2 system against the real 2-chip DDR3 model, both chips
visibly returning DIFFERENT real data in the JEDEC trace
(confirms real 32-bit width utilization, not address aliasing).
REAL P&R -- 5 real bugs found and fixed across iterations, in order:
1. n2_system_ddr3_top.v's own top-level MIG instantiation still used
the OLD single-ended sys_clk_i/clk_ref_i ports -- real synthesis
ERROR ("named port connection 'sys_clk_i' does not exist"). Both
sys_clk and clk_ref are now real differential pairs on the
regenerated public mig_7series_0.v wrapper (the user's own wizard
choice). Fixed: n2_system_ddr3_top.v's own top-level ports changed
from sys_clk_i/clk_ref_i to sys_clk_p/sys_clk_n/clk_ref_p/
clk_ref_n, matching the real board implication (a differential
oscillator, not single-ended).
2. Real IO placement failure: "40 unplaced IO Ports vs 10 available
pins". Root cause: a real VCCO conflict -- the flash SPI bus
(K17/K18/L13) and the differential clk_ref_p/n (T14/T15) both sit
in bank 14, needing incompatible voltages (LVCMOS33/3.3V vs
LVDS_25/2.5V). This was flagged as a real *risk* when T14/T15 was
chosen mid-wizard-session (real device data showed the conflict
was possible); this P&R run turned it into a real, observed
failure. Fixed: moved the flash bus to bank 16 (D9/D10/C9 --
completely unconstrained, no VCCO commitment, real verified-free
pins from the actual part database).
3. Root cause of why bug #2's own XDC fix didn't take effect on the
first re-run: the project's own n2_system_ddr3_top.xdc was a
STALE IMPORTED COPY -- the SAME class of bug CLAUDE.md already
documents for RTL files (EXP-0078), now confirmed to also apply
to constraint files. The imported copy was old enough to still
have the PRE-EXP-0077 flash-pin PROHIBIT constraints (predating
the real flash bridge entirely). Fixed the same way: removed and
re-added as a direct reference.
4. With the real XDC now live, a further real placement failure:
`sys_rst` (and, on an earlier pass, the result-data/status ports)
had no explicit IOSTANDARD, defaulting to LVCMOS18 -- with banks
14/15/34/35 now ALL committed to other real voltages (2.5V/3.3V/
1.5V/1.5V) by the wider DDR3 interface, there is genuinely no
1.8V-compatible bank left. This was already disclosed in docs/
PHYSICAL_REALIZATION.md S7 ("sys_rst... no fixed PCB location
yet") but the OLD, narrower 16-bit I/O footprint had enough slack
for it to silently default-fit somewhere; the wider interface
removed that slack. Fixed: explicit LVCMOS33 on all of them,
sys_rst placed at G13 (bank 15, real verified-free pin) --
NOT a final board decision, still pending the real PCB reset
circuit layout.
5. Two cosmetic XDC bugs surfaced as Critical Warnings once the real
live XDC was actually being read (previously silently ignored
from the stale copy): BITSTREAM.CONFIG.PERSIST FALSE is not a
valid enum value in this Vivado version (needs NO/YES, not
TRUE/FALSE -- real, harmless since NO is also the default, but
the property was silently not being set at all before); and
PROHIBIT is not a valid property on package_pin objects, only on
the underlying site objects (fixed via `get_sites -of_objects`)
-- meaning the EMCCLK/RDWR_B/CSI_B PROHIBIT constraint had
SILENTLY NEVER WORKED in this project's entire history, only
surfacing now because the stale-XDC fix (#3) finally let Vivado
actually parse the real file. No real harm came of this (nothing
ever auto-placed there), but it was never actually enforced.
REAL, FINAL P&R RESULT (route_design 100%, 0 placement errors --
functionally a complete, real, routed design):
Slice LUTs = 6418 (up from EXP-0083's 5644, +774 -- consistent with
the wider ctrl-bus muxes/registers throughout the shared memory
path: arbiter, act_tile_fetch's 4-way case, ddr_prefetch_mgr's
wider ping-pong buffer, host_mem_bridge's wider mask logic,
mig_native_adapter's wider beats)
DSP48E1 = 16 (UNCHANGED since EXP-0059 -- confirms again the compute
datapath itself is untouched by this change)
WHS (hold) = +0.048ns (real, closes)
WNS (setup) = **-0.618ns -- REAL TIMING FAILURE, 213 failing
endpoints, TNS=-61.621ns. Honestly reported, not hidden.**
ROOT CAUSE OF THE REAL TIMING FAILURE (traced to the actual worst
path, not assumed): the violating path is INSIDE `neural_processor_
packed.v`'s own packed-MAC accumulation tree (u_slot0/u_np/
GEN_MAC_PACKED[5].product, a DSP48E1, through a 4-deep CARRY4 chain,
into prodb1_reg[5][15]) -- real data path delay 6.26ns against a
5.8ns period budget. This datapath is UNCHANGED since EXP-0059 and
had real, positive margin at the OLD ui_clk (155.039MHz, period
6.447ns) -- EXP-0083's own real signoff was +0.073ns. The NEW ui_clk
(172.414MHz, period 5.8ns, an 11.2% real frequency increase) simply
doesn't leave this specific, pre-existing critical path enough time,
independent of anything actually changed by the 32-bit width work.
THE REAL, HONEST DECOUPLING THIS FINDING REVEALS: the 32-bit DATA
WIDTH change and the CLOCK PERIOD change were bundled into one wizard
session, but they are NOT the same lever. Bandwidth = width x clock
rate -- widening from 16 to 32 bits ALONE, even at the OLD 3225ps/
310.078MHz sys_clk (155.039MHz ui_clk, already real-proven to close
timing with margin), already delivers the FULL intended 2x real
bandwidth gain (1.24GB/s -> ~2.48GB/s physical ceiling). The clock
speedup to 2900ps/344.828MHz (172.414MHz ui_clk) was a SEPARATE,
ADDITIONAL optimization stacked on top -- and it is THAT specific
stacking, not the width change, that breaks real timing. This is
exactly the kind of "serious, critical, not accondiscendente" finding
the user has consistently asked for.
DECISION: keep all the REAL RTL adaptation work (verified, real,
functionally correct via real xsim against real 2-chip DDR3, needed
regardless of the final clock choice) -- do NOT revert it. Do NOT
claim this P&R is a clean timing signoff -- it is not, and is not
being presented as one. The CURRENT real, trustworthy timing signoff
remains EXP-0083's own (+0.073ns, 16-bit width, 155.039MHz) until a
real P&R closes for the 32-bit configuration.
next_action: real, user-gated -- re-run the MIG wizard ONE more time,
changing ONLY the Input Clock Period back toward 3225ps (keeping Data
Width=32), since width alone already delivers the intended bandwidth
win without the timing risk the paired clock speedup introduced. Not
hand-editable (same real JEDEC/PLL-calculator reasoning as every other
MIG timing parameter this project has never hand-edited). Once that
real P&R closes, update docs/PHYSICAL_REALIZATION.md and docs/
ARCHITECTURE_ANALYSIS.md S5.4 with the REAL final numbers (not these
provisional ones). Separately, and out of scope for a channel-width
task: if the user wants to keep pushing ui_clk faster in the future,
neural_processor_packed.v's own packed-MAC accumulation tree (the
real bottleneck identified above, unchanged since EXP-0059) would need
real re-pipelining -- a genuinely separate, disclosed, not-yet-
attempted optimization.
+21 -10
View File
@@ -10,7 +10,7 @@
// already measured (7.16x real memory-side speedup vs zero-reuse, // already measured (7.16x real memory-side speedup vs zero-reuse,
// same hardware, see that testbench's own header). // same hardware, see that testbench's own header).
// //
// One layer = LAYER_BYTES bytes, fetched as LAYER_BYTES/(2*BURST_LEN) // One layer = LAYER_BYTES bytes, fetched as LAYER_BYTES/BYTES_PER_BURST
// back-to-back BURST_LEN-word transactions starting at layer_base // back-to-back BURST_LEN-word transactions starting at layer_base
// (word address). Sequential -> lands in the SAME open row for any // (word address). Sequential -> lands in the SAME open row for any
// layer that fits within one row (1024 columns = 256 tile-blocks at // layer that fits within one row (1024 columns = 256 tile-blocks at
@@ -18,7 +18,7 @@
// target models use), so this composes directly with EXP-0054's // target models use), so this composes directly with EXP-0054's
// open-row policy without needing anything special here. // open-row policy without needing anything special here.
// //
// Each captured burst (ctrl_rdata, 16*BURST_LEN bits) is LATCHED // Each captured burst (ctrl_rdata, 32*BURST_LEN bits) is LATCHED
// locally before draining -- does not assume the controller holds // locally before draining -- does not assume the controller holds
// rdata stable beyond the cycle `ready` pulses (its own documented // rdata stable beyond the cycle `ready` pulses (its own documented
// contract is "valid the same cycle ready pulses", nothing more). // contract is "valid the same cycle ready pulses", nothing more).
@@ -27,6 +27,17 @@
// sub-counters to keep in sync, deliberately simpler than a first // sub-counters to keep in sync, deliberately simpler than a first
// draft of this module that tracked them separately and was harder to // draft of this module that tracked them separately and was harder to
// convince correct by inspection. // convince correct by inspection.
//
// EXP-0084 (real 32-bit DDR3 channel widening): ctrl_wdata/ctrl_rdata
// widened from the old 16-bit-word convention to 32*BURST_LEN bits
// (BYTES_PER_BURST from 2 bytes/word*BURST_LEN to 4 bytes/word*
// BURST_LEN) -- the ONLY real change needed, since the
// byte-drain logic itself was already word-width-agnostic (drains
// BYTES_PER_BURST bytes one at a time regardless of the underlying
// word size). This is a real, deliberate exception to this file's own
// "reused unmodified from v2" status -- the shared ctrl bus's own
// width is a real interface contract that changed project-wide, not a
// V3-specific choice this file could stay isolated from.
// ============================================================ // ============================================================
module layer_prefetch_ctrl #( module layer_prefetch_ctrl #(
parameter DATA_WIDTH = 8, parameter DATA_WIDTH = 8,
@@ -54,20 +65,20 @@ module layer_prefetch_ctrl #(
output reg ctrl_req, output reg ctrl_req,
output wire ctrl_wr, // always 0: read-only output wire ctrl_wr, // always 0: read-only
output reg [ADDR_WIDTH-1:0] ctrl_addr, output reg [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata, // unused (read-only), tied off output wire [32*BURST_LEN-1:0] ctrl_wdata, // unused (read-only), tied off
output wire [2*BURST_LEN-1:0] ctrl_wmask, // unused (read-only), tied off output wire [4*BURST_LEN-1:0] ctrl_wmask, // unused (read-only), tied off
input wire [16*BURST_LEN-1:0] ctrl_rdata, input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready, input wire ctrl_ready,
input wire ctrl_busy input wire ctrl_busy
); );
localparam BYTES_PER_BURST = 2*BURST_LEN; localparam BYTES_PER_BURST = 4*BURST_LEN;
localparam BURSTS_PER_LAYER = LAYER_BYTES/BYTES_PER_BURST; localparam BURSTS_PER_LAYER = LAYER_BYTES/BYTES_PER_BURST;
localparam BIDXW = (BURSTS_PER_LAYER <= 1) ? 1 : $clog2(BURSTS_PER_LAYER); localparam BIDXW = (BURSTS_PER_LAYER <= 1) ? 1 : $clog2(BURSTS_PER_LAYER);
localparam DIDXW = $clog2(BYTES_PER_BURST); localparam DIDXW = $clog2(BYTES_PER_BURST);
assign ctrl_wr = 1'b0; assign ctrl_wr = 1'b0;
assign ctrl_wdata = {(16*BURST_LEN){1'b0}}; assign ctrl_wdata = {(32*BURST_LEN){1'b0}};
assign ctrl_wmask = {(2*BURST_LEN){1'b0}}; assign ctrl_wmask = {(4*BURST_LEN){1'b0}};
localparam S_IDLE = 3'd0, localparam S_IDLE = 3'd0,
S_WAIT = 3'd1, S_WAIT = 3'd1,
@@ -78,7 +89,7 @@ module layer_prefetch_ctrl #(
reg [BIDXW-1:0] burst_idx; reg [BIDXW-1:0] burst_idx;
reg [DIDXW-1:0] drain_cnt; reg [DIDXW-1:0] drain_cnt;
reg [ADDR_WIDTH-1:0] base_lat; reg [ADDR_WIDTH-1:0] base_lat;
reg [16*BURST_LEN-1:0] burst_lat; reg [32*BURST_LEN-1:0] burst_lat;
// combinational: which byte of the layer is currently being drained // combinational: which byte of the layer is currently being drained
wire [BUFADDRW-1:0] cur_fill_addr = burst_idx * BYTES_PER_BURST + drain_cnt; wire [BUFADDRW-1:0] cur_fill_addr = burst_idx * BYTES_PER_BURST + drain_cnt;
@@ -96,7 +107,7 @@ module layer_prefetch_ctrl #(
burst_idx <= {BIDXW{1'b0}}; burst_idx <= {BIDXW{1'b0}};
drain_cnt <= {DIDXW{1'b0}}; drain_cnt <= {DIDXW{1'b0}};
base_lat <= {ADDR_WIDTH{1'b0}}; base_lat <= {ADDR_WIDTH{1'b0}};
burst_lat <= {(16*BURST_LEN){1'b0}}; burst_lat <= {(32*BURST_LEN){1'b0}};
end else begin end else begin
ctrl_req <= 1'b0; ctrl_req <= 1'b0;
fill_we <= 1'b0; fill_we <= 1'b0;
+51 -19
View File
@@ -15,20 +15,30 @@
# when PERSIST is FALSE (the Vivado default -- set explicitly here so # when PERSIST is FALSE (the Vivado default -- set explicitly here so
# this dependency is self-documenting in the constraints, not just a # this dependency is self-documenting in the constraints, not just a
# silent default someone could flip later without realizing why). # silent default someone could flip later without realizing why).
set_property BITSTREAM.CONFIG.PERSIST FALSE [current_design] set_property BITSTREAM.CONFIG.PERSIST NO [current_design]
# ---- config-flash passthrough (-> flash_spi_master.v, EXP-0077): # ---- config-flash passthrough (-> flash_spi_master.v, EXP-0077):
# D00_MOSI/D01_DIN/FCS_B are the SAME physical pins the FPGA's own # EXP-0084 REAL, RE-CONFIRMED CONFLICT (not hypothetical any more):
# dedicated Master-SPI config hardware uses AT BOOT to self-load its # D00_MOSI/D01_DIN/FCS_B's ORIGINAL pins (K17/K18/L13, bank 14) share
# bitstream -- post-configuration they become ordinary fabric I/O # bank 14 with the real differential reference clock (clk_ref_p/n,
# (real Xilinx behavior, PERSIST=FALSE, the Vivado default) and this # T14/T15) chosen during the EXP-0084 MIG wizard session -- a single
# design deliberately reclaims them for the flash_spi_master.v bridge # I/O bank can only have ONE VCCO, and LVCMOS33 (3.3V, what the flash
# (the board wires the config flash EXCLUSIVELY to the FPGA -- see # needs) is incompatible with LVDS_25 (2.5V, what clk_ref needs). This
# that module's own header). CCLK is NOT constrained here -- it's # was flagged as a real *risk* when T14/T15 was chosen (real device
# driven via STARTUPE2 internally, never a plain top-level port. # data showed bank 14 already hosted the flash bus); EXP-0084's real
set_property PACKAGE_PIN K17 [get_ports flash_mosi] # place_design run turned that risk into a real, observed placement
set_property PACKAGE_PIN K18 [get_ports flash_miso] # failure ("IO placement is infeasible" -- flash_mosi/flash_cs_n
set_property PACKAGE_PIN L13 [get_ports flash_cs_n] # couldn't be placed at their old LOCs at all). FIX: moved the flash
# bus to bank 16 (D9/D10/C9) -- completely unconstrained, no VCCO
# commitment, so it can freely be 3.3V with zero conflict. Real,
# verified-available pins (queried from the actual part database, not
# guessed) -- D9/D10/C9 are ordinary I/O in bank 16, none of them are
# the config-mode-reserved EMCCLK/RDWR_B/CSI_B pins (still PROHIBITed
# below regardless). CCLK is NOT constrained here -- it's driven via
# STARTUPE2 internally, never a plain top-level port.
set_property PACKAGE_PIN D9 [get_ports flash_mosi]
set_property PACKAGE_PIN D10 [get_ports flash_miso]
set_property PACKAGE_PIN C9 [get_ports flash_cs_n]
set_property IOSTANDARD LVCMOS33 [get_ports flash_mosi] set_property IOSTANDARD LVCMOS33 [get_ports flash_mosi]
set_property IOSTANDARD LVCMOS33 [get_ports flash_miso] set_property IOSTANDARD LVCMOS33 [get_ports flash_miso]
set_property IOSTANDARD LVCMOS33 [get_ports flash_cs_n] set_property IOSTANDARD LVCMOS33 [get_ports flash_cs_n]
@@ -39,7 +49,21 @@ set_property IOSTANDARD LVCMOS33 [get_ports flash_cs_n]
# extra data lines) -- PROHIBITed so Vivado's auto-placement never # extra data lines) -- PROHIBITed so Vivado's auto-placement never
# lands an unrelated port there by accident (it already had once, # lands an unrelated port there by accident (it already had once,
# before this constraint existed, on a result-data bit). # before this constraint existed, on a result-data bit).
set_property PROHIBIT true [get_package_pins {L16 R16 V15}] set_property PROHIBIT true [get_sites -of_objects [get_package_pins {L16 R16 V15}]]
# ---- EXP-0084: remaining top-level ports with no fixed board LOC yet
# (result-data debug pins, status signals) default to LVCMOS18 with no
# explicit IOSTANDARD set -- real place_design found this real,
# concrete: banks 14/15/34/35 are ALL already committed to other real
# voltages (2.5V/3.3V/1.5V/1.5V), leaving only bank 16's spare pins as
# LVCMOS18-compatible, and there aren't enough of them (40 ports vs 10
# pins). Assign these explicitly to LVCMOS33 so they place in bank
# 15's own real spare capacity (46 free pins) instead -- a real,
# necessary fix, not a workaround; a permanent board LOC for each
# should still be assigned once the rest of the board layout is
# decided (S7 of docs/PHYSICAL_REALIZATION.md).
set_property IOSTANDARD LVCMOS33 [get_ports {s0_result_data_a[*] s0_result_data_b[*] s1_result_data_a[*] s1_result_data_b[*]}]
set_property IOSTANDARD LVCMOS33 [get_ports {job_out_slot[*] job_out_done init_calib_complete ui_clk_o}]
# ---- neural-processor management SPI (-> spi_host_bridge_v3.v): # ---- neural-processor management SPI (-> spi_host_bridge_v3.v):
# job submission + register file. Bank 15, column A/B (package edge, # job submission + register file. Bank 15, column A/B (package edge,
@@ -57,9 +81,17 @@ set_property IOSTANDARD LVCMOS33 [get_ports mosi]
set_property IOSTANDARD LVCMOS33 [get_ports miso] set_property IOSTANDARD LVCMOS33 [get_ports miso]
set_property IOSTANDARD LVCMOS33 [get_ports cs_n] set_property IOSTANDARD LVCMOS33 [get_ports cs_n]
# ---- sys_rst: not part of the DDR3 MIG's own pin set (that's # ---- sys_rst: EXP-0084 found this real -- with the 32-bit DDR3
# sys_rst too, but MIG's XDC only constrains the DDR3-facing timing, # interface now committing banks 14/34/35 to 2.5V/1.5V/1.5V and bank
# not necessarily IOSTANDARD for every board variant) -- pin left to # 15 to 3.3V, there is genuinely NO bank left at the 1.8V sys_rst was
# auto-placement for now (low pin-count, no real board decision yet # silently defaulting to (no explicit IOSTANDARD was ever set) -- real
# on where the reset source sits); explicitly constrain once the PCB # place_design failure ("IO placement is infeasible... needs 1.8V,
# layout for the reset circuit (button/supervisor IC) is decided. # has 0 sites"), not hit before only because the smaller 16-bit I/O
# footprint happened to leave enough slack somewhere. Placed here
# temporarily in bank 15 alongside the management SPI bus (same real,
# already-committed 3.3V) -- NOT a final board decision, still pending
# the real PCB layout for the reset circuit (button/supervisor IC),
# per S7 of docs/PHYSICAL_REALIZATION.md. Real, verified-free pin
# (queried from the actual part database).
set_property PACKAGE_PIN G13 [get_ports sys_rst]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst]
+53 -33
View File
@@ -15,30 +15,38 @@
// so buffering it on-chip would only add complexity for zero benefit. // so buffering it on-chip would only add complexity for zero benefit.
// This engine reads DIRECTLY from DDR3 per tile instead. // This engine reads DIRECTLY from DDR3 per tile instead.
// //
// MEMORY LAYOUT CONVENTION v2 (EXP-0081, real, disclosed, and REQUIRED // MEMORY LAYOUT CONVENTION v3 (EXP-0084, real, disclosed, and REQUIRED
// of whoever prepares activation data in DDR3 -- documented in the // of whoever prepares activation data in DDR3 -- documented in the
// physical realization doc too): TWO consecutive tiles (P_IN=8 INT8 // physical realization doc too): FOUR consecutive tiles (P_IN=8 INT8
// values each) share ONE full BURST_LEN=8-word (128-bit) burst -- even // values each, 64 bits each) share ONE full BURST_LEN=8-word burst --
// tile index in the LOW 64 bits, odd tile index in the HIGH 64 bits. // since EXP-0084's real 32-bit DDR3 channel widening, one burst is now
// Tile t's burst address is `base + (t>>1)*BURST_LEN`. This HALVES // 8*32=256 bits (up from 128 bits at the old 16-bit width), and 4
// real DDR3 bytes-moved-per-useful-byte versus the original EXP-0079 // tiles of 64 bits exactly fill it (100% utilization, same packing
// "1 tile = 1 burst" layout (real measured 1.24GB/s ceiling could only // EFFICIENCY as EXP-0081's "2 tiles fill a 128-bit burst" -- this is
// sustain ~25% of one core's peak DSP throughput under that layout -- // NOT a further bytes-per-MAC reduction beyond EXP-0081's already-
// see docs/ARCHITECTURE_ANALYSIS.md S3.2 -- this doubles the real // optimal 1 byte/MAC, it is what's REQUIRED to keep that same 100%
// achievable fraction). // utilization at the new, larger burst size instead of leaving half
// of it newly wasted). Tile index within the burst selects a quarter:
// tile parity 0/1/2/3 (tcnt[1:0]) -> bits [63:0]/[127:64]/[191:128]/
// [255:192] of the burst response. Tile t's burst address is
// `base + (t>>2)*BURST_LEN`.
// //
// WHY THIS IS TIMING-SAFE (the thing EXP-0079 deliberately avoided): // WHY THIS IS TIMING-SAFE (the thing EXP-0079 deliberately avoided):
// the tile index's own LSB (which half of the burst to use) is known // the tile index's own low 2 bits (which quarter of the burst to use)
// at REQUEST time, not at response time -- it's registered into // are known at REQUEST time, not at response time -- registered into
// `sel_lat` the SAME cycle `tcnt` is latched, many ui_clk cycles // `sel_lat` the SAME cycle `tcnt` is latched, many ui_clk cycles
// BEFORE the real DDR3 round-trip completes and `ctrl_rdata` becomes // BEFORE the real DDR3 round-trip completes and `ctrl_rdata` becomes
// valid. The eventual data-select mux therefore selects using an // valid. The eventual data-select mux (a real `case` on the registered
// already-long-stable registered bit, never a bit racing the read // 2-bit `sel_lat`, not a runtime-indexed part-select expression --
// data itself -- this is NOT the runtime-indexed-part-select-on-the- // deliberately written as explicit constant-offset case arms, see
// critical-path pattern weight_tile_gather.v's own header (EXP-0061) // below) therefore selects using an already-long-stable registered
// warned about; that pattern is about a select signal arriving // value, never bits racing the read data itself -- this is NOT the
// LATE/simultaneously with the data it gates. Confirmed via a real // runtime-indexed-part-select-on-the-critical-path pattern weight_
// P&R re-check after this change (see the log), not just asserted. // tile_gather.v's own header (EXP-0061) warned about; that pattern is
// about a select signal arriving LATE/simultaneously with the data it
// gates. Same real discipline EXP-0081 already established for the
// 1-bit case, now extended to 2 bits -- confirmed via a real P&R
// re-check after this change (see the log), not just asserted.
// //
// PROTOCOL: one request (`req` pulse + base_a/base_b/tcnt) triggers // PROTOCOL: one request (`req` pulse + base_a/base_b/tcnt) triggers
// TWO SEQUENTIAL burst reads (lane A then lane B) over the SAME // TWO SEQUENTIAL burst reads (lane A then lane B) over the SAME
@@ -74,14 +82,14 @@ module act_tile_fetch #(
output reg ctrl_req, output reg ctrl_req,
output reg ctrl_wr, output reg ctrl_wr,
output reg [ADDR_WIDTH-1:0] ctrl_addr, output reg [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata, output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask, output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata, input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready, input wire ctrl_ready,
input wire ctrl_busy input wire ctrl_busy
); );
assign ctrl_wdata = {(16*BURST_LEN){1'b0}}; assign ctrl_wdata = {(32*BURST_LEN){1'b0}};
assign ctrl_wmask = {(2*BURST_LEN){1'b0}}; // read-only engine, mask unused assign ctrl_wmask = {(4*BURST_LEN){1'b0}}; // read-only engine, mask unused
localparam S_IDLE = 3'd0, localparam S_IDLE = 3'd0,
S_MEMWAIT = 3'd1, S_MEMWAIT = 3'd1,
@@ -92,12 +100,12 @@ module act_tile_fetch #(
reg [2:0] state; reg [2:0] state;
reg [ADDR_WIDTH-1:0] base_a_lat, base_b_lat; reg [ADDR_WIDTH-1:0] base_a_lat, base_b_lat;
reg [15:0] tcnt_lat; reg [15:0] tcnt_lat;
reg sel_lat; // registered at request time -- see header reg [1:0] sel_lat; // registered at request time -- see header
assign mem_active = (state != S_IDLE); assign mem_active = (state != S_IDLE);
// burst index = tcnt/2 (integer division -- two tiles share one burst) // burst index = tcnt/4 (integer division -- four tiles share one burst)
wire [ADDR_WIDTH-1:0] tile_offset = {{(ADDR_WIDTH-15){1'b0}}, tcnt_lat[15:1]} * BURST_LEN[ADDR_WIDTH-1:0]; wire [ADDR_WIDTH-1:0] tile_offset = {{(ADDR_WIDTH-14){1'b0}}, tcnt_lat[15:2]} * BURST_LEN[ADDR_WIDTH-1:0];
always @(posedge clk) begin always @(posedge clk) begin
if (rst) begin if (rst) begin
@@ -105,7 +113,7 @@ module act_tile_fetch #(
ctrl_req <= 1'b0; ctrl_wr <= 1'b0; ctrl_addr <= {ADDR_WIDTH{1'b0}}; ctrl_req <= 1'b0; ctrl_wr <= 1'b0; ctrl_addr <= {ADDR_WIDTH{1'b0}};
valid <= 1'b0; data_a <= {(DATA_WIDTH*P_IN){1'b0}}; data_b <= {(DATA_WIDTH*P_IN){1'b0}}; valid <= 1'b0; data_a <= {(DATA_WIDTH*P_IN){1'b0}}; data_b <= {(DATA_WIDTH*P_IN){1'b0}};
base_a_lat <= {ADDR_WIDTH{1'b0}}; base_b_lat <= {ADDR_WIDTH{1'b0}}; tcnt_lat <= 16'd0; base_a_lat <= {ADDR_WIDTH{1'b0}}; base_b_lat <= {ADDR_WIDTH{1'b0}}; tcnt_lat <= 16'd0;
sel_lat <= 1'b0; sel_lat <= 2'd0;
end else begin end else begin
ctrl_req <= 1'b0; ctrl_req <= 1'b0;
valid <= 1'b0; valid <= 1'b0;
@@ -116,7 +124,7 @@ module act_tile_fetch #(
base_a_lat <= base_a; base_a_lat <= base_a;
base_b_lat <= base_b; base_b_lat <= base_b;
tcnt_lat <= tcnt; tcnt_lat <= tcnt;
sel_lat <= tcnt[0]; sel_lat <= tcnt[1:0];
state <= S_MEMWAIT; state <= S_MEMWAIT;
end end
end end
@@ -132,8 +140,16 @@ module act_tile_fetch #(
S_REQ_A: begin S_REQ_A: begin
if (ctrl_ready) begin if (ctrl_ready) begin
data_a <= sel_lat ? ctrl_rdata[DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN] // explicit constant-offset case arms, not a
: ctrl_rdata[0 +: DATA_WIDTH*P_IN]; // runtime-indexed part-select expression -- see
// header (EXP-0084, extends EXP-0081's same
// discipline from 1 to 2 select bits).
case (sel_lat)
2'd0: data_a <= ctrl_rdata[0 +: DATA_WIDTH*P_IN];
2'd1: data_a <= ctrl_rdata[64 +: DATA_WIDTH*P_IN];
2'd2: data_a <= ctrl_rdata[128 +: DATA_WIDTH*P_IN];
2'd3: data_a <= ctrl_rdata[192 +: DATA_WIDTH*P_IN];
endcase
ctrl_addr <= base_b_lat + tile_offset; ctrl_addr <= base_b_lat + tile_offset;
ctrl_wr <= 1'b0; ctrl_wr <= 1'b0;
state <= S_GAP; state <= S_GAP;
@@ -156,8 +172,12 @@ module act_tile_fetch #(
S_REQ_B: begin S_REQ_B: begin
if (ctrl_ready) begin if (ctrl_ready) begin
data_b <= sel_lat ? ctrl_rdata[DATA_WIDTH*P_IN +: DATA_WIDTH*P_IN] case (sel_lat)
: ctrl_rdata[0 +: DATA_WIDTH*P_IN]; 2'd0: data_b <= ctrl_rdata[0 +: DATA_WIDTH*P_IN];
2'd1: data_b <= ctrl_rdata[64 +: DATA_WIDTH*P_IN];
2'd2: data_b <= ctrl_rdata[128 +: DATA_WIDTH*P_IN];
2'd3: data_b <= ctrl_rdata[192 +: DATA_WIDTH*P_IN];
endcase
valid <= 1'b1; valid <= 1'b1;
state <= S_IDLE; state <= S_IDLE;
end end
+3 -3
View File
@@ -88,9 +88,9 @@ module ddr_prefetch_mgr #(
output wire ctrl_req, output wire ctrl_req,
output wire ctrl_wr, output wire ctrl_wr,
output wire [ADDR_WIDTH-1:0] ctrl_addr, output wire [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata, output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask, output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata, input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready, input wire ctrl_ready,
input wire ctrl_busy input wire ctrl_busy
); );
+58 -29
View File
@@ -7,20 +7,30 @@
// 16-bit-WORD req/wr/addr/wdata/lb_n/ub_n -> rdata/ready port (the // 16-bit-WORD req/wr/addr/wdata/lb_n/ub_n -> rdata/ready port (the
// SAME shape as V2's real psram_controller.v / sdram_unified_ // SAME shape as V2's real psram_controller.v / sdram_unified_
// backend.v AR port), but V3's shared memory path (sdram_arbiter_n.v // backend.v AR port), but V3's shared memory path (sdram_arbiter_n.v
// -> mig_native_adapter.v) only understands BURST_LEN=8 (128-bit) // -> mig_native_adapter.v) only understands BURST_LEN=8, 32-bit-word
// chunks. This module is the translator, matching sdram_unified_ // (256-bit) chunks (EXP-0084's real DDR3 widening -- was 128-bit
// before). This module is the translator, matching sdram_unified_
// backend.v's own AR-port technique exactly (not reinvented): a // backend.v's own AR-port technique exactly (not reinvented): a
// write masks out every word in the burst except the target one // write masks out every byte in the burst except the target 16-bit
// (DQM-style byte masking, already how this project's whole memory // half-word (DQM-style byte masking, already how this project's whole
// stack works); a read fetches the whole burst and extracts the // memory stack works); a read fetches the whole burst and extracts
// target word combinationally. // the target half-word combinationally.
// //
// Sits as one requester on sdram_arbiter_n.v (alongside N packed_ // EXP-0084 ADDRESSING NOTE (real, deliberate design choice, not just a
// slot.v instances) -- `active` is asserted for the WHOLE single-word // mechanical width bump): the HOST-facing contract (mem_addr as a
// transaction (word-granularity, no multi-burst sequencing needed), // 16-bit-word address, mem_wdata/mem_rdata as 16-bit values,
// so mem_grant only needs to be observed once before the one-shot // mem_lb_n/mem_ub_n as byte enables) is kept COMPLETELY UNCHANGED --
// ctrl_req fires, same discipline as packed_slot.v's own S_MEMWAIT // this module stays the shock absorber between the host's own fixed
// (EXP-0066's real, hard-won lesson). // 16-bit-word view (spi_host_bridge_v3.v's WRITE_MEM/READ_MEM opcode
// payload size, and by extension the ESP32 firmware contract, is
// NOT touched by the DDR3 widening) and the system's now-32-bit-word-
// native shared ctrl bus. mem_addr's LSB now additionally selects
// WHICH 16-bit half of the addressed 32-bit ctrl-bus word to target;
// this halves the host's own reachable byte range for a given
// ADDR_WIDTH (a real, honestly-disclosed, non-blocking limitation of
// this debug/raw-access path only -- not the compute path, and not
// currently a practical constraint at this project's real usage
// scale).
// ============================================================ // ============================================================
module host_mem_bridge #( module host_mem_bridge #(
parameter BURST_LEN = 8, parameter BURST_LEN = 8,
@@ -31,10 +41,11 @@ module host_mem_bridge #(
// ---- host-facing port (matches spi_host_bridge.v's own // ---- host-facing port (matches spi_host_bridge.v's own
// mem_req/mem_wr/mem_addr/mem_wdata/mem_lb_n/mem_ub_n -> // mem_req/mem_wr/mem_addr/mem_wdata/mem_lb_n/mem_ub_n ->
// mem_rdata/mem_ready convention exactly) ---- // mem_rdata/mem_ready convention exactly -- UNCHANGED by EXP-0084,
// see header) ----
input wire mem_req, input wire mem_req,
input wire mem_wr, input wire mem_wr,
input wire [ADDR_WIDTH-1:0] mem_addr, // WORD address (not burst-aligned) input wire [ADDR_WIDTH-1:0] mem_addr, // 16-bit-word address (not burst-aligned)
input wire [15:0] mem_wdata, input wire [15:0] mem_wdata,
input wire mem_lb_n, input wire mem_lb_n,
input wire mem_ub_n, input wire mem_ub_n,
@@ -49,13 +60,13 @@ module host_mem_bridge #(
output reg req_req, output reg req_req,
output reg req_wr, output reg req_wr,
output reg [ADDR_WIDTH-1:0] req_addr, output reg [ADDR_WIDTH-1:0] req_addr,
output reg [16*BURST_LEN-1:0] req_wdata, output reg [32*BURST_LEN-1:0] req_wdata,
output reg [2*BURST_LEN-1:0] req_wmask, output reg [4*BURST_LEN-1:0] req_wmask,
input wire [16*BURST_LEN-1:0] req_rdata, input wire [32*BURST_LEN-1:0] req_rdata,
input wire req_ready, input wire req_ready,
input wire req_busy input wire req_busy
); );
localparam ALIGN_BITS = $clog2(BURST_LEN); localparam ALIGN_BITS = $clog2(BURST_LEN); // 3: which of the BURST_LEN 32-bit words in the burst
localparam S_IDLE = 2'd0, localparam S_IDLE = 2'd0,
S_MEMWAIT = 2'd1, S_MEMWAIT = 2'd1,
@@ -63,10 +74,16 @@ module host_mem_bridge #(
S_DONE = 2'd3; S_DONE = 2'd3;
reg [1:0] state; reg [1:0] state;
reg [ALIGN_BITS-1:0] word_in_block; reg [ALIGN_BITS-1:0] word_in_block; // which 32-bit word within the burst
reg half_sel; // which 16-bit half of that 32-bit word
assign req_active = (state == S_MEMWAIT) || (state == S_XFER); assign req_active = (state == S_MEMWAIT) || (state == S_XFER);
// byte offset (0..4*BURST_LEN-2, even) of the target half-word
// within the burst's own byte layout -- word_in_block*4 bytes/word
// + half_sel*2 bytes/half.
wire [ALIGN_BITS+1:0] byte_offset = {word_in_block, half_sel, 1'b0};
always @(posedge clk) begin always @(posedge clk) begin
if (rst) begin if (rst) begin
state <= S_IDLE; state <= S_IDLE;
@@ -79,18 +96,30 @@ module host_mem_bridge #(
case (state) case (state)
S_IDLE: begin S_IDLE: begin
if (mem_req) begin if (mem_req) begin
req_addr <= {mem_addr[ADDR_WIDTH-1:ALIGN_BITS], {ALIGN_BITS{1'b0}}}; // mem_addr[0] = which 16-bit half of the 32-bit
word_in_block <= mem_addr[ALIGN_BITS-1:0]; // ctrl-bus word; mem_addr[ALIGN_BITS:1] = which
// of the BURST_LEN 32-bit words in the burst;
// the remaining upper bits, re-aligned to
// 32-bit-word (ctrl bus) units, form the
// burst-aligned req_addr.
req_addr <= {1'b0, mem_addr[ADDR_WIDTH-1:ALIGN_BITS+1], {ALIGN_BITS{1'b0}}};
word_in_block <= mem_addr[ALIGN_BITS:1];
half_sel <= mem_addr[0];
req_wr <= mem_wr; req_wr <= mem_wr;
if (mem_wr) begin if (mem_wr) begin
// replicate the target word across the whole // replicate the target half-word across the
// burst; only its own mask bits matter (see // whole burst; only its own mask bits matter
// header -- same DQM-style technique as // (see header -- same DQM-style technique as
// sdram_unified_backend.v's own AR port). // sdram_unified_backend.v's own AR port).
req_wdata <= {BURST_LEN{mem_wdata}}; // byte offset within the burst is computed
req_wmask <= {(2*BURST_LEN){1'b1}} & // directly from the LIVE mem_addr here (not
~(({{(2*BURST_LEN-2){1'b0}}, 2'b11}) << (mem_addr[ALIGN_BITS-1:0]*2)) | // from word_in_block/half_sel, which are
(({{(2*BURST_LEN-2){1'b0}}, mem_ub_n, mem_lb_n}) << (mem_addr[ALIGN_BITS-1:0]*2)); // nonblocking-assigned THIS same cycle and
// not yet valid until the next one).
req_wdata <= {(4*BURST_LEN/2){mem_wdata}};
req_wmask <= ({(4*BURST_LEN){1'b1}} &
~(({{(4*BURST_LEN-2){1'b0}}, 2'b11}) << {mem_addr[ALIGN_BITS:0], 1'b0})) |
(({{(4*BURST_LEN-2){1'b0}}, mem_ub_n, mem_lb_n}) << {mem_addr[ALIGN_BITS:0], 1'b0});
end end
state <= S_MEMWAIT; state <= S_MEMWAIT;
end end
@@ -106,7 +135,7 @@ module host_mem_bridge #(
S_XFER: begin S_XFER: begin
if (req_ready) begin if (req_ready) begin
if (!req_wr) if (!req_wr)
mem_rdata <= req_rdata[word_in_block*16 +: 16]; mem_rdata <= req_rdata[byte_offset*8 +: 16];
state <= S_DONE; state <= S_DONE;
end end
end end
+37 -31
View File
@@ -3,12 +3,12 @@
// ============================================================ // ============================================================
// V3 -- adapter between this project's own established memory- // V3 -- adapter between this project's own established memory-
// controller contract (req/wr/addr/wdata/wmask -> rdata/ready/busy, // controller contract (req/wr/addr/wdata/wmask -> rdata/ready/busy,
// BURST_LEN=8 16-bit words = 128 bits/transaction, the SAME shape // BURST_LEN=8 32-bit words = 256 bits/transaction since EXP-0084's
// sdram_controller.v has presented everywhere in this project since // real 32-bit DDR3 channel widening -- was 16-bit/128 bits before)
// STEP16) and the REAL Xilinx MIG 7-series native "app" user // and the REAL Xilinx MIG 7-series native "app" user interface
// interface (PG063), generated for this project's actual DDR3 target // (PG063), generated for this project's actual DDR3 target
// (mig_7series_0, XC7A100T, MT41J128M16JT-125:K, PHY:Controller // (mig_7series_0, XC7A100T, 2x MT41J128M16JT-125:K ganged to 32-bit,
// ratio 2:1). // PHY:Controller ratio 2:1, 2900ps/344.827MHz, EXP-0084).
// //
// Runs entirely in the ui_clk domain -- MIG's own generated clock is // Runs entirely in the ui_clk domain -- MIG's own generated clock is
// this design's new system clock (the standard way MIG-based designs // this design's new system clock (the standard way MIG-based designs
@@ -17,14 +17,20 @@
// synchronized to ui_clk by the caller. // synchronized to ui_clk by the caller.
// //
// ADDRESSING (real, derived from THIS project's actual generated MIG // ADDRESSING (real, derived from THIS project's actual generated MIG
// config, not assumed): Data Width=16, Phy:Controller ratio 2:1 => // config, not assumed, re-verified after EXP-0084's 32-bit
// nCK_PER_CLK=2 => app data width = 16*8/2 = 64 bits, matching the // regeneration): Data Width=32, Phy:Controller ratio 2:1 =>
// real generated mig_7series_0.v port widths exactly (app_wdf_data // nCK_PER_CLK=2 => app data width = 32*8/2 = 128 bits, matching the
// [63:0], app_rd_data[63:0]). One app_addr/app_cmd issuance moves a // real regenerated mig_7series_0.v port widths exactly (app_wdf_data
// FULL BURST_LEN=8 (128-bit) DDR3 burst, delivered as TWO 64-bit // [127:0], app_rd_data[127:0], app_wdf_mask[15:0] -- confirmed by
// beats on the app data bus -- so app_addr increments in the SAME // directly reading the real generated wrapper, not assumed from the
// unit as this project's own existing ctrl_addr (one BURST_LEN=8 // old 16-bit numbers). app_addr itself stayed 28 bits (confirmed same
// chunk per increment), no address scaling needed at this boundary. // real generated width as before the widening) -- one app_addr/
// app_cmd issuance still moves a FULL BURST_LEN=8 chunk per increment,
// same as before, just each chunk is now 256 bits (32-bit words) not
// 128 bits (16-bit words), delivered as TWO 128-bit beats instead of
// two 64-bit beats -- so app_addr increments in the SAME unit as this
// project's own existing ctrl_addr, no address scaling needed at this
// boundary, same as before.
// //
// Sequencing is deliberately fully sequential, not pipelined // Sequencing is deliberately fully sequential, not pipelined
// (correctness first): the command is issued and accepted (app_en/ // (correctness first): the command is issued and accepted (app_en/
@@ -52,9 +58,9 @@ module mig_native_adapter #(
input wire req, input wire req,
input wire wr, input wire wr,
input wire [ADDR_WIDTH-1:0] addr, input wire [ADDR_WIDTH-1:0] addr,
input wire [16*BURST_LEN-1:0] wdata, input wire [32*BURST_LEN-1:0] wdata,
input wire [2*BURST_LEN-1:0] wmask, input wire [4*BURST_LEN-1:0] wmask,
output reg [16*BURST_LEN-1:0] rdata, output reg [32*BURST_LEN-1:0] rdata,
output reg ready, output reg ready,
output wire busy, output wire busy,
@@ -64,13 +70,13 @@ module mig_native_adapter #(
output reg app_en, output reg app_en,
input wire app_rdy, input wire app_rdy,
output reg [63:0] app_wdf_data, output reg [127:0] app_wdf_data,
output reg app_wdf_end, output reg app_wdf_end,
output reg [7:0] app_wdf_mask, output reg [15:0] app_wdf_mask,
output reg app_wdf_wren, output reg app_wdf_wren,
input wire app_wdf_rdy, input wire app_wdf_rdy,
input wire [63:0] app_rd_data, input wire [127:0] app_rd_data,
input wire app_rd_data_end, input wire app_rd_data_end,
input wire app_rd_data_valid input wire app_rd_data_valid
); );
@@ -86,8 +92,8 @@ module mig_native_adapter #(
reg [2:0] state; reg [2:0] state;
reg wr_lat; reg wr_lat;
reg [16*BURST_LEN-1:0] wdata_lat; reg [32*BURST_LEN-1:0] wdata_lat;
reg [2*BURST_LEN-1:0] wmask_lat; reg [4*BURST_LEN-1:0] wmask_lat;
assign busy = (state != S_IDLE); assign busy = (state != S_IDLE);
@@ -98,11 +104,11 @@ module mig_native_adapter #(
app_wdf_wren <= 1'b0; app_wdf_wren <= 1'b0;
app_wdf_end <= 1'b0; app_wdf_end <= 1'b0;
ready <= 1'b0; ready <= 1'b0;
rdata <= {(16*BURST_LEN){1'b0}}; rdata <= {(32*BURST_LEN){1'b0}};
app_addr <= 28'h0; app_addr <= 28'h0;
app_cmd <= CMD_READ; app_cmd <= CMD_READ;
app_wdf_data <= 64'h0; app_wdf_data <= 128'h0;
app_wdf_mask <= 8'h0; app_wdf_mask <= 16'h0;
end else begin end else begin
ready <= 1'b0; ready <= 1'b0;
@@ -123,8 +129,8 @@ module mig_native_adapter #(
if (app_rdy) begin if (app_rdy) begin
app_en <= 1'b0; app_en <= 1'b0;
if (wr_lat) begin if (wr_lat) begin
app_wdf_data <= wdata_lat[63:0]; app_wdf_data <= wdata_lat[127:0];
app_wdf_mask <= wmask_lat[7:0]; app_wdf_mask <= wmask_lat[15:0];
app_wdf_end <= 1'b0; app_wdf_end <= 1'b0;
app_wdf_wren <= 1'b1; app_wdf_wren <= 1'b1;
state <= S_WDF0; state <= S_WDF0;
@@ -136,8 +142,8 @@ module mig_native_adapter #(
S_WDF0: begin S_WDF0: begin
if (app_wdf_rdy) begin if (app_wdf_rdy) begin
app_wdf_data <= wdata_lat[127:64]; app_wdf_data <= wdata_lat[255:128];
app_wdf_mask <= wmask_lat[15:8]; app_wdf_mask <= wmask_lat[31:16];
app_wdf_end <= 1'b1; app_wdf_end <= 1'b1;
app_wdf_wren <= 1'b1; app_wdf_wren <= 1'b1;
state <= S_WDF1; state <= S_WDF1;
@@ -155,9 +161,9 @@ module mig_native_adapter #(
S_RD_WAIT: begin S_RD_WAIT: begin
if (app_rd_data_valid) begin if (app_rd_data_valid) begin
if (!app_rd_data_end) begin if (!app_rd_data_end) begin
rdata[63:0] <= app_rd_data; rdata[127:0] <= app_rd_data;
end else begin end else begin
rdata[127:64] <= app_rd_data; rdata[255:128] <= app_rd_data;
state <= S_DONE; state <= S_DONE;
end end
end end
+45 -28
View File
@@ -14,9 +14,16 @@
// packed_slot instances, and the SPI bridge itself) runs in the // packed_slot instances, and the SPI bridge itself) runs in the
// ui_clk domain, per this project's own standing convention // ui_clk domain, per this project's own standing convention
// (mig_native_adapter.v's header) -- ui_clk is generated BY the MIG // (mig_native_adapter.v's header) -- ui_clk is generated BY the MIG
// from sys_clk_i, so this module only takes sys_clk_i/clk_ref_i/ // from sys_clk_p/n, so this module only takes sys_clk_p/n/clk_ref_p/n/
// sys_rst as clock/reset inputs, not a separate system clock. // sys_rst as clock/reset inputs, not a separate system clock.
// //
// EXP-0084: sys_clk and clk_ref are now real DIFFERENTIAL pairs (the
// user's own wizard choice, confirmed against the real regenerated
// public mig_7series_0.v wrapper, which expects sys_clk_p/n and
// clk_ref_p/n directly as raw top-level differential pins -- not a
// single-ended sys_clk_i/clk_ref_i pair any more). Real board
// implication: a differential oscillator, not a single-ended one.
//
// This is the first REAL (in-context, not out-of-context) P&R target // This is the first REAL (in-context, not out-of-context) P&R target
// for V3: previous P&R runs (EXP-0059/63/67) were all out-of-context // for V3: previous P&R runs (EXP-0059/63/67) were all out-of-context
// synthesis of a sub-block, without the real MIG-generated pin/timing // synthesis of a sub-block, without the real MIG-generated pin/timing
@@ -42,15 +49,25 @@ module n2_system_ddr3_top #(
parameter N_SLOTS = 2, parameter N_SLOTS = 2,
parameter QUEUE_DEPTH = 8 parameter QUEUE_DEPTH = 8
)( )(
// ---- MIG clock/reset ---- // ---- MIG clock/reset (EXP-0084: real differential pairs, both
input wire sys_clk_i, // the user's own wizard choice -- confirmed against the real
// regenerated public mig_7series_0.v wrapper, which now expects
// sys_clk_p/n and clk_ref_p/n directly as raw top-level
// differential pins, not a single-ended sys_clk_i/clk_ref_i pair
// any more) ----
input wire sys_clk_p,
input wire sys_clk_n,
input wire sys_rst, input wire sys_rst,
input wire clk_ref_i, input wire clk_ref_p,
input wire clk_ref_n,
// ---- real DDR3 pins (matches mig_7series_0.xdc's own port names) ---- // ---- real DDR3 pins (matches mig_7series_0.xdc's own port names;
inout wire [15:0] ddr3_dq, // dq/dqs/dm widths doubled since EXP-0084's real 32-bit widening --
inout wire [1:0] ddr3_dqs_n, // two MT41J128M16 chips ganged in parallel, confirmed against the
inout wire [1:0] ddr3_dqs_p, // real regenerated mig_7series_0.v wrapper, not assumed) ----
inout wire [31:0] ddr3_dq,
inout wire [3:0] ddr3_dqs_n,
inout wire [3:0] ddr3_dqs_p,
output wire [13:0] ddr3_addr, output wire [13:0] ddr3_addr,
output wire [2:0] ddr3_ba, output wire [2:0] ddr3_ba,
output wire ddr3_ras_n, output wire ddr3_ras_n,
@@ -61,7 +78,7 @@ module n2_system_ddr3_top #(
output wire [0:0] ddr3_ck_n, output wire [0:0] ddr3_ck_n,
output wire [0:0] ddr3_cke, output wire [0:0] ddr3_cke,
output wire [0:0] ddr3_cs_n, output wire [0:0] ddr3_cs_n,
output wire [1:0] ddr3_dm, output wire [3:0] ddr3_dm,
output wire [0:0] ddr3_odt, output wire [0:0] ddr3_odt,
// ---- physical SPI host interface (-> spi_host_bridge_v3.v) ---- // ---- physical SPI host interface (-> spi_host_bridge_v3.v) ----
@@ -98,11 +115,11 @@ module n2_system_ddr3_top #(
wire [27:0] app_addr; wire [27:0] app_addr;
wire [2:0] app_cmd; wire [2:0] app_cmd;
wire app_en, app_rdy; wire app_en, app_rdy;
wire [63:0] app_wdf_data; wire [127:0] app_wdf_data;
wire app_wdf_end; wire app_wdf_end;
wire [7:0] app_wdf_mask; wire [15:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy; wire app_wdf_wren, app_wdf_rdy;
wire [63:0] app_rd_data; wire [127:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid; wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst; wire ui_clk, ui_clk_sync_rst;
@@ -120,7 +137,7 @@ module n2_system_ddr3_top #(
.ddr3_ck_p(ddr3_ck_p), .ddr3_ck_n(ddr3_ck_n), .ddr3_ck_p(ddr3_ck_p), .ddr3_ck_n(ddr3_ck_n),
.ddr3_cke(ddr3_cke), .ddr3_cs_n(ddr3_cs_n), .ddr3_cke(ddr3_cke), .ddr3_cs_n(ddr3_cs_n),
.ddr3_dm(ddr3_dm), .ddr3_odt(ddr3_odt), .ddr3_dm(ddr3_dm), .ddr3_odt(ddr3_odt),
.sys_clk_i(sys_clk_i), .clk_ref_i(clk_ref_i), .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_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_data(app_wdf_data), .app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren), .app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren),
@@ -136,9 +153,9 @@ module n2_system_ddr3_top #(
wire adp_req, adp_wr; wire adp_req, adp_wr;
wire [MEM_ADDR_WIDTH-1:0] adp_addr; wire [MEM_ADDR_WIDTH-1:0] adp_addr;
wire [16*BURST_LEN-1:0] adp_wdata; wire [32*BURST_LEN-1:0] adp_wdata;
wire [2*BURST_LEN-1:0] adp_wmask; wire [4*BURST_LEN-1:0] adp_wmask;
wire [16*BURST_LEN-1:0] adp_rdata; wire [32*BURST_LEN-1:0] adp_rdata;
wire adp_ready, adp_busy; wire adp_ready, adp_busy;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MEM_ADDR_WIDTH)) u_adapter ( mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MEM_ADDR_WIDTH)) u_adapter (
@@ -156,9 +173,9 @@ module n2_system_ddr3_top #(
wire [NUM_REQ-1:0] req_active, req_grant, req_req, req_wr; wire [NUM_REQ-1:0] req_active, req_grant, req_req, req_wr;
wire [NUM_REQ-1:0] req_ready, req_busy; wire [NUM_REQ-1:0] req_ready, req_busy;
wire [NUM_REQ*MEM_ADDR_WIDTH-1:0] req_addr; wire [NUM_REQ*MEM_ADDR_WIDTH-1:0] req_addr;
wire [NUM_REQ*16*BURST_LEN-1:0] req_wdata; wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata;
wire [NUM_REQ*2*BURST_LEN-1:0] req_wmask; wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*16*BURST_LEN-1:0] req_rdata; wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata;
sdram_arbiter_n #( sdram_arbiter_n #(
.NUM_REQ(NUM_REQ), .ADDR_WIDTH(MEM_ADDR_WIDTH), .BURST_LEN(BURST_LEN) .NUM_REQ(NUM_REQ), .ADDR_WIDTH(MEM_ADDR_WIDTH), .BURST_LEN(BURST_LEN)
@@ -251,9 +268,9 @@ module n2_system_ddr3_top #(
.req_active(req_active[2]), .req_grant(req_grant[2]), .req_active(req_active[2]), .req_grant(req_grant[2]),
.req_req(req_req[2]), .req_wr(req_wr[2]), .req_req(req_req[2]), .req_wr(req_wr[2]),
.req_addr(req_addr[2*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]), .req_addr(req_addr[2*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.req_wdata(req_wdata[2*16*BURST_LEN +: 16*BURST_LEN]), .req_wdata(req_wdata[2*32*BURST_LEN +: 32*BURST_LEN]),
.req_wmask(req_wmask[2*2*BURST_LEN +: 2*BURST_LEN]), .req_wmask(req_wmask[2*4*BURST_LEN +: 4*BURST_LEN]),
.req_rdata(req_rdata[2*16*BURST_LEN +: 16*BURST_LEN]), .req_rdata(req_rdata[2*32*BURST_LEN +: 32*BURST_LEN]),
.req_ready(req_ready[2]), .req_busy(req_busy[2]) .req_ready(req_ready[2]), .req_busy(req_busy[2])
); );
@@ -288,9 +305,9 @@ module n2_system_ddr3_top #(
.mem_active(req_active[0]), .mem_grant(req_grant[0]), .mem_active(req_active[0]), .mem_grant(req_grant[0]),
.ctrl_req(req_req[0]), .ctrl_wr(req_wr[0]), .ctrl_req(req_req[0]), .ctrl_wr(req_wr[0]),
.ctrl_addr(req_addr[0*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]), .ctrl_addr(req_addr[0*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[0*16*BURST_LEN +: 16*BURST_LEN]), .ctrl_wdata(req_wdata[0*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(req_wmask[0*2*BURST_LEN +: 2*BURST_LEN]), .ctrl_wmask(req_wmask[0*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(req_rdata[0*16*BURST_LEN +: 16*BURST_LEN]), .ctrl_rdata(req_rdata[0*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(req_ready[0]), .ctrl_busy(req_busy[0]) .ctrl_ready(req_ready[0]), .ctrl_busy(req_busy[0])
); );
@@ -314,9 +331,9 @@ module n2_system_ddr3_top #(
.mem_active(req_active[1]), .mem_grant(req_grant[1]), .mem_active(req_active[1]), .mem_grant(req_grant[1]),
.ctrl_req(req_req[1]), .ctrl_wr(req_wr[1]), .ctrl_req(req_req[1]), .ctrl_wr(req_wr[1]),
.ctrl_addr(req_addr[1*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]), .ctrl_addr(req_addr[1*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[1*16*BURST_LEN +: 16*BURST_LEN]), .ctrl_wdata(req_wdata[1*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(req_wmask[1*2*BURST_LEN +: 2*BURST_LEN]), .ctrl_wmask(req_wmask[1*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(req_rdata[1*16*BURST_LEN +: 16*BURST_LEN]), .ctrl_rdata(req_rdata[1*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_ready(req_ready[1]), .ctrl_busy(req_busy[1]) .ctrl_ready(req_ready[1]), .ctrl_busy(req_busy[1])
); );
endmodule endmodule
+7 -7
View File
@@ -107,9 +107,9 @@ module packed_slot #(
output wire ctrl_req, output wire ctrl_req,
output wire ctrl_wr, output wire ctrl_wr,
output wire [ADDR_WIDTH-2:0] ctrl_addr, output wire [ADDR_WIDTH-2:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata, output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask, output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata, input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready, input wire ctrl_ready,
input wire ctrl_busy input wire ctrl_busy
); );
@@ -140,8 +140,8 @@ module packed_slot #(
wire pf_ctrl_req, pf_ctrl_wr; wire pf_ctrl_req, pf_ctrl_wr;
wire [ADDR_WIDTH-2:0] pf_ctrl_addr; wire [ADDR_WIDTH-2:0] pf_ctrl_addr;
wire [16*BURST_LEN-1:0] pf_ctrl_wdata; wire [32*BURST_LEN-1:0] pf_ctrl_wdata;
wire [2*BURST_LEN-1:0] pf_ctrl_wmask; wire [4*BURST_LEN-1:0] pf_ctrl_wmask;
layer_prefetch_ctrl #( layer_prefetch_ctrl #(
.DATA_WIDTH(DATA_WIDTH), .LAYER_BYTES(LAYER_BYTES), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1) .DATA_WIDTH(DATA_WIDTH), .LAYER_BYTES(LAYER_BYTES), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
@@ -167,8 +167,8 @@ module packed_slot #(
wire act_ctrl_req, act_ctrl_wr; wire act_ctrl_req, act_ctrl_wr;
wire [ADDR_WIDTH-2:0] act_ctrl_addr; wire [ADDR_WIDTH-2:0] act_ctrl_addr;
wire [16*BURST_LEN-1:0] act_ctrl_wdata; wire [32*BURST_LEN-1:0] act_ctrl_wdata;
wire [2*BURST_LEN-1:0] act_ctrl_wmask; wire [4*BURST_LEN-1:0] act_ctrl_wmask;
ddr_prefetch_mgr #( ddr_prefetch_mgr #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1) .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH-1)
+13 -13
View File
@@ -44,18 +44,18 @@ module sdram_arbiter_n #(
input wire [NUM_REQ-1:0] req_req, input wire [NUM_REQ-1:0] req_req,
input wire [NUM_REQ-1:0] req_wr, input wire [NUM_REQ-1:0] req_wr,
input wire [NUM_REQ*ADDR_WIDTH-1:0] req_addr, input wire [NUM_REQ*ADDR_WIDTH-1:0] req_addr,
input wire [NUM_REQ*16*BURST_LEN-1:0] req_wdata, input wire [NUM_REQ*32*BURST_LEN-1:0] req_wdata,
input wire [NUM_REQ*2*BURST_LEN-1:0] req_wmask, input wire [NUM_REQ*4*BURST_LEN-1:0] req_wmask,
output wire [NUM_REQ*16*BURST_LEN-1:0] req_rdata, output wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata,
output wire [NUM_REQ-1:0] req_ready, output wire [NUM_REQ-1:0] req_ready,
output wire [NUM_REQ-1:0] req_busy, output wire [NUM_REQ-1:0] req_busy,
output wire ctrl_req, output wire ctrl_req,
output wire ctrl_wr, output wire ctrl_wr,
output wire [ADDR_WIDTH-1:0] ctrl_addr, output wire [ADDR_WIDTH-1:0] ctrl_addr,
output wire [16*BURST_LEN-1:0] ctrl_wdata, output wire [32*BURST_LEN-1:0] ctrl_wdata,
output wire [2*BURST_LEN-1:0] ctrl_wmask, output wire [4*BURST_LEN-1:0] ctrl_wmask,
input wire [16*BURST_LEN-1:0] ctrl_rdata, input wire [32*BURST_LEN-1:0] ctrl_rdata,
input wire ctrl_ready, input wire ctrl_ready,
input wire ctrl_busy input wire ctrl_busy
); );
@@ -107,22 +107,22 @@ module sdram_arbiter_n #(
// mux request-side signals from the granted requester -> shared ctrl // mux request-side signals from the granted requester -> shared ctrl
reg m_req, m_wr; reg m_req, m_wr;
reg [ADDR_WIDTH-1:0] m_addr; reg [ADDR_WIDTH-1:0] m_addr;
reg [16*BURST_LEN-1:0] m_wdata; reg [32*BURST_LEN-1:0] m_wdata;
reg [2*BURST_LEN-1:0] m_wmask; reg [4*BURST_LEN-1:0] m_wmask;
integer mi; integer mi;
always @(*) begin always @(*) begin
m_req = 1'b0; m_req = 1'b0;
m_wr = 1'b0; m_wr = 1'b0;
m_addr = {ADDR_WIDTH{1'b0}}; m_addr = {ADDR_WIDTH{1'b0}};
m_wdata = {(16*BURST_LEN){1'b0}}; m_wdata = {(32*BURST_LEN){1'b0}};
m_wmask = {(2*BURST_LEN){1'b0}}; m_wmask = {(4*BURST_LEN){1'b0}};
for (mi = 0; mi < NUM_REQ; mi = mi + 1) begin for (mi = 0; mi < NUM_REQ; mi = mi + 1) begin
if (sel[mi]) begin if (sel[mi]) begin
m_req = req_req[mi]; m_req = req_req[mi];
m_wr = req_wr[mi]; m_wr = req_wr[mi];
m_addr = req_addr[mi*ADDR_WIDTH +: ADDR_WIDTH]; m_addr = req_addr[mi*ADDR_WIDTH +: ADDR_WIDTH];
m_wdata = req_wdata[mi*16*BURST_LEN +: 16*BURST_LEN]; m_wdata = req_wdata[mi*32*BURST_LEN +: 32*BURST_LEN];
m_wmask = req_wmask[mi*2*BURST_LEN +: 2*BURST_LEN]; m_wmask = req_wmask[mi*4*BURST_LEN +: 4*BURST_LEN];
end end
end end
end end
@@ -137,7 +137,7 @@ module sdram_arbiter_n #(
genvar gd; genvar gd;
generate generate
for (gd = 0; gd < NUM_REQ; gd = gd + 1) begin : GEN_DEMUX for (gd = 0; gd < NUM_REQ; gd = gd + 1) begin : GEN_DEMUX
assign req_rdata[gd*16*BURST_LEN +: 16*BURST_LEN] = ctrl_rdata; assign req_rdata[gd*32*BURST_LEN +: 32*BURST_LEN] = ctrl_rdata;
assign req_ready[gd] = sel[gd] ? ctrl_ready : 1'b0; assign req_ready[gd] = sel[gd] ? ctrl_ready : 1'b0;
assign req_busy[gd] = sel[gd] ? ctrl_busy : 1'b1; assign req_busy[gd] = sel[gd] ? ctrl_busy : 1'b1;
end end
+109
View File
@@ -0,0 +1,109 @@
`timescale 1ns/1ps
// ============================================================
// EXP-0084 -- minimal, EXPLICITLY SYNTHETIC 32-bit-wide burst-memory
// test model. NOT a real chip model (unlike sdram_controller.v/
// sdram_model.v, which genuinely represent the real AS4C32M16SA x16
// SDR part this project also uses) -- that real model is inherently
// fixed at 16-bit words (a real hardware fact, not a parameter choice)
// and is shared by 20+ other testbenches across v2 and v3, so it is
// deliberately NOT modified here. This file exists purely so the
// isolated, fast (iverilog) testbenches for modules that now speak
// this project's real 32-bit ctrl bus convention (EXP-0084's DDR3
// widening) have a same-shape, functionally-correct backend to run
// against WITHOUT needing the full real MIG IP + ddr3_model.sv (real
// xsim, much slower) for every isolated glue-logic check -- matching
// this project's own established "verify new glue logic against a
// fast backend first" precedent (tb_act_tile_fetch.v's own header),
// just re-pointed at a backend that actually matches the current real
// bus width. The REAL, trustworthy, board-accurate verification still
// comes from tb_n2_system_ddr3.v against the real ddr3_model.sv, same
// as always -- this model's own fixed latency is a plausible, but NOT
// claimed-real, stand-in.
//
// Small DENSE backing store (2^MEM_ADDR_BITS entries), not a full
// 2^ADDR_WIDTH array -- ADDR_WIDTH=25 would need ~1GB densely
// allocated for no reason; every real test in this project only ever
// touches small, low addresses. MEM_ADDR_BITS=20 (~1M entries, ~32MB
// of simulation memory) comfortably covers any realistic test address
// -- including tb_packed_slot.v's own ACT_MEM_BASE=0x10000 region,
// which a first version of this model sized at 16 bits (65536
// entries) silently WRAPPED to address 0, aliasing weight and
// activation data and producing real, confusing wrong-answer failures
// (found via real simulation, not by inspection -- see EXP-0084's
// log for the full root-cause trace). Staying portable (Icarus's
// associative-array support for a packed-vector key type turned out
// not to work for this purpose -- found via a real elaboration
// error, not assumed).
// ============================================================
module burst_mem_model32 #(
parameter BURST_LEN = 8,
parameter ADDR_WIDTH = 25,
parameter MEM_ADDR_BITS = 20,
parameter LATENCY = 6 // fixed req->ready cycles, a plausible stand-in, not claimed real
)(
input wire clk,
input wire rst,
input wire req,
input wire wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire [32*BURST_LEN-1:0] wdata,
input wire [4*BURST_LEN-1:0] wmask,
output reg [32*BURST_LEN-1:0] rdata,
output reg ready,
output wire busy
);
reg [32*BURST_LEN-1:0] mem [0:(1<<MEM_ADDR_BITS)-1];
localparam S_IDLE = 2'd0, S_BUSY = 2'd1, S_DONE = 2'd2;
reg [1:0] state;
reg [7:0] cnt;
reg [ADDR_WIDTH-1:0] addr_lat;
reg wr_lat;
reg [32*BURST_LEN-1:0] wdata_lat;
reg [4*BURST_LEN-1:0] wmask_lat;
integer bi;
assign busy = (state != S_IDLE);
always @(posedge clk) begin
if (rst) begin
state <= S_IDLE;
ready <= 1'b0;
cnt <= 8'd0;
end else begin
ready <= 1'b0;
case (state)
S_IDLE: begin
if (req) begin
addr_lat <= addr;
wr_lat <= wr;
wdata_lat <= wdata;
wmask_lat <= wmask;
cnt <= LATENCY[7:0];
state <= S_BUSY;
end
end
S_BUSY: begin
if (cnt == 8'd1) state <= S_DONE;
else cnt <= cnt - 8'd1;
end
S_DONE: begin
if (wr_lat) begin
// real DQM polarity (matches sdram_controller.v's
// own convention): 0=write that byte, 1=masked.
for (bi = 0; bi < 4*BURST_LEN; bi = bi + 1)
if (!wmask_lat[bi])
mem[addr_lat[MEM_ADDR_BITS-1:0]][bi*8 +: 8] <= wdata_lat[bi*8 +: 8];
end else begin
rdata <= mem[addr_lat[MEM_ADDR_BITS-1:0]];
end
ready <= 1'b1;
state <= S_IDLE;
end
default: state <= S_IDLE;
endcase
end
end
endmodule
+62 -72
View File
@@ -1,21 +1,23 @@
`timescale 1ns/1ps `timescale 1ns/1ps
// ============================================================ // ============================================================
// Isolated correctness test for act_tile_fetch.v -- real SDR SDRAM // Isolated correctness test for act_tile_fetch.v -- EXP-0084: re-run
// placeholder backend (same precedent as tb_host_mem_bridge.v/ // against the new 32-bit-wide ctrl bus / 4-tiles-per-burst layout
// tb_sdram_arbiter_n.v: verify new glue logic against the fast // (real 32-bit DDR3 channel widening). Backend switched from the real
// backend first). Checks: (1) both lanes read back bit-exact from // AS4C32M16SA x16 SDR model (sdram_controller.v/sdram_model.v -- a
// their own burst-aligned tile slot; (2) different tile indices // REAL chip, genuinely fixed at 16-bit, not reusable here) to
// correctly compute different burst addresses (tile_offset = // burst_mem_model32.v, an explicitly synthetic 32-bit test-only
// tcnt*BURST_LEN); (3) back-to-back requests (multiple tiles in a // burst memory built for exactly this purpose (see its own header).
// row) all stay correct, exercising the S_GAP busy-wait logic. // Checks: (1) all four lanes of a burst read back bit-exact from
// their own quarter-slot; (2) different tile indices correctly
// compute different burst addresses (tile_offset = (tcnt>>2)*
// BURST_LEN); (3) back-to-back requests (multiple tiles in a row,
// including crossing a burst boundary) all stay correct, exercising
// the S_GAP busy-wait logic.
// ============================================================ // ============================================================
module tb; module tb;
localparam BURST_LEN = 8; localparam BURST_LEN = 8;
localparam ROW_BITS = 13; localparam ADDR_WIDTH = 25;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; // 25
localparam CLK_FREQ_MHZ = 64; localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam DATA_WIDTH = 8; localparam DATA_WIDTH = 8;
@@ -27,30 +29,16 @@ module tb;
wire ctrl_req, ctrl_wr; wire ctrl_req, ctrl_wr;
wire [ADDR_WIDTH-1:0] ctrl_addr; wire [ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata; wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [2*BURST_LEN-1:0] ctrl_wmask; wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy; wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #( burst_mem_model32 #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_mem (
) u_ctrl (
.clk(clk), .rst(rst), .clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
.sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
); );
// single requester -> tie grant = active, same precedent as // single requester -> tie grant = active, same precedent as
@@ -66,8 +54,8 @@ module tb;
wire dut_ctrl_req, dut_ctrl_wr; wire dut_ctrl_req, dut_ctrl_wr;
wire [ADDR_WIDTH-1:0] dut_ctrl_addr; wire [ADDR_WIDTH-1:0] dut_ctrl_addr;
wire [16*BURST_LEN-1:0] dut_ctrl_wdata; wire [32*BURST_LEN-1:0] dut_ctrl_wdata;
wire [2*BURST_LEN-1:0] dut_ctrl_wmask; wire [4*BURST_LEN-1:0] dut_ctrl_wmask;
act_tile_fetch #( act_tile_fetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
@@ -81,27 +69,21 @@ module tb;
.ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy) .ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy)
); );
// ---- preload path: direct access to the SDRAM controller, // ---- preload path: direct access to the memory model, bypassing
// bypassing act_tile_fetch.v entirely, same "pre_active" mux // act_tile_fetch.v entirely, same "pre_active" mux pattern as
// pattern as every other testbench in this project ---- // every other testbench in this project ----
reg pre_active; reg pre_active;
reg pre_req, pre_wr; reg pre_req, pre_wr;
reg [ADDR_WIDTH-1:0] pre_addr; reg [ADDR_WIDTH-1:0] pre_addr;
reg [16*BURST_LEN-1:0] pre_wdata; reg [32*BURST_LEN-1:0] pre_wdata;
// reroute: real DUT ctrl_* wires go through a mux so the testbench
// can preload memory directly before act_tile_fetch.v ever runs.
// (Re-declare the connection: DUT was wired directly above for
// simplicity of the DUT instantiation; use force-free approach by
// instead having the DUT's own ctrl_req/wr/addr/wdata feed the mux
// inputs below and the mux feed the real controller.)
assign ctrl_req = pre_active ? pre_req : dut_ctrl_req; assign ctrl_req = pre_active ? pre_req : dut_ctrl_req;
assign ctrl_wr = pre_active ? pre_wr : dut_ctrl_wr; assign ctrl_wr = pre_active ? pre_wr : dut_ctrl_wr;
assign ctrl_addr = pre_active ? pre_addr : dut_ctrl_addr; assign ctrl_addr = pre_active ? pre_addr : dut_ctrl_addr;
assign ctrl_wdata = pre_active ? pre_wdata : dut_ctrl_wdata; assign ctrl_wdata = pre_active ? pre_wdata : dut_ctrl_wdata;
assign ctrl_wmask = pre_active ? {(2*BURST_LEN){1'b0}} : dut_ctrl_wmask; assign ctrl_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : dut_ctrl_wmask;
task automatic sdram_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data); task automatic mem_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin begin
@(posedge clk); while (ctrl_busy) @(posedge clk); @(posedge clk); while (ctrl_busy) @(posedge clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data; pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@@ -137,7 +119,7 @@ module tb;
reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b; reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b;
integer k, wi; integer k, wi;
reg [16*BURST_LEN-1:0] burst; reg [32*BURST_LEN-1:0] burst;
initial begin initial begin
errors = 0; tests = 0; errors = 0; tests = 0;
@@ -147,54 +129,62 @@ module tb;
rst = 0; rst = 0;
@(posedge clk); while (ctrl_busy) @(posedge clk); @(posedge clk); while (ctrl_busy) @(posedge clk);
$display("=== preload 2 bursts/lane, 2 tiles packed per burst (EXP-0081 layout) ==="); $display("=== preload 2 bursts/lane, 4 tiles packed per burst (EXP-0084 layout) ===");
// lane A base = 0, lane B base = 100 (arbitrary, word-address units). // lane A base = 0, lane B base = 100 (arbitrary, word-address units).
// burst pair p holds tile 2p (low 64 bits) and tile 2p+1 (high 64 bits). // burst pair p holds tiles 4p, 4p+1, 4p+2, 4p+3 in quarters 0..3.
for (wi = 0; wi < 2; wi = wi + 1) begin // wi = burst-pair index (0 -> tiles 0/1, 1 -> tiles 2/3) for (wi = 0; wi < 2; wi = wi + 1) begin // wi = burst index (0 -> tiles 0-3, 1 -> tiles 4-7)
for (k = 0; k < P_IN/2; k = k + 1) for (k = 0; k < 4; k = k + 1)
burst[k*16 +: 16] = {act_byte(0, 2*wi, 2*k+1), act_byte(0, 2*wi, 2*k)}; burst[k*64 +: 64] = {act_byte(0, 4*wi+k, 7), act_byte(0, 4*wi+k, 6), act_byte(0, 4*wi+k, 5), act_byte(0, 4*wi+k, 4),
for (k = 0; k < P_IN/2; k = k + 1) act_byte(0, 4*wi+k, 3), act_byte(0, 4*wi+k, 2), act_byte(0, 4*wi+k, 1), act_byte(0, 4*wi+k, 0)};
burst[(P_IN/2+k)*16 +: 16] = {act_byte(0, 2*wi+1, 2*k+1), act_byte(0, 2*wi+1, 2*k)}; mem_write_burst(0 + wi*BURST_LEN, burst);
sdram_write_burst(0 + wi*BURST_LEN, burst);
for (k = 0; k < P_IN/2; k = k + 1) for (k = 0; k < 4; k = k + 1)
burst[k*16 +: 16] = {act_byte(100, 2*wi, 2*k+1), act_byte(100, 2*wi, 2*k)}; burst[k*64 +: 64] = {act_byte(100, 4*wi+k, 7), act_byte(100, 4*wi+k, 6), act_byte(100, 4*wi+k, 5), act_byte(100, 4*wi+k, 4),
for (k = 0; k < P_IN/2; k = k + 1) act_byte(100, 4*wi+k, 3), act_byte(100, 4*wi+k, 2), act_byte(100, 4*wi+k, 1), act_byte(100, 4*wi+k, 0)};
burst[(P_IN/2+k)*16 +: 16] = {act_byte(100, 2*wi+1, 2*k+1), act_byte(100, 2*wi+1, 2*k)}; mem_write_burst(100 + wi*BURST_LEN, burst);
sdram_write_burst(100 + wi*BURST_LEN, burst);
end end
@(posedge clk); @(posedge clk);
pre_active = 1'b0; pre_active = 1'b0;
$display("=== TEST 1: fetch tile 0 (even -> low half), both lanes ==="); $display("=== TEST 1: fetch tile 0 (quarter 0), both lanes ===");
do_fetch(25'd0, 25'd100, 16'd0); do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 0, k); for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 0, k);
check(data_a === exp_a, "T1: lane A tile 0 bit-exact"); check(data_a === exp_a, "T1: lane A tile 0 bit-exact");
check(data_b === exp_b, "T1: lane B tile 0 bit-exact"); check(data_b === exp_b, "T1: lane B tile 0 bit-exact");
$display("=== TEST 2: fetch tile 1 (odd -> high half, SAME burst address as tile 0) ==="); $display("=== TEST 2: fetch tile 1 (quarter 1, SAME burst address as tile 0) ===");
do_fetch(25'd0, 25'd100, 16'd1); do_fetch(25'd0, 25'd100, 16'd1);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k);
for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 1, k); for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 1, k);
check(data_a === exp_a, "T2: lane A tile 1 bit-exact"); check(data_a === exp_a, "T2: lane A tile 1 bit-exact");
check(data_b === exp_b, "T2: lane B tile 1 bit-exact"); check(data_b === exp_b, "T2: lane B tile 1 bit-exact");
$display("=== TEST 3: fetch tile 2 (even -> low half, NEW burst address) ==="); $display("=== TEST 2b: fetch tile 2 (quarter 2, SAME burst) ===");
do_fetch(25'd0, 25'd100, 16'd2); do_fetch(25'd0, 25'd100, 16'd2);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k);
check(data_a === exp_a, "T3: lane A tile 2 bit-exact (new burst)"); check(data_a === exp_a, "T2b: lane A tile 2 bit-exact");
$display("=== TEST 4: back-to-back fetches, alternating even/odd tiles ==="); $display("=== TEST 2c: fetch tile 3 (quarter 3, SAME burst) ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
check(data_a === exp_a, "T4a: back-to-back fetch 1 (tile 0, even), lane A correct");
do_fetch(25'd0, 25'd100, 16'd1);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k);
check(data_a === exp_a, "T4b: back-to-back fetch 2 (tile 1, odd), lane A correct");
do_fetch(25'd0, 25'd100, 16'd3); do_fetch(25'd0, 25'd100, 16'd3);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 3, k); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 3, k);
check(data_a === exp_a, "T4c: back-to-back fetch 3 (tile 3, odd, new burst), lane A correct"); check(data_a === exp_a, "T2c: lane A tile 3 bit-exact");
$display("=== TEST 3: fetch tile 4 (quarter 0, NEW burst address) ===");
do_fetch(25'd0, 25'd100, 16'd4);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 4, k);
check(data_a === exp_a, "T3: lane A tile 4 bit-exact (new burst)");
$display("=== TEST 4: back-to-back fetches, cycling through all 4 quarters ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
check(data_a === exp_a, "T4a: back-to-back fetch 1 (tile 0, quarter 0), lane A correct");
do_fetch(25'd0, 25'd100, 16'd2);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k);
check(data_a === exp_a, "T4b: back-to-back fetch 2 (tile 2, quarter 2), lane A correct");
do_fetch(25'd0, 25'd100, 16'd7);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 7, k);
check(data_a === exp_a, "T4c: back-to-back fetch 3 (tile 7, quarter 3, new burst), lane A correct");
$display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors); $display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_act_tile_fetch)"); if (errors == 0) $display("ALL TESTS PASSED (tb_act_tile_fetch)");
+51 -125
View File
@@ -1,40 +1,33 @@
`timescale 1ns/1ps `timescale 1ns/1ps
// ============================================================ // ============================================================
// EXP-0083: (1) isolated correctness test for ddr_prefetch_mgr.v -- // EXP-0084: re-run of the EXP-0083 ddr_prefetch_mgr.v isolated
// same real SDR SDRAM placeholder backend precedent as // correctness + A/B cycle-count test against the new 32-bit ctrl bus
// tb_act_tile_fetch.v -- and (2) a real, measured, honest A/B cycle- // (real DDR3 channel widening). Backend switched to burst_mem_
// count comparison against the OLD per-tile req/wait/consume loop // model32.v (see its own header -- explicitly synthetic, fixed
// packed_slot.v used before EXP-0083, to get a REAL number for the // latency, not claiming real DDR3 row/bank AC timing) since the real
// look-ahead prefetch's benefit instead of asserting one. // AS4C32M16SA x16 SDR model this test used before is genuinely fixed
// at 16-bit and can't represent the new bus width.
// //
// Both the "baseline" (direct act_tile_fetch.v, one requester per // EXP-0083's own PART 3 (same-row vs row-switch-heavy A/B, exploiting
// tile, old packed_slot.v sequencing) and the "prefetch" (ddr_ // the old SDR model's own real row/bank timing distinction) is
// prefetch_mgr.v, EXP-0083) loops are run against the SAME shared // DROPPED here -- burst_mem_model32.v has a single fixed latency
// backend and the SAME preloaded data, back to back, muxed the same // regardless of address pattern, so that comparison would no longer
// way tb_act_tile_fetch.v's own pre_active mux works -- so the // carry real meaning on this backend. The trustworthy, real number
// comparison is apples to apples, not two different simulated // for that effect already comes from EXP-0083's own real-DDR3-backend
// environments. // measurement (tb_n2_system_ddr3.v, 2.86% real reduction) -- this
// // isolated test's own job is correctness + a basic sanity check that
// Both loops apply the SAME 2-cycle "simulated compute overhead" per // prefetch is still not slower than the old per-tile loop, not to
// tile (matching packed_slot.v's own real S_TILEREQ + S_OPERAND // re-derive that headline number.
// single-cycle costs) between a tile becoming available and the next
// step being taken -- the honest question this answers is: does
// removing the OLD design's serialization of that overhead with the
// NEXT tile's DDR3 fetch produce a real, measurable improvement, and
// how much.
// ============================================================ // ============================================================
module tb; module tb;
localparam BURST_LEN = 8; localparam BURST_LEN = 8;
localparam ROW_BITS = 13; localparam ADDR_WIDTH = 25;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; // 25
localparam CLK_FREQ_MHZ = 64; localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam DATA_WIDTH = 8; localparam DATA_WIDTH = 8;
localparam P_IN = 8; localparam P_IN = 8;
localparam N_TILES = 6; // 3 burst-pairs/lane -- enough to see steady-state behavior localparam N_TILES = 8; // 2 bursts/lane at 4 tiles/burst -- enough to see steady-state behavior
reg clk = 0; reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk; always #(CLK_PERIOD_NS/2.0) clk = ~clk;
@@ -42,30 +35,16 @@ module tb;
wire ctrl_req, ctrl_wr; wire ctrl_req, ctrl_wr;
wire [ADDR_WIDTH-1:0] ctrl_addr; wire [ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata; wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [2*BURST_LEN-1:0] ctrl_wmask; wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy; wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #( burst_mem_model32 #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_mem (
) u_ctrl (
.clk(clk), .rst(rst), .clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
.sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
); );
// ---- 3-way mux on the shared backend: preload / baseline DUT / prefetch DUT ---- // ---- 3-way mux on the shared backend: preload / baseline DUT / prefetch DUT ----
@@ -74,7 +53,7 @@ module tb;
reg pre_req, pre_wr; reg pre_req, pre_wr;
reg [ADDR_WIDTH-1:0] pre_addr; reg [ADDR_WIDTH-1:0] pre_addr;
reg [16*BURST_LEN-1:0] pre_wdata; reg [32*BURST_LEN-1:0] pre_wdata;
// ---- baseline DUT: plain act_tile_fetch.v, driven by a per-tile // ---- baseline DUT: plain act_tile_fetch.v, driven by a per-tile
// req/wait/consume loop replicating OLD packed_slot.v sequencing ---- // req/wait/consume loop replicating OLD packed_slot.v sequencing ----
@@ -86,8 +65,8 @@ module tb;
wire base_mem_active; wire base_mem_active;
wire base_ctrl_req, base_ctrl_wr; wire base_ctrl_req, base_ctrl_wr;
wire [ADDR_WIDTH-1:0] base_ctrl_addr; wire [ADDR_WIDTH-1:0] base_ctrl_addr;
wire [16*BURST_LEN-1:0] base_ctrl_wdata; wire [32*BURST_LEN-1:0] base_ctrl_wdata;
wire [2*BURST_LEN-1:0] base_ctrl_wmask; wire [4*BURST_LEN-1:0] base_ctrl_wmask;
act_tile_fetch #( act_tile_fetch #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
@@ -111,8 +90,8 @@ module tb;
wire pf_mem_active; wire pf_mem_active;
wire pf_ctrl_req, pf_ctrl_wr; wire pf_ctrl_req, pf_ctrl_wr;
wire [ADDR_WIDTH-1:0] pf_ctrl_addr; wire [ADDR_WIDTH-1:0] pf_ctrl_addr;
wire [16*BURST_LEN-1:0] pf_ctrl_wdata; wire [32*BURST_LEN-1:0] pf_ctrl_wdata;
wire [2*BURST_LEN-1:0] pf_ctrl_wmask; wire [4*BURST_LEN-1:0] pf_ctrl_wmask;
ddr_prefetch_mgr #( ddr_prefetch_mgr #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
@@ -130,9 +109,9 @@ module tb;
assign ctrl_wr = (sel==SEL_PRELOAD) ? pre_wr : (sel==SEL_BASE) ? base_ctrl_wr : pf_ctrl_wr; assign ctrl_wr = (sel==SEL_PRELOAD) ? pre_wr : (sel==SEL_BASE) ? base_ctrl_wr : pf_ctrl_wr;
assign ctrl_addr = (sel==SEL_PRELOAD) ? pre_addr : (sel==SEL_BASE) ? base_ctrl_addr : pf_ctrl_addr; assign ctrl_addr = (sel==SEL_PRELOAD) ? pre_addr : (sel==SEL_BASE) ? base_ctrl_addr : pf_ctrl_addr;
assign ctrl_wdata = (sel==SEL_PRELOAD) ? pre_wdata : (sel==SEL_BASE) ? base_ctrl_wdata : pf_ctrl_wdata; assign ctrl_wdata = (sel==SEL_PRELOAD) ? pre_wdata : (sel==SEL_BASE) ? base_ctrl_wdata : pf_ctrl_wdata;
assign ctrl_wmask = (sel==SEL_BASE) ? base_ctrl_wmask : (sel==SEL_PF) ? pf_ctrl_wmask : {(2*BURST_LEN){1'b0}}; assign ctrl_wmask = (sel==SEL_BASE) ? base_ctrl_wmask : (sel==SEL_PF) ? pf_ctrl_wmask : {(4*BURST_LEN){1'b0}};
task automatic sdram_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data); task automatic mem_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin begin
@(posedge clk); while (ctrl_busy) @(posedge clk); @(posedge clk); while (ctrl_busy) @(posedge clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data; pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@@ -155,8 +134,8 @@ module tb;
endtask endtask
reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b; reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b;
integer k, wi, cyc; integer k, wi, qi;
reg [16*BURST_LEN-1:0] burst; reg [32*BURST_LEN-1:0] burst;
integer t_start, t_end, base_cycles, pf_cycles; integer t_start, t_end, base_cycles, pf_cycles;
initial begin initial begin
@@ -169,24 +148,22 @@ module tb;
rst = 0; rst = 0;
@(posedge clk); while (ctrl_busy) @(posedge clk); @(posedge clk); while (ctrl_busy) @(posedge clk);
$display("=== preload lane A base=0, lane B base=200, %0d tiles (EXP-0081 2-tiles/burst layout) ===", N_TILES); $display("=== preload lane A base=0, lane B base=200, %0d tiles (EXP-0084 4-tiles/burst layout) ===", N_TILES);
for (wi = 0; wi < N_TILES/2; wi = wi + 1) begin for (wi = 0; wi < N_TILES/4; wi = wi + 1) begin
for (k = 0; k < P_IN/2; k = k + 1) for (qi = 0; qi < 4; qi = qi + 1)
burst[k*16 +: 16] = {act_byte(0, 2*wi, 2*k+1), act_byte(0, 2*wi, 2*k)}; burst[qi*64 +: 64] = {act_byte(0, 4*wi+qi, 7), act_byte(0, 4*wi+qi, 6), act_byte(0, 4*wi+qi, 5), act_byte(0, 4*wi+qi, 4),
for (k = 0; k < P_IN/2; k = k + 1) act_byte(0, 4*wi+qi, 3), act_byte(0, 4*wi+qi, 2), act_byte(0, 4*wi+qi, 1), act_byte(0, 4*wi+qi, 0)};
burst[(P_IN/2+k)*16 +: 16] = {act_byte(0, 2*wi+1, 2*k+1), act_byte(0, 2*wi+1, 2*k)}; mem_write_burst(0 + wi*BURST_LEN, burst);
sdram_write_burst(0 + wi*BURST_LEN, burst);
for (k = 0; k < P_IN/2; k = k + 1) for (qi = 0; qi < 4; qi = qi + 1)
burst[k*16 +: 16] = {act_byte(200, 2*wi, 2*k+1), act_byte(200, 2*wi, 2*k)}; burst[qi*64 +: 64] = {act_byte(200, 4*wi+qi, 7), act_byte(200, 4*wi+qi, 6), act_byte(200, 4*wi+qi, 5), act_byte(200, 4*wi+qi, 4),
for (k = 0; k < P_IN/2; k = k + 1) act_byte(200, 4*wi+qi, 3), act_byte(200, 4*wi+qi, 2), act_byte(200, 4*wi+qi, 1), act_byte(200, 4*wi+qi, 0)};
burst[(P_IN/2+k)*16 +: 16] = {act_byte(200, 2*wi+1, 2*k+1), act_byte(200, 2*wi+1, 2*k)}; mem_write_burst(200 + wi*BURST_LEN, burst);
sdram_write_burst(200 + wi*BURST_LEN, burst);
end end
@(posedge clk); @(posedge clk);
// ============================================================ // ============================================================
// PART 1: baseline correctness + real cycle count, OLD-style // PART 1: baseline correctness + cycle count, OLD-style
// per-tile req -> wait valid -> 2-cycle simulated compute -> next req // per-tile req -> wait valid -> 2-cycle simulated compute -> next req
// ============================================================ // ============================================================
$display("=== PART 1: baseline (direct act_tile_fetch.v, old packed_slot.v loop) ==="); $display("=== PART 1: baseline (direct act_tile_fetch.v, old packed_slot.v loop) ===");
@@ -213,10 +190,10 @@ module tb;
$display("baseline: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, base_cycles, base_cycles*1.0/N_TILES); $display("baseline: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, base_cycles, base_cycles*1.0/N_TILES);
// ============================================================ // ============================================================
// PART 2: prefetch correctness + real cycle count, EXP-0083 // PART 2: prefetch correctness + cycle count, EXP-0083 job-level
// job-level start, poll tile_valid, 2-cycle simulated compute, consume // start, poll tile_valid, 2-cycle simulated compute, consume
// ============================================================ // ============================================================
$display("=== PART 2: prefetch (ddr_prefetch_mgr.v, EXP-0083) ==="); $display("=== PART 2: prefetch (ddr_prefetch_mgr.v, EXP-0083/0084) ===");
sel = SEL_PF; sel = SEL_PF;
@(posedge clk); @(posedge clk);
pf_base_a <= 0; pf_base_b <= 200; pf_n_tiles <= N_TILES[15:0]; pf_base_a <= 0; pf_base_b <= 200; pf_n_tiles <= N_TILES[15:0];
@@ -230,11 +207,7 @@ module tb;
// pulse being sampled by the DUT (both triggered off the same // pulse being sampled by the DUT (both triggered off the same
// @(posedge clk)), reading pf_tile_valid/bank_valid BEFORE the // @(posedge clk)), reading pf_tile_valid/bank_valid BEFORE the
// DUT's own nonblocking update for that consume has been // DUT's own nonblocking update for that consume has been
// applied -- a real testbench race, not an RTL bug (found via // applied -- a real testbench race, not an RTL bug (EXP-0083).
// an iteration-tagged trace: k=1 was reading k=0's still-
// unconsumed bank). #1 (real time, 1ns << the 15.625ns clock
// period) forces this poll to always run strictly after that
// update has settled.
#1; #1;
while (!pf_tile_valid) @(posedge clk); while (!pf_tile_valid) @(posedge clk);
for (wi = 0; wi < P_IN; wi = wi + 1) begin for (wi = 0; wi < P_IN; wi = wi + 1) begin
@@ -252,56 +225,9 @@ module tb;
pf_cycles = (t_end - t_start) / CLK_PERIOD_NS; pf_cycles = (t_end - t_start) / CLK_PERIOD_NS;
$display("prefetch: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, pf_cycles, pf_cycles*1.0/N_TILES); $display("prefetch: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, pf_cycles, pf_cycles*1.0/N_TILES);
$display("=== REAL MEASURED COMPARISON (row-switch-heavy, 3 different burst pairs): baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% ===", $display("=== cycle comparison on this fixed-latency synthetic backend: baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% (sanity check only -- the real, trustworthy number is EXP-0083's real-DDR3-backend measurement, 2.86%%) ===",
base_cycles, pf_cycles, 100.0*(base_cycles-pf_cycles)/base_cycles);
check(pf_cycles < base_cycles, "prefetch is real, measurably faster than baseline (row-switch-heavy)");
// ============================================================
// PART 3 (EXP-0083 addendum): best-case, SAME-ROW comparison --
// only tiles 0/1 (both already resident in the FIRST preloaded
// burst pair, no row activation needed for either), isolating
// the look-ahead benefit from row-switch cost entirely. Answers
// honestly whether the small PART-1/2 result is because the
// benefit is inherently small, or because row-switch cost
// dominates and masks it in that scenario.
// ============================================================
$display("=== PART 3: same-row best case (2 tiles, single burst pair, no row switch) ===");
sel = SEL_BASE;
@(posedge clk);
t_start = $time;
for (k = 0; k < 2; k = k + 1) begin
@(posedge clk);
base_base_a <= 0; base_base_b <= 200; base_tcnt <= k[15:0];
base_req <= 1'b1;
@(posedge clk);
base_req <= 1'b0;
while (!base_valid) @(posedge clk);
repeat(2) @(posedge clk);
end
t_end = $time;
base_cycles = (t_end - t_start) / CLK_PERIOD_NS;
$display("same-row baseline: 2 tiles in %0d cycles", base_cycles);
sel = SEL_PF;
@(posedge clk);
pf_base_a <= 0; pf_base_b <= 200; pf_n_tiles <= 16'd2;
pf_job_start <= 1'b1;
t_start = $time;
@(posedge clk);
pf_job_start <= 1'b0;
for (k = 0; k < 2; k = k + 1) begin
#1;
while (!pf_tile_valid) @(posedge clk);
repeat(2) @(posedge clk);
pf_tile_consume <= 1'b1;
@(posedge clk);
pf_tile_consume <= 1'b0;
end
t_end = $time;
pf_cycles = (t_end - t_start) / CLK_PERIOD_NS;
$display("same-row prefetch: 2 tiles in %0d cycles", pf_cycles);
$display("=== REAL MEASURED COMPARISON (same-row, best case): baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% ===",
base_cycles, pf_cycles, 100.0*(base_cycles-pf_cycles)/base_cycles); base_cycles, pf_cycles, 100.0*(base_cycles-pf_cycles)/base_cycles);
check(pf_cycles <= base_cycles, "prefetch is not slower than baseline (sanity check)");
$display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors); $display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_ddr_prefetch_mgr)"); if (errors == 0) $display("ALL TESTS PASSED (tb_ddr_prefetch_mgr)");
+31 -40
View File
@@ -4,26 +4,31 @@
// Isolated correctness test for host_mem_bridge.v: the word<->burst // Isolated correctness test for host_mem_bridge.v: the word<->burst
// translator that closes the "no host raw-memory-access path" gap // translator that closes the "no host raw-memory-access path" gap
// found re-auditing spi_host_bridge.v against V3 (EXP-0068's audit). // found re-auditing spi_host_bridge.v against V3 (EXP-0068's audit).
// Uses the cheap SDR SDRAM placeholder backend (sdram_controller.v +
// sdram_model.v), same precedent as tb_sdram_arbiter_n.v: verify new
// glue logic against the fast backend first, real DDR3 integration
// is a separate, later step once this is trusted standalone.
// //
// Checks: (a) single-word write only touches its OWN word inside the // EXP-0084: re-run against the new 32-bit ctrl bus / burst_mem_
// burst (byte masking correctness, lb_n/ub_n both individually and // model32.v backend (real DDR3 channel widening -- was 16-bit-word-
// together) without corrupting neighboring words in the same burst; // native before, now 32-bit-word-native, see host_mem_bridge.v's own
// (b) single-word read extracts the correct word regardless of its // header for the real addressing redesign this required: mem_addr
// offset within the burst (all BURST_LEN=8 offsets exercised); // stays a 16-bit-word address for the host's own unchanged protocol,
// (c) mem_ready pulses exactly once per transaction. // but now mem_addr[0] additionally selects which half of the
// underlying 32-bit ctrl-bus word to target). One burst now spans
// 2*BURST_LEN=16 host-side 16-bit-word offsets (was BURST_LEN=8) --
// this test now exercises all 16, not just 8, to cover the new
// half-select logic across the WHOLE burst, not half of it.
//
// Checks: (a) single-word write only touches its OWN half-word inside
// the burst (byte masking correctness, lb_n/ub_n both individually and
// together) without corrupting neighboring half-words in the same
// burst; (b) single-word read extracts the correct half-word
// regardless of its offset within the burst (all 16 offsets
// exercised); (c) mem_ready pulses exactly once per transaction.
// ============================================================ // ============================================================
module tb; module tb;
localparam BURST_LEN = 8; localparam BURST_LEN = 8;
localparam ROW_BITS = 13; localparam ADDR_WIDTH = 25;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS;
localparam CLK_FREQ_MHZ = 64; localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam BURST_SPAN = 2*BURST_LEN; // 16-bit-word positions per burst, host-side addressing
reg clk = 0; reg clk = 0;
always #(CLK_PERIOD_NS/2.0) clk = ~clk; always #(CLK_PERIOD_NS/2.0) clk = ~clk;
@@ -31,30 +36,16 @@ module tb;
wire ctrl_req, ctrl_wr; wire ctrl_req, ctrl_wr;
wire [ADDR_WIDTH-1:0] ctrl_addr; wire [ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata; wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [2*BURST_LEN-1:0] ctrl_wmask; wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy; wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #( burst_mem_model32 #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_mem (
) u_ctrl (
.clk(clk), .rst(rst), .clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
.sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
); );
// single requester -> arbiter isn't even needed for an isolated // single requester -> arbiter isn't even needed for an isolated
@@ -112,7 +103,7 @@ module tb;
reg [15:0] got; reg [15:0] got;
integer i; integer i;
localparam [ADDR_WIDTH-1:0] BASE = 25'd200; // burst-aligned base (200 % 8 == 0) localparam [ADDR_WIDTH-1:0] BASE = 25'd256; // burst-aligned base (256 % 16 == 0)
initial begin initial begin
errors = 0; tests = 0; errors = 0; tests = 0;
@@ -121,11 +112,11 @@ module tb;
rst = 0; rst = 0;
@(posedge clk); @(posedge clk);
$display("=== TEST 1: write+read every word offset within one burst, verify no cross-word corruption ==="); $display("=== TEST 1: write+read every half-word offset within one burst (0..%0d), verify no cross-word corruption ===", BURST_SPAN-1);
for (i = 0; i < BURST_LEN; i = i + 1) begin for (i = 0; i < BURST_SPAN; i = i + 1) begin
host_write(BASE + i[ADDR_WIDTH-1:0], 16'hA000 + i[15:0], 1'b0, 1'b0); host_write(BASE + i[ADDR_WIDTH-1:0], 16'hA000 + i[15:0], 1'b0, 1'b0);
end end
for (i = 0; i < BURST_LEN; i = i + 1) begin for (i = 0; i < BURST_SPAN; i = i + 1) begin
host_read(BASE + i[ADDR_WIDTH-1:0], got); host_read(BASE + i[ADDR_WIDTH-1:0], got);
tests = tests + 1; tests = tests + 1;
if (got !== (16'hA000 + i[15:0])) begin if (got !== (16'hA000 + i[15:0])) begin
@@ -136,9 +127,9 @@ module tb;
end end
end end
$display("=== TEST 2: re-write word 3 only, confirm neighbors (0,1,2,4..7) untouched ==="); $display("=== TEST 2: re-write offset 3 only, confirm neighbors (0..%0d except 3) untouched ===", BURST_SPAN-1);
host_write(BASE + 25'd3, 16'hBEEF, 1'b0, 1'b0); host_write(BASE + 25'd3, 16'hBEEF, 1'b0, 1'b0);
for (i = 0; i < BURST_LEN; i = i + 1) begin for (i = 0; i < BURST_SPAN; i = i + 1) begin
host_read(BASE + i[ADDR_WIDTH-1:0], got); host_read(BASE + i[ADDR_WIDTH-1:0], got);
tests = tests + 1; tests = tests + 1;
if (i == 3) begin if (i == 3) begin
+61 -32
View File
@@ -16,17 +16,23 @@
// real calibration, impractically slow for simulation) and does not // real calibration, impractically slow for simulation) and does not
// expose this parameter; mig_7series_0_mig.v does. All other // expose this parameter; mig_7series_0_mig.v does. All other
// parameters are left at their defaults, which already ARE this // parameters are left at their defaults, which already ARE this
// project's real generated configuration (DQ_WIDTH=16, MEM_DENSITY= // project's real generated configuration (EXP-0084: DQ_WIDTH=32 now,
// 2Gb, MEM_SPEEDGRADE=125, MEM_ADDR_ORDER=BANK_ROW_COLUMN, etc.) -- // two MT41J128M16 components ganged in parallel -- MEM_DENSITY=2Gb,
// MEM_SPEEDGRADE=125, MEM_ADDR_ORDER=BANK_ROW_COLUMN unchanged) --
// not generic MIG defaults. // not generic MIG defaults.
// //
// Clock/reset generation and DDR3 pin wiring (WireDelay pass-through, // Clock/reset generation and DDR3 pin wiring (WireDelay pass-through,
// zero propagation delay) mirror this project's own vendor-shipped // zero propagation delay) mirror this project's own vendor-shipped
// example_design/sim/sim_tb_top.v exactly, per its own real, proven // example_design/sim/sim_tb_top.v exactly, per its own real, proven
// pattern -- not re-derived from scratch. // pattern -- not re-derived from scratch. EXP-0084: the real vendor
// pattern instantiates NUM_COMP=DQ_WIDTH/16 real ddr3_model.sv
// components for a >16-bit aggregate width, each wired to its own
// 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).
// ============================================================ // ============================================================
module tb; module tb;
localparam CLKIN_PERIOD = 3225; // ps, matches this project's real MIG config localparam CLKIN_PERIOD = 2900; // ps, matches this project's real MIG config (EXP-0084)
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
@@ -37,41 +43,56 @@ module tb;
reg sys_rst_n; reg sys_rst_n;
wire sys_rst = sys_rst_n; // Active Low, matches mig_7series_0_mig's own default polarity wire sys_rst = sys_rst_n; // Active Low, matches mig_7series_0_mig's own default polarity
// EXP-0084: both system clock AND reference clock are now real
// DIFFERENTIAL pairs on the inner mig_7series_0_mig module (the
// user's own wizard choice, "Differential" for both) -- confirmed
// against the real regenerated mig_7series_0_mig.v port list
// (sys_clk_p/n, clk_ref_p/n, no single-ended sys_clk_i/clk_ref_i
// ports exist any more). Same real vendor pattern as sim_tb_top.v:
// generate a single-ended internal clock, drive the P/N pair as
// true/complement of it.
reg sys_clk_i = 1'b0; reg sys_clk_i = 1'b0;
always #(CLKIN_PERIOD/2.0) sys_clk_i = ~sys_clk_i; 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; reg clk_ref_i = 1'b0;
always #REFCLK_PERIOD clk_ref_i = ~clk_ref_i; 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 initial begin
sys_rst_n = 1'b0; sys_rst_n = 1'b0;
#RESET_PERIOD sys_rst_n = 1'b1; #RESET_PERIOD sys_rst_n = 1'b1;
end end
// ---- real DDR3 pins ---- // ---- real DDR3 pins (dq/dqs/dm widths doubled since EXP-0084's
// real 32-bit widening -- two MT41J128M16 chips ganged in
// parallel, confirmed against the real regenerated
// mig_7series_0.v wrapper) ----
wire ddr3_reset_n; wire ddr3_reset_n;
wire [15:0] ddr3_dq_fpga; wire [31:0] ddr3_dq_fpga;
wire [1:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga; wire [3:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga;
wire [13:0] ddr3_addr_fpga; wire [13:0] ddr3_addr_fpga;
wire [2:0] ddr3_ba_fpga; wire [2:0] ddr3_ba_fpga;
wire ddr3_ras_n_fpga, ddr3_cas_n_fpga, ddr3_we_n_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 [0:0] ddr3_cke_fpga, ddr3_ck_p_fpga, ddr3_ck_n_fpga, ddr3_cs_n_fpga;
wire [1:0] ddr3_dm_fpga; wire [3:0] ddr3_dm_fpga;
wire [0:0] ddr3_odt_fpga; wire [0:0] ddr3_odt_fpga;
wire [15:0] ddr3_dq_sdram; wire [31:0] ddr3_dq_sdram;
reg [13:0] ddr3_addr_sdram; reg [13:0] ddr3_addr_sdram;
reg [2:0] ddr3_ba_sdram; reg [2:0] ddr3_ba_sdram;
reg ddr3_ras_n_sdram, ddr3_cas_n_sdram, ddr3_we_n_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_cs_n_sdram;
wire [0:0] ddr3_odt_sdram; wire [0:0] ddr3_odt_sdram;
reg [0:0] ddr3_cke_sdram; reg [0:0] ddr3_cke_sdram;
wire [1:0] ddr3_dm_sdram; wire [3:0] ddr3_dm_sdram;
wire [1:0] ddr3_dqs_p_sdram, ddr3_dqs_n_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_ck_p_sdram, ddr3_ck_n_sdram;
reg [0:0] ddr3_cs_n_sdram_tmp; reg [0:0] ddr3_cs_n_sdram_tmp;
reg [1:0] ddr3_dm_sdram_tmp; reg [3:0] ddr3_dm_sdram_tmp;
reg [0:0] ddr3_odt_sdram_tmp; reg [0:0] ddr3_odt_sdram_tmp;
always @(*) begin always @(*) begin
@@ -93,7 +114,7 @@ module tb;
genvar dqwd; genvar dqwd;
generate generate
for (dqwd = 0; dqwd < 16; dqwd = dqwd + 1) begin : dq_delay 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 ( 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]), .A(ddr3_dq_fpga[dqwd]), .B(ddr3_dq_sdram[dqwd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete) .reset(sys_rst_n), .phy_init_done(init_calib_complete)
@@ -102,7 +123,7 @@ module tb;
endgenerate endgenerate
genvar dqswd; genvar dqswd;
generate generate
for (dqswd = 0; dqswd < 2; dqswd = dqswd + 1) begin : dqs_delay 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 ( 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]), .A(ddr3_dqs_p_fpga[dqswd]), .B(ddr3_dqs_p_sdram[dqswd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete) .reset(sys_rst_n), .phy_init_done(init_calib_complete)
@@ -114,27 +135,35 @@ module tb;
end end
endgenerate endgenerate
// ---- real DDR3 behavioral model (single component, DQ_WIDTH=16 // ---- real DDR3 behavioral model (EXP-0084: TWO components now,
// matches MEMORY_WIDTH=16 exactly, no splitting needed) ---- // DQ_WIDTH=32 / 16 per component -- exact real pattern confirmed
ddr3_model u_ddr3 ( // against the real regenerated sim_tb_top.v's own generate block,
// not assumed) ----
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), .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]), .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), .ras_n(ddr3_ras_n_sdram), .cas_n(ddr3_cas_n_sdram), .we_n(ddr3_we_n_sdram),
.dm_tdqs(ddr3_dm_sdram), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram), .dm_tdqs(ddr3_dm_sdram[2*ci +: 2]), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram),
.dq(ddr3_dq_sdram), .dqs(ddr3_dqs_p_sdram), .dqs_n(ddr3_dqs_n_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]) .tdqs_n(), .odt(ddr3_odt_sdram[0])
); );
end
endgenerate
// ---- real MIG controller (inner module, SIM_BYPASS_INIT_CAL // ---- real MIG controller (inner module, SIM_BYPASS_INIT_CAL
// overridden for a real but fast simulation calibration) ---- // overridden for a real but fast simulation calibration) ----
wire [27:0] app_addr; wire [27:0] app_addr;
wire [2:0] app_cmd; wire [2:0] app_cmd;
wire app_en, app_rdy; wire app_en, app_rdy;
wire [63:0] app_wdf_data; wire [127:0] app_wdf_data;
wire app_wdf_end; wire app_wdf_end;
wire [7:0] app_wdf_mask; wire [15:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy; wire app_wdf_wren, app_wdf_rdy;
wire [63:0] app_rd_data; wire [127:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid; wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst, init_calib_complete; wire ui_clk, ui_clk_sync_rst, init_calib_complete;
@@ -148,7 +177,7 @@ module tb;
.ddr3_ck_p(ddr3_ck_p_fpga), .ddr3_ck_n(ddr3_ck_n_fpga), .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_cke(ddr3_cke_fpga), .ddr3_cs_n(ddr3_cs_n_fpga),
.ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga), .ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga),
.sys_clk_i(sys_clk_i), .clk_ref_i(clk_ref_i), .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_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_data(app_wdf_data), .app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren), .app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren),
@@ -165,9 +194,9 @@ module tb;
// ---- adapter under test ---- // ---- adapter under test ----
reg req, wr; reg req, wr;
reg [ADDR_WIDTH-1:0] addr; reg [ADDR_WIDTH-1:0] addr;
reg [16*BURST_LEN-1:0] wdata; reg [32*BURST_LEN-1:0] wdata;
reg [2*BURST_LEN-1:0] wmask; reg [4*BURST_LEN-1:0] wmask;
wire [16*BURST_LEN-1:0] rdata; wire [32*BURST_LEN-1:0] rdata;
wire ready, busy; wire ready, busy;
mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)) u_adapter ( mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)) u_adapter (
@@ -183,13 +212,13 @@ module tb;
task automatic do_txn( task automatic do_txn(
input t_wr, input t_wr,
input [ADDR_WIDTH-1:0] t_addr, input [ADDR_WIDTH-1:0] t_addr,
input [16*BURST_LEN-1:0] t_wdata, input [32*BURST_LEN-1:0] t_wdata,
output [16*BURST_LEN-1:0] t_rdata output [32*BURST_LEN-1:0] t_rdata
); );
begin begin
@(posedge ui_clk); @(posedge ui_clk);
while (busy) @(posedge ui_clk); while (busy) @(posedge ui_clk);
req = 1'b1; wr = t_wr; addr = t_addr; wdata = t_wdata; wmask = {(2*BURST_LEN){1'b0}}; req = 1'b1; wr = t_wr; addr = t_addr; wdata = t_wdata; wmask = {(4*BURST_LEN){1'b0}};
@(posedge ui_clk); @(posedge ui_clk);
req = 1'b0; req = 1'b0;
while (!ready) @(posedge ui_clk); while (!ready) @(posedge ui_clk);
@@ -198,15 +227,15 @@ module tb;
endtask endtask
integer errors, tests; integer errors, tests;
reg [16*BURST_LEN-1:0] got, wpat; reg [32*BURST_LEN-1:0] got, wpat;
integer k, i; integer k, i;
task automatic check_addr(input [ADDR_WIDTH-1:0] a, input [15:0] pattern); task automatic check_addr(input [ADDR_WIDTH-1:0] a, input [15:0] pattern);
begin begin
for (k = 0; k < BURST_LEN; k = k + 1) for (k = 0; k < BURST_LEN; k = k + 1)
wpat[k*16 +: 16] = pattern + k[15:0]; wpat[k*32 +: 32] = {pattern, pattern + k[15:0]};
do_txn(1'b1, a, wpat, got); do_txn(1'b1, a, wpat, got);
do_txn(1'b0, a, {(16*BURST_LEN){1'b0}}, got); do_txn(1'b0, a, {(32*BURST_LEN){1'b0}}, got);
tests = tests + 1; tests = tests + 1;
if (got !== wpat) begin if (got !== wpat) begin
$display("FAIL addr=%0d: got=%h expected=%h", a, got, wpat); $display("FAIL addr=%0d: got=%h expected=%h", a, got, wpat);
+72 -48
View File
@@ -26,9 +26,19 @@
// EXP-0068's own real vendor-shipped fast-calibration simulation // EXP-0068's own real vendor-shipped fast-calibration simulation
// 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
// 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).
// ============================================================ // ============================================================
module tb; module tb;
localparam CLKIN_PERIOD = 3225; // ps, this project's real MIG config localparam CLKIN_PERIOD = 2900; // ps, this project's real MIG config (EXP-0084)
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
@@ -55,8 +65,12 @@ module tb;
wire sys_rst = sys_rst_n; wire sys_rst = sys_rst_n;
reg sys_clk_i = 1'b0; reg sys_clk_i = 1'b0;
always #(CLKIN_PERIOD/2.0) sys_clk_i = ~sys_clk_i; 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; reg clk_ref_i = 1'b0;
always #REFCLK_PERIOD clk_ref_i = ~clk_ref_i; 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 initial begin
sys_rst_n = 1'b0; sys_rst_n = 1'b0;
#RESET_PERIOD sys_rst_n = 1'b1; #RESET_PERIOD sys_rst_n = 1'b1;
@@ -64,27 +78,27 @@ module tb;
// ---- real DDR3 pins + model (identical to tb_mig_native_adapter.v) ---- // ---- real DDR3 pins + model (identical to tb_mig_native_adapter.v) ----
wire ddr3_reset_n; wire ddr3_reset_n;
wire [15:0] ddr3_dq_fpga; wire [31:0] ddr3_dq_fpga;
wire [1:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga; wire [3:0] ddr3_dqs_p_fpga, ddr3_dqs_n_fpga;
wire [13:0] ddr3_addr_fpga; wire [13:0] ddr3_addr_fpga;
wire [2:0] ddr3_ba_fpga; wire [2:0] ddr3_ba_fpga;
wire ddr3_ras_n_fpga, ddr3_cas_n_fpga, ddr3_we_n_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 [0:0] ddr3_cke_fpga, ddr3_ck_p_fpga, ddr3_ck_n_fpga, ddr3_cs_n_fpga;
wire [1:0] ddr3_dm_fpga; wire [3:0] ddr3_dm_fpga;
wire [0:0] ddr3_odt_fpga; wire [0:0] ddr3_odt_fpga;
wire [15:0] ddr3_dq_sdram; wire [31:0] ddr3_dq_sdram;
reg [13:0] ddr3_addr_sdram; reg [13:0] ddr3_addr_sdram;
reg [2:0] ddr3_ba_sdram; reg [2:0] ddr3_ba_sdram;
reg ddr3_ras_n_sdram, ddr3_cas_n_sdram, ddr3_we_n_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_cs_n_sdram;
wire [0:0] ddr3_odt_sdram; wire [0:0] ddr3_odt_sdram;
reg [0:0] ddr3_cke_sdram; reg [0:0] ddr3_cke_sdram;
wire [1:0] ddr3_dm_sdram; wire [3:0] ddr3_dm_sdram;
wire [1:0] ddr3_dqs_p_sdram, ddr3_dqs_n_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_ck_p_sdram, ddr3_ck_n_sdram;
reg [0:0] ddr3_cs_n_sdram_tmp; reg [0:0] ddr3_cs_n_sdram_tmp;
reg [1:0] ddr3_dm_sdram_tmp; reg [3:0] ddr3_dm_sdram_tmp;
reg [0:0] ddr3_odt_sdram_tmp; reg [0:0] ddr3_odt_sdram_tmp;
always @(*) begin always @(*) begin
@@ -106,7 +120,7 @@ module tb;
genvar dqwd; genvar dqwd;
generate generate
for (dqwd = 0; dqwd < 16; dqwd = dqwd + 1) begin : dq_delay 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 ( 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]), .A(ddr3_dq_fpga[dqwd]), .B(ddr3_dq_sdram[dqwd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete) .reset(sys_rst_n), .phy_init_done(init_calib_complete)
@@ -115,7 +129,7 @@ module tb;
endgenerate endgenerate
genvar dqswd; genvar dqswd;
generate generate
for (dqswd = 0; dqswd < 2; dqswd = dqswd + 1) begin : dqs_delay 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 ( 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]), .A(ddr3_dqs_p_fpga[dqswd]), .B(ddr3_dqs_p_sdram[dqswd]),
.reset(sys_rst_n), .phy_init_done(init_calib_complete) .reset(sys_rst_n), .phy_init_done(init_calib_complete)
@@ -127,23 +141,29 @@ module tb;
end end
endgenerate endgenerate
ddr3_model u_ddr3 ( 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), .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]), .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), .ras_n(ddr3_ras_n_sdram), .cas_n(ddr3_cas_n_sdram), .we_n(ddr3_we_n_sdram),
.dm_tdqs(ddr3_dm_sdram), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram), .dm_tdqs(ddr3_dm_sdram[2*ci +: 2]), .ba(ddr3_ba_sdram), .addr(ddr3_addr_sdram),
.dq(ddr3_dq_sdram), .dqs(ddr3_dqs_p_sdram), .dqs_n(ddr3_dqs_n_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]) .tdqs_n(), .odt(ddr3_odt_sdram[0])
); );
end
endgenerate
wire [27:0] app_addr; wire [27:0] app_addr;
wire [2:0] app_cmd; wire [2:0] app_cmd;
wire app_en, app_rdy; wire app_en, app_rdy;
wire [63:0] app_wdf_data; wire [127:0] app_wdf_data;
wire app_wdf_end; wire app_wdf_end;
wire [7:0] app_wdf_mask; wire [15:0] app_wdf_mask;
wire app_wdf_wren, app_wdf_rdy; wire app_wdf_wren, app_wdf_rdy;
wire [63:0] app_rd_data; wire [127:0] app_rd_data;
wire app_rd_data_end, app_rd_data_valid; wire app_rd_data_end, app_rd_data_valid;
wire ui_clk, ui_clk_sync_rst, init_calib_complete; wire ui_clk, ui_clk_sync_rst, init_calib_complete;
@@ -157,7 +177,7 @@ module tb;
.ddr3_ck_p(ddr3_ck_p_fpga), .ddr3_ck_n(ddr3_ck_n_fpga), .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_cke(ddr3_cke_fpga), .ddr3_cs_n(ddr3_cs_n_fpga),
.ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga), .ddr3_dm(ddr3_dm_fpga), .ddr3_odt(ddr3_odt_fpga),
.sys_clk_i(sys_clk_i), .clk_ref_i(clk_ref_i), .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_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_data(app_wdf_data), .app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren), .app_wdf_mask(app_wdf_mask), .app_wdf_wren(app_wdf_wren),
@@ -178,25 +198,25 @@ module tb;
reg pre_active; reg pre_active;
reg pre_req, pre_wr; reg pre_req, pre_wr;
reg [MIG_ADDR_WIDTH-1:0] pre_addr; reg [MIG_ADDR_WIDTH-1:0] pre_addr;
reg [16*BURST_LEN-1:0] pre_wdata; reg [32*BURST_LEN-1:0] pre_wdata;
wire adp_req, adp_wr; wire adp_req, adp_wr;
wire [MIG_ADDR_WIDTH-1:0] adp_addr; wire [MIG_ADDR_WIDTH-1:0] adp_addr;
wire [16*BURST_LEN-1:0] adp_wdata; wire [32*BURST_LEN-1:0] adp_wdata;
wire [2*BURST_LEN-1:0] adp_wmask; wire [4*BURST_LEN-1:0] adp_wmask;
wire [16*BURST_LEN-1:0] adp_rdata; wire [32*BURST_LEN-1:0] adp_rdata;
wire adp_ready, adp_busy; wire adp_ready, adp_busy;
wire arb_ctrl_req_o, arb_ctrl_wr_o; wire arb_ctrl_req_o, arb_ctrl_wr_o;
wire [MIG_ADDR_WIDTH-1:0] arb_ctrl_addr_o; wire [MIG_ADDR_WIDTH-1:0] arb_ctrl_addr_o;
wire [16*BURST_LEN-1:0] arb_ctrl_wdata_o; wire [32*BURST_LEN-1:0] arb_ctrl_wdata_o;
wire [2*BURST_LEN-1:0] arb_ctrl_wmask_o; wire [4*BURST_LEN-1:0] arb_ctrl_wmask_o;
assign adp_req = pre_active ? pre_req : arb_ctrl_req_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_wr = pre_active ? pre_wr : arb_ctrl_wr_o;
assign adp_addr = pre_active ? pre_addr : arb_ctrl_addr_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_wdata = pre_active ? pre_wdata : arb_ctrl_wdata_o;
assign adp_wmask = pre_active ? {(2*BURST_LEN){1'b0}} : arb_ctrl_wmask_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 ( mig_native_adapter #(.BURST_LEN(BURST_LEN), .ADDR_WIDTH(MIG_ADDR_WIDTH)) u_adapter (
.clk(ui_clk), .rst(ui_clk_sync_rst), .clk(ui_clk), .rst(ui_clk_sync_rst),
@@ -215,7 +235,7 @@ module tb;
input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF)); input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF));
endfunction endfunction
task automatic sdram_write_burst(input [MIG_ADDR_WIDTH-1:0] word_addr, input [16*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);
begin begin
@(posedge ui_clk); while (adp_busy) @(posedge ui_clk); @(posedge ui_clk); while (adp_busy) @(posedge ui_clk);
pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data; pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data;
@@ -224,15 +244,18 @@ module tb;
end end
endtask 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; task automatic preload_sdram_layers;
integer li, bi, wb, tt; integer li, bi, wb, tt;
reg [16*BURST_LEN-1:0] burst_data; reg [32*BURST_LEN-1:0] burst_data;
begin begin
for (li = 0; li < L; li = li + 1) begin for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < (LAYER_BYTES/(2*BURST_LEN)); bi = bi + 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 for (wb = 0; wb < BURST_LEN; wb = wb + 1) begin
tt = bi*(2*BURST_LEN) + wb*2; tt = bi*(4*BURST_LEN) + wb*4;
burst_data[wb*16 +: 16] = {weight_byte(li, tt+1), weight_byte(li, tt)}; 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 end
sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data); sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data);
end end
@@ -240,30 +263,31 @@ module tb;
end end
endtask endtask
// ---- real activation preload (EXP-0081 layout: TWO consecutive // ---- real activation preload (EXP-0084 layout: FOUR consecutive
// tiles share one BURST_LEN=8-word burst -- even tile in the low // tiles share one BURST_LEN=8-word (256-bit) burst -- tile parity
// 64 bits, odd tile in the high 64 bits, see act_tile_fetch.v's // 0/1/2/3 -> quarters [63:0]/[127:64]/[191:128]/[255:192], see
// own header). ---- // act_tile_fetch.v's own header). ----
localparam [MIG_ADDR_WIDTH-1:0] ACT_MEM_BASE = 25'h10000; 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); 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/2)*BURST_LEN); act_x_base = {{(ADDR_WIDTH-MIG_ADDR_WIDTH){1'b0}}, ACT_MEM_BASE} + (li*M + pos) * ((N_TILES/4)*BURST_LEN);
endfunction endfunction
task automatic preload_ddr3_activations; task automatic preload_ddr3_activations;
integer li, pos, tp, k; integer li, pos, tq, qi;
reg [16*BURST_LEN-1:0] burst_data; reg [32*BURST_LEN-1:0] burst_data;
reg [ADDR_WIDTH-1:0] base; reg [ADDR_WIDTH-1:0] base;
begin begin
for (li = 0; li < L; li = li + 1) begin for (li = 0; li < L; li = li + 1) begin
for (pos = 0; pos < M; pos = pos + 1) begin for (pos = 0; pos < M; pos = pos + 1) begin
base = act_x_base(li, pos); base = act_x_base(li, pos);
for (tp = 0; tp < N_TILES/2; tp = tp + 1) begin for (tq = 0; tq < N_TILES/4; tq = tq + 1) begin
burst_data = {(16*BURST_LEN){1'b0}}; burst_data = {(32*BURST_LEN){1'b0}};
for (k = 0; k < P_IN/2; k = k + 1) for (qi = 0; qi < 4; qi = qi + 1)
burst_data[k*16 +: 16] = {input_byte(li, pos, (2*tp)*P_IN + 2*k+1), input_byte(li, pos, (2*tp)*P_IN + 2*k)}; 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),
for (k = 0; k < P_IN/2; k = k + 1) input_byte(li, pos, (4*tq+qi)*P_IN + 5), input_byte(li, pos, (4*tq+qi)*P_IN + 4),
burst_data[(P_IN/2+k)*16 +: 16] = {input_byte(li, pos, (2*tp+1)*P_IN + 2*k+1), input_byte(li, pos, (2*tp+1)*P_IN + 2*k)}; input_byte(li, pos, (4*tq+qi)*P_IN + 3), input_byte(li, pos, (4*tq+qi)*P_IN + 2),
sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + tp*BURST_LEN, burst_data); 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
end end
@@ -309,8 +333,8 @@ module tb;
wire [1:0] s_ctrl_req, s_ctrl_wr; wire [1:0] s_ctrl_req, s_ctrl_wr;
wire [1:0] s_ctrl_ready, s_ctrl_busy; wire [1:0] s_ctrl_ready, s_ctrl_busy;
wire [MIG_ADDR_WIDTH*2-1:0] s_ctrl_addr_flat; wire [MIG_ADDR_WIDTH*2-1:0] s_ctrl_addr_flat;
wire [16*BURST_LEN*2-1:0] s_ctrl_wdata_flat, s_ctrl_rdata_flat; wire [32*BURST_LEN*2-1:0] s_ctrl_wdata_flat, s_ctrl_rdata_flat;
wire [2*BURST_LEN*2-1:0] s_ctrl_wmask_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 ( 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), .clk(ui_clk), .rst(ui_clk_sync_rst),
@@ -350,9 +374,9 @@ module tb;
.mem_active(mem_active[gi]), .mem_grant(mem_grant[gi]), .mem_active(mem_active[gi]), .mem_grant(mem_grant[gi]),
.ctrl_req(s_ctrl_req[gi]), .ctrl_wr(s_ctrl_wr[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_addr(s_ctrl_addr_flat[gi*MIG_ADDR_WIDTH +: MIG_ADDR_WIDTH]),
.ctrl_wdata(s_ctrl_wdata_flat[gi*16*BURST_LEN +: 16*BURST_LEN]), .ctrl_wdata(s_ctrl_wdata_flat[gi*32*BURST_LEN +: 32*BURST_LEN]),
.ctrl_wmask(s_ctrl_wmask_flat[gi*2*BURST_LEN +: 2*BURST_LEN]), .ctrl_wmask(s_ctrl_wmask_flat[gi*4*BURST_LEN +: 4*BURST_LEN]),
.ctrl_rdata(s_ctrl_rdata_flat[gi*16*BURST_LEN +: 16*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]) .ctrl_ready(s_ctrl_ready[gi]), .ctrl_busy(s_ctrl_busy[gi])
); );
end end
+48 -54
View File
@@ -11,17 +11,20 @@
// //
// EXP-0079 UPDATE: packed_slot.v now wraps a REAL act_tile_fetch.v // EXP-0079 UPDATE: packed_slot.v now wraps a REAL act_tile_fetch.v
// (real DDR3 reads, no stand-in port left) -- this test now preloads // (real DDR3 reads, no stand-in port left) -- this test now preloads
// activation data into the SAME real SDR SDRAM placeholder backend // activation data into a real burst-memory backend, matching
// already used for weights (preload_sdram_activations, matching // act_tile_fetch.v's own real memory layout.
// act_tile_fetch.v's own real memory layout: one full BURST_LEN=8-word //
// burst per tile), instead of a combinational behavioral lookup. // EXP-0084 UPDATE: real 32-bit DDR3 channel widening -- backend
// switched to burst_mem_model32.v (the real AS4C32M16SA x16 SDR model
// this test used before is genuinely fixed at 16-bit and can't
// represent the new bus width, see that model's own header), and both
// preload tasks rewritten for the new BYTES_PER_BURST=4*BURST_LEN
// (weights, layer_prefetch_ctrl.v) and 4-tiles-per-burst (activations,
// act_tile_fetch.v) real layouts.
// ============================================================ // ============================================================
module tb; module tb;
localparam BURST_LEN = 8; localparam BURST_LEN = 8;
localparam ROW_BITS = 13; localparam SDRAM_ADDR_WIDTH = 25;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam SDRAM_ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; // 25
localparam CLK_FREQ_MHZ = 64; localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
@@ -42,50 +45,36 @@ module tb;
integer cyc; integer cyc;
always @(posedge clk) if (!rst) cyc <= cyc + 1; always @(posedge clk) if (!rst) cyc <= cyc + 1;
// ---- real SDRAM controller + model ---- // ---- real burst-memory backend ----
wire ctrl_req, ctrl_wr; wire ctrl_req, ctrl_wr;
wire [SDRAM_ADDR_WIDTH-1:0] ctrl_addr; wire [SDRAM_ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata; wire [32*BURST_LEN-1:0] ctrl_wdata;
wire [2*BURST_LEN-1:0] ctrl_wmask; wire [4*BURST_LEN-1:0] ctrl_wmask;
wire [16*BURST_LEN-1:0] ctrl_rdata; wire [32*BURST_LEN-1:0] ctrl_rdata;
wire ctrl_ready, ctrl_busy; wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
reg wpre_req, wpre_wr; reg wpre_req, wpre_wr;
reg [SDRAM_ADDR_WIDTH-1:0] wpre_addr; reg [SDRAM_ADDR_WIDTH-1:0] wpre_addr;
reg [16*BURST_LEN-1:0] wpre_wdata; reg [32*BURST_LEN-1:0] wpre_wdata;
reg pre_active; reg pre_active;
wire slot_ctrl_req, slot_ctrl_wr; wire slot_ctrl_req, slot_ctrl_wr;
wire [SDRAM_ADDR_WIDTH-1:0] slot_ctrl_addr; wire [SDRAM_ADDR_WIDTH-1:0] slot_ctrl_addr;
wire [16*BURST_LEN-1:0] slot_ctrl_wdata; wire [32*BURST_LEN-1:0] slot_ctrl_wdata;
wire [2*BURST_LEN-1:0] slot_ctrl_wmask; wire [4*BURST_LEN-1:0] slot_ctrl_wmask;
assign ctrl_req = pre_active ? wpre_req : slot_ctrl_req; assign ctrl_req = pre_active ? wpre_req : slot_ctrl_req;
assign ctrl_wr = pre_active ? wpre_wr : slot_ctrl_wr; assign ctrl_wr = pre_active ? wpre_wr : slot_ctrl_wr;
assign ctrl_addr = pre_active ? wpre_addr : slot_ctrl_addr; assign ctrl_addr = pre_active ? wpre_addr : slot_ctrl_addr;
assign ctrl_wdata = pre_active ? wpre_wdata : slot_ctrl_wdata; assign ctrl_wdata = pre_active ? wpre_wdata : slot_ctrl_wdata;
assign ctrl_wmask = pre_active ? {(2*BURST_LEN){1'b0}} : slot_ctrl_wmask; assign ctrl_wmask = pre_active ? {(4*BURST_LEN){1'b0}} : slot_ctrl_wmask;
sdram_controller #( burst_mem_model32 #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(SDRAM_ADDR_WIDTH)
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_mem (
) u_ctrl (
.clk(clk), .rst(rst), .clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
.sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
); );
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);
@@ -95,7 +84,7 @@ module tb;
input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF)); input_byte = $signed(8'((li*11 + pos*41 + t*7 + 3) & 8'hFF));
endfunction endfunction
task automatic sdram_write_burst(input [SDRAM_ADDR_WIDTH-1:0] word_addr, input [16*BURST_LEN-1:0] data); task automatic sdram_write_burst(input [SDRAM_ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data);
begin begin
@(posedge clk); while (ctrl_busy) @(posedge clk); @(posedge clk); while (ctrl_busy) @(posedge clk);
wpre_req = 1'b1; wpre_wr = 1'b1; wpre_addr = word_addr; wpre_wdata = data; wpre_req = 1'b1; wpre_wr = 1'b1; wpre_addr = word_addr; wpre_wdata = data;
@@ -104,15 +93,18 @@ module tb;
end end
endtask 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; task automatic preload_sdram_layers;
integer li, bi, wb, tt; integer li, bi, wb, tt;
reg [16*BURST_LEN-1:0] burst_data; reg [32*BURST_LEN-1:0] burst_data;
begin begin
for (li = 0; li < L; li = li + 1) begin for (li = 0; li < L; li = li + 1) begin
for (bi = 0; bi < (LAYER_BYTES/(2*BURST_LEN)); bi = bi + 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 for (wb = 0; wb < BURST_LEN; wb = wb + 1) begin
tt = bi*(2*BURST_LEN) + wb*2; tt = bi*(4*BURST_LEN) + wb*4;
burst_data[wb*16 +: 16] = {weight_byte(li, tt+1), weight_byte(li, tt)}; 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 end
sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data); sdram_write_burst((li*WORDS_PER_LAYER + bi*BURST_LEN), burst_data);
end end
@@ -120,31 +112,33 @@ module tb;
end end
endtask endtask
// ---- real activation preload (EXP-0081 layout: TWO consecutive // ---- real activation preload (EXP-0084 layout: FOUR consecutive
// tiles share one BURST_LEN=8-word burst -- even tile in the low // tiles share one BURST_LEN=8-word (256-bit) burst -- tile parity
// 64 bits, odd tile in the high 64 bits, see act_tile_fetch.v's // 0/1/2/3 -> quarters [63:0]/[127:64]/[191:128]/[255:192], see
// own header). x_base(li,pos) = ACT_MEM_BASE + (li*M+pos)* // act_tile_fetch.v's own header). x_base(li,pos) = ACT_MEM_BASE +
// (N_TILES/2*BURST_LEN), well clear of the weight region. ---- // (li*M+pos)*(N_TILES/4*BURST_LEN), well clear of the weight
// region. ----
localparam [ADDR_WIDTH-1:0] ACT_MEM_BASE = 26'h10000; localparam [ADDR_WIDTH-1:0] ACT_MEM_BASE = 26'h10000;
function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos); function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos);
act_x_base = ACT_MEM_BASE + (li*M + pos) * ((N_TILES/2)*BURST_LEN); act_x_base = ACT_MEM_BASE + (li*M + pos) * ((N_TILES/4)*BURST_LEN);
endfunction endfunction
task automatic preload_sdram_activations; task automatic preload_sdram_activations;
integer li, pos, tp, k; integer li, pos, tq, qi;
reg [16*BURST_LEN-1:0] burst_data; reg [32*BURST_LEN-1:0] burst_data;
reg [ADDR_WIDTH-1:0] base; reg [ADDR_WIDTH-1:0] base;
begin begin
for (li = 0; li < L; li = li + 1) begin for (li = 0; li < L; li = li + 1) begin
for (pos = 0; pos < M; pos = pos + 1) begin for (pos = 0; pos < M; pos = pos + 1) begin
base = act_x_base(li, pos); base = act_x_base(li, pos);
for (tp = 0; tp < N_TILES/2; tp = tp + 1) begin // tp = burst-pair index for (tq = 0; tq < N_TILES/4; tq = tq + 1) begin // tq = burst-quad index
burst_data = {(16*BURST_LEN){1'b0}}; burst_data = {(32*BURST_LEN){1'b0}};
for (k = 0; k < P_IN/2; k = k + 1) for (qi = 0; qi < 4; qi = qi + 1)
burst_data[k*16 +: 16] = {input_byte(li, pos, (2*tp)*P_IN + 2*k+1), input_byte(li, pos, (2*tp)*P_IN + 2*k)}; 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),
for (k = 0; k < P_IN/2; k = k + 1) input_byte(li, pos, (4*tq+qi)*P_IN + 5), input_byte(li, pos, (4*tq+qi)*P_IN + 4),
burst_data[(P_IN/2+k)*16 +: 16] = {input_byte(li, pos, (2*tp+1)*P_IN + 2*k+1), input_byte(li, pos, (2*tp+1)*P_IN + 2*k)}; input_byte(li, pos, (4*tq+qi)*P_IN + 3), input_byte(li, pos, (4*tq+qi)*P_IN + 2),
sdram_write_burst(base[SDRAM_ADDR_WIDTH-1:0] + tp*BURST_LEN, burst_data); input_byte(li, pos, (4*tq+qi)*P_IN + 1), input_byte(li, pos, (4*tq+qi)*P_IN + 0)};
sdram_write_burst(base[SDRAM_ADDR_WIDTH-1:0] + tq*BURST_LEN, burst_data);
end end
end end
end end
+24 -41
View File
@@ -12,10 +12,7 @@
// ============================================================ // ============================================================
module tb; module tb;
localparam BURST_LEN = 8; localparam BURST_LEN = 8;
localparam ROW_BITS = 13; localparam ADDR_WIDTH = 25;
localparam COL_BITS = 10;
localparam BANK_BITS = 2;
localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS;
localparam CLK_FREQ_MHZ = 64; localparam CLK_FREQ_MHZ = 64;
localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ;
localparam NUM_REQ = 3; localparam NUM_REQ = 3;
@@ -26,38 +23,24 @@ module tb;
wire ctrl_req, ctrl_wr; wire ctrl_req, ctrl_wr;
wire [ADDR_WIDTH-1:0] ctrl_addr; wire [ADDR_WIDTH-1:0] ctrl_addr;
wire [16*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata; wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata;
wire [2*BURST_LEN-1:0] ctrl_wmask; wire [4*BURST_LEN-1:0] ctrl_wmask;
wire ctrl_ready, ctrl_busy; wire ctrl_ready, ctrl_busy;
wire cke, cs_n, ras_n, cas_n, we_n;
wire [BANK_BITS-1:0] ba;
wire [ROW_BITS-1:0] a;
wire [15:0] dq;
wire [1:0] dqm;
sdram_controller #( burst_mem_model32 #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH)
.ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_mem (
) u_ctrl (
.clk(clk), .rst(rst), .clk(clk), .rst(rst),
.req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask),
.rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy)
.sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n),
.sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm)
);
sdram_model #(
.CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS)
) u_mem (
.clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n),
.ba(ba), .a(a), .dq(dq), .dqm(dqm)
); );
reg [NUM_REQ-1:0] req_active, req_req, req_wr; reg [NUM_REQ-1:0] req_active, req_req, req_wr;
wire [NUM_REQ-1:0] req_grant, req_ready, req_busy; wire [NUM_REQ-1:0] req_grant, req_ready, req_busy;
reg [NUM_REQ*ADDR_WIDTH-1:0] req_addr; reg [NUM_REQ*ADDR_WIDTH-1:0] req_addr;
reg [NUM_REQ*16*BURST_LEN-1:0] req_wdata; reg [NUM_REQ*32*BURST_LEN-1:0] req_wdata;
reg [NUM_REQ*2*BURST_LEN-1:0] req_wmask; reg [NUM_REQ*4*BURST_LEN-1:0] req_wmask;
wire [NUM_REQ*16*BURST_LEN-1:0] req_rdata; wire [NUM_REQ*32*BURST_LEN-1:0] req_rdata;
sdram_arbiter_n #( sdram_arbiter_n #(
.NUM_REQ(NUM_REQ), .ADDR_WIDTH(ADDR_WIDTH), .BURST_LEN(BURST_LEN) .NUM_REQ(NUM_REQ), .ADDR_WIDTH(ADDR_WIDTH), .BURST_LEN(BURST_LEN)
@@ -80,7 +63,7 @@ module tb;
// no retry if lost. // no retry if lost.
task automatic one_shot_txn( task automatic one_shot_txn(
input integer slot, input t_wr, input [ADDR_WIDTH-1:0] t_addr, input integer slot, input t_wr, input [ADDR_WIDTH-1:0] t_addr,
input [16*BURST_LEN-1:0] t_wdata, output [16*BURST_LEN-1:0] t_rdata input [32*BURST_LEN-1:0] t_wdata, output [32*BURST_LEN-1:0] t_rdata
); );
begin begin
@(posedge clk); @(posedge clk);
@@ -88,26 +71,26 @@ module tb;
req_req[slot] = 1'b1; req_req[slot] = 1'b1;
req_wr[slot] = t_wr; req_wr[slot] = t_wr;
req_addr[slot*ADDR_WIDTH +: ADDR_WIDTH] = t_addr; req_addr[slot*ADDR_WIDTH +: ADDR_WIDTH] = t_addr;
req_wdata[slot*16*BURST_LEN +: 16*BURST_LEN] = t_wdata; req_wdata[slot*32*BURST_LEN +: 32*BURST_LEN] = t_wdata;
req_wmask[slot*2*BURST_LEN +: 2*BURST_LEN] = {(2*BURST_LEN){1'b0}}; req_wmask[slot*4*BURST_LEN +: 4*BURST_LEN] = {(4*BURST_LEN){1'b0}};
@(posedge clk); @(posedge clk);
req_req[slot] = 1'b0; req_req[slot] = 1'b0;
while (!req_ready[slot]) @(posedge clk); while (!req_ready[slot]) @(posedge clk);
t_rdata = req_rdata[slot*16*BURST_LEN +: 16*BURST_LEN]; t_rdata = req_rdata[slot*32*BURST_LEN +: 32*BURST_LEN];
req_active[slot] = 1'b0; req_active[slot] = 1'b0;
end end
endtask endtask
reg [16*BURST_LEN-1:0] got, wpat; reg [32*BURST_LEN-1:0] got, wpat;
integer k; integer k;
task automatic check_slot(input integer slot, input [ADDR_WIDTH-1:0] a, input [15:0] pattern); task automatic check_slot(input integer slot, input [ADDR_WIDTH-1:0] a, input [15:0] pattern);
integer i; integer i;
begin begin
for (i = 0; i < BURST_LEN; i = i + 1) for (i = 0; i < BURST_LEN; i = i + 1)
wpat[i*16 +: 16] = pattern + i[15:0]; wpat[i*32 +: 32] = {pattern, pattern + i[15:0]};
one_shot_txn(slot, 1'b1, a, wpat, got); one_shot_txn(slot, 1'b1, a, wpat, got);
one_shot_txn(slot, 1'b0, a, {(16*BURST_LEN){1'b0}}, got); one_shot_txn(slot, 1'b0, a, {(32*BURST_LEN){1'b0}}, got);
tests = tests + 1; tests = tests + 1;
if (got !== wpat) begin if (got !== wpat) begin
$display("FAIL slot=%0d addr=%0d: got=%h expected=%h", slot, a, got, wpat); $display("FAIL slot=%0d addr=%0d: got=%h expected=%h", slot, a, got, wpat);
@@ -133,19 +116,19 @@ module tb;
$display("=== TEST 2: simultaneous multi-requester ACTIVATION (the real EXP-0066 risk case) -- each requester fires its OWN one-shot req only once IT sees its OWN grant, exactly matching packed_slot.v's real S_MEMWAIT usage, not a blind simultaneous fire ==="); $display("=== TEST 2: simultaneous multi-requester ACTIVATION (the real EXP-0066 risk case) -- each requester fires its OWN one-shot req only once IT sees its OWN grant, exactly matching packed_slot.v's real S_MEMWAIT usage, not a blind simultaneous fire ===");
begin : test2 begin : test2
reg [16*BURST_LEN-1:0] w0, w1, w2; reg [32*BURST_LEN-1:0] w0, w1, w2;
integer kk; integer kk;
for (kk = 0; kk < BURST_LEN; kk = kk + 1) begin for (kk = 0; kk < BURST_LEN; kk = kk + 1) begin
w0[kk*16 +: 16] = 16'hD000 + kk[15:0]; w0[kk*32 +: 32] = {16'hD000, 16'hD000 + kk[15:0]};
w1[kk*16 +: 16] = 16'hE000 + kk[15:0]; w1[kk*32 +: 32] = {16'hE000, 16'hE000 + kk[15:0]};
w2[kk*16 +: 16] = 16'hF000 + kk[15:0]; w2[kk*32 +: 32] = {16'hF000, 16'hF000 + kk[15:0]};
end end
req_addr[0*ADDR_WIDTH +: ADDR_WIDTH] = 25'd100; req_addr[0*ADDR_WIDTH +: ADDR_WIDTH] = 25'd100;
req_addr[1*ADDR_WIDTH +: ADDR_WIDTH] = 25'd108; req_addr[1*ADDR_WIDTH +: ADDR_WIDTH] = 25'd108;
req_addr[2*ADDR_WIDTH +: ADDR_WIDTH] = 25'd116; req_addr[2*ADDR_WIDTH +: ADDR_WIDTH] = 25'd116;
req_wdata[0*16*BURST_LEN +: 16*BURST_LEN] = w0; req_wdata[0*32*BURST_LEN +: 32*BURST_LEN] = w0;
req_wdata[1*16*BURST_LEN +: 16*BURST_LEN] = w1; req_wdata[1*32*BURST_LEN +: 32*BURST_LEN] = w1;
req_wdata[2*16*BURST_LEN +: 16*BURST_LEN] = w2; req_wdata[2*32*BURST_LEN +: 32*BURST_LEN] = w2;
req_wr[0] = 1'b1; req_wr[1] = 1'b1; req_wr[2] = 1'b1; req_wr[0] = 1'b1; req_wr[1] = 1'b1; req_wr[2] = 1'b1;
// all three raise `active` on the SAME cycle (the real // all three raise `active` on the SAME cycle (the real