// Standalone Verilator testbench for LDPC decoder // Tests the decoder core directly via Wishbone (no Caravel dependency) // // Test 1: Read VERSION register (expect 0x1D010001) // Test 2: Decode all-zero codeword with strong +31 LLRs `timescale 1ns / 1ps module tb_ldpc_decoder; // ========================================================================= // 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_decoder.vcd"); $dumpvars(0, tb_ldpc_decoder); end // ========================================================================= // Watchdog timeout // ========================================================================= int cycle_cnt; initial begin cycle_cnt = 0; forever begin @(posedge clk); cycle_cnt++; if (cycle_cnt > 100000) begin $display("TIMEOUT: exceeded 100000 cycles"); $finish; end end end // ========================================================================= // Wishbone tasks // ========================================================================= 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; logic [31:0] rd_data; // ========================================================================= // 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); // ================================================================= // TEST 1: Read VERSION register // ================================================================= $display("[TEST 1] Read VERSION register"); wb_read(8'h54, rd_data); if (rd_data === 32'h1D01_0001) begin $display(" PASS: VERSION = 0x%08X", rd_data); pass_cnt++; end else begin $display(" FAIL: VERSION = 0x%08X (expected 0x1D010001)", rd_data); fail_cnt++; end // ================================================================= // TEST 2: Decode clean all-zero codeword // ================================================================= $display("[TEST 2] Decode clean all-zero codeword"); // Write 52 LLR words at addresses 0x10..0xDC // Each word = 5x +31 packed: {6'h1F, 6'h1F, 6'h1F, 6'h1F, 6'h1F} // = 0x1F | (0x1F<<6) | (0x1F<<12) | (0x1F<<18) | (0x1F<<24) // = 0x1F7DF7DF begin int i; for (i = 0; i < 52; i++) begin wb_write(8'h10 + i * 4, 32'h1F7D_F7DF); end end // Start decode: write CTRL // bit[0]=1 (start), bit[1]=1 (early_term), bits[12:8]=0x1E=30 (max_iter) // 0x00001E03 wb_write(8'h00, 32'h0000_1E03); // Poll STATUS (addr 0x04) until busy (bit[0]) = 0 // Allow a few cycles for busy to assert first repeat (5) @(posedge clk); begin int poll_cnt; poll_cnt = 0; do begin wb_read(8'h04, rd_data); poll_cnt++; if (poll_cnt > 10000) begin $display(" FAIL: decoder stuck busy after %0d polls", poll_cnt); fail_cnt++; $display("=== %0d PASSED, %0d FAILED ===", pass_cnt, fail_cnt); $finish; end end while (rd_data[0] == 1'b1); end // Check convergence: bit[1] of STATUS if (rd_data[1] == 1'b1) begin $display(" converged=1 (OK)"); end else begin $display(" FAIL: converged=0 (expected 1)"); fail_cnt++; end // Check syndrome weight: bits[23:16] of STATUS if (rd_data[23:16] == 8'd0) begin $display(" syndrome_weight=0 (OK)"); end else begin $display(" FAIL: syndrome_weight=%0d (expected 0)", rd_data[23:16]); fail_cnt++; end // Check iterations used: bits[12:8] of STATUS $display(" iterations_used=%0d", rd_data[12:8]); // Read DECODED register (addr 0x50) wb_read(8'h50, rd_data); if (rd_data === 32'h0000_0000) begin $display(" PASS: decoded=0x%08X", rd_data); pass_cnt++; end else begin $display(" FAIL: decoded=0x%08X (expected 0x00000000)", rd_data); fail_cnt++; end // ================================================================= // Summary // ================================================================= $display(""); if (fail_cnt == 0) begin $display("=== ALL %0d TESTS PASSED ===", pass_cnt); end else begin $display("=== %0d PASSED, %0d FAILED ===", pass_cnt, fail_cnt); end $finish; end endmodule