`timescale 1ns/1ps // ============================================================ // EXP-0054 -- isolated correctness + real measured speedup for // sdram_controller_openrow.v. Two DUTs share the exact same // transaction sequence, each with its own sdram_model.v instance: // dut_base : sdram_controller.v (today's real, unchanged baseline // -- always auto-precharges). // dut_openrow : sdram_controller_openrow.v (new, page-hit policy). // // Covers: // 1) correctness battery (write->read, sequential/bank-sweep/ // pseudo-random), bit-exact vs the same golden pattern, on BOTH // DUTs. // 2) SAME-ROW consecutive access (the real production pattern: // weight_prefetch_engine_wide.v's own strictly sequential tile // stream) -- bit-exact AND real measured cycle savings vs // baseline. // 3) DIFFERENT-ROW access immediately after a row is open -- must // stay bit-exact and NOT regress vs baseline (on-demand precharge // pays the same total cost, just deferred). // 4) explicit read-after-read / write-after-read / read-after-write // / write-after-write SAME-ROW turnaround sequences -- the one // hazard class sdram_model.v does NOT itself assert (no tCCD/ // tRTW/tWTR check in that model -- see sdram_controller_ // openrow.v's own header) -- checked here for DATA correctness, // which is the strongest check available without a turnaround- // timing-aware reference model. // 5) refresh spanning while a row is open -- watches for ANY // VIOLATION/WARNING from sdram_model.v (this project's own real // command-sequence checker) across many iterations, specifically // exercising the new precharge-before-refresh path. // 6) REAL measured total-cycle comparison over a long, strictly // sequential same-row run -- the actual production access // pattern (weight_prefetch_engine_wide.v), the number this // experiment exists to produce. // ============================================================ module tb; localparam BURST_LEN = 8; localparam ROW_BITS = 13; localparam COL_BITS = 10; localparam BANK_BITS = 2; localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; localparam ALIGN_BITS = (BURST_LEN<=1) ? 0 : $clog2(BURST_LEN); localparam CLK_FREQ_MHZ = 64; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; reg clk = 0; always #(CLK_PERIOD_NS/2.0) clk = ~clk; reg rst; integer cyc; always @(posedge clk) if (!rst) cyc <= cyc + 1; function automatic [BANK_BITS-1:0] bank_of; input [ADDR_WIDTH-1:0] a; begin bank_of = a[ALIGN_BITS +: BANK_BITS]; end endfunction function automatic [ROW_BITS-1:0] row_of; input [ADDR_WIDTH-1:0] a; begin row_of = a[ALIGN_BITS+BANK_BITS +: ROW_BITS]; end endfunction // ================= DUT BASE (today's real baseline) ================= reg reqA, wrA; reg [ADDR_WIDTH-1:0] addrA; reg [16*BURST_LEN-1:0] wdataA; reg [2*BURST_LEN-1:0] wmaskA; wire [16*BURST_LEN-1:0] rdataA; wire readyA, busyA; wire cke_A, cs_A, ras_A, cas_A, we_A; wire [BANK_BITS-1:0] ba_A; wire [ROW_BITS-1:0] a_A; wire [15:0] dq_A; wire [1:0] dqm_A; sdram_controller #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) dut_base ( .clk(clk), .rst(rst), .req(reqA), .wr(wrA), .addr(addrA), .wdata(wdataA), .wmask(wmaskA), .rdata(rdataA), .ready(readyA), .busy(busyA), .sdram_cke(cke_A), .sdram_cs_n(cs_A), .sdram_ras_n(ras_A), .sdram_cas_n(cas_A), .sdram_we_n(we_A), .sdram_ba(ba_A), .sdram_a(a_A), .sdram_dq(dq_A), .sdram_dqm(dqm_A) ); sdram_model #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) mem_base ( .clk(clk), .cke(cke_A), .cs_n(cs_A), .ras_n(ras_A), .cas_n(cas_A), .we_n(we_A), .ba(ba_A), .a(a_A), .dq(dq_A), .dqm(dqm_A) ); // ================= DUT OPENROW ================= reg reqB, wrB; reg [ADDR_WIDTH-1:0] addrB; reg [16*BURST_LEN-1:0] wdataB; reg [2*BURST_LEN-1:0] wmaskB; wire [16*BURST_LEN-1:0] rdataB; wire readyB, busyB; wire cke_B, cs_B, ras_B, cas_B, we_B; wire [BANK_BITS-1:0] ba_B; wire [ROW_BITS-1:0] a_B; wire [15:0] dq_B; wire [1:0] dqm_B; sdram_controller_openrow #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) dut_openrow ( .clk(clk), .rst(rst), .req(reqB), .wr(wrB), .addr(addrB), .wdata(wdataB), .wmask(wmaskB), .rdata(rdataB), .ready(readyB), .busy(busyB), .sdram_cke(cke_B), .sdram_cs_n(cs_B), .sdram_ras_n(ras_B), .sdram_cas_n(cas_B), .sdram_we_n(we_B), .sdram_ba(ba_B), .sdram_a(a_B), .sdram_dq(dq_B), .sdram_dqm(dqm_B) ); sdram_model #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) mem_openrow ( .clk(clk), .cke(cke_B), .cs_n(cs_B), .ras_n(ras_B), .cas_n(cas_B), .we_n(we_B), .ba(ba_B), .a(a_B), .dq(dq_B), .dqm(dqm_B) ); integer errors, tests; task automatic do_txn_A( input t_wr, input [ADDR_WIDTH-1:0] t_addr, input [16*BURST_LEN-1:0] t_wdata, output [16*BURST_LEN-1:0] t_rdata, output integer t_cycles ); integer t0; begin @(posedge clk); while (busyA) @(posedge clk); t0 = cyc; reqA = 1'b1; wrA = t_wr; addrA = t_addr; wdataA = t_wdata; wmaskA = {(2*BURST_LEN){1'b0}}; @(posedge clk); reqA = 1'b0; while (!readyA) @(posedge clk); t_rdata = rdataA; t_cycles = cyc - t0; end endtask task automatic do_txn_B( input t_wr, input [ADDR_WIDTH-1:0] t_addr, input [16*BURST_LEN-1:0] t_wdata, output [16*BURST_LEN-1:0] t_rdata, output integer t_cycles ); integer t0; begin @(posedge clk); while (busyB) @(posedge clk); t0 = cyc; reqB = 1'b1; wrB = t_wr; addrB = t_addr; wdataB = t_wdata; wmaskB = {(2*BURST_LEN){1'b0}}; @(posedge clk); reqB = 1'b0; while (!readyB) @(posedge clk); t_rdata = rdataB; t_cycles = cyc - t0; end endtask reg [16*BURST_LEN-1:0] gotA, gotB, wpat; integer elapsedA, elapsedB; task automatic check_word_both(input [ADDR_WIDTH-1:0] a, input [15:0] pattern); integer k; begin for (k = 0; k < BURST_LEN; k = k + 1) wpat[k*16 +: 16] = pattern + k[15:0]; do_txn_A(1'b1, a, wpat, gotA, elapsedA); do_txn_A(1'b0, a, {(16*BURST_LEN){1'b0}}, gotA, elapsedA); do_txn_B(1'b1, a, wpat, gotB, elapsedB); do_txn_B(1'b0, a, {(16*BURST_LEN){1'b0}}, gotB, elapsedB); tests = tests + 1; if (gotA !== wpat) begin $display("FAIL (base) addr=%0d: got=%h expected=%h", a, gotA, wpat); errors = errors + 1; end if (gotB !== wpat) begin $display("FAIL (openrow) addr=%0d: got=%h expected=%h", a, gotB, wpat); errors = errors + 1; end if (gotA === wpat && gotB === wpat) begin $display("PASS addr=%0d bank=%0d row=%0d: both bit-exact (base=%0d cyc, openrow=%0d cyc)", a, bank_of(a), row_of(a), elapsedA, elapsedB); end end endtask integer seed; integer i; reg [ADDR_WIDTH-1:0] rnd_addr; initial begin errors = 0; tests = 0; cyc = 0; seed = 32'hBADC0FFE; rst = 1; reqA = 0; wrA = 0; addrA = 0; wdataA = 0; wmaskA = 0; reqB = 0; wrB = 0; addrB = 0; wdataB = 0; wmaskB = 0; repeat(5) @(posedge clk); rst = 0; @(posedge clk); while (busyA || busyB) @(posedge clk); $display("=== TEST 1: correctness battery (base vs openrow, same golden pattern) ==="); check_word_both({ADDR_WIDTH{1'b0}}, 16'hA5A5); for (i = 0; i < 8; i = i + 1) check_word_both(i*BURST_LEN, 16'h1000 + i); for (i = 0; i < 4; i = i + 1) check_word_both((i << ALIGN_BITS) + (100 << (ALIGN_BITS+BANK_BITS)), 16'h2000 + i); for (i = 0; i < 24; i = i + 1) begin rnd_addr = ($random(seed) % ((1<