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>
539 lines
25 KiB
Systemverilog
539 lines
25 KiB
Systemverilog
// retroDE_ps2 — tb_gs_texture_bilinear (Ch308)
|
|
//
|
|
// Focused TB for BILINEAR (4-tap) PSMCT32 texture filtering at the sampler
|
|
// level. gs_texture_unit is instantiated with BILINEAR_ENABLE=1 +
|
|
// TEX_WRAP_ENABLE=1; for a fractional texel coord it fetches the 4 surrounding
|
|
// texels (u,v)(u+1,v)(u,v+1)(u+1,v+1), each independently wrapped/clamped, and
|
|
// blends them per channel by the 4-bit fractional u_frac/v_frac using the
|
|
// >>>4 fixed-point lerp:
|
|
// lerp(a,b,f) = a + (($signed(b)-$signed(a)) * $signed(f)) >>> 4 , f in 0..15
|
|
// The expected values below mirror that lerp EXACTLY (same >>4, same rounding),
|
|
// so every check is an exact == match, not a tolerance.
|
|
//
|
|
// 4x4 PSMCT32 texture (TW=TH=2 => width=height=4), TBW=1, distinct ABGR per
|
|
// texel so 4-tap blends are individually checkable.
|
|
//
|
|
// PROOF CASES:
|
|
// 1. EXACT CENTER (uf=0,vf=0) : result == texel(u,v) (== nearest).
|
|
// 2. HALFWAY (uf=8,vf=8) : result == 4-neighbor blend (lerp).
|
|
// 3. HALFWAY-U (uf=8,vf=0) : result == lerp(texel(u,v),texel(u+1,v)).
|
|
// 4. CLAMP edge (wms=wmt=1,u=W-1) : (u+1) clamps to W-1 => result ==
|
|
// texel(W-1,v); issued addrs <= last col.
|
|
// 5. REPEAT edge (wms=wmt=0,u=W-1) : (u+1) wraps to 0 => result ==
|
|
// lerp(texel(W-1,v),texel(0,v),uf).
|
|
// 6. NEAREST UNCHANGED: a 2nd instance with BILINEAR_ENABLE=0 returns the
|
|
// single nearest texel and ignores u_frac/v_frac.
|
|
//
|
|
// Prints [tb_gs_texture_bilinear] PASS / FAIL + $finish.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_texture_bilinear;
|
|
|
|
localparam logic [5:0] PSMCT32 = 6'h00;
|
|
|
|
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_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
|
|
|
|
// Ch314 — PSMT8: one index byte per texel at the linear byte address.
|
|
task automatic poke_t8(input int x, input int y, input logic [7:0] idx);
|
|
vram[T8_BASE + (y * (TBWv*64)) + x] = idx; // 1 byte/texel
|
|
endtask
|
|
// Ch314 — PSMT4: pack 2 indices per byte; even texel-offset -> LOW nibble,
|
|
// odd -> HIGH nibble (matches gs_texel_addr's texel_offset[0]==nibble_hi).
|
|
task automatic poke_t4(input int x, input int y, input logic [3:0] idx);
|
|
int off; int ba;
|
|
begin
|
|
off = (y * (TBWv*64)) + x;
|
|
ba = T4_BASE + (off >> 1);
|
|
if (off[0]) vram[ba] = {idx, vram[ba][3:0]}; // high nibble
|
|
else vram[ba] = {vram[ba][7:4], idx}; // low nibble
|
|
end
|
|
endtask
|
|
|
|
// ---- bilinear DUT ----
|
|
logic in_valid;
|
|
logic [10:0] u, v;
|
|
logic [3:0] u_frac, v_frac;
|
|
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;
|
|
logic busy;
|
|
|
|
// Ch314 — CLUT model (256 PSMCT32 entries) shared by both DUTs. The sampler
|
|
// drives clut_rd_idx combinationally from the extracted index; we return the
|
|
// palette color, exactly like clut_stub's 2nd (combinational) read port.
|
|
logic [31:0] clut [0:255];
|
|
assign clut_rd_data = clut[clut_rd_idx];
|
|
|
|
gs_texture_unit #(.BILINEAR_ENABLE(1'b1), .PALETTE_BILINEAR(1'b1),
|
|
.TEX_WRAP_ENABLE(1'b1), .RD_LATENCY(1)) dut (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(in_valid),
|
|
.u(u), .v(v),
|
|
.u_frac(u_frac), .v_frac(v_frac),
|
|
.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),
|
|
.busy(busy)
|
|
);
|
|
|
|
// ---- nearest reference DUT (BILINEAR_ENABLE=0) ----
|
|
logic n_in_valid;
|
|
logic [10:0] n_u, n_v;
|
|
logic [3:0] n_uf, n_vf;
|
|
logic [31:0] n_tex_rd_addr;
|
|
logic [31:0] n_tex_rd_data;
|
|
logic n_out_valid;
|
|
logic [31:0] n_tex_color;
|
|
logic n_busy;
|
|
logic [7:0] n_clut_rd_idx; // Ch314 — nearest DUT's CLUT index (for PSMT8 nearest x-check)
|
|
assign n_tex_rd_data = { vram[(n_tex_rd_addr & ~32'd3) + 3],
|
|
vram[(n_tex_rd_addr & ~32'd3) + 2],
|
|
vram[(n_tex_rd_addr & ~32'd3) + 1],
|
|
vram[(n_tex_rd_addr & ~32'd3) + 0] };
|
|
gs_texture_unit #(.BILINEAR_ENABLE(1'b0), .TEX_WRAP_ENABLE(1'b1), .RD_LATENCY(1)) dut_near (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(n_in_valid),
|
|
.u(n_u), .v(n_v),
|
|
.u_frac(n_uf), .v_frac(n_vf),
|
|
.wms(WM_REPEAT), .wmt(WM_REPEAT), .tw(tw), .th(th),
|
|
.tbp0_base_bytes(tbp0_base_bytes),
|
|
.tbw(tbw),
|
|
.psm(psm),
|
|
.tex_rd_en(),
|
|
.tex_rd_addr(n_tex_rd_addr),
|
|
.tex_rd_data(n_tex_rd_data),
|
|
.clut_rd_idx(n_clut_rd_idx),
|
|
.clut_rd_data(clut[n_clut_rd_idx]),
|
|
.out_valid(n_out_valid),
|
|
.tex_color(n_tex_color),
|
|
.busy(n_busy)
|
|
);
|
|
|
|
// ---- 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;
|
|
// Ch314 — indexed textures (same 4x4 geometry, TBW=1).
|
|
localparam logic [5:0] PSMT8v = 6'h13;
|
|
localparam logic [5:0] PSMT4v = 6'h14;
|
|
localparam [31:0] T8_BASE = 32'h0000_1000; // PSMT8: 1 byte/texel, row stride TBW*64=64 B
|
|
localparam [31:0] T4_BASE = 32'h0000_1800; // PSMT4: 1 nibble/texel, row stride TBW*64/2=32 B
|
|
// Palette: distinct, well-separated colors so a COLOR interpolation is
|
|
// visibly different from CLUT-of-an-interpolated-index. idx 0=red 1=blue
|
|
// 2=green 3=white. The column-striped index layout idx(x,y)=x gives
|
|
// y-independent taps so vf is irrelevant and the blend is a clean U-lerp.
|
|
localparam [31:0] CLUT0 = 32'hFF_00_00_FF; // red (A,B,G,R)
|
|
localparam [31:0] CLUT1 = 32'hFF_FF_00_00; // blue
|
|
localparam [31:0] CLUT2 = 32'hFF_00_FF_00; // green
|
|
localparam [31:0] CLUT3 = 32'hFF_FF_FF_FF; // white
|
|
|
|
// distinct PSMCT32 ABGR word per (x,y): pack varied per-channel values so
|
|
// each tap differs in R,G,B AND A (so blends are individually checkable).
|
|
function automatic logic [31:0] ct32_word(input int x, input int y);
|
|
logic [7:0] r, g, b, a;
|
|
begin
|
|
r = 8'(16 + (y*TEXW + x) * 12); // 16,28,40,... distinct
|
|
g = 8'(8 + (x) * 30); // varies with x
|
|
b = 8'(4 + (y) * 40); // varies with y
|
|
a = 8'(255 - (y*TEXW + x) * 8); // varies, stays high
|
|
ct32_word = {a, b, g, r};
|
|
end
|
|
endfunction
|
|
|
|
// reference lerp — mirrors the RTL EXACTLY (same >>>4, same clamp).
|
|
function automatic logic [7:0] ref_lerp8(input logic [7:0] a,
|
|
input logic [7:0] b,
|
|
input logic [3:0] f);
|
|
logic signed [16:0] diff;
|
|
logic signed [21:0] prod;
|
|
logic signed [21:0] shifted;
|
|
logic signed [21:0] res;
|
|
begin
|
|
diff = $signed({1'b0, b}) - $signed({1'b0, a});
|
|
prod = diff * $signed({1'b0, f});
|
|
shifted = prod >>> 4;
|
|
res = $signed({14'd0, a}) + shifted;
|
|
if (res < 0) ref_lerp8 = 8'd0;
|
|
else if (res > 22'sd255) ref_lerp8 = 8'd255;
|
|
else ref_lerp8 = res[7:0];
|
|
end
|
|
endfunction
|
|
|
|
// expected bilinear blend of four ABGR words by (uf,vf)
|
|
function automatic logic [31:0] ref_blend(input logic [31:0] w00,
|
|
input logic [31:0] w10,
|
|
input logic [31:0] w01,
|
|
input logic [31:0] w11,
|
|
input logic [3:0] uf,
|
|
input logic [3:0] vf);
|
|
logic [7:0] tr, tg, tb, ta; // top
|
|
logic [7:0] br, bg, bb, ba; // bottom
|
|
logic [7:0] cr, cg, cb, ca; // out
|
|
begin
|
|
tr = ref_lerp8(w00[ 7: 0], w10[ 7: 0], uf);
|
|
tg = ref_lerp8(w00[15: 8], w10[15: 8], uf);
|
|
tb = ref_lerp8(w00[23:16], w10[23:16], uf);
|
|
ta = ref_lerp8(w00[31:24], w10[31:24], uf);
|
|
br = ref_lerp8(w01[ 7: 0], w11[ 7: 0], uf);
|
|
bg = ref_lerp8(w01[15: 8], w11[15: 8], uf);
|
|
bb = ref_lerp8(w01[23:16], w11[23:16], uf);
|
|
ba = ref_lerp8(w01[31:24], w11[31:24], uf);
|
|
cr = ref_lerp8(tr, br, vf);
|
|
cg = ref_lerp8(tg, bg, vf);
|
|
cb = ref_lerp8(tb, bb, vf);
|
|
ca = ref_lerp8(ta, ba, vf);
|
|
ref_blend = {ca, cb, cg, cr};
|
|
end
|
|
endfunction
|
|
|
|
int errors;
|
|
initial errors = 0;
|
|
|
|
// track the max issued texel column/row during a bilinear sample (for the
|
|
// clamp proof: addresses must never read beyond the texture).
|
|
logic watch_addr;
|
|
int max_addr_seen;
|
|
always @(posedge clk) begin
|
|
if (watch_addr && tex_rd_en) begin
|
|
if ($signed(tex_rd_addr) > max_addr_seen)
|
|
max_addr_seen = tex_rd_addr;
|
|
end
|
|
end
|
|
|
|
// drive one BILINEAR sample, wait for out_valid, return tex_color.
|
|
task automatic bsample(input int su, input int sv,
|
|
input logic [3:0] suf, input logic [3:0] svf,
|
|
output logic [31:0] col);
|
|
int guard;
|
|
begin
|
|
@(negedge clk);
|
|
in_valid = 1'b1;
|
|
u = 11'(su); v = 11'(sv); u_frac = suf; v_frac = svf;
|
|
@(negedge clk);
|
|
in_valid = 1'b0; // single-cycle request; FSM is running
|
|
guard = 0;
|
|
while (out_valid !== 1'b1 && guard < 200) begin
|
|
@(negedge clk);
|
|
guard++;
|
|
end
|
|
col = tex_color;
|
|
if (guard >= 200) begin
|
|
$error("[bilinear] out_valid never asserted (u=%0d v=%0d)", su, sv);
|
|
errors++;
|
|
end
|
|
end
|
|
endtask
|
|
|
|
// drive the NEAREST reference instance, settle combinationally.
|
|
task automatic nsample(input int su, input int sv,
|
|
input logic [3:0] suf, input logic [3:0] svf,
|
|
output logic [31:0] col);
|
|
begin
|
|
@(negedge clk);
|
|
n_in_valid = 1'b1;
|
|
n_u = 11'(su); n_v = 11'(sv); n_uf = suf; n_vf = svf;
|
|
@(posedge clk); // RD_LATENCY=1 -> out_valid next cycle
|
|
#2;
|
|
col = n_tex_color;
|
|
@(negedge clk);
|
|
n_in_valid = 1'b0;
|
|
end
|
|
endtask
|
|
|
|
logic [31:0] got, exp;
|
|
logic [31:0] w00, w10, w01, w11;
|
|
int x, y;
|
|
|
|
initial begin
|
|
rst_n = 1'b0; in_valid = 1'b0; n_in_valid = 1'b0;
|
|
u = 0; v = 0; u_frac = 0; v_frac = 0;
|
|
n_u = 0; n_v = 0; n_uf = 0; n_vf = 0;
|
|
wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
tw = TWv; th = THv;
|
|
tbp0_base_bytes = CT32_BASE; tbw = TBWv; psm = PSMCT32;
|
|
watch_addr = 1'b0; max_addr_seen = 0;
|
|
for (int i = 0; i < VRAM_BYTES; i++) vram[i] = 8'd0;
|
|
|
|
// upload 4x4 PSMCT32 (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));
|
|
|
|
// Ch314 — CLUT palette + column-striped indexed textures idx(x,y)=x.
|
|
for (int i = 0; i < 256; i++) clut[i] = 32'hFF_80_80_80; // neutral filler
|
|
clut[0] = CLUT0; clut[1] = CLUT1; clut[2] = CLUT2; clut[3] = CLUT3;
|
|
for (y = 0; y < TEXH; y++)
|
|
for (x = 0; x < TEXW; x++) begin
|
|
poke_t8(x, y, 8'(x)); // PSMT8 index = column
|
|
poke_t4(x, y, 4'(x)); // PSMT4 index = column
|
|
end
|
|
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (2) @(posedge clk);
|
|
|
|
// ============================================================
|
|
// CASE 1 — EXACT TEXEL CENTER (uf=0,vf=0) == nearest texel(u,v)
|
|
// ============================================================
|
|
wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
bsample(1, 2, 4'd0, 4'd0, got);
|
|
exp = ct32_word(1, 2);
|
|
if (got !== exp) begin
|
|
$error("[case1 center] u=1 v=2 uf=0 vf=0 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE1 center pass: got=%08x == texel(1,2)", got);
|
|
|
|
// ============================================================
|
|
// CASE 2 — HALFWAY (uf=8,vf=8) == blend of 4 neighbors
|
|
// ============================================================
|
|
bsample(1, 1, 4'd8, 4'd8, got);
|
|
w00 = ct32_word(1,1); w10 = ct32_word(2,1);
|
|
w01 = ct32_word(1,2); w11 = ct32_word(2,2);
|
|
exp = ref_blend(w00, w10, w01, w11, 4'd8, 4'd8);
|
|
if (got !== exp) begin
|
|
$error("[case2 halfway] u=1 v=1 uf=8 vf=8 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE2 halfway pass: got=%08x == 4-tap blend", got);
|
|
|
|
// ============================================================
|
|
// CASE 3 — HALFWAY-U only (uf=8,vf=0) == lerp(texel(u,v),texel(u+1,v))
|
|
// ============================================================
|
|
bsample(0, 0, 4'd8, 4'd0, got);
|
|
w00 = ct32_word(0,0); w10 = ct32_word(1,0);
|
|
w01 = ct32_word(0,1); w11 = ct32_word(1,1);
|
|
exp = ref_blend(w00, w10, w01, w11, 4'd8, 4'd0); // vf=0 -> top only
|
|
if (got !== exp) begin
|
|
$error("[case3 halfway-u] u=0 v=0 uf=8 vf=0 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE3 halfway-u pass: got=%08x == lerp((0,0),(1,0))", got);
|
|
|
|
// ============================================================
|
|
// CASE 4 — CLAMP edge (wms=wmt=1, u=W-1, uf>0): (u+1) clamps to W-1
|
|
// => result == texel(W-1,v). Also assert the issued addresses never
|
|
// exceed the last texel's address (no read outside texture).
|
|
// ============================================================
|
|
wms = WM_CLAMP; wmt = WM_CLAMP;
|
|
watch_addr = 1'b1; max_addr_seen = 0;
|
|
bsample(TEXW-1, 1, 4'd8, 4'd0, got); // u=3, u+1 clamps back to 3
|
|
watch_addr = 1'b0;
|
|
// all 4 taps collapse to (3,1) for u; vf=0 keeps top row (v and v+1
|
|
// both clamp/stay -> here v=1, v+1=2 but vf=0 picks v=1). With u
|
|
// clamped, w00==w10 and w01==w11, and vf=0 -> top only == texel(3,1).
|
|
w00 = ct32_word(3,1); w10 = ct32_word(3,1); // (u+1) clamped to 3
|
|
w01 = ct32_word(3,2); w11 = ct32_word(3,2);
|
|
exp = ref_blend(w00, w10, w01, w11, 4'd8, 4'd0);
|
|
if (got !== ct32_word(3,1)) begin
|
|
$error("[case4 clamp] u=3 v=1 uf=8 got=%08x exp=texel(3,1)=%08x", got, ct32_word(3,1));
|
|
errors++;
|
|
end else if (got !== exp) begin
|
|
$error("[case4 clamp blend] got=%08x exp(blend)=%08x", got, exp);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE4 clamp pass: got=%08x == texel(3,1), no OOB read", got);
|
|
// last legal PSMCT32 texel addr = base + (3*64+3)*4 region; the issued
|
|
// word addr for texel(3,3) is base + (3*64+3)*4 = base + 0x30C. Any
|
|
// higher would mean reading past column 3.
|
|
if (max_addr_seen > $signed(CT32_BASE + ((3*(TBWv*64)) + 3)*4)) begin
|
|
$error("[case4 clamp addr] max issued addr=0x%08x exceeds last-texel addr 0x%08x",
|
|
max_addr_seen, CT32_BASE + ((3*(TBWv*64)) + 3)*4);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE4 max issued addr=0x%08x <= last texel 0x%08x",
|
|
max_addr_seen, CT32_BASE + ((3*(TBWv*64)) + 3)*4);
|
|
|
|
// ============================================================
|
|
// CASE 5 — REPEAT edge (wms=wmt=0, u=W-1, uf>0): (u+1) wraps to 0
|
|
// => result == lerp(texel(W-1,v),texel(0,v),uf) (vf=0, top row).
|
|
// ============================================================
|
|
wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
bsample(TEXW-1, 1, 4'd8, 4'd0, got); // u=3, u+1=4 wraps to 0
|
|
w00 = ct32_word(3,1); w10 = ct32_word(0,1); // (u+1) wraps to col 0
|
|
w01 = ct32_word(3,2); w11 = ct32_word(0,2);
|
|
exp = ref_blend(w00, w10, w01, w11, 4'd8, 4'd0);
|
|
if (got !== exp) begin
|
|
$error("[case5 repeat] u=3 v=1 uf=8 got=%08x exp=lerp(t(3,1),t(0,1))=%08x", got, exp);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE5 repeat pass: got=%08x == lerp(t(3,1),t(0,1))", got);
|
|
|
|
// ============================================================
|
|
// CASE 6 — NEAREST UNCHANGED: BILINEAR_ENABLE=0 instance returns the
|
|
// single nearest texel and IGNORES u_frac/v_frac.
|
|
// ============================================================
|
|
nsample(1, 2, 4'd0, 4'd0, got);
|
|
exp = ct32_word(1, 2);
|
|
if (got !== exp) begin
|
|
$error("[case6 nearest] u=1 v=2 got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end
|
|
// same (u,v) but NON-ZERO frac must give the SAME nearest texel.
|
|
nsample(1, 2, 4'd15, 4'd15, got);
|
|
if (got !== exp) begin
|
|
$error("[case6 nearest frac-ignored] u=1 v=2 uf=15 vf=15 got=%08x exp=%08x (frac must be ignored)", got, exp);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE6 nearest pass: got=%08x (frac ignored), busy=%0b", got, n_busy);
|
|
if (n_busy !== 1'b0) begin
|
|
$error("[case6 nearest busy] nearest instance busy=%0b (must be 0)", n_busy);
|
|
errors++;
|
|
end
|
|
|
|
// ============================================================
|
|
// CASE 7 — PSMT8 BILINEAR halfway-U: the GUARDRAIL. taps idx(0,*)=0(red)
|
|
// and idx(1,*)=1(blue) -> CLUT to red/blue/red/blue, lerp blends the
|
|
// COLORS (purple). Result must equal the color-blend AND be NEITHER
|
|
// endpoint color (which is what interpolating INDICES then CLUT'ing
|
|
// once would give). idx(x,y)=x so vf is irrelevant.
|
|
// ============================================================
|
|
psm = PSMT8v; tbp0_base_bytes = T8_BASE; wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
bsample(0, 0, 4'd8, 4'd0, got);
|
|
exp = ref_blend(CLUT0, CLUT1, CLUT0, CLUT1, 4'd8, 4'd0);
|
|
if (got !== exp) begin
|
|
$error("[case7 t8 color-interp] got=%08x exp(blend red,blue)=%08x", got, exp);
|
|
errors++;
|
|
end else if (got === CLUT0 || got === CLUT1) begin
|
|
$error("[case7 t8 GUARDRAIL] got=%08x is an endpoint color -> indices were interpolated, not colors", got);
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE7 PSMT8 CLUT-before-interp pass: got=%08x (blended red<->blue, neither endpoint)", got);
|
|
|
|
// ============================================================
|
|
// CASE 8 — PSMT8 halfway BOTH (uf=vf=8) blue<->green at (1,1)..(2,2).
|
|
// ============================================================
|
|
bsample(1, 1, 4'd8, 4'd8, got);
|
|
exp = ref_blend(CLUT1, CLUT2, CLUT1, CLUT2, 4'd8, 4'd8);
|
|
if (got !== exp) begin
|
|
$error("[case8 t8 halfway-both] got=%08x exp=%08x", got, exp);
|
|
errors++;
|
|
end else if (got === CLUT1 || got === CLUT2) begin
|
|
$error("[case8 t8 GUARDRAIL] got=%08x endpoint color", got); errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE8 PSMT8 halfway-both pass: got=%08x (blue<->green)", got);
|
|
|
|
// ============================================================
|
|
// CASE 9 — PSMT8 EXACT texel center == CLUT[idx(2,2)]=green. Also the
|
|
// NEAREST instance (BILINEAR_ENABLE=0) must return the same CLUT color.
|
|
// ============================================================
|
|
bsample(2, 2, 4'd0, 4'd0, got);
|
|
if (got !== CLUT2) begin
|
|
$error("[case9 t8 center] got=%08x exp=CLUT[2]=%08x", got, CLUT2); errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE9 PSMT8 center pass: got=%08x == CLUT[2]", got);
|
|
nsample(2, 2, 4'd0, 4'd0, got);
|
|
if (got !== CLUT2) begin
|
|
$error("[case9 t8 nearest] nearest got=%08x exp=CLUT[2]=%08x", got, CLUT2); errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE9 PSMT8 nearest x-check pass: got=%08x == CLUT[2]", got);
|
|
|
|
// ============================================================
|
|
// CASE 10 — PSMT4 halfway-U ACROSS A BYTE BOUNDARY. At y=0 the nibbles
|
|
// are: off0=low(b0)=idx0, off1=high(b0)=idx1, off2=low(b1)=idx2,
|
|
// off3=high(b1)=idx3. Sampling u=1 (idx1=blue, HIGH nibble of byte0)
|
|
// with u+1=2 (idx2=green, LOW nibble of byte1) proves nibble extraction
|
|
// for ALL FOUR taps + the byte-boundary crossing + CLUT-before-interp.
|
|
// ============================================================
|
|
psm = PSMT4v; tbp0_base_bytes = T4_BASE;
|
|
bsample(1, 0, 4'd8, 4'd0, got);
|
|
exp = ref_blend(CLUT1, CLUT2, CLUT1, CLUT2, 4'd8, 4'd0);
|
|
if (got !== exp) begin
|
|
$error("[case10 t4 nibble blend] got=%08x exp(blue,green)=%08x", got, exp);
|
|
errors++;
|
|
end else if (got === CLUT1 || got === CLUT2) begin
|
|
$error("[case10 t4 GUARDRAIL] got=%08x endpoint color", got); errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE10 PSMT4 nibble + CLUT-before-interp pass: got=%08x", got);
|
|
|
|
// ============================================================
|
|
// CASE 11 — PSMT8 REPEAT edge: u=3,u+1 wraps to col0. idx(3,*)=3(white),
|
|
// idx(0,*)=0(red) -> blend white<->red.
|
|
// ============================================================
|
|
psm = PSMT8v; tbp0_base_bytes = T8_BASE; wms = WM_REPEAT; wmt = WM_REPEAT;
|
|
bsample(TEXW-1, 1, 4'd8, 4'd0, got);
|
|
exp = ref_blend(CLUT3, CLUT0, CLUT3, CLUT0, 4'd8, 4'd0);
|
|
if (got !== exp) begin
|
|
$error("[case11 t8 repeat] got=%08x exp(white,red)=%08x", got, exp); errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE11 PSMT8 repeat-edge pass: got=%08x", got);
|
|
|
|
// ============================================================
|
|
// CASE 12 — PSMT8 CLAMP edge: u=3,u+1 clamps to col3 -> all u-taps white;
|
|
// vf=0 -> result == CLUT[idx(3,1)]=white, and NO address reads past the
|
|
// last texel column.
|
|
// ============================================================
|
|
wms = WM_CLAMP; wmt = WM_CLAMP;
|
|
watch_addr = 1'b1; max_addr_seen = 0;
|
|
bsample(TEXW-1, 1, 4'd8, 4'd0, got);
|
|
watch_addr = 1'b0;
|
|
if (got !== CLUT3) begin
|
|
$error("[case12 t8 clamp] got=%08x exp=CLUT[3]=white=%08x", got, CLUT3); errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE12 PSMT8 clamp-edge pass: got=%08x == white", got);
|
|
if (max_addr_seen > $signed(T8_BASE + ((3*(TBWv*64)) + 3))) begin
|
|
$error("[case12 t8 clamp addr] max issued=0x%08x exceeds last texel 0x%08x",
|
|
max_addr_seen, T8_BASE + ((3*(TBWv*64)) + 3));
|
|
errors++;
|
|
end else
|
|
$display("[tb_gs_texture_bilinear] CASE12 max issued addr=0x%08x <= last texel 0x%08x",
|
|
max_addr_seen, T8_BASE + ((3*(TBWv*64)) + 3));
|
|
|
|
$display("[tb_gs_texture_bilinear] errors=%0d", errors);
|
|
if (errors == 0)
|
|
$display("[tb_gs_texture_bilinear] PASS");
|
|
else
|
|
$display("[tb_gs_texture_bilinear] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#2000000;
|
|
$error("[tb_gs_texture_bilinear] TIMEOUT");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_gs_texture_bilinear
|