ec82764bef
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>
609 lines
33 KiB
Systemverilog
609 lines
33 KiB
Systemverilog
// 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
|