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:
2026-09-20 10:59:45 +02:00
co-authored by Claude Sonnet 5
parent cbd16dd727
commit fa327b75ca
6 changed files with 801 additions and 121 deletions
+128
View File
@@ -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.