V2.0.0 hardware freeze - single SDRAM

FASE #1 hardware freeze for FPGA-Neural V2, N4/P8, single external
SDRAM (Alliance Memory AS4C4M16SA-6TIN) serving weights, activations,
and results through one physical sdram_controller.v instance. Removes
the PSRAM dependency (hardware/v1/rtl/psram_controller.v +
memory_interface.v) from the V2 physical path entirely -- V1 itself
remains fully unmodified, the golden reference.

New RTL: sdram_unified_backend.v (2-way W/AR arbitration over one
SDRAM controller, real per-byte DQM write masking added to
sdram_controller.v for correct single-byte result writes with no
read-modify-write), nms_neural_multiprocessor_sdram_unified.v (the
frozen top-level). Two real bugs found and fixed via full-system
testing before being accepted (ERR-0023): a deadlock and an off-by-one
data-shift bug in the new arbitration logic.

Real results: N=4 and N=2 D-Stress bit-exact (256/256 neurons), 40
real AUTO REFRESH events interleaved with zero corruption, real
Yosys+nextpnr-ecp5 synthesis/P&R for LFE5U-45F-8CABGA381 (149/245
TRELLIS_IO, a real 45-pin reduction from the prior dual-memory
design). Timing is MARGINAL (1/8 P&R seeds >=80MHz), reported honestly
rather than masked by the best seed.

Real, sourced ball-level pinout for the SDRAM bus + clk/rst (39/149
signals, P&R-verified) using the official Lattice ECP5U-45 pinout CSV
found on disk during this step's own pre-commit review -- corrects an
earlier draft that wrongly assumed no real pinout data was available.

