// Vector-driven Verilator testbench for LDPC decoder // Loads test vectors from hex files generated by model/gen_verilator_vectors.py // Verifies RTL decoder produces bit-exact results matching Python behavioral model // // Files loaded: // vectors/llr_words.hex - 52 words per vector, packed 5x6-bit LLRs // vectors/expected.hex - 4 lines per vector: decoded_word, converged, iterations, syndrome_weight // vectors/num_vectors.txt - single line with vector count (read at generation time) `timescale 1ns / 1ps module tb_ldpc_vectors; // ========================================================================= // Parameters // ========================================================================= localparam int NUM_VECTORS = 20; localparam int LLR_WORDS = 52; // 256 LLRs / 5 per word, rounded up localparam int EXPECTED_LINES = 4; // per vector: decoded, converged, iter, syn_wt // Wishbone register addresses (byte-addressed) localparam logic [7:0] REG_CTRL = 8'h00; localparam logic [7:0] REG_STATUS = 8'h04; localparam logic [7:0] REG_LLR_BASE = 8'h10; localparam logic [7:0] REG_DECODED = 8'h50; localparam logic [7:0] REG_VERSION = 8'h54; // CTRL register fields localparam int MAX_ITER = 30; // ========================================================================= // Clock and reset // ========================================================================= logic clk; logic rst_n; logic wb_cyc_i; logic wb_stb_i; logic wb_we_i; logic [7:0] wb_adr_i; logic [31:0] wb_dat_i; logic [31:0] wb_dat_o; logic wb_ack_o; logic irq_o; // 50 MHz clock (20 ns period) initial clk = 0; always #10 clk = ~clk; // ========================================================================= // DUT instantiation // ========================================================================= ldpc_decoder_top dut ( .clk (clk), .rst_n (rst_n), .wb_cyc_i (wb_cyc_i), .wb_stb_i (wb_stb_i), .wb_we_i (wb_we_i), .wb_adr_i (wb_adr_i), .wb_dat_i (wb_dat_i), .wb_dat_o (wb_dat_o), .wb_ack_o (wb_ack_o), .irq_o (irq_o) ); // ========================================================================= // VCD dump // ========================================================================= initial begin $dumpfile("tb_ldpc_vectors.vcd"); $dumpvars(0, tb_ldpc_vectors); end // ========================================================================= // Watchdog timeout (generous for 20 vectors * 30 iterations each) // ========================================================================= int cycle_cnt; initial begin cycle_cnt = 0; forever begin @(posedge clk); cycle_cnt++; if (cycle_cnt > 2000000) begin $display("TIMEOUT: exceeded 2000000 cycles"); $finish; end end end // ========================================================================= // Test vector memory // ========================================================================= // LLR words: 52 words per vector, total 52 * NUM_VECTORS = 1040 logic [31:0] llr_mem [LLR_WORDS * NUM_VECTORS]; // Expected results: 4 words per vector, total 4 * NUM_VECTORS = 80 logic [31:0] expected_mem [EXPECTED_LINES * NUM_VECTORS]; initial begin $readmemh("vectors/llr_words.hex", llr_mem); $readmemh("vectors/expected.hex", expected_mem); end // ========================================================================= // Wishbone tasks (same as standalone testbench) // ========================================================================= task automatic wb_write(input logic [7:0] addr, input logic [31:0] data); @(posedge clk); wb_cyc_i = 1'b1; wb_stb_i = 1'b1; wb_we_i = 1'b1; wb_adr_i = addr; wb_dat_i = data; // Wait for ack do begin @(posedge clk); end while (!wb_ack_o); // Deassert wb_cyc_i = 1'b0; wb_stb_i = 1'b0; wb_we_i = 1'b0; endtask task automatic wb_read(input logic [7:0] addr, output logic [31:0] data); @(posedge clk); wb_cyc_i = 1'b1; wb_stb_i = 1'b1; wb_we_i = 1'b0; wb_adr_i = addr; // Wait for ack do begin @(posedge clk); end while (!wb_ack_o); data = wb_dat_o; // Deassert wb_cyc_i = 1'b0; wb_stb_i = 1'b0; endtask // ========================================================================= // Test variables // ========================================================================= int pass_cnt; int fail_cnt; int vec_pass; // per-vector pass flag logic [31:0] rd_data; // Expected values for current vector logic [31:0] exp_decoded; logic [31:0] exp_converged; logic [31:0] exp_iterations; logic [31:0] exp_syndrome_wt; // Actual values from RTL logic [31:0] act_decoded; logic act_converged; logic [4:0] act_iter_used; logic [7:0] act_syndrome_wt; // ========================================================================= // Main test sequence // ========================================================================= initial begin pass_cnt = 0; fail_cnt = 0; // Initialize Wishbone signals wb_cyc_i = 1'b0; wb_stb_i = 1'b0; wb_we_i = 1'b0; wb_adr_i = 8'h00; wb_dat_i = 32'h0; // Reset rst_n = 1'b0; repeat (10) @(posedge clk); rst_n = 1'b1; repeat (5) @(posedge clk); // ================================================================= // Sanity check: Read VERSION register // ================================================================= $display("=== LDPC Vector-Driven Testbench ==="); $display("Vectors: %0d, LLR words/vector: %0d", NUM_VECTORS, LLR_WORDS); $display(""); wb_read(REG_VERSION, rd_data); if (rd_data === 32'h1D01_0001) begin $display("[SANITY] VERSION = 0x%08X (OK)", rd_data); end else begin $display("[SANITY] VERSION = 0x%08X (UNEXPECTED, expected 0x1D010001)", rd_data); end $display(""); // ================================================================= // Process each test vector // ================================================================= for (int v = 0; v < NUM_VECTORS; v++) begin vec_pass = 1; // Load expected values exp_decoded = expected_mem[v * EXPECTED_LINES + 0]; exp_converged = expected_mem[v * EXPECTED_LINES + 1]; exp_iterations = expected_mem[v * EXPECTED_LINES + 2]; exp_syndrome_wt = expected_mem[v * EXPECTED_LINES + 3]; $display("[VEC %0d] Expected: decoded=0x%08X, converged=%0d, iter=%0d, syn_wt=%0d", v, exp_decoded, exp_converged[0], exp_iterations, exp_syndrome_wt); // --------------------------------------------------------- // Step 1: Write 52 LLR words via Wishbone // --------------------------------------------------------- for (int w = 0; w < LLR_WORDS; w++) begin wb_write(REG_LLR_BASE + w * 4, llr_mem[v * LLR_WORDS + w]); end // --------------------------------------------------------- // Step 2: Start decode // CTRL: bit[0]=start, bit[1]=early_term, bits[12:8]=max_iter // max_iter=30 -> 0x1E, so CTRL = 0x00001E03 // --------------------------------------------------------- wb_write(REG_CTRL, {19'b0, 5'(MAX_ITER), 6'b0, 1'b1, 1'b1}); // Wait a few cycles for busy to assert repeat (5) @(posedge clk); // --------------------------------------------------------- // Step 3: Poll STATUS until busy=0 // --------------------------------------------------------- begin int poll_cnt; poll_cnt = 0; do begin wb_read(REG_STATUS, rd_data); poll_cnt++; if (poll_cnt > 50000) begin $display(" FAIL: decoder stuck busy after %0d polls", poll_cnt); fail_cnt++; $display(""); $display("=== ABORTED: %0d PASSED, %0d FAILED ===", pass_cnt, fail_cnt); $finish; end end while (rd_data[0] == 1'b1); end // --------------------------------------------------------- // Step 4: Read results // --------------------------------------------------------- // STATUS fields (from last poll read) act_converged = rd_data[1]; act_iter_used = rd_data[12:8]; act_syndrome_wt = rd_data[23:16]; // Read DECODED register wb_read(REG_DECODED, act_decoded); $display(" Actual: decoded=0x%08X, converged=%0d, iter=%0d, syn_wt=%0d", act_decoded, act_converged, act_iter_used, act_syndrome_wt); // --------------------------------------------------------- // Step 5: Compare results // --------------------------------------------------------- if (exp_converged[0]) begin // CONVERGED vector: decoded_word MUST match (bit-exact) if (act_decoded !== exp_decoded) begin $display(" FAIL: decoded mismatch (expected 0x%08X, got 0x%08X)", exp_decoded, act_decoded); vec_pass = 0; end // Converged: RTL must also report converged if (!act_converged) begin $display(" FAIL: RTL did not converge (Python model converged)"); vec_pass = 0; end // Converged: syndrome weight must be 0 if (act_syndrome_wt !== 8'd0) begin $display(" FAIL: syndrome_weight=%0d (expected 0 for converged)", act_syndrome_wt); vec_pass = 0; end // Iteration count: informational (allow +/- 2 tolerance) if (act_iter_used > exp_iterations[4:0] + 2 || (exp_iterations[4:0] > 2 && act_iter_used < exp_iterations[4:0] - 2)) begin $display(" NOTE: iteration count differs (expected %0d, got %0d)", exp_iterations, act_iter_used); end end else begin // NON-CONVERGED vector // Decoded word comparison is informational only if (act_decoded !== exp_decoded) begin $display(" INFO: decoded differs from Python model (expected for non-converged)"); end // Convergence status: RTL should also report non-converged if (act_converged) begin // Interesting: RTL converged but Python didn't. Could happen with // fixed-point vs floating-point differences. Report but don't fail. $display(" NOTE: RTL converged but Python model did not"); end // Syndrome weight should be non-zero for non-converged if (!act_converged && act_syndrome_wt == 8'd0) begin $display(" FAIL: syndrome_weight=0 but converged=0 (inconsistent)"); vec_pass = 0; end end // --------------------------------------------------------- // Step 6: Record result // --------------------------------------------------------- if (vec_pass) begin $display(" PASS"); pass_cnt++; end else begin $display(" FAIL"); fail_cnt++; end $display(""); end // for each vector // ================================================================= // Summary // ================================================================= $display("=== RESULTS: %0d PASSED, %0d FAILED out of %0d vectors ===", pass_cnt, fail_cnt, NUM_VECTORS); if (fail_cnt == 0) begin $display("=== ALL VECTORS PASSED ==="); end else begin $display("=== SOME VECTORS FAILED ==="); end $finish; end endmodule