// retroDE_ps2 — Ch350 synthetic TB: CSM1 16×16 CT32 grid CLUT load.
//
// Proves the parameter-gated CSM1-grid path in clut_loader_stub maps palette index i to the VRAM word the
// real GS grid order points at — NOT the linear i*4 word. A deliberately permuted layout makes the two
// orders disagree for most entries, so a pass can only happen if the grid swizzle is correct.
//
// Two instances share one behavioural VRAM model:
//   u_csm1 : CLUT_CSM1_ENABLE=1, driven with csm=0 (CSM1) → must read via the grid offset.
//   u_csm2 : CLUT_CSM1_ENABLE=1, driven with csm=1 (CSM2) → must STILL read linear i*4 (regression guard:
//            enabling the param must not perturb CSM2 behaviour).
// A third check: u_csm1 driven with csm=0 but CLUT_CSM1_ENABLE=0 (separate instance u_off) must NOT load at
// all (load_busy never asserts) — byte-identical-to-legacy proof.
`timescale 1ns/1ps

module tb_clut_loader_csm1;
    logic clk = 0, rst_n = 0;
    always #5 clk = ~clk;

    // ---- behavioural VRAM: 256 PSMCT32 words at CBP=0 (offsets 0..1020) ----
    logic [31:0] vram [0:255];

    // CSM1 grid offset (bytes) for entry i — MUST match clut_loader_stub.addr_offset_csm1 exactly.
    function automatic logic [31:0] off_csm1(input logic [7:0] i);
        logic [31:0] blk, by32, bx4;
        blk  = ({30'd0, i[7], i[3]}) << 8;   // block * 256
        by32 = ({29'd0, i[6:4]})     << 5;   // iy[2:0] * 32
        bx4  = ({29'd0, i[2:0]})     << 2;   // ix[2:0] * 4
        return blk + by32 + bx4;
    endfunction

    // The "logical palette" we want index i to resolve to. Place it at the GRID address for i, so that
    // reading linearly (word i) lands on a DIFFERENT logical entry for every permuted index.
    logic [31:0] expect_pal [0:255];   // grid expectation
    integer k;
    initial begin
        for (k = 0; k < 256; k = k + 1) expect_pal[k] = 32'hA0000000 | k;  // distinct per index
        // scatter expect_pal[i] into VRAM at the grid byte-offset for i
        for (k = 0; k < 256; k = k + 1) vram[off_csm1(k[7:0]) >> 2] = expect_pal[k];
    end

    // ---- DUT ports (shared stimulus) ----
    logic        tex0_wr_pulse;
    logic [13:0] tex0_cbp;
    logic [3:0]  tex0_cpsm;
    logic        tex0_csm;
    logic [4:0]  tex0_csa;
    logic [2:0]  tex0_cld;

    logic [31:0] a_csm1, a_csm2, a_off;
    logic        we_csm1, we_csm2, we_off;
    logic [7:0]  wi_csm1, wi_csm2, wi_off;
    logic [31:0] wd_csm1, wd_csm2, wd_off;
    logic        busy_csm1, busy_csm2, busy_off;

    // combinational VRAM read for each instance's addr
    wire [31:0] rd_csm1 = vram[a_csm1[9:2]];
    wire [31:0] rd_csm2 = vram[a_csm2[9:2]];
    wire [31:0] rd_off  = vram[a_off [9:2]];

    clut_loader_stub #(.CLUT_CSM1_ENABLE(1'b1)) u_csm1 (
        .clk(clk), .rst_n(rst_n), .tex0_wr_pulse(tex0_wr_pulse), .tex0_cbp(tex0_cbp),
        .tex0_cpsm(tex0_cpsm), .tex0_csm(1'b0), .tex0_csa(tex0_csa), .tex0_cld(tex0_cld),
        .vram_read_addr(a_csm1), .vram_read_data(rd_csm1),
        .clut_write_en(we_csm1), .clut_write_idx(wi_csm1), .clut_write_data(wd_csm1), .load_busy(busy_csm1));

    clut_loader_stub #(.CLUT_CSM1_ENABLE(1'b1)) u_csm2 (
        .clk(clk), .rst_n(rst_n), .tex0_wr_pulse(tex0_wr_pulse), .tex0_cbp(tex0_cbp),
        .tex0_cpsm(tex0_cpsm), .tex0_csm(1'b1), .tex0_csa(tex0_csa), .tex0_cld(tex0_cld),
        .vram_read_addr(a_csm2), .vram_read_data(rd_csm2),
        .clut_write_en(we_csm2), .clut_write_idx(wi_csm2), .clut_write_data(wd_csm2), .load_busy(busy_csm2));

    clut_loader_stub #(.CLUT_CSM1_ENABLE(1'b0)) u_off (
        .clk(clk), .rst_n(rst_n), .tex0_wr_pulse(tex0_wr_pulse), .tex0_cbp(tex0_cbp),
        .tex0_cpsm(tex0_cpsm), .tex0_csm(1'b0), .tex0_csa(tex0_csa), .tex0_cld(tex0_cld),
        .vram_read_addr(a_off), .vram_read_data(rd_off),
        .clut_write_en(we_off), .clut_write_idx(wi_off), .clut_write_data(wd_off), .load_busy(busy_off));

    // capture each instance's writes
    logic [31:0] got_csm1 [0:255];
    logic [31:0] got_csm2 [0:255];
    integer wr_off_count = 0;
    always_ff @(posedge clk) begin
        if (we_csm1) got_csm1[wi_csm1] <= wd_csm1;
        if (we_csm2) got_csm2[wi_csm2] <= wd_csm2;
        if (we_off)  wr_off_count <= wr_off_count + 1;   // must stay 0
    end

    integer i, errors = 0, permuted = 0;
    initial begin
        tex0_wr_pulse = 0; tex0_cbp = 14'd0; tex0_cpsm = 4'd0; tex0_csm = 1'b0;
        tex0_csa = 5'd0; tex0_cld = 3'd1;     // CLD=1 = always full load
        repeat (4) @(posedge clk);
        rst_n = 1;
        @(posedge clk);
        // fire one TEX0 commit pulse to all three loaders
        tex0_wr_pulse <= 1'b1; @(posedge clk); tex0_wr_pulse <= 1'b0;
        // a full load is 256 cycles; wait comfortably past it. (Can't `wait(busy==0)` — busy is already 0
        // in S_IDLE before the load starts, so that returns immediately.)
        repeat (300) @(posedge clk);

        // (1) CSM1 grid: got_csm1[i] must equal expect_pal[i]
        for (i = 0; i < 256; i = i + 1) begin
            if (got_csm1[i] !== expect_pal[i]) begin
                if (errors < 8) $display("  CSM1 MISMATCH idx %0d: got %08x exp %08x", i, got_csm1[i], expect_pal[i]);
                errors = errors + 1;
            end
        end
        // (2) CSM2 linear: got_csm2[i] must equal vram[i] (the raw linear word). Count how many indices
        //     where linear != grid — proves the test is non-trivial (the orders genuinely disagree).
        for (i = 0; i < 256; i = i + 1) begin
            if (got_csm2[i] !== vram[i]) begin
                if (errors < 16) $display("  CSM2 LINEAR BROKEN idx %0d: got %08x exp(linear) %08x", i, got_csm2[i], vram[i]);
                errors = errors + 1;
            end
            if (vram[i] !== expect_pal[i]) permuted = permuted + 1;
        end
        // (3) param OFF: csm=0 must have produced NO writes at all
        if (wr_off_count != 0) begin
            $display("  PARAM-OFF BROKEN: csm=0 produced %0d writes (expected 0)", wr_off_count);
            errors = errors + 1;
        end

        $display("[tb_clut_loader_csm1] grid-vs-linear disagreeing indices=%0d/256 (test non-triviality)", permuted);
        if (errors == 0 && permuted > 64)
            $display("[tb_clut_loader_csm1] PASS");
        else
            $display("[tb_clut_loader_csm1] FAIL errors=%0d permuted=%0d", errors, permuted);
        $finish;
    end
endmodule
