feat: DDRManager phase 1 - single-slot look-ahead activation prefetch (EXP-0083)
New ddr_prefetch_mgr.v wraps act_tile_fetch.v with a depth-2 ping-pong buffer, issuing the next tile's DDR3 fetch as soon as the fetch engine is free instead of waiting for packed_slot.v to finish consuming the current tile. Wired into packed_slot.v's tile loop (job-level start instead of per-tile req), simplifying the S_TILEWAIT join in the process (ddrpf_tile_valid is level-held, no separate act_seen latch needed). Verification: new tb_ddr_prefetch_mgr.v (25/25 PASS after fixing a real testbench polling race found via iteration-tagged tracing, not an RTL bug), tb_packed_slot.v re-run unmodified (9/9 PASS, bit-identical results), tb_n2_system_ddr3.v re-run via real xsim against real ddr3_model.sv (8/8 PASS). Real P&R: WNS +0.073ns (up from EXP-0082's +0.068ns), LUTs 5644, DSP48E1 16 unchanged, 0 failing endpoints. Honest result: real A/B on the actual DDR3 backend (same testbench, before/after) shows a real but modest 2.86% reduction in total simulated time - smaller than the original hypothesis suggested, because neural_processor_packed.v already accepts one operand per cycle, so the per-tile dead time being removed was already small relative to real DDR3 fetch latency. Docs updated to report this honestly rather than oversell it; the larger multi-slot DDRManager is deferred pending re-measurement against the (still pending, user-gated) 32-bit channel widening. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -5202,3 +5202,131 @@ place -- user's own explicit framing: N=8 is the realistic target,
|
||||
N=16 is being built specifically to document where/how it breaks
|
||||
(real data for the analysis, not assumed to be a viable deployment
|
||||
point).
|
||||
|
||||
EXP-0083 -- DDRManager phase 1: single-slot look-ahead activation
|
||||
prefetch, real modest benefit measured honestly (2026-09-20, same
|
||||
autonomous continuation, user's own direction: "cerchiamo di spremere
|
||||
al massimo il timing con una gestione intelligente della memoria (un
|
||||
DDRManager ... che sia evoluto e completo)")
|
||||
|
||||
CONTEXT: docs/ARCHITECTURE_ANALYSIS.md S5.2 laid out a validation plan
|
||||
for the user's own proposed DDRManager idea (orchestrator "prenota" future
|
||||
DDR3 reads ahead of demand) -- build a minimal single-slot activation
|
||||
look-ahead prototype FIRST, get a real measured stall-reduction number,
|
||||
before attempting the full multi-slot/whole-Director-queue scheduler.
|
||||
This experiment is that phase-1 deliverable.
|
||||
|
||||
DESIGN: new module `ddr_prefetch_mgr.v` wraps `act_tile_fetch.v`
|
||||
(unmodified, reused as the "fetch exactly one tile" engine) with a
|
||||
depth-2 ping-pong buffer. Instead of packed_slot.v issuing one req/wait/
|
||||
consume cycle per tile (old EXP-0079/0081 sequencing), the whole job's
|
||||
tile loop is now driven from a single job-level `job_start` pulse into
|
||||
ddr_prefetch_mgr.v, which issues tile N+1's fetch the INSTANT the fetch
|
||||
engine is free (not waiting for packed_slot.v to finish consuming tile
|
||||
N) -- overlapping "fetch next tile" with "consume current tile". Depth 2
|
||||
is provably sufficient (fetch can be at most 1 tile ahead of consume, by
|
||||
construction of the `can_issue` guard). Bank selection on both the fill
|
||||
and read side uses a REGISTERED index bit (fetch_idx[0]/consume_idx[0]),
|
||||
same "select known long before the data it gates" discipline
|
||||
act_tile_fetch.v's own EXP-0081 header established as timing-safe.
|
||||
packed_slot.v's S_TILEWAIT join simplified as a side effect: ddrpf_tile_
|
||||
valid is LEVEL-held (unlike the old one-cycle act_valid pulse), so the
|
||||
separate act_seen latch is no longer needed.
|
||||
|
||||
VERIFICATION (same 3-level discipline as EXP-0079/0081):
|
||||
1. tb_ddr_prefetch_mgr.v (NEW, isolated, real SDR placeholder backend,
|
||||
same precedent as tb_act_tile_fetch.v): found and fixed a REAL
|
||||
TESTBENCH RACE during bring-up, not an RTL bug -- the per-tile poll
|
||||
loop was re-checking `pf_tile_valid` in the same simulation delta as
|
||||
the DUT's own nonblocking update for the PREVIOUS tile_consume
|
||||
pulse (both triggered off the same `@(posedge clk)`), reading
|
||||
pre-update state. Root-caused via an iteration-tagged $display trace
|
||||
(k=1 was silently reading k=0's still-unconsumed data) -- NOT found
|
||||
by inspection, exactly this project's own standing "root-cause via
|
||||
signal tracing" discipline. Fixed with a `#1` settle delay before
|
||||
each poll. After the fix: 25/25 PASS, 0 errors, including a real
|
||||
A/B cycle-count comparison against the OLD per-tile req/wait/
|
||||
consume loop (same backend, same preloaded data, same simulated
|
||||
2-cycle compute overhead applied to BOTH loops for fairness):
|
||||
- row-switch-heavy (3 different burst pairs, 6 tiles): baseline
|
||||
216 cycles vs prefetch 214 cycles = 0.9% real reduction.
|
||||
- same-row best case (2 tiles, single burst pair, isolating the
|
||||
look-ahead benefit from row-switch cost): baseline 72 cycles vs
|
||||
prefetch 73 cycles = -1.4% (real measured, i.e. NOT faster) --
|
||||
this SDR placeholder backend's own per-fetch latency (~36
|
||||
cycles/tile in both scenarios, row-switching or not) is
|
||||
dominated by a near-fixed protocol/timing-model cost, not by
|
||||
real row/bank locality the way the actual DDR3 controller is --
|
||||
so this specific backend does not exercise the scenario where
|
||||
look-ahead would show its largest benefit. Reported as measured,
|
||||
not hidden.
|
||||
2. tb_packed_slot.v -- re-run unmodified (external packed_slot.v
|
||||
interface didn't change). 9/9 PASS, numeric results bit-identical
|
||||
to EXP-0081's own run -- confirms zero effect on computed results,
|
||||
purely an internal timing/sequencing change.
|
||||
3. tb_n2_system_ddr3.v -- re-run via real xsim against the real
|
||||
ddr3_model.sv (fresh Vivado project source add: ddr_prefetch_mgr.v
|
||||
added as a direct, non-copied reference, same pattern as act_tile_
|
||||
fetch.v/packed_slot.v -- avoids the stale-import class of bug from
|
||||
the start rather than needing a later fix). 8/8 PASS, 0 errors,
|
||||
8/8 positions completed, results bit-identical in shape to EXP-0081
|
||||
(result=0/127 alternating pattern, testbench's own expected-value
|
||||
checks all passed). REAL, HONEST, apples-to-apples total-simulated-
|
||||
time comparison against EXP-0081's own preserved real xsim run
|
||||
(mig_sim3, same testbench, same real ddr3_model.sv, same N=2/8-
|
||||
position workload, only packed_slot.v's internal activation-fetch
|
||||
sequencing differs):
|
||||
EXP-0081 (no prefetch mgr): $finish at 108370.8835 ns
|
||||
EXP-0083 (with prefetch mgr): $finish at 105268.4335 ns
|
||||
-> 2.86% real reduction in total simulated time.
|
||||
This is the real, trustworthy headline number for this experiment
|
||||
-- modest, not transformative, and reported as such.
|
||||
|
||||
REAL P&R (fresh synth_1 + impl_1, xc7a100tcsg324-2, ddr_prefetch_mgr.v
|
||||
added to the project fileset as a direct source, same as act_tile_
|
||||
fetch.v/packed_slot.v):
|
||||
WNS = +0.073ns (UP slightly from EXP-0082's +0.068ns)
|
||||
WHS = +0.036ns
|
||||
Failing endpoints: 0/21065 (setup), 0/21062 (hold)
|
||||
Slice LUTs = 5644 (up from EXP-0082's 5437, +207 for the new module's
|
||||
ping-pong buffer + sequencing FSM)
|
||||
DSP48E1 = 16 (unchanged since EXP-0059 -- confirms again all real
|
||||
margin pressure in this project comes from control/glue logic, never
|
||||
the compute datapath)
|
||||
Route: 100%, 0 errors. All user specified timing constraints are met.
|
||||
|
||||
HONEST ASSESSMENT (per the user's own explicit "critica, non
|
||||
accondiscendente" standard): this phase-1 DDRManager delivers a real,
|
||||
verified, but genuinely MODEST benefit (~2.9% on the real system test),
|
||||
not the larger improvement a naive read of "look-ahead prefetching"
|
||||
might suggest. Root cause, confirmed by this experiment's own data:
|
||||
neural_processor_packed.v's own pipeline accepts one operand PER CYCLE
|
||||
once in NP_WAIT_OPERANDS (operand_ready is state-only, not gated on any
|
||||
internal pipeline stall) -- so the real per-tile "dead time" this module
|
||||
removes (the old design's serialized request/consume handshake) was
|
||||
already small relative to the real DDR3 fetch latency itself (dominated
|
||||
by row activation/precharge, per docs/ARCHITECTURE_ANALYSIS.md S3.3).
|
||||
This CONFIRMS, with real data, what docs/ARCHITECTURE_ANALYSIS.md S5.2
|
||||
already flagged going in: this optimization hides latency, it does not
|
||||
raise the physical DDR3 bandwidth ceiling (S5.1/S5.4 do that). It is
|
||||
real, free (no timing cost -- margin improved), and a correct building
|
||||
block, but the 32-bit channel widening (S5.4, user-decided, pending the
|
||||
user's own MIG wizard session) remains the higher-leverage next step for
|
||||
real throughput, not further investment in latency-hiding alone.
|
||||
|
||||
DECISION: keep this change (real, verified, zero timing cost, modest but
|
||||
genuine benefit, and it establishes the DDRManager pattern the user
|
||||
asked for). Do NOT present it as a bigger win than measured. The full
|
||||
multi-slot/whole-Director-queue scheduler version (S5.2's larger design
|
||||
sketch) is NOT built here -- per the user's own confirmed validation-
|
||||
first approach, and because this phase-1 result suggests the larger
|
||||
version's ROI should be re-examined against the 32-bit-widened channel's
|
||||
real numbers first, not assumed.
|
||||
|
||||
next_action: (1) user's own real MIG wizard session (Data Width 16->32 +
|
||||
Input Clock Period, S5.4, still pending); (2) once that lands, re-measure
|
||||
this SAME real A/B (tb_n2_system_ddr3.v total simulated time, with vs
|
||||
without ddr_prefetch_mgr) against the wider channel to see whether look-
|
||||
ahead's real benefit grows once the physical ceiling is higher; (3) build
|
||||
the result-writeback engine (S5.3, still the real blocker for N>2); (4)
|
||||
real N=2/4/8/16 scaling tests per the user's own final directive.
|
||||
|
||||
Reference in New Issue
Block a user