feat: denser activation packing, real bandwidth ceiling doubled (EXP-0081)

Implements the highest-leverage fix from EXP-0080's bottleneck
analysis: act_tile_fetch.v now packs 2 consecutive tiles per DDR3
burst (even tile low 64 bits, odd tile high 64 bits) instead of 1
tile per burst, halving real DDR3 bytes-per-useful-byte. Timing-safe
by construction: the tile-index select bit is registered at request
time, long before the real DDR3 round-trip completes, never racing
the arriving read data (unlike the runtime part-select pattern
EXP-0079 deliberately avoided).

Re-verified at all 3 levels (isolated engine 8/8, packed_slot.v 9/9
with bit-identical results to EXP-0079, full N=2 system on real DDR3
8/8) -- the real JEDEC trace now shows no half-burst padding, direct
confirmation the fix works in practice, not just in theory.

Also: real device data gathered on this package's I/O bank layout
(only 5 banks total, 14/15/16/34/35) informing the next bandwidth step
(32-bit-wide single controller recommended over a second independent
channel, given the pin/logic cost comparison).

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 09:55:16 +02:00
co-authored by Claude Sonnet 5
parent 27cf5f36da
commit 8ad04987de
5 changed files with 153 additions and 50 deletions
+26 -12
View File
@@ -147,40 +147,54 @@ module tb;
rst = 0;
@(posedge clk); while (ctrl_busy) @(posedge clk);
$display("=== preload 4 burst-aligned tile slots (2 lanes x 2 tiles) ===");
// lane A base = 0, lane B base = 100 (arbitrary, word-address units)
for (wi = 0; wi < 2; wi = wi + 1) begin // wi = tile index
for (k = 0; k < BURST_LEN; k = k + 1)
burst[k*16 +: 16] = (k < P_IN/2) ? {act_byte(0, wi, 2*k+1), act_byte(0, wi, 2*k)} : 16'h0000;
$display("=== preload 2 bursts/lane, 2 tiles packed per burst (EXP-0081 layout) ===");
// lane A base = 0, lane B base = 100 (arbitrary, word-address units).
// burst pair p holds tile 2p (low 64 bits) and tile 2p+1 (high 64 bits).
for (wi = 0; wi < 2; wi = wi + 1) begin // wi = burst-pair index (0 -> tiles 0/1, 1 -> tiles 2/3)
for (k = 0; k < P_IN/2; k = k + 1)
burst[k*16 +: 16] = {act_byte(0, 2*wi, 2*k+1), act_byte(0, 2*wi, 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst[(P_IN/2+k)*16 +: 16] = {act_byte(0, 2*wi+1, 2*k+1), act_byte(0, 2*wi+1, 2*k)};
sdram_write_burst(0 + wi*BURST_LEN, burst);
for (k = 0; k < BURST_LEN; k = k + 1)
burst[k*16 +: 16] = (k < P_IN/2) ? {act_byte(100, wi, 2*k+1), act_byte(100, wi, 2*k)} : 16'h0000;
for (k = 0; k < P_IN/2; k = k + 1)
burst[k*16 +: 16] = {act_byte(100, 2*wi, 2*k+1), act_byte(100, 2*wi, 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst[(P_IN/2+k)*16 +: 16] = {act_byte(100, 2*wi+1, 2*k+1), act_byte(100, 2*wi+1, 2*k)};
sdram_write_burst(100 + wi*BURST_LEN, burst);
end
@(posedge clk);
pre_active = 1'b0;
$display("=== TEST 1: fetch tile 0, both lanes ===");
$display("=== TEST 1: fetch tile 0 (even -> low half), both lanes ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 0, k);
check(data_a === exp_a, "T1: lane A tile 0 bit-exact");
check(data_b === exp_b, "T1: lane B tile 0 bit-exact");
$display("=== TEST 2: fetch tile 1, both lanes (different burst address) ===");
$display("=== TEST 2: fetch tile 1 (odd -> high half, SAME burst address as tile 0) ===");
do_fetch(25'd0, 25'd100, 16'd1);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k);
for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 1, k);
check(data_a === exp_a, "T2: lane A tile 1 bit-exact");
check(data_b === exp_b, "T2: lane B tile 1 bit-exact");
$display("=== TEST 3: back-to-back fetches (tile 0 then tile 1 immediately) ===");
$display("=== TEST 3: fetch tile 2 (even -> low half, NEW burst address) ===");
do_fetch(25'd0, 25'd100, 16'd2);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k);
check(data_a === exp_a, "T3: lane A tile 2 bit-exact (new burst)");
$display("=== TEST 4: back-to-back fetches, alternating even/odd tiles ===");
do_fetch(25'd0, 25'd100, 16'd0);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k);
check(data_a === exp_a, "T3a: back-to-back fetch 1, lane A correct");
check(data_a === exp_a, "T4a: back-to-back fetch 1 (tile 0, even), lane A correct");
do_fetch(25'd0, 25'd100, 16'd1);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k);
check(data_a === exp_a, "T3b: back-to-back fetch 2, lane A correct");
check(data_a === exp_a, "T4b: back-to-back fetch 2 (tile 1, odd), lane A correct");
do_fetch(25'd0, 25'd100, 16'd3);
for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 3, k);
check(data_a === exp_a, "T4c: back-to-back fetch 3 (tile 3, odd, new burst), lane A correct");
$display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors);
if (errors == 0) $display("ALL TESTS PASSED (tb_act_tile_fetch)");
+11 -9
View File
@@ -240,28 +240,30 @@ module tb;
end
endtask
// ---- 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. ----
// ---- real activation preload (EXP-0081 layout: TWO consecutive
// tiles share one BURST_LEN=8-word burst -- even tile in the low
// 64 bits, odd tile in the high 64 bits, see act_tile_fetch.v's
// own header). ----
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);
act_x_base = {{(ADDR_WIDTH-MIG_ADDR_WIDTH){1'b0}}, ACT_MEM_BASE} + (li*M + pos) * ((N_TILES/2)*BURST_LEN);
endfunction
task automatic preload_ddr3_activations;
integer li, pos, t, k;
integer li, pos, tp, 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
for (tp = 0; tp < N_TILES/2; tp = tp + 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);
burst_data[k*16 +: 16] = {input_byte(li, pos, (2*tp)*P_IN + 2*k+1), input_byte(li, pos, (2*tp)*P_IN + 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[(P_IN/2+k)*16 +: 16] = {input_byte(li, pos, (2*tp+1)*P_IN + 2*k+1), input_byte(li, pos, (2*tp+1)*P_IN + 2*k)};
sdram_write_burst(base[MIG_ADDR_WIDTH-1:0] + tp*BURST_LEN, burst_data);
end
end
end
+12 -12
View File
@@ -120,31 +120,31 @@ module tb;
end
endtask
// ---- real activation preload (EXP-0079: act_tile_fetch.v replaces
// the old combinational stand-in) -- one full BURST_LEN=8-word
// burst PER TILE (act_tile_fetch.v's own real memory layout
// convention, see that module's header), P_IN=8 bytes in the low
// 64 bits, upper 64 bits padding. x_base(li,pos) = ACT_MEM_BASE +
// (li*M+pos)*(N_TILES*BURST_LEN), well clear of the weight region
// (word addresses 0..L*WORDS_PER_LAYER-1). ----
// ---- real activation preload (EXP-0081 layout: TWO consecutive
// tiles share one BURST_LEN=8-word burst -- even tile in the low
// 64 bits, odd tile in the high 64 bits, see act_tile_fetch.v's
// own header). x_base(li,pos) = ACT_MEM_BASE + (li*M+pos)*
// (N_TILES/2*BURST_LEN), well clear of the weight region. ----
localparam [ADDR_WIDTH-1:0] ACT_MEM_BASE = 26'h10000;
function automatic [ADDR_WIDTH-1:0] act_x_base(input integer li, input integer pos);
act_x_base = ACT_MEM_BASE + (li*M + pos) * (N_TILES*BURST_LEN);
act_x_base = ACT_MEM_BASE + (li*M + pos) * ((N_TILES/2)*BURST_LEN);
endfunction
task automatic preload_sdram_activations;
integer li, pos, t, k;
integer li, pos, tp, 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
for (tp = 0; tp < N_TILES/2; tp = tp + 1) begin // tp = burst-pair index
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[SDRAM_ADDR_WIDTH-1:0] + t*BURST_LEN, burst_data);
burst_data[k*16 +: 16] = {input_byte(li, pos, (2*tp)*P_IN + 2*k+1), input_byte(li, pos, (2*tp)*P_IN + 2*k)};
for (k = 0; k < P_IN/2; k = k + 1)
burst_data[(P_IN/2+k)*16 +: 16] = {input_byte(li, pos, (2*tp+1)*P_IN + 2*k+1), input_byte(li, pos, (2*tp+1)*P_IN + 2*k)};
sdram_write_burst(base[SDRAM_ADDR_WIDTH-1:0] + tp*BURST_LEN, burst_data);
end
end
end