feat: MILESTONE - real activation-fetch engine, full N=2 system verified on real DDR3 (EXP-0079)

Closes the last major disclosed functional gap: packed_slot.v's
activation data was read through a combinational stand-in since
EXP-0062. New act_tile_fetch.v reads activation tiles directly from
DDR3 (no on-chip buffering needed, unlike weights -- activation data
has no reuse), sharing each slot's existing ctrl port with its own
weight-prefetch engine. Real memory layout: one full BURST_LEN=8-word
burst per tile, deliberately avoiding any runtime-indexed part-select
given this project's thin P&R timing margin (EXP-0078).

Verified at three levels: act_tile_fetch.v alone (6/6), packed_slot.v
with real preloaded activation data (9/9), and the full N=2 system
against real DDR3 via xsim (8/8, 0 errors) -- the first time this
project's compute path has been verified end-to-end with real DDR3
for both weights and activations.

Retired hardware/v3/rtl/n2_system_top.v and its testbench (pre-DDR3
SDR-placeholder era, fully superseded by n2_system_ddr3_top.v).

Also: docs/PHYSICAL_REALIZATION.md (real pinout/parts/timing/protocol
reference for the physical board) and CLAUDE.md (persistent project
instructions for future Claude Code sessions).

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 08:13:15 +02:00
co-authored by Claude Sonnet 5
parent 78577dde59
commit 43a12379a5
11 changed files with 946 additions and 682 deletions
+35 -21
View File
@@ -15,8 +15,12 @@
// memory backend is swapped, isolating that as the one variable
// under test.
//
// Activation stand-in (see packed_slot.v's own header) is unchanged
// too -- still a disclosed, separate gap, not addressed here.
// EXP-0079 UPDATE: activations are now fetched via a REAL act_tile_
// fetch.v inside each packed_slot.v instance (real DDR3 reads, same
// physical bus each slot already uses for weights) -- no more stand-
// in. This test now preloads real activation data into the SAME real
// DDR3 model too (preload_ddr3_activations), on top of the weight
// preload that was already here.
//
// Uses mig_7series_0_mig_sim (SIM_BYPASS_INIT_CAL="FAST" default,
// EXP-0068's own real vendor-shipped fast-calibration simulation
@@ -236,19 +240,34 @@ module tb;
end
endtask
function automatic signed [DATA_WIDTH*P_IN-1:0] act_lookup(input [ADDR_WIDTH-1:0] addr);
integer li_d, pos_d, tidx_d, k;
reg signed [DATA_WIDTH*P_IN-1:0] r;
begin
li_d = addr / 100000;
pos_d = (addr / 1000) % 100;
tidx_d = addr % 1000;
for (k = 0; k < P_IN; k = k + 1)
r[k*DATA_WIDTH +: DATA_WIDTH] = input_byte(li_d, pos_d, tidx_d*P_IN + k);
act_lookup = r;
end
// ---- real activation preload (EXP-0079: packed_slot.v now wraps
// a real act_tile_fetch.v, no more stand-in) -- same convention as
// tb_packed_slot.v/tb_act_tile_fetch.v: one full BURST_LEN=8-word
// burst per tile, P_IN=8 bytes in the low 64 bits. ----
localparam [MIG_ADDR_WIDTH-1:0] ACT_MEM_BASE = 25'h10000;
function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos);
act_x_base = {{(ADDR_WIDTH-MIG_ADDR_WIDTH){1'b0}}, ACT_MEM_BASE} + (li*M + pos) * (N_TILES*BURST_LEN);
endfunction
task automatic preload_ddr3_activations;
integer li, pos, t, k;
reg [16*BURST_LEN-1:0] burst_data;
reg [ADDR_WIDTH-1:0] base;
begin
for (li = 0; li < L; li = li + 1) begin
for (pos = 0; pos < M; pos = pos + 1) begin
base = act_x_base(li, pos);
for (t = 0; t < N_TILES; t = t + 1) begin
burst_data = {(16*BURST_LEN){1'b0}};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[k*16 +: 16] = {input_byte(li, pos, t*P_IN + 2*k+1), input_byte(li, pos, t*P_IN + 2*k)};
sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + t*BURST_LEN, burst_data);
end
end
end
end
endtask
// ---- neural_director_packed.v ----
reg job_in_valid;
wire job_in_ready;
@@ -308,11 +327,6 @@ module tb;
wire signed [DATA_WIDTH-1:0] res_a, res_b;
wire [15:0] res_nid_a, res_nid_b;
wire [ADDR_WIDTH-1:0] res_addr_a_out, res_addr_b_out;
wire [ADDR_WIDTH-1:0] act_addr_a, act_addr_b;
wire signed [DATA_WIDTH*P_IN-1:0] act_data_a, act_data_b;
assign act_data_a = act_lookup(act_addr_a);
assign act_data_b = act_lookup(act_addr_b);
packed_slot #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH),
@@ -332,8 +346,6 @@ module tb;
.result_node_id_a(res_nid_a), .result_node_id_b(res_nid_b),
.result_addr_a_out(res_addr_a_out), .result_addr_b_out(res_addr_b_out),
.mem_active(mem_active[gi]), .mem_grant(mem_grant[gi]),
.act_tile_addr_a(act_addr_a), .act_tile_addr_b(act_addr_b),
.act_tile_data_a(act_data_a), .act_tile_data_b(act_data_b),
.ctrl_req(s_ctrl_req[gi]), .ctrl_wr(s_ctrl_wr[gi]),
.ctrl_addr(s_ctrl_addr_flat[gi*MIG_ADDR_WIDTH +: MIG_ADDR_WIDTH]),
.ctrl_wdata(s_ctrl_wdata_flat[gi*16*BURST_LEN +: 16*BURST_LEN]),
@@ -429,6 +441,8 @@ module tb;
$display("=== preload SDRAM with %0d resident-filter weight sets ===", L);
preload_sdram_layers;
$display("=== preload SDRAM with real activation data (EXP-0079) ===");
preload_ddr3_activations;
@(posedge ui_clk);
pre_active = 1'b0;
repeat (5) @(posedge ui_clk);
@@ -436,7 +450,7 @@ module tb;
$display("=== N=2 system on REAL DDR3: submitting %0d layers x %0d positions ===", L, M);
for (li_i = 0; li_i < L; li_i = li_i + 1) begin
for (pp_i = 0; pp_i < M; pp_i = pp_i + 1) begin
submit_job(li_i*100000 + pp_i*1000, li_i*WORDS_PER_LAYER, N_TILES[15:0],
submit_job(act_x_base(li_i, pp_i), li_i*WORDS_PER_LAYER, N_TILES[15:0],
26'h9000 + li_i*10 + pp_i, (li_i*M + pp_i));
expect_node[n_expected] = (li_i*M + pp_i);
expect_val[n_expected] = golden_result(li_i, pp_i);