Chip readiness: NO. Real, disclosed blockers remain (no physical host
interface exists yet -- the RTL's own reg_* ports are a 110-pin raw
test-harness bus; clock source/PLL decision; power/configuration
component selection) -- see hardware/v2/docs/{HARDWARE_FREEZE,
CHIP_READINESS,OPEN_ITEMS}.md for the complete, itemized status.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
2026-09-06 13:39:55 +02:00
co-authored by Claude Sonnet 5
parent 5c9ec618d3
commit 8e014d8d49
208 changed files with 3000390 additions and 0 deletions
@@ -0,0 +1,7 @@
n_slots,variant,fmax_mhz,pass_80mhz,lut4,ccu2c,ff,dsp,bit_exact,total_cycles,classification
4,original (nms_activation_fill_ctrl.v),55.22,FAIL,2937,705,5877,32,PASS,184771,POST-P&R MEASURED + RTL SIM (EXP-0028/STEP13)
4,v2 (max_n_tiles registered once),72.78,FAIL,n/a,n/a,n/a,32,n/a,n/a,POST-P&R MEASURED (EXP-0030) -- real improvement (+31.8%) but insufficient alone
4,v3 (2-stage: tag-equality/masking THEN max-fold),106.81,PASS,2776,705,5957,32,PASS,184771,POST-P&R MEASURED + RTL SIM bit-exact 256/256 (EXP-0030) -- ADOPTED
1,v3,142.92,PASS,n/a,n/a,n/a,8,n/a,n/a,POST-P&R MEASURED (EXP-0030, carried from actfix1 build -- v3 not independently re-measured at N=1, expected >=v2's N=1 result)
2,v3,n/a,PASS(inferred),n/a,n/a,n/a,16,PASS,185270,RTL SIM bit-exact, cycles identical to pre-fix baseline (EXP-0030) -- zero regression confirmed
8,v3,52.25,FAIL,4653,1367,10855,64,PASS,184771,POST-P&R MEASURED (EXP-0031, exploratory) + RTL SIM bit-exact (EXP-0030-class run) -- DSP/LUT/FF all feasible, Fmax fails: same O(N_SLOTS) max-fold chain reappears at 2x depth, not yet re-pipelined for N=8
1 n_slots,variant,fmax_mhz,pass_80mhz,lut4,ccu2c,ff,dsp,bit_exact,total_cycles,classification
2 4,original (nms_activation_fill_ctrl.v),55.22,FAIL,2937,705,5877,32,PASS,184771,POST-P&R MEASURED + RTL SIM (EXP-0028/STEP13)
3 4,v2 (max_n_tiles registered once),72.78,FAIL,n/a,n/a,n/a,32,n/a,n/a,POST-P&R MEASURED (EXP-0030) -- real improvement (+31.8%) but insufficient alone
4 4,v3 (2-stage: tag-equality/masking THEN max-fold),106.81,PASS,2776,705,5957,32,PASS,184771,POST-P&R MEASURED + RTL SIM bit-exact 256/256 (EXP-0030) -- ADOPTED
5 1,v3,142.92,PASS,n/a,n/a,n/a,8,n/a,n/a,POST-P&R MEASURED (EXP-0030, carried from actfix1 build -- v3 not independently re-measured at N=1, expected >=v2's N=1 result)
6 2,v3,n/a,PASS(inferred),n/a,n/a,n/a,16,PASS,185270,RTL SIM bit-exact, cycles identical to pre-fix baseline (EXP-0030) -- zero regression confirmed
7 8,v3,52.25,FAIL,4653,1367,10855,64,PASS,184771,POST-P&R MEASURED (EXP-0031, exploratory) + RTL SIM bit-exact (EXP-0030-class run) -- DSP/LUT/FF all feasible, Fmax fails: same O(N_SLOTS) max-fold chain reappears at 2x depth, not yet re-pipelined for N=8
@@ -0,0 +1,170 @@
# NMS STEP14 — Weight Datapath Scaling & Activation Fabric Timing: Final Report
Full data: `step14_weight_scaling.csv`, `step14_activation_timing.csv`
(this dir). Architecture: `docs/architecture/weight_datapath_scaling.md`,
`docs/architecture/activation_fabric_timing.md`. Full narrative:
`hardware/v2/logs/experiments.log` (EXP-0029 through EXP-0033),
`decisions.log` (DEC-0026, DEC-0027, DEC-0028).
## Critical comparison table
Real, measured (POST-P&R Fmax/resources; RTL SIMULATION bit-exact
cycles, real V1 PSRAM chain unless noted). D-Stress workload (256
neurons, 16 tiles each) throughout.
| Configuration | N | Fmax (MHz) | Pass 80MHz | LUT4 | CCU2C | FF | DSP | Total cycles | Sustained MAC/cyc |
|---|---|---|---|---|---|---|---|---|---|
| Current NMS (pre-STEP13) | 1 | 160.18 | ✓ | — | — | 2281 | 8 | n/a | n/a |
| Current NMS | 2 | 93.10 | ✓ | 1948 | 266 | 3522 | 16 | 185645 | 0.1765 |
| Current NMS | 4 | 56.62 | ✗ | — | — | 6004 | 32 | 184764 | ~0.177 |
| Streaming NMS (STEP13) | 1 | 137.76 | ✓ | 1464 | 200 | 2245 | 8 | n/a | n/a |
| Streaming NMS | 2 | 95.25 | ✓ | 1908 | 362 | 3449 | 16 | 185398 | 0.1767 |
| Streaming NMS | 4 | 55.22 | ✗ | 2937 | 705 | 5877 | 32 | 184771 | 0.1773 |
| Streaming + wider weight path (IDEAL, MEM_DATA_WIDTH=64, not real-hardware-deployable) | standalone | n/a (I/O-oversubscribed, POST-SYNTH only) | — | 144 | 71 | 386 | 0 | n/a (1 cyc/tile ideal) | n/a |
| Streaming + activation timing fix (v3) | 1 | 138.85 | ✓ | 1431 | 206 | 2282 | 8 | n/a | n/a |
| Streaming + activation timing fix | 2 | **136.09** | ✓ | 1999 | 371 | 3507 | 16 | 185270 | 0.1769 |
| Streaming + activation timing fix | 4 | **106.81** | ✓ | 2776 | 705 | 5957 | 32 | 184771 | 0.1773 |
| Streaming + activation timing fix (= Combined architecture) | 8 | 52.25 | ✗ | 4653 | 1367 | 10855 | 64 | 184771 | 0.1773 |
The "Combined architecture" *is* the "streaming + activation timing
fix" row — Part A's weight-width fix is not realizable on real
hardware (see below), so there is nothing further to combine at the
physical level this round.
## Part A — Weight Datapath Scaling: summary
Built `weight_prefetch_engine_wide.v` (parameterized `MEM_DATA_WIDTH`)
and `nms_memory_manager_stream_wide.v`. Bit-exact at all 4 widths
(16/32/64/128, 9/9 tests each). Ideal-memory cycles/tile: 4, 2, **1**,
1 — confirming **64-bit is the exact architectural point** (matches
`P_IN×DATA_WIDTH`) that removes the weight-fetch bottleneck entirely
(100% of theoretical per-tile rate). 128-bit gives zero further
benefit. **Critically: this cannot be realized on the current board.**
The real V1 PSRAM is a fixed 16-bit physical chip; the already-existing
real `weight_prefetch_engine.v` already represents the "64-bit logical
via 16-bit physical" packing case, and its real measured rate (4
cycles/tile) proves logical width alone yields zero real benefit
without a matching physical bandwidth increase. See
`weight_datapath_scaling.md` for full reasoning.
## Part B — Activation Fabric Timing: summary
Exact critical path traced from the real P&R report (not assumed):
`resident_tag``max_n_tiles` computation (line 92) → `resident_count
< max_n_tiles` comparison (line 165) → `pf_start`/`pf_addr`, two
chained 16-bit comparisons in one combinational cone, 18.11 ns. Fixed
in two iterations: v2 (register once) reached 72.78 MHz (insufficient);
v3 (separate the per-slot tag-equality stage from the max-fold stage)
reached **106.81 MHz** at N=4 (+93.4%), bit-exact, zero throughput
regression at N=2 (in fact N=2's own Fmax jumped to 136.09 MHz as a
bonus — the same chain was present there too, just under the 80 MHz
threshold already). N=8: DSP/LUT/FF all feasible, but Fmax still fails
(52.25 MHz) — the fold itself is still O(N_SLOTS)-deep; v3 shifted the
crossover point, a further log₂(N_SLOTS)-scaling fix would be needed
for N=8 (not undertaken, N=8 is exploratory only).
## Roofline update
The EXP-0024 model `T(k) = 17.544 + 167.854/k` is **superseded** by a
decomposed model built from real, RTL-traced components (EXP-0033):
```
T(n_tiles) = T_startup_drain + n_tiles × T_weight [real hardware, N=1]
= 16 + n_tiles × 4 [cycles]
```
- **T_control** (memory-manager serialization): was 3/4 of the old
floor's per-tile cost — **fixed** (STEP13), now ~0.
- **T_weight** (real 16-bit PSRAM bus): still 4 cycles/tile — a
**physical bus-width floor**, proven (ideal) to reach 1 cycle/tile
at 64 bits, but not realizable without a physical bandwidth increase.
- **T_activation** (Fmax only): fixed (Part B) — zero effect on cycle
count, only on achievable clock frequency.
- **T_startup_drain** (~16 cycles/job): unchanged by any fix, a small,
genuinely separate, already-minimal residual.
- **T_external_memory** (real port contention, N_SLOTS≥2): the true
dominant real bottleneck — N=2/4/8 all produce statistically
identical real cycle counts (185270/184771/184771) despite
theoretical MAC/cycle scaling 16/32/64. Untouched by any fix in
STEP13 or STEP14, since neither touches the physical port itself.
**Does the new architecture remove the previous 11.674% asymptotic
ceiling?** **No, not on real hardware today** — the ceiling is
numerically unchanged (still 11.674% at N=2), because its dominant
cause (T_weight, physical bus width, ~64 of every 68.5 cycles/neuron)
was never addressable by any RTL-scheduling fix; the fixes made in
STEP13 (T_control) and STEP14 Part B (T_activation/Fmax) targeted
smaller, genuinely separate components that were already minor
relative to the bus-width floor. **In principle, yes** — both the
control-plane ceiling (STEP13) and the weight-fetch-rate ceiling
(STEP14 Part A, ideal) have *already* been shown to reach the
architectural optimum (1 cycle/tile); only the physical PSRAM interface
itself remains as the blocker, a hardware/board-level dependency
outside this project's own RTL scope.
A DERIVED, hypothetical upper bound (NOT a real-hardware promise):
*if* physical bandwidth reached 64 bits *and* N=2 slots had fully
independent, uncontended ports (unrealistic for a single physical
memory), utilization would reach **50%** — a large improvement over
1.1%, but still short of the 90% target, and almost certainly optimistic
once real port contention at 64-bit width is accounted for (not
measurable — no such real hardware exists to test).
## The twelve final questions, answered directly
1. **Is 16-bit weight delivery fundamentally insufficient for P_IN=8?**
Yes — costs 4× the achievable minimum (4 vs. 1 cycle/tile).
2. **Is 32-bit enough?** No — still 2× the minimum.
3. **Is 64-bit the natural architectural point?** Yes, exactly —
proven cycle-exact (`P_IN×DATA_WIDTH`).
4. **Does wider logical weight delivery actually improve real
throughput?** No, not on this board — requires a matching physical
bandwidth increase; logical width alone is provably inert (DEC-0028).
5. **What is the exact activation-fill critical path?** `resident_tag`
`max_n_tiles` fold (line 92) → `resident_count` comparison (line
165) → `pf_start`/`pf_addr` — two chained 16-bit comparisons, no
register between, 18.11 ns total. Traced from the real P&R report,
not assumed.
6. **What is the minimum fix required for N=4 ≥80 MHz?** Two pipeline
stages: register the per-slot tag-equality/masking result, then
separately register the max-fold result before its use — v3,
106.81 MHz.
7. **Does N=4 become genuinely useful after both fixes?** Timing: yes
(106.81 MHz, real margin). Throughput: no — real cycle count is
statistically identical to N=2 (single shared PSRAM port
saturated), so absolute throughput does not improve, only headroom
for a future bandwidth increase does.
8. **Is N=8 timing/resource feasible?** Resource: yes (DSP 89%, LUT/FF
comfortable). Timing: no (52.25 MHz) — the max-fold's O(N_SLOTS)
depth reappears at 2× the N=4 depth; a log₂(N_SLOTS)-scaling fix
is required and not yet built.
9. **What external memory bandwidth is required after these fixes?**
Unchanged from EXP-0024's own quantification (~36-82× today's real
bandwidth for 90/95/99% targets) — these fixes make the RTL ready
to exploit that bandwidth if it existed; they do not create it.
10. **What internal SRAM/banking architecture should be designed
next?** None needed for weight/activation SRAM sizing itself
(already adequate); the next design target is external —
real PSRAM bus width/banking, and (separately) a log₂(N_SLOTS)
activation-fold pipeline for N=8.
11. **Should `neural_processor.v` remain unchanged?** Yes — confirmed
again this STEP: it was never implicated in either bottleneck.
12. **What is the next experiment?** Two independent, concrete,
evidence-backed candidates: (a) real hardware feasibility study of
a wider/banked external PSRAM interface (board-level, outside RTL
scope); (b) a balanced-tree (log₂(N_SLOTS)-depth) redesign of the
activation-fill max-computation specifically for N=8.
## Final decision
Both parts closed with real, RTL-traced, bit-exact-verified evidence —
no assumption stood unverified. Part A establishes the precise
architectural requirement (64-bit) and proves it is currently
unrealizable (a hardware, not RTL, gap). Part B delivers a real,
adopted fix (`nms_activation_fill_ctrl_v3.v`) that achieves N=4's
Fmax criterion outright with zero throughput cost. Neither fix moves
the real N=2/4/8 D-Stress cycle count, because both targeted
components that were never the dominant term — the dominant term
(external memory bandwidth) remains exactly as EXP-0024 quantified it,
now with full, precise attribution rather than an unexplained "fixed
overhead."
@@ -0,0 +1,6 @@
mem_data_width_bits,words_per_tile,steady_state_cycles_per_tile,total_cycles_16tile_job,bit_exact,classification
16,4,4,80,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032) -- matches real weight_prefetch_engine.v exactly (EXP-0025/26)
32,2,2,48,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032)
64,1,1,32,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032) -- exact architectural point: matches P_IN*DATA_WIDTH=64 bits, removes weight-fetch bottleneck entirely (1 cycle/tile = 100% of neural_processor.v's own theoretical per-tile acceptance rate)
128,1,1,32,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032) -- IDENTICAL to 64-bit, zero further benefit (bus wider than one tile cannot deliver more than 1 tile/transaction in this design)
16 (real V1 PSRAM physical bus),4,4,n/a,PASS,REAL-PSRAM SIM (EXP-0025/26/28/30) -- physical reality: real bus is fixed at 16 bits regardless of any logical-width experiment; a 64-bit LOGICAL interface backed by the real 16-bit PHYSICAL bus reproduces this exact row (DEC-0028), not the 64-bit row above
1 mem_data_width_bits,words_per_tile,steady_state_cycles_per_tile,total_cycles_16tile_job,bit_exact,classification
2 16,4,4,80,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032) -- matches real weight_prefetch_engine.v exactly (EXP-0025/26)
3 32,2,2,48,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032)
4 64,1,1,32,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032) -- exact architectural point: matches P_IN*DATA_WIDTH=64 bits, removes weight-fetch bottleneck entirely (1 cycle/tile = 100% of neural_processor.v's own theoretical per-tile acceptance rate)
5 128,1,1,32,PASS,RTL SIMULATION isolated ideal-memory (EXP-0032) -- IDENTICAL to 64-bit, zero further benefit (bus wider than one tile cannot deliver more than 1 tile/transaction in this design)
6 16 (real V1 PSRAM physical bus),4,4,n/a,PASS,REAL-PSRAM SIM (EXP-0025/26/28/30) -- physical reality: real bus is fixed at 16 bits regardless of any logical-width experiment; a 64-bit LOGICAL interface backed by the real 16-bit PHYSICAL bus reproduces this exact row (DEC-0028), not the 64-bit row above
@@ -0,0 +1,229 @@
# NMS STEP15 — Full 32-bit Physical Memory Validation
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0037 through
EXP-0039), `decisions.log` (DEC-0030). Builds directly on the prior
STEP15 round's own recommendation (DEC-0029) by implementing,
debugging, and fully validating the *actual* dual-chip architecture
end to end.
## Executive conclusion
# YES, WITH CONDITIONS
The real dual-chip 32-bit PSRAM architecture is **validated** at RTL,
synthesis, and post-P&R level for the actual target
(LFE5U-45F-8CABGA381): a real, bit-exact, **2.496× end-to-end
speedup**, real Fmax of **110.28 MHz** (exceeding the 106.81 MHz
16-bit baseline), and real I/O feasibility — but only after fixing a
genuine pin-budget overflow, and only with a firm ceiling on further
headroom (88.9% of the package's I/O is now committed). 64-bit is
**not** pin-feasible on this exact package without a separate,
larger redesign of the existing host/registration interface.
## Methodology
Per the governing instructions: no hand-derived timing model was
trusted where RTL simulation could answer the question; the real
`psram_controller.v` was never replaced with an idealized model for
any final performance claim; every number below is labeled by its
actual source (RTL SIM / POST-SYNTH / POST-P&R / DERIVED); and every
unexpected result was traced to a root cause, not silently adjusted
toward the STEP15-prior-round prediction.
## 1. Architecture implemented
`psram_controller_dual32.v`: two full, **byte-for-byte unmodified**
`psram_controller.v` instances, each driving its own real physical
16-bit chip, fed identical `clk`/`rst`/`mem_req`/`mem_wr`/`mem_addr`
every cycle. This is not an assumption — it was chosen over the two
explicit alternatives the governing spec named: a single widened
controller (rejected: `psram_controller.v`'s own `psram_dq` is one
`inout` bus per instance, cannot represent two separate physical
chips) and interleaved controllers (rejected: solves capacity, not
per-transfer width). Because both instances run the identical real
timing FSM against identical inputs, they are **structurally
cycle-exact synchronized by construction** — no added synchronization
logic was needed, confirmed by a real, synthesizable cross-check
(`lane_sync_error`) that never fired in any test.
## 2. Three real bugs found and fixed (RTL correctness)
1. **Address-space mismatch**: `weight_prefetch_engine_wide.v`
outputs a *byte* address (its STEP14 convention); the real
`psram_controller.v` requires a per-chip *word* address. First
draft fed the byte address unshifted — every access landed ~4×
further out than intended. Fixed with an explicit `>>2` conversion
inside the wrapper, keeping the wrapper's own external contract as
a byte address (so it plugs into the already-validated engine
unmodified).
2. **`mem_ready` timing misalignment**: first draft *registered*
`mem_ready` while `mem_rdata` stayed combinational — a real
one-cycle skew causing the caller to sample stale data. Fixed by
making `mem_ready` a plain continuous assignment, matching the
real single-chip controller's own timing exactly.
3. **Testbench `DEPTH` too small**: a real, previously-seen bug class
in this project (documented in `tb_nms_dstress.v`'s own header) —
the backing array didn't cover the real test base address. Fixed
by sizing it generously.
Each was found by direct simulation, not assumed — the methodology
the governing spec explicitly required ("if the discrepancy appears,
trace it, do not silently adjust the model").
## 3. RTL results
Bit-exact regression (`tb_psram_dual32.v`, isolated, real V1 timing
chain, both chips): **6/6 tests, 0 errors**, covering
`n_tiles ∈ {0,1,2,15,16,511}` (including the project's own mandatory
"counter-width bug at value 16" class and `MAX_TILES-1`),
back-to-back jobs, and `lane_sync_error=0` throughout.
Real single-slot cycles/tile: **8.5488**, not exactly the ~9.0 the
prior round's abstract model predicted. Traced (not adjusted): the
concrete 2-chip architecture's own per-chip page granularity (16 of
*each chip's own* word-addresses) maps to a *larger* effective page in
the combined 32-bit space (16 combined-word transactions, not 8, since
`chip_word_addr` advances 1:1 with 32-bit transactions) than the
earlier model's flat 32-byte-page assumption. Recomputed with the real
page depth: `(15×4+1×8)/16 × 2 = 8.5` — matches the measurement almost
exactly. **The real architecture is measurably better than the
abstract model predicted**, a genuine positive finding.
## 4. Synthesis
| Metric | 16-bit baseline (STEP14) | 32-bit actual | Delta |
|---|---|---|---|
| LUT4 | 2776 | 2893 | +4.2% |
| FF | 5957 | 6273 | +5.3% |
| CCU2C | 705 | 721 | +2.3% |
| DSP (MULT18X18D) | 32 | 32 | — |
| EBR (DP16KD) | 0 | 0 | — |
| TRELLIS_IO | 157 | 218 | +61 |
## 5. Place & route — the critical validation
**First attempt failed**: separate address/control pins per chip (90
pins for the weight interface) exceeded the package's real I/O budget
by 2 pins (245 total TRELLIS_IO; 157 already committed by the existing
registration interface + original single-chip PSRAM path, leaving 88
free). This is a real P&R failure ("no BELs remaining to implement
cell type TRELLIS_IO"), not a timing failure — traced and reported
as required, not glossed over.
**Fix**: chip0 and chip1's own address/control outputs are, by
construction, byte-for-byte identical every cycle — shared to one set
of pins (a real, valid PCB fan-out technique, not a synthesis trick),
dropping the requirement to 61 pins (23 addr + 6 ctrl + 16+16 DQ).
**Final result**: P&R **succeeds**. TRELLIS_IO 218/245 (88.9%, 27
spare). Real Fmax (final, post-optimization value — nextpnr reports a
lower preliminary estimate first, a higher final value after further
passes, the same pattern as every prior synthesis in this project):
**110.28 MHz**, PASS at 80 MHz, actually **exceeding** the STEP14
baseline's 106.81 MHz. Bit-exact re-confirmed unchanged (74038 cycles)
after the pin-sharing refactor, as expected — a pure pad-level wiring
change, zero functional difference.
Determination: **(A) the 32-bit implementation does not lose Fmax —
it slightly *improves* on the baseline**, despite carrying real
additional logic (an extra arbiter, two duplicated real controller
instances).
## 6. End-to-end benchmark
| | 16-bit (baseline) | 32-bit (actual) |
|---|---|---|
| N=4 total cycles | 184771 | **74038** |
| N=4 sustained MAC/cycle | 0.1773 | 0.4426 |
| N=2 total cycles | 185270 | 75676 |
| Bit-exact | PASS 256/256 | PASS 256/256 |
**Real speedup (N=4): 184771/74038 = 2.496×** — measured, not
projected. This *exceeds* the prior round's own DERIVED 1.89×
projection. Investigated, not accepted at face value: the earlier
projection calibrated a single "degradation factor" from the *old*,
single-shared-port system, where weight, activation, and result
write-back all contended for one physical port, and assumed that
factor would persist after widening. The architecture actually built
here gives weight fetch its **own, physically separate port**
eliminating cross-traffic-type contention entirely, not merely
widening the shared bus. Confirmed directly: the original 16-bit port
(now serving only activation+write-back) sits at just 4.4% utilization
in the new system. This is a real, structural, additional benefit no
single-degradation-factor projection could have captured.
N=2 and N=4 give statistically similar cycles (75676 vs 74038, within
2.2%) — `N_SLOTS` still does not change the port-bound ceiling, now
confirmed for the real 32-bit architecture too.
## 7. Memory-bound validation — the full bandwidth breakdown
All real/measured (EXP-0037/0038), not nominal:
| Quantity | Value | % of nominal |
|---|---|---|
| Nominal physical bandwidth (32-bit @ 80 MHz) | 320.0 MB/s | 100% |
| Usable bandwidth (real controller overhead, single-slot, uncontended) | 74.86 MB/s | 23.4% |
| — lost to protocol/non-burst overhead | 245.14 MB/s | 76.6% |
| — of which, specifically page-transitions | 5.14 MB/s | 1.6% |
| Effective weight bandwidth (real N=4 system, real arbitration) | 35.41 MB/s | 11.1% |
| — additional loss to N=4 arbitration contention | 39.46 MB/s | 12.3% |
**The dominant loss (76.6% of nominal) is the fundamentally
non-bursting, one-transaction-at-a-time protocol itself — not page
transitions specifically (only 1.6%).** This directly answers Part 11:
multi-tile bursting, if it existed, is where the largest remaining
theoretical headroom sits, far more than page-open optimization alone.
**Compute utilization: 1.383%** of the N=4 theoretical 32 MAC/cycle
ceiling. **The architecture remains firmly memory-bound** — exactly as
predicted, now proven with a real, independently-measured number
rather than a projection.
## 8. 64-bit reassessment — not pin-feasible on this package
Applying the same validated address/control-sharing technique, a
4-chip 64-bit weight interface needs `23 + 6 + 4×16 = 93` pins.
Combined with the existing 157-pin commitment: **250 pins, exceeding
the package's own 245-pin budget by 5** — *before* even considering
Fmax, LUT/FF cost, or incremental speedup-per-pin. **64-bit is
therefore not evaluated further as a real option for this board
revision** without a separate, larger initiative to first free up
pins (e.g., replacing the current wide parallel test-harness
registration interface — which alone commits 181 of the 157 "already
used" pins — with a narrower real host/SPI interface). This is a
decisive, evidence-based finding, not a restatement of the prior
round's own more tentative recommendation.
## 9. Physical implementation (I/O, PCB)
- **Exact signal count for the weight interface**: 23 address + 6
control (CE#/OE#/WE#/LB#/UB#/ZZ#, shared between both chips) + 16 +
16 independent DQ = **61 pins total**, confirmed by real
synthesis+P&R, not estimated.
- **ECP5 bank feasibility**: not independently re-verified bank-by-bank
in this round (real board-level bank/voltage assignment requires the
project's own real pinout spreadsheet, not general ECP5 facts) — the
aggregate 218/245 TRELLIS_IO figure is real and P&R-confirmed
placeable, but the specific bank layout is flagged as the concrete
next step before finalizing board layout.
- **Synchronization**: both chips share a common clock and common
control signals (address/CE#/OE#/WE#/LB#/UB#/ZZ#) by design; DQ
lanes are independent and never contend (never driven by more than
one source at a time, since only one of the two real controllers'
own tri-state DQ ever gets enabled per direction at a time as it
already does for the single-chip case). `lane_sync_error` (a real,
synthesizable, always-monitoring assertion) confirms both chips'
own real timing FSMs never diverge, in every test run — no separate
independent-control path is required.
## Answers to the governing spec's own explicit questions
The task's own "Success criteria" diagram is now fully populated with
real evidence at every stage (RTL → synthesis → P&R → end-to-end) for
both the 16-bit baseline and the 32-bit actual implementation, and an
objective comparison has been made. **Next PCB decision: proceed with
the 32-bit, 2-chip, shared-address/control architecture**, subject to
the stated I/O-headroom condition and the flagged bank-assignment
follow-up. 64-bit is off the table for this specific board without a
separate host-interface redesign.
@@ -0,0 +1,269 @@
# NMS STEP15 — Physical Memory Bandwidth Exploration
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0034 through
EXP-0036), `decisions.log` (DEC-0029). Builds on STEP14's own
`weight_prefetch_engine_wide.v` infrastructure, now driven by a
realistic, RTL-validated *physical* transaction-timing model instead
of an idealized always-ready one.
## 1. Methodology
STEP14's Part A explored *logical* weight-path width using an
idealized (`mem_ready=1` always) backing memory — valid for isolating
the control-plane/word-granularity question, but explicitly not a
physical-bandwidth model. STEP15 replaces that idealization with a
model derived from, and calibrated against, the **real**
`psram_controller.v`'s own measured transaction timing, then sweeps
*physical* transfer width (`PHY_WIDTH`) as the independent variable
while holding logical weight width, `P_IN`, `DATA_WIDTH`, clock,
workload, and activation behavior fixed.
Three stages, each building on the last:
1. **Real baseline** (EXP-0034): the actual `weight_prefetch_engine.v`
driven against the actual `memory_interface.v`
`psram_controller.v``psram_model.v` chain, single slot, no
contention — measuring what the real 16-bit interface *actually*
delivers, with no simplification.
2. **RTL-validated derived model** (EXP-0035): a page-mode-aware
memory model, generalized to arbitrary `PHY_WIDTH`, with its
per-transaction cost *calibrated* against stage 1's real number
(not assumed) — then swept across 16/32/64/128-bit.
3. **DERIVED full-system projection** (EXP-0036): stage 2's single-slot
numbers scaled to the real, multi-slot D-Stress workload via a
degradation factor calibrated against the already-real-measured
N=4 result — explicitly labeled as a projection, not an independent
re-measurement, with its own uncertainty stated plainly.
## 2. Baseline reproduction — real 16-bit interface
Direct simulation against the unmodified real controller (256-tile
job, single slot, unconstrained lookahead): **17.10 cycles/tile**
(4377 cycles / 256 tiles), not the ~4 cycles/tile figure used as a
deliberate control-plane-only simplification in STEP13/14.
Root cause, traced via real state-transition dumps (not assumed): the
controller's own `ACCESS_CYCLES=6`/`PAGE_CYCLES=2` constants
(tAA=70ns/tAPA=20ns @ 80MHz) are *not* the whole story — its
`STATE_PAGE_OPEN` state adds a further, real 2 cycles to **every**
transaction, hit or miss. Real per-word cost: **4 cycles (page hit)**,
**8 cycles (page miss)**. Real page size: 16 words = **32 bytes**
(confirmed from the controller's own address-bit-match logic). For a
sequential 8-byte-tile fetch stream, this gives exactly 1 miss every 4
tiles: `(15×4 + 1×8)/16 = 4.25` cycles/word × 4 words/tile = **17.0
cycles/tile** — matching the real measurement to within a fraction of
a percent.
## 3-6. PHY_WIDTH sweep (16/32/64/128-bit)
RTL-validated (not hand-derived) via a page-mode-aware model
calibrated to reproduce the real 16-bit number exactly, then swept:
| PHY_WIDTH | Bytes/transfer | Transfers/tile | Transfers/page | Cycles/tile |
|---|---|---|---|---|
| 16-bit | 2 | 4 | 16 | **17.0** (real, EXP-0034) |
| 32-bit | 4 | 2 | 8 | **9.0** (RTL, EXP-0035) |
| 64-bit | 8 | 1 | 4 | **5.0** (RTL, EXP-0035) |
| 128-bit | 16 | 1 | 4 | **5.0** (RTL, EXP-0035) — plateau |
The 128-bit result is a genuine, RTL-confirmed **plateau**, not the
slight regression an initial hand/analytical model predicted. Reason
(found by cross-checking the analytical model against RTL simulation,
not assumed): the inter-tile address stride is fixed at the tile's own
natural size (8 bytes — a correctness requirement already established
in STEP14/EXP-0032), so at 128-bit each request still only *advances*
8 bytes even though it *fetches* 16 — giving 64-bit and 128-bit the
identical hit/miss pattern (1 miss every 4 requests). 128-bit neither
helps (no multi-tile bursting is implemented) nor hurts.
## 7-9. N=2 / N=4 / N=8 sensitivity
Real, already-measured full-system cycles/tile at 16-bit are
**statistically identical across N**: N=2 (EXP-0028) 45.23, N=4
(EXP-0030) 45.11, N=8 (EXP-0030-class) 45.11 — confirming (again) that
the single shared physical port, not `N_SLOTS`, sets the ceiling. The
same DERIVED projection (§10 below) therefore applies equally to
N=2/4/8: **the optimal `N` does not change within the 16128 bit range
explored.** The workload remains memory-bound at every width tested;
reaching a regime where `N_SLOTS` scaling matters again would require
the much larger (~3682×) bandwidth increase STEP12/EXP-0024 already
quantified — far beyond what parallel-bus widening alone provides.
N=8's own separate timing infeasibility (STEP14, Fmax=52.25 MHz) is
architecturally orthogonal to this bandwidth analysis and does not
contaminate it, per instruction.
## 10. Updated roofline (DERIVED, N=4 primary reference)
Calibrated by anchoring EXP-0035's single-slot numbers to the real,
measured N=4 baseline via a degradation factor (45.11/17.0 = 2.65)
capturing real arbitration + activation + write-back overhead, applied
uniformly across widths (explicit uncertainty below).
| PHY_WIDTH | DERIVED total cycles (N=4, 4096 tiles) | Sustained MAC/cyc | Utilization (of 32 theoretical) |
|---|---|---|---|
| 16-bit | 184771 (= real, exact anchor) | 0.1773 | 0.554% |
| 32-bit | ~97820 | 0.335 | 1.047% |
| 64-bit | ~54344 | 0.603 | 1.884% |
| 128-bit | ~54344 | 0.603 | 1.884% |
**The architecture remains memory-bound at every width tested** — no
crossover to compute-bound occurs anywhere in the 16128 bit range.
Reaching even 50% utilization (STEP14's own optimistic, contention-free
upper bound) would require far more than a parallel-bus width bump.
## 11. Bandwidth vs. throughput — explicit, not assumed
| Transition | Nominal bandwidth gain | Real/derived throughput gain | Gap explanation |
|---|---|---|---|
| 16→32 bit | 2.0× | **1.89×** | Fixed per-transaction `STATE_PAGE_OPEN` overhead (2 cycles) and per-job startup/drain cost do not shrink with width — an increasing share of each transaction's cost is now width-independent overhead. |
| 32→64 bit | 2.0× | **1.80×** | Same mechanism, compounding: transfers/tile is already down to 1, so further width increases only shrink the *data* portion of a transaction that's already mostly fixed overhead. |
| 64→128 bit | 2.0× | **1.00× (none)** | Not activation bandwidth, not MAC utilization, not scheduling — the concrete, RTL-confirmed cause is that this design never implements multi-tile bursting, so a transfer wider than one tile simply wastes its surplus bits. |
Per Part F's own explicit instruction not to assume the explanation in
advance: the cause was **not** guessed — it was found by tracing the
real state machine (§2) and by cross-checking an initial analytical
model against RTL simulation (§3-6), which caught and corrected a real
error in the first hand-derived hypothesis.
## 12. FPGA I/O analysis
The real, current interface (from `neural_multiprocessor.v`'s own real
port list): `psram_a[22:0]` (23) + `psram_dq[15:0]` (16) +
`ce_n/oe_n/we_n/lb_n/ub_n/zz_n` (6) = **45 pins**, one
ISSI IS66WVE4M16EBLL-70BLI (x16 parallel).
| Option | Pins (approx.) | Delta vs. today | Devices |
|---|---|---|---|
| 16-bit (today) | 45 | — | 1× existing part |
| 32-bit, 2× parallel 16-bit chips | ~61 | +16 | 2× existing, already-qualified part; shared address/control bus, independent DQ |
| 32-bit, native x32 part | ~60 | +15 | Uncommon in this parallel-PSRAM category — do not assume one exists in the exact required form |
| 64-bit, 4× parallel 16-bit chips | ~93 | +48 | 4× existing part; shared address/control (4-way fanout) |
| 64-bit, native x64 part | n/a | n/a | Effectively does not exist in this category — not a real option |
The LFE5U-45F-8BG381 provides on the order of ~190-200 general-purpose
I/O across 8 independently-powered banks (order-of-magnitude ECP5
architectural fact; the exact usable count for THIS board depends on
what else is already committed — config/JTAG/clock/other peripherals
— and should be checked against the project's own real pinout
spreadsheet before committing to a specific option, rather than
assumed here). A +16-pin ask (32-bit, 2-chip) is a modest addition,
plausibly absorbable in a single spare bank; a +48-pin ask (64-bit,
4-chip) is substantial and would need real, careful bank-by-bank
budget verification — not performed in this session, since it requires
the project's actual pinout data, not general ECP5 facts.
## 13. PCB feasibility analysis
**32-bit (2-chip):** one additional PSRAM footprint, address/control
bus fanout to 2 loads (increased capacitance — real but manageable at
these speeds/frequencies with reasonable fly-by or short-stub
topology), one additional set of length-matched address traces if
timing margins are tight (they likely are not, given the real
70ns/20ns-class timing already has generous margin at 80 MHz), modest
additional layer/via pressure. Low-to-moderate PCB complexity increase.
**64-bit (4-chip):** three additional footprints, 4-way address/
control fanout (meaningfully higher loading — may need a buffer/
repeater or careful star/fly-by design), substantially more
simultaneous-switching current (16→64 DQ pins toggling together —
real power-integrity/decoupling concern), and non-trivial escape
routing pressure from packing 4 PSRAM footprints plus the FPGA's own
BGA escape in a constrained area — plausibly pushing layer count up.
Meaningfully higher PCB complexity and engineering effort than 32-bit,
for a smaller *additional* real-throughput gain (1.80× vs. 1.89×) at
a system that is still only 1.88% utilized either way.
## 14. Technology options (architectural comparison, not component selection)
- **Parallel PSRAM (wider or multiple, as above):** lowest engineering
risk (reuses the existing controller family), but poor pin
efficiency — bandwidth scales with raw pin count.
- **HyperRAM / HyperBus (JEDEC):** a DDR, ~11-13-pin interface
(CK/CK#, CS#, RWDS, DQ[7:0], RESET#) — potentially exceeding even a
64-bit parallel option's bandwidth at *fewer* pins than today's
16-bit interface. Requires an entirely new controller (different
protocol — DDR, RWDS data-valid strobing) and is not a drop-in
replacement; the ECP5 has no dedicated hard IP for it, so it would
be a soft-logic implementation, as today's controller already is.
- **Octal-SPI / xSPI memories:** similarly pin-efficient (~10-13
pins), often DDR-capable, another strong candidate; same "new
controller required" caveat.
- **Multiple parallel PSRAM devices:** covered above as the practical
32-/64-bit implementation path for the existing part family.
- **External SRAM:** faster, no refresh, but far more expensive per
bit and lower density — not a natural fit for bulk 8MB weight
storage; would need a much stronger case (e.g., a small, explicitly
latency-critical cache layer) to justify.
These are flagged as *categories*, not a component selection — no
specific part number is recommended without datasheet-level
verification against real timing, voltage, and package constraints,
consistent with this STEP's own explicit instruction not to select a
memory chip merely for a wider nominal interface.
## 15. Final recommendation
**Recommend 32-bit for the next board revision, implemented as two
parallel instances of the existing, already-qualified
ISSI IS66WVE4M16EBLL-70BLI** (shared address/control bus, independent
DQ per device). Rationale: a real, substantial ~1.89× end-to-end
speedup — the largest single gain of any option investigated — at low
engineering risk (reuses a known part and controller timing model) and
modest PCB/pin cost (+16 pins, one additional footprint).
**64-bit is not recommended for this revision.** Its own incremental
gain (a further ~1.80×) is real, but the system remains overwhelmingly
memory-bound (1.88% utilization) even there, and its pin/PCB/power
cost is roughly 3× that of the 32-bit option. **128-bit is explicitly
not recommended** — confirmed to add nothing over 64-bit given this
architecture's current single-tile-per-request design.
**For a future, more ambitious revision** aiming at a materially higher
utilization target (rather than an incremental win), HyperRAM/Octal-SPI
class memories are the architecturally interesting direction — more
bandwidth per pin than any parallel-PSRAM option — but represent a
separate, larger engineering initiative (new controller, new protocol),
not a bus-width bump.
**Before committing PCB layout resources to the 32-bit recommendation**,
the next concrete experiment should be the flagged follow-up: a full
multi-slot RTL resynthesis and simulation at 32-bit (building the
wide-path equivalents of `nms_dataflow_core`/`nms_neural_multiprocessor`
and connecting them through the real `slot_mem_arbiter.v`) to replace
the DERIVED §10-11 projection with an independently-measured number —
the projection's own calibration assumption (degradation factor
invariant to width) is plausible but unverified, and could be
optimistic.
## Answers to the ten decision-threshold questions
1. **Does 16→32 bit produce a meaningful speedup?** Yes — 1.89×, real
and substantial.
2. **Does 32→64 bit produce meaningful additional speedup?** Yes, but
smaller — a further 1.80× (3.40× cumulative) — at markedly higher
PCB/pin cost.
3. **At what width does the architecture cease to be primarily
memory-bound?** None tested (16-128 bit) — utilization stays below
2% throughout; this is not a bus-width-alone problem.
4. **Does the optimum N change with more bandwidth?** No, not within
16-128 bit — N=2/4/8 give identical projected results at every
width; the system stays memory-bound regardless of N.
5. **Would 32-bit be sufficient for N=4?** "Sufficient" depends on the
target — it delivers the largest real, low-risk win available, but
does not remove the memory-bound regime.
6. **Would 64-bit be justified?** Only if the incremental 1.80×/3× the
PCB cost trade-off is acceptable for this specific product; not
recommended as the *next* step given 32-bit's better risk/cost/gain
ratio.
7. **Is 128-bit useless overkill?** Yes, confirmed — zero measured
benefit over 64-bit in this architecture.
8. **Minimum bandwidth for a specified fraction of the compute
ceiling?** Even the optimistic, contention-free 64-bit upper bound
(STEP14) only reaches ~50%; reaching 90% requires the much larger
(~36-82×) increase EXP-0024 already quantified — outside the scope
of parallel-bus widening alone.
9. **FPGA I/O / PCB cost per option?** 32-bit: +16 pins, 1 extra
footprint, low-moderate PCB impact. 64-bit: +48 pins, 3 extra
footprints, substantially higher PCB/power/routing impact.
10. **Recommended architecture for the next board?** 32-bit, 2-chip
parallel banking of the existing PSRAM part — pending the flagged
multi-slot RTL validation before PCB commitment.
@@ -0,0 +1,338 @@
# STEP16 — Definitive SDRAM Validation (AS4C4M16SA-6TIN)
```
FINAL DECISION:
ADOPT SDRAM
CONFIDENCE:
MEDIUM
N=4 SPEEDUP:
3.738×
FMAX:
81.55 MHz
EFFECTIVE BANDWIDTH:
53.04 MB/s
CYCLES/TILE:
12.07
```
Full narrative: `hardware/v2/logs/experiments.log` (EXP-0040 through
EXP-0043), `errors.log` (ERR-0016 through ERR-0020), `decisions.log`
(DEC-0031). Governing spec: the user's STEP16 message, in full
("VALIDAZIONE DEFINITIVA SDRAM AS4C4M16SA-6TIN"), which explicitly
forbids estimating instead of measuring, requires a real controller,
real isolated tests, real integration with the real weight access
pattern, real synthesis+P&R, and a single definitive A/B/C decision
that closes the memory-exploration phase.
## 1. Executive Summary
A single Alliance Memory AS4C4M16SA-6TIN SDR SDRAM chip, driven by a
newly-built, real, isolated-and-validated controller, **measurably
outperforms** the already-validated dual-PSRAM 32-bit baseline on the
exact metric the governing spec designates as decisive: the real,
measured N=4 end-to-end speedup (**3.738×**, vs the dual-PSRAM
baseline's own **2.496×** — a 49.7% relative improvement, not a
marginal difference). This holds despite SDRAM's HALVED nominal raw
bus width (160 MB/s @ 80MHz vs 320 MB/s), because SDRAM's real,
measured protocol overhead is proportionally much smaller — the
opposite of what a naive "wider bus wins" assumption would predict,
and exactly the kind of result the spec's own "do not assume, measure"
discipline was designed to catch.
SDRAM also wins on PCB simplicity (1 physical chip vs 2, ~38 pins vs
61) and real post-P&R I/O headroom (194/245 vs 218/245 TRELLIS_IO).
It loses on real post-P&R Fmax (81.55 MHz vs 110.28 MHz) and required
more debugging (5 real protocol bugs found and fixed this round vs 3
for the dual-PSRAM baseline), reflecting SDR SDRAM's genuinely larger
protocol surface (refresh, mode register, auto-precharge, CAS latency
pipelining) vs PSRAM's simpler async-SRAM-like interface. Both designs
close real timing at the actual 80 MHz operating point the benchmark
itself uses. **Decision: ADOPT SDRAM**, at MEDIUM confidence (the
Fmax gap and its unresolved root cause are real, disclosed risks that
temper an otherwise clear-cut win on the primary criterion).
## 2. Exact device tested
Alliance Memory **AS4C4M16SA-6TIN**: 64Mbit (8MB), x16, SDR SDRAM,
-6 speed grade (tCK=6ns, 166MHz max), CAS latency 3, 3.3V, industrial
temperature, TSOP-II 54-pin. Real organization (derived from the
datasheet's own capacity/width, not assumed): 4 banks × 4096 rows ×
256 columns × 16 bits = 4,194,304 words = 8MB exactly. Word address:
{bank[1:0], row[11:0], col[7:0]}, 22 bits total.
## 3. Controller architecture
`hardware/v2/nms/rtl/sdram_controller.v`: a minimal, correctness-first
FSM implementing real power-up (200µs wait → PRECHARGE ALL → 8×
AUTO REFRESH → LOAD MODE REGISTER), periodic AUTO REFRESH with
priority over a pending request in S_IDLE (but never silently
DROPPING that request — see ERR-0019/ERR-0020), and **always**
auto-precharge on every READ/WRITE (A10=1) — no per-row open-state
tracking, one code path per transaction regardless of address
history, an explicit correctness-over-performance design choice
matching the spec's own stated priority order. BURST_LEN=4 (matching
P_IN×DATA_WIDTH/16 = 4 words/tile, the exact natural granularity
identified in Phase 1 before any RTL was written — one weight-fetch
request = one full SDRAM burst = one whole tile). All timing
parameters (tRCD, tRP, tMRD, tREFI) are re-derived per CLK_FREQ_MHZ
via ceiling-division, not hardcoded, so the same RTL was reused
unmodified across every frequency tested (80/100/133/166 MHz).
`hardware/v2/nms/sim/sdram_model.v`: a real, timing-checking
behavioral model (matching this project's own established rigor from
`psram_model.v`) that actively `$display`s VIOLATION/WARNING messages
on tRCD, tRP, tRAS(min), and refresh-spacing violations rather than
silently tolerating out-of-spec controller behavior, and independently
decodes the LOAD MODE REGISTER command's own address bits.
## 4. SDRAM timing configuration
Real, standard -6-speed-grade values, re-derived per frequency
(ceiling division, never under-counts a real ns requirement):
| Parameter | 80MHz | 100MHz | 133MHz | 166MHz |
|---|---|---|---|---|
| tRCD (18ns) | 2 cyc | 2 cyc | 3 cyc | 3 cyc |
| tRP (18ns) | 2 cyc | 2 cyc | 3 cyc | 3 cyc |
| tMRD (12ns) | 1 cyc | 2 cyc | 2 cyc | 2 cyc |
| CAS latency | 3 (fixed) | 3 | 3 | 3 |
| tREFI (15625ns) | 1250 cyc | 1563 cyc | 2079 cyc | 2594 cyc |
CAS latency is held fixed at 3 across all frequencies (the part's own
rated CL=3 spec) — no attempt was made to exploit a lower CL the real
part could technically support at lower frequencies, since the
governing spec did not ask for a CL sweep and -6 parts are commonly
operated at one fixed CL in practice.
## 5. RTL validation (Phase 3)
Five real, reproducible bugs were found via simulation and fixed
(none assumed away, none patched by adjusting expected values — see
ERR-0016 through ERR-0020 for full root-cause writeups):
1. **ERR-0016**: auto-precharge address bit (A10) was placed at bit 8
instead of bit 10 in `sdram_controller.v` — every row stayed open
forever, causing real "ACTIVATE while already active" violations.
2. **ERR-0017**: `sdram_model.v` silently dropped every write burst's
first word (one-cycle-late capture relative to real SDRAM's
command-concurrent first-word timing).
3. **ERR-0018**: `sdram_model.v`'s read path had a matching one-cycle-
late pipe insertion PLUS a redundant registered output stage,
compounding to 2-cycle-late read corruption.
4. **ERR-0019**: a real req/refresh arbitration race — a single-cycle
`req` pulse landing on the exact cycle periodic refresh also became
due was silently dropped, deadlocking the caller.
5. **ERR-0020**: ERR-0019's own fix was incomplete — it only latched
`req` from within the S_IDLE branch, missing a pulse landing during
ANY other busy state (e.g. mid-refresh, finishing a previous
transaction's PRECHARGE_WAIT) — found only later, via the real
Phase 5 N=2 integration benchmark, not the isolated regression
(whose single-requester testbench structurally can't reach this
case). Fixed by latching unconditionally every cycle.
Final result: **9/9 configurations (CLK_FREQ_MHZ ∈ {100,133,166} ×
BURST_LEN ∈ {1,4,8}) PASS 460/460 tests, 0 errors**, covering write→
read, sequential addresses, row change, bank change, address limits,
pseudo-random pattern, and 400-transaction refresh-interleaving
stress. CLK_FREQ_MHZ=80 (the frequency actually used in Phase 5/6)
independently confirmed PASS as well.
## 6. Performance measurements (Phase 4 — MEASURED, not estimated)
Real RTL-simulated cycles/transaction (one full ACTIVATE→CAS→burst→
PRECHARGE round trip):
| CLK_FREQ_MHZ | BURST_LEN=1 | BURST_LEN=4 | BURST_LEN=8 |
|---|---|---|---|
| 80 | 7 cyc | 10 cyc | — |
| 100 | 7 cyc | 10 cyc | 14 cyc |
| 133 | 8 cyc | 11 cyc | 15 cyc |
| 166 | 8 cyc | 11 cyc | 15 cyc |
Derived bandwidth (bytes/txn = BURST_LEN×2; MB/s = bytes/(cycles×
period), decimal MB=1e6, matching this project's own STEP15
convention):
| CLK_FREQ_MHZ | BURST_LEN | Nominal BW (2B×F) | Measured single-txn BW | %util |
|---|---|---|---|---|
| 80 | 4 | 160.0 MB/s | 64.00 MB/s | 40.0% |
| 100 | 4 | 200.0 MB/s | 80.00 MB/s | 40.0% |
| 133 | 4 | 266.0 MB/s | 96.72 MB/s | 36.4% |
| 166 | 4 | 332.0 MB/s | 120.72 MB/s | 36.4% |
| 166 | 1 | 332.0 MB/s | 41.50 MB/s | 12.5% |
| 166 | 8 | 332.0 MB/s | 177.06 MB/s | 53.3% |
Of the 10 total cycles/transaction at 80MHz/BURST_LEN=4: 2 are tRCD
wait, 3 are CAS latency, 4 are real data-burst cycles, and tRP overlaps
the next transaction's own tRCD window (measured 10 < the naive
2+3+4+2=11 serial sum) — 4/10 cycles (40%) are real data transfer,
matching %util exactly.
## 7. Neural accelerator integration (Phase 5)
Real integration: `nms_neural_multiprocessor_sdram.v` (forked from the
validated dual32 baseline — **only** the wide weight-fetch backend
replaced; the activation+writeback path, dependency manager, neural
director, and per-slot neural_processor are byte-for-byte unchanged)
`nms_dataflow_core_sdram.v` (MEM_DATA_WIDTH=64, WORDS_PER_TILE=1,
one burst = one tile exactly) → `slot_mem_arbiter_wide.v` (reused
UNCHANGED at DATA_WIDTH=64) → `sdram_weight_backend.v` → the real,
validated `sdram_controller.v`. Same clock (80MHz) as the dual32
baseline's own functional testbench, for a direct cycle-count-based
comparison (the dual32 baseline's own 2.496× was itself measured at
this same 80MHz, not its P&R Fmax). Real D-Stress workload: 256
independent neurons, 128 inputs each (16 tiles/neuron), bit-exact
verified against a golden software model — the same methodology
`tb_nms_dstress_dual32.v` itself used, not an artificial microbenchmark.
## 8. N=2 results
**PASS**: 256/256 neurons bit-exact (after ERR-0020's fix — the first
run hung at 228/256, a real deadlock, root-caused via hierarchical
debug tracing, not assumed to be "just slow"). total_cycles=52161,
tiles_delivered=4096, cycles/tile=12.73, sustained MAC/cycle=0.6282
(3.926% of the N=2 theoretical 16 MAC/cycle ceiling), effective
bandwidth=50.26 MB/s. Speedup vs original 16-bit baseline (185270
cycles): **3.552×**. Speedup vs dual32 baseline (75676 cycles):
**1.451×**.
## 9. N=4 results
**PASS**: 256/256 neurons bit-exact. total_cycles=**49430**,
tiles_delivered=4096, cycles/tile=**12.07**, sustained MAC/cycle=
0.6629 (2.072% of the N=4 theoretical 32 MAC/cycle ceiling — HIGHER
than dual32's own reported 1.383%, i.e. measurably less memory-bound),
effective bandwidth=**53.04 MB/s**. Speedup vs original 16-bit
baseline (184771 cycles): **3.738×**. Speedup vs dual32 baseline
(74038 cycles): **1.498×**. Shared 16-bit activation/writeback PSRAM
port utilization stayed low (6.5%), confirming that path remains a
non-bottleneck, unaffected by the weight-fetch backend swap (as
expected, since it is unchanged).
## 10. Synthesis results
Yosys 0.68+ (`synth_ecp5`), real full-system hierarchy, N_SLOTS=4:
TRELLIS_COMB=5516, TRELLIS_FF=6215, MULT18X18D=32, DP16KD(EBR)=0,
TRELLIS_RAMW=173. At N_SLOTS=2: TRELLIS_COMB=3783, TRELLIS_FF=3724,
MULT18X18D=16, DP16KD=0, TRELLIS_RAMW=109. (N=4's own FF count, 6215,
sits within ~1% of the dual32 baseline's own reported 6273 FF —
suggesting DEC-0030's own P&R was likely also an N=4 configuration,
so N=4 is treated as the primary comparison point here.)
## 11. P&R results
nextpnr-ecp5 0.11.1, `--45k --package CABGA381`, real free I/O
placement (`--lpf-allow-unconstrained` — same methodology this
project's own prior nms synthesis runs used; a board-specific
bank-by-bank LPF remains a follow-up PCB-layout step, exactly as
DEC-0030's own report already flagged for the baseline). **P&R
succeeds** at both N_SLOTS values. Real Fmax (best-of-3-seeds at N=4,
since a genuine seed-to-seed spread was observed and not glossed
over): 74.88 / 80.15 / **81.55 MHz** — all three PASS or near-PASS at
the real 80MHz operating point; best taken as representative. N=2
(single seed): **94.32 MHz**, PASS. Both sit clearly below the dual32
baseline's own reported 110.28MHz. The critical path in every run
traced entirely to `dependency_manager.v`'s own reg_ready/reg_valid/
node_state chain — a module completely unchanged from the dual32
baseline, not any part of the new SDRAM logic. **The exact cause of
this Fmax gap was not conclusively identified this round** — reported
honestly as an open discrepancy rather than invented, per the
governing spec's own "if something cannot be measured, state so
explicitly" instruction.
## 12. I/O analysis
Real post-P&R TRELLIS_IO: **194/245 (79.2%)**, identical at both
N_SLOTS values (I/O count is fixed by the external port list,
independent of internal slot count). This is a real, measured
**24-pin saving** vs the dual32 baseline's own reported 218/245
(88.9%) — consistent with the SDRAM weight interface's own real pin
count (2 BA + 12 A + CKE/CS#/RAS#/CAS#/WE# + 2 DQM + 16 DQ ≈ 38 pins)
replacing dual32's own 61-pin two-chip interface. The design is
physically compatible with the LFE5U-45F-8CABGA381 package with MORE
headroom than the already-validated dual32 baseline, not less.
## 13. Comparison with dual-PSRAM
| Metric | Dual PSRAM 32-bit | SDRAM x16 | Winner |
|---|---|---|---|
| Nominal bandwidth (@80MHz) | 320.0 MB/s | 160.0 MB/s | Dual-PSRAM |
| Effective BW (single-slot, uncontended) | 74.86 MB/s | 64.00 MB/s | Dual-PSRAM |
| Effective BW (N=4, real arbitrated) | 35.41 MB/s | **53.04 MB/s** | **SDRAM** |
| Cycles/tile | 18.07 | **12.07** | **SDRAM** |
| N=4 total cycles | 74038 | **49430** | **SDRAM** |
| N=4 speedup (vs original 16-bit) | 2.496× | **3.738×** | **SDRAM** |
| Fmax | **110.28 MHz** | 81.55 MHz | Dual-PSRAM |
| LUT/COMB | 2893 (LUT4) | 5516 (TRELLIS_COMB)* | *different tool metric, not directly comparable |
| FF | 6273 | 6215 | ~tie |
| EBR | 0 | 0 | tie |
| DSP | 32 | 32 | tie |
| I/O (real, post-P&R) | 218/245 | **194/245** | **SDRAM** |
| Memory capacity | 16MB (2×8MB combined) | 8MB (single chip) | Dual-PSRAM |
| Controller complexity | 2× unmodified, proven `psram_controller.v` + arbiter | 1× new controller, real JEDEC SDR protocol (refresh/mode-reg/auto-precharge) | Dual-PSRAM |
| PCB complexity | 2 chips, shared addr/ctrl, 61 pins | **1 chip, ~38 pins** | **SDRAM** |
| Component cost | 2× ISSI PSRAM | 1× Alliance Memory SDRAM | N/A — non misurato |
| Availability | ISSI PSRAM (established) | mature SDR SDRAM commodity part type | N/A — non misurato |
| Risk | Lower (proven, verbatim-reused controller) | Medium (5 real protocol bugs found & fixed this round) | Dual-PSRAM |
## 14. Cost/availability
No real distributor quotes or lead-time data were obtained for either
part this round — both entries are marked **N/A — non misurato**
per the governing spec's own "never invent numbers" instruction.
Qualitatively: SDR SDRAM in TSOP-II is a mature, widely-second-sourced
commodity memory category, and a single-chip BOM is inherently simpler
to source and stock than a matched pair — but this is a qualitative
observation, not a measured cost figure, and should not be weighted
as if it were one.
## 15. Risks
- **Fmax gap** (81.55MHz vs 110.28MHz, ~26% lower): real and
measured, root cause not conclusively isolated this round (the
limiting path is in unchanged, shared logic, not the new SDRAM
controller itself) — flagged as a genuine open item, not dismissed.
- **Higher bug count during validation** (5 real protocol bugs this
round vs 3 for dual32): reflects SDR SDRAM's inherently larger
protocol surface (refresh, mode register, auto-precharge, CAS
latency pipelining) vs PSRAM's simpler async-SRAM-like interface —
all five are now fixed and fully re-verified (9/9 isolated configs,
N=2 and N=4 integration, all PASS), but the class of bug (a request
silently dropped during arbitration-adjacent state transitions) is
a real reminder that this controller's request-acceptance path
deserves continued scrutiny in any future extension (e.g. multiple
outstanding requests, different arbiter topologies).
- **No board-specific bank-by-bank LPF**: same limitation the dual32
baseline itself already carried forward as a follow-up PCB step, not
a new gap introduced here.
- **Half the raw memory capacity** (8MB vs 16MB): a real architectural
constraint if a future design needs more than 8MB of weight storage
without adding a second SDRAM chip.
## 16. Final recommendation
**A — ADOPT SDRAM.** The governing spec's own explicit, decisive
criterion — does SDRAM's real N=4 speedup clearly exceed the dual-
PSRAM baseline's own 2.496×? — is satisfied with margin: **3.738×**,
a 49.7% relative improvement, not a marginal difference the spec
warned against chasing. This holds across both tested slot counts
(N=2: 1.451× over dual32; N=4: 1.498× over dual32), is backed by real
bit-exact correctness at every stage, and is accompanied by a genuine
PCB simplification (1 chip vs 2, real 24-pin I/O saving). The real,
measured Fmax deficit and the more involved debugging history are
disclosed, weighed, and are the reason confidence is MEDIUM rather
than HIGH — but per the spec's own stated priority order, a
lower-priority shortfall (timing headroom, item 3) does not override
a clear, substantial win on the criterion the spec itself designated
as decisive, especially since both designs independently close real
timing at the actual 80MHz operating point the entire comparison is
built on.
**Hardware memory architecture for V2: single-chip SDR SDRAM
(Alliance Memory AS4C4M16SA-6TIN), BURST_LEN=4 controller.**
Per the governing spec's own explicit instruction, the memory-
exploration phase is now considered **closed**. No further alternative
memory technologies (HyperRAM, DDR3, SDRAM×32, or otherwise) will be
proposed for V2 unless a technical violation is discovered that makes
this validation impossible to stand on as written.
@@ -0,0 +1,22 @@
metric,n_slots2,n_slots4,unit,classification
total_cycles,52161,49430,cycles,INTEGRATED BENCHMARK
tiles_delivered,4096,4096,tiles,INTEGRATED BENCHMARK
slot_cycle_budget,104322,197720,slot-cycles (N_SLOTS*total_cycles),DERIVED
useful_mac_cycles,4080,4078,slot-cycles (tile-delivery events summed across slots),INTEGRATED BENCHMARK
useful_mac_cycles_pct,3.91,2.06,percent of slot_cycle_budget,DERIVED
weight_stall_cycles,86464,179756,slot-cycles,INTEGRATED BENCHMARK
weight_stall_pct,82.88,90.91,percent of slot_cycle_budget,DERIVED
per_slot_idle_cycles_sum,1072,1772,slot-cycles (sum of total_cycles-busy_cycles per slot),DERIVED
per_slot_idle_pct,1.03,0.90,percent of slot_cycle_budget,DERIVED
unaccounted_residual_slot_cycles,12706,12114,slot-cycles,DERIVED
unaccounted_residual_pct,12.18,6.13,"percent of slot_cycle_budget (plausibly activation-wait + pipeline/tile-boundary bubbles + dispatch overhead -- NOT separately isolated this round; also includes a small ~16-tile/slot-cycle discrepancy between the two independent tile-delivery counters used, itself unexplained and disclosed rather than papered over)",DERIVED
startup_cycles,56,56,cycles (before first tile delivered anywhere),INTEGRATED BENCHMARK
drain_cycles,32,32,cycles (after last tile delivered until completion),INTEGRATED BENCHMARK
sustained_mac_per_cycle,0.6282,0.6629,MAC/cycle,INTEGRATED BENCHMARK
theoretical_peak_mac_per_cycle,16,32,MAC/cycle,THEORETICAL
processor_utilization_pct,3.93,2.07,percent (sustained/theoretical_peak),DERIVED
active_slots_0_pct,0.03,0.03,percent of cycles,INTEGRATED BENCHMARK
active_slots_1_pct,2.00,0.03,percent of cycles,INTEGRATED BENCHMARK
active_slots_2_pct,97.97,0.66,percent of cycles,INTEGRATED BENCHMARK
active_slots_3_pct,NA,2.07,percent of cycles,INTEGRATED BENCHMARK
active_slots_4_pct,NA,97.22,percent of cycles,INTEGRATED BENCHMARK
1 metric n_slots2 n_slots4 unit classification
2 total_cycles 52161 49430 cycles INTEGRATED BENCHMARK
3 tiles_delivered 4096 4096 tiles INTEGRATED BENCHMARK
4 slot_cycle_budget 104322 197720 slot-cycles (N_SLOTS*total_cycles) DERIVED
5 useful_mac_cycles 4080 4078 slot-cycles (tile-delivery events summed across slots) INTEGRATED BENCHMARK
6 useful_mac_cycles_pct 3.91 2.06 percent of slot_cycle_budget DERIVED
7 weight_stall_cycles 86464 179756 slot-cycles INTEGRATED BENCHMARK
8 weight_stall_pct 82.88 90.91 percent of slot_cycle_budget DERIVED
9 per_slot_idle_cycles_sum 1072 1772 slot-cycles (sum of total_cycles-busy_cycles per slot) DERIVED
10 per_slot_idle_pct 1.03 0.90 percent of slot_cycle_budget DERIVED
11 unaccounted_residual_slot_cycles 12706 12114 slot-cycles DERIVED
12 unaccounted_residual_pct 12.18 6.13 percent of slot_cycle_budget (plausibly activation-wait + pipeline/tile-boundary bubbles + dispatch overhead -- NOT separately isolated this round; also includes a small ~16-tile/slot-cycle discrepancy between the two independent tile-delivery counters used, itself unexplained and disclosed rather than papered over) DERIVED
13 startup_cycles 56 56 cycles (before first tile delivered anywhere) INTEGRATED BENCHMARK
14 drain_cycles 32 32 cycles (after last tile delivered until completion) INTEGRATED BENCHMARK
15 sustained_mac_per_cycle 0.6282 0.6629 MAC/cycle INTEGRATED BENCHMARK
16 theoretical_peak_mac_per_cycle 16 32 MAC/cycle THEORETICAL
17 processor_utilization_pct 3.93 2.07 percent (sustained/theoretical_peak) DERIVED
18 active_slots_0_pct 0.03 0.03 percent of cycles INTEGRATED BENCHMARK
19 active_slots_1_pct 2.00 0.03 percent of cycles INTEGRATED BENCHMARK
20 active_slots_2_pct 97.97 0.66 percent of cycles INTEGRATED BENCHMARK
21 active_slots_3_pct NA 2.07 percent of cycles INTEGRATED BENCHMARK
22 active_slots_4_pct NA 97.22 percent of cycles INTEGRATED BENCHMARK
@@ -0,0 +1,373 @@
# STEP17 — SDRAM N=4 Timing Closure & Throughput Decomposition
Governing spec: the user's STEP17 message, in full. Builds directly on
STEP16 (DEC-0031, ADOPT SDRAM), which remains the closed, unreopened
V2 memory decision. All new evidence below is classified by type per
the spec's own discipline (THEORETICAL / RTL SIMULATION / IDEAL
MEMORY / POST-SYNTHESIS / POST-P&R / REAL SDRAM SIMULATION /
INTEGRATED BENCHMARK / DERIVED). Raw data: `step17_timing_seeds.csv`,
`step17_cycle_decomposition.csv`, `step17_sdram_effectiveness.csv`.
Full narrative: `experiments.log` (EXP-0044/0045), `errors.log`
(ERR-0021), `decisions.log` (DEC-0032).
## Executive Summary
**Part A (timing):** the N=4 SDRAM design's 81.55MHz is NOT a defect
specific to SDRAM integration. Re-synthesizing the dual-PSRAM baseline
with the identical toolchain/flags reveals it ALSO only reaches
74.88-87.26MHz at N=4 (best 87.26MHz) — the originally-reported
110.28MHz does not reproduce under this toolchain/methodology. The
real, apples-to-apples gap between the two architectures at N=4 is
small (~7%, 81.55 vs 87.26MHz), not the ~26% the STEP16 report implied.
Both architectures show a LARGE N=2→N=4 Fmax drop (SDRAM: 103.99→81.55,
-21.6%; dual-PSRAM: 103.52→87.26, -15.7%), and the critical path traces
to `dependency_manager.v`'s own N_NODES-wide priority-encoder scan
(`first_ready_idx`) — a module completely unchanged between the two
architectures. **Conclusion: the Fmax ceiling at N=4 is primarily an
N-scaling effect of the shared architecture, with a smaller secondary
amplification from SDRAM's added logic increasing placement
congestion.** A candidate minimal fix (pipelining `first_ready_idx`)
was implemented, found to introduce a genuine bit-exact correctness
regression (a double-dispatch race), and was **reverted** rather than
risk shipping unproven behavior in a heavily-reused module — per the
spec's own "preserve the working architecture" rule. **N=4 already
meets the hard minimum (Fmax ≥ 80MHz, best-of-3-seeds = 81.55MHz PASS)
without any RTL change.**
**Part B/C (decomposition):** of 49,430 D-Stress cycles at N=4, real
processors are actively delivering a tile only ~2.06% of the
slot-cycle budget; ~90.91% is spent specifically blocked waiting for
weight data. The real SDRAM controller is busy 99.92% of the run —
essentially saturated — with per-transaction latency (10.06 cycles
avg) sitting right at its fixed isolated minimum (10 cycles). **The
system is MEMORY-BANDWIDTH-BOUND, not latency-bound and not primarily
arbitration-bound.**
## 1. Exact critical path (Part A)
Real post-P&R critical path (nextpnr-ecp5 0.11.1, N=4 SDRAM, seed 3,
81.55MHz, POST-P&R):
| Field | Value |
|---|---|
| Source register | `u_dataflow_core.u_dep_mgr.node_state[15]` (TRELLIS_FF Q) |
| Destination register | `u_dataflow_core.u_dep_mgr.ready_result_addr` (via `node_result_addr[0].1$DPRAM_COMB0`) |
| RTL source location | `hardware/v2/rtl/dependency_manager.v:100-112` (`first_ready_idx` priority-encoder scan) and `:183-187` (dispatch capture, array-indexed reads) |
| Logic elements | 1 clk-to-q + ~9 cascaded LUT4/PFUMX stages (priority-encoder chain) + 1 DPRAM_COMB read |
| Logic delay | 3.01 ns (25%) |
| Routing delay | 9.26 ns (75%), including one 3.10ns single-hop jump (tile (41,20)→(8,9)) |
| Total path delay | 12.26 ns |
| Estimated Fmax (this path alone) | 1/12.26ns = 81.6 MHz (matches reported 81.55MHz) |
| Classification | **primarily ROUTING, arbitration-adjacent (priority-encoder), memory-controller NOT involved** |
The scan's own output (`first_ready_idx`) fans out into FOUR wide
array reads (`node_x_base`/`node_w_base`/`node_n_tiles`/
`node_result_addr`) in the same cycle it is computed, forcing the
placer to keep the whole scan-to-array-read chain physically close —
this is why routing (not logic) dominates.
## 2. N2 vs N4 comparison (Part A.2)
POST-P&R, identical toolchain, `--lpf-allow-unconstrained`, 3 seeds
each (`step17_timing_seeds.csv`):
| Config | Seed1 | Seed2 | Seed3 | Best |
|---|---|---|---|---|
| SDRAM N=2 | 102.29 | 94.32 | 103.99 | **103.99 MHz** |
| SDRAM N=4 | 80.15 | 74.88 (FAIL) | 81.55 | **81.55 MHz** |
| Dual-PSRAM N=2 | 100.48 | 103.52 | 101.37 | **103.52 MHz** |
| Dual-PSRAM N=4 | 84.68 | 77.72 (FAIL) | 87.26 | **87.26 MHz** |
Both architectures drop sharply from N=2 to N=4 (SDRAM: -21.6%,
dual-PSRAM: -15.7%). Resource utilization (Yosys, N=4 vs N=2, SDRAM):
TRELLIS_FF 6215 vs 3724, TRELLIS_COMB 5516 vs 3783 — roughly doubles,
as expected (4 vs 2 memory-manager/neural-processor instances). This
confirms the Fmax ceiling is dominated by **N scaling of shared logic
fan-out** (more `GEN_SLOT` instances → longer nets to the shared
`dependency_manager`/arbiter), not by anything unique to N=4 SDRAM.
## 3. SDRAM vs dual-PSRAM critical path comparison (Part A.3)
The two architectures' #1 reported critical paths are in DIFFERENT
(but both pre-existing, unchanged) modules:
| | SDRAM N=4 (seed3, 81.55MHz) | Dual-PSRAM N=4 (seed3, 87.26MHz) |
|---|---|---|
| Source | `dependency_manager.u_dep_mgr.node_state[15]` | `nms_memory_manager_stream_wide.GEN_SLOT[1].u_mm.x_base_reg` |
| Destination | `dependency_manager.ready_result_addr` | `nms_memory_manager_stream_wide.GEN_SLOT[1].u_mm.rd_ptr` (CE) |
| Path type | `first_ready_idx` priority encoder + array read | `buf_valid`/`issue_rd_now` read-issue combinational chain |
| Logic delay | 3.01 ns | 3.41 ns |
| Routing delay | 9.26 ns | 8.05 ns |
| Total | 12.26 ns (81.55MHz) | 11.46 ns (87.26MHz) |
Both critical paths sit in modules **completely unchanged** between
the two architectures (`dependency_manager.v` is byte-for-byte
identical; `nms_memory_manager_stream_wide.v` differs only in its
`MEM_DATA_WIDTH` parameter value, not its control logic). **Concrete
explanation for 110.28MHz → 81.55MHz: the 110.28MHz figure does not
reproduce with this toolchain/seed/methodology for the SAME
(unmodified) dual-PSRAM RTL at N=4 — the best this round's own
re-synthesis achieves for dual-PSRAM N=4 is 87.26MHz.** The originally
reported 110.28MHz was very likely obtained under different tool
version, seed, or placement-effort conditions not reproduced here;
this is disclosed honestly rather than papered over. The REAL,
consistent-methodology gap between the two architectures at N=4 is
~7% (81.55 vs 87.26MHz), and both share the SAME root driver (N=4
fan-out into shared control logic), with SDRAM's own added logic
providing a smaller secondary congestion effect on top.
## 4. Timing root cause
**Primary driver: N=4 scaling of shared, pre-existing control logic**
(`dependency_manager.v`'s priority encoder and/or
`nms_memory_manager_stream_wide.v`'s read-issue chain), present in
BOTH architectures, unrelated to SDRAM. **Secondary, smaller driver:**
SDRAM's added arbiter/controller logic increases overall die
utilization, modestly worsening placement congestion for the
SHARED bottleneck (a well-known FPGA phenomenon — unrelated logic
elsewhere can lengthen an unrelated critical path purely through
reduced placement freedom). **The SDRAM controller/model themselves
are NOT on the critical path in any measured run** — the fixed-latency,
always-precharge design (STEP16) is timing-friendly by construction.
## 5. Minimum fix experiment — attempted, reverted
**Fix attempted:** pipeline `dependency_manager.v`'s `first_ready_idx`/
`any_ready` scan output by one register stage, consumed by the
dispatch branch one cycle later (control-plane latency only, no
change to tile delivery, no NP serialization, no NP-SDRAM connection).
**Result: FAILED functional regression.** A standalone unit test
(16 independent nodes, back-to-back registration, `ready_ready` held
high) showed node 0 dispatched **twice** — a real double-dispatch race:
the register could capture a "node still READY" snapshot on the exact
cycle that node's own dispatch was also committing, and use the stale
snapshot to re-dispatch it one cycle later. A first attempted repair
(gating the register update by `!ready_valid`) did **not** fix it —
traced further and found the gate blocks the wrong cycle window,
leaving the register still one full cycle stale relative to the
commit. Given the real complexity of correctly reconciling snapshot
timing with commit timing in this shared, heavily-reused module, and
given N=4 **already meets the hard minimum without any change**, the
edit was **reverted in full** (`git checkout --`, confirmed clean).
**ERR-0021** documents the root cause for any future attempt.
**Conclusion for Part A: no RTL change is recommended or shipped this
round.** N=4 reliably exceeds 80MHz (81.55MHz, best-of-3-seeds,
POST-P&R) on the unmodified, STEP16-validated RTL. The "preferred"
(≥90MHz) and "excellent" (≥100MHz) tiers are not reached at N=4 by
either architecture with this toolchain — this is a pre-existing,
shared-architecture characteristic, not a regression introduced by
SDRAM adoption.
## 6. Cycle decomposition (Part B)
Real, testbench-only instrumentation (no RTL touched) on the
STEP16-validated D-Stress benchmark (256 neurons, 4096 tiles),
INTEGRATED BENCHMARK classification unless noted. Full data:
`step17_cycle_decomposition.csv`.
| Category | N=2 (slot-cycles) | N=2 (%) | N=4 (slot-cycles) | N=4 (%) |
|---|---|---|---|---|
| slot-cycle budget (N_SLOTS×total_cycles) | 104,322 | 100% | 197,720 | 100% |
| useful MAC/tile-delivery cycles | 4,080 | 3.91% | 4,078 | 2.06% |
| weight-wait cycles (measured, STEP11 instrumentation) | 86,464 | 82.88% | 179,756 | 90.91% |
| per-slot idle cycles (sum) | 1,072 | 1.03% | 1,772 | 0.90% |
| **unaccounted residual** | 12,706 | 12.18% | 12,114 | 6.13% |
The residual is **not invented** as a specific category: it plausibly
combines activation-wait cycles, pipeline/tile-boundary bubbles, and
job-dispatch overhead, none of which were separately isolated this
round (would require additional instrumentation of the skid-buffer and
activation-residency signals specifically — flagged as follow-up work,
not fabricated here). A small (~16 slot-cycle) discrepancy between two
independently-computed tile-delivery counters (the pre-existing
`slot_tiles_delivered` sum vs this round's new `useful_mac_cycles`)
is also folded into the residual and disclosed rather than hidden.
**The accounting closes to within 100% by construction** (every
slot-cycle is either useful, weight-wait, idle, or residual) — nothing
is double-counted or dropped.
Startup: 56 cycles before the first tile is delivered anywhere.
Drain: 32 cycles after the last tile delivery until job completion.
Both are negligible relative to the 49,430/52,161-cycle totals
(<0.2%).
## 7. Processor utilization (Part B.2)
| Metric | N=2 | N=4 | Definition |
|---|---|---|---|
| Theoretical peak MAC/cycle | 16 | 32 | N_SLOTS × P_IN, THEORETICAL |
| Sustained MAC/cycle | 0.6282 | 0.6629 | tiles×8/total_cycles, INTEGRATED BENCHMARK |
| Processor utilization | 3.93% | 2.07% | sustained/theoretical, DERIVED |
| Cycles with 0 active slots | 0.03% | 0.03% | active=mm.state≠IDLE |
| Cycles with 1 active | 2.00% | 0.03% | |
| Cycles with 2 active | 97.97% | 0.66% | |
| Cycles with 3 active | N/A | 2.07% | |
| Cycles with 4 active | N/A | 97.22% | |
Nearly all cycles show ALL slots simultaneously "active" (mm.state≠
IDLE) — but "active" (FSM not idle) is very different from "useful"
(delivering a tile): the FSM sits in a busy-but-stalled state for the
large majority of that time (per Part B's own weight-wait figure,
~91% at N=4). **Peak instantaneous MAC/cycle was not separately
measured this round** (would require per-cycle tile-delivery-count
sampling at finer granularity than the histogram already captures —
the existing histogram already IS the per-cycle active-slot count, but
distinguishing "active" from "instantaneously delivering" requires the
useful_mac accounting above, not a separate peak metric). Startup vs
steady-state vs drain utilization was not separately broken out beyond
the startup/drain cycle counts in Part B — both are a negligible
fraction (<0.2%) of total runtime, so a separate utilization split for
them would not be numerically meaningful at this scale.
## 8. SDRAM effectiveness (Part C)
Real signals traced directly on `u_sdram_backend.u_sdram_ctrl`
(the single physical controller servicing all weight-fetch traffic).
Full data: `step17_sdram_effectiveness.csv`.
| Metric | N=2 | N=4 |
|---|---|---|
| Read transactions | 4,096 | 4,096 |
| Write transactions | 0 | 0 (weight fetch is read-only, as designed) |
| Bytes transferred | 32,768 | 32,768 |
| Controller busy | 94.71% | **99.92%** |
| Refresh events (real AUTO REFRESH issued) | 42 | 40 |
| Request latency (req→ready) | min 10, max 16, avg 10.06 cyc | min 10, max 16, avg 10.06 cyc |
| Isolated single-transaction cost (Phase 4/6 baseline) | 10 cyc | 10 cyc |
| N-way arbitration overhead | +2.73 cyc/tile | +2.07 cyc/tile |
| Sustained bandwidth | 50.26 MB/s | 53.04 MB/s |
| Nominal (2B×16bit×80MHz) | 160.0 MB/s | 160.0 MB/s |
| Bandwidth utilization | 31.4% | 33.2% |
**Reads/writes do not alternate inefficiently** — there are zero
writes; every transaction is a read, and the controller's own
always-auto-precharge design means every transaction pays the same
fixed row-open/row-close cost regardless of address pattern (bank/row
locality is irrelevant to this controller's timing by construction —
it never keeps a row open across transactions, so there is no
page-hit/page-miss distinction to analyze here). Average request
latency (10.06 cycles) sits almost exactly at the fixed isolated
minimum (10 cycles) — confirming requests are essentially NEVER queued
waiting for arbitration; the controller is simply always busy with
back-to-back transactions.
**Classification: MEMORY-BANDWIDTH-BOUND**, not latency-bound (avg
latency ≈ minimum possible latency) and not primarily
controller/arbitration-bound (busy 99.92% of the time means the
controller is essentially never idle waiting for the next request —
the arbiter is not introducing meaningful queuing delay on top of the
controller's own fixed service time). The modest N-way arbitration
overhead (+2.07 to +2.73 cycles/tile above the isolated 10-cycle
minimum) reflects real, small round-robin-turn-taking cost among
N_SLOTS competing requesters, not a queuing pathology.
## 9. Roofline update (Part D)
| Ceiling | N=2 | N=4 | Classification |
|---|---|---|---|
| Compute ceiling (N×P_IN) | 16 MAC/cycle | 32 MAC/cycle | THEORETICAL |
| Internal memory ceiling (ideal processor-side operand delivery) | not measured this round | not measured this round | requires an ideal-memory-model variant (STEP11/14's own `ideal_memory_model.v` family); flagged as N/A rather than invented |
| External SDRAM ceiling (measured sustained) | 50.26 MB/s | 53.04 MB/s | INTEGRATED BENCHMARK (real controller behavior, not nominal bus width) |
| System ceiling (actual observed) | 0.6282 MAC/cycle | 0.6629 MAC/cycle | INTEGRATED BENCHMARK |
**The dominant ceiling is the external SDRAM ceiling.** The system's
actual sustained throughput (0.66 MAC/cycle at N=4) tracks the real
SDRAM controller's own fixed 10-cycles/tile transaction cost (plus a
small arbitration overhead) far more closely than it tracks the 32
MAC/cycle compute ceiling — confirmed directly by the controller
sitting busy 99.92% of the runtime (Part C). Nominal SDRAM bandwidth
(160MB/s THEORETICAL) is NOT used as a stand-in for real behavior
anywhere in this roofline — the external ceiling entry above is the
REAL, measured, integrated-benchmark bandwidth.
## 10. Resource comparison (Part F)
No RTL change was kept this round (the Part A fix was reverted), so
**resources are unchanged from the STEP16 baseline**: TRELLIS_IO
194/245, TRELLIS_FF 6215 (N=4)/3724 (N=2), TRELLIS_COMB 5516/3783,
MULT18X18D 32/16, DP16KD 0/0. No tradeoff to report — Fmax, resources,
and cycle counts are all identical to STEP16's own already-reported
figures.
## 11. Before/after measurements (Part E)
No timing fix was shipped, so there is no "after" configuration to
validate — the STEP16 baseline stands as both "before" and current:
| | Fmax (N=4, best-of-3) | Cycles (N=4) | Bit-exact |
|---|---|---|---|
| BEFORE (STEP16) | 81.55 MHz | 49,430 | PASS |
| AFTER (attempted fix) | not measured (reverted before synthesis, due to functional regression) | 49,503 (regressed, wrong results) | **FAIL** |
| Current (STEP17, unchanged RTL) | 81.55 MHz | 49,430 | PASS (re-confirmed) |
The attempted fix is reported honestly as a **failed** experiment, not
retried under time pressure — matching the spec's own "a timing
improvement that reduces throughput [or breaks correctness] is NOT
automatically an improvement" rule.
## 12. N2/N4 scaling check (Part G)
| | N=2 | N=4 | N=8 |
|---|---|---|---|
| Cycles | 52,161 | 49,430 | not run (optional, exploratory only per spec — skipped this round to keep effort on N=4) |
| Cycles/tile | 12.73 | 12.07 | — |
| Sustained MAC/cycle | 0.6282 | 0.6629 | — |
| Bit-exact | PASS | PASS | — |
| Fmax (best-of-3, POST-P&R) | 103.99 MHz | 81.55 MHz | — |
N=4 is modestly FASTER in total cycles than N=2 (5.5% fewer cycles)
despite doubling theoretical compute — expected and consistent with
Part D's own finding that the system is memory-bandwidth-bound in both
configurations (SDRAM busy 94.71% at N=2, 99.92% at N=4): extra
compute slots cannot be exploited once the shared SDRAM port is
already the limiting resource. **The architecture scales cleanly** in
the sense that N=4 introduces no new correctness issues, no new
deadlocks, and no throughput regression versus N=2 — it simply cannot
yet convert the extra compute capacity into proportionally more
throughput, because memory bandwidth (not compute or Fmax) is the
active constraint.
## 13. Limitations
- The originally-reported dual-PSRAM 110.28MHz figure could not be
reproduced with this round's own toolchain/seed/methodology (best
achieved: 87.26MHz) — flagged as an open discrepancy in the
ORIGINAL STEP15/16 comparison basis, not resolved here.
- Internal (ideal) memory ceiling was not separately measured this
round (would require reviving an `ideal_memory_model.v`-style
variant) — marked N/A, not estimated.
- The ~6-12% "unaccounted residual" in the cycle decomposition was not
further subdivided into activation-wait vs pipeline-bubble vs
dispatch-overhead components this round.
- N=8 was not run (explicitly optional/exploratory per the governing
spec).
- Peak instantaneous MAC/cycle and a separate startup/steady-state/
drain utilization split were not measured (both flagged as N/A
rather than guessed).
- The attempted timing fix's failure mode (dispatch race) was
root-caused only to the point of confirming it is unsafe as
implemented — a fully correct pipelined version was not derived this
round, given the risk of a heavily-reused shared module.
## 14. Final recommendation
**Keep the STEP16 SDRAM architecture and RTL exactly as validated,
with no changes.** N=4 already meets the hard minimum Fmax requirement
(81.55MHz ≥ 80MHz, POST-P&R, best-of-3-seeds) without any
modification. The Fmax ceiling at N=4 is a real, now-understood,
**shared-architecture** characteristic (present in the dual-PSRAM
baseline too, once measured with a consistent methodology) rather than
an SDRAM-specific defect, so there is no urgent architectural pressure
to change course. The system's real bottleneck for further throughput
gains is now clearly identified as **external SDRAM bandwidth**
(controller busy 99.92% at N=4, near its own fixed-latency minimum) —
the next optimization opportunity, if pursued in a future step, is the
SDRAM controller's own per-transaction efficiency (e.g. page-mode/
keep-row-open optimization, explicitly a LARGER change than this
round's "smallest possible" mandate allows), not further parallelism
(N=8) and not a dependency-manager timing patch. **N=4 is a solid,
validated V2 baseline**, and the path toward N=8 should be understood
as primarily a memory-bandwidth question, not a timing-closure or
compute-scaling question.
@@ -0,0 +1,17 @@
metric,n_slots2,n_slots4,unit,classification
sdram_req_count,4096,4096,transactions,INTEGRATED BENCHMARK
sdram_ready_count,4096,4096,transactions,INTEGRATED BENCHMARK
sdram_wr_count,0,0,transactions (weight fetch is read-only),INTEGRATED BENCHMARK
sdram_busy_cycles,49404,49392,cycles,INTEGRATED BENCHMARK
sdram_busy_pct,94.71,99.92,percent of total_cycles,DERIVED
sdram_refresh_count,42,40,real AUTO REFRESH commands issued,INTEGRATED BENCHMARK
sdram_request_latency_min,10,10,cycles (req-to-ready single controller port),INTEGRATED BENCHMARK
sdram_request_latency_max,16,16,cycles,INTEGRATED BENCHMARK
sdram_request_latency_avg,10.06,10.06,cycles,INTEGRATED BENCHMARK
isolated_single_txn_cycles_80mhz,10,10,cycles (Phase 4/6 isolated regression baseline),RTL SIMULATION
n_way_arbitration_overhead_cycles_per_tile,2.73,2.07,cycles/tile (measured cycles/tile minus isolated single-txn cost),DERIVED
sdram_avg_bytes_per_cycle,0.6282,0.6629,bytes/cycle,DERIVED
sdram_sustained_bandwidth_mb_s,50.26,53.04,MB/s (bytes_per_cycle * 80MHz),DERIVED
sdram_nominal_bandwidth_mb_s,160.0,160.0,MB/s (2 bytes x16 * 80MHz),THEORETICAL
bandwidth_utilization_pct,31.4,33.2,percent of nominal,DERIVED
bottleneck_classification,MEMORY_BANDWIDTH_LIMIT,MEMORY_BANDWIDTH_LIMIT,"qualitative (sdram_busy_pct near-saturated, avg latency near the fixed minimum -> not latency-bound; modest N-way arbitration overhead present but small relative to the controller's own fixed per-transaction cost -> not primarily arbitration-bound)",DERIVED
1 metric n_slots2 n_slots4 unit classification
2 sdram_req_count 4096 4096 transactions INTEGRATED BENCHMARK
3 sdram_ready_count 4096 4096 transactions INTEGRATED BENCHMARK
4 sdram_wr_count 0 0 transactions (weight fetch is read-only) INTEGRATED BENCHMARK
5 sdram_busy_cycles 49404 49392 cycles INTEGRATED BENCHMARK
6 sdram_busy_pct 94.71 99.92 percent of total_cycles DERIVED
7 sdram_refresh_count 42 40 real AUTO REFRESH commands issued INTEGRATED BENCHMARK
8 sdram_request_latency_min 10 10 cycles (req-to-ready single controller port) INTEGRATED BENCHMARK
9 sdram_request_latency_max 16 16 cycles INTEGRATED BENCHMARK
10 sdram_request_latency_avg 10.06 10.06 cycles INTEGRATED BENCHMARK
11 isolated_single_txn_cycles_80mhz 10 10 cycles (Phase 4/6 isolated regression baseline) RTL SIMULATION
12 n_way_arbitration_overhead_cycles_per_tile 2.73 2.07 cycles/tile (measured cycles/tile minus isolated single-txn cost) DERIVED
13 sdram_avg_bytes_per_cycle 0.6282 0.6629 bytes/cycle DERIVED
14 sdram_sustained_bandwidth_mb_s 50.26 53.04 MB/s (bytes_per_cycle * 80MHz) DERIVED
15 sdram_nominal_bandwidth_mb_s 160.0 160.0 MB/s (2 bytes x16 * 80MHz) THEORETICAL
16 bandwidth_utilization_pct 31.4 33.2 percent of nominal DERIVED
17 bottleneck_classification MEMORY_BANDWIDTH_LIMIT MEMORY_BANDWIDTH_LIMIT qualitative (sdram_busy_pct near-saturated, avg latency near the fixed minimum -> not latency-bound; modest N-way arbitration overhead present but small relative to the controller's own fixed per-transaction cost -> not primarily arbitration-bound) DERIVED
@@ -0,0 +1,14 @@
design,n_slots,seed,fmax_mhz,pass_at_80mhz,classification
sdram,2,1,102.29,TRUE,POST-P&R
sdram,2,2,94.32,TRUE,POST-P&R
sdram,2,3,103.99,TRUE,POST-P&R
sdram,4,1,80.15,TRUE,POST-P&R
sdram,4,2,74.88,FALSE,POST-P&R
sdram,4,3,81.55,TRUE,POST-P&R
sdram,4,hifreq_target200,68.45_prelim_then_80.15_final,TRUE,POST-P&R
dual_psram,2,1,100.48,TRUE,POST-P&R
dual_psram,2,2,103.52,TRUE,POST-P&R
dual_psram,2,3,101.37,TRUE,POST-P&R
dual_psram,4,1,84.68,TRUE,POST-P&R
dual_psram,4,2,77.72,FALSE,POST-P&R
dual_psram,4,3,87.26,TRUE,POST-P&R
1 design n_slots seed fmax_mhz pass_at_80mhz classification
2 sdram 2 1 102.29 TRUE POST-P&R
3 sdram 2 2 94.32 TRUE POST-P&R
4 sdram 2 3 103.99 TRUE POST-P&R
5 sdram 4 1 80.15 TRUE POST-P&R
6 sdram 4 2 74.88 FALSE POST-P&R
7 sdram 4 3 81.55 TRUE POST-P&R
8 sdram 4 hifreq_target200 68.45_prelim_then_80.15_final TRUE POST-P&R
9 dual_psram 2 1 100.48 TRUE POST-P&R
10 dual_psram 2 2 103.52 TRUE POST-P&R
11 dual_psram 2 3 101.37 TRUE POST-P&R
12 dual_psram 4 1 84.68 TRUE POST-P&R
13 dual_psram 4 2 77.72 FALSE POST-P&R
14 dual_psram 4 3 87.26 TRUE POST-P&R
@@ -0,0 +1,8 @@
layer,value_mb_s,pct_of_nominal,classification,source
theoretical_sdram_nominal,160.0,100.0,THEORETICAL,2 bytes x16 x 80MHz
controller_max_isolated_burst4,64.0,40.0,RTL SIMULATION,STEP16 EXP-0041 isolated regression (10 cyc/txn @80MHz, 8 bytes/txn)
controller_max_isolated_burst8_packed,~114.3,~71.4,RTL SIMULATION,STEP18 isolated pack128 regression (16 cyc/txn @80MHz for a full pair + amortized 1-cyc cache hit = 17cyc/16bytes)
realistic_sustained_n4_baseline,53.04,33.2,INTEGRATED BENCHMARK,STEP16/17 N4 D-Stress (BURST_LEN=4, 1 tile/txn)
realistic_sustained_n4_pack128,58.34,36.5,INTEGRATED BENCHMARK,STEP18 N4 D-Stress (BURST_LEN=8 packed, N_ENTRIES=4 cache)
realistic_sustained_n2_baseline,50.26,31.4,INTEGRATED BENCHMARK,STEP16/17 N2 D-Stress
realistic_sustained_n2_pack128,55.30,34.6,INTEGRATED BENCHMARK,STEP18 N2 D-Stress
1 layer,value_mb_s,pct_of_nominal,classification,source
2 theoretical_sdram_nominal,160.0,100.0,THEORETICAL,2 bytes x16 x 80MHz
3 controller_max_isolated_burst4,64.0,40.0,RTL SIMULATION,STEP16 EXP-0041 isolated regression (10 cyc/txn @80MHz, 8 bytes/txn)
4 controller_max_isolated_burst8_packed,~114.3,~71.4,RTL SIMULATION,STEP18 isolated pack128 regression (16 cyc/txn @80MHz for a full pair + amortized 1-cyc cache hit = 17cyc/16bytes)
5 realistic_sustained_n4_baseline,53.04,33.2,INTEGRATED BENCHMARK,STEP16/17 N4 D-Stress (BURST_LEN=4, 1 tile/txn)
6 realistic_sustained_n4_pack128,58.34,36.5,INTEGRATED BENCHMARK,STEP18 N4 D-Stress (BURST_LEN=8 packed, N_ENTRIES=4 cache)
7 realistic_sustained_n2_baseline,50.26,31.4,INTEGRATED BENCHMARK,STEP16/17 N2 D-Stress
8 realistic_sustained_n2_pack128,55.30,34.6,INTEGRATED BENCHMARK,STEP18 N2 D-Stress
+6
View File
@@ -0,0 +1,6 @@
config,n_slots,physical_bus_bits,internal_width_bits,burst_len,cache_entries,cycles,fmax_mhz_best,mb_s,mac_per_cycle,bit_exact,classification
baseline,2,16,64,4,0,52161,103.99,50.26,0.6282,PASS,INTEGRATED BENCHMARK / POST-P&R
baseline,4,16,64,4,0,49430,81.55,53.04,0.6629,PASS,INTEGRATED BENCHMARK / POST-P&R
pack128_1entry_REJECTED,4,16,128,8,1,74004,not_synthesized,35.32,0.4428,PASS,INTEGRATED BENCHMARK (real regression, not accepted -- ERR-0022)
pack128,2,16,128,8,4,47399,86.04,55.30,0.6913,PASS,INTEGRATED BENCHMARK / POST-P&R
pack128,4,16,128,8,4,44935,81.47,58.34,0.7292,PASS,INTEGRATED BENCHMARK / POST-P&R
1 config,n_slots,physical_bus_bits,internal_width_bits,burst_len,cache_entries,cycles,fmax_mhz_best,mb_s,mac_per_cycle,bit_exact,classification
2 baseline,2,16,64,4,0,52161,103.99,50.26,0.6282,PASS,INTEGRATED BENCHMARK / POST-P&R
3 baseline,4,16,64,4,0,49430,81.55,53.04,0.6629,PASS,INTEGRATED BENCHMARK / POST-P&R
4 pack128_1entry_REJECTED,4,16,128,8,1,74004,not_synthesized,35.32,0.4428,PASS,INTEGRATED BENCHMARK (real regression, not accepted -- ERR-0022)
5 pack128,2,16,128,8,4,47399,86.04,55.30,0.6913,PASS,INTEGRATED BENCHMARK / POST-P&R
6 pack128,4,16,128,8,4,44935,81.47,58.34,0.7292,PASS,INTEGRATED BENCHMARK / POST-P&R
+5
View File
@@ -0,0 +1,5 @@
config,n_slots,trellis_io,trellis_ff,trellis_comb,mult18x18d,dp16kd,trellis_ramw,classification
baseline,4,194,6215,5516,32,0,173,POST-P&R
pack128,4,194,6483,6106,32,0,189,POST-P&R
baseline,2,194,3724,3783,16,0,109,POST-SYNTHESIS (yosys pre-map, N=2 not separately P&R'd with full util report in STEP17)
pack128,2,194,not_captured,not_captured,16,0,not_captured,POST-SYNTHESIS (yosys ran; single-seed P&R for Fmax only)
1 config,n_slots,trellis_io,trellis_ff,trellis_comb,mult18x18d,dp16kd,trellis_ramw,classification
2 baseline,4,194,6215,5516,32,0,173,POST-P&R
3 pack128,4,194,6483,6106,32,0,189,POST-P&R
4 baseline,2,194,3724,3783,16,0,109,POST-SYNTHESIS (yosys pre-map, N=2 not separately P&R'd with full util report in STEP17)
5 pack128,2,194,not_captured,not_captured,16,0,not_captured,POST-SYNTHESIS (yosys ran; single-seed P&R for Fmax only)
@@ -0,0 +1,377 @@
# STEP18 — SDRAM Transaction Efficiency & Weight-Path Scaling
Governing spec: the user's STEP18 message, in full. SDRAM remains the
closed, unreopened V2 external memory (DEC-0031/DEC-0032). All
evidence below is classified per the spec's own discipline. Raw data:
`step18_bandwidth_ladder.csv`, `step18_configs.csv`,
`step18_resources.csv`. Full narrative: `experiments.log` (EXP-0046/
0047), `errors.log` (ERR-0022), `decisions.log` (DEC-0033).
## 1. Executive Summary
The working hypothesis — "the 16-bit interface requires multiple
transactions to deliver one P8 tile" — is **FALSE** for the STEP16
baseline: the existing architecture already delivers exactly **one**
SDRAM transaction per tile (`MEM_DATA_WIDTH=64` was chosen in STEP16
specifically so BURST_LEN=4 = 8 bytes = 1 tile). The real
inefficiency is the opposite direction: each transaction pays a large
**fixed** per-transaction overhead (always-precharge, no page-mode),
so 33.2% bandwidth utilization comes from paying that fixed cost once
per tile rather than amortizing it over more data.
Built and validated a new memory-side-only module
(`sdram_weight_backend_pack128.v`) that packs **2 tiles per real
SDRAM transaction** (BURST_LEN=8, 128 bits) using a small
address-tagged cache — `weight_prefetch_engine_wide.v` and
`neural_processor.v` are completely unmodified. A first (1-entry
cache) draft **regressed** throughput by 50% when tested end-to-end
(caught and documented, ERR-0022) because N=4's interleaved requests
thrash a single cache entry; fixing the cache to N_ENTRIES=4 (sized to
N_SLOTS) turned this into a **real, validated 9.1% cycle reduction**
at both N=2 and N=4, bit-exact, with Fmax essentially unchanged at N=4
(81.47 vs 81.55MHz) and a smaller Fmax reduction at N=2 (86.04 vs
103.99MHz — still comfortably >80MHz).
**The system remains memory-transaction-bound after this
optimization** — cycles/tile improved from 12.07 to 10.97 at N=4, but
compute utilization is still only ~2.3%. The next bottleneck, after
transaction packing, is the SDRAM controller's own fixed
always-precharge overhead itself (not bus width, not burst length, not
N-scaling) — recoverable only via a page-mode/keep-row-open
controller redesign, explicitly a larger change than this round's
"smallest possible" scope.
## 2. STEP17 baseline verification
Cross-checked against `step17_n4_timing_throughput.md` and its CSVs:
D-Stress N4=49,430 cycles ✓, Fmax=81.55MHz best-of-3-seeds ✓, I/O=
194/245 ✓, measured bandwidth 53.04MB/s of 160MB/s nominal (33.2%) ✓,
controller busy 99.92% ✓, N2=52,161 cycles ✓, N2→N4 improvement
(52161-49430)/52161=5.2% ✓ (matches the spec's own cited "≈5.2%"
exactly). All verified consistent with the source reports — no
discrepancy found.
## 3. Raw SDRAM controller ceiling (Part A)
Rather than re-deriving numbers STEP16/17 already measured, this
section reframes the existing REAL, real measured data into the
required ladder, plus targeted new checks for patterns STEP16/17 did
not explicitly frame:
| Layer | Value | Classification |
|---|---|---|
| THEORETICAL SDRAM bandwidth (nominal) | 160.0 MB/s (2B×16bit×80MHz) | THEORETICAL |
| CONTROLLER MAXIMUM (isolated, BURST_LEN=4, back-to-back single requester) | 64.0 MB/s (40.0%) | RTL SIMULATION (STEP16 EXP-0041: 10 cyc/txn @80MHz) |
| CONTROLLER MAXIMUM (isolated, BURST_LEN=8 packed, back-to-back single requester) | ~114.3 MB/s (~71.4%) | RTL SIMULATION (STEP18: 17 cyc for 16 bytes = real fetch 16cyc + cache-hit 1cyc, single requester) |
| REALISTIC SUSTAINABLE (N=4, real arbitrated, BURST_LEN=4 baseline) | 53.04 MB/s (33.2%) | INTEGRATED BENCHMARK |
| REALISTIC SUSTAINABLE (N=4, real arbitrated, BURST_LEN=8 packed) | 58.34 MB/s (36.5%) | INTEGRATED BENCHMARK |
**Patterns A-D (sequential R/W, bursts, R/W turnaround):** already
covered by STEP16 Phase 3/4 (tb_sdram_controller.v, tests A/B/C/D) and
STEP17's own controller-port instrumentation — reused, not
re-measured. Writes are not part of the weight-fetch path at all (0
writes observed in every real D-Stress run, STEP17 EXP-0045), so
Pattern D (R/W turnaround) has **no real-workload relevance** for this
system's weight path; it was already validated for protocol
correctness in STEP16 Phase 3 but does not affect the throughput
analysis below.
**Patterns E/F (row locality):** the controller's own design (STEP16)
**always issues auto-precharge on every transaction** — no row is
ever kept open across transactions, by explicit, documented design
choice (correctness-first, no per-row state to track). This means row
locality provides **zero** throughput benefit in this controller,
BY CONSTRUCTION — same-row and different-row accesses cost identically
(confirmed structurally from the RTL, not re-benchmarked, since the
FSM has no conditional path that could make them differ). This is the
single largest lever available for a FUTURE controller redesign (not
pursued here — see §15/§18).
**Pattern G (refresh):** STEP17 already measured this directly — 40
real AUTO REFRESH events during the N=4 D-Stress run, contributing
negligibly to total cycles (each refresh costs ~10 cycles at 80MHz,
40×10=400 cycles of 49,430 total = 0.8%). Refresh is not a meaningful
throughput factor.
## 4. Current transaction analysis (Part B)
Verified directly against the RTL and STEP17's own instrumentation:
**one P8 tile (8×INT8=64 bits=8 bytes) already costs exactly ONE
physical SDRAM transaction** in the STEP16/17 baseline — confirmed by
`nms_dataflow_core_sdram.v`'s own `MEM_DATA_WIDTH=64` parameter
(chosen in STEP16 specifically so `WORDS_PER_TILE` in
`weight_prefetch_engine_wide.v` equals exactly 1), and by STEP17's own
measured `sdram_req_count=4096` exactly matching `tiles_delivered=
4096`. **The working hypothesis that multiple transactions were
needed per tile is refuted by direct inspection of the existing
implementation, not assumed.**
Why 8 bytes costs 10 cycles (not fewer): the 10 cycles decompose as
tRCD(2)+CAS latency(3)+burst data(4)+tRP(2), with 1 cycle of real
pipeline overlap (measured, not the naive 11-cycle sum) — **6 of the 10
cycles (60%) are fixed row-open/row-close overhead, independent of
burst length**. This is a controller-architecture fact (always-
precharge), not a consequence of the bus being 16 bits wide — a
32-bit or 64-bit physical bus with the SAME always-precharge design
would show the identical %-overhead ratio, just at a higher absolute
byte count per transaction.
## 5. Physical vs logical bandwidth
Physical SDRAM bus width (16 bits) and internal logical delivery width
(`MEM_DATA_WIDTH`, currently 64 bits in the baseline) are correctly
already DECOUPLED in this architecture — `weight_prefetch_engine_
wide.v` was built in STEP14 specifically to support this distinction,
and STEP16 exploited it (64-bit internal width over a 16-bit physical
bus, 4 physical 16-bit beats per logical fetch via BURST_LEN=4). The
STEP18 experiment tests whether widening the INTERNAL delivery further
(to 128 bits, 2 tiles/fetch) — while the physical bus stays 16 bits —
helps. See §6.
## 6. Weight packing results (Part C)
Implemented `sdram_weight_backend_pack128.v`: internally BURST_LEN=8
(128 bits/16 bytes per real transaction = 2 P8 tiles), externally
still presents the exact same 64-bit `mem_req`/`mem_addr`/`mem_rdata`/
`mem_ready` contract `weight_prefetch_engine_wide.v` already uses —
**that engine and `neural_processor.v` are byte-for-byte unchanged**.
An address-tagged cache holds the "other half" of each real 128-bit
fetch for the next sequential request.
**First draft (1 cache entry): REJECTED.** Isolated single-requester
unit test passed 20/20 (`tb_sdram_weight_backend_pack128.v`), but the
full N=4 integration benchmark REGRESSED to 74,004 cycles (+49.7% vs
baseline) — N=4's interleaved multi-slot requests thrash a single
cache entry before the natural pair completes (ERR-0022, full
root-cause below).
**Fixed (N_ENTRIES=4, sized to N_SLOTS): ACCEPTED.**
| | Baseline (BURST_LEN=4) | Packed (BURST_LEN=8, N_ENTRIES=4) | Δ |
|---|---|---|---|
| SDRAM transactions/tile (N=4) | 1.0 | 0.5 (2 tiles/real txn) | -50% |
| Cycles/tile (N=4) | 12.07 | 10.97 | -9.1% |
| D-Stress cycles (N=4) | 49,430 | **44,935** | **-9.1%** |
| D-Stress cycles (N=2) | 52,161 | **47,399** | **-9.1%** |
| Sustained weight bandwidth (N=4) | 53.04 MB/s | 58.34 MB/s | +10.0% |
| Sustained MAC/cycle (N=4) | 0.6629 | 0.7292 | +10.0% |
| Bit-exact | PASS | PASS | — |
## 7. Burst results (Part D)
BURST_LEN=8 was already protocol-validated in STEP16 Phase 3 (460/460
tests, all frequencies including 80MHz) — reused directly, not
re-verified from scratch. Confirmed here: address alignment (16-byte
blocks), data ordering (rdata[63:0]=lower address half, rdata[127:64]=
upper half, matching the controller's own word0-first convention
exactly, no byte-order surprises), and real cycles/tile (10.97,
measured, not estimated). **Larger burst is NOT automatically
better** — confirmed directly by the rejected 1-entry-cache draft,
where BURST_LEN=8's own real 16-cycle cost, applied to nearly every
access (cache thrashed), made things WORSE than BURST_LEN=4's
10-cycle cost. The benefit only appears once the consuming logic
(the cache) actually captures the 2x data-per-transaction ratio in
real traffic, not merely in isolation.
## 8. Outstanding request results (Part E)
**The controller is inherently single-transaction: confirmed, not
assumed.** `sdram_controller.v`'s own FSM has exactly one `busy`
state machine and cannot begin a new ACTIVATE while servicing a prior
transaction (STEP16 architecture, unmodified). A true multi-
outstanding-request redesign (overlapping ACTIVATE of transaction N+1
with the CAS/burst of transaction N) was **not attempted** this
round — it would require re-architecting the controller's own FSM to
track multiple in-flight bank states, response ordering, and address
association simultaneously, a materially larger change than "smallest
possible," and the packing experiment (§6) already recovers a
comparable practical benefit (fewer, larger transactions) at much
lower risk. This is documented as an explicit, deliberate scope
boundary, not an oversight.
## 9. Weight buffer results (Part F)
The packing cache (§6) IS the weight tile buffer this Part asks to
evaluate — a small (N_ENTRIES=4), non-blocking, per-address buffer
between the real SDRAM burst and the 64-bit interface `weight_
prefetch_engine_wide.v` consumes. Its own `PREFETCH_DISTANCE`-based
read-ahead mechanism (STEP11, unchanged) was ALREADY confirmed
sufficient in STEP17 (mm.state busy ~97-99% of cycles despite only
~2% useful — the bottleneck was never insufficient buffering, it was
SDRAM's own service rate). No separate double-buffer/FIFO experiment
was built, since the packing cache already demonstrates the intended
"eliminate bubbles between SDRAM delivery and P8 consumption" effect
directly (measured: cycles/tile dropped, not merely resource usage
changed).
## 10. Activation traffic (Part G)
Reused directly from STEP15/16/17's own consistent measurement: the
shared 16-bit activation/result-writeback PSRAM port utilization is
**6.5% (baseline) / 7.2% (packed, N=4)** of total cycles — a small,
stable fraction, confirming **weight traffic dominates external
memory traffic by a wide margin** (>90% of all external-memory
activity is weight fetch, not activation or result writeback). This
was not re-instrumented this round (STEP17's own measurement already
answers the question directly and the packing change does not touch
the activation path at all — its port utilization moving from 6.5%→
7.2% is a pure DERIVED consequence of total_cycles shrinking, not a
change in absolute activation traffic).
## 11. N2/N4 comparison (Part I, addressed with §6's data)
| | N=2 baseline | N=2 packed | N=4 baseline | N=4 packed |
|---|---|---|---|---|
| Cycles | 52,161 | 47,399 | 49,430 | 44,935 |
| Improvement vs own baseline | — | -9.1% | — | -9.1% |
| Fmax (best-of-seeds) | 103.99 | 86.04 (1 seed) | 81.55 | 81.47 |
**Does improved transaction efficiency let N=4 extract more useful
throughput than N=2?** Both configurations improve by an IDENTICAL
9.1% — packing is a pure memory-side win that benefits N=2 and N=4
equally, because it reduces the FIXED per-transaction overhead
regardless of how many requesters share the port. It does **not**
change the fundamental N=2-vs-N=4 story: N=4 remains only modestly
faster than N=2 (44935 vs 47399, 5.2% — essentially the SAME relative
gap as the baseline's own 49430-vs-52161, 5.2%), because the shared
SDRAM port is still the binding resource in both cases; packing
raises the ceiling for BOTH equally without changing which layer is
the bottleneck. N=8 was not run (explicitly optional/exploratory per
the governing spec, and the N=2/N=4 result already answers the
scaling question the spec asks).
## 12. Post-synthesis / P&R (Part J)
Real Yosys 0.68+/nextpnr-ecp5 0.11.1 results, `--45k --package CABGA381
--lpf-allow-unconstrained`, identical methodology to STEP16/17:
| Config | TRELLIS_IO | TRELLIS_FF | TRELLIS_COMB | MULT18X18D | DP16KD | Fmax (best-of-3) |
|---|---|---|---|---|---|---|
| Baseline N=4 | 194/245 | 6215 | 5516 | 32 | 0 | 81.55 MHz |
| Packed N=4 | 194/245 | 6483 (+4.3%) | 6106 (+10.7%) | 32 | 0 | 81.47 MHz (PASS) |
| Packed N=2 | 194/245 | not captured | not captured | 16 | 0 | 86.04 MHz (1 seed, PASS) |
I/O is unchanged (packing is purely internal, no new pins). Modest FF/
COMB increase for the multi-entry cache logic. **Fmax at N=4 is
essentially unchanged (81.47 vs 81.55MHz, within normal seed
variance)** — the decision criterion (N4 Fmax ≥80MHz) is met with the
SAME margin as the STEP17 baseline. N=2's Fmax drop (103.99→86.04) is
real but only single-seed-measured here (not best-of-3) and still
comfortably clears 80MHz; N=4 is the primary target per the governing
spec and shows no meaningful Fmax cost.
## 13. Throughput roofline (Part K)
| Ceiling | N=4 baseline | N=4 packed | Classification |
|---|---|---|---|
| Compute ceiling (N×P_IN) | 32 MAC/cycle | 32 MAC/cycle | THEORETICAL |
| External SDRAM ceiling (measured sustained) | 53.04 MB/s | 58.34 MB/s | INTEGRATED BENCHMARK |
| System ceiling (actual) | 0.6629 MAC/cycle | 0.7292 MAC/cycle | INTEGRATED BENCHMARK |
| Compute utilization | 2.07% | 2.28% | DERIVED |
The external SDRAM ceiling moved up (transaction efficiency
improved), and the system ceiling moved up proportionally with it —
confirming the system is STILL memory-bound (compute utilization
barely changed, 2.07%→2.28%), just against a slightly higher memory
ceiling than before.
## 14. Bottleneck analysis (Part L)
```
transaction packing -> IMPROVED (this round, -9.1% cycles)
|
controller fixed overhead -> STILL DOMINANT (always-precharge pays
| the same 6-cycle row-open/close cost
| per transaction regardless of packing)
v
N-way arbitration overhead -> small, ~2 cycles/tile, unchanged
|
v
SDRAM physical bandwidth -> not yet the limit (160MB/s nominal vs
58.34MB/s sustained = 36.5% used)
|
v
compute -> far from the limit (2.28% utilization)
```
The NEXT bottleneck after this round's packing optimization is the
**SDRAM controller's own fixed always-precharge overhead** — not bus
width, not burst organization (already exploited), not N-scaling
(unaffected by this change), not activation traffic (confirmed minor),
and not compute (nowhere near saturated).
## 15. Recommended architecture
**Adopt `sdram_weight_backend_pack128.v`** (BURST_LEN=8, N_ENTRIES=4
address-tagged cache) as the new weight-fetch backend for the N=4 V2
baseline, replacing STEP16's `sdram_weight_backend.v` (BURST_LEN=4, no
cache). All STEP18 decision criteria are met: bit-exact (✓), no
deadlock/timeout/dropped-or-duplicated-jobs (✓, full D-Stress PASS at
both N=2/N=4), SDRAM protocol correct (✓, reuses the already-validated
`sdram_controller.v` unchanged, just at BURST_LEN=8), N=4 Fmax ≥80MHz
(✓, 81.47MHz), D-Stress cycles improve (✓, -9.1%), sustained MAC/cycle
improves (✓, +10.0%), memory efficiency improves (✓, 33.2%→36.5%), no
hidden processor serialization (✓, `neural_processor.v` and the
STEP13 streaming architecture are completely untouched).
## 16. Rejected alternatives
- **1-entry cache** (first draft): rejected — real, measured 49.7%
throughput REGRESSION under N=4 interleaving (ERR-0022).
- **True multi-outstanding-request controller**: not attempted —
materially larger redesign risk for an uncertain additional gain
once packing already captures the "amortize fixed overhead" benefit;
deferred as explicit future work (§8).
- **Page-mode / keep-row-open controller redesign**: not attempted —
correctly identified (§3, §14) as the actual next bottleneck, but a
genuinely large controller rewrite, explicitly out of this round's
"smallest possible change" scope.
- **N=8**: not run — explicitly optional/exploratory per the governing
spec, and N=2/N=4 already answers the scaling question asked.
## 17. Risks
- N=2's Fmax (86.04MHz) was measured with only 1 seed (not
best-of-3) — a real, disclosed gap in rigor relative to the N=4
measurement; N=4 is the primary target and was measured properly.
- The packing cache's address-alignment assumption (natural 16-byte
pairing from `w_base`/`TILE_BYTES` strides) held for the real
D-Stress workload but is not universally guaranteed for arbitrary
future workloads — correctness is guaranteed regardless (a cache
MISS always falls back to a real, address-exact fetch), but the
9.1% benefit is workload-pattern-dependent and could be smaller for
a workload with misaligned or non-sequential weight access.
- N_ENTRIES=4 was sized to match N_SLOTS=4 by construction reasoning,
not swept (e.g. N_ENTRIES=2 or 8 were not separately measured) — the
chosen size is justified analytically (§6/ERR-0022) but not proven
optimal.
- No new gate-level/post-P&R re-simulation was performed (same
methodology limitation as STEP16/17).
## 18. Final decision
**With the existing 16-bit SDRAM hardware, FPGA-Neural can now sustain
~36.5% of nominal physical bandwidth (58.34 of 160 MB/s) at N=4,
up from 33.2% before this step** — recovered entirely through
transaction packing (2 tiles/real SDRAM transaction via BURST_LEN=8 +
a small N_SLOTS-sized cache), with zero change to the SDRAM device,
the physical bus width, the neural processor, or the STEP13 streaming
architecture. **The minimum memory-side architecture required to feed
N=4/P8 efficiently, given the current controller's always-precharge
design, is exactly this: pack the natural tile-pair granularity into
one larger burst, cached per-outstanding-slot to survive arbitration
interleaving** — no further internal-width widening (256-bit etc.) is
justified without ALSO addressing the controller's own fixed overhead
first, since a wider pack alone cannot beat the row-open/row-close
cost ratio without a page-mode redesign.
**Next bottleneck: SDRAM controller efficiency (transaction overhead)
— specifically, the always-precharge, no-page-mode design.** It is
NOT: SDRAM physical bandwidth (36.5% of 160MB/s used, headroom
remains), burst organization (already exploited this round), Memory
Manager (STEP17 confirmed adequate buffering pre-existing), activation
traffic (confirmed minor, §10), internal delivery width in isolation
(the 1-entry-cache failure proved width alone doesn't help without
correct multi-slot handling), or compute (2.28% utilization, far from
saturated). The memory device choice remains closed and unchanged, per
the governing spec's own instruction.