feat(tb): add Vivado XSIM Verilog testbenches for all 10 sync modules
Add file-based vector testbenches ( + ) for: - mod_add_sync, rng_sync, poly_arith_sync, comp_decomp_sync - s_bram/sd_bram, sha3_chain_top - ntt_core, poly_mul_sync - sample_cbd_sync, sample_ntt_sync Each module includes: - tb_<module>_xsim.v: Vivado XSIM testbench - gen_vectors.py: Python vector generator (stdlib only) - vectors/<module>_input.hex: test input vectors - xsim_run.tcl: compile + elaborate + simulate script
This commit is contained in:
380
sync_rtl/storage/TB/tb_storage_xsim.v
Normal file
380
sync_rtl/storage/TB/tb_storage_xsim.v
Normal file
@@ -0,0 +1,380 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user