`timescale 1ns/1ps // ============================================================ // Isolated correctness test for act_tile_fetch.v -- real SDR SDRAM // placeholder backend (same precedent as tb_host_mem_bridge.v/ // tb_sdram_arbiter_n.v: verify new glue logic against the fast // backend first). Checks: (1) both lanes read back bit-exact from // their own burst-aligned tile slot; (2) different tile indices // correctly compute different burst addresses (tile_offset = // tcnt*BURST_LEN); (3) back-to-back requests (multiple tiles in a // row) all stay correct, exercising the S_GAP busy-wait logic. // ============================================================ 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; // 25 localparam CLK_FREQ_MHZ = 64; localparam CLK_PERIOD_NS = 1000.0/CLK_FREQ_MHZ; localparam DATA_WIDTH = 8; localparam P_IN = 8; 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 [16*BURST_LEN-1:0] ctrl_wdata, ctrl_rdata; wire [2*BURST_LEN-1:0] ctrl_wmask; wire ctrl_ready, ctrl_busy; wire cke, cs_n, ras_n, cas_n, we_n; wire [BANK_BITS-1:0] ba; wire [ROW_BITS-1:0] a; wire [15:0] dq; wire [1:0] dqm; sdram_controller #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .BURST_LEN(BURST_LEN), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_ctrl ( .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), .sdram_cke(cke), .sdram_cs_n(cs_n), .sdram_ras_n(ras_n), .sdram_cas_n(cas_n), .sdram_we_n(we_n), .sdram_ba(ba), .sdram_a(a), .sdram_dq(dq), .sdram_dqm(dqm) ); sdram_model #( .CLK_FREQ_MHZ(CLK_FREQ_MHZ), .ROW_BITS(ROW_BITS), .COL_BITS(COL_BITS), .BANK_BITS(BANK_BITS) ) u_mem ( .clk(clk), .cke(cke), .cs_n(cs_n), .ras_n(ras_n), .cas_n(cas_n), .we_n(we_n), .ba(ba), .a(a), .dq(dq), .dqm(dqm) ); // single requester -> tie grant = active, same precedent as // tb_host_mem_bridge.v (a 1-requester arbiter would produce this). wire req_active_dut; wire mem_grant = req_active_dut; reg req; reg [ADDR_WIDTH-1:0] base_a, base_b; reg [15:0] tcnt; wire valid; wire signed [DATA_WIDTH*P_IN-1:0] data_a, data_b; wire dut_ctrl_req, dut_ctrl_wr; wire [ADDR_WIDTH-1:0] dut_ctrl_addr; wire [16*BURST_LEN-1:0] dut_ctrl_wdata; wire [2*BURST_LEN-1:0] dut_ctrl_wmask; act_tile_fetch #( .DATA_WIDTH(DATA_WIDTH), .P_IN(P_IN), .BURST_LEN(BURST_LEN), .ADDR_WIDTH(ADDR_WIDTH) ) u_dut ( .clk(clk), .rst(rst), .req(req), .base_a(base_a), .base_b(base_b), .tcnt(tcnt), .valid(valid), .data_a(data_a), .data_b(data_b), .mem_active(req_active_dut), .mem_grant(mem_grant), .ctrl_req(dut_ctrl_req), .ctrl_wr(dut_ctrl_wr), .ctrl_addr(dut_ctrl_addr), .ctrl_wdata(dut_ctrl_wdata), .ctrl_wmask(dut_ctrl_wmask), .ctrl_rdata(ctrl_rdata), .ctrl_ready(ctrl_ready), .ctrl_busy(ctrl_busy) ); // ---- preload path: direct access to the SDRAM controller, // bypassing act_tile_fetch.v entirely, same "pre_active" mux // pattern as every other testbench in this project ---- reg pre_active; reg pre_req, pre_wr; reg [ADDR_WIDTH-1:0] pre_addr; reg [16*BURST_LEN-1:0] pre_wdata; // reroute: real DUT ctrl_* wires go through a mux so the testbench // can preload memory directly before act_tile_fetch.v ever runs. // (Re-declare the connection: DUT was wired directly above for // simplicity of the DUT instantiation; use force-free approach by // instead having the DUT's own ctrl_req/wr/addr/wdata feed the mux // inputs below and the mux feed the real controller.) assign ctrl_req = pre_active ? pre_req : dut_ctrl_req; assign ctrl_wr = pre_active ? pre_wr : dut_ctrl_wr; assign ctrl_addr = pre_active ? pre_addr : dut_ctrl_addr; assign ctrl_wdata = pre_active ? pre_wdata : dut_ctrl_wdata; assign ctrl_wmask = pre_active ? {(2*BURST_LEN){1'b0}} : dut_ctrl_wmask; task automatic sdram_write_burst(input [ADDR_WIDTH-1:0] word_addr, input [16*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 task automatic do_fetch(input [ADDR_WIDTH-1:0] ba, input [ADDR_WIDTH-1:0] bb, input [15:0] tc); begin @(posedge clk); base_a <= ba; base_b <= bb; tcnt <= tc; req <= 1'b1; @(posedge clk); req <= 1'b0; while (!valid) @(posedge clk); @(posedge clk); end endtask reg signed [DATA_WIDTH*P_IN-1:0] exp_a, exp_b; integer k, wi; reg [16*BURST_LEN-1:0] burst; initial begin errors = 0; tests = 0; rst = 1; pre_active = 1'b1; pre_req = 0; pre_wr = 0; pre_addr = 0; pre_wdata = 0; req = 0; base_a = 0; base_b = 0; tcnt = 0; repeat(5) @(posedge clk); rst = 0; @(posedge clk); while (ctrl_busy) @(posedge clk); $display("=== preload 2 bursts/lane, 2 tiles packed per burst (EXP-0081 layout) ==="); // lane A base = 0, lane B base = 100 (arbitrary, word-address units). // burst pair p holds tile 2p (low 64 bits) and tile 2p+1 (high 64 bits). for (wi = 0; wi < 2; wi = wi + 1) begin // wi = burst-pair index (0 -> tiles 0/1, 1 -> tiles 2/3) for (k = 0; k < P_IN/2; k = k + 1) burst[k*16 +: 16] = {act_byte(0, 2*wi, 2*k+1), act_byte(0, 2*wi, 2*k)}; for (k = 0; k < P_IN/2; k = k + 1) burst[(P_IN/2+k)*16 +: 16] = {act_byte(0, 2*wi+1, 2*k+1), act_byte(0, 2*wi+1, 2*k)}; sdram_write_burst(0 + wi*BURST_LEN, burst); for (k = 0; k < P_IN/2; k = k + 1) burst[k*16 +: 16] = {act_byte(100, 2*wi, 2*k+1), act_byte(100, 2*wi, 2*k)}; for (k = 0; k < P_IN/2; k = k + 1) burst[(P_IN/2+k)*16 +: 16] = {act_byte(100, 2*wi+1, 2*k+1), act_byte(100, 2*wi+1, 2*k)}; sdram_write_burst(100 + wi*BURST_LEN, burst); end @(posedge clk); pre_active = 1'b0; $display("=== TEST 1: fetch tile 0 (even -> low half), both lanes ==="); do_fetch(25'd0, 25'd100, 16'd0); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k); for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 0, k); check(data_a === exp_a, "T1: lane A tile 0 bit-exact"); check(data_b === exp_b, "T1: lane B tile 0 bit-exact"); $display("=== TEST 2: fetch tile 1 (odd -> high half, SAME burst address as tile 0) ==="); do_fetch(25'd0, 25'd100, 16'd1); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k); for (k = 0; k < P_IN; k = k + 1) exp_b[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(100, 1, k); check(data_a === exp_a, "T2: lane A tile 1 bit-exact"); check(data_b === exp_b, "T2: lane B tile 1 bit-exact"); $display("=== TEST 3: fetch tile 2 (even -> low half, NEW burst address) ==="); do_fetch(25'd0, 25'd100, 16'd2); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 2, k); check(data_a === exp_a, "T3: lane A tile 2 bit-exact (new burst)"); $display("=== TEST 4: back-to-back fetches, alternating even/odd tiles ==="); do_fetch(25'd0, 25'd100, 16'd0); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 0, k); check(data_a === exp_a, "T4a: back-to-back fetch 1 (tile 0, even), lane A correct"); do_fetch(25'd0, 25'd100, 16'd1); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 1, k); check(data_a === exp_a, "T4b: back-to-back fetch 2 (tile 1, odd), lane A correct"); do_fetch(25'd0, 25'd100, 16'd3); for (k = 0; k < P_IN; k = k + 1) exp_a[k*DATA_WIDTH +: DATA_WIDTH] = act_byte(0, 3, k); check(data_a === exp_a, "T4c: back-to-back fetch 3 (tile 3, odd, new burst), lane A correct"); $display("=== %0d/%0d tests, %0d errors ===", tests-errors, tests, errors); if (errors == 0) $display("ALL TESTS PASSED (tb_act_tile_fetch)"); $finish; end endmodule