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>
390 lines
15 KiB
Systemverilog
390 lines
15 KiB
Systemverilog
// retroDE_ps2 — tb_gs_texture_wrap (Ch294)
|
|
//
|
|
// Focused TB for GS texture WRAP MODES (REPEAT + CLAMP) at the sampler level.
|
|
// gs_texture_unit is instantiated with TEX_WRAP_ENABLE=1; out-of-range (u,v)
|
|
// are resolved against the texture's power-of-two dims (width=2^TW, height=
|
|
// 2^TH from TEX0) using the CLAMP_1 wrap mode (WMS for u/S, WMT for v/T):
|
|
// REPEAT (mode 0): u_eff = u & (width-1)
|
|
// CLAMP (mode 1): u_eff = (u >= width) ? width-1 : u
|
|
// The wrap is applied BEFORE address generation, so it covers the LINEAR path
|
|
// (gs_texel_addr) AND the SWIZZLE paths. u/v are unsigned -> no negative /
|
|
// underflow case exists (stated, not tested): u>=width REPEAT wraps, u>=width
|
|
// CLAMP sticks; same for v.
|
|
//
|
|
// PROOFS (TEX_WRAP_ENABLE=1):
|
|
// PSMCT32 (psm 0x00) 4x4 (TW=TH=2): width=height=4, mask=3, limit=3.
|
|
// - REPEAT u=5 -> samples texel u=1 (5&3); v=6 -> v=2.
|
|
// - CLAMP u=5 -> samples texel u=3 (width-1); v=6 -> v=3.
|
|
// - in-range u=2,v=1 wraps/clamps to itself (identity, both modes).
|
|
// Each verifies the SAMPLED COLOR equals the wrapped/clamped texel's word.
|
|
// PSMT8 (psm 0x13) 4x4 linear: same OOB REPEAT vs CLAMP, verifies the
|
|
// CLUT-resolved color matches the wrapped/clamped INDEX (and clut_rd_idx
|
|
// is the wrapped/clamped texel's index — the address is what wrap changes).
|
|
// PSMT4_SWIZZLE (psm 0x14): proves wrap happens BEFORE swizzle — the
|
|
// swizzled tex_rd_addr for an OOB coord equals the swizzled addr of the
|
|
// in-range wrapped/clamped coord, and differs from the OOB coord's own
|
|
// (unwrapped) swizzle. (Address is the thing wrap changes; no CLUT needed
|
|
// for this proof, though the same CLUT model feeds it.)
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_texture_wrap;
|
|
|
|
localparam logic [5:0] PSMCT32 = 6'h00;
|
|
localparam logic [5:0] PSMT8 = 6'h13;
|
|
localparam logic [5:0] PSMT4 = 6'h14;
|
|
|
|
// Wrap modes
|
|
localparam logic [1:0] WM_REPEAT = 2'd0;
|
|
localparam logic [1:0] WM_CLAMP = 2'd1;
|
|
|
|
logic clk;
|
|
logic rst_n;
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
// ---- tiny combinational VRAM model (word-addressed little-endian) ----
|
|
localparam int VRAM_BYTES = 16384;
|
|
logic [7:0] vram [0:VRAM_BYTES-1];
|
|
|
|
logic [31:0] tex_rd_addr;
|
|
logic [31:0] tex_rd_data;
|
|
assign tex_rd_data = { vram[(tex_rd_addr & ~32'd3) + 3],
|
|
vram[(tex_rd_addr & ~32'd3) + 2],
|
|
vram[(tex_rd_addr & ~32'd3) + 1],
|
|
vram[(tex_rd_addr & ~32'd3) + 0] };
|
|
|
|
task automatic poke_byte(input logic [31:0] a, input logic [7:0] d);
|
|
vram[a] = d;
|
|
endtask
|
|
task automatic poke_word(input logic [31:0] a, input logic [31:0] d);
|
|
vram[a+0] = d[7:0]; vram[a+1] = d[15:8];
|
|
vram[a+2] = d[23:16]; vram[a+3] = d[31:24];
|
|
endtask
|
|
|
|
// ---- DUT (linear path: PSMCT32 + PSMT8) with wrap enabled ----
|
|
logic in_valid;
|
|
logic [10:0] u, v;
|
|
logic [1:0] wms, wmt;
|
|
logic [3:0] tw, th;
|
|
logic [31:0] tbp0_base_bytes;
|
|
logic [13:0] tbw;
|
|
logic [5:0] psm;
|
|
|
|
logic tex_rd_en;
|
|
logic [7:0] clut_rd_idx;
|
|
logic [31:0] clut_rd_data;
|
|
logic out_valid;
|
|
logic [31:0] tex_color;
|
|
|
|
gs_texture_unit #(.TEX_WRAP_ENABLE(1'b1), .RD_LATENCY(1)) dut (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(in_valid),
|
|
.u(u), .v(v),
|
|
.wms(wms), .wmt(wmt), .tw(tw), .th(th),
|
|
.tbp0_base_bytes(tbp0_base_bytes),
|
|
.tbw(tbw),
|
|
.psm(psm),
|
|
.tex_rd_en(tex_rd_en),
|
|
.tex_rd_addr(tex_rd_addr),
|
|
.tex_rd_data(tex_rd_data),
|
|
.clut_rd_idx(clut_rd_idx),
|
|
.clut_rd_data(clut_rd_data),
|
|
.out_valid(out_valid),
|
|
.tex_color(tex_color)
|
|
);
|
|
|
|
// ---- CLUT (reused on-chip palette) for the PSMT8 proof ----
|
|
logic clut_we;
|
|
logic [7:0] clut_widx;
|
|
logic [31:0] clut_wdata;
|
|
clut_stub u_clut (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.write_en (clut_we),
|
|
.write_idx(clut_widx),
|
|
.write_data(clut_wdata),
|
|
.read_idx (8'd0),
|
|
.read_data(),
|
|
.tex_read_idx (clut_rd_idx),
|
|
.tex_read_data(clut_rd_data)
|
|
);
|
|
task automatic clut_write(input logic [7:0] idx, input logic [31:0] data);
|
|
@(negedge clk);
|
|
clut_we = 1'b1; clut_widx = idx; clut_wdata = data;
|
|
@(negedge clk);
|
|
clut_we = 1'b0;
|
|
endtask
|
|
|
|
// ---- texture geometry: 4x4 (TW=TH=2), TBW=1 ----
|
|
localparam int TEXW = 4;
|
|
localparam int TEXH = 4;
|
|
localparam [3:0] TWv = 4'd2; // log2(4)
|
|
localparam [3:0] THv = 4'd2;
|
|
localparam [13:0] TBWv = 14'd1; // row stride = TBW*64 texels
|
|
localparam [31:0] CT32_BASE = 32'h0000_2000; // PSMCT32 4 B/texel
|
|
localparam [31:0] T8_BASE = 32'h0000_0800; // PSMT8 1 B/texel
|
|
|
|
// distinct PSMCT32 texel word per (x,y)
|
|
function automatic logic [31:0] ct32_word(input int x, input int y);
|
|
return 32'hFF000000 | (x << 4) | (y << 12);
|
|
endfunction
|
|
// distinct PSMT8 index per (x,y): 0..15 over the 4x4
|
|
function automatic logic [7:0] t8_idx(input int x, input int y);
|
|
return 8'(y*TEXW + x);
|
|
endfunction
|
|
function automatic logic [31:0] pal(input logic [7:0] i);
|
|
return 32'hFF000000 | (i << 8) | i; // recognizable per-index color
|
|
endfunction
|
|
|
|
// ---- drive one sample, let the combinational sampler chain settle ----
|
|
task automatic sample(input int su, input int sv, output logic [31:0] col);
|
|
@(negedge clk);
|
|
in_valid = 1'b1;
|
|
u = 11'(su);
|
|
v = 11'(sv);
|
|
#2;
|
|
col = tex_color;
|
|
endtask
|
|
|
|
int errors;
|
|
initial errors = 0;
|
|
|
|
logic [31:0] got, exp;
|
|
logic [31:0] addr_inrange, addr_oob, addr_unwrapped;
|
|
int x, y;
|
|
|
|
initial begin
|
|
rst_n = 1'b0; in_valid = 1'b0;
|
|
u = 0; v = 0; wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
tw = TWv; th = THv;
|
|
tbp0_base_bytes = CT32_BASE; tbw = TBWv; psm = PSMCT32;
|
|
clut_we = 1'b0; clut_widx = 0; clut_wdata = 0;
|
|
for (int i = 0; i < VRAM_BYTES; i++) vram[i] = 8'd0;
|
|
|
|
// upload PSMCT32 4x4 (row stride TBW*64 texels * 4 bytes)
|
|
for (y = 0; y < TEXH; y++)
|
|
for (x = 0; x < TEXW; x++)
|
|
poke_word(CT32_BASE + ((y * (TBWv*64)) + x) * 4, ct32_word(x, y));
|
|
// upload PSMT8 4x4 indices (row stride TBW*64 bytes)
|
|
for (y = 0; y < TEXH; y++)
|
|
for (x = 0; x < TEXW; x++)
|
|
poke_byte(T8_BASE + (y * (TBWv*64)) + x, t8_idx(x, y));
|
|
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (2) @(posedge clk);
|
|
|
|
// ============================================================
|
|
// PSMCT32 wrap proofs (direct color; sampled word == texel word)
|
|
// ============================================================
|
|
psm = PSMCT32; tbp0_base_bytes = CT32_BASE;
|
|
|
|
// REPEAT: u=5 -> u_eff=1 ; v=6 -> v_eff=2
|
|
wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
sample(5, 6, got);
|
|
exp = ct32_word(1, 2);
|
|
if (got !== exp) begin
|
|
$error("[ct32 REPEAT] u=5 v=6 got=%08x exp=%08x (expect texel 1,2)", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// REPEAT identity: in-range u=2 v=1 -> itself
|
|
sample(2, 1, got);
|
|
exp = ct32_word(2, 1);
|
|
if (got !== exp) begin
|
|
$error("[ct32 REPEAT id] u=2 v=1 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// CLAMP: u=5 -> u_eff=3 (width-1) ; v=6 -> v_eff=3 (height-1)
|
|
wms = WM_CLAMP; wmt = WM_CLAMP;
|
|
sample(5, 6, got);
|
|
exp = ct32_word(3, 3);
|
|
if (got !== exp) begin
|
|
$error("[ct32 CLAMP] u=5 v=6 got=%08x exp=%08x (expect texel 3,3)", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// CLAMP only-u OOB: u=9 -> 3, v=1 stays 1
|
|
sample(9, 1, got);
|
|
exp = ct32_word(3, 1);
|
|
if (got !== exp) begin
|
|
$error("[ct32 CLAMP u-only] u=9 v=1 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// mixed: REPEAT u, CLAMP v: u=5->1, v=7->3
|
|
wms = WM_REPEAT; wmt = WM_CLAMP;
|
|
sample(5, 7, got);
|
|
exp = ct32_word(1, 3);
|
|
if (got !== exp) begin
|
|
$error("[ct32 mix R-u C-v] u=5 v=7 got=%08x exp=%08x (expect 1,3)", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// CLAMP identity in-range
|
|
wms = WM_CLAMP; wmt = WM_CLAMP;
|
|
sample(0, 0, got);
|
|
exp = ct32_word(0, 0);
|
|
if (got !== exp) begin
|
|
$error("[ct32 CLAMP id] u=0 v=0 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// ============================================================
|
|
// PSMT8 wrap proofs (CLUT-resolved color matches wrapped index)
|
|
// ============================================================
|
|
psm = PSMT8; tbp0_base_bytes = T8_BASE;
|
|
for (int i = 0; i < 16; i++) clut_write(8'(i), pal(8'(i)));
|
|
|
|
// REPEAT: u=5 v=6 -> index of texel (1,2)
|
|
wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
sample(5, 6, got);
|
|
if (clut_rd_idx !== t8_idx(1, 2)) begin
|
|
$error("[t8 REPEAT idx] u=5 v=6 idx=0x%02x exp=0x%02x", clut_rd_idx, t8_idx(1,2));
|
|
errors++;
|
|
end
|
|
exp = pal(t8_idx(1, 2));
|
|
if (got !== exp) begin
|
|
$error("[t8 REPEAT col] u=5 v=6 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// CLAMP: u=5 v=6 -> index of texel (3,3)
|
|
wms = WM_CLAMP; wmt = WM_CLAMP;
|
|
sample(5, 6, got);
|
|
if (clut_rd_idx !== t8_idx(3, 3)) begin
|
|
$error("[t8 CLAMP idx] u=5 v=6 idx=0x%02x exp=0x%02x", clut_rd_idx, t8_idx(3,3));
|
|
errors++;
|
|
end
|
|
exp = pal(t8_idx(3, 3));
|
|
if (got !== exp) begin
|
|
$error("[t8 CLAMP col] u=5 v=6 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end
|
|
|
|
// PSMT8 identity in-range, REPEAT
|
|
wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
sample(2, 1, got);
|
|
if (clut_rd_idx !== t8_idx(2, 1)) begin
|
|
$error("[t8 REPEAT id idx] u=2 v=1 idx=0x%02x exp=0x%02x", clut_rd_idx, t8_idx(2,1));
|
|
errors++;
|
|
end
|
|
|
|
$display("[tb_gs_texture_wrap] linear (PSMCT32+PSMT8) wrap proofs done, errors=%0d", errors);
|
|
run_swizzle_proof();
|
|
|
|
$display("[tb_gs_texture_wrap] errors=%0d", errors);
|
|
if (errors == 0)
|
|
$display("[tb_gs_texture_wrap] PASS");
|
|
else
|
|
$display("[tb_gs_texture_wrap] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
// ============================================================
|
|
// SWIZZLE proof — wrap happens BEFORE swizzle.
|
|
// A separate DUT with PSMT4_SWIZZLE=1; for psm==PSMT4 the address is
|
|
// generated by gs_swizzle_psmt4_stub from u_eff/v_eff. We prove:
|
|
// tex_rd_addr(OOB coord, wrap on) == tex_rd_addr(in-range wrapped coord)
|
|
// tex_rd_addr(OOB coord, wrap on) != tex_rd_addr(OOB coord, wrap off)
|
|
// i.e. the swizzle consumes the WRAPPED coord, not the raw OOB coord.
|
|
// Address is the thing wrap changes; no CLUT lookup needed for this proof.
|
|
// ============================================================
|
|
// wrap-on swizzle DUT
|
|
logic sw_in_valid;
|
|
logic [10:0] sw_u, sw_v;
|
|
logic [1:0] sw_wms, sw_wmt;
|
|
logic [3:0] sw_tw, sw_th;
|
|
logic [31:0] sw_addr;
|
|
logic sw_rd_en;
|
|
logic [7:0] sw_idx;
|
|
gs_texture_unit #(.TEX_WRAP_ENABLE(1'b1), .PSMT4_SWIZZLE(1'b1), .RD_LATENCY(1)) dut_sw_on (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(sw_in_valid),
|
|
.u(sw_u), .v(sw_v),
|
|
.wms(sw_wms), .wmt(sw_wmt), .tw(TWv), .th(THv),
|
|
.tbp0_base_bytes(CT32_BASE), .tbw(TBWv), .psm(PSMT4),
|
|
.tex_rd_en(sw_rd_en),
|
|
.tex_rd_addr(sw_addr),
|
|
.tex_rd_data(32'd0),
|
|
.clut_rd_idx(sw_idx),
|
|
.clut_rd_data(32'd0),
|
|
.out_valid(), .tex_color()
|
|
);
|
|
// wrap-off swizzle DUT (same geometry, TEX_WRAP_ENABLE=0)
|
|
logic [10:0] swoff_u, swoff_v;
|
|
logic [31:0] swoff_addr;
|
|
gs_texture_unit #(.TEX_WRAP_ENABLE(1'b0), .PSMT4_SWIZZLE(1'b1), .RD_LATENCY(1)) dut_sw_off (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(1'b1),
|
|
.u(swoff_u), .v(swoff_v),
|
|
.wms(2'd0), .wmt(2'd0), .tw(TWv), .th(THv),
|
|
.tbp0_base_bytes(CT32_BASE), .tbw(TBWv), .psm(PSMT4),
|
|
.tex_rd_en(),
|
|
.tex_rd_addr(swoff_addr),
|
|
.tex_rd_data(32'd0),
|
|
.clut_rd_idx(),
|
|
.clut_rd_data(32'd0),
|
|
.out_valid(), .tex_color()
|
|
);
|
|
|
|
task automatic run_swizzle_proof;
|
|
// --- REPEAT u=5 v=6 -> wrapped (1,2) ---
|
|
sw_in_valid = 1'b1;
|
|
sw_wms = WM_REPEAT; sw_wmt = WM_REPEAT;
|
|
// OOB coord through wrap-on path
|
|
sw_u = 11'd5; sw_v = 11'd6; #2; addr_oob = sw_addr;
|
|
// in-range wrapped coord (1,2) through SAME wrap-on path (wraps to self)
|
|
sw_u = 11'd1; sw_v = 11'd2; #2; addr_inrange = sw_addr;
|
|
// OOB coord through wrap-OFF path (raw swizzle of 5,6)
|
|
swoff_u = 11'd5; swoff_v = 11'd6; #2; addr_unwrapped = swoff_addr;
|
|
|
|
if (addr_oob !== addr_inrange) begin
|
|
$error("[swz REPEAT] wrapped OOB addr=%08x != in-range(1,2) addr=%08x (wrap not before swizzle)",
|
|
addr_oob, addr_inrange);
|
|
errors++;
|
|
end
|
|
if (addr_oob === addr_unwrapped) begin
|
|
$error("[swz REPEAT] wrap-on OOB addr=%08x == raw OOB addr=%08x (wrap had no effect)",
|
|
addr_oob, addr_unwrapped);
|
|
errors++;
|
|
end
|
|
|
|
// --- CLAMP u=5 v=6 -> wrapped (3,3) ---
|
|
sw_wms = WM_CLAMP; sw_wmt = WM_CLAMP;
|
|
sw_u = 11'd5; sw_v = 11'd6; #2; addr_oob = sw_addr;
|
|
sw_u = 11'd3; sw_v = 11'd3; #2; addr_inrange = sw_addr;
|
|
swoff_u = 11'd5; swoff_v = 11'd6; #2; addr_unwrapped = swoff_addr;
|
|
|
|
if (addr_oob !== addr_inrange) begin
|
|
$error("[swz CLAMP] clamped OOB addr=%08x != in-range(3,3) addr=%08x",
|
|
addr_oob, addr_inrange);
|
|
errors++;
|
|
end
|
|
if (addr_oob === addr_unwrapped) begin
|
|
$error("[swz CLAMP] wrap-on OOB addr=%08x == raw OOB addr=%08x (clamp had no effect)",
|
|
addr_oob, addr_unwrapped);
|
|
errors++;
|
|
end
|
|
|
|
// --- in-range identity must NOT differ from raw ---
|
|
sw_wms = WM_REPEAT; sw_wmt = WM_REPEAT;
|
|
sw_u = 11'd2; sw_v = 11'd1; #2; addr_oob = sw_addr;
|
|
swoff_u = 11'd2; swoff_v = 11'd1; #2; addr_unwrapped = swoff_addr;
|
|
if (addr_oob !== addr_unwrapped) begin
|
|
$error("[swz identity] in-range (2,1) wrap-on addr=%08x != wrap-off addr=%08x",
|
|
addr_oob, addr_unwrapped);
|
|
errors++;
|
|
end
|
|
$display("[tb_gs_texture_wrap] swizzle (PSMT4) wrap-before-swizzle proofs done, errors=%0d", errors);
|
|
endtask
|
|
|
|
initial begin
|
|
#2000000;
|
|
$error("[tb_gs_texture_wrap] TIMEOUT");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_gs_texture_wrap
|