// 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