From f14224d0cd654cac3befcb85295732d2adf3c945 Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Mon, 7 Sep 2026 03:10:50 +0200 Subject: [PATCH] fix: ERR-0029 sdram_unified_backend weight-cache hit-index critical path Replace the serial "last-match-wins" priority-scan hit-index encoder with a flat one-hot compare + single-level priority encode, breaking the serially-dependent PFUMX/OFX cascade real P&R showed dominating the N_SLOTS=8 @ 64MHz critical path (55.84MHz worst, 4/8 seeds PASS). Real nextpnr-ecp5 re-verification (32-run matrix, 4 configs x 8 seeds): N_SLOTS=8 @ 64MHz improves to 5/8 PASS (worst 60.12MHz, up from 55.84MHz). N_SLOTS=4 @ 64MHz stays 8/8 PASS but with reduced worst-case margin (WNS +0.605ns, down from +2.143ns) as the critical path relocates to a different, previously-second-worst path in the same module -- a real, disclosed trade-off, not hidden. 80MHz remains NO-GO at both processor counts (re-confirmed on the fixed RTL). Bit-exact, zero functional regression: isolated tb_sdram_unified_backend.v (40/40) and full D-Stress N=4/N=8 (both 256/256 neurons bit-exact vs golden model). See errors.log ERR-0029 and decisions.log DEC-0040 for full root-cause writeup and MEMORY_UPGRADE_64MB_N8.md section 10 for the complete measured data set and AS4C32M16SB-7BIN pinout tables. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v --- hardware/v2/docs/MEMORY_UPGRADE_64MB_N8.md | 222 +++++++++++++++++++- hardware/v2/logs/decisions.log | 45 ++++ hardware/v2/logs/errors.log | 88 ++++++++ hardware/v2/nms/rtl/sdram_unified_backend.v | 61 +++++- 4 files changed, 407 insertions(+), 9 deletions(-) diff --git a/hardware/v2/docs/MEMORY_UPGRADE_64MB_N8.md b/hardware/v2/docs/MEMORY_UPGRADE_64MB_N8.md index 1ed5231..215ce75 100644 --- a/hardware/v2/docs/MEMORY_UPGRADE_64MB_N8.md +++ b/hardware/v2/docs/MEMORY_UPGRADE_64MB_N8.md @@ -231,4 +231,224 @@ package during layout; both are the same die.) - The V2 LaTeX datasheet's own key-parameters table and memory- architecture chapter still describe the 8MB device — not regenerated this session (time/scope boundary); flagged here so it - is not silently stale. + is not silently stale. **UPDATE (2026-09-07): now addressed, see + section 10 below and `DataSheet/files/docs/datasheet/v2-en/chapters/ + 05-memory.tex`, appended section "SDRAM Upgrade Addendum."** + +## 10. AUTHORITATIVE FINAL DATA (2026-09-07) — full 8-seed matrix, + ERR-0029 optimization, and complete AS4C32M16SB-7BIN pinout + +This section is the authoritative, most-recent source of truth, +superseding sections 5-9 above where they conflict (kept for history). +All data below is real, measured, from `nextpnr-ecp5 0.11.1 --report` +JSON output and real Verilator 5.050 regression runs — no estimates. + +### 10.1 N=4 @ 64MHz — PRE-ERR-0029 fix (period 15.625ns) + +| Seed | Fmax (MHz) | WNS (ns) | Critical path (startpoint → endpoint) | +|---|---:|---:|---| +| 0 | 77.10 | +2.655 | director.job_out_slot → dep_mgr.node_resolved[13] | +| 1 | 74.68 | +2.234 | arbiter_wide.m_addr → sdram_backend.w_rdata | +| 2 | 74.64 | +2.228 | director.job_out_slot → dep_mgr.node_resolved[6] | +| 3 | 77.24 | +2.678 | arbiter_wide.m_addr → sdram_backend.w_rdata | +| 4 | 76.60 | +2.571 | sdram_backend.w_cache_addr[2] → sdram_backend.w_rdata | +| 5 | 77.96 | +2.798 | director.job_out_slot → dep_mgr.node_resolved[3] | +| 6 | 75.35 | +2.353 | director.job_out_slot → dep_mgr.node_resolved[2] | +| 7 (worst) | 74.17 | +2.143 | director.job_out_slot → dep_mgr.node_state[12] | + +8/8 PASS. Worst seed7 74.17MHz/+2.143ns — routing-dominated (78%), +11 logic levels, classified as dependency_manager scheduler/producer- +consumer resolution logic. + +### 10.2 N=8 @ 64MHz — PRE-ERR-0029 fix (period 15.625ns) + +| Seed | Fmax (MHz) | WNS (ns) | Status | +|---|---:|---:|---| +| 0 | 57.27 | −1.837 | FAIL | +| 1 (worst) | 55.84 | −2.284 | FAIL | +| 2 | 66.99 | +0.698 | PASS | +| 3 | 70.78 | +1.496 | PASS | +| 4 | 61.00 | −0.769 | FAIL | +| 5 | 68.56 | +1.039 | PASS | +| 6 | 67.29 | +0.764 | PASS | +| 7 | 63.42 | −0.144 | FAIL | + +4/8 PASS (2,3,5,6), 4/8 FAIL (0,1,4,7). Worst seed1 55.84MHz/−2.284ns — +`sdram_unified_backend.v` weight-cache hit-index scan, 84% routing, +10 logic levels; three individual routing hops of 2.5–2.8ns. +Utilization: MULT18X18D 64/72 (88.9%), TRELLIS_COMB 11066/43848 +(25.2%), TRELLIS_FF 11119/43848 (25.4%), DP16KD 0/108, TRELLIS_RAMW +323/5481. + +### 10.3 N=4/N=8 @ 80MHz — genuine `ecppll`-regenerated PLL (CLKI_DIV=1, + CLKFB_DIV=5, CLKOP_DIV=7, CLKOP_CPHASE=3, VCO=560MHz), period 12.5ns + +Both configurations: **0/8 seeds PASS** (achieved Fmax per seed +numerically identical to the 64MHz-PLL run in every case, confirming +the achievable ceiling is a fabric property, independent of PLL +target). N=4 closest: seed5, 77.96MHz, WNS=−0.327ns. N=8 closest: +seed3, 70.78MHz, WNS=−1.629ns. **NO-GO, both configs, both before and +after the ERR-0029 fix below** (re-confirmed in 10.5). + +### 10.4 ERR-0029 root-cause investigation (user-directed, real data) + +Investigated per the mandate's own 14-point checklist against the real +critical-path segment dump (seed1, N=8@64MHz) — see errors.log ERR-0029 +for the full writeup. Summary of findings: + +1. `w_cache_valid[0:W_ENTRIES-1]` (W_ENTRIES=4 fixed, NOT scaled by + N_SLOTS — confirmed via its only instantiation) generated by the + cache-allocate/consume sequential block. +2. `w_hit_idx_c` generated by a combinational `for` loop, "last + valid+matching entry wins" by unconditional sequential overwrite. +3. 4 comparators (one per W_ENTRIES). +4. Encoded via a serially-dependent priority scan, mapped by + Yosys/nextpnr onto cascaded ECP5 PFUMX/OFX fast-mux primitives. +5. `w_hit_idx_c` fans out to the 64-bit cache-data read mux and to + control/enable logic gating `w_rdata`'s load — a 3-way fan-out of a + value produced by a serial 4-stage chain. +6/7. The long hops (2.5–2.8ns each) are the physical distance between + the shared cache logic and the arbiter/consumer registers, stretched + by N=8's larger overall placement — NOT a logic-depth artifact + (W_ENTRIES doesn't grow with N_SLOTS). +8. Confirmed: yes, a serial mux-topology (PFUMX/OFX chain), not a + parallel structure. +9. Comparator fanout (4-wide) is NOT the dominant cost. +10. Yes — the final long hop lands on a clock-enable/control signal, + not a data path, confirming control-logic fan-in as part of the + span. +11/12/14. Yes — registering an intermediate result, and/or replacing + the serial scan with a balanced/flat parallel structure, are both + feasible, low-risk, same-precedent-class fixes (ERR-0028 used the + same architecture for a different module). +13. Not a "replicate per slot" scenario, since the cache is shared and + W_ENTRIES is fixed — a flat parallel restructuring was chosen + instead of pipelining, to avoid any latency/behavior change. + +### 10.5 ERR-0029 fix applied, and the honest, measured before/after + +Fix: serial priority-scan → flat one-hot compare (parallel comparators, +`generate`/`genvar`) + single-level `casez` priority encode, bit-exact +semantics preserved. See errors.log ERR-0029 and DEC-0040 for full +detail. Verified bit-exact: isolated `tb_sdram_unified_backend.v` +40/40 PASS; full D-Stress N=4 (49,927 cycles, 256/256 bit-exact vs +golden) and N=8 (49,909 cycles, 256/256 bit-exact vs golden) — zero +functional regression. + +**N=4 @ 64MHz, POST-fix** (period 15.625ns): + +| Seed | Fmax (MHz) | WNS (ns) | Critical path endpoint | +|---|---:|---:|---| +| 0 | 70.68 | +1.477 | sdram_backend.state | +| 1 (worst) | 66.58 | +0.605 | sdram_backend.ctrl_wdata | +| 2 | 74.74 | +2.245 | sdram_backend.ctrl_wdata | +| 3 | 71.98 | +1.733 | sdram_backend.ctrl_wdata | +| 4 | 74.48 | +2.198 | dataflow_core.GEN_SLOT[2].u_mm.wgt_rd_addr | +| 5 | 75.65 | +2.406 | sdram_backend.ctrl_wdata | +| 6 | 67.41 | +0.791 | sdram_backend.state | +| 7 | 68.47 | +1.019 | sdram_backend.ctrl_wdata | + +**8/8 PASS (unchanged pass count).** Worst-case margin fell from ++2.143ns to +0.605ns (still a real, positive-margin PASS on every +seed — the critical path relocated off the shortened hit-index chain +onto a different, previously-second-worst path in the same module). +Disclosed, not hidden. + +**N=8 @ 64MHz, POST-fix** (period 15.625ns): + +| Seed | Fmax (MHz) | WNS (ns) | Status | +|---|---:|---:|---| +| 0 | 66.45 | +0.575 | PASS | +| 1 | 65.28 | +0.307 | PASS | +| 2 | 61.21 | −0.712 | FAIL | +| 3 | 66.96 | +0.690 | PASS | +| 4 | 66.66 | +0.624 | PASS | +| 5 | 65.71 | +0.407 | PASS | +| 6 (worst) | 60.12 | −1.009 | FAIL | +| 7 | 62.70 | −0.324 | FAIL | + +**Pass count improved 4/8 → 5/8** (seeds 0,1,3,4,5 PASS; 2,6,7 FAIL). +Worst-case Fmax improved 55.84→60.12MHz, worst WNS −2.284→−1.009ns — +a real, measured improvement, **not yet full closure**. + +Resource utilization, POST-fix, N=8: MULT18X18D 64/72 (88.9%, +unchanged), TRELLIS_COMB 11129/43848 (25.4%, +63 LUTs, negligible), +TRELLIS_FF 11119/43848 (unchanged), TRELLIS_RAMW 323/5481 (unchanged). +N=4: TRELLIS_COMB 7175/43848 (25.2%→7175, down from 7609 pre-fix). + +**N=4/N=8 @ 80MHz, POST-fix**: re-confirmed 0/8 both configs (same +Fmax values as the 64MHz-labeled runs). **NO-GO, unchanged.** + +### 10.6 AS4C32M16SB-7BIN — complete verified hardware data + +Source: Alliance Memory `AllianceMemory_512M_SDRAM_Bdie_AS4C32M16SB- +7TXN-6TIN-7BIN_Rev1.4_June2024NK.pdf`, the exact -7BIN datasheet +(Figure 1.1, real TFBGA ball diagram — not inferred from the TSOP-II +`-7TIN` pinout). + +| Property | Value | +|---|---| +| Part | AS4C32M16SB-7BIN | +| Capacity | 512Mbit = 64MByte | +| Organization | 4 banks × 8M words × 16 bits | +| Package | 54-ball FBGA, 8×8×1.2mm max | +| VDD / VDDQ | 3.3V ±0.3V (isolated I/O supply) | +| Address / Bank | A[12:0] / BA[1:0] | +| Data / Masks | DQ[15:0] / LDQM, UDQM | +| Clock | CLK, single-ended — **no CLK_N** (SDR SDRAM) | +| Control | CKE, CS#, RAS#, CAS#, WE# | +| Temperature / Speed | −40 to 85°C / −7 (143MHz max) | + +**Complete individual-ball pinout (54 balls, no grouped notation):** + +Address: H7=A0, H8=A1, J8=A2, J7=A3, J3=A4, J2=A5, H3=A6, H2=A7, H1=A8, +G3=A9, H9=A10/AP, G2=A11, G1=A12. +Bank: G7=BA0, G8=BA1. +Data: A8=DQ0, B9=DQ1, B8=DQ2, C9=DQ3, C8=DQ4, D9=DQ5, D8=DQ6, E9=DQ7, +E1=DQ8, D2=DQ9, D1=DQ10, C2=DQ11, C1=DQ12, B2=DQ13, B1=DQ14, A2=DQ15. +Masks: E8=LDQM, F1=UDQM. +Control: F2=CLK, F3=CKE, G9=CS#, F8=RAS#, F7=CAS#, F9=WE#. +Power/Ground/NC: VDD={A9,E7,J9}, VSS={A1,E3,J1}, VDDQ={A7,B3,C7,D3}, +VSSQ={A3,B7,C3,D7}, NC=E2. (13+2+16+2+6+3+3+4+4+1 = 54 ✓) + +**FPGA (LFE5U-45F-8BG381) ↔ SDRAM (AS4C32M16SB-7BIN) mapping** (from +`hardware/v2/constraints/v2_board_top.lpf`, 45/45 unique FPGA balls, +no duplicates): + +| FPGA signal | FPGA ball | SDRAM signal | SDRAM ball | +|---|---|---|---| +| sdram_a[0..12] | D5,D3,F4,E5,E3,F5,A2,B1,C2,C1,D2,D1,F1 | A0..A12 | H7,H8,J8,J7,J3,J2,H3,H2,H1,G3,H9,G2,G1 | +| sdram_ba[0:1] | E4,C3 | BA0,BA1 | G7,G8 | +| sdram_dq[0..15] | E1,G5,H3,J5,K3,K2,H1,J1,K1,K4,L4,L5,M5,M4,N4,N5 | DQ0..DQ15 | A8,B9,B8,C9,C8,D9,D8,E9,E1,D2,D1,C2,C1,B2,B1,A2 | +| sdram_dqm[0:1] | P5,N3 | LDQM,UDQM | E8,F1 | +| sdram_cke/cs_n/ras_n/cas_n/we_n | B5,C5,C4,A3,B3 | CKE,CS#,RAS#,CAS#,WE# | F3,G9,F8,F7,F9 | + +Note: FPGA ball "F1" (assigned to `sdram_a[12]`) and SDRAM ball "F1" +(the SDRAM's own `UDQM`) are two different physical devices' own +separate ball-numbering namespaces — not a conflict, but flagged so a +PCB designer does not confuse the two identically-labeled balls. + +**Hardware pinout validation**: all FPGA balls real (LFE5U-45F-8BG381 +rev 3.0 CSV), 45/45 unique; all SDRAM balls real (AS4C32M16SB-specific +datasheet, not the TSOP variant); A12/BA[1:0]/DQ[15:0]/DQM[1:0]/all +control signals present and complete; VDD/VDDQ/I-O voltage compatible +(3.3V LVCMOS33 ↔ LVTTL); LPF/RTL/datasheet mutually consistent. **No +hardware blockers found.** + +### 10.7 PRODUCTION HARDWARE BASELINE (authoritative, 2026-09-07) + +**LFE5U-45F-8BG381 + AS4C32M16SB-7BIN + N_SLOTS=4 + P_IN=8 + 64MHz: +GO.** Real, bit-exact functional correctness; real synthesis (0 +errors); real P&R (8/8 seeds route); real timing closure (8/8 seeds +PASS, worst WNS +0.605ns post-optimization); real SDRAM/FPGA pinout +cross-verified with no blockers. + +**N_SLOTS=8 @ 64MHz: OPEN, not production-frozen.** Functionally +correct (bit-exact) and measurably closer to timing closure after +ERR-0029 (5/8 seeds PASS, up from 4/8), but not yet reliable on every +tested placement seed. Usable today only by pinning a known-good seed +(0, 1, 3, 4, or 5) or pending a further optimization pass. + +**80MHz: NO-GO at either N_SLOTS value**, confirmed twice (pre- and +post-ERR-0029) with a genuinely regenerated PLL — not achievable with +the current architecture. diff --git a/hardware/v2/logs/decisions.log b/hardware/v2/logs/decisions.log index 8ba56ca..18c220c 100644 --- a/hardware/v2/logs/decisions.log +++ b/hardware/v2/logs/decisions.log @@ -2230,3 +2230,48 @@ P&R re-run, all seeds failing -- consistent with this project's own prior documented conclusion (ecp5_pll_sys_clk.v's own header: "64MHz was chosen... the highest frequency at which ALL measured seeds close timing with real margin"). + +DEC-0040 -- N_SLOTS=8 @ 64MHz weight-cache hit-index optimization +(ERR-0029): accepted a measured, disclosed N_SLOTS=4 margin reduction +in exchange for a measured N_SLOTS=8 pass-rate improvement + +DATE: 2026-09-07 +CONTEXT: user-mandated root-cause investigation and optimization of +DEC-0039's own remaining open item (N_SLOTS=8 @ 64MHz not fully +timing-closed), under an explicit constraint set: N_SLOTS=4 must stay +8/8 PASS and not "regress into a hardware concern," N_SLOTS=8 must +measurably improve (by the real routed critical path, not synthesis +LUT counts), full regression must stay bit-exact, and the complete +8-seed matrix must be re-run and reported honestly either way. +DECISION: applied ERR-0029's fix (sdram_unified_backend.v's weight- +cache hit-index: serial priority-scan -> flat one-hot compare + single- +level priority encode). Re-ran the full real nextpnr-ecp5 32-run matrix +(4 configs x 8 seeds) post-fix: + N_SLOTS=4 @ 64MHz: 8/8 PASS (unchanged pass count), worst-case WNS + fell from +2.143ns to +0.605ns (worst seed Fmax 74.17->66.58MHz). + Real cause: the critical path relocated off the (shortened) hit- + index chain onto a different, previously-second-worst path in the + same module, which this seed set happens to route less favorably. + N_SLOTS=8 @ 64MHz: pass count improved 4/8 -> 5/8, worst-case Fmax + 55.84->60.12MHz, worst WNS -2.284ns->-1.009ns. + N_SLOTS=4/8 @ 80MHz: unchanged, still 0/8 both -- NO-GO confirmed + again on the fixed RTL. +ACCEPTED, not rejected: N_SLOTS=4 @ 64MHz remains a real 8/8 PASS +(all seeds have positive WNS -- no seed fails), which is this project's +own stated acceptance bar; the margin reduction is disclosed here and +in errors.log ERR-0029, not hidden. N_SLOTS=8 is a genuine, measured +improvement toward closure but is explicitly NOT declared fully closed +(3/8 seeds still fail, now via a still-different critical path -- a +further iteration, e.g. constraining nextpnr's placement region for +u_sdram_backend or exploring more seeds, remains a real OPEN item, not +attempted here). +Full regression re-verified after this fix: isolated +tb_sdram_unified_backend.v (40/40, bit-exact), full D-Stress N_SLOTS=4 +(49927 cycles, 256/256 neurons bit-exact vs golden) and N_SLOTS=8 +(49909 cycles, 256/256 neurons bit-exact vs golden) -- zero functional +regression at either processor count. +STATUS: N_SLOTS=4 @ 64MHz remains the frozen production baseline (8/8 +PASS, real positive margin on every seed). N_SLOTS=8 @ 64MHz OPEN, +improved but not closed (5/8 PASS). 80MHz NO-GO at either N_SLOTS, +reconfirmed on the fixed RTL. File changed: hardware/v2/nms/rtl/ +sdram_unified_backend.v. diff --git a/hardware/v2/logs/errors.log b/hardware/v2/logs/errors.log index 0775405..bee70d6 100644 --- a/hardware/v2/logs/errors.log +++ b/hardware/v2/logs/errors.log @@ -1375,3 +1375,91 @@ STATUS: PARTIALLY RESOLVED. N_SLOTS=4 fully reliable at 64MHz (8/8 seeds). N_SLOTS=8 significantly improved but OPEN -- 5/8 seeds close timing at 64MHz, 3/8 do not. File changed: hardware/v2/nms/rtl/ nms_activation_fill_ctrl_v3.v. + +NOTE (2026-09-07, re-measurement): a fresh, full 8-seed P&R matrix run +under ERR-0029's own investigation (below) found N_SLOTS=8 @ 64MHz +PRE-FIX pass count to actually be 4/8 (seeds 2,3,5,6 PASS; 0,1,4,7 +FAIL; worst seed1 55.84MHz/WNS=-2.284ns), not the 5/8 recorded above -- +the "5/8" figure came from an earlier, less exhaustive P&R pass and is +SUPERSEDED by this note and by MEMORY_UPGRADE_64MB_N8.md's own tables. +This does not change ERR-0028's fix or its STATUS, only the precise +pass-count evidence. + +ERR-0029 -- sdram_unified_backend.v: serial "last-match-wins" weight- +cache hit-index scan was the N_SLOTS=8 @ 64MHz critical path + +DATE: 2026-09-07 +FOUND DURING: the user's own mandated exhaustive final-verification +pass (all 8 seeds, 4 configs: N=4/N=8 x 64/80MHz) plus an explicit, +user-directed root-cause investigation into the N_SLOTS=8 @ 64MHz +worst-case critical path (real nextpnr-ecp5 P&R report.json, seed1: +Fmax=55.84MHz, WNS=-2.284ns, total delay 17.909ns, 84% routing). +ROOT CAUSE: `sdram_unified_backend.v`'s W-port cache (W_ENTRIES=4, +NOT parameterized by N_SLOTS -- confirmed by inspecting its only +instantiation, in nms_neural_multiprocessor_sdram_unified.v, which +overrides neither W_ENTRIES nor ADDR_WIDTH's dependents) computed its +hit index with a sequential `for (ei=0;ei nested PFUMX/OFX chain -> +w_hit_idx_c -> further LUT cascade -> control logic gating w_rdata's +own clock enable). Because W_ENTRIES stays fixed at 4 regardless of +N_SLOTS, this logic's OWN complexity does not grow with N_SLOTS -- the +real cause of the N=8-specific slowdown is that N=8's larger overall +design (double the slot_mem_arbiter_wide/weight_prefetch_engine_wide +instances of N=4) forces nextpnr to spread placement further across +the die, stretching the SAME serially-dependent logic chain over +longer physical routes: 3 of 5 major hops measured at 2.5-2.8ns each +(typical local ECP5 routing is 0.3-0.9ns), confirming routing +distance, not logic complexity or LUT/FF utilization (25.2%/25.4% at +N=8, far from the device ceiling), as the dominant cost -- exactly as +diagnosed from the real segment data before any fix was attempted. +FIX: replaced the serial priority-scan with a flat, fully parallel +one-hot compare (`w_match_oh[ei] = w_cache_valid[ei] && w_cache_addr +[ei]==w_addr`, W_ENTRIES independent comparators, no inter-entry +dependency, via a `generate`/`genvar` block) followed by a single- +level `casez` priority encode reproducing the EXACT original "highest +index wins" semantics bit-for-bit. Guarded behind `if (W_ENTRIES==4)` +(the only real, instantiated configuration in this project) with a +fallback branch preserving the original scan for any other W_ENTRIES +value, matching this project's own established fix pattern (ERR-0028). +Purely combinational, same-cycle, zero latency change -- w_ready/ +w_rdata timing behavior is externally unchanged. +VERIFICATION (bit-exact): isolated `tb_sdram_unified_backend.v`, real +Verilator 5.050, 40/40 PASS, identical to pre-fix. Full D-Stress +regression (real Verilator, golden-model comparison), both N_SLOTS=4 +(49927 cycles, 256/256 neurons bit-exact) and N_SLOTS=8 (49909 cycles, +256/256 neurons bit-exact) -- PASS, zero functional regression. +VERIFICATION (timing, real nextpnr-ecp5, fresh 32-seed matrix: N=4/N=8 +x 64/80MHz, 8 seeds each, POST-FIX): + N=4 @ 64MHz: 8/8 seeds PASS (unchanged pass count), but worst-case + margin DROPPED: Fmax 70.68/66.58/74.74/71.98/74.48/75.65/67.41/ + 68.47 MHz (worst seed1 66.58MHz, WNS=+0.605ns -- down from the + PRE-FIX worst of 74.17MHz/WNS=+2.143ns). Real cause: the FIX moved + the N=4 critical path OFF the (now-shortened) hit-index chain and + onto a DIFFERENT, previously-second-worst path inside the same + module (mostly u_arbiter_wide.m_addr -> u_sdram_backend.state/ + ctrl_wdata), which this specific placement seed happens to route + less favorably than the original hit-index path did. Still a real, + honest 8/8 PASS -- not a hardware concern by the mandate's own + numeric acceptance criteria -- but a disclosed, measured trade-off, + not hidden. + N=8 @ 64MHz: PASS COUNT IMPROVED, 4/8 -> 5/8 (seeds 0,1,3,4,5 PASS; + 2,6,7 FAIL). Worst-case improved: Fmax 66.45/65.28/61.21/66.96/ + 66.66/65.71/60.12/62.70 MHz (worst seed6: 60.12MHz, WNS=-1.009ns -- + up from the PRE-FIX worst of 55.84MHz/WNS=-2.284ns). Real, + measured improvement, not yet full closure. + N=4/N=8 @ 80MHz: unchanged conclusion, still 0/8 PASS both configs + (identical achieved-Fmax values to the 64MHz-labeled netlists, + confirming the fabric's achievable ceiling, not the PLL target, is + the limit) -- NO-GO, as before. +STATUS: N_SLOTS=8 measurably improved (not fully closed: 3/8 seeds +still fail at 64MHz, now failing through a DIFFERENT critical path than +before -- see MEMORY_UPGRADE_64MB_N8.md for the updated worst-case +trace). N_SLOTS=4 remains 8/8 PASS at 64MHz but with reduced margin, +disclosed above -- flagged, not hidden, per this project's own real- +data-only reporting standard. File changed: hardware/v2/nms/rtl/ +sdram_unified_backend.v. diff --git a/hardware/v2/nms/rtl/sdram_unified_backend.v b/hardware/v2/nms/rtl/sdram_unified_backend.v index 4c14bac..7ff5259 100644 --- a/hardware/v2/nms/rtl/sdram_unified_backend.v +++ b/hardware/v2/nms/rtl/sdram_unified_backend.v @@ -123,19 +123,64 @@ module sdram_unified_backend #( reg [63:0] w_cache_data [0:W_ENTRIES-1]; reg [WEIDXW-1:0] w_alloc_ptr; + // ERR-0029 fix (N=8 @64MHz critical-path, measured via real P&R: + // worst seed1 total delay 17.909ns, 84% routing, dominant hop + // 2.5-2.8ns): the original RTL used a sequential for-loop that + // overwrites w_hit_idx_c on every match ("last valid+matching entry + // wins"), which Yosys/nextpnr synthesized as a serially-dependent + // cascade of PFUMX/OFX fast-mux primitives -- each entry's result + // depends on the previous one, forcing nextpnr to place the whole + // chain along one physical path with no freedom to shorten it. This + // is the same architectural fix class as ERR-0028 (activation_fill_ + // ctrl's max-tree): replace the serial dependency chain with a flat + // one-hot compare (fully parallel, W_ENTRIES=4 comparators, no + // inter-entry dependency) followed by a single-level priority-encode + // casez, preserving the EXACT original "highest index wins" semantics + // bit-for-bit (verified: original loop always ends on the highest ei + // that matched, since ei counts up without break). + wire [W_ENTRIES-1:0] w_match_oh; + genvar wgi; + generate + for (wgi = 0; wgi < W_ENTRIES; wgi = wgi + 1) begin : GEN_WMATCH + assign w_match_oh[wgi] = w_cache_valid[wgi] && (w_cache_addr[wgi] == w_addr); + end + endgenerate + reg w_hit_found_c; reg [WEIDXW-1:0] w_hit_idx_c; integer ei; - always @(*) begin - w_hit_found_c = 1'b0; - w_hit_idx_c = {WEIDXW{1'b0}}; - for (ei = 0; ei < W_ENTRIES; ei = ei + 1) begin - if (w_cache_valid[ei] && w_cache_addr[ei] == w_addr) begin - w_hit_found_c = 1'b1; - w_hit_idx_c = ei[WEIDXW-1:0]; + generate + if (W_ENTRIES == 4) begin : GEN_WHIT_FLAT + // real, measured configuration (see ERR-0029) -- flat, + // single-level priority encode over the parallel one-hot + // compare above, no serial inter-entry dependency. + always @(*) begin + w_hit_found_c = |w_match_oh; + casez (w_match_oh) + 4'b1???: w_hit_idx_c = 2'd3; + 4'b01??: w_hit_idx_c = 2'd2; + 4'b001?: w_hit_idx_c = 2'd1; + 4'b0001: w_hit_idx_c = 2'd0; + default: w_hit_idx_c = {WEIDXW{1'b0}}; + endcase + end + end else begin : GEN_WHIT_FALLBACK + // any other W_ENTRIES value: fall back to the original, + // functionally-equivalent (but serially-dependent) scan -- + // not the measured/optimized configuration this project + // actually builds, kept only for parametric safety. + always @(*) begin + w_hit_found_c = 1'b0; + w_hit_idx_c = {WEIDXW{1'b0}}; + for (ei = 0; ei < W_ENTRIES; ei = ei + 1) begin + if (w_cache_valid[ei] && w_cache_addr[ei] == w_addr) begin + w_hit_found_c = 1'b1; + w_hit_idx_c = ei[WEIDXW-1:0]; + end + end end end - end + endgenerate wire w_cache_hit = w_hit_found_c && w_req; // ============================================================