Files
retroDE_ps2/sim/tb/gif_gs/tb_gs_swizzle_psmct16.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

609 lines
33 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 — 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 2939, 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 91109, 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 91109):
// 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 2939):
// 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