// retroDE_ps2 — gs_swizzle_psmct16_stub (Ch125)
//
// Pure-combinational PSMCT16 page/block/column swizzle: maps a
// pixel coordinate (x, y) within a framebuffer at (FBP, FBW) to
// its physical VRAM byte address using the real PS2 GS PSMCT16
// layout. Mirrors Ch119's `gs_swizzle_psmct32_stub` shape but
// with PSMCT16's 4-cols × 8-rows page block grid (represented
// as `blockTable16[8][4]` indexed `[block_y][block_x]`) and the
// within-block column table that PSMCT32 didn't need (CT32
// within-block IS row-major halfwords by accident; CT16 is not).
//
// THIS MODULE IS NOT YET WIRED INTO gs_pcrtc_stub /
// gif_image_xfer_stub / gs_stub. Future chapters will wire it
// behind a `PSMCT16_SWIZZLE`-style parameter gate, mirroring the
// PSMCT32 progression (Ch120 read-side → Ch121 image-xfer write
// side → Ch122 raster write side → Ch123/Ch124 e2e demos).
// Default-off keeps the legacy linear PSMCT16 TBs (Ch94, Ch95,
// Ch103, Ch116) on the linear path.
//
// SOURCE-TABLE PROVENANCE (per Codex's Ch125 guidance):
//   blockTable16   — pcsx2/GS/GSTables.cpp lines 29–39, master
//                    HEAD commit 3d71e310 (file-touch commit
//                    d983b2b0, 2026-01-12). 8 rows × 4 cols,
//                    indexed [block_y][block_x].
//   columnTable16  — pcsx2/GS/GSTables.cpp lines 91–109, same
//                    commit. 8 rows × 16 cols, indexed [yb][xb],
//                    values are halfword-within-block (0..127).
//   Cross-check    — older GSdx (Debian pcsx2 1.5.0~gfc1d9aef0)
//                    PixelAddressOrg16(x, y, bp, bw) =
//                    (BlockNumber16(x, y, bp, bw) << 7) +
//                    columnTable16[y & 7][x & 15], with
//                    BlockNumber16 = bp + ((y>>1) & ~0x1f)*bw +
//                    ((x>>1) & ~0x1f) +
//                    blockTable16[(y>>3)&7][(x>>4)&3].
//                    The `<< 7` confirms columnTable16 is in
//                    halfword units (block = 128 halfwords).
//                    Multiply final value by 2 for byte address.
//                    PCSX2's `bp` is in 256-byte block-pointer
//                    units; in our FBP (2048-byte) units,
//                    bp = FBP * 8, so bp*256 = FBP*2048.
//
// NOTE on PCSX2 license: the PCSX2 project is GPL-3.0+. This
// stub re-expresses the same PSMCT16 swizzle math in
// SystemVerilog as a hardware contract — the values in the
// blockTable16 / columnTable16 case statements come from PCSX2
// source and represent the PS2 hardware layout itself (not
// PCSX2-original creative content). The retroDE_ps2 project
// authors should consider whether this provenance affects
// licensing for downstream consumers; from an engineering
// correctness standpoint, locking against the canonical source
// is the only way to be byte-accurate to real PS2 VRAM.
//
// Real PS2 PSMCT16 layout:
//   - VRAM is 4 MiB total, organized in 8 KiB pages.
//   - Each page is 64×64 PSMCT16 pixels (= 64*64*2 = 8192 bytes).
//     2× as many pixels per page as PSMCT32 (which has 64×32 px)
//     because each PSMCT16 pixel is 2 bytes vs CT32's 4.
//   - Each page is divided into a 4×8 grid of blocks (4 cols of
//     blocks across, 8 rows down). Each block is 16×8 PSMCT16
//     pixels (= 16*8*2 = 256 bytes). 4×8 = 32 blocks/page.
//   - Block ordering within a page follows blockTable16, which
//     differs from blockTable32 because the grid shape is
//     different (8×4 vs 4×8).
//   - Within a block, halfword placement follows columnTable16:
//     a 16×8 → 128-entry permutation that organizes the 4
//     internal 16×2-pixel sub-columns and interleaves the two
//     pixel rows per sub-column.
//
// Address formula (FBP in 2048-byte units; FBW in 64-pixel
// units; addr in bytes):
//   page_x          = x / 64
//   page_y          = y / 64
//   page_index      = page_y * FBW + page_x
//   page_base       = FBP*2048 + page_index*8192
//
//   block_x_in_page = (x % 64) / 16             // 0..3
//   block_y_in_page = (y % 64) / 8              // 0..7
//   block_idx       = blockTable16[block_y_in_page][block_x_in_page]
//   block_base      = page_base + block_idx*256
//
//   xb              = x % 16
//   yb              = y % 8
//   hw_idx          = columnTable16[yb][xb]     // 0..127
//   addr            = block_base + hw_idx*2

