// retroDE_ps2 — tb_gs_texture_swizzle_psmct32 (Ch300)
//
// Focused TB for SWIZZLED PSMCT32 (direct-color) texture sampling — the CLOSURE
// rung of the swizzle layout family (after PSMT4 / PSMT8 swizzle samplers).
// PSMCT32 is DIRECT color: 4 bytes/texel, NO CLUT, NO nibble, NO byte-lane
// select — the fetched 32-bit word IS the texel color. So when built with
// PSMCT32_SWIZZLE=1 AND psm==PSMCT32, gs_texture_unit need only compute the
// texel byte address through gs_swizzle_psmct32_stub (real PS2 page/block
// layout) instead of the linear gs_texel_addr; downstream is untouched.
//
// Proofs (mirror the architect's swizzle-sampler acceptance):
//
//   PROOF 1 — SWIZZLED ADDRESS + COLOR ARE CORRECT (incl. ACROSS BLOCK / PAGE
//             BOUNDARIES). A TB-side reference of the EXACT gs_swizzle_psmct32
//             formula bakes a coordinate-encoded ABGR texel into VRAM at each
//             swizzled byte address; sampling with PSMCT32_SWIZZLE=1 must read
//             back the ORIGINAL color (and present the matching swizzled addr).
//             The sweep crosses 8-px BLOCK boundaries AND the 32-px PAGE-ROW
//             boundaries at y=32 and y=64 — exactly where the PSMCT32 swizzle
//             layout diverges from a linear row-major one.
//
//   PROOF 2 — SWIZZLED != LINEAR for the SAME VRAM bytes. A second DUT with
//             PSMCT32_SWIZZLE=0 samples the identical (swizzled-baked) VRAM and
//             the identical (u,v); for coords in a different block/page it reads
//             a DIFFERENT word -> different color. Assert a healthy count of
//             across-boundary mismatches (proves the swizzle is applied, not a
//             no-op), plus an explicit page-row-crossing pair.
//
//   PROOF 3 — registered-read (SEL_DELAY=1) sanity: a held-address registered
//             read still resolves the right color. PSMCT32 ignores the sel/nibble
//             pipe (whole word is the color), so this just confirms the latency
//             path is intact under the board's registered-read mode.
//
// No EE/BIOS scaffolding — pure sampler unit test against a comb VRAM model.

