// 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
