`timescale 1ns/1ps // ============================================================ // Isolated correctness + cycle-savings regression for // sdram_controller_pipelined.v, forked from tb_sdram_controller.v's // own idiom (same do_transaction task style, same sdram_model.v DUT // pairing). Adds what the original testbench cannot exercise (it // always waits for `busy` to clear before issuing the next request): // deliberately pulsing a SECOND req WHILE the controller is still // mid-transaction, to test the new shadow-pipeline slot. // // Covers: // 1) same correctness battery as the original (write->read, // sequential, all 4 banks, address limits, pseudo-random) -- // using the ORIGINAL wait-for-ready protocol throughout, so this // also proves the re-sliced address decomposition (header note // (1) in sdram_controller_pipelined.v) is a correct bijection. // 2) DIFFERENT-bank early injection: issue a second request for a // different bank while the first is still in S_CAS_WAIT, verify // both results bit-exact AND that the combined cycle count is // LOWER than 2x the serial baseline. // 3) SAME-bank consecutive (both via the normal wait-for-ready // protocol): must cost exactly the same as the original // controller, no regression. // 4) refresh spanning an early-injected interleave: run enough // interleaved pairs to cross >=1 real tREFI interval, watch for // any "VIOLATION"/"WARNING" from sdram_model.v. // ============================================================ module tb #( parameter BURST_LEN = 8, parameter CLK_FREQ_MHZ = 80 ); localparam ROW_BITS = 13; localparam COL_BITS = 10; localparam BANK_BITS = 2; localparam ADDR_WIDTH = BANK_BITS + ROW_BITS + COL_BITS; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam ALIGN_BITS = (BURST_LEN<=1) ? 0 : $clog2(BURST_LEN); reg clk = 0; always #(CLK_PERIOD_NS/2.0) clk = ~clk; reg rst; reg req, wr; reg [ADDR_WIDTH-1:0] addr; reg [16*BURST_LEN-1:0] wdata; reg [2*BURST_LEN-1:0] wmask; wire [16*BURST_LEN-1:0] rdata; wire ready, busy; wire sdram_cke, sdram_cs_n, sdram_ras_n, sdram_cas_n, sdram_we_n; wire [BANK_BITS-1:0] sdram_ba; wire [ROW_BITS-1:0] sdram_a; wire [15:0] sdram_dq; wire [1:0] sdram_dqm; sdram_controller_pipelined #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) dut ( .clk(clk), .rst(rst), .req(req), .wr(wr), .addr(addr), .wdata(wdata), .wmask(wmask), .rdata(rdata), .ready(ready), .busy(busy), .sdram_cke(sdram_cke), .sdram_cs_n(sdram_cs_n), .sdram_ras_n(sdram_ras_n), .sdram_cas_n(sdram_cas_n), .sdram_we_n(sdram_we_n), .sdram_ba(sdram_ba), .sdram_a(sdram_a), .sdram_dq(sdram_dq), .sdram_dqm(sdram_dqm) ); sdram_model #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) mem ( .clk(clk), .cke(sdram_cke), .cs_n(sdram_cs_n), .ras_n(sdram_ras_n), .cas_n(sdram_cas_n), .we_n(sdram_we_n), .ba(sdram_ba), .a(sdram_a), .dq(sdram_dq), .dqm(sdram_dqm) ); integer errors, tests; integer cyc; always @(posedge clk) if (!rst) cyc <= cyc + 1; // ---- helper: which bank a given flat word address maps to under // the PIPELINED decomposition (must match sdram_controller_ // pipelined.v's own addr_bank wire exactly) ---- function automatic [BANK_BITS-1:0] bank_of; input [ADDR_WIDTH-1:0] a; begin bank_of = a[ALIGN_BITS +: BANK_BITS]; end endfunction task automatic do_transaction( 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 (busy) @(posedge clk); t0 = cyc; req = 1'b1; wr = t_wr; addr = t_addr; wdata = t_wdata; @(posedge clk); req = 1'b0; while (!ready) @(posedge clk); t_rdata = rdata; t_cycles = cyc - t0; end endtask reg [16*BURST_LEN-1:0] got, wpat; integer elapsed; task automatic check_word(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_transaction(1'b1, a, wpat, got, elapsed); do_transaction(1'b0, a, {(16*BURST_LEN){1'b0}}, got, elapsed); tests = tests + 1; if (got !== wpat) begin $display("FAIL addr=%0d bank=%0d: got=%h expected=%h", a, bank_of(a), got, wpat); errors = errors + 1; end else begin $display("PASS addr=%0d bank=%0d: burst=%0d bit-exact, cycles=%0d", a, bank_of(a), BURST_LEN, elapsed); end end endtask // issue a request THIS cycle without waiting for busy/ready -- // the caller is responsible for knowing this is safe (shadow slot // free, or accepting fallback-to-req_pending semantics otherwise) task automatic issue_req_now(input t_wr, input [ADDR_WIDTH-1:0] t_addr, input [16*BURST_LEN-1:0] t_wdata); begin @(posedge clk); req = 1'b1; wr = t_wr; addr = t_addr; wdata = t_wdata; @(posedge clk); req = 1'b0; end endtask task automatic wait_ready(output [16*BURST_LEN-1:0] t_rdata, output integer t_cyc_at_ready); begin // always advance at least one cycle first -- otherwise two // back-to-back calls can both observe the SAME still-high // `ready` pulse from the previous call's own exit cycle // (a single-cycle-wide pulse level-checked with no // intervening clock edge looks identical to a fresh one). @(posedge clk); while (!ready) @(posedge clk); t_rdata = rdata; t_cyc_at_ready = cyc; end endtask integer seed; integer i; reg [ADDR_WIDTH-1:0] rnd_addr; initial begin errors = 0; tests = 0; cyc = 0; seed = 32'hC0FFEE; rst = 1; req = 0; wr = 0; addr = 0; wdata = 0; wmask = 0; repeat(5) @(posedge clk); rst = 0; while (busy) @(posedge clk); $display("=== TEST 1: correctness battery (original wait-for-ready protocol) ==="); check_word({ADDR_WIDTH{1'b0}}, 16'hA5A5); for (i = 0; i < 8; i = i + 1) check_word(i*BURST_LEN, 16'h1000 + i); // all 4 banks (bank now comes from LOW bits above the burst // alignment -- addr values chosen so bank_of() sweeps 0..3) for (i = 0; i < 4; i = i + 1) check_word((i << ALIGN_BITS) + (100 << (ALIGN_BITS+BANK_BITS)), 16'h2000 + i); // pseudo-random for (i = 0; i < 24; i = i + 1) begin rnd_addr = ($random(seed) % ((1< B-ready = %0d (serial baseline for 2 back-to-back BURST_LEN=%0d transactions is ~%0d; savings expected ~tRCD per pipelined pair, NOT a multiple-x speedup -- see sdram_controller_pipelined.v header)", cyc_ready1 - t0, BURST_LEN, 2*(1+2+(BURST_LEN==1?0:3+1)+ (BURST_LEN>1?BURST_LEN-1:0) +2)); end end $display("=== TEST 4: refresh spanning interleaved traffic (watch for VIOLATION/WARNING above) ==="); begin : test4 integer t0b, cyc_r0, cyc_r1, j; reg [ADDR_WIDTH-1:0] ba0, ba1; reg [16*BURST_LEN-1:0] rr0, rr1; for (j = 0; j < 60; j = j + 1) begin ba0 = ((j*3) << ALIGN_BITS); ba1 = ((j*3+1) << ALIGN_BITS); if (bank_of(ba0) == bank_of(ba1)) ba1 = ba1 + (1 << ALIGN_BITS); do_transaction(1'b1, ba0, {(16*BURST_LEN){16'hAA55}}, got, elapsed); do_transaction(1'b1, ba1, {(16*BURST_LEN){16'h55AA}}, got, elapsed); @(posedge clk); while (busy) @(posedge clk); t0b = cyc; issue_req_now(1'b0, ba0, {(16*BURST_LEN){1'b0}}); while (dut.state !== 13 && dut.state !== 7) @(posedge clk); // S_CAS_WAIT or back to S_IDLE (refresh could have won) if (dut.state === 13 && !dut.pipe_valid) issue_req_now(1'b0, ba1, {(16*BURST_LEN){1'b0}}); wait_ready(rr0, cyc_r0); if (dut.pipe_valid || dut.state != 7) wait_ready(rr1, cyc_r1); end $display(" TEST4: 60 interleaved read pairs completed (spans real tREFI at CLK_FREQ_MHZ=%0d) -- check log above for VIOLATION/WARNING", CLK_FREQ_MHZ); end $display("=== %0d/%0d tests, %0d errors (BURST_LEN=%0d, CLK_FREQ_MHZ=%0d) ===", tests-errors, tests, errors, BURST_LEN, CLK_FREQ_MHZ); if (errors == 0) $display("ALL TESTS PASSED (tb_sdram_controller_pipelined, BURST_LEN=%0d, CLK_FREQ_MHZ=%0d)", BURST_LEN, CLK_FREQ_MHZ); $finish; end endmodule