diff --git a/docs/ARCHITECTURE_ANALYSIS.md b/docs/ARCHITECTURE_ANALYSIS.md index fd9c746..311d007 100644 --- a/docs/ARCHITECTURE_ANALYSIS.md +++ b/docs/ARCHITECTURE_ANALYSIS.md @@ -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=` 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 diff --git a/hardware/v2/logs/experiments.log b/hardware/v2/logs/experiments.log index 11d2a0e..a0a1377 100644 --- a/hardware/v2/logs/experiments.log +++ b/hardware/v2/logs/experiments.log @@ -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=` 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). diff --git a/hardware/v3/rtl/n16_system_ddr3_top.v b/hardware/v3/rtl/n16_system_ddr3_top.v index e4f0510..4e0e50b 100644 --- a/hardware/v3/rtl/n16_system_ddr3_top.v +++ b/hardware/v3/rtl/n16_system_ddr3_top.v @@ -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; diff --git a/hardware/v3/rtl/neural_director_grouped.v b/hardware/v3/rtl/neural_director_grouped.v index e59974a..532875f 100644 --- a/hardware/v3/rtl/neural_director_grouped.v +++ b/hardware/v3/rtl/neural_director_grouped.v @@ -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]];