`timescale 1ns/1ps

module gs_swizzle_psmct16_stub
(
    input  logic [8:0]  fbp,    // FBP — frame base, in 2048-byte units
    input  logic [5:0]  fbw,    // FBW — frame width, in 64-pixel units
    input  logic [11:0] x,
    input  logic [11:0] y,
    output logic [31:0] addr
);

    // --------------------------------------------------------------
    // blockTable16 (verbatim from pcsx2/GS/GSTables.cpp lines 29–39).
    // Indexed [block_y_in_page (0..7)][block_x_in_page (0..3)].
    // --------------------------------------------------------------
    function automatic logic [4:0] swizzle_psmct16(
        input logic [2:0] by,
        input logic [1:0] bx);
        case ({by, bx})
            5'd0:  return 5'd0;     // (0,0)
            5'd1:  return 5'd2;     // (0,1)
            5'd2:  return 5'd8;     // (0,2)
            5'd3:  return 5'd10;    // (0,3)
            5'd4:  return 5'd1;     // (1,0)
            5'd5:  return 5'd3;     // (1,1)
            5'd6:  return 5'd9;     // (1,2)
            5'd7:  return 5'd11;    // (1,3)
            5'd8:  return 5'd4;     // (2,0)
            5'd9:  return 5'd6;     // (2,1)
            5'd10: return 5'd12;    // (2,2)
            5'd11: return 5'd14;    // (2,3)
            5'd12: return 5'd5;     // (3,0)
            5'd13: return 5'd7;     // (3,1)
            5'd14: return 5'd13;    // (3,2)
            5'd15: return 5'd15;    // (3,3)
            5'd16: return 5'd16;    // (4,0)
            5'd17: return 5'd18;    // (4,1)
            5'd18: return 5'd24;    // (4,2)
            5'd19: return 5'd26;    // (4,3)
            5'd20: return 5'd17;    // (5,0)
            5'd21: return 5'd19;    // (5,1)
            5'd22: return 5'd25;    // (5,2)
            5'd23: return 5'd27;    // (5,3)
            5'd24: return 5'd20;    // (6,0)
            5'd25: return 5'd22;    // (6,1)
            5'd26: return 5'd28;    // (6,2)
            5'd27: return 5'd30;    // (6,3)
            5'd28: return 5'd21;    // (7,0)
            5'd29: return 5'd23;    // (7,1)
            5'd30: return 5'd29;    // (7,2)
            default: return 5'd31;  // (7,3)
        endcase
    endfunction

    // --------------------------------------------------------------
    // columnTable16 (verbatim from pcsx2/GS/GSTables.cpp lines 91–109).
    // Indexed [yb (0..7)][xb (0..15)] → halfword-within-block 0..127.
    //   yb=0:   0  2  8 10 16 18 24 26  1  3  9 11 17 19 25 27
    //   yb=1:   4  6 12 14 20 22 28 30  5  7 13 15 21 23 29 31
    //   yb=2:  32 34 40 42 48 50 56 58 33 35 41 43 49 51 57 59
    //   yb=3:  36 38 44 46 52 54 60 62 37 39 45 47 53 55 61 63
    //   yb=4:  64 66 72 74 80 82 88 90 65 67 73 75 81 83 89 91
    //   yb=5:  68 70 76 78 84 86 92 94 69 71 77 79 85 87 93 95
    //   yb=6:  96 98 104 106 112 114 120 122  97 99 105 107 113 115 121 123
    //   yb=7: 100 102 108 110 116 118 124 126 101 103 109 111 117 119 125 127
    // --------------------------------------------------------------
    function automatic logic [6:0] col_idx_psmct16(
        input logic [2:0] yb,
        input logic [3:0] xb);
        case ({yb, xb})
            // yb=0
            7'd0:   return 7'd0;   7'd1:   return 7'd2;   7'd2:   return 7'd8;   7'd3:   return 7'd10;
            7'd4:   return 7'd16;  7'd5:   return 7'd18;  7'd6:   return 7'd24;  7'd7:   return 7'd26;
            7'd8:   return 7'd1;   7'd9:   return 7'd3;   7'd10:  return 7'd9;   7'd11:  return 7'd11;
            7'd12:  return 7'd17;  7'd13:  return 7'd19;  7'd14:  return 7'd25;  7'd15:  return 7'd27;
            // yb=1
            7'd16:  return 7'd4;   7'd17:  return 7'd6;   7'd18:  return 7'd12;  7'd19:  return 7'd14;
            7'd20:  return 7'd20;  7'd21:  return 7'd22;  7'd22:  return 7'd28;  7'd23:  return 7'd30;
            7'd24:  return 7'd5;   7'd25:  return 7'd7;   7'd26:  return 7'd13;  7'd27:  return 7'd15;
            7'd28:  return 7'd21;  7'd29:  return 7'd23;  7'd30:  return 7'd29;  7'd31:  return 7'd31;
            // yb=2
            7'd32:  return 7'd32;  7'd33:  return 7'd34;  7'd34:  return 7'd40;  7'd35:  return 7'd42;
            7'd36:  return 7'd48;  7'd37:  return 7'd50;  7'd38:  return 7'd56;  7'd39:  return 7'd58;
            7'd40:  return 7'd33;  7'd41:  return 7'd35;  7'd42:  return 7'd41;  7'd43:  return 7'd43;
            7'd44:  return 7'd49;  7'd45:  return 7'd51;  7'd46:  return 7'd57;  7'd47:  return 7'd59;
            // yb=3
            7'd48:  return 7'd36;  7'd49:  return 7'd38;  7'd50:  return 7'd44;  7'd51:  return 7'd46;
            7'd52:  return 7'd52;  7'd53:  return 7'd54;  7'd54:  return 7'd60;  7'd55:  return 7'd62;
            7'd56:  return 7'd37;  7'd57:  return 7'd39;  7'd58:  return 7'd45;  7'd59:  return 7'd47;
            7'd60:  return 7'd53;  7'd61:  return 7'd55;  7'd62:  return 7'd61;  7'd63:  return 7'd63;
            // yb=4
            7'd64:  return 7'd64;  7'd65:  return 7'd66;  7'd66:  return 7'd72;  7'd67:  return 7'd74;
            7'd68:  return 7'd80;  7'd69:  return 7'd82;  7'd70:  return 7'd88;  7'd71:  return 7'd90;
            7'd72:  return 7'd65;  7'd73:  return 7'd67;  7'd74:  return 7'd73;  7'd75:  return 7'd75;
            7'd76:  return 7'd81;  7'd77:  return 7'd83;  7'd78:  return 7'd89;  7'd79:  return 7'd91;
            // yb=5
            7'd80:  return 7'd68;  7'd81:  return 7'd70;  7'd82:  return 7'd76;  7'd83:  return 7'd78;
            7'd84:  return 7'd84;  7'd85:  return 7'd86;  7'd86:  return 7'd92;  7'd87:  return 7'd94;
            7'd88:  return 7'd69;  7'd89:  return 7'd71;  7'd90:  return 7'd77;  7'd91:  return 7'd79;
            7'd92:  return 7'd85;  7'd93:  return 7'd87;  7'd94:  return 7'd93;  7'd95:  return 7'd95;
            // yb=6
            7'd96:  return 7'd96;  7'd97:  return 7'd98;  7'd98:  return 7'd104; 7'd99:  return 7'd106;
            7'd100: return 7'd112; 7'd101: return 7'd114; 7'd102: return 7'd120; 7'd103: return 7'd122;
            7'd104: return 7'd97;  7'd105: return 7'd99;  7'd106: return 7'd105; 7'd107: return 7'd107;
            7'd108: return 7'd113; 7'd109: return 7'd115; 7'd110: return 7'd121; 7'd111: return 7'd123;
            // yb=7
            7'd112: return 7'd100; 7'd113: return 7'd102; 7'd114: return 7'd108; 7'd115: return 7'd110;
            7'd116: return 7'd116; 7'd117: return 7'd118; 7'd118: return 7'd124; 7'd119: return 7'd126;
            7'd120: return 7'd101; 7'd121: return 7'd103; 7'd122: return 7'd109; 7'd123: return 7'd111;
            7'd124: return 7'd117; 7'd125: return 7'd119; 7'd126: return 7'd125; default: return 7'd127;
        endcase
    endfunction

    // Decompose pixel coord into page / block / pixel-in-block.
    logic [11:0] page_x;
    logic [11:0] page_y;
    logic [2:0]  by;
    logic [1:0]  bx;
    logic [3:0]  xb;
    logic [2:0]  yb;

    assign page_x = x >> 6;     // x / 64
    assign page_y = y >> 6;     // y / 64
    assign by     = y[5:3];     // (y % 64) / 8
    assign bx     = x[5:4];     // (x % 64) / 16
    assign xb     = x[3:0];     // x % 16
    assign yb     = y[2:0];     // y % 8

    logic [4:0] block_idx;
    assign block_idx = swizzle_psmct16(by, bx);

    logic [6:0] hw_idx;
    assign hw_idx = col_idx_psmct16(yb, xb);

    logic [31:0] page_base;
    logic [31:0] block_base;
    logic [31:0] byte_in_block;
    logic [31:0] page_index;
    assign page_index    = ({20'd0, page_y} * {26'd0, fbw}) + {20'd0, page_x};
    assign page_base     = ({23'd0, fbp} << 11) + (page_index << 13);   // FBP*2048 + page_index*8192
    assign block_base    = page_base + ({27'd0, block_idx} << 8);       // + block_idx*256
    assign byte_in_block = {24'd0, hw_idx, 1'b0};                       // hw_idx * 2

    assign addr = block_base + byte_in_block;

endmodule : gs_swizzle_psmct16_stub
