`timescale 1ns/1ps // ============================================================ // EXP-0084: re-run of the EXP-0083 ddr_prefetch_mgr.v isolated // correctness + A/B cycle-count test against the new 32-bit ctrl bus // (real DDR3 channel widening). Backend switched to burst_mem_ // model32.v (see its own header -- explicitly synthetic, fixed // latency, not claiming real DDR3 row/bank AC timing) since the real // AS4C32M16SA x16 SDR model this test used before is genuinely fixed // at 16-bit and can't represent the new bus width. // // EXP-0083's own PART 3 (same-row vs row-switch-heavy A/B, exploiting // the old SDR model's own real row/bank timing distinction) is // DROPPED here -- burst_mem_model32.v has a single fixed latency // regardless of address pattern, so that comparison would no longer // carry real meaning on this backend. The trustworthy, real number // for that effect already comes from EXP-0083's own real-DDR3-backend // measurement (tb_n2_system_ddr3.v, 2.86% real reduction) -- this // isolated test's own job is correctness + a basic sanity check that // prefetch is still not slower than the old per-tile loop, not to // re-derive that headline number. // ============================================================ module tb; localparam BURST_LEN = 8; localparam ADDR_WIDTH = 25; localparam CLK_FREQ_MHZ = 64; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam DATA_WIDTH = 8; localparam P_IN = 8; localparam N_TILES = 8; // 2 bursts/lane at 4 tiles/burst -- enough to see steady-state behavior reg clk = 0; always #(CLK_PERIOD_NS/2.0) clk = ~clk; reg rst; wire ctrl_req, ctrl_wr; wire [ADDR_WIDTH-1:0] ctrl_addr; wire [32*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata; wire [4*BURST_LEN-1:0] ctrl_wmask; wire ctrl_ready, ctrl_busy; burst_mem_model32 #( .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) ) u_mem ( .clk(clk), .rst(rst), .req(ctrl_req), .wr(ctrl_wr), .addr(ctrl_addr), .wdata(ctrl_wdata), .wmask(ctrl_wmask), .rdata(ctrl_rdata), .ready(ctrl_ready), .busy(ctrl_busy) ); // ---- 3-way mux on the shared backend: preload / baseline DUT / prefetch DUT ---- localparam SEL_PRELOAD = 2'd0, SEL_BASE = 2'd1, SEL_PF = 2'd2; reg [1:0] sel; reg pre_req, pre_wr; reg [ADDR_WIDTH-1:0] pre_addr; reg [32*BURST_LEN-1:0] pre_wdata; // ---- baseline DUT: plain act_tile_fetch.v, driven by a per-tile // req/wait/consume loop replicating OLD packed_slot.v sequencing ---- reg base_req; reg [ADDR_WIDTH-1:0] base_base_a, base_base_b; reg [15:0] base_tcnt; wire base_valid; wire signed [DATA_WIDTH*P_IN-1:0] base_data_a, base_data_b; wire base_mem_active; wire base_ctrl_req, base_ctrl_wr; wire [ADDR_WIDTH-1:0] base_ctrl_addr; wire [32*BURST_LEN-1:0] base_ctrl_wdata; wire [4*BURST_LEN-1:0] base_ctrl_wmask; act_tile_fetch #( .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) ) u_base ( .clk(clk), .rst(rst), .req(base_req), .base_a(base_base_a), .base_b(base_base_b), .tcnt(base_tcnt), .valid(base_valid), .data_a(base_data_a), .data_b(base_data_b), .mem_active(base_mem_active), .mem_grant(sel == SEL_BASE), .ctrl_req(base_ctrl_req), .ctrl_wr(base_ctrl_wr), .ctrl_addr(base_ctrl_addr), .ctrl_wdata(base_ctrl_wdata), .ctrl_wmask(base_ctrl_wmask), .ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy) ); // ---- prefetch DUT: ddr_prefetch_mgr.v (EXP-0083) ---- reg pf_job_start; reg [ADDR_WIDTH-1:0] pf_base_a, pf_base_b; reg [15:0] pf_n_tiles; wire pf_tile_valid; wire signed [DATA_WIDTH*P_IN-1:0] pf_data_a, pf_data_b; reg pf_tile_consume; wire pf_mem_active; wire pf_ctrl_req, pf_ctrl_wr; wire [ADDR_WIDTH-1:0] pf_ctrl_addr; wire [32*BURST_LEN-1:0] pf_ctrl_wdata; wire [4*BURST_LEN-1:0] pf_ctrl_wmask; ddr_prefetch_mgr #( .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) ) u_pf ( .clk(clk), .rst(rst), .job_start(pf_job_start), .base_a(pf_base_a), .base_b(pf_base_b), .n_tiles(pf_n_tiles), .tile_valid(pf_tile_valid), .data_a(pf_data_a), .data_b(pf_data_b), .tile_consume(pf_tile_consume), .mem_active(pf_mem_active), .mem_grant(sel == SEL_PF), .ctrl_req(pf_ctrl_req), .ctrl_wr(pf_ctrl_wr), .ctrl_addr(pf_ctrl_addr), .ctrl_wdata(pf_ctrl_wdata), .ctrl_wmask(pf_ctrl_wmask), .ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy) ); assign ctrl_req = (sel==SEL_PRELOAD) ? pre_req : (sel==SEL_BASE) ? base_ctrl_req : pf_ctrl_req; assign ctrl_wr = (sel==SEL_PRELOAD) ? pre_wr : (sel==SEL_BASE) ? base_ctrl_wr : pf_ctrl_wr; assign ctrl_addr = (sel==SEL_PRELOAD) ? pre_addr : (sel==SEL_BASE) ? base_ctrl_addr : pf_ctrl_addr; assign ctrl_wdata = (sel==SEL_PRELOAD) ? pre_wdata : (sel==SEL_BASE) ? base_ctrl_wdata : pf_ctrl_wdata; assign ctrl_wmask = (sel==SEL_BASE) ? base_ctrl_wmask : (sel==SEL_PF) ? pf_ctrl_wmask : {(4*BURST_LEN){1'b0}}; task automatic mem_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [32*BURST_LEN-1:0] data); begin @(posedge clk); while (ctrl_busy) @(posedge clk); pre_req = 1'b1; pre_wr = 1'b1; pre_addr = word_addr; pre_wdata = data; @(posedge clk); pre_req = 1'b0; while (!ctrl_ready) @(posedge clk); end endtask function automatic signed [7:0] act_byte(input integer base, input integer t, input integer k); act_byte = $signed(8'((base*13 + t*31 + k*7 + 5) & 8'hFF)); endfunction integer errors, tests; task automatic check(input cond, input [255:0] name); begin tests = tests + 1; if (!cond) begin errors = errors + 1; $display("FAIL: %0s", name); end else $display("PASS: %0s", name); end endtask reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b; integer k, wi, qi; reg [32*BURST_LEN-1:0] burst; integer t_start, t_end, base_cycles, pf_cycles; initial begin errors = 0; tests = 0; rst = 1; sel = SEL_PRELOAD; pre_req = 0; pre_wr = 0; pre_addr = 0; pre_wdata = 0; base_req = 0; base_base_a = 0; base_base_b = 0; base_tcnt = 0; pf_job_start = 0; pf_base_a = 0; pf_base_b = 0; pf_n_tiles = 0; pf_tile_consume = 0; repeat(5) @(posedge clk); rst = 0; @(posedge clk); while (ctrl_busy) @(posedge clk); $display("=== preload lane A base=0, lane B base=200, %0d tiles (EXP-0084 4-tiles/burst layout) ===", N_TILES); for (wi = 0; wi < N_TILES/4; wi = wi + 1) begin for (qi = 0; qi < 4; qi = qi + 1) burst[qi*64 +: 64] = {act_byte(0, 4*wi+qi, 7), act_byte(0, 4*wi+qi, 6), act_byte(0, 4*wi+qi, 5), act_byte(0, 4*wi+qi, 4), act_byte(0, 4*wi+qi, 3), act_byte(0, 4*wi+qi, 2), act_byte(0, 4*wi+qi, 1), act_byte(0, 4*wi+qi, 0)}; mem_write_burst(0 + wi*BURST_LEN, burst); for (qi = 0; qi < 4; qi = qi + 1) burst[qi*64 +: 64] = {act_byte(200, 4*wi+qi, 7), act_byte(200, 4*wi+qi, 6), act_byte(200, 4*wi+qi, 5), act_byte(200, 4*wi+qi, 4), act_byte(200, 4*wi+qi, 3), act_byte(200, 4*wi+qi, 2), act_byte(200, 4*wi+qi, 1), act_byte(200, 4*wi+qi, 0)}; mem_write_burst(200 + wi*BURST_LEN, burst); end @(posedge clk); // ============================================================ // PART 1: baseline correctness + cycle count, OLD-style // per-tile req -> wait valid -> 2-cycle simulated compute -> next req // ============================================================ $display("=== PART 1: baseline (direct act_tile_fetch.v, old packed_slot.v loop) ==="); sel = SEL_BASE; @(posedge clk); t_start = $time; for (k = 0; k < N_TILES; k = k + 1) begin @(posedge clk); base_base_a <= 0; base_base_b <= 200; base_tcnt <= k[15:0]; base_req <= 1'b1; @(posedge clk); base_req <= 1'b0; while (!base_valid) @(posedge clk); for (wi = 0; wi < P_IN; wi = wi + 1) begin exp_a[wi*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, k, wi); exp_b[wi*DATA_WIDTH +: DATA_WIDTH] = act_byte(200, k, wi); end check(base_data_a === exp_a, "baseline: lane A bit-exact"); check(base_data_b === exp_b, "baseline: lane B bit-exact"); repeat(2) @(posedge clk); // simulated S_TILEREQ + S_OPERAND overhead end t_end = $time; base_cycles = (t_end - t_start) / CLK_PERIOD_NS; $display("baseline: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, base_cycles, base_cycles*1.0/N_TILES); // ============================================================ // PART 2: prefetch correctness + cycle count, EXP-0083 job-level // start, poll tile_valid, 2-cycle simulated compute, consume // ============================================================ $display("=== PART 2: prefetch (ddr_prefetch_mgr.v, EXP-0083/0084) ==="); sel = SEL_PF; @(posedge clk); pf_base_a <= 0; pf_base_b <= 200; pf_n_tiles <= N_TILES[15:0]; pf_job_start <= 1'b1; t_start = $time; @(posedge clk); pf_job_start <= 1'b0; for (k = 0; k < N_TILES; k = k + 1) begin // #1 settle delay: without it, this check can run in the same // simulation delta as the PREVIOUS iteration's tile_consume // pulse being sampled by the DUT (both triggered off the same // @(posedge clk)), reading pf_tile_valid/bank_valid BEFORE the // DUT's own nonblocking update for that consume has been // applied -- a real testbench race, not an RTL bug (EXP-0083). #1; while (!pf_tile_valid) @(posedge clk); for (wi = 0; wi < P_IN; wi = wi + 1) begin exp_a[wi*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, k, wi); exp_b[wi*DATA_WIDTH +: DATA_WIDTH] = act_byte(200, k, wi); end check(pf_data_a === exp_a, "prefetch: lane A bit-exact"); check(pf_data_b === exp_b, "prefetch: lane B bit-exact"); repeat(2) @(posedge clk); // SAME simulated compute overhead as baseline pf_tile_consume <= 1'b1; @(posedge clk); pf_tile_consume <= 1'b0; end t_end = $time; pf_cycles = (t_end - t_start) / CLK_PERIOD_NS; $display("prefetch: %0d tiles in %0d cycles (%0.2f cycles/tile)", N_TILES, pf_cycles, pf_cycles*1.0/N_TILES); $display("=== cycle comparison on this fixed-latency synthetic backend: baseline=%0d cycles, prefetch=%0d cycles, reduction=%0.1f%% (sanity check only -- the real, trustworthy number is EXP-0083's real-DDR3-backend measurement, 2.86%%) ===", base_cycles, pf_cycles, 100.0*(base_cycles-pf_cycles)/base_cycles); check(pf_cycles <= base_cycles, "prefetch is not slower than baseline (sanity check)"); $display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors); if (errors == 0) $display("ALL TESTS PASSED (tb_ddr_prefetch_mgr)"); $finish; end endmodule