// retroDE_ps2 — tb_gs_texture_wrap (Ch294)
//
// Focused TB for GS texture WRAP MODES (REPEAT + CLAMP) at the sampler level.
// gs_texture_unit is instantiated with TEX_WRAP_ENABLE=1; out-of-range (u,v)
// are resolved against the texture's power-of-two dims (width=2^TW, height=
// 2^TH from TEX0) using the CLAMP_1 wrap mode (WMS for u/S, WMT for v/T):
//   REPEAT (mode 0): u_eff = u & (width-1)
//   CLAMP  (mode 1): u_eff = (u >= width) ? width-1 : u
// The wrap is applied BEFORE address generation, so it covers the LINEAR path
// (gs_texel_addr) AND the SWIZZLE paths. u/v are unsigned -> no negative /
// underflow case exists (stated, not tested): u>=width REPEAT wraps, u>=width
// CLAMP sticks; same for v.
//
// PROOFS (TEX_WRAP_ENABLE=1):
//   PSMCT32 (psm 0x00) 4x4 (TW=TH=2): width=height=4, mask=3, limit=3.
//     - REPEAT u=5 -> samples texel u=1 (5&3); v=6 -> v=2.
//     - CLAMP  u=5 -> samples texel u=3 (width-1); v=6 -> v=3.
//     - in-range u=2,v=1 wraps/clamps to itself (identity, both modes).
//     Each verifies the SAMPLED COLOR equals the wrapped/clamped texel's word.
//   PSMT8 (psm 0x13) 4x4 linear: same OOB REPEAT vs CLAMP, verifies the
//     CLUT-resolved color matches the wrapped/clamped INDEX (and clut_rd_idx
//     is the wrapped/clamped texel's index — the address is what wrap changes).
//   PSMT4_SWIZZLE (psm 0x14): proves wrap happens BEFORE swizzle — the
//     swizzled tex_rd_addr for an OOB coord equals the swizzled addr of the
//     in-range wrapped/clamped coord, and differs from the OOB coord's own
//     (unwrapped) swizzle. (Address is the thing wrap changes; no CLUT needed
//     for this proof, though the same CLUT model feeds it.)

