// Focused proof for the runtime CLUT staging transaction.
`timescale 1ns/1ps

module tb_clut_stage_cdc;
    logic bclk = 1'b0, dclk = 1'b0;
    always #5 bclk = ~bclk;
    always #7 dclk = ~dclk;

    logic breset_n, dreset_n;
    logic b_we;
    logic [7:0] b_waddr;
    logic [31:0] b_wdata;
    logic b_commit_tgl;
    logic [31:0] b_expected_crc;
    logic clut_wr_en;
    logic [7:0] clut_wr_idx;
    logic [31:0] clut_wr_data;
    logic busy, done_tgl;
    logic [31:0] crc;
    logic [31:0] observed [0:255];
    logic [31:0] expected [0:255];
    logic seen_done;
    int writes, errors;
    logic [31:0] expected_sum;

    clut_stage_cdc u_dut (
        .bclk, .breset_n, .b_we, .b_waddr, .b_wdata, .b_commit_tgl,
        .b_expected_crc, .dclk, .dreset_n, .clut_wr_en, .clut_wr_idx,
        .clut_wr_data, .busy, .done_tgl, .crc
    );

    always_ff @(posedge dclk) begin
        if (clut_wr_en) begin
            observed[clut_wr_idx] <= clut_wr_data;
            writes <= writes + 1;
        end
    end

    task automatic check(input string label, input logic got, input logic want);
        if (got !== want) begin
            $error("[%s] got %b expected %b", label, got, want);
            errors = errors + 1;
        end
    endtask

    task automatic stage_word(input logic [7:0] addr, input logic [31:0] data);
        @(posedge bclk);
        b_waddr <= addr;
        b_wdata <= data;
        b_we    <= 1'b1;
        @(posedge bclk);
        b_we    <= 1'b0;
    endtask

    initial begin
        breset_n = 1'b0;
        dreset_n = 1'b0;
        b_we = 1'b0;
        b_waddr = 8'd0;
        b_wdata = 32'd0;
        b_commit_tgl = 1'b0;
        b_expected_crc = 32'd0;
        writes = 0;
        errors = 0;
        seen_done = 1'b0;
        expected_sum = 32'd0;
        for (int i = 0; i < 256; i++) begin
            expected[i] = 32'h3C000000 ^ (i * 32'h01020409);
            observed[i] = 32'd0;
            expected_sum = expected_sum + expected[i];
        end

        repeat (3) @(posedge bclk);
        breset_n = 1'b1;
        repeat (3) @(posedge dclk);
        dreset_n = 1'b1;

        for (int i = 0; i < 256; i++)
            stage_word(i[7:0], expected[i]);

        b_expected_crc = expected_sum;
        @(posedge bclk);
        b_commit_tgl <= ~b_commit_tgl;
        @(posedge bclk);

        // A second commit during the copy is intentionally ignored.
        wait (busy);
        @(posedge bclk);
        b_commit_tgl <= ~b_commit_tgl;
        @(posedge bclk);

        wait (done_tgl != seen_done);
        seen_done = done_tgl;
        repeat (4) @(posedge dclk);
        check("busy_after_copy", busy, 1'b0);
        if (writes != 256) begin
            $error("[write_count] got %0d expected 256", writes);
            errors = errors + 1;
        end
        if (crc !== expected_sum) begin
            $error("[sum32] got 0x%08x expected 0x%08x", crc, expected_sum);
            errors = errors + 1;
        end
        for (int i = 0; i < 256; i++) begin
            if (observed[i] !== expected[i]) begin
                $error("[entry %0d] got 0x%08x expected 0x%08x", i, observed[i], expected[i]);
                errors = errors + 1;
            end
        end
        repeat (12) @(posedge dclk);
        if (writes != 256) begin
            $error("[busy_commit_ignored] writes=%0d expected 256", writes);
            errors = errors + 1;
        end
        if (errors != 0) $fatal(1, "tb_clut_stage_cdc FAILED: %0d errors", errors);
        $display("tb_clut_stage_cdc PASS: 256 entries, sum32=0x%08x", crc);
        $finish;
    end
endmodule
