// retroDE_ps2 — tb_gs_swizzle_psmct16 (Ch125) // // Locks the contract for `gs_swizzle_psmct16_stub` — the second // PSM swizzle math primitive in the project, mirroring Ch119's // `tb_gs_swizzle_psmct32` for the new PSM. Future chapters will // wire it into gs_pcrtc_stub / gif_image_xfer_stub / gs_stub // behind a `PSMCT16_SWIZZLE`-style parameter gate so the existing // legacy linear-PSMCT16 TBs (Ch94 / Ch95 / Ch103 read side; Ch95 // / Ch116 write side) stay on the linear path. Pre-Ch126+ those // modules use the linear `(FBW*64)*y + x*2` PSMCT16 byte stride. // // 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], where // 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. // // Address formula in OUR units (FBP in 2048-byte units; FBW in // 64-pixel units; addr in bytes): // page_x = x / 64 // page_y = y / 64 (PSMCT16 page is 64 tall) // page_index = page_y * FBW + page_x // page_base = FBP*2048 + page_index*8192 // // block_x_in_page = (x % 64) / 16 // 0..3 (block 16 wide) // block_y_in_page = (y % 64) / 8 // 0..7 (block 8 tall) // 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 // // Verification strategy (mirrors Ch119, with two source-table // lock phases — one for blockTable16 AND one for columnTable16, // since the column table was the exact blocker for this chapter): // 1) Spot-checks against hand-computed addresses for the // well-defined edge cases. // 2a) **Independent column-table source lock** — 128 hard-coded // address checks (one per (yb, xb) inside block (0,0)) // where the expected halfword index is taken VERBATIM from // PCSX2 columnTable16, NOT derived from the in-TB // ref_col_idx16 function. A consistently-miscopied-but- // still-permuted DUT column table that happened to also // miscopy ref_col_idx16 the same way could not pass this // phase. Locks the column table to the canonical source // independently of the in-TB reference. // 2b) Within-block 16×8 walk via ref_col_idx16. Redundant // with Phase 2a; cross-checks ref_col_idx16 against the // DUT (the bijectivity sweep below relies on it). // 3) **Independent block-table source lock** — 32 hard-coded // address checks (one per block in page 0) where the // expected block index is taken VERBATIM from PCSX2 // blockTable16, NOT derived from ref_block_idx16. // 4) Block-swizzle walk via ref_block_idx16. Redundant with // phase 3; cross-checks the in-TB ref against the DUT. // 5) Bijectivity sweep over the 64×64 page (4096 halfword // slots = 8192 bytes). Asserts each halfword address is // hit exactly once and lands in [0, 8192). Catches any // swap in EITHER table. // // Plus: multi-page sanity at FBW=2; non-page-aligned FBP // coverage at FBP ∈ {1, 2, 3} (real PS2 supports any 2048-byte- // aligned FBP, same broadening Ch119 adopted after audit). `timescale 1ns/1ps module tb_gs_swizzle_psmct16; // ----------------------------------------------------------- // DUT // ----------------------------------------------------------- logic [8:0] fbp; logic [5:0] fbw; logic [11:0] x; logic [11:0] y; logic [31:0] addr; gs_swizzle_psmct16_stub u_dut ( .fbp(fbp), .fbw(fbw), .x(x), .y(y), .addr(addr) ); int errors; initial errors = 0; // ----------------------------------------------------------- // Helpers. // ----------------------------------------------------------- task automatic compute( input logic [8:0] fbp_v, input logic [5:0] fbw_v, input logic [11:0] x_v, input logic [11:0] y_v, output logic [31:0] addr_o); fbp = fbp_v; fbw = fbw_v; x = x_v; y = y_v; #1; addr_o = addr; endtask task automatic check( input logic [8:0] fbp_v, input logic [5:0] fbw_v, input logic [11:0] x_v, input logic [11:0] y_v, input logic [31:0] expected, input string tag); logic [31:0] got; compute(fbp_v, fbw_v, x_v, y_v, got); if (got !== expected) begin $error("[%s] fbp=%0d fbw=%0d (x=%0d,y=%0d) got 0x%08x expected 0x%08x", tag, fbp_v, fbw_v, x_v, y_v, got, expected); errors = errors + 1; end endtask // ----------------------------------------------------------- // Reference tables (verbatim from pcsx2/GS/GSTables.cpp). // ----------------------------------------------------------- function automatic int ref_block_idx16(input int by, input int bx); // _blockTable16[8][4] = { // { 0, 2, 8, 10 }, // { 1, 3, 9, 11 }, // { 4, 6, 12, 14 }, // { 5, 7, 13, 15 }, // { 16, 18, 24, 26 }, // { 17, 19, 25, 27 }, // { 20, 22, 28, 30 }, // { 21, 23, 29, 31 } }; case ({by[2:0], bx[1:0]}) 5'd0: return 0; 5'd1: return 2; 5'd2: return 8; 5'd3: return 10; 5'd4: return 1; 5'd5: return 3; 5'd6: return 9; 5'd7: return 11; 5'd8: return 4; 5'd9: return 6; 5'd10: return 12; 5'd11: return 14; 5'd12: return 5; 5'd13: return 7; 5'd14: return 13; 5'd15: return 15; 5'd16: return 16; 5'd17: return 18; 5'd18: return 24; 5'd19: return 26; 5'd20: return 17; 5'd21: return 19; 5'd22: return 25; 5'd23: return 27; 5'd24: return 20; 5'd25: return 22; 5'd26: return 28; 5'd27: return 30; 5'd28: return 21; 5'd29: return 23; 5'd30: return 29; default: return 31; endcase endfunction function automatic int ref_col_idx16(input int yb, input int xb); // columnTable16[8][16], halfword-within-block index 0..127. // Row 0 (yb=0): 0 2 8 10 16 18 24 26 1 3 9 11 17 19 25 27 // Row 1 (yb=1): 4 6 12 14 20 22 28 30 5 7 13 15 21 23 29 31 // Row 2 (yb=2):32 34 40 42 48 50 56 58 33 35 41 43 49 51 57 59 // Row 3 (yb=3):36 38 44 46 52 54 60 62 37 39 45 47 53 55 61 63 // Row 4 (yb=4):64 66 72 74 80 82 88 90 65 67 73 75 81 83 89 91 // Row 5 (yb=5):68 70 76 78 84 86 92 94 69 71 77 79 85 87 93 95 // Row 6 (yb=6):96 98 104 106 112 114 120 122 97 99 105 107 113 115 121 123 // Row 7 (yb=7):100 102 108 110 116 118 124 126 101 103 109 111 117 119 125 127 case ({yb[2:0], xb[3:0]}) // yb=0 7'd0: return 0; 7'd1: return 2; 7'd2: return 8; 7'd3: return 10; 7'd4: return 16; 7'd5: return 18; 7'd6: return 24; 7'd7: return 26; 7'd8: return 1; 7'd9: return 3; 7'd10: return 9; 7'd11: return 11; 7'd12: return 17; 7'd13: return 19; 7'd14: return 25; 7'd15: return 27; // yb=1 7'd16: return 4; 7'd17: return 6; 7'd18: return 12; 7'd19: return 14; 7'd20: return 20; 7'd21: return 22; 7'd22: return 28; 7'd23: return 30; 7'd24: return 5; 7'd25: return 7; 7'd26: return 13; 7'd27: return 15; 7'd28: return 21; 7'd29: return 23; 7'd30: return 29; 7'd31: return 31; // yb=2 7'd32: return 32; 7'd33: return 34; 7'd34: return 40; 7'd35: return 42; 7'd36: return 48; 7'd37: return 50; 7'd38: return 56; 7'd39: return 58; 7'd40: return 33; 7'd41: return 35; 7'd42: return 41; 7'd43: return 43; 7'd44: return 49; 7'd45: return 51; 7'd46: return 57; 7'd47: return 59; // yb=3 7'd48: return 36; 7'd49: return 38; 7'd50: return 44; 7'd51: return 46; 7'd52: return 52; 7'd53: return 54; 7'd54: return 60; 7'd55: return 62; 7'd56: return 37; 7'd57: return 39; 7'd58: return 45; 7'd59: return 47; 7'd60: return 53; 7'd61: return 55; 7'd62: return 61; 7'd63: return 63; // yb=4 7'd64: return 64; 7'd65: return 66; 7'd66: return 72; 7'd67: return 74; 7'd68: return 80; 7'd69: return 82; 7'd70: return 88; 7'd71: return 90; 7'd72: return 65; 7'd73: return 67; 7'd74: return 73; 7'd75: return 75; 7'd76: return 81; 7'd77: return 83; 7'd78: return 89; 7'd79: return 91; // yb=5 7'd80: return 68; 7'd81: return 70; 7'd82: return 76; 7'd83: return 78; 7'd84: return 84; 7'd85: return 86; 7'd86: return 92; 7'd87: return 94; 7'd88: return 69; 7'd89: return 71; 7'd90: return 77; 7'd91: return 79; 7'd92: return 85; 7'd93: return 87; 7'd94: return 93; 7'd95: return 95; // yb=6 7'd96: return 96; 7'd97: return 98; 7'd98: return 104; 7'd99: return 106; 7'd100: return 112; 7'd101: return 114; 7'd102: return 120; 7'd103: return 122; 7'd104: return 97; 7'd105: return 99; 7'd106: return 105; 7'd107: return 107; 7'd108: return 113; 7'd109: return 115; 7'd110: return 121; 7'd111: return 123; // yb=7 7'd112: return 100; 7'd113: return 102; 7'd114: return 108; 7'd115: return 110; 7'd116: return 116; 7'd117: return 118; 7'd118: return 124; 7'd119: return 126; 7'd120: return 101; 7'd121: return 103; 7'd122: return 109; 7'd123: return 111; 7'd124: return 117; 7'd125: return 119; 7'd126: return 125; default: return 127; endcase endfunction function automatic logic [31:0] ref_addr16( input int fbp_v, input int fbw_v, input int x_v, input int y_v); int page_x, page_y, page_idx, page_base; int by, bx, blk_idx, xb, yb, hw_idx; int addr_v; page_x = x_v / 64; page_y = y_v / 64; page_idx = page_y * fbw_v + page_x; page_base = fbp_v * 2048 + page_idx * 8192; by = (y_v % 64) / 8; bx = (x_v % 64) / 16; blk_idx = ref_block_idx16(by, bx); xb = x_v % 16; yb = y_v % 8; hw_idx = ref_col_idx16(yb, xb); addr_v = page_base + blk_idx * 256 + hw_idx * 2; return addr_v[31:0]; endfunction initial begin fbp = 9'd0; fbw = 6'd1; x = 12'd0; y = 12'd0; #1; // ----------------------------------------------------------- // Phase 1 — spot-checks (hand-computed corners). // ----------------------------------------------------------- // (0, 0) → block (0,0)=0, hw=0 → addr 0. check(9'd0, 6'd1, 12'd0, 12'd0, 32'd0, "origin"); // (1, 0) → block 0, xb=1 yb=0 → hw=2 → byte 4. check(9'd0, 6'd1, 12'd1, 12'd0, 32'd4, "origin+1px"); // (8, 0) → block 0, xb=8 yb=0 → hw=1 → byte 2. check(9'd0, 6'd1, 12'd8, 12'd0, 32'd2, "intra-block-x8"); // (0, 1) → block 0, xb=0 yb=1 → hw=4 → byte 8. check(9'd0, 6'd1, 12'd0, 12'd1, 32'd8, "intra-block-y1"); // (15, 7) → last px of block (0,0): xb=15 yb=7 → hw=127 → // byte 254. block_base=0 → addr 254. check(9'd0, 6'd1, 12'd15, 12'd7, 32'd254, "block-(0,0)-last"); // (16, 0) → block (0,1) (by=0, bx=1) → swizzle=2 → // block_base=512. xb=0 yb=0 → hw=0 → addr 512. check(9'd0, 6'd1, 12'd16, 12'd0, 32'd512, "block-(0,1)-origin"); // (0, 8) → block (1,0) → swizzle=1 → block_base=256 → addr 256. check(9'd0, 6'd1, 12'd0, 12'd8, 32'd256, "block-(1,0)-origin"); // (32, 0) → block (0,2) → swizzle=8 → block_base=2048. check(9'd0, 6'd1, 12'd32, 12'd0, 32'd2048, "block-(0,2)-origin"); // (0, 16) → block (2,0) → swizzle=4 → block_base=1024. check(9'd0, 6'd1, 12'd0, 12'd16, 32'd1024, "block-(2,0)-origin"); // (63, 63) → last pixel of last block of page 0: // block (7,3) → swizzle=31 → block_base=7936. // xb=15 yb=7 → hw=127 → byte 254. addr = 7936 + 254 = 8190. check(9'd0, 6'd1, 12'd63, 12'd63, 32'd8190, "page0-last-pixel"); // (64, 0) → start of page 1 (FBW=1) → page_idx=1, // page_base=8192. block (0,0) → byte 0 → addr 8192. check(9'd0, 6'd1, 12'd64, 12'd0, 32'd8192, "page1-x-origin"); // (0, 64) → start of page-row 1 (FBW=1) → page_idx=1, // page_base=8192. check(9'd0, 6'd1, 12'd0, 12'd64, 32'd8192, "page1-y-origin"); // FBP=4 → FBP*2048=8192 = 1 page. (0,0) → addr 8192. check(9'd4, 6'd1, 12'd0, 12'd0, 32'd8192, "fbp4-origin"); // ----------------------------------------------------------- // Phase 2a — INDEPENDENT column-table source lock. 128 hard- // coded address checks (one per (yb, xb) inside block (0,0)) // where the expected halfword index is taken VERBATIM from // PCSX2 columnTable16, NOT derived from the in-TB // ref_col_idx16 function. Since columnTable16 was the exact // blocker for this chapter, this phase pins the DUT's column // table to the canonical source independently of // ref_col_idx16: a consistently miscopied-but-still-permuted // DUT column table that happened to also miscopy // ref_col_idx16 the same way could not pass this phase // (a literal-by-literal mismatch would surface). The // ref_col_idx16 + bijectivity sweep below then validates // the address path's integration on top of the locked table. // // columnTable16[8][16] (PCSX2 master GSTables.cpp lines 91–109): // 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 // // pixel (xb, yb) at FBP=0, FBW=1 lands in page (0,0), block // (0,0) → swizzle 0 → block_base 0; expected addr = 2 * hw. // ----------------------------------------------------------- // yb=0 check(9'd0, 6'd1, 12'd0, 12'd0, 32'(2*0), "col-yb0-xb0"); check(9'd0, 6'd1, 12'd1, 12'd0, 32'(2*2), "col-yb0-xb1"); check(9'd0, 6'd1, 12'd2, 12'd0, 32'(2*8), "col-yb0-xb2"); check(9'd0, 6'd1, 12'd3, 12'd0, 32'(2*10), "col-yb0-xb3"); check(9'd0, 6'd1, 12'd4, 12'd0, 32'(2*16), "col-yb0-xb4"); check(9'd0, 6'd1, 12'd5, 12'd0, 32'(2*18), "col-yb0-xb5"); check(9'd0, 6'd1, 12'd6, 12'd0, 32'(2*24), "col-yb0-xb6"); check(9'd0, 6'd1, 12'd7, 12'd0, 32'(2*26), "col-yb0-xb7"); check(9'd0, 6'd1, 12'd8, 12'd0, 32'(2*1), "col-yb0-xb8"); check(9'd0, 6'd1, 12'd9, 12'd0, 32'(2*3), "col-yb0-xb9"); check(9'd0, 6'd1, 12'd10, 12'd0, 32'(2*9), "col-yb0-xb10"); check(9'd0, 6'd1, 12'd11, 12'd0, 32'(2*11), "col-yb0-xb11"); check(9'd0, 6'd1, 12'd12, 12'd0, 32'(2*17), "col-yb0-xb12"); check(9'd0, 6'd1, 12'd13, 12'd0, 32'(2*19), "col-yb0-xb13"); check(9'd0, 6'd1, 12'd14, 12'd0, 32'(2*25), "col-yb0-xb14"); check(9'd0, 6'd1, 12'd15, 12'd0, 32'(2*27), "col-yb0-xb15"); // yb=1 check(9'd0, 6'd1, 12'd0, 12'd1, 32'(2*4), "col-yb1-xb0"); check(9'd0, 6'd1, 12'd1, 12'd1, 32'(2*6), "col-yb1-xb1"); check(9'd0, 6'd1, 12'd2, 12'd1, 32'(2*12), "col-yb1-xb2"); check(9'd0, 6'd1, 12'd3, 12'd1, 32'(2*14), "col-yb1-xb3"); check(9'd0, 6'd1, 12'd4, 12'd1, 32'(2*20), "col-yb1-xb4"); check(9'd0, 6'd1, 12'd5, 12'd1, 32'(2*22), "col-yb1-xb5"); check(9'd0, 6'd1, 12'd6, 12'd1, 32'(2*28), "col-yb1-xb6"); check(9'd0, 6'd1, 12'd7, 12'd1, 32'(2*30), "col-yb1-xb7"); check(9'd0, 6'd1, 12'd8, 12'd1, 32'(2*5), "col-yb1-xb8"); check(9'd0, 6'd1, 12'd9, 12'd1, 32'(2*7), "col-yb1-xb9"); check(9'd0, 6'd1, 12'd10, 12'd1, 32'(2*13), "col-yb1-xb10"); check(9'd0, 6'd1, 12'd11, 12'd1, 32'(2*15), "col-yb1-xb11"); check(9'd0, 6'd1, 12'd12, 12'd1, 32'(2*21), "col-yb1-xb12"); check(9'd0, 6'd1, 12'd13, 12'd1, 32'(2*23), "col-yb1-xb13"); check(9'd0, 6'd1, 12'd14, 12'd1, 32'(2*29), "col-yb1-xb14"); check(9'd0, 6'd1, 12'd15, 12'd1, 32'(2*31), "col-yb1-xb15"); // yb=2 check(9'd0, 6'd1, 12'd0, 12'd2, 32'(2*32), "col-yb2-xb0"); check(9'd0, 6'd1, 12'd1, 12'd2, 32'(2*34), "col-yb2-xb1"); check(9'd0, 6'd1, 12'd2, 12'd2, 32'(2*40), "col-yb2-xb2"); check(9'd0, 6'd1, 12'd3, 12'd2, 32'(2*42), "col-yb2-xb3"); check(9'd0, 6'd1, 12'd4, 12'd2, 32'(2*48), "col-yb2-xb4"); check(9'd0, 6'd1, 12'd5, 12'd2, 32'(2*50), "col-yb2-xb5"); check(9'd0, 6'd1, 12'd6, 12'd2, 32'(2*56), "col-yb2-xb6"); check(9'd0, 6'd1, 12'd7, 12'd2, 32'(2*58), "col-yb2-xb7"); check(9'd0, 6'd1, 12'd8, 12'd2, 32'(2*33), "col-yb2-xb8"); check(9'd0, 6'd1, 12'd9, 12'd2, 32'(2*35), "col-yb2-xb9"); check(9'd0, 6'd1, 12'd10, 12'd2, 32'(2*41), "col-yb2-xb10"); check(9'd0, 6'd1, 12'd11, 12'd2, 32'(2*43), "col-yb2-xb11"); check(9'd0, 6'd1, 12'd12, 12'd2, 32'(2*49), "col-yb2-xb12"); check(9'd0, 6'd1, 12'd13, 12'd2, 32'(2*51), "col-yb2-xb13"); check(9'd0, 6'd1, 12'd14, 12'd2, 32'(2*57), "col-yb2-xb14"); check(9'd0, 6'd1, 12'd15, 12'd2, 32'(2*59), "col-yb2-xb15"); // yb=3 check(9'd0, 6'd1, 12'd0, 12'd3, 32'(2*36), "col-yb3-xb0"); check(9'd0, 6'd1, 12'd1, 12'd3, 32'(2*38), "col-yb3-xb1"); check(9'd0, 6'd1, 12'd2, 12'd3, 32'(2*44), "col-yb3-xb2"); check(9'd0, 6'd1, 12'd3, 12'd3, 32'(2*46), "col-yb3-xb3"); check(9'd0, 6'd1, 12'd4, 12'd3, 32'(2*52), "col-yb3-xb4"); check(9'd0, 6'd1, 12'd5, 12'd3, 32'(2*54), "col-yb3-xb5"); check(9'd0, 6'd1, 12'd6, 12'd3, 32'(2*60), "col-yb3-xb6"); check(9'd0, 6'd1, 12'd7, 12'd3, 32'(2*62), "col-yb3-xb7"); check(9'd0, 6'd1, 12'd8, 12'd3, 32'(2*37), "col-yb3-xb8"); check(9'd0, 6'd1, 12'd9, 12'd3, 32'(2*39), "col-yb3-xb9"); check(9'd0, 6'd1, 12'd10, 12'd3, 32'(2*45), "col-yb3-xb10"); check(9'd0, 6'd1, 12'd11, 12'd3, 32'(2*47), "col-yb3-xb11"); check(9'd0, 6'd1, 12'd12, 12'd3, 32'(2*53), "col-yb3-xb12"); check(9'd0, 6'd1, 12'd13, 12'd3, 32'(2*55), "col-yb3-xb13"); check(9'd0, 6'd1, 12'd14, 12'd3, 32'(2*61), "col-yb3-xb14"); check(9'd0, 6'd1, 12'd15, 12'd3, 32'(2*63), "col-yb3-xb15"); // yb=4 check(9'd0, 6'd1, 12'd0, 12'd4, 32'(2*64), "col-yb4-xb0"); check(9'd0, 6'd1, 12'd1, 12'd4, 32'(2*66), "col-yb4-xb1"); check(9'd0, 6'd1, 12'd2, 12'd4, 32'(2*72), "col-yb4-xb2"); check(9'd0, 6'd1, 12'd3, 12'd4, 32'(2*74), "col-yb4-xb3"); check(9'd0, 6'd1, 12'd4, 12'd4, 32'(2*80), "col-yb4-xb4"); check(9'd0, 6'd1, 12'd5, 12'd4, 32'(2*82), "col-yb4-xb5"); check(9'd0, 6'd1, 12'd6, 12'd4, 32'(2*88), "col-yb4-xb6"); check(9'd0, 6'd1, 12'd7, 12'd4, 32'(2*90), "col-yb4-xb7"); check(9'd0, 6'd1, 12'd8, 12'd4, 32'(2*65), "col-yb4-xb8"); check(9'd0, 6'd1, 12'd9, 12'd4, 32'(2*67), "col-yb4-xb9"); check(9'd0, 6'd1, 12'd10, 12'd4, 32'(2*73), "col-yb4-xb10"); check(9'd0, 6'd1, 12'd11, 12'd4, 32'(2*75), "col-yb4-xb11"); check(9'd0, 6'd1, 12'd12, 12'd4, 32'(2*81), "col-yb4-xb12"); check(9'd0, 6'd1, 12'd13, 12'd4, 32'(2*83), "col-yb4-xb13"); check(9'd0, 6'd1, 12'd14, 12'd4, 32'(2*89), "col-yb4-xb14"); check(9'd0, 6'd1, 12'd15, 12'd4, 32'(2*91), "col-yb4-xb15"); // yb=5 check(9'd0, 6'd1, 12'd0, 12'd5, 32'(2*68), "col-yb5-xb0"); check(9'd0, 6'd1, 12'd1, 12'd5, 32'(2*70), "col-yb5-xb1"); check(9'd0, 6'd1, 12'd2, 12'd5, 32'(2*76), "col-yb5-xb2"); check(9'd0, 6'd1, 12'd3, 12'd5, 32'(2*78), "col-yb5-xb3"); check(9'd0, 6'd1, 12'd4, 12'd5, 32'(2*84), "col-yb5-xb4"); check(9'd0, 6'd1, 12'd5, 12'd5, 32'(2*86), "col-yb5-xb5"); check(9'd0, 6'd1, 12'd6, 12'd5, 32'(2*92), "col-yb5-xb6"); check(9'd0, 6'd1, 12'd7, 12'd5, 32'(2*94), "col-yb5-xb7"); check(9'd0, 6'd1, 12'd8, 12'd5, 32'(2*69), "col-yb5-xb8"); check(9'd0, 6'd1, 12'd9, 12'd5, 32'(2*71), "col-yb5-xb9"); check(9'd0, 6'd1, 12'd10, 12'd5, 32'(2*77), "col-yb5-xb10"); check(9'd0, 6'd1, 12'd11, 12'd5, 32'(2*79), "col-yb5-xb11"); check(9'd0, 6'd1, 12'd12, 12'd5, 32'(2*85), "col-yb5-xb12"); check(9'd0, 6'd1, 12'd13, 12'd5, 32'(2*87), "col-yb5-xb13"); check(9'd0, 6'd1, 12'd14, 12'd5, 32'(2*93), "col-yb5-xb14"); check(9'd0, 6'd1, 12'd15, 12'd5, 32'(2*95), "col-yb5-xb15"); // yb=6 check(9'd0, 6'd1, 12'd0, 12'd6, 32'(2*96), "col-yb6-xb0"); check(9'd0, 6'd1, 12'd1, 12'd6, 32'(2*98), "col-yb6-xb1"); check(9'd0, 6'd1, 12'd2, 12'd6, 32'(2*104), "col-yb6-xb2"); check(9'd0, 6'd1, 12'd3, 12'd6, 32'(2*106), "col-yb6-xb3"); check(9'd0, 6'd1, 12'd4, 12'd6, 32'(2*112), "col-yb6-xb4"); check(9'd0, 6'd1, 12'd5, 12'd6, 32'(2*114), "col-yb6-xb5"); check(9'd0, 6'd1, 12'd6, 12'd6, 32'(2*120), "col-yb6-xb6"); check(9'd0, 6'd1, 12'd7, 12'd6, 32'(2*122), "col-yb6-xb7"); check(9'd0, 6'd1, 12'd8, 12'd6, 32'(2*97), "col-yb6-xb8"); check(9'd0, 6'd1, 12'd9, 12'd6, 32'(2*99), "col-yb6-xb9"); check(9'd0, 6'd1, 12'd10, 12'd6, 32'(2*105), "col-yb6-xb10"); check(9'd0, 6'd1, 12'd11, 12'd6, 32'(2*107), "col-yb6-xb11"); check(9'd0, 6'd1, 12'd12, 12'd6, 32'(2*113), "col-yb6-xb12"); check(9'd0, 6'd1, 12'd13, 12'd6, 32'(2*115), "col-yb6-xb13"); check(9'd0, 6'd1, 12'd14, 12'd6, 32'(2*121), "col-yb6-xb14"); check(9'd0, 6'd1, 12'd15, 12'd6, 32'(2*123), "col-yb6-xb15"); // yb=7 check(9'd0, 6'd1, 12'd0, 12'd7, 32'(2*100), "col-yb7-xb0"); check(9'd0, 6'd1, 12'd1, 12'd7, 32'(2*102), "col-yb7-xb1"); check(9'd0, 6'd1, 12'd2, 12'd7, 32'(2*108), "col-yb7-xb2"); check(9'd0, 6'd1, 12'd3, 12'd7, 32'(2*110), "col-yb7-xb3"); check(9'd0, 6'd1, 12'd4, 12'd7, 32'(2*116), "col-yb7-xb4"); check(9'd0, 6'd1, 12'd5, 12'd7, 32'(2*118), "col-yb7-xb5"); check(9'd0, 6'd1, 12'd6, 12'd7, 32'(2*124), "col-yb7-xb6"); check(9'd0, 6'd1, 12'd7, 12'd7, 32'(2*126), "col-yb7-xb7"); check(9'd0, 6'd1, 12'd8, 12'd7, 32'(2*101), "col-yb7-xb8"); check(9'd0, 6'd1, 12'd9, 12'd7, 32'(2*103), "col-yb7-xb9"); check(9'd0, 6'd1, 12'd10, 12'd7, 32'(2*109), "col-yb7-xb10"); check(9'd0, 6'd1, 12'd11, 12'd7, 32'(2*111), "col-yb7-xb11"); check(9'd0, 6'd1, 12'd12, 12'd7, 32'(2*117), "col-yb7-xb12"); check(9'd0, 6'd1, 12'd13, 12'd7, 32'(2*119), "col-yb7-xb13"); check(9'd0, 6'd1, 12'd14, 12'd7, 32'(2*125), "col-yb7-xb14"); check(9'd0, 6'd1, 12'd15, 12'd7, 32'(2*127), "col-yb7-xb15"); // ----------------------------------------------------------- // Phase 2b — within-block 16×8 walk via the in-TB // ref_col_idx16 function. Redundant with Phase 2a (which // already locked the column table to canonical PCSX2 values), // kept as a self-check that ref_col_idx16 agrees with the // DUT — the bijectivity sweep below relies on ref_col_idx16 // being correct. // ----------------------------------------------------------- for (int yb_i = 0; yb_i < 8; yb_i++) begin for (int xb_i = 0; xb_i < 16; xb_i++) begin logic [31:0] expected; expected = 32'(ref_col_idx16(yb_i, xb_i) * 2); check(9'd0, 6'd1, 12'(xb_i), 12'(yb_i), expected, "within-block-col-table"); end end // ----------------------------------------------------------- // Phase 3 — source-table lock. 32 hard-coded address checks, // one per block in page 0. Expected block index taken VERBATIM // from PCSX2 blockTable16 (NOT derived from ref_block_idx16). // For each block (block_y, block_x), we use the pixel // (16*block_x, 8*block_y) which lands at xb=yb=0 → hw=0 → // byte 0 within the block; total addr = block_idx * 256. // // blockTable16[8][4] (PCSX2 master GSTables.cpp lines 29–39): // by=0: 0 2 8 10 // by=1: 1 3 9 11 // by=2: 4 6 12 14 // by=3: 5 7 13 15 // by=4: 16 18 24 26 // by=5: 17 19 25 27 // by=6: 20 22 28 30 // by=7: 21 23 29 31 // ----------------------------------------------------------- check(9'd0, 6'd1, 12'(0*16), 12'(0*8), 32'(0*256), "src-lock-by0bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(0*8), 32'(2*256), "src-lock-by0bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(0*8), 32'(8*256), "src-lock-by0bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(0*8), 32'(10*256), "src-lock-by0bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(1*8), 32'(1*256), "src-lock-by1bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(1*8), 32'(3*256), "src-lock-by1bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(1*8), 32'(9*256), "src-lock-by1bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(1*8), 32'(11*256), "src-lock-by1bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(2*8), 32'(4*256), "src-lock-by2bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(2*8), 32'(6*256), "src-lock-by2bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(2*8), 32'(12*256), "src-lock-by2bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(2*8), 32'(14*256), "src-lock-by2bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(3*8), 32'(5*256), "src-lock-by3bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(3*8), 32'(7*256), "src-lock-by3bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(3*8), 32'(13*256), "src-lock-by3bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(3*8), 32'(15*256), "src-lock-by3bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(4*8), 32'(16*256), "src-lock-by4bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(4*8), 32'(18*256), "src-lock-by4bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(4*8), 32'(24*256), "src-lock-by4bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(4*8), 32'(26*256), "src-lock-by4bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(5*8), 32'(17*256), "src-lock-by5bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(5*8), 32'(19*256), "src-lock-by5bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(5*8), 32'(25*256), "src-lock-by5bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(5*8), 32'(27*256), "src-lock-by5bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(6*8), 32'(20*256), "src-lock-by6bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(6*8), 32'(22*256), "src-lock-by6bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(6*8), 32'(28*256), "src-lock-by6bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(6*8), 32'(30*256), "src-lock-by6bx3"); check(9'd0, 6'd1, 12'(0*16), 12'(7*8), 32'(21*256), "src-lock-by7bx0"); check(9'd0, 6'd1, 12'(1*16), 12'(7*8), 32'(23*256), "src-lock-by7bx1"); check(9'd0, 6'd1, 12'(2*16), 12'(7*8), 32'(29*256), "src-lock-by7bx2"); check(9'd0, 6'd1, 12'(3*16), 12'(7*8), 32'(31*256), "src-lock-by7bx3"); // ----------------------------------------------------------- // Phase 4 — block-swizzle walk. Cross-checks the in-TB // ref_block_idx16 against the DUT (the bijectivity sweep // below relies on ref_block_idx16 being correct). // ----------------------------------------------------------- for (int by_i = 0; by_i < 8; by_i++) begin for (int bx_i = 0; bx_i < 4; bx_i++) begin logic [31:0] expected; expected = 32'(ref_block_idx16(by_i, bx_i) * 256); check(9'd0, 6'd1, 12'(bx_i*16), 12'(by_i*8), expected, "block-swizzle-walk"); end end // ----------------------------------------------------------- // Phase 5 — bijectivity sweep over the 64×64 page. 4096 // halfword slots; every pixel must hit a unique halfword // address in [0, 8192), and the address must agree with // ref_addr16. Catches any swap in either table. // ----------------------------------------------------------- begin : sweep logic seen [0:4095]; for (int i = 0; i < 4096; i++) seen[i] = 1'b0; for (int yy = 0; yy < 64; yy++) begin for (int xx = 0; xx < 64; xx++) begin logic [31:0] got, ref_; int hw_slot; compute(9'd0, 6'd1, 12'(xx), 12'(yy), got); ref_ = ref_addr16(0, 1, xx, yy); if (got !== ref_) begin $error("sweep: (%0d,%0d) DUT=0x%08x ref=0x%08x", xx, yy, got, ref_); errors = errors + 1; end if (got >= 32'd8192) begin $error("sweep: (%0d,%0d) addr=0x%08x out of page bounds", xx, yy, got); errors = errors + 1; end if (got[0]) begin $error("sweep: (%0d,%0d) addr=0x%08x not halfword-aligned", xx, yy, got); errors = errors + 1; end hw_slot = got[12:1]; if (seen[hw_slot]) begin $error("sweep: (%0d,%0d) addr=0x%08x duplicate (hw_slot=%0d)", xx, yy, got, hw_slot); errors = errors + 1; end seen[hw_slot] = 1'b1; end end for (int i = 0; i < 4096; i++) begin if (!seen[i]) begin $error("sweep: hw_slot %0d (= byte 0x%08x) never reached", i, i*2); errors = errors + 1; end end end // ----------------------------------------------------------- // Multi-page sanity at FBW=2: pixel (96, 16) → page (1, 0). // page_idx = 0*2 + 1 = 1, page_base = 8192. // bx = (96 % 64) / 16 = 32/16 = 2. // by = (16 % 64) / 8 = 16/8 = 2. // block (by=2, bx=2) → blockTable16[2][2] = 12 → // block_base = 8192 + 12*256 = 11264. // xb=96%16=0, yb=16%8=0 → hw=0 → addr = 11264. // ----------------------------------------------------------- check(9'd0, 6'd2, 12'd96, 12'd16, 32'd11264, "fbw2-multi-page"); // ----------------------------------------------------------- // Non-page-aligned FBP coverage (real PS2 supports any // 2048-byte-aligned FBP). FBP=1 → base 2048; FBP=2 → 4096; // FBP=3 → 6144. (0,0) → addr = FBP*2048. // ----------------------------------------------------------- check(9'd1, 6'd1, 12'd0, 12'd0, 32'd2048, "fbp1-mid-page-origin"); check(9'd2, 6'd1, 12'd0, 12'd0, 32'd4096, "fbp2-mid-page-origin"); check(9'd3, 6'd1, 12'd0, 12'd0, 32'd6144, "fbp3-mid-page-origin"); // FBP=3 + FBW=2 + intra-block + page-(1,1) crossing: // (65, 65) → page (1, 1), page_idx = 1*2+1 = 3, // page_base = 6144 + 3*8192 = 30720. block (1/16=0, // 1/8=0) inside this page → block (0,0) → swizzle 0, // block_base = 30720. xb=1 yb=1 → hw = ref_col_idx16(1,1) // = 6 → byte 12 → addr 30732. check(9'd3, 6'd2, 12'd65, 12'd65, 32'd30732, "fbp3-fbw2-page1-1-intra"); $display("[tb_gs_swizzle_psmct16] errors=%0d", errors); if (errors == 0) $display("[tb_gs_swizzle_psmct16] PASS"); else $display("[tb_gs_swizzle_psmct16] FAIL"); $finish; end initial begin #500000; $error("[tb_gs_swizzle_psmct16] timeout"); $finish; end endmodule : tb_gs_swizzle_psmct16