`timescale 1ns/1ps

module tb_gs_texture_wrap;

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

    // Wrap modes
    localparam logic [1:0] WM_REPEAT = 2'd0;
    localparam logic [1:0] WM_CLAMP  = 2'd1;

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

    // ---- tiny combinational VRAM model (word-addressed little-endian) ----
    localparam int VRAM_BYTES = 16384;
    logic [7:0] vram [0:VRAM_BYTES-1];

    logic [31:0] tex_rd_addr;
    logic [31:0] tex_rd_data;
    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 (linear path: PSMCT32 + PSMT8) with wrap enabled ----
    logic        in_valid;
    logic [10:0] u, v;
    logic [1:0]  wms, wmt;
    logic [3:0]  tw, th;
    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 #(.TEX_WRAP_ENABLE(1'b1), .RD_LATENCY(1)) dut (
        .clk(clk), .rst_n(rst_n),
        .in_valid(in_valid),
        .u(u), .v(v),
        .wms(wms), .wmt(wmt), .tw(tw), .th(th),
        .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 (reused on-chip palette) for the PSMT8 proof ----
    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),
        .read_data(),
        .tex_read_idx (clut_rd_idx),
        .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: 4x4 (TW=TH=2), TBW=1 ----
    localparam int    TEXW = 4;
    localparam int    TEXH = 4;
    localparam [3:0]  TWv  = 4'd2;     // log2(4)
    localparam [3:0]  THv  = 4'd2;
    localparam [13:0] TBWv = 14'd1;    // row stride = TBW*64 texels
    localparam [31:0] CT32_BASE = 32'h0000_2000;  // PSMCT32 4 B/texel
    localparam [31:0] T8_BASE   = 32'h0000_0800;  // PSMT8  1 B/texel

    // distinct PSMCT32 texel word per (x,y)
    function automatic logic [31:0] ct32_word(input int x, input int y);
        return 32'hFF000000 | (x << 4) | (y << 12);
    endfunction
    // distinct PSMT8 index per (x,y): 0..15 over the 4x4
    function automatic logic [7:0] t8_idx(input int x, input int y);
        return 8'(y*TEXW + x);
    endfunction
    function automatic logic [31:0] pal(input logic [7:0] i);
        return 32'hFF000000 | (i << 8) | i;  // recognizable per-index color
    endfunction

    // ---- drive one sample, let the combinational sampler chain settle ----
    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;
        col = tex_color;
    endtask

    int errors;
    initial errors = 0;

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

    initial begin
        rst_n = 1'b0; in_valid = 1'b0;
        u = 0; v = 0; wms = WM_REPEAT; wmt = WM_REPEAT;
        tw = TWv; th = THv;
        tbp0_base_bytes = CT32_BASE; tbw = TBWv; psm = PSMCT32;
        clut_we = 1'b0; clut_widx = 0; clut_wdata = 0;
        for (int i = 0; i < VRAM_BYTES; i++) vram[i] = 8'd0;

        // upload PSMCT32 4x4 (row stride TBW*64 texels * 4 bytes)
        for (y = 0; y < TEXH; y++)
            for (x = 0; x < TEXW; x++)
                poke_word(CT32_BASE + ((y * (TBWv*64)) + x) * 4, ct32_word(x, y));
        // upload PSMT8 4x4 indices (row stride TBW*64 bytes)
        for (y = 0; y < TEXH; y++)
            for (x = 0; x < TEXW; x++)
                poke_byte(T8_BASE + (y * (TBWv*64)) + x, t8_idx(x, y));

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

        // ============================================================
        // PSMCT32 wrap proofs (direct color; sampled word == texel word)
        // ============================================================
        psm = PSMCT32; tbp0_base_bytes = CT32_BASE;

        // REPEAT: u=5 -> u_eff=1 ; v=6 -> v_eff=2
        wms = WM_REPEAT; wmt = WM_REPEAT;
        sample(5, 6, got);
        exp = ct32_word(1, 2);
        if (got !== exp) begin
            $error("[ct32 REPEAT] u=5 v=6 got=%08x exp=%08x (expect texel 1,2)", got, exp);
            errors++;
        end

        // REPEAT identity: in-range u=2 v=1 -> itself
        sample(2, 1, got);
        exp = ct32_word(2, 1);
        if (got !== exp) begin
            $error("[ct32 REPEAT id] u=2 v=1 got=%08x exp=%08x", got, exp);
            errors++;
        end

        // CLAMP: u=5 -> u_eff=3 (width-1) ; v=6 -> v_eff=3 (height-1)
        wms = WM_CLAMP; wmt = WM_CLAMP;
        sample(5, 6, got);
        exp = ct32_word(3, 3);
        if (got !== exp) begin
            $error("[ct32 CLAMP] u=5 v=6 got=%08x exp=%08x (expect texel 3,3)", got, exp);
            errors++;
        end

        // CLAMP only-u OOB: u=9 -> 3, v=1 stays 1
        sample(9, 1, got);
        exp = ct32_word(3, 1);
        if (got !== exp) begin
            $error("[ct32 CLAMP u-only] u=9 v=1 got=%08x exp=%08x", got, exp);
            errors++;
        end

        // mixed: REPEAT u, CLAMP v: u=5->1, v=7->3
        wms = WM_REPEAT; wmt = WM_CLAMP;
        sample(5, 7, got);
        exp = ct32_word(1, 3);
        if (got !== exp) begin
            $error("[ct32 mix R-u C-v] u=5 v=7 got=%08x exp=%08x (expect 1,3)", got, exp);
            errors++;
        end

        // CLAMP identity in-range
        wms = WM_CLAMP; wmt = WM_CLAMP;
        sample(0, 0, got);
        exp = ct32_word(0, 0);
        if (got !== exp) begin
            $error("[ct32 CLAMP id] u=0 v=0 got=%08x exp=%08x", got, exp);
            errors++;
        end

        // ============================================================
        // PSMT8 wrap proofs (CLUT-resolved color matches wrapped index)
        // ============================================================
        psm = PSMT8; tbp0_base_bytes = T8_BASE;
        for (int i = 0; i < 16; i++) clut_write(8'(i), pal(8'(i)));

        // REPEAT: u=5 v=6 -> index of texel (1,2)
        wms = WM_REPEAT; wmt = WM_REPEAT;
        sample(5, 6, got);
        if (clut_rd_idx !== t8_idx(1, 2)) begin
            $error("[t8 REPEAT idx] u=5 v=6 idx=0x%02x exp=0x%02x", clut_rd_idx, t8_idx(1,2));
            errors++;
        end
        exp = pal(t8_idx(1, 2));
        if (got !== exp) begin
            $error("[t8 REPEAT col] u=5 v=6 got=%08x exp=%08x", got, exp);
            errors++;
        end

        // CLAMP: u=5 v=6 -> index of texel (3,3)
        wms = WM_CLAMP; wmt = WM_CLAMP;
        sample(5, 6, got);
        if (clut_rd_idx !== t8_idx(3, 3)) begin
            $error("[t8 CLAMP idx] u=5 v=6 idx=0x%02x exp=0x%02x", clut_rd_idx, t8_idx(3,3));
            errors++;
        end
        exp = pal(t8_idx(3, 3));
        if (got !== exp) begin
            $error("[t8 CLAMP col] u=5 v=6 got=%08x exp=%08x", got, exp);
            errors++;
        end

        // PSMT8 identity in-range, REPEAT
        wms = WM_REPEAT; wmt = WM_REPEAT;
        sample(2, 1, got);
        if (clut_rd_idx !== t8_idx(2, 1)) begin
            $error("[t8 REPEAT id idx] u=2 v=1 idx=0x%02x exp=0x%02x", clut_rd_idx, t8_idx(2,1));
            errors++;
        end

        $display("[tb_gs_texture_wrap] linear (PSMCT32+PSMT8) wrap proofs done, errors=%0d", errors);
        run_swizzle_proof();

        $display("[tb_gs_texture_wrap] errors=%0d", errors);
        if (errors == 0)
            $display("[tb_gs_texture_wrap] PASS");
        else
            $display("[tb_gs_texture_wrap] FAIL");
        $finish;
    end

    // ============================================================
    // SWIZZLE proof — wrap happens BEFORE swizzle.
    // A separate DUT with PSMT4_SWIZZLE=1; for psm==PSMT4 the address is
    // generated by gs_swizzle_psmt4_stub from u_eff/v_eff. We prove:
    //   tex_rd_addr(OOB coord, wrap on) == tex_rd_addr(in-range wrapped coord)
    //   tex_rd_addr(OOB coord, wrap on) != tex_rd_addr(OOB coord, wrap off)
    // i.e. the swizzle consumes the WRAPPED coord, not the raw OOB coord.
    // Address is the thing wrap changes; no CLUT lookup needed for this proof.
    // ============================================================
    // wrap-on swizzle DUT
    logic        sw_in_valid;
    logic [10:0] sw_u, sw_v;
    logic [1:0]  sw_wms, sw_wmt;
    logic [3:0]  sw_tw, sw_th;
    logic [31:0] sw_addr;
    logic        sw_rd_en;
    logic [7:0]  sw_idx;
    gs_texture_unit #(.TEX_WRAP_ENABLE(1'b1), .PSMT4_SWIZZLE(1'b1), .RD_LATENCY(1)) dut_sw_on (
        .clk(clk), .rst_n(rst_n),
        .in_valid(sw_in_valid),
        .u(sw_u), .v(sw_v),
        .wms(sw_wms), .wmt(sw_wmt), .tw(TWv), .th(THv),
        .tbp0_base_bytes(CT32_BASE), .tbw(TBWv), .psm(PSMT4),
        .tex_rd_en(sw_rd_en),
        .tex_rd_addr(sw_addr),
        .tex_rd_data(32'd0),
        .clut_rd_idx(sw_idx),
        .clut_rd_data(32'd0),
        .out_valid(), .tex_color()
    );
    // wrap-off swizzle DUT (same geometry, TEX_WRAP_ENABLE=0)
    logic [10:0] swoff_u, swoff_v;
    logic [31:0] swoff_addr;
    gs_texture_unit #(.TEX_WRAP_ENABLE(1'b0), .PSMT4_SWIZZLE(1'b1), .RD_LATENCY(1)) dut_sw_off (
        .clk(clk), .rst_n(rst_n),
        .in_valid(1'b1),
        .u(swoff_u), .v(swoff_v),
        .wms(2'd0), .wmt(2'd0), .tw(TWv), .th(THv),
        .tbp0_base_bytes(CT32_BASE), .tbw(TBWv), .psm(PSMT4),
        .tex_rd_en(),
        .tex_rd_addr(swoff_addr),
        .tex_rd_data(32'd0),
        .clut_rd_idx(),
        .clut_rd_data(32'd0),
        .out_valid(), .tex_color()
    );

    task automatic run_swizzle_proof;
        // --- REPEAT u=5 v=6 -> wrapped (1,2) ---
        sw_in_valid = 1'b1;
        sw_wms = WM_REPEAT; sw_wmt = WM_REPEAT;
        // OOB coord through wrap-on path
        sw_u = 11'd5; sw_v = 11'd6; #2; addr_oob = sw_addr;
        // in-range wrapped coord (1,2) through SAME wrap-on path (wraps to self)
        sw_u = 11'd1; sw_v = 11'd2; #2; addr_inrange = sw_addr;
        // OOB coord through wrap-OFF path (raw swizzle of 5,6)
        swoff_u = 11'd5; swoff_v = 11'd6; #2; addr_unwrapped = swoff_addr;

        if (addr_oob !== addr_inrange) begin
            $error("[swz REPEAT] wrapped OOB addr=%08x != in-range(1,2) addr=%08x (wrap not before swizzle)",
                   addr_oob, addr_inrange);
            errors++;
        end
        if (addr_oob === addr_unwrapped) begin
            $error("[swz REPEAT] wrap-on OOB addr=%08x == raw OOB addr=%08x (wrap had no effect)",
                   addr_oob, addr_unwrapped);
            errors++;
        end

        // --- CLAMP u=5 v=6 -> wrapped (3,3) ---
        sw_wms = WM_CLAMP; sw_wmt = WM_CLAMP;
        sw_u = 11'd5; sw_v = 11'd6; #2; addr_oob = sw_addr;
        sw_u = 11'd3; sw_v = 11'd3; #2; addr_inrange = sw_addr;
        swoff_u = 11'd5; swoff_v = 11'd6; #2; addr_unwrapped = swoff_addr;

        if (addr_oob !== addr_inrange) begin
            $error("[swz CLAMP] clamped OOB addr=%08x != in-range(3,3) addr=%08x",
                   addr_oob, addr_inrange);
            errors++;
        end
        if (addr_oob === addr_unwrapped) begin
            $error("[swz CLAMP] wrap-on OOB addr=%08x == raw OOB addr=%08x (clamp had no effect)",
                   addr_oob, addr_unwrapped);
            errors++;
        end

        // --- in-range identity must NOT differ from raw ---
        sw_wms = WM_REPEAT; sw_wmt = WM_REPEAT;
        sw_u = 11'd2; sw_v = 11'd1; #2; addr_oob = sw_addr;
        swoff_u = 11'd2; swoff_v = 11'd1; #2; addr_unwrapped = swoff_addr;
        if (addr_oob !== addr_unwrapped) begin
            $error("[swz identity] in-range (2,1) wrap-on addr=%08x != wrap-off addr=%08x",
                   addr_oob, addr_unwrapped);
            errors++;
        end
        $display("[tb_gs_texture_wrap] swizzle (PSMT4) wrap-before-swizzle proofs done, errors=%0d", errors);
    endtask

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

endmodule : tb_gs_texture_wrap
