// retroDE_ps2 — gs_swizzle_psmt8_stub (Ch131) // // Pure-combinational PSMT8 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 PSMT8 // layout. Mirrors Ch119's `gs_swizzle_psmct32_stub` and Ch125's // `gs_swizzle_psmct16_stub` shape, but with PSMT8's wider page // (128 px vs 64 px), 8-cols × 4-rows page block grid, and the // 16×16 within-block column table. // // THIS MODULE IS NOT YET WIRED INTO gs_pcrtc_stub / // gif_image_xfer_stub / gs_stub. Future chapters will wire it // behind a `PSMT8_SWIZZLE`-style parameter gate, mirroring the // PSMCT32 (Ch120/121/122) and PSMCT16 (Ch126/127/128) progressions. // Default-off keeps the legacy linear PSMT8 TBs (Ch96, Ch97, // Ch103, Ch105, Ch107, Ch117) on the linear path. // // SOURCE-TABLE PROVENANCE (per Codex's Ch125/Ch131 guidance): // blockTable8 — pcsx2/GS/GSTables.cpp lines 53–59, master // HEAD commit 3000e113e2b3a76357c08dfa80d3c747f40e2706 // (file blob SHA 3581209b8217378f473f9de22a9dbc8c45ca49b6). // 4 rows × 8 cols, indexed [block_y][block_x]. // columnTable8 — pcsx2/GS/GSTables.cpp lines 111–145, same // commit. 16 rows × 16 cols, indexed [yb][xb], // values are byte-within-block (0..255). // Cross-check — GSLocalMemory.h line 551 BlockNumber8 + // pxOffset template at GSTables.cpp lines 247–258 // (blockSize=256, pageSize=8192, pageWidth=128). // PSMT8 has pageShiftX=7, pageShiftY=6, // blockShiftX=4, blockShiftY=4, // m_bwPg = bw >> (pageShiftX - 6) = bw >> 1 // (so FBW must be even for PSMT8 — PCSX2 asserts // `(bw & 1) == 0` at GSLocalMemory.h:553). // 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 PSMT8 swizzle math in SystemVerilog // as a hardware contract — the values in the blockTable8 / // columnTable8 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 PSMT8 layout: // - VRAM is 4 MiB total, organized in 8 KiB pages. // - Each page is 128×64 PSMT8 pixels (= 128*64*1 = 8192 bytes). // 2× as many pixels per page as PSMCT16 (which has 64×64 px) // and 4× as many as PSMCT32 (64×32 px) because each PSMT8 // pixel is only 1 byte vs CT16's 2 vs CT32's 4. // - Each page is divided into a 8×4 grid of blocks (8 cols of // blocks across, 4 rows down). Each block is 16×16 PSMT8 // pixels (= 16*16*1 = 256 bytes). 8×4 = 32 blocks/page. // - Block ordering within a page follows blockTable8. // - Within a block, byte placement follows columnTable8: a // 16×16 → 256-entry permutation that organizes 4 internal // columns (4 wide each) × 4 internal row-groups (4 tall each) // with intra-group y-pair interleaving. // // Address formula (FBP in 2048-byte units; FBW in 64-pixel // units; addr in bytes; FBW must be even): // page_x = x / 128 // page_y = y / 64 // bw_pg = FBW / 2 // pages per row // page_index = page_y * bw_pg + page_x // page_base = FBP*2048 + page_index*8192 // // block_x_in_page = (x % 128) / 16 // 0..7 // block_y_in_page = (y % 64) / 16 // 0..3 // block_idx = blockTable8[block_y_in_page][block_x_in_page] // block_base = page_base + block_idx*256 // // xb = x % 16 // yb = y % 16 // byte_idx = columnTable8[yb][xb] // 0..255 // addr = block_base + byte_idx `timescale 1ns/1ps module gs_swizzle_psmt8_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 (must be even) input logic [11:0] x, input logic [11:0] y, output logic [31:0] addr ); // -------------------------------------------------------------- // blockTable8 (verbatim from pcsx2/GS/GSTables.cpp lines 53–59). // Indexed [block_y_in_page (0..3)][block_x_in_page (0..7)]. // by=0: { 0, 1, 4, 5,16,17,20,21} // by=1: { 2, 3, 6, 7,18,19,22,23} // by=2: { 8, 9,12,13,24,25,28,29} // by=3: {10,11,14,15,26,27,30,31} // -------------------------------------------------------------- function automatic logic [4:0] swizzle_psmt8( input logic [1:0] by, input logic [2:0] bx); case ({by, bx}) // by=0 5'd0: return 5'd0; 5'd1: return 5'd1; 5'd2: return 5'd4; 5'd3: return 5'd5; 5'd4: return 5'd16; 5'd5: return 5'd17; 5'd6: return 5'd20; 5'd7: return 5'd21; // by=1 5'd8: return 5'd2; 5'd9: return 5'd3; 5'd10: return 5'd6; 5'd11: return 5'd7; 5'd12: return 5'd18; 5'd13: return 5'd19; 5'd14: return 5'd22; 5'd15: return 5'd23; // by=2 5'd16: return 5'd8; 5'd17: return 5'd9; 5'd18: return 5'd12; 5'd19: return 5'd13; 5'd20: return 5'd24; 5'd21: return 5'd25; 5'd22: return 5'd28; 5'd23: return 5'd29; // by=3 5'd24: return 5'd10; 5'd25: return 5'd11; 5'd26: return 5'd14; 5'd27: return 5'd15; 5'd28: return 5'd26; 5'd29: return 5'd27; 5'd30: return 5'd30; default: return 5'd31; endcase endfunction // -------------------------------------------------------------- // columnTable8 (verbatim from pcsx2/GS/GSTables.cpp lines 111–145). // Indexed [yb (0..15)][xb (0..15)] → byte-within-block 0..255. // yb=0: 0 4 16 20 32 36 48 52 2 6 18 22 34 38 50 54 // yb=1: 8 12 24 28 40 44 56 60 10 14 26 30 42 46 58 62 // yb=2: 33 37 49 53 1 5 17 21 35 39 51 55 3 7 19 23 // yb=3: 41 45 57 61 9 13 25 29 43 47 59 63 11 15 27 31 // yb=4: 96 100 112 116 64 68 80 84 98 102 114 118 66 70 82 86 // yb=5: 104 108 120 124 72 76 88 92 106 110 122 126 74 78 90 94 // yb=6: 65 69 81 85 97 101 113 117 67 71 83 87 99 103 115 119 // yb=7: 73 77 89 93 105 109 121 125 75 79 91 95 107 111 123 127 // yb=8: 128 132 144 148 160 164 176 180 130 134 146 150 162 166 178 182 // yb=9: 136 140 152 156 168 172 184 188 138 142 154 158 170 174 186 190 // yb=10: 161 165 177 181 129 133 145 149 163 167 179 183 131 135 147 151 // yb=11: 169 173 185 189 137 141 153 157 171 175 187 191 139 143 155 159 // yb=12: 224 228 240 244 192 196 208 212 226 230 242 246 194 198 210 214 // yb=13: 232 236 248 252 200 204 216 220 234 238 250 254 202 206 218 222 // yb=14: 193 197 209 213 225 229 241 245 195 199 211 215 227 231 243 247 // yb=15: 201 205 217 221 233 237 249 253 203 207 219 223 235 239 251 255 // -------------------------------------------------------------- function automatic logic [7:0] col_idx_psmt8( input logic [3:0] yb, input logic [3:0] xb); case ({yb, xb}) // yb=0 8'd0: return 8'd0; 8'd1: return 8'd4; 8'd2: return 8'd16; 8'd3: return 8'd20; 8'd4: return 8'd32; 8'd5: return 8'd36; 8'd6: return 8'd48; 8'd7: return 8'd52; 8'd8: return 8'd2; 8'd9: return 8'd6; 8'd10: return 8'd18; 8'd11: return 8'd22; 8'd12: return 8'd34; 8'd13: return 8'd38; 8'd14: return 8'd50; 8'd15: return 8'd54; // yb=1 8'd16: return 8'd8; 8'd17: return 8'd12; 8'd18: return 8'd24; 8'd19: return 8'd28; 8'd20: return 8'd40; 8'd21: return 8'd44; 8'd22: return 8'd56; 8'd23: return 8'd60; 8'd24: return 8'd10; 8'd25: return 8'd14; 8'd26: return 8'd26; 8'd27: return 8'd30; 8'd28: return 8'd42; 8'd29: return 8'd46; 8'd30: return 8'd58; 8'd31: return 8'd62; // yb=2 8'd32: return 8'd33; 8'd33: return 8'd37; 8'd34: return 8'd49; 8'd35: return 8'd53; 8'd36: return 8'd1; 8'd37: return 8'd5; 8'd38: return 8'd17; 8'd39: return 8'd21; 8'd40: return 8'd35; 8'd41: return 8'd39; 8'd42: return 8'd51; 8'd43: return 8'd55; 8'd44: return 8'd3; 8'd45: return 8'd7; 8'd46: return 8'd19; 8'd47: return 8'd23; // yb=3 8'd48: return 8'd41; 8'd49: return 8'd45; 8'd50: return 8'd57; 8'd51: return 8'd61; 8'd52: return 8'd9; 8'd53: return 8'd13; 8'd54: return 8'd25; 8'd55: return 8'd29; 8'd56: return 8'd43; 8'd57: return 8'd47; 8'd58: return 8'd59; 8'd59: return 8'd63; 8'd60: return 8'd11; 8'd61: return 8'd15; 8'd62: return 8'd27; 8'd63: return 8'd31; // yb=4 8'd64: return 8'd96; 8'd65: return 8'd100; 8'd66: return 8'd112; 8'd67: return 8'd116; 8'd68: return 8'd64; 8'd69: return 8'd68; 8'd70: return 8'd80; 8'd71: return 8'd84; 8'd72: return 8'd98; 8'd73: return 8'd102; 8'd74: return 8'd114; 8'd75: return 8'd118; 8'd76: return 8'd66; 8'd77: return 8'd70; 8'd78: return 8'd82; 8'd79: return 8'd86; // yb=5 8'd80: return 8'd104; 8'd81: return 8'd108; 8'd82: return 8'd120; 8'd83: return 8'd124; 8'd84: return 8'd72; 8'd85: return 8'd76; 8'd86: return 8'd88; 8'd87: return 8'd92; 8'd88: return 8'd106; 8'd89: return 8'd110; 8'd90: return 8'd122; 8'd91: return 8'd126; 8'd92: return 8'd74; 8'd93: return 8'd78; 8'd94: return 8'd90; 8'd95: return 8'd94; // yb=6 8'd96: return 8'd65; 8'd97: return 8'd69; 8'd98: return 8'd81; 8'd99: return 8'd85; 8'd100: return 8'd97; 8'd101: return 8'd101; 8'd102: return 8'd113; 8'd103: return 8'd117; 8'd104: return 8'd67; 8'd105: return 8'd71; 8'd106: return 8'd83; 8'd107: return 8'd87; 8'd108: return 8'd99; 8'd109: return 8'd103; 8'd110: return 8'd115; 8'd111: return 8'd119; // yb=7 8'd112: return 8'd73; 8'd113: return 8'd77; 8'd114: return 8'd89; 8'd115: return 8'd93; 8'd116: return 8'd105; 8'd117: return 8'd109; 8'd118: return 8'd121; 8'd119: return 8'd125; 8'd120: return 8'd75; 8'd121: return 8'd79; 8'd122: return 8'd91; 8'd123: return 8'd95; 8'd124: return 8'd107; 8'd125: return 8'd111; 8'd126: return 8'd123; 8'd127: return 8'd127; // yb=8 8'd128: return 8'd128; 8'd129: return 8'd132; 8'd130: return 8'd144; 8'd131: return 8'd148; 8'd132: return 8'd160; 8'd133: return 8'd164; 8'd134: return 8'd176; 8'd135: return 8'd180; 8'd136: return 8'd130; 8'd137: return 8'd134; 8'd138: return 8'd146; 8'd139: return 8'd150; 8'd140: return 8'd162; 8'd141: return 8'd166; 8'd142: return 8'd178; 8'd143: return 8'd182; // yb=9 8'd144: return 8'd136; 8'd145: return 8'd140; 8'd146: return 8'd152; 8'd147: return 8'd156; 8'd148: return 8'd168; 8'd149: return 8'd172; 8'd150: return 8'd184; 8'd151: return 8'd188; 8'd152: return 8'd138; 8'd153: return 8'd142; 8'd154: return 8'd154; 8'd155: return 8'd158; 8'd156: return 8'd170; 8'd157: return 8'd174; 8'd158: return 8'd186; 8'd159: return 8'd190; // yb=10 8'd160: return 8'd161; 8'd161: return 8'd165; 8'd162: return 8'd177; 8'd163: return 8'd181; 8'd164: return 8'd129; 8'd165: return 8'd133; 8'd166: return 8'd145; 8'd167: return 8'd149; 8'd168: return 8'd163; 8'd169: return 8'd167; 8'd170: return 8'd179; 8'd171: return 8'd183; 8'd172: return 8'd131; 8'd173: return 8'd135; 8'd174: return 8'd147; 8'd175: return 8'd151; // yb=11 8'd176: return 8'd169; 8'd177: return 8'd173; 8'd178: return 8'd185; 8'd179: return 8'd189; 8'd180: return 8'd137; 8'd181: return 8'd141; 8'd182: return 8'd153; 8'd183: return 8'd157; 8'd184: return 8'd171; 8'd185: return 8'd175; 8'd186: return 8'd187; 8'd187: return 8'd191; 8'd188: return 8'd139; 8'd189: return 8'd143; 8'd190: return 8'd155; 8'd191: return 8'd159; // yb=12 8'd192: return 8'd224; 8'd193: return 8'd228; 8'd194: return 8'd240; 8'd195: return 8'd244; 8'd196: return 8'd192; 8'd197: return 8'd196; 8'd198: return 8'd208; 8'd199: return 8'd212; 8'd200: return 8'd226; 8'd201: return 8'd230; 8'd202: return 8'd242; 8'd203: return 8'd246; 8'd204: return 8'd194; 8'd205: return 8'd198; 8'd206: return 8'd210; 8'd207: return 8'd214; // yb=13 8'd208: return 8'd232; 8'd209: return 8'd236; 8'd210: return 8'd248; 8'd211: return 8'd252; 8'd212: return 8'd200; 8'd213: return 8'd204; 8'd214: return 8'd216; 8'd215: return 8'd220; 8'd216: return 8'd234; 8'd217: return 8'd238; 8'd218: return 8'd250; 8'd219: return 8'd254; 8'd220: return 8'd202; 8'd221: return 8'd206; 8'd222: return 8'd218; 8'd223: return 8'd222; // yb=14 8'd224: return 8'd193; 8'd225: return 8'd197; 8'd226: return 8'd209; 8'd227: return 8'd213; 8'd228: return 8'd225; 8'd229: return 8'd229; 8'd230: return 8'd241; 8'd231: return 8'd245; 8'd232: return 8'd195; 8'd233: return 8'd199; 8'd234: return 8'd211; 8'd235: return 8'd215; 8'd236: return 8'd227; 8'd237: return 8'd231; 8'd238: return 8'd243; 8'd239: return 8'd247; // yb=15 8'd240: return 8'd201; 8'd241: return 8'd205; 8'd242: return 8'd217; 8'd243: return 8'd221; 8'd244: return 8'd233; 8'd245: return 8'd237; 8'd246: return 8'd249; 8'd247: return 8'd253; 8'd248: return 8'd203; 8'd249: return 8'd207; 8'd250: return 8'd219; 8'd251: return 8'd223; 8'd252: return 8'd235; 8'd253: return 8'd239; 8'd254: return 8'd251; default: return 8'd255; 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 [3:0] xb; logic [3:0] yb; logic [5:0] bw_pg; assign page_x = x >> 7; // x / 128 assign page_y = y >> 6; // y / 64 assign by = y[5:4]; // (y % 64) / 16 assign bx = x[6:4]; // (x % 128) / 16 assign xb = x[3:0]; // x % 16 assign yb = y[3:0]; // y % 16 assign bw_pg = fbw >> 1; // FBW / 2 (FBW must be even) logic [4:0] block_idx; assign block_idx = swizzle_psmt8(by, bx); logic [7:0] byte_idx; assign byte_idx = col_idx_psmt8(yb, xb); logic [31:0] page_base; logic [31:0] block_base; logic [31:0] page_index; assign page_index = ({20'd0, page_y} * {26'd0, bw_pg}) + {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 addr = block_base + {24'd0, byte_idx}; // + byte_idx (1 byte/pixel) endmodule : gs_swizzle_psmt8_stub