// retroDE_ps2 — tb_gs_texture_psmt8_clut (Ch296)
//
// Focused TB for PSMT8 indexed texture sampling: gs_texture_unit fetches
// an 8-bit INDEX from VRAM (PSMT8, 1 byte/texel, 4 indices packed per
// 32-bit word) and looks it up in clut_stub to produce a PSMCT32 color
// (DECAL). This locks the four architect-required proofs at the sampler
// level (no full raster needed — the substitution gs_stub does at emit is
// a pass-through of gs_texture_unit.tex_color):
//
//   PROOF 1 — INDEX fetch is correct: for each (u,v) the sampler selects
//             the right byte lane of the fetched word (addr[1:0]).
//   PROOF 2 — CLUT lookup is correct: index -> clut_stub[index] -> the
//             expected PSMCT32 color appears on tex_color.
//   PROOF 3 — palette indirection is REAL: re-load the CLUT with a
//             different palette over the SAME index texture; the output
//             colors change while the fetched indices are identical.
//   PROOF 4 — PSMCT32 direct path is UNCHANGED: with psm==PSMCT32 the
//             fetched word IS the color (no CLUT), byte-identical to v1.
//
// The CLUT is filled here through clut_stub's TB-direct write port (the
// same port clut_loader_stub drives in the board top); the lookup uses
// the NEW second (combinational) read port the sampler consumes.

