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
+8 -28
View File
@@ -260,30 +260,14 @@ module n2_system_ddr3_top #(
wire [15:0] s0_nid_a, s0_nid_b, s1_nid_a, s1_nid_b;
wire [JOB_ADDR_WIDTH-1:0] s0_raddr_a, s0_raddr_b, s1_raddr_a, s1_raddr_b;
// ---- activation-fetch STUB (disclosed gap, see packed_slot.v's
// own header: no real activation-fetch engine exists yet). Kept
// fully INTERNAL rather than exposed as top-level chip pins --
// exposing act_addr/act_data literally as pins was a real mistake
// caught by this same P&R run: s0/s1's act_addr_a/b (JOB_ADDR_
// WIDTH=26 bits x4) + act_data_a/b (DATA_WIDTH*P_IN=64 bits x4)
// alone demand ~360 I/O, but XC7A100T-CSG324 has only 324 pins
// total (DDR3 alone already uses ~53) -- place_design failed with
// "IO Clock Placer failed" for exactly this reason. A free-
// running counter-addressed pattern stands in for real activation
// data until a real fetch engine (DDR3-backed, like the weight
// path) is built; this keeps real timing/placement meaningful for
// everything else in this P&R run without claiming activation
// fetch is solved.
wire [JOB_ADDR_WIDTH-1:0] s0_act_addr_a, s0_act_addr_b, s1_act_addr_a, s1_act_addr_b;
reg [DATA_WIDTH*P_IN-1:0] act_stub_reg;
always @(posedge ui_clk)
if (ui_clk_sync_rst) act_stub_reg <= {(DATA_WIDTH*P_IN){1'b0}};
else act_stub_reg <= act_stub_reg + 1'b1;
wire signed [DATA_WIDTH*P_IN-1:0] s0_act_data_a = act_stub_reg;
wire signed [DATA_WIDTH*P_IN-1:0] s0_act_data_b = act_stub_reg;
wire signed [DATA_WIDTH*P_IN-1:0] s1_act_data_a = act_stub_reg;
wire signed [DATA_WIDTH*P_IN-1:0] s1_act_data_b = act_stub_reg;
// ---- activation fetch: REAL now (EXP-0079) -- each packed_slot
// instance owns its own act_tile_fetch.v internally, sharing that
// SAME slot's existing ctrl_req/addr/etc port (already wired to
// the arbiter below) with its own weight-prefetch engine. No
// top-level activation ports exist any more -- the old stand-in
// (act_tile_addr_a/b -> act_tile_data_a/b, and before that, a
// free-running counter stub that nearly blew the package's whole
// I/O budget, see git history) is gone; this is fully internal.
packed_slot #(
.DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .ACC_WIDTH(ACC_WIDTH),
.BURST_LEN(BURST_LEN), .ADDR_WIDTH(JOB_ADDR_WIDTH), .LAYER_BYTES(LAYER_BYTES)
@@ -302,8 +286,6 @@ module n2_system_ddr3_top #(
.result_node_id_a(s0_nid_a), .result_node_id_b(s0_nid_b),
.result_addr_a_out(s0_raddr_a), .result_addr_b_out(s0_raddr_b),
.mem_active(req_active[0]), .mem_grant(req_grant[0]),
.act_tile_addr_a(s0_act_addr_a), .act_tile_addr_b(s0_act_addr_b),
.act_tile_data_a(s0_act_data_a), .act_tile_data_b(s0_act_data_b),
.ctrl_req(req_req[0]), .ctrl_wr(req_wr[0]),
.ctrl_addr(req_addr[0*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[0*16*BURST_LEN +: 16*BURST_LEN]),
@@ -330,8 +312,6 @@ module n2_system_ddr3_top #(
.result_node_id_a(s1_nid_a), .result_node_id_b(s1_nid_b),
.result_addr_a_out(s1_raddr_a), .result_addr_b_out(s1_raddr_b),
.mem_active(req_active[1]), .mem_grant(req_grant[1]),
.act_tile_addr_a(s1_act_addr_a), .act_tile_addr_b(s1_act_addr_b),
.act_tile_data_a(s1_act_data_a), .act_tile_data_b(s1_act_data_b),
.ctrl_req(req_req[1]), .ctrl_wr(req_wr[1]),
.ctrl_addr(req_addr[1*MEM_ADDR_WIDTH +: MEM_ADDR_WIDTH]),
.ctrl_wdata(req_wdata[1*16*BURST_LEN +: 16*BURST_LEN]),