// retroDE_ps2 — tb_gs_texture_swizzle_psmt8_clut (Ch299)
//
// Focused TB for SWIZZLED PSMT8 texture sampling — the SIBLING of Ch298's
// swizzled-PSMT4 sampler, MINUS the nibble (PSMT8 is 1 byte/texel). When built
// with PSMT8_SWIZZLE=1 AND psm==PSMT8, gs_texture_unit must compute the texel
// byte address through gs_swizzle_psmt8_stub (real PS2 block layout) instead of
// the linear gs_texel_addr — then run the SAME downstream byte-lane / CLUT path.
//
// The architect's required proofs:
//
//   PROOF 1 — SWIZZLED ADDRESS IS CORRECT (incl. ACROSS BLOCK / PAGE
//             BOUNDARIES). A TB-side reference of the EXACT same PSMT8 swizzle
//             formula bakes a recognizable logical index pattern into VRAM at
//             swizzled byte addresses; sampling with PSMT8_SWIZZLE=1 must read
//             back the ORIGINAL logical index for each (u,v). We deliberately
//             include (u,v) that cross 16-px block columns, 16-px block rows,
//             AND the 128-px / 64-px page boundaries — exactly where the PSMT8
//             swizzle layout diverges from a linear row-major one.
//
//   PROOF 2 — SWIZZLED != LINEAR for the SAME VRAM bytes. A second DUT with
//             PSMT8_SWIZZLE=0 samples the identical (swizzled-baked) VRAM and
//             the identical (u,v). For coords in a different block/page the
//             linear sampler reads a DIFFERENT byte -> different index. We
//             assert a healthy number of across-boundary coords where swizzled
//             != linear (proves the swizzle is applied, not a no-op), plus an
//             explicit page-crossing pair.
//
//   PROOF 3 — CLUT indirection on the swizzled path: index -> clut_stub[index]
//             -> PSMCT32 color on tex_color (DECAL), for 8-bit indices.
//
//   PROOF 4 — SEL_DELAY=1 (registered-read) byte-lane alignment for the
//             SWIZZLED selector. Under TEX_RD_REGISTERED the swizzled byte-lane
//             low bits advance while the read is in flight, so they must travel
//             the SAME SEL_DELAY pipe. A registered-read DUT (RD_LATENCY=1,
//             SEL_DELAY=1) re-pairs them; a misaligned byte lane would smear.
//
// No EE/BIOS scaffolding — pure sampler unit test against a comb VRAM model.

