Files
retroDE_ps2/rtl/gif_gs/gs_swizzle_psmt8_stub.sv
thejayman77 ec82764bef Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression
(272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps,
and all dump-derived textures/traces) is excluded via .gitignore and stays local.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 20:10:50 -04:00

260 lines
16 KiB
Systemverilog
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 5359, master
// HEAD commit 3000e113e2b3a76357c08dfa80d3c747f40e2706
// (file blob SHA 3581209b8217378f473f9de22a9dbc8c45ca49b6).
// 4 rows × 8 cols, indexed [block_y][block_x].
// columnTable8 — pcsx2/GS/GSTables.cpp lines 111145, 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 247258
// (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 5359).
// 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 111145).
// 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