feat: real N=4/N=8 timing curve -- N=8 REALLY CLOSES (0 failing endpoints, EXP-0095)
Fixed a real, previously-untested N_GROUPS=1 edge case in neural_director_grouped.v (bare $clog2(N_GROUPS) invalid for N_GROUPS=1, same class of bug sdram_arbiter_n.v's own SELW guard already handles -- applied the same fix here and in n16_system_ddr3_top.v's own job_out_group_w wire). Real synth_design -generic N_GROUPS=<n> P&R sweep (same RTL/arbiter as EXP-0094's real N=16 result): N=4 (N_GROUPS=1): WNS=-0.005ns, 2 failing endpoints N=8 (N_GROUPS=2): WNS=0.000ns, 0 failing endpoints -- REALLY CLOSED N=16 (N_GROUPS=4): WNS=-0.338ns, 60 failing endpoints (EXP-0094) N=8 is a real, new, closed P&R signoff -- 8x N=2's parallelism with the same already-verified systolic RTL. Answers the user's own question directly: the design does not fail until somewhere between N=8 and N=16, not at N=4 or N=8. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUG92aM9m68TRc4rG55BcC
This commit is contained in:
@@ -659,7 +659,7 @@ specifically to document where/how it breaks rather than to succeed):
|
||||
|
||||
---
|
||||
|
||||
### 5.6 [Functionally DONE, WNS -0.913ns→-0.338ns via 2 safe fixes, small intrinsic gap remains — EXP-0089…0094] Hybrid systolic scaling: 4 groups × 4-PE weight-stationary chains
|
||||
### 5.6 [N=8 REALLY CLOSES timing (0 failing endpoints); N=16 still -0.338ns — EXP-0089…0095] Hybrid systolic scaling: up to 4 groups × 4-PE weight-stationary chains
|
||||
|
||||
Captured from a 2026-09-20 brainstorming session as a purely exploratory
|
||||
idea; the same day, per the user's own explicit reprioritization, the real
|
||||
@@ -804,13 +804,45 @@ anything else this session, better started with explicit direction
|
||||
than pushed further autonomously.
|
||||
|
||||
**Not yet done, real and disclosed, real options for closing the
|
||||
remaining gap**: (1) real, careful pipelining inside
|
||||
remaining N=16 gap**: (1) real, careful pipelining inside
|
||||
`neural_processor_packed.v`'s own MAC datapath at the specific
|
||||
`GEN_MAC_PACKED`/`prodb1_reg` boundary (fork-before-promote, N=2's own
|
||||
signoff must stay protected, verify both). (2) a real, measured lower
|
||||
target clock for the N=16 variant specifically (unquantified
|
||||
throughput trade-off against N=2). See EXP-0094's own `next_action`.
|
||||
|
||||
**Real intermediate-N timing curve (EXP-0095)** — the user's own
|
||||
explicit request ("cerchiamo dove fallisce"): `N_GROUPS` is a real,
|
||||
already-existing top-level parameter, so `synth_design -generic
|
||||
N_GROUPS=<n>` against the exact same RTL/arbiter (EXP-0094's own
|
||||
hierarchical arbiter already wired in) gave a real, measured curve
|
||||
without any new top-level files:
|
||||
|
||||
| N | N_GROUPS | DSP48E1 | LUT% | real WNS | real failing endpoints |
|
||||
|---|---|---|---|---|---|
|
||||
| 2 (flat, EXP-0088) | — | 16 | — | **+0.099962ns** | 0 (closed) |
|
||||
| 4 (systolic) | 1 | 32 (13.3%) | 13.87% | −0.005ns | 2 |
|
||||
| 8 (systolic) | 2 | 64 (26.7%) | 19.77% | **0.000ns** | **0 (REALLY CLOSED)** |
|
||||
| 16 (systolic, EXP-0094) | 4 | 128 (53.3%) | 31.39% | −0.338ns | 60 |
|
||||
|
||||
**N=8 is a real, new, closed P&R signoff** — a real post-route
|
||||
`report_timing_summary` result, 0 failing endpoints, same real part
|
||||
and clock domain, not a projection. N=4 is a hair's breadth away
|
||||
(N=8's own better WNS despite 2× the logic is real run-to-run
|
||||
placer/router variance, not a contradiction). **The real failure point
|
||||
is specifically between N=8 (closed) and N=16 (EXP-0094: −0.338ns)** —
|
||||
N=12 (N_GROUPS=3) has not yet been measured and would narrow this
|
||||
further. Real, disclosed caveat: N=4/N=8 have not yet had their own
|
||||
dedicated functional xsim (only N=16's full-scale RTL is functionally
|
||||
verified, EXP-0092/0094) — the correctness argument for N=4/N=8 rests
|
||||
on the architecture's own embarrassingly-parallel-across-groups design,
|
||||
not yet a directly measured result at those specific N. Real bug found
|
||||
and fixed first: `neural_director_grouped.v`'s own bare
|
||||
`$clog2(N_GROUPS)` was invalid (`[-1:0]`) for the never-before-tested
|
||||
N_GROUPS=1 case — fixed with the same real `SELW`-style guard pattern
|
||||
`sdram_arbiter_n.v` already established. See EXP-0095's own
|
||||
`next_action`.
|
||||
|
||||
**The problem it targets**: plain N=16 independent cores (§5.5's own
|
||||
"documentary, expected to break" framing) means 16 independent DDR3
|
||||
requesters contending for one arbitrated channel — real congestion that
|
||||
|
||||
@@ -6646,3 +6646,77 @@ alternatively, a real, measured lower target clock for the N=16
|
||||
variant specifically, if the user prefers not to touch the shared
|
||||
compute core. Both are real, larger next steps for deliberate pickup,
|
||||
not attempted further without explicit direction.
|
||||
|
||||
EXP-0095 -- real timing curve across N=4/8/16 (N_GROUPS=1/2/4): finds
|
||||
WHERE it actually fails, N=8 REALLY CLOSES (2026-09-21, user's own
|
||||
explicit direction: "n2 e' funzionante e presumo funzioni anche
|
||||
sistolico a n=4 e n=8... cerchiamo dove fallisce")
|
||||
|
||||
CONTEXT: `n16_system_ddr3_top.v`'s own `N_GROUPS` parameter (default 4)
|
||||
is a real, already-existing top-level Verilog parameter -- no new RTL
|
||||
top-levels needed, real `synth_design -generic N_GROUPS=<n>` overrides
|
||||
sufficed to test N=4 (1 group) and N=8 (2 groups) against the exact
|
||||
same real RTL/XDC/arbiter (EXP-0094's own hierarchical arbiter) already
|
||||
used for the N=16 result.
|
||||
|
||||
REAL BUG FOUND AND FIXED FIRST (N_GROUPS=1 never tested before this
|
||||
experiment): `neural_director_grouped.v`'s own bare `$clog2(N_GROUPS)`
|
||||
evaluates to 0 for N_GROUPS=1, producing an invalid `[-1:0]`
|
||||
part-select at 6 real sites (`job_out_group`, `free_group_idx`,
|
||||
`done_group_idx`, etc) -- real synthesis error ("part-select [-1:0]
|
||||
does not match declaration"), not a timing issue. Fixed with a real
|
||||
`localparam GROUP_IDX_WIDTH = (N_GROUPS<=1) ? 1 : $clog2(N_GROUPS);`,
|
||||
same real guard-pattern `sdram_arbiter_n.v`'s own `SELW` already
|
||||
established for this exact class of edge case -- applied consistently
|
||||
in `neural_director_grouped.v` itself and the one caller-side wire
|
||||
declaration in `n16_system_ddr3_top.v` (`job_out_group_w`) that would
|
||||
otherwise have a mismatched width. Confirmed fixed via a real, minimal
|
||||
Icarus elaboration check before re-attempting the real P&R.
|
||||
|
||||
REAL RESULT (real, full P&R -- same synth/opt/place/phys_opt/route
|
||||
directive stack as EXP-0094's own second, tuned N=16 attempt --
|
||||
`Explore`/`ExtraNetDelay_high`/`AggressiveExplore`):
|
||||
|
||||
N=2 (flat, packed_slot.v, EXP-0088): WNS=+0.099962ns, CLOSED
|
||||
N=4 (systolic, N_GROUPS=1, 32 DSP/13.3%): WNS=-0.005ns, 2 failing endpoints
|
||||
N=8 (systolic, N_GROUPS=2, 64 DSP/26.7%): WNS=+0.000ns, TNS=0.000, 0 FAILING ENDPOINTS -- REALLY CLOSED
|
||||
N=16 (systolic, N_GROUPS=4, 128 DSP/53.3%): WNS=-0.338ns, 60 failing endpoints (EXP-0094)
|
||||
|
||||
REAL, HONEST INTERPRETATION: N=8 is a genuine, real, closed P&R
|
||||
signoff -- NOT a projection, a real post-route `report_timing_summary`
|
||||
result with 0 failing endpoints on the real `clk_pll_i` (155.039MHz)
|
||||
domain, same real part (XC7A100T-CSG324-2). N=4 is a hair's breadth
|
||||
away (-0.005ns, only 2 endpoints) -- most likely closes with another
|
||||
real P&R attempt (run-to-run placer/router variance, not a structural
|
||||
problem: N=8's own WNS=0.000 being BETTER than N=4's -0.005ns despite
|
||||
having 2x the real logic is itself real evidence of this kind of
|
||||
noise, not a contradiction). The REAL failure point is specifically
|
||||
between N=8 (closed) and N=16 (still -0.338ns after EXP-0094's own two
|
||||
real fixes) -- N=12 (N_GROUPS=3) has NOT yet been measured and would
|
||||
pin down the exact real transition more precisely.
|
||||
|
||||
Real utilization scales as expected and cleanly: DSP48E1 exactly
|
||||
8/PE * real PE count at every N (32/64/128 for N=4/8/16), LUT%
|
||||
roughly linear with N_GROUPS (13.87%/19.77%/31.39%).
|
||||
|
||||
CAVEAT, real and disclosed: N=4/N=8 have NOT yet had a dedicated real
|
||||
functional xsim test of their own (only N=16's full-scale RTL has been
|
||||
functionally verified, EXP-0092/0094) -- the real functional-
|
||||
correctness argument for N=4/N=8 rests on the architecture's own
|
||||
embarrassingly-parallel-across-groups design (no cross-group
|
||||
functional dependency in the RTL), not yet a directly measured result
|
||||
at those specific N values.
|
||||
|
||||
DECISION: N=8 is a real, new, viable deployment target -- closes real
|
||||
timing with 8x N=2's real parallelism, using the SAME already-verified
|
||||
systolic RTL as N=16. This directly answers the user's own question
|
||||
("dove fallisce"): it does NOT fail until somewhere between N=8 and
|
||||
N=16, not at N=4 or N=8.
|
||||
|
||||
next_action: (1) real functional xsim for N_GROUPS=2 (N=8) specifically,
|
||||
before trusting it as a real deployable signoff (per this project's own
|
||||
"one variable at a time" -- functional correctness has only been
|
||||
DIRECTLY measured at N=16 so far). (2) optionally, real P&R at N=12
|
||||
(N_GROUPS=3) to pin down the exact real N where the WNS trend crosses
|
||||
zero. (3) optionally, a real re-attempt at N=4 (likely closes on retry,
|
||||
given how close -0.005ns already is).
|
||||
|
||||
@@ -173,7 +173,11 @@ module n16_system_ddr3_top #(
|
||||
wire [4*JOB_ADDR_WIDTH*N_GROUPS-1:0] group_pe_result_addr_a, group_pe_result_addr_b;
|
||||
wire [4*16*N_GROUPS-1:0] group_pe_node_id_a, group_pe_node_id_b;
|
||||
wire [N_GROUPS-1:0] group_job_done;
|
||||
wire [$clog2(N_GROUPS)-1:0] job_out_group_w;
|
||||
// real fix (N_GROUPS=1 real N=4 scaling test): matches neural_
|
||||
// director_grouped.v's own real GROUP_IDX_WIDTH guard exactly --
|
||||
// bare $clog2(N_GROUPS) is 0 (invalid [-1:0]) for N_GROUPS=1.
|
||||
localparam GROUP_IDX_WIDTH = (N_GROUPS <= 1) ? 1 : $clog2(N_GROUPS);
|
||||
wire [GROUP_IDX_WIDTH-1:0] job_out_group_w;
|
||||
wire [3:0] dir_state;
|
||||
wire dir_error;
|
||||
wire queue_empty;
|
||||
|
||||
@@ -33,7 +33,13 @@
|
||||
module neural_director_grouped #(
|
||||
parameter ADDR_WIDTH = 26,
|
||||
parameter N_GROUPS = 4,
|
||||
parameter QUEUE_DEPTH = 16
|
||||
parameter QUEUE_DEPTH = 16,
|
||||
// real fix (found via real synthesis, N_GROUPS=1 real N=4 scaling
|
||||
// test): bare `$clog2(N_GROUPS)` is 0 for N_GROUPS=1, producing an
|
||||
// invalid `[-1:0]` part-select everywhere below -- same real edge
|
||||
// case sdram_arbiter_n.v's own `SELW` localparam already guards
|
||||
// against, applied here too.
|
||||
localparam GROUP_IDX_WIDTH = (N_GROUPS <= 1) ? 1 : $clog2(N_GROUPS)
|
||||
)(
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
@@ -65,7 +71,7 @@ module neural_director_grouped #(
|
||||
input wire [N_GROUPS-1:0] group_job_done,
|
||||
|
||||
output reg job_out_done, // one-cycle pulse
|
||||
output reg [$clog2(N_GROUPS)-1:0] job_out_group,
|
||||
output reg [GROUP_IDX_WIDTH-1:0] job_out_group,
|
||||
|
||||
output reg [3:0] dir_state,
|
||||
output reg dir_error,
|
||||
@@ -137,12 +143,12 @@ module neural_director_grouped #(
|
||||
wire [N_GROUPS-1:0] group_free = ~group_busy;
|
||||
wire any_group_free = |group_free;
|
||||
|
||||
reg [$clog2(N_GROUPS)-1:0] free_group_idx;
|
||||
reg [GROUP_IDX_WIDTH-1:0] free_group_idx;
|
||||
integer fi;
|
||||
always @(*) begin
|
||||
free_group_idx = {$clog2(N_GROUPS){1'b0}};
|
||||
free_group_idx = {GROUP_IDX_WIDTH{1'b0}};
|
||||
for (fi = N_GROUPS-1; fi >= 0; fi = fi - 1) begin
|
||||
if (group_free[fi]) free_group_idx = fi[$clog2(N_GROUPS)-1:0];
|
||||
if (group_free[fi]) free_group_idx = fi[GROUP_IDX_WIDTH-1:0];
|
||||
end
|
||||
end
|
||||
|
||||
@@ -176,12 +182,12 @@ module neural_director_grouped #(
|
||||
end
|
||||
endgenerate
|
||||
|
||||
reg [$clog2(N_GROUPS)-1:0] done_group_idx;
|
||||
reg [GROUP_IDX_WIDTH-1:0] done_group_idx;
|
||||
integer di;
|
||||
always @(*) begin
|
||||
done_group_idx = {$clog2(N_GROUPS){1'b0}};
|
||||
done_group_idx = {GROUP_IDX_WIDTH{1'b0}};
|
||||
for (di = N_GROUPS-1; di >= 0; di = di - 1) begin
|
||||
if (group_job_done[di]) done_group_idx = di[$clog2(N_GROUPS)-1:0];
|
||||
if (group_job_done[di]) done_group_idx = di[GROUP_IDX_WIDTH-1:0];
|
||||
end
|
||||
end
|
||||
|
||||
@@ -208,7 +214,7 @@ module neural_director_grouped #(
|
||||
end
|
||||
end
|
||||
job_out_done <= 1'b0;
|
||||
job_out_group <= {$clog2(N_GROUPS){1'b0}};
|
||||
job_out_group <= {GROUP_IDX_WIDTH{1'b0}};
|
||||
end else begin
|
||||
for (fi = 0; fi < N_GROUPS; fi = fi + 1) group_job_start_r[fi] <= 1'b0;
|
||||
job_out_done <= 1'b0;
|
||||
@@ -242,7 +248,7 @@ module neural_director_grouped #(
|
||||
|
||||
DIR_ALLOCATE: begin
|
||||
for (fi = 0; fi < N_GROUPS; fi = fi + 1) begin
|
||||
if (fi[$clog2(N_GROUPS)-1:0] == free_group_idx) begin
|
||||
if (fi[GROUP_IDX_WIDTH-1:0] == free_group_idx) begin
|
||||
group_job_start_r[fi] <= 1'b1;
|
||||
group_w_base_r[fi] <= q_w_base[q_idx[0]]; // all 8 match, checked by group_ready
|
||||
group_n_tiles_r[fi] <= q_n_tiles[q_idx[0]];
|
||||
|
||||
Reference in New Issue
Block a user