`timescale 1ns/1ps

module tb_gs_texture_swizzle_psmct32;

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

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

    // ---- combinational VRAM model (word-addressed view) ----
    // Large enough to span >2 PSMCT32 pages (8192 B/page) so the page-row
    // boundary coords land in real, distinct addresses.
    localparam int VRAM_BYTES = 65536;
    logic [7:0] vram [0:VRAM_BYTES-1];

    // ---- texture geometry ----
    // PSMCT32 page = 64x32 px (8 KiB). bw_pg = FBW directly (NO >>1, unlike the
    // PSMT8/PSMT4 swizzle). TBW=1 -> 64 texels/row -> exactly one page wide, so
    // page_x is always 0 and page_index = page_y has no aliasing; a 96-tall
    // texture then spans page_y = 0,1,2 (crossing the 32-px page rows at y=32,64).
    localparam [31:0] T32_BASE = 32'h0000_4000;  // 2048-aligned (FBP=8)
    localparam [13:0] T32_TBW  = 14'd1;          // 64 texels/row = one page wide

    // ---- TB-side reference of the EXACT gs_swizzle_psmct32_stub block table ----
    // [by(0..3)][bx(0..7)] -> linear block index within the page (0..31).
    function automatic int blocktbl32(input int by, input int bx);
        case ({by[1:0], bx[2:0]})
            5'd0:  return 0;   5'd1:  return 1;   5'd2:  return 4;   5'd3:  return 5;
            5'd4:  return 16;  5'd5:  return 17;  5'd6:  return 20;  5'd7:  return 21;
            5'd8:  return 2;   5'd9:  return 3;   5'd10: return 6;   5'd11: return 7;
            5'd12: return 18;  5'd13: return 19;  5'd14: return 22;  5'd15: return 23;
            5'd16: return 8;   5'd17: return 9;   5'd18: return 12;  5'd19: return 13;
            5'd20: return 24;  5'd21: return 25;  5'd22: return 28;  5'd23: return 29;
            5'd24: return 10;  5'd25: return 11;  5'd26: return 14;  5'd27: return 15;
            5'd28: return 26;  5'd29: return 27;  5'd30: return 30;  default: return 31;
        endcase
    endfunction

    // reference swizzled byte address for (u,v). PSMCT32: page 64x32 px; block
    // grid 8 cols x 4 rows; block = 8x8 px = 256 bytes; within a block PSMCT32 is
    // row-major: byte_in_block = yb*32 + xb*4.
    task automatic ref_swizzle32(input int uu, input int vv,
                                 output logic [31:0] baddr);
        int page_x, page_y, by, bx, xb, yb, bw_pg;
        int page_index, byte_idx, block_idx;
        longint page_base, block_base;
        page_x = uu >> 6;          // x / 64
        page_y = vv >> 5;          // y / 32
        by     = (vv >> 3) & 2'h3; // (y % 32) / 8  -> 0..3
        bx     = (uu >> 3) & 3'h7; // (x % 64) / 8  -> 0..7
        xb     = uu & 3'h7;        // x % 8
        yb     = vv & 3'h7;        // y % 8
        bw_pg  = T32_TBW;          // PSMCT32: bw_pg = FBW (no >>1)
        page_index = page_y * bw_pg + page_x;
        page_base  = longint'(T32_BASE) + longint'(page_index) * 8192;
        block_idx  = blocktbl32(by, bx);
        block_base = page_base + longint'(block_idx) * 256;
        byte_idx   = yb*32 + xb*4;
        baddr  = 32'(block_base + byte_idx);
    endtask

    // coordinate-encoded direct-color texel: R=x, G=y, B=(x+y), A=0xFF. Each
    // (x,y) is a distinct ABGR word so an address error mislands a recognizable
    // color. (Bit-distinct over the 64x96 grid: R wraps mod 256 at x=64 but the
    // (R,G) pair stays unique.)
    function automatic logic [31:0] texel(input int x, input int y);
        return 32'hFF000000 | (((x + y) & 8'hFF) << 16) | ((y & 8'hFF) << 8) | (x & 8'hFF);
    endfunction

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

    // swizzled DUT (comb read, SEL_DELAY=0)
    logic [31:0] sw_rd_addr;
    logic [31:0] sw_tex_color;
    logic [31:0] sw_rd_data;
    assign sw_rd_data = { vram[(sw_rd_addr & ~32'd3) + 3],
                          vram[(sw_rd_addr & ~32'd3) + 2],
                          vram[(sw_rd_addr & ~32'd3) + 1],
                          vram[(sw_rd_addr & ~32'd3) + 0] };

    gs_texture_unit #(.PSMCT32_SWIZZLE(1'b1), .RD_LATENCY(1), .SEL_DELAY(0)) dut_sw (
        .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_addr(sw_rd_addr), .tex_rd_data(sw_rd_data),
        .clut_rd_idx(), .clut_rd_data(32'd0),
        .out_valid(), .tex_color(sw_tex_color)
    );

    // linear DUT (PSMCT32_SWIZZLE=0) sampling the SAME VRAM bytes.
    logic [31:0] li_rd_addr;
    logic [31:0] li_tex_color;
    logic [31:0] li_rd_data;
    assign li_rd_data = { vram[(li_rd_addr & ~32'd3) + 3],
                          vram[(li_rd_addr & ~32'd3) + 2],
                          vram[(li_rd_addr & ~32'd3) + 1],
                          vram[(li_rd_addr & ~32'd3) + 0] };

    gs_texture_unit #(.PSMCT32_SWIZZLE(1'b0), .RD_LATENCY(1), .SEL_DELAY(0)) dut_li (
        .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_addr(li_rd_addr), .tex_rd_data(li_rd_data),
        .clut_rd_idx(), .clut_rd_data(32'd0),
        .out_valid(), .tex_color(li_tex_color)
    );

    // Bake the coordinate-encoded texel into VRAM at SWIZZLED byte addresses, so
    // a swizzled READ recovers the original. Whole 32-bit word per texel.
    task automatic upload_swizzled(input int w, input int h);
        logic [31:0] ba;
        logic [31:0] c;
        for (int yy = 0; yy < h; yy++) begin
            for (int xx = 0; xx < w; xx++) begin
                ref_swizzle32(xx, yy, ba);
                c = texel(xx, yy);
                vram[(ba & ~32'd3) + 0] = c[7:0];
                vram[(ba & ~32'd3) + 1] = c[15:8];
                vram[(ba & ~32'd3) + 2] = c[23:16];
                vram[(ba & ~32'd3) + 3] = c[31:24];
            end
        end
    endtask

    // ---- sample helper (comb, SEL_DELAY=0) ----
    task automatic sample(input int su, input int sv);
        @(negedge clk); in_valid=1'b1; u=11'(su); v=11'(sv); #2;
    endtask

    int errors;
    int sw_ne_li;     // count of across-boundary coords where swz != linear
    bit sel_done;
    logic [31:0] ba;
    logic [31:0] expc;

    initial begin
        errors = 0; sw_ne_li = 0; sel_done = 1'b0;
        rst_n = 1'b0; in_valid=1'b0; u=0; v=0;
        tbp0_base_bytes = T32_BASE; tbw = T32_TBW; psm = PSMCT32;
        for (int i=0;i<VRAM_BYTES;i++) vram[i]=8'd0;
        // 64 wide (one page-column) x 96 tall -> crosses the 32-px page rows at
        // y=32 and y=64 (page_y 0,1,2) plus all 8-px block boundaries within
        // each page. One page wide keeps page_index = page_y (page_x=0) so no
        // aliasing.
        upload_swizzled(64, 96);

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

        // ============================================================
        // PROOF 1 — swizzled read recovers the coordinate-encoded color and
        // presents the matching swizzled address. Sweep crosses block (x=8,
        // y=8) AND page-row (y=32, y=64) boundaries.
        // ============================================================
        for (int yy = 0; yy < 96; yy += 3) begin
            for (int xx = 0; xx < 64; xx += 3) begin
                expc = texel(xx, yy);
                ref_swizzle32(xx, yy, ba);
                sample(xx, yy);
                if ((sw_rd_addr & ~32'd3) !== (ba & ~32'd3)) begin
                    $error("[P1 addr] (%0d,%0d) dut_addr=0x%08x ref=0x%08x",
                           xx, yy, sw_rd_addr, ba); errors++;
                end
                if (sw_tex_color !== expc) begin
                    $error("[P1 color] (%0d,%0d) got=%08x exp=%08x",
                           xx, yy, sw_tex_color, expc); errors++;
                end
            end
        end

        // ============================================================
        // PROOF 2 — swizzled != linear for the SAME bytes. For coords that land
        // in a DIFFERENT block/page than the linear layout would address, the
        // linear DUT reads a different word -> different color. Require a healthy
        // count (proves not a no-op).
        // ============================================================
        for (int yy = 0; yy < 96; yy += 2) begin
            for (int xx = 0; xx < 64; xx += 2) begin
                sample(xx, yy);
                if (sw_tex_color !== li_tex_color) sw_ne_li++;
            end
        end
        if (sw_ne_li < 32) begin
            $error("[P2] swizzled vs linear color differs in only %0d coords — swizzle may be a no-op", sw_ne_li);
            errors++;
        end

        // explicit across-page-ROW-boundary coord: (2,40) is page (0,1) for the
        // swizzled layout (page_y=1 -> page_index 1 -> base+8192); the linear
        // addresser places y=40 only ~40 rows in, still near base.
        sample(2, 40);
        begin
            logic [31:0] sw_a, li_a;
            sw_a = sw_rd_addr & ~32'd3;
            li_a = li_rd_addr & ~32'd3;
            if (sw_a === li_a) begin
                $error("[P2 page] (2,40) swizzled addr == linear addr 0x%08x (page boundary not honored)", sw_a);
                errors++;
            end
            if (sw_a < (T32_BASE + 8192)) begin
                $error("[P2 page] (2,40) swizzled addr 0x%08x not in page 1 (>=0x%08x)", sw_a, T32_BASE+8192);
                errors++;
            end
        end

        wait (sel_done == 1'b1);
        $display("[tb_gs_texture_swizzle_psmct32] swizzled samples checked, swz!=lin coords=%0d errors=%0d",
                 sw_ne_li, errors);
        if (errors == 0) $display("[tb_gs_texture_swizzle_psmct32] PASS");
        else             $display("[tb_gs_texture_swizzle_psmct32] FAIL");
        $finish;
    end

    // ================================================================
    // PROOF 3 — registered-read (SEL_DELAY=1) sanity. PSMCT32 ignores the
    // sel/nibble pipe (whole word is the color), so this just confirms the
    // RD_LATENCY=1 path resolves the right color under the board's mode.
    // ================================================================
    logic        d2_in_valid;
    logic [10:0] d2_u, d2_v;
    logic [31:0] d2_rd_addr;
    logic [31:0] d2_rd_data_q;
    logic [31:0] d2_tex_color;

    always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) d2_rd_data_q <= 32'd0;
        else d2_rd_data_q <= { vram[(d2_rd_addr & ~32'd3) + 3],
                               vram[(d2_rd_addr & ~32'd3) + 2],
                               vram[(d2_rd_addr & ~32'd3) + 1],
                               vram[(d2_rd_addr & ~32'd3) + 0] };
    end

    gs_texture_unit #(.PSMCT32_SWIZZLE(1'b1), .RD_LATENCY(1), .SEL_DELAY(1)) dut2 (
        .clk(clk), .rst_n(rst_n),
        .in_valid(d2_in_valid), .u(d2_u), .v(d2_v),
        .tbp0_base_bytes(T32_BASE), .tbw(T32_TBW), .psm(PSMCT32),
        .tex_rd_en(), .tex_rd_addr(d2_rd_addr), .tex_rd_data(d2_rd_data_q),
        .clut_rd_idx(), .clut_rd_data(32'd0),
        .out_valid(), .tex_color(d2_tex_color)
    );

    int errors2;

    initial begin
        errors2 = 0;
        d2_in_valid=1'b0; d2_u=0; d2_v=0;
        wait (rst_n == 1'b1);
        repeat (40) @(posedge clk);    // let the main scan get underway

        // HELD-address sweep across a row crossing block & page boundaries:
        // address registered, data one cycle later, then verify recovered color.
        for (int xx = 0; xx < 64; xx += 7) begin
            logic [31:0] ec;
            @(negedge clk); d2_in_valid=1'b1; d2_u=11'(xx); d2_v=11'(40);
            @(posedge clk);            // address registered into read
            @(negedge clk); #2;        // data aligned (RD_LATENCY=1)
            ec = texel(xx, 40);
            if (d2_tex_color !== ec) begin
                $error("[P3 SEL_DELAY=1 held] x=%0d got=%08x exp=%08x (registered-read color mis-pipe)",
                       xx, d2_tex_color, ec); errors2++;
            end
        end
        d2_in_valid=1'b0;

        if (errors2 == 0) $display("[tb_gs_texture_swizzle_psmct32] SEL_DELAY=1 registered-read PASS");
        else              $display("[tb_gs_texture_swizzle_psmct32] SEL_DELAY=1 registered-read FAIL errors=%0d", errors2);
        errors = errors + errors2;
        sel_done = 1'b1;
    end

    initial begin
        #5000000;
        $error("[tb_gs_texture_swizzle_psmct32] TIMEOUT");
        $finish;
    end

endmodule : tb_gs_texture_swizzle_psmct32