`timescale 1ns/1ps

module tb_gs_texture_swizzle_psmt8_clut;

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

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

    // ---- combinational VRAM model (word-addressed view) ----
    // Large enough to span >1 PSMT8 page (8192 B/page) so page-boundary
    // coords land in real, distinct addresses.
    localparam int VRAM_BYTES = 65536;
    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] };

    // ---- texture geometry ----
    // FBW must be even for PSMT8 swizzle (bw_pg = FBW>>1). TBW=2 -> 128
    // texels/row -> exactly one PSMT8 page wide (page = 128x64 px). A texture
    // taller than 64 rows therefore spans >1 page-row.
    localparam [31:0] T8_BASE = 32'h0000_4000;  // 2048-aligned (FBP=8)
    localparam [13:0] T8_TBW  = 14'd2;           // 128 texels/row, even

    // ---- TB-side reference of the EXACT gs_swizzle_psmt8_stub formula ----
    // blockTable8[by(0..3)][bx(0..7)] and columnTable8[yb(0..15)][xb(0..15)]
    // reproduced from the same PCSX2 source the RTL stub cites.
    function automatic int blocktbl8(input int by, input int bx);
        // [by(0..3)][bx(0..7)]
        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

    // columnTable8[yb(0..15)][xb(0..15)] -> byte-within-block 0..255.
    int coltbl8 [0:15][0:15];

    task automatic init_coltbl8;
        // yb=0
        coltbl8[0][0]=0;   coltbl8[0][1]=4;   coltbl8[0][2]=16;  coltbl8[0][3]=20;
        coltbl8[0][4]=32;  coltbl8[0][5]=36;  coltbl8[0][6]=48;  coltbl8[0][7]=52;
        coltbl8[0][8]=2;   coltbl8[0][9]=6;   coltbl8[0][10]=18; coltbl8[0][11]=22;
        coltbl8[0][12]=34; coltbl8[0][13]=38; coltbl8[0][14]=50; coltbl8[0][15]=54;
        // yb=1
        coltbl8[1][0]=8;   coltbl8[1][1]=12;  coltbl8[1][2]=24;  coltbl8[1][3]=28;
        coltbl8[1][4]=40;  coltbl8[1][5]=44;  coltbl8[1][6]=56;  coltbl8[1][7]=60;
        coltbl8[1][8]=10;  coltbl8[1][9]=14;  coltbl8[1][10]=26; coltbl8[1][11]=30;
        coltbl8[1][12]=42; coltbl8[1][13]=46; coltbl8[1][14]=58; coltbl8[1][15]=62;
        // yb=2
        coltbl8[2][0]=33;  coltbl8[2][1]=37;  coltbl8[2][2]=49;  coltbl8[2][3]=53;
        coltbl8[2][4]=1;   coltbl8[2][5]=5;   coltbl8[2][6]=17;  coltbl8[2][7]=21;
        coltbl8[2][8]=35;  coltbl8[2][9]=39;  coltbl8[2][10]=51; coltbl8[2][11]=55;
        coltbl8[2][12]=3;  coltbl8[2][13]=7;  coltbl8[2][14]=19; coltbl8[2][15]=23;
        // yb=3
        coltbl8[3][0]=41;  coltbl8[3][1]=45;  coltbl8[3][2]=57;  coltbl8[3][3]=61;
        coltbl8[3][4]=9;   coltbl8[3][5]=13;  coltbl8[3][6]=25;  coltbl8[3][7]=29;
        coltbl8[3][8]=43;  coltbl8[3][9]=47;  coltbl8[3][10]=59; coltbl8[3][11]=63;
        coltbl8[3][12]=11; coltbl8[3][13]=15; coltbl8[3][14]=27; coltbl8[3][15]=31;
        // yb=4
        coltbl8[4][0]=96;  coltbl8[4][1]=100; coltbl8[4][2]=112; coltbl8[4][3]=116;
        coltbl8[4][4]=64;  coltbl8[4][5]=68;  coltbl8[4][6]=80;  coltbl8[4][7]=84;
        coltbl8[4][8]=98;  coltbl8[4][9]=102; coltbl8[4][10]=114;coltbl8[4][11]=118;
        coltbl8[4][12]=66; coltbl8[4][13]=70; coltbl8[4][14]=82; coltbl8[4][15]=86;
        // yb=5
        coltbl8[5][0]=104; coltbl8[5][1]=108; coltbl8[5][2]=120; coltbl8[5][3]=124;
        coltbl8[5][4]=72;  coltbl8[5][5]=76;  coltbl8[5][6]=88;  coltbl8[5][7]=92;
        coltbl8[5][8]=106; coltbl8[5][9]=110; coltbl8[5][10]=122;coltbl8[5][11]=126;
        coltbl8[5][12]=74; coltbl8[5][13]=78; coltbl8[5][14]=90; coltbl8[5][15]=94;
        // yb=6
        coltbl8[6][0]=65;  coltbl8[6][1]=69;  coltbl8[6][2]=81;  coltbl8[6][3]=85;
        coltbl8[6][4]=97;  coltbl8[6][5]=101; coltbl8[6][6]=113; coltbl8[6][7]=117;
        coltbl8[6][8]=67;  coltbl8[6][9]=71;  coltbl8[6][10]=83; coltbl8[6][11]=87;
        coltbl8[6][12]=99; coltbl8[6][13]=103;coltbl8[6][14]=115;coltbl8[6][15]=119;
        // yb=7
        coltbl8[7][0]=73;  coltbl8[7][1]=77;  coltbl8[7][2]=89;  coltbl8[7][3]=93;
        coltbl8[7][4]=105; coltbl8[7][5]=109; coltbl8[7][6]=121; coltbl8[7][7]=125;
        coltbl8[7][8]=75;  coltbl8[7][9]=79;  coltbl8[7][10]=91; coltbl8[7][11]=95;
        coltbl8[7][12]=107;coltbl8[7][13]=111;coltbl8[7][14]=123;coltbl8[7][15]=127;
        // yb=8
        coltbl8[8][0]=128; coltbl8[8][1]=132; coltbl8[8][2]=144; coltbl8[8][3]=148;
        coltbl8[8][4]=160; coltbl8[8][5]=164; coltbl8[8][6]=176; coltbl8[8][7]=180;
        coltbl8[8][8]=130; coltbl8[8][9]=134; coltbl8[8][10]=146;coltbl8[8][11]=150;
        coltbl8[8][12]=162;coltbl8[8][13]=166;coltbl8[8][14]=178;coltbl8[8][15]=182;
        // yb=9
        coltbl8[9][0]=136; coltbl8[9][1]=140; coltbl8[9][2]=152; coltbl8[9][3]=156;
        coltbl8[9][4]=168; coltbl8[9][5]=172; coltbl8[9][6]=184; coltbl8[9][7]=188;
        coltbl8[9][8]=138; coltbl8[9][9]=142; coltbl8[9][10]=154;coltbl8[9][11]=158;
        coltbl8[9][12]=170;coltbl8[9][13]=174;coltbl8[9][14]=186;coltbl8[9][15]=190;
        // yb=10
        coltbl8[10][0]=161; coltbl8[10][1]=165; coltbl8[10][2]=177; coltbl8[10][3]=181;
        coltbl8[10][4]=129; coltbl8[10][5]=133; coltbl8[10][6]=145; coltbl8[10][7]=149;
        coltbl8[10][8]=163; coltbl8[10][9]=167; coltbl8[10][10]=179;coltbl8[10][11]=183;
        coltbl8[10][12]=131;coltbl8[10][13]=135;coltbl8[10][14]=147;coltbl8[10][15]=151;
        // yb=11
        coltbl8[11][0]=169; coltbl8[11][1]=173; coltbl8[11][2]=185; coltbl8[11][3]=189;
        coltbl8[11][4]=137; coltbl8[11][5]=141; coltbl8[11][6]=153; coltbl8[11][7]=157;
        coltbl8[11][8]=171; coltbl8[11][9]=175; coltbl8[11][10]=187;coltbl8[11][11]=191;
        coltbl8[11][12]=139;coltbl8[11][13]=143;coltbl8[11][14]=155;coltbl8[11][15]=159;
        // yb=12
        coltbl8[12][0]=224; coltbl8[12][1]=228; coltbl8[12][2]=240; coltbl8[12][3]=244;
        coltbl8[12][4]=192; coltbl8[12][5]=196; coltbl8[12][6]=208; coltbl8[12][7]=212;
        coltbl8[12][8]=226; coltbl8[12][9]=230; coltbl8[12][10]=242;coltbl8[12][11]=246;
        coltbl8[12][12]=194;coltbl8[12][13]=198;coltbl8[12][14]=210;coltbl8[12][15]=214;
        // yb=13
        coltbl8[13][0]=232; coltbl8[13][1]=236; coltbl8[13][2]=248; coltbl8[13][3]=252;
        coltbl8[13][4]=200; coltbl8[13][5]=204; coltbl8[13][6]=216; coltbl8[13][7]=220;
        coltbl8[13][8]=234; coltbl8[13][9]=238; coltbl8[13][10]=250;coltbl8[13][11]=254;
        coltbl8[13][12]=202;coltbl8[13][13]=206;coltbl8[13][14]=218;coltbl8[13][15]=222;
        // yb=14
        coltbl8[14][0]=193; coltbl8[14][1]=197; coltbl8[14][2]=209; coltbl8[14][3]=213;
        coltbl8[14][4]=225; coltbl8[14][5]=229; coltbl8[14][6]=241; coltbl8[14][7]=245;
        coltbl8[14][8]=195; coltbl8[14][9]=199; coltbl8[14][10]=211;coltbl8[14][11]=215;
        coltbl8[14][12]=227;coltbl8[14][13]=231;coltbl8[14][14]=243;coltbl8[14][15]=247;
        // yb=15
        coltbl8[15][0]=201; coltbl8[15][1]=205; coltbl8[15][2]=217; coltbl8[15][3]=221;
        coltbl8[15][4]=233; coltbl8[15][5]=237; coltbl8[15][6]=249; coltbl8[15][7]=253;
        coltbl8[15][8]=203; coltbl8[15][9]=207; coltbl8[15][10]=219;coltbl8[15][11]=223;
        coltbl8[15][12]=235;coltbl8[15][13]=239;coltbl8[15][14]=251;coltbl8[15][15]=255;
    endtask

    // reference swizzled byte address for (u,v) at our texture (PSMT8: page is
    // 128x64 px; block grid 8 cols x 4 rows; block is 16x16 px = 256 bytes).
    task automatic ref_swizzle(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 >> 7;          // x / 128
        page_y = vv >> 6;          // y / 64
        by     = (vv >> 4) & 2'h3; // (y % 64) / 16
        bx     = (uu >> 4) & 3'h7; // (x % 128) / 16
        xb     = uu & 4'hf;        // x % 16
        yb     = vv & 4'hf;        // y % 16
        bw_pg  = T8_TBW >> 1;
        page_index = page_y * bw_pg + page_x;
        page_base  = (longint'(T8_BASE)) + longint'(page_index) * 8192;
        block_idx  = blocktbl8(by, bx);
        block_base = page_base + longint'(block_idx) * 256;
        byte_idx   = coltbl8[yb][xb];
        baddr  = 32'(block_base + byte_idx);
    endtask

    // ---- two DUTs sharing the comb VRAM model ----
    logic        in_valid;
    logic [10:0] u, v;
    logic [31:0] tbp0_base_bytes;
    logic [13:0] tbw;
    logic [5:0]  psm;

    // swizzled DUT
    logic [31:0] sw_rd_addr;
    logic [7:0]  sw_clut_idx;
    logic [31:0] sw_clut_data;
    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 #(.PSMT8_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(sw_clut_idx), .clut_rd_data(sw_clut_data),
        .out_valid(), .tex_color(sw_tex_color)
    );

    // linear DUT (PSMT8_SWIZZLE=0) sampling the SAME VRAM bytes.
    logic [31:0] li_rd_addr;
    logic [7:0]  li_clut_idx;
    logic [31:0] li_clut_data;
    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 #(.PSMT8_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(li_clut_idx), .clut_rd_data(li_clut_data),
        .out_valid(), .tex_color(li_tex_color)
    );

    // ---- 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), .read_data(),
        .tex_read_idx(sw_clut_idx), .tex_read_data(sw_clut_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

    // recognizable logical index pattern. (u+v) ramp gives a smooth gradient
    // that is unmistakably scrambled if read with the wrong (linear) layout.
    // Full 8-bit range exercised.
    function automatic logic [7:0] idx_pattern(input int x, input int y);
        return 8'((3*x + 5*y) & 8'hff);
    endfunction

    // Palette: distinct PSMCT32 per index.
    function automatic logic [31:0] pal(input logic [7:0] i);
        return 32'hFF000000 | ({24'd0, i} * 32'h00010305) | 32'h00000010;
    endfunction

    // Bake the logical index pattern into VRAM at SWIZZLED byte addresses, so a
    // swizzled READ recovers the original pattern. One byte per texel — direct
    // write, no nibble RMW (unlike the PSMT4 fixture).
    task automatic upload_swizzled(input int w, input int h);
        logic [31:0] ba;
        for (int yy = 0; yy < h; yy++) begin
            for (int xx = 0; xx < w; xx++) begin
                ref_swizzle(xx, yy, ba);
                vram[ba] = idx_pattern(xx, yy);
            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 [7:0]  expidx;

    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 = 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;
        init_coltbl8();
        // texture spans 128 wide (exactly one page-column, x in 0..127) x 96
        // tall -> crosses the 64-px page-ROW boundary in y (page_y 0 and 1)
        // plus all 16-px block boundaries within each page. We keep the width
        // at one page so page_index = page_y*bw_pg + page_x has no aliasing
        // (with TBW=2 -> bw_pg=1, a >1-page-wide AND >1-page-tall texture would
        // alias page(1,0) onto page(0,1)). Page-row crossing alone exercises
        // the across-PAGE swizzle divergence the architect requires.
        upload_swizzled(128, 96);

        repeat (4) @(posedge clk); rst_n=1'b1; repeat (2) @(posedge clk);
        // load full 256-entry CLUT so any 8-bit index resolves.
        for (int i=0;i<256;i++) clut_write(8'(i), pal(8'(i)));

        // ============================================================
        // PROOF 1 + PROOF 3 — swizzled read recovers logical pattern,
        // CLUT maps index->color. Sweep a grid that crosses block (x=16,
        // y=16) AND the page-row (y=64) boundary.
        // ============================================================
        for (int yy = 0; yy < 96; yy += 5) begin
            for (int xx = 0; xx < 128; xx += 3) begin
                expidx = idx_pattern(xx, yy);
                ref_swizzle(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_clut_idx !== expidx) begin
                    $error("[P1 idx] (%0d,%0d) got idx=0x%02x exp 0x%02x",
                           xx, yy, sw_clut_idx, expidx); errors++;
                end
                if (sw_tex_color !== pal(expidx)) begin
                    $error("[P3 color] (%0d,%0d) idx=0x%02x got=%08x exp=%08x",
                           xx, yy, expidx, sw_tex_color, pal(expidx)); errors++;
                end
            end
        end

        // ============================================================
        // PROOF 2 — swizzled != linear for the SAME bytes. For coords that
        // land in a DIFFERENT block than the linear layout would address,
        // the linear DUT reads a different byte -> different index. Count
        // such coords; require a healthy number (proves not a no-op).
        // ============================================================
        for (int yy = 0; yy < 96; yy += 2) begin
            for (int xx = 0; xx < 128; xx += 2) begin
                sample(xx, yy);
                if (sw_clut_idx !== li_clut_idx) sw_ne_li++;
            end
        end
        if (sw_ne_li < 32) begin
            $error("[P2] swizzled vs linear index differs in only %0d coords — swizzle may be a no-op", sw_ne_li);
            errors++;
        end

        // explicit across-page-ROW-boundary coord: (2,70) is page (0,1) for
        // the swizzled layout (page_y=1 -> page_index 1 -> base+8192); the
        // linear addresser places y=70 only ~70 rows in, still near base.
        sample(2, 70);
        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,70) swizzled addr == linear addr 0x%08x (page boundary not honored)", sw_a);
                errors++;
            end
            if (sw_a < (T8_BASE + 8192)) begin
                $error("[P2 page] (2,70) swizzled addr 0x%08x not in page 1 (>=0x%08x)", sw_a, T8_BASE+8192);
                errors++;
            end
        end

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

    // ================================================================
    // PROOF 4 — SEL_DELAY=1 registered-read byte-lane alignment for SWIZZLED.
    // ================================================================
    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 [7:0]  d2_clut_idx;
    logic [31:0] d2_clut_data;
    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 #(.PSMT8_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(T8_BASE), .tbw(T8_TBW), .psm(PSMT8),
        .tex_rd_en(), .tex_rd_addr(d2_rd_addr), .tex_rd_data(d2_rd_data_q),
        .clut_rd_idx(d2_clut_idx), .clut_rd_data(d2_clut_data),
        .out_valid(), .tex_color(d2_tex_color)
    );

    clut_stub u_clut2 (
        .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(d2_clut_idx), .tex_read_data(d2_clut_data)
    );

    int errors2;

    initial begin
        errors2 = 0;
        d2_in_valid=1'b0; d2_u=0; d2_v=0;
        wait (rst_n == 1'b1);
        repeat (600) @(posedge clk);   // 256-entry CLUT load + main scan underway

        // HELD-address sweep across a row crossing block & page boundaries:
        // address registered, data + pipe aligned, then verify recovered idx.
        for (int xx = 0; xx < 128; xx += 9) begin
            logic [7:0] ei;
            @(negedge clk); d2_in_valid=1'b1; d2_u=11'(xx); d2_v=11'(3);
            @(posedge clk);            // address registered into read
            @(negedge clk); #2;        // data + SEL_DELAY pipe aligned
            ei = idx_pattern(xx, 3);
            if (d2_clut_idx !== ei) begin
                $error("[P4 SEL_DELAY=1 held] x=%0d got idx=0x%02x exp 0x%02x (swizzled byte-lane mis-pipe)",
                       xx, d2_clut_idx, ei); errors2++;
            end
        end
        d2_in_valid=1'b0;

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

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

endmodule : tb_gs_texture_swizzle_psmt8_clut
