// 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