prod: promote EXP-0056 fixes into production RTL (dependency_manager.v, nms_activation_fill_ctrl_v3.v)

Per explicit instruction: when a bug/fix found in an experimental fork
also applies to the production file it was forked from, apply it
there too, not just in the fork.

dependency_manager.v: swaps in priority_encoder_lsb.v for the
first_ready_idx scan (was: serial O(N_NODES) for-loop). Bit-exact
equivalent, correctness-neutral by construction.

nms_activation_fill_ctrl_v3.v: adds the missing N_SLOTS==16 balanced
max-tree case (was: silently falling back to the slow flat scan for
any N_SLOTS not in {1,2,4,8}) -- this was the real cause of N_SLOTS=16
failing timing closure (23-24MHz vs 64MHz target), fixed to 71.01MHz
PASS in the experimental fork.

Verified on the REAL, unmodified production top (fpga_neural_v2_top.v,
N_SLOTS=4 default): tb_dependency_manager.v 4/4 PASS, board smoke test
11/11 PASS, D-Stress N=4 total_cycles=49927 (bit-exact, IDENTICAL to
the pre-fix baseline -- zero functional regression, as expected from a
pure combinational-depth change). Real nextpnr-ecp5 P&R (LFE5U-45F,
seed 1): 97.36MHz, PASS at 64MHz -- BETTER margin than the pre-fix
baseline's own 76.80-88.25MHz seed range, not just neutral.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
2026-09-16 12:29:52 +02:00
co-authored by Claude Sonnet 5
parent 49b25f6eb6
commit ca94083366
2 changed files with 59 additions and 14 deletions
+29 -14
View File
@@ -102,21 +102,36 @@ module dependency_manager #(
// therefore cover the full id range a caller intends to use.
assign reg_ready = (node_state[reg_node_id] == ST_EMPTY);
// ---- priority-encoded first READY node (first-found scan, same
// idiom as neural_director's own free-slot scan) ----
reg [NODE_IDW-1:0] first_ready_idx;
reg any_ready;
integer ri;
always @(*) begin
first_ready_idx = {NODE_IDW{1'b0}};
any_ready = 1'b0;
for (ri = N_NODES-1; ri >= 0; ri = ri - 1) begin
if (node_state[ri] == ST_READY) begin
first_ready_idx = ri[NODE_IDW-1:0];
any_ready = 1'b1;
end
// ---- EXP-0056: priority-encoded first READY node, via a
// recursive binary-tree lowest-set-bit encoder (O(log2(N_NODES))
// depth) instead of the original serial for-loop scan (O(N_NODES)
// depth, the same architectural anti-pattern already fixed twice
// elsewhere in this project -- ERR-0027/ERR-0028/ERR-0029).
// Promoted from dependency_manager_fast.v (experimental fork)
// after that fork's own fix was verified bit-exact equivalent to
// this original scan (isolated: 65536/65536 exhaustive at
// WIDTH=16, 76562/76562 at WIDTH=1024; 20000/20000 cycles matched
// under random stimulus against this exact module, plus the
// original hand-crafted DAG testbench, both 100% -- see
// priority_encoder_lsb.v's own header and experiments.log
// EXP-0056). Correctness-neutral by construction (shorter
// combinational depth is never worse); NOTE this was NOT the real
// N_SLOTS=16 timing bottleneck (that was nms_activation_fill_
// ctrl_v3.v's own missing N==16 case, see that file) -- kept here
// as a real, disclosed, strictly-better improvement regardless. ----
wire [N_NODES-1:0] ready_oh;
generate
genvar gi;
for (gi = 0; gi < N_NODES; gi = gi + 1) begin : GEN_READY_OH
assign ready_oh[gi] = (node_state[gi] == ST_READY);
end
end
endgenerate
wire [NODE_IDW-1:0] first_ready_idx;
wire any_ready;
priority_encoder_lsb #(.WIDTH(N_NODES)) u_ready_penc (
.in(ready_oh), .idx(first_ready_idx), .valid(any_ready)
);
// ---- FPGA_DATA_READY support (see any_pending port comment above).
// Originally a combinational OR-reduce over node_state[0:N_NODES-1]