// retroDE_ps2 — gs_swizzle_psmct32_stub (Ch119)
//
// Pure-combinational PSMCT32 page/block swizzle: maps a pixel
// coordinate (x, y) within a framebuffer at (FBP, FBW) to its
// physical VRAM byte address using the real PS2 GS layout.
//
// THIS MODULE DOES NOT YET REPLACE THE LINEAR ADDRESSING in
// gs_stub / gs_pcrtc_stub / gif_image_xfer_stub. It is the
// math primitive that future chapters will wire into the
// existing address paths to swap "linear FBW*64*y + x*4"
// for the real GS swizzled addressing. Ch119 establishes the
// math, locks it against the canonical PCSX2 PSMCT32 block
// table with a focused TB, and leaves integration to follow-on
// chapters so the existing 109 TBs stay on the linear path.
//
// Real PS2 PSMCT32 layout (per PCSX2 GS source):
//   - VRAM is 4 MiB total, organized in 8 KiB pages.
//   - Each page is 64×32 PSMCT32 pixels (= 64×32×4 = 8192 bytes).
//   - Each page is divided into a 4×8 grid of blocks (4 rows of
//     blocks, 8 cols of blocks per row), each block 8×8 pixels
//     (= 256 bytes). 4×8 = 32 blocks/page.
//   - Block ordering within a page is NOT row-major; it follows
//     the PSMCT32 swizzle table below (a Z-order-like permutation
//     of (block_x, block_y) that PCSX2's GSLocalMemoryFunctions.cpp
//     defines verbatim).
//   - Within a block, PSMCT32 is row-major: pixel (xb, yb) maps
//     to byte_offset_in_block = yb*32 + xb*4 (no further swizzle
//     for PSMCT32 — other PSMs have intra-block reorderings).
//
// Address formula (linear in pages, swizzled in blocks within a
// page, linear within a block):
//   pages_per_fbrow = FBW                      // FBW is in 64-px units; PSMCT32 page is 64 px wide
//   page_x          = x / 64
//   page_y          = y / 32
//   page_index      = page_y * pages_per_fbrow + page_x
//   page_base       = FBP*2048 + page_index*8192
//
//   block_x_in_page = (x % 64) / 8             // 0..7
//   block_y_in_page = (y % 32) / 8             // 0..3
//   block_idx       = SWIZZLE[block_y_in_page][block_x_in_page]
//   block_base      = page_base + block_idx*256
//
//   xb              = x % 8
//   yb              = y % 8
//   byte_in_block   = yb*32 + xb*4
//
//   addr            = block_base + byte_in_block
//
// FBP is a 9-bit field at 2048-byte granularity, so FBP*2048
// can land at any 2048-byte boundary in VRAM — including mid-
// page boundaries (FBP[1:0] != 0). The math here treats FBP*2048
// as the literal byte base, with the swizzled page/block/pixel
// offset added on top, which matches real-PS2 behavior. Page-
// aligned FBP (FBP[1:0]==0) is the common case in our demo, but
// the address formula is bit-correct for any 2048-byte-aligned
// FBP, and the focused TB exercises non-page-aligned FBP=1,2,3
// to lock that.

`timescale 1ns/1ps

module gs_swizzle_psmct32_stub
(
    // Framebuffer config (matches FRAME_1 register fields).
    input  logic [8:0]  fbp,    // FBP   — frame base, in 2048-byte units
    input  logic [5:0]  fbw,    // FBW   — frame width, in 64-pixel units
    // Pixel coordinate within the framebuffer.
    input  logic [11:0] x,
    input  logic [11:0] y,
    // Resulting VRAM byte address.
    output logic [31:0] addr
);

    // --------------------------------------------------------------
    // Block swizzle table for PSMCT32 (PCSX2 GSLocalMemoryFunctions.cpp,
    // psmt32 block order). Indexed [block_y_in_page][block_x_in_page]
    // with block_x ∈ 0..7 and block_y ∈ 0..3; value is the linear
    // block index within the page (0..31).
    // --------------------------------------------------------------
    function automatic logic [4:0] swizzle_psmct32(
        input logic [1:0] by,    // block_y_in_page (0..3)
        input logic [2:0] bx);   // block_x_in_page (0..7)
        case ({by, bx})
            5'h00: return 5'd0;    // (0,0)
            5'h01: return 5'd1;    // (0,1)
            5'h02: return 5'd4;    // (0,2)
            5'h03: return 5'd5;    // (0,3)
            5'h04: return 5'd16;   // (0,4)
            5'h05: return 5'd17;   // (0,5)
            5'h06: return 5'd20;   // (0,6)
            5'h07: return 5'd21;   // (0,7)
            5'h08: return 5'd2;    // (1,0)
            5'h09: return 5'd3;    // (1,1)
            5'h0A: return 5'd6;    // (1,2)
            5'h0B: return 5'd7;    // (1,3)
            5'h0C: return 5'd18;   // (1,4)
            5'h0D: return 5'd19;   // (1,5)
            5'h0E: return 5'd22;   // (1,6)
            5'h0F: return 5'd23;   // (1,7)
            5'h10: return 5'd8;    // (2,0)
            5'h11: return 5'd9;    // (2,1)
            5'h12: return 5'd12;   // (2,2)
            5'h13: return 5'd13;   // (2,3)
            5'h14: return 5'd24;   // (2,4)
            5'h15: return 5'd25;   // (2,5)
            5'h16: return 5'd28;   // (2,6)
            5'h17: return 5'd29;   // (2,7)
            5'h18: return 5'd10;   // (3,0)
            5'h19: return 5'd11;   // (3,1)
            5'h1A: return 5'd14;   // (3,2)
            5'h1B: return 5'd15;   // (3,3)
            5'h1C: return 5'd26;   // (3,4)
            5'h1D: return 5'd27;   // (3,5)
            5'h1E: return 5'd30;   // (3,6)
            default: return 5'd31; // (3,7)
        endcase
    endfunction

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

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

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

    // Address composition.
    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 = ({29'd0, yb} << 5) + ({29'd0, xb} << 2);     // yb*32 + xb*4

    assign addr = block_base + byte_in_block;

endmodule : gs_swizzle_psmct32_stub
