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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013xXuuRUWZScuo1DeYJxs3v
This commit is contained in:
@@ -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;
|
||||
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user