// tb_storage_xsim.v - Vivado xsim testbench for s_bram and sd_bram // // Tests both single-port (s_bram) and simple dual-port (sd_bram) BRAM // using $readmemh file-based test vectors. // // Vector format (64 bits = 16 hex chars per line): // bit [63] : module (0=s_bram, 1=sd_bram) // bit [62] : cmd (0=write, 1=read-verify) // bits [61:56] : addr (6-bit address) // bits [55:32] : reserved // bits [31:0] : data (write data / expected read data) // // Test coverage: // - Write then read (same address) // - Write multiple addresses then read all // - Read during write on different addresses (sd_bram only) // - Address wrap (addr 63, max for A=6) // - All-zeros data edge case // // Parameters: W=32, D=64, A=6 `timescale 1ns / 1ps module tb_storage_xsim; // ================================================================ // Parameters // ================================================================ parameter W = 32; parameter D = 64; parameter A = 6; parameter VECTOR_FILE = "sync_rtl/storage/TB/vectors/storage_input.hex"; parameter RESULT_FILE = "sync_rtl/storage/TB/vectors/storage_result.hex"; parameter MAX_VECTORS = 256; parameter TIMEOUT_CYCLES = 2000; // ================================================================ // DUT signals — s_bram (single-port) // ================================================================ reg clk; reg rst_n; reg s_rd_en; reg [A-1:0] s_rd_addr; wire [W-1:0] s_rd_data; reg s_wr_en; reg [A-1:0] s_wr_addr; reg [W-1:0] s_wr_data; // ================================================================ // DUT signals — sd_bram (simple dual-port) // ================================================================ reg [A-1:0] sd_rd_addr; wire [W-1:0] sd_rd_data; reg sd_wr_en; reg [A-1:0] sd_wr_addr; reg [W-1:0] sd_wr_data; // ================================================================ // DUT instantiations // ================================================================ s_bram #(.W(W), .D(D), .A(A)) u_sbram ( .clk (clk), .rd_en (s_rd_en), .rd_addr (s_rd_addr), .rd_data (s_rd_data), .wr_en (s_wr_en), .wr_addr (s_wr_addr), .wr_data (s_wr_data) ); sd_bram #(.W(W), .D(D), .A(A)) u_sdbram ( .clk (clk), .rd_addr (sd_rd_addr), .rd_data (sd_rd_data), .wr_en (sd_wr_en), .wr_addr (sd_wr_addr), .wr_data (sd_wr_data) ); // ================================================================ // Clock generation: 100 MHz (10 ns period) // ================================================================ initial clk = 1'b0; always #5 clk = ~clk; // ================================================================ // Vector memory (loaded by $readmemh) // ================================================================ reg [63:0] vector_mem [0:MAX_VECTORS-1]; integer vec_count; integer idx; integer cycle_count; integer result_fd; integer pass_count; integer fail_count; // ================================================================ // Main test sequence // ================================================================ initial begin // Load vectors from hex file vec_count = 0; $readmemh(VECTOR_FILE, vector_mem); // Count non-X entries begin integer found_end; found_end = 0; for (idx = 0; idx < MAX_VECTORS; idx = idx + 1) begin if (!found_end && (vector_mem[idx] === 64'hx || vector_mem[idx] === 64'hz)) found_end = 1; else if (!found_end) vec_count = vec_count + 1; end end if (vec_count == 0) begin $display("ERROR: No vectors loaded from %s", VECTOR_FILE); $display(" Check that the file exists."); $display(" Format: 16 hex chars = {module, cmd, addr, reserved, data}"); $finish; end $display("INFO: Loaded %0d test vectors from %s", vec_count, VECTOR_FILE); // Open result file result_fd = $fopen(RESULT_FILE, "w"); if (result_fd == 0) begin $display("ERROR: Cannot open result file: %s", RESULT_FILE); $finish; end // Initialize all DUT inputs s_rd_en <= 1'b0; s_rd_addr <= {A{1'b0}}; s_wr_en <= 1'b0; s_wr_addr <= {A{1'b0}}; s_wr_data <= {W{1'b0}}; sd_rd_addr <= {A{1'b0}}; sd_wr_en <= 1'b0; sd_wr_addr <= {A{1'b0}}; sd_wr_data <= {W{1'b0}}; // Reset sequence: rst_n low for 3 cycles, then high rst_n <= 1'b0; repeat (3) @(posedge clk); rst_n <= 1'b1; @(posedge clk); pass_count = 0; fail_count = 0; // ============================================================ // Process each vector // ============================================================ for (idx = 0; idx < vec_count; idx = idx + 1) begin begin reg vec_module; reg vec_cmd; reg [A-1:0] vec_addr; reg [W-1:0] vec_data; vec_module = vector_mem[idx][63]; vec_cmd = vector_mem[idx][62]; vec_addr = vector_mem[idx][61:56]; vec_data = vector_mem[idx][31:0]; if (vec_module == 1'b0) begin // ── s_bram ────────────────────────────── if (vec_cmd == 1'b0) begin // Write s_wr_en <= 1'b1; s_wr_addr <= vec_addr; s_wr_data <= vec_data; s_rd_en <= 1'b0; s_rd_addr <= {A{1'b0}}; @(posedge clk); s_wr_en <= 1'b0; end else begin // Read-verify s_rd_en <= 1'b1; s_rd_addr <= vec_addr; s_wr_en <= 1'b0; s_wr_addr <= {A{1'b0}}; s_wr_data <= {W{1'b0}}; @(posedge clk); // 1-cycle read latency // rd_data valid now if (s_rd_data !== vec_data) begin $display("FAIL: s_bram addr=%0d expected=0x%08h got=0x%08h", vec_addr, vec_data, s_rd_data); $fwrite(result_fd, "FAIL: s_bram addr=%0d exp=0x%08h got=0x%08h\n", vec_addr, vec_data, s_rd_data); fail_count = fail_count + 1; end else begin pass_count = pass_count + 1; end s_rd_en <= 1'b0; end end else begin // ── sd_bram ───────────────────────────── if (vec_cmd == 1'b0) begin // Write sd_wr_en <= 1'b1; sd_wr_addr <= vec_addr; sd_wr_data <= vec_data; @(posedge clk); sd_wr_en <= 1'b0; end else begin // Read-verify: drive read address, wait 1 cycle sd_rd_addr <= vec_addr; @(posedge clk); // 1-cycle read latency // rd_data valid now if (sd_rd_data !== vec_data) begin $display("FAIL: sd_bram addr=%0d expected=0x%08h got=0x%08h", vec_addr, vec_data, sd_rd_data); $fwrite(result_fd, "FAIL: sd_bram addr=%0d exp=0x%08h got=0x%08h\n", vec_addr, vec_data, sd_rd_data); fail_count = fail_count + 1; end else begin pass_count = pass_count + 1; end end end end // inner begin block end // ============================================================ // Edge-case tests (hardcoded, beyond hex vectors) // ============================================================ // ── s_bram: write-priority (rd_en + wr_en both high) ── begin $display("INFO: s_bram write-priority test"); // Write 0xCAFECAFE to addr 5 s_wr_en <= 1'b1; s_wr_addr <= 6'd5; s_wr_data <= 32'hCAFECAFE; s_rd_en <= 1'b1; s_rd_addr <= 6'd5; // same addr, rd also asserted @(posedge clk); s_wr_en <= 1'b0; // Write should win — memory now has CAFECAFE at addr 5 // Read back to verify s_rd_en <= 1'b1; s_rd_addr <= 6'd5; @(posedge clk); if (s_rd_data !== 32'hCAFECAFE) begin $display("FAIL: s_bram write-priority (expected CAFECAFE, got 0x%08h)", s_rd_data); fail_count = fail_count + 1; end else begin $display("PASS: s_bram write-priority"); pass_count = pass_count + 1; end s_rd_en <= 1'b0; end // ── sd_bram: concurrent read-during-write (different addresses) ── begin $display("INFO: sd_bram read-during-write test"); // Pre-fill addr 10 with known value sd_wr_en <= 1'b1; sd_wr_addr <= 6'd10; sd_wr_data <= 32'h12345678; @(posedge clk); sd_wr_en <= 1'b0; // Now write addr 20 while reading addr 10 (different addrs) sd_wr_en <= 1'b1; sd_wr_addr <= 6'd20; sd_wr_data <= 32'hFEEDFACE; sd_rd_addr <= 6'd10; @(posedge clk); sd_wr_en <= 1'b0; // rd_data should be OLD value at addr 10 (12345678) if (sd_rd_data !== 32'h12345678) begin $display("FAIL: sd_bram rd-during-wr: expected 0x12345678, got 0x%08h", sd_rd_data); fail_count = fail_count + 1; end else begin $display("PASS: sd_bram read-during-write (old data)"); pass_count = pass_count + 1; end // Verify addr 20 got new data sd_rd_addr <= 6'd20; @(posedge clk); if (sd_rd_data !== 32'hFEEDFACE) begin $display("FAIL: sd_bram wr-during-rd: expected 0xFEEDFACE at addr 20, got 0x%08h", sd_rd_data); fail_count = fail_count + 1; end else begin $display("PASS: sd_bram write-during-read (new data)"); pass_count = pass_count + 1; end end // ── sd_bram: concurrent read-during-write (SAME address) ── begin $display("INFO: sd_bram read-during-write (same address)"); sd_wr_en <= 1'b1; sd_wr_addr <= 6'd30; sd_wr_data <= 32'hAAAAAAAA; sd_rd_addr <= 6'd30; // same address @(posedge clk); sd_wr_en <= 1'b0; // On sd_bram, mem write happens at posedge, rd_addr is registered // So rd_data gets OLD mem[30] (before write), not new AAAAAAAA // We just verify the read port didn't get X if (sd_rd_data === 32'hx || sd_rd_data === 32'hz) begin $display("FAIL: sd_bram rd-during-wr same addr: got X/Z"); fail_count = fail_count + 1; end else begin $display("PASS: sd_bram read-during-write (same addr, no X)"); pass_count = pass_count + 1; end // Now read addr 30 to verify write took effect sd_rd_addr <= 6'd30; @(posedge clk); if (sd_rd_data !== 32'hAAAAAAAA) begin $display("FAIL: sd_bram same-addr write: expected 0xAAAAAAAA, got 0x%08h", sd_rd_data); fail_count = fail_count + 1; end else begin $display("PASS: sd_bram same-addr write verified"); pass_count = pass_count + 1; end end // ── s_bram: address wrap / all-zeros data ── begin $display("INFO: s_bram address wrap test"); // Write to max address (63) and read back s_wr_en <= 1'b1; s_wr_addr <= 6'd63; s_wr_data <= 32'hFFFF0000; @(posedge clk); s_wr_en <= 1'b0; s_rd_en <= 1'b1; s_rd_addr <= 6'd63; @(posedge clk); if (s_rd_data !== 32'hFFFF0000) begin $display("FAIL: s_bram addr 63: expected 0xFFFF0000, got 0x%08h", s_rd_data); fail_count = fail_count + 1; end else begin $display("PASS: s_bram addr 63 (max)"); pass_count = pass_count + 1; end s_rd_en <= 1'b0; // Write all-zeros to addr 0 and read back s_wr_en <= 1'b1; s_wr_addr <= 6'd0; s_wr_data <= 32'h00000000; @(posedge clk); s_wr_en <= 1'b0; s_rd_en <= 1'b1; s_rd_addr <= 6'd0; @(posedge clk); if (s_rd_data !== 32'h00000000) begin $display("FAIL: s_bram all-zeros: expected 0x00000000, got 0x%08h", s_rd_data); fail_count = fail_count + 1; end else begin $display("PASS: s_bram all-zeros data"); pass_count = pass_count + 1; end s_rd_en <= 1'b0; end // ============================================================ // Summary // ============================================================ $fclose(result_fd); $display("========================================"); $display("STORAGE TEST COMPLETE"); $display(" Total vectors: %0d", vec_count); $display(" Edge-case tests: %0d", 7); // hardcoded count above $display(" Passed: %0d", pass_count); $display(" Failed: %0d", fail_count); $display(" Results written to: %s", RESULT_FILE); if (fail_count == 0) $display(" RESULT: ALL TESTS PASSED"); else $display(" RESULT: SOME TESTS FAILED"); $display("========================================"); $finish; end // ================================================================ // Timeout watchdog // ================================================================ initial begin #(TIMEOUT_CYCLES * 10 * 100); $display("FATAL: Global simulation timeout reached"); $finish; end endmodule