feat: real result-writeback engine, removes the last hard N-scaling pin blocker (EXP-0088)

Adds result_writeback.v, one instance per packed_slot.v, writing each
completed job's result directly into DDR3 at the job's own
result_addr_a/b instead of driving literal top-level pins -- the same
architectural shape as the weight-fetch path, in reverse. job_done now
means "durably in DDR3", not "captured in a register only a pin could
see". n2_system_ddr3_top.v's own s0_result_data_a/b, s1_result_data_a/b
top-level package pins are removed (and the now-dangling XDC constraint
for them), closing the real, hard scaling blocker docs/ARCHITECTURE_
ANALYSIS.md flagged since EXP-0074/0079 (8 bits x 2 lanes x N cores ->
256 pins at N=16).

Addressing reuses the exact same JOB_ADDR_WIDTH->ctrl-bus-word
truncation x_base_a/w_base already use (verified against act_tile_
fetch.v's/layer_prefetch_ctrl.v's own real code, not guessed). The host
reads results back via the already-existing READ_MEM (0x02) SPI opcode
-- no new protocol. A real EXP-0066-class bug (issuing ctrl_req before
mem_grant) was caught and fixed before ever compiling, by re-deriving
the design against act_tile_fetch.v's own proven S_MEMWAIT/S_GAP
sequencing.

Verified two ways: tb_packed_slot.v extended with a real DDR3
read-after-write check (9/9 PASS, confirms the write actually landed,
not just that job_done pulsed); tb_n2_system_ddr3.v re-run via real
xsim to confirm correct behavior under real 2-slot shared-bus
arbitration (8/8 PASS, 0 errors, consistent timing with EXP-0087's own
baseline for this workload).

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 19:18:57 +02:00
co-authored by Claude Sonnet 5
parent 344e798ad5
commit ccaf3ee059
8 changed files with 534 additions and 51 deletions
+53
View File
@@ -179,6 +179,55 @@ module tb;
reg signed [DATA_WIDTH-1:0] expected_a, expected_b;
integer wd;
// EXP-0088: real read-after-write check that result_writeback.v
// (inside the DUT) actually landed the correct value in DDR3 at
// result_addr_a/b -- not just that job_done eventually pulsed.
// Format matches result_writeback.v's own header exactly: one
// 32-bit ctrl-word per lane, {node_id[15:0], 8'h00,
// result_data[7:0]}.
task automatic verify_writeback(
input integer li, input integer pos_a, input integer pos_b,
input [ADDR_WIDTH-1:0] raddr_a, input [ADDR_WIDTH-1:0] raddr_b,
input signed [DATA_WIDTH-1:0] exp_data_a, input signed [DATA_WIDTH-1:0] exp_data_b,
input [15:0] exp_nid_a, input [15:0] exp_nid_b
);
reg [31:0] word_a, word_b;
reg [SDRAM_ADDR_WIDTH-1:0] burst_addr;
reg [2:0] word_in_block;
begin
pre_active = 1'b1;
burst_addr = {raddr_a[SDRAM_ADDR_WIDTH-1:3], 3'b0};
word_in_block = raddr_a[2:0];
@(posedge clk); while (ctrl_busy) @(posedge clk);
wpre_req = 1'b1; wpre_wr = 1'b0; wpre_addr = burst_addr;
@(posedge clk); wpre_req = 1'b0;
while (!ctrl_ready) @(posedge clk);
word_a = ctrl_rdata[word_in_block*32 +: 32];
burst_addr = {raddr_b[SDRAM_ADDR_WIDTH-1:3], 3'b0};
word_in_block = raddr_b[2:0];
@(posedge clk); while (ctrl_busy) @(posedge clk);
wpre_req = 1'b1; wpre_wr = 1'b0; wpre_addr = burst_addr;
@(posedge clk); wpre_req = 1'b0;
while (!ctrl_ready) @(posedge clk);
word_b = ctrl_rdata[word_in_block*32 +: 32];
pre_active = 1'b0;
if (word_a[7:0] !== exp_data_a || word_a[31:16] !== exp_nid_a) begin
$display("FAIL li=%0d pos_a=%0d: WRITEBACK readback mismatch lane A: word=%08h (data=%0d nid=%0d) expected data=%0d nid=%0d",
li, pos_a, word_a, $signed(word_a[7:0]), word_a[31:16], $signed(exp_data_a), exp_nid_a);
errors = errors + 1;
end
if (word_b[7:0] !== exp_data_b || word_b[31:16] !== exp_nid_b) begin
$display("FAIL li=%0d pos_b=%0d: WRITEBACK readback mismatch lane B: word=%08h (data=%0d nid=%0d) expected data=%0d nid=%0d",
li, pos_b, word_b, $signed(word_b[7:0]), word_b[31:16], $signed(exp_data_b), exp_nid_b);
errors = errors + 1;
end
end
endtask
task automatic run_one_pair(input integer li, input integer pos_a, input integer pos_b);
begin
tests = tests + 1;
@@ -224,6 +273,10 @@ module tb;
end else begin
$display("PASS li=%0d pos_a=%0d pos_b=%0d: a=%0d b=%0d (packed_slot.v real sequencer)",
li, pos_a, pos_b, $signed(result_data_a), $signed(result_data_b));
// EXP-0088: real DDR3 read-after-write check -- job_done
// now means "written to DDR3", confirm it actually was.
verify_writeback(li, pos_a, pos_b, result_addr_a, result_addr_b,
expected_a, expected_b, node_id_a, node_id_b);
end
end
endtask