feat: real hierarchical 2-level arbiter, real N=16 timing WNS -0.913ns -> -0.646ns (EXP-0094)
sdram_arbiter_hier.v: fixes EXP-0093's own real, traced P&R timing failure (flat 21-way req_wdata mux, route-delay-dominated). Reuses sdram_arbiter_n.v unmodified, twice: 4 leaf instances (NUM_REQ=5, one per group) + 1 top instance (NUM_REQ=5: 4 groups + host, host bypassed/unpipelined), one real pipeline register stage between levels. Isolated verification (tb_sdram_arbiter_hier.v): 23/23 PASS. Two real bugs found and fixed via signal tracing: a testbench helper not waiting for grant before firing req, and a genuine RTL lost-pulse bug at the leaf-to-top boundary (a transient one-shot request could be dropped if the top level was busy with a different group) -- fixed with a sticky per-group pending_req_r latch. Wired into n16_system_ddr3_top.v (drop-in). Real, full P&R re-run: WNS improved -0.913ns -> -0.646ns, TNS -690ns -> -97.5ns, failing endpoints 3021 -> 771 -- substantial, measured improvement, confirming the arbiter was correctly root-caused (bottleneck moved elsewhere: neural_processor_packed.v's own already-thin-margin MAC datapath, eroded by N=16's higher overall congestion). Functional xsim still 32/32 PASS. Timing not yet fully closed -- real next steps documented, not yet attempted without further direction. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -6397,3 +6397,193 @@ N=16 variant specifically (a real, measured tradeoff against N=2's own
|
||||
real throughput, not yet quantified). Real signal-tracing already done
|
||||
above narrows the fix to the arbiter's own write-data mux specifically
|
||||
-- future work should start there, not guess elsewhere in the design.
|
||||
|
||||
EXP-0094 -- real, hierarchical 2-level arbiter (sdram_arbiter_hier.v):
|
||||
real fix for EXP-0093's own real timing failure, isolated verification
|
||||
23/23 PASS (2026-09-21, user's own explicit direction: "sarei tentato
|
||||
per un arbiter a due livelli... secondo te funziona meglio?", confirmed
|
||||
"ok ora cerca di terminare il lavoro")
|
||||
|
||||
CONTEXT: EXP-0093's own real, traced root cause: sdram_arbiter_n.v's
|
||||
own flat req_wdata/req_addr mux grew from 3-way (N=2) to real 20/21-way
|
||||
(N=16) over a 256-bit bus, dominated by ROUTE delay (73% of the real
|
||||
critical path) -- a real PHYSICAL fan-in/placement problem (20
|
||||
separately-placed sources converging on one central mux), not primarily
|
||||
a logic-depth one.
|
||||
|
||||
METHOD: `sdram_arbiter_hier.v` (NEW), reusing `sdram_arbiter_n.v`
|
||||
UNMODIFIED, twice, hierarchically -- 4 real LEAF instances (NUM_REQ=5:
|
||||
1 weight-fetch + 4 PEs per group, physically local to their own real
|
||||
systolic_group.v) + 1 real TOP instance (NUM_REQ=5: 4 groups' own
|
||||
pipelined output + 1 host_mem_bridge.v, deliberately UNPIPELINED/
|
||||
bypassed since it was never the reported critical path -- see the
|
||||
file's own header for the full real design rationale). ONE real
|
||||
pipeline register stage, both directions, between the two levels --
|
||||
the actual real fix for the route-delay-dominated critical path, not
|
||||
just a logic restructuring. Fork-before-promote: `sdram_arbiter_n.v`
|
||||
itself untouched, N=2's own real EXP-0088 signoff unaffected.
|
||||
|
||||
REAL CORRECTNESS ARGUMENT (derived, not asserted): every real requester
|
||||
in this project (`act_tile_fetch.v`, `layer_prefetch_ctrl.v`,
|
||||
`host_mem_bridge.v`) already keeps its own `mem_active` asserted for
|
||||
the FULL duration of its own outstanding transaction -- the same real
|
||||
invariant `sdram_arbiter_n.v`'s own `locked` state already relies on
|
||||
for transactions spanning many real DDR3-latency cycles today. The
|
||||
added real pipeline latency (GROUP-sourced traffic only; host bypasses
|
||||
it entirely) is indistinguishable, from any requester's own point of
|
||||
view, from "DDR3 was slightly slower this time" -- confirmed, not
|
||||
assumed, via real isolated verification below. EXP-0066's own real
|
||||
"own grant same cycle as own active" requirement is preserved EXACTLY
|
||||
for all 21 real requesters (weight-fetch/PE via the leaf's own
|
||||
unmodified combinational grant; host via the top's own direct
|
||||
combinational grant) -- only the underlying ctrl_req/wdata/etc
|
||||
reaching mig_native_adapter.v is pipelined, never the grant signal a
|
||||
real requester actually polls.
|
||||
|
||||
REAL BUGS FOUND AND FIXED VIA SIGNAL TRACING BEFORE A TRUSTWORTHY
|
||||
RESULT WAS POSSIBLE (per this project's own "root-cause every anomaly,
|
||||
never guess" discipline):
|
||||
1. New isolated testbench `tb_sdram_arbiter_hier.v` (21-way, N_GROUPS=4/
|
||||
PES_PER_GROUP=4/+1 host, real `burst_mem_model32.v` mock controller,
|
||||
same real methodology as `tb_sdram_arbiter_n.v`) had NO real
|
||||
watchdog -- a real protocol bug spun the real Icarus sim forever
|
||||
(99% CPU, zero output) instead of failing cleanly. Added a real
|
||||
500000ns watchdog (a NEW, generalizable gap: every OTHER testbench
|
||||
in this project already has a `wd`-counted watchdog inside its own
|
||||
completion-wait loop; this one, copied from a simpler precedent,
|
||||
didn't). Also hit, mid-debugging, a real false alarm from the
|
||||
user's own accidental Ctrl-C of a *different*, harmless foreground
|
||||
`vvp` run -- confirmed via `git status`/`ps -ef` that nothing was
|
||||
actually lost before continuing.
|
||||
2. Testbench bug (NOT RTL): `one_shot_txn`'s own original version,
|
||||
copied verbatim from `tb_sdram_arbiter_n.v`, fired `req_req` the
|
||||
SAME cycle as `req_active`, unconditionally, without first
|
||||
checking `req_grant` -- real, traced failure mode: this module's
|
||||
own real extra lock-release lag (1-2 cycles, after a PRIOR
|
||||
transaction on a DIFFERENT slot completes) can race a same-cycle
|
||||
blind fire, losing the pulse. Confirmed, via `act_tile_fetch.v`'s
|
||||
own real S_MEMWAIT state ("ctrl_req is only issued after mem_grant
|
||||
is observed, never blind"), that REAL requesters in this project
|
||||
already wait for grant before firing req -- fixed the testbench
|
||||
helper to match real behavior (and this file's own already-correct
|
||||
`concurrent_contention` task), not the RTL.
|
||||
3. REAL RTL BUG (the actual fix this experiment exists to deliver):
|
||||
`leaf_ctrl_req` is a TRANSIENT one-shot pulse (mirrors a real
|
||||
requester's own one-cycle `ctrl_req`) -- if the TOP level is still
|
||||
busy servicing a DIFFERENT group at the exact cycle it fires, a
|
||||
bare "register leaf_ctrl_req every cycle" pipeline (the first,
|
||||
broken version of this file) lets the pulse revert to 0 and be
|
||||
silently lost before TOP ever gets to it -- the EXACT EXP-0066
|
||||
lost-pulse class, newly exposed at the leaf-to-top boundary
|
||||
because a leaf's own LOCAL grant does not guarantee the top level
|
||||
is free to act on it the same cycle (unlike the flat, single-level
|
||||
arbiter, where the winning requester's own grant and the physical
|
||||
controller's own readiness to capture it are the SAME decision).
|
||||
Root-caused via real $monitor signal tracing of TEST 3 (cross-
|
||||
group contention: all 4 groups' own weight-fetch simultaneously) --
|
||||
traced `u_top.locked`/`grant_idx_r` staying locked onto group 0
|
||||
for ~125000fs (~8 real cycles, DDR3-model latency) while groups
|
||||
1-3's own already-captured `top_req_req_r` bits had already
|
||||
reverted to 0. Fixed with a real, sticky per-group `pending_req_r`
|
||||
latch: SET the cycle `leaf_ctrl_req` first pulses, CLEARED only
|
||||
once `top_req_grant && top_req_req` both confirm real dispatch for
|
||||
that group -- `(pending_req_r | leaf_ctrl_req)` feeds the outgoing
|
||||
pipeline register instead of the bare transient signal. addr/wr/
|
||||
wdata/wmask do NOT need the same treatment (the leaf stays locked
|
||||
onto the SAME real requester for its whole transaction, so those
|
||||
fields are already stable throughout).
|
||||
|
||||
REAL RESULT (isolated, `tb_sdram_arbiter_hier.v`, real Icarus xsim):
|
||||
**23/23 PASS, 0 errors** -- sequential single-requester across weight-
|
||||
fetch/PE/host slots in all 4 groups, WITHIN-group contention (leaf-
|
||||
level, group 1's weight-fetch + all 4 PEs simultaneously), CROSS-group
|
||||
contention (top-level, all 4 groups' own weight-fetch simultaneously),
|
||||
and full worst-case mixed contention (one PE from each of the 4 groups
|
||||
+ host, all simultaneously) -- every real response verified bit-exact
|
||||
back to its own correct requester, no misrouting through either
|
||||
arbiter level.
|
||||
|
||||
Wired into `n16_system_ddr3_top.v` (drop-in replacement, same real
|
||||
external port shape -- only the module name/parameters at the
|
||||
instantiation site changed) and into `tb_n16_system_ddr3.v` (host slot
|
||||
tied off inactive, matching the real topology exactly).
|
||||
|
||||
DECISION: the real, isolated arbiter fix is verified correct. Re-
|
||||
running EXP-0092's own real functional DDR3 xsim (whole-system,
|
||||
`tb_n16_system_ddr3.v`) next, to confirm the fix holds wired into the
|
||||
full system before re-attempting real P&R.
|
||||
|
||||
next_action: (1) real functional xsim of the whole N=16 system with
|
||||
the new hierarchical arbiter (re-run of EXP-0092's own test). (2) if
|
||||
that passes, real, full P&R (place_design+route_design) to check
|
||||
whether WNS actually closes now.
|
||||
|
||||
EXP-0094 (continued) -- real functional re-verification + real, full
|
||||
P&R with the hierarchical arbiter wired in: SUBSTANTIAL real
|
||||
improvement, root cause demonstrably shifted, timing STILL not fully
|
||||
closed (2026-09-21)
|
||||
|
||||
METHOD: (1) real functional xsim, `tb_n16_system_ddr3.v` updated to
|
||||
instantiate `sdram_arbiter_hier.v` (host slot tied off inactive,
|
||||
matching the real topology) instead of the flat `sdram_arbiter_n.v`,
|
||||
same real DDR3-model methodology as EXP-0092. (2) real, full P&R
|
||||
(synth_design+opt_design+place_design+route_design) against the real
|
||||
XC7A100T-CSG324-2 part, `n16_system_ddr3_top.v` updated to instantiate
|
||||
`sdram_arbiter_hier.v` in place of the flat arbiter (drop-in, same
|
||||
external port shape). `sdram_arbiter_hier.v` added to the real Vivado
|
||||
project's own `sources_1` fileset as a direct reference (confirmed via
|
||||
a real stale-import check before trusting the result: empty, no
|
||||
`*/imports/*` copies of anything in the real dependency chain).
|
||||
|
||||
REAL RESULT (1), functional: **32/32 PASS, 0 errors**, `$finish` at
|
||||
197735.6335ns -- the hierarchical arbiter fix does not break real
|
||||
functional correctness at full N=16 scale.
|
||||
|
||||
REAL RESULT (2), P&R: real post-route utilization essentially
|
||||
unchanged from EXP-0093 (19902 LUTs/31.39%, 35395 regs/27.91%, 128
|
||||
DSP48E1/53.33%). **Real timing: WNS=-0.646ns (up from EXP-0093's real
|
||||
-0.913ns), TNS=-97.541ns (up from -690.085ns, ~7x fewer), 771 failing
|
||||
setup endpoints (down from 3021, ~4x fewer)** -- a real, substantial,
|
||||
measured improvement in the RIGHT direction, but timing constraints
|
||||
are STILL NOT MET.
|
||||
|
||||
REAL, HONEST NEW FINDING (traced via the actual new worst violated
|
||||
path, not guessed): the bottleneck has DEMONSTRABLY SHIFTED away from
|
||||
the arbiter entirely. The new worst path runs from `neural_processor_
|
||||
packed.v`'s own `GEN_MAC_PACKED[4].product` (a real DSP48E1 MAC output,
|
||||
inside group 3 / PE 2's own compute core) to its own `prodb1_reg[4][13]`
|
||||
-- a path dominated by LOGIC delay (79%, CARRY4=4/LUT2=1), NOT route,
|
||||
unlike EXP-0093's own arbiter-driven, route-dominated (73%) violation.
|
||||
This is a PRE-EXISTING module, byte-for-byte unchanged since N=2 (EXP-
|
||||
0088), where it closed with an already razor-thin real margin
|
||||
(WNS=+0.099962ns). Real, coherent interpretation (not asserted without
|
||||
the evidence above): N=16's real overall die utilization (31% LUT vs
|
||||
N=2's much lower level) increases general placement/routing congestion
|
||||
enough, on its own, to erode that already-thin pre-existing margin --
|
||||
a DIFFERENT, more diffuse, congestion-driven problem than the
|
||||
arbiter's own single, structural fan-in bottleneck, without an
|
||||
equally obvious single-point fix.
|
||||
|
||||
DECISION: the hierarchical arbiter (EXP-0094) is a real, verified,
|
||||
substantial improvement, kept and committed regardless of whether full
|
||||
N=16 timing closure is reached -- it fixed the exact problem it was
|
||||
built for (confirmed by the bottleneck moving elsewhere entirely).
|
||||
Full N=16 timing closure needs a SEPARATE, further real intervention
|
||||
(likely touching `neural_processor_packed.v` itself, the compute core
|
||||
shared by every real PE in this project, or accepting a lower target
|
||||
clock for the N=16 variant specifically) -- a real engineering decision
|
||||
better made with the user's own direction than guessed at further
|
||||
unprompted, given `neural_processor_packed.v`'s own central, shared
|
||||
role across the whole project.
|
||||
|
||||
next_action: real options for closing the REMAINING gap, not yet
|
||||
attempted, to discuss before committing more RTL effort: (1) real
|
||||
pipelining inside `neural_processor_packed.v`'s own MAC datapath at the
|
||||
specific `GEN_MAC_PACKED`/`prodb1_reg` boundary (fork-before-promote,
|
||||
same discipline as the arbiter fix -- N=2's own real signoff must stay
|
||||
protected). (2) a real, measured lower target clock for the N=16
|
||||
variant specifically (quantify the real throughput trade-off against
|
||||
N=2, not yet done). (3) real Vivado placement/timing directives
|
||||
(e.g. `-directive` on `place_design`/`route_design`, or a `PBLOCK`
|
||||
constraint per group to reduce congestion) as a lower-RTL-risk first
|
||||
attempt before touching `neural_processor_packed.v` itself.
|
||||
|
||||
Reference in New Issue
Block a user