`timescale 1ns/1ps

module tb_gs_texture_psmt8_clut;

    localparam logic [5:0] PSMCT32 = 6'h00;
    localparam logic [5:0] PSMT8   = 6'h13;

    logic clk;
    logic rst_n;
    initial clk = 1'b0;
    always #5 clk = ~clk;

    // ---- a tiny combinational VRAM model (word-addressed view) ----
    // 16 KiB holds the PSMT8 index texture (base 0x800) and the PSMCT32
    // texture (base 0x2000, 256-B row stride) without overlap.
    localparam int VRAM_BYTES = 16384;
    logic [7:0] vram [0:VRAM_BYTES-1];

    // sampler read port (byte addr in, 32-bit word back, combinational).
    logic [31:0] tex_rd_addr;
    logic [31:0] tex_rd_data;
    // word-aligned little-endian read.
    assign tex_rd_data = { vram[(tex_rd_addr & ~32'd3) + 3],
                           vram[(tex_rd_addr & ~32'd3) + 2],
                           vram[(tex_rd_addr & ~32'd3) + 1],
                           vram[(tex_rd_addr & ~32'd3) + 0] };

    task automatic poke_byte(input logic [31:0] a, input logic [7:0] d);
        vram[a] = d;
    endtask
    task automatic poke_word(input logic [31:0] a, input logic [31:0] d);
        vram[a+0] = d[7:0];  vram[a+1] = d[15:8];
        vram[a+2] = d[23:16]; vram[a+3] = d[31:24];
    endtask

    // ---- DUT inputs ----
    logic        in_valid;
    logic [10:0] u, v;
    logic [31:0] tbp0_base_bytes;
    logic [13:0] tbw;
    logic [5:0]  psm;

    logic        tex_rd_en;
    logic [7:0]  clut_rd_idx;
    logic [31:0] clut_rd_data;
    logic        out_valid;
    logic [31:0] tex_color;

    gs_texture_unit #(.RD_LATENCY(1)) dut (
        .clk(clk), .rst_n(rst_n),
        .in_valid(in_valid),
        .u(u), .v(v),
        .tbp0_base_bytes(tbp0_base_bytes),
        .tbw(tbw),
        .psm(psm),
        .tex_rd_en(tex_rd_en),
        .tex_rd_addr(tex_rd_addr),
        .tex_rd_data(tex_rd_data),
        .clut_rd_idx(clut_rd_idx),
        .clut_rd_data(clut_rd_data),
        .out_valid(out_valid),
        .tex_color(tex_color)
    );

    // ---- CLUT palette table (the reused on-chip CLUT) ----
    logic        clut_we;
    logic [7:0]  clut_widx;
    logic [31:0] clut_wdata;

    clut_stub u_clut (
        .clk(clk), .rst_n(rst_n),
        .write_en (clut_we),
        .write_idx(clut_widx),
        .write_data(clut_wdata),
        .read_idx (8'd0),       // pcrtc scanout port unused here
        .read_data(),
        .tex_read_idx (clut_rd_idx),   // sampler lookup
        .tex_read_data(clut_rd_data)
    );

    task automatic clut_write(input logic [7:0] idx, input logic [31:0] data);
        @(negedge clk);
        clut_we   = 1'b1;
        clut_widx = idx;
        clut_wdata = data;
        @(negedge clk);
        clut_we   = 1'b0;
    endtask

    // ---- texture geometry ----
    // 8x8 PSMT8 texture at byte base 0x0800 (TBP0=8 -> 8*256). TBW=1 ->
    // 64 texels/row stride (matches gs_texel_addr: row_texels = tbw*64).
    localparam int    TEXW  = 8;
    localparam int    TEXH  = 8;
    // PSMT8 indices live at 0x0800 (1 B/texel, row stride 64 B -> spans
    // up to ~0x9C7). The PSMCT32 texture lives well clear of it at
    // 0x2000 (4 B/texel, row stride 256 B -> spans up to ~0x271F) so the
    // two uploads never overlap.
    localparam [31:0] T8_BASE  = 32'h0000_0800;
    localparam [13:0] T8_TBW   = 14'd1;
    localparam [31:0] CT32_BASE = 32'h0000_2000;

    // The index pattern: a recognizable "frame" — border indices = 1,
    // interior = 2, plus a diagonal of index 3, corner (0,0) index 0.
    function automatic logic [7:0] idx_pattern(input int x, input int y);
        if (x == 0 && y == 0)                         return 8'd0;
        else if (x == y)                              return 8'd3;
        else if (x == 0 || y == 0 ||
                 x == TEXW-1 || y == TEXH-1)          return 8'd1;
        else                                          return 8'd2;
    endfunction

    // Palette A and Palette B — boldly distinct PSMCT32 ABGR per index.
    function automatic logic [31:0] palA(input logic [7:0] i);
        case (i)
            8'd0:    return 32'hFF000000; // black   (A=FF)
            8'd1:    return 32'hFF0000FF; // red
            8'd2:    return 32'hFF00FF00; // green
            8'd3:    return 32'hFFFF0000; // blue
            default: return 32'hFF7F7F7F;
        endcase
    endfunction
    function automatic logic [31:0] palB(input logic [7:0] i);
        case (i)
            8'd0:    return 32'hFFFFFFFF; // white
            8'd1:    return 32'hFF00FFFF; // yellow
            8'd2:    return 32'hFFFF00FF; // magenta
            8'd3:    return 32'hFF00007F; // dark-red
            default: return 32'hFF101010;
        endcase
    endfunction

    // ---- drive one sample and capture the color (RD_LATENCY=1) ----
    // in_valid + (u,v) presented; combinational read returns the word
    // this cycle, the combinational CLUT lookup resolves the color, and
    // tex_color is valid in the SAME cycle (gs_stub registers it to S2).
    // The texel-fetch + index-extract + CLUT lookup -> tex_color path is
    // PURELY COMBINATIONAL (the only register is gs_texture_unit's valid
    // pipe, which doesn't gate tex_color). So driving u/v and letting the
    // combinational chain settle is sufficient; no clock edge is needed
    // to read the sampled color. Keep clk free-running but read between
    // edges so no synchronous CLUT write is mid-flight.
    task automatic sample(input int su, input int sv, output logic [31:0] col);
        @(negedge clk);
        in_valid = 1'b1;
        u = 11'(su);
        v = 11'(sv);
        #2;                       // settle the combinational sampler chain
        col = tex_color;
    endtask

    int errors;
    initial errors = 0;

    // capture indices observed during the PSMT8 passes for PROOF 3.
    logic [7:0] idx_obs_A [0:TEXH-1][0:TEXW-1];
    logic [7:0] idx_obs_B [0:TEXH-1][0:TEXW-1];

    logic [31:0] got, exp;
    int x, y;

    initial begin
        rst_n    = 1'b0;
        in_valid = 1'b0;
        u = 0; v = 0;
        tbp0_base_bytes = T8_BASE;
        tbw = T8_TBW;
        psm = PSMT8;
        clut_we = 1'b0; clut_widx = 0; clut_wdata = 0;
        for (int i = 0; i < VRAM_BYTES; i++) vram[i] = 8'd0;

        // Upload the PSMT8 index texture: 1 byte/texel, linear, stride
        // = TBW*64 bytes/row.
        for (y = 0; y < TEXH; y++)
            for (x = 0; x < TEXW; x++)
                poke_byte(T8_BASE + (y * (T8_TBW * 64)) + x, idx_pattern(x, y));

        // Upload a PSMCT32 texture for PROOF 4 (distinct word per texel).
        for (y = 0; y < TEXH; y++)
            for (x = 0; x < TEXW; x++)
                poke_word(CT32_BASE + ((y * (T8_TBW * 64)) + x) * 4,
                          32'hFF000000 | (x << 4) | (y << 12));

        repeat (4) @(posedge clk);
        rst_n = 1'b1;
        repeat (2) @(posedge clk);

        // ============================================================
        // Load Palette A, sample the whole PSMT8 texture.
        // PROOF 1 (index byte select) + PROOF 2 (CLUT lookup).
        // ============================================================
        for (int i = 0; i < 4; i++) clut_write(8'(i), palA(8'(i)));
        psm = PSMT8;
        tbp0_base_bytes = T8_BASE;

        for (y = 0; y < TEXH; y++) begin
            for (x = 0; x < TEXW; x++) begin
                logic [7:0] idx;
                idx = idx_pattern(x, y);
                // The sampler should have selected this index byte...
                sample(x, y, got);
                // PROOF 1: the index the sampler extracted must match.
                if (clut_rd_idx !== idx) begin
                    $error("[P1 idx] (%0d,%0d) sampler idx=0x%02x expected 0x%02x",
                           x, y, clut_rd_idx, idx);
                    errors++;
                end
                idx_obs_A[y][x] = clut_rd_idx;
                // PROOF 2: index -> palette A color.
                exp = palA(idx);
                if (got !== exp) begin
                    $error("[P2 lkup A] (%0d,%0d) idx=0x%02x got=%08x exp=%08x",
                           x, y, idx, got, exp);
                    errors++;
                end
            end
        end

        // ============================================================
        // PROOF 3 — reload a DIFFERENT palette over the SAME texture.
        // Output colors must change; fetched indices must be identical.
        // ============================================================
        for (int i = 0; i < 4; i++) clut_write(8'(i), palB(8'(i)));

        for (y = 0; y < TEXH; y++) begin
            for (x = 0; x < TEXW; x++) begin
                logic [7:0] idx;
                idx = idx_pattern(x, y);
                sample(x, y, got);
                idx_obs_B[y][x] = clut_rd_idx;
                // indices identical across the two palette passes
                if (idx_obs_B[y][x] !== idx_obs_A[y][x]) begin
                    $error("[P3 idx-stable] (%0d,%0d) idxA=0x%02x idxB=0x%02x — indices changed!",
                           x, y, idx_obs_A[y][x], idx_obs_B[y][x]);
                    errors++;
                end
                // color now follows palette B
                exp = palB(idx);
                if (got !== exp) begin
                    $error("[P3 lkup B] (%0d,%0d) idx=0x%02x got=%08x exp=%08x",
                           x, y, idx, got, exp);
                    errors++;
                end
                // and (for non-degenerate indices) differs from palette A
                if (palA(idx) !== palB(idx) && got === palA(idx)) begin
                    $error("[P3 changed] (%0d,%0d) idx=0x%02x output still palette-A color %08x",
                           x, y, idx, got);
                    errors++;
                end
            end
        end

        // ============================================================
        // PROOF 4 — PSMCT32 DIRECT path unchanged (no CLUT indirection).
        // ============================================================
        psm = PSMCT32;
        tbp0_base_bytes = CT32_BASE;
        for (y = 0; y < TEXH; y++) begin
            for (x = 0; x < TEXW; x++) begin
                sample(x, y, got);
                exp = 32'hFF000000 | (x << 4) | (y << 12);   // = stored word
                if (got !== exp) begin
                    $error("[P4 ct32] (%0d,%0d) got=%08x exp=%08x (direct word, no CLUT)",
                           x, y, got, exp);
                    errors++;
                end
            end
        end

        $display("[tb_gs_texture_psmt8_clut] PSMT8 samples=%0d palettes=2 PSMCT32 samples=%0d errors=%0d",
                 TEXW*TEXH*2, TEXW*TEXH, errors);
        if (errors == 0)
            $display("[tb_gs_texture_psmt8_clut] PASS");
        else
            $display("[tb_gs_texture_psmt8_clut] FAIL");
        $finish;
    end

    initial begin
        #2000000;
        $error("[tb_gs_texture_psmt8_clut] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_texture_psmt8_clut
