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>
138 lines
4.5 KiB
Systemverilog
138 lines
4.5 KiB
Systemverilog
// retroDE_ps2 — tb_gs_persp_uv (Ch301)
|
|
//
|
|
// Focused TB for gs_persp_uv — the per-pixel perspective-correct divide
|
|
// (recip + multiply + clamp) that recovers integer texel (u,v) from the
|
|
// interpolated perspective attributes (u/w, v/w, 1/w). Proves:
|
|
//
|
|
// PROOF 1 — ROUND-TRIP: for known (u,v,w), feeding uq=(u<<FRAC)/w,
|
|
// vq=(v<<FRAC)/w, q=(1<<FRAC)/w returns texel (u,v) within the
|
|
// reciprocal's mantissa tolerance (<= ~1 texel for the demo range).
|
|
// Sweep covers near (w small) and far (w large) — the regime where
|
|
// perspective diverges from affine.
|
|
//
|
|
// PROOF 2 — THROUGHPUT: back-to-back inputs, one result/cycle, no stall.
|
|
//
|
|
// PROOF 3 — CLAMP: an over-range result saturates to TEXEL_MAX, not wrap.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_persp_uv;
|
|
|
|
localparam int ATTR_W = 24;
|
|
localparam int Q_W = 24;
|
|
localparam int FRAC = 12;
|
|
localparam int SCALE = 24;
|
|
localparam int TEXEL_W = 11;
|
|
localparam int TEXEL_MAX = 63; // demo texture max coord
|
|
|
|
logic clk, rst_n;
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
logic in_valid;
|
|
logic [ATTR_W-1:0] uq, vq;
|
|
logic [Q_W-1:0] q;
|
|
logic out_valid;
|
|
logic [TEXEL_W-1:0] u, v;
|
|
|
|
gs_persp_uv #(
|
|
.ATTR_W(ATTR_W), .Q_W(Q_W), .FRAC(FRAC), .SCALE(SCALE),
|
|
.RECIP_W(25), .TEXEL_W(TEXEL_W), .TEXEL_MAX(TEXEL_MAX)
|
|
) dut (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(in_valid), .uq(uq), .vq(vq), .q(q),
|
|
.out_valid(out_valid), .u(u), .v(v)
|
|
);
|
|
|
|
// input vectors: (u,v,w) triples + their fixed-point attributes
|
|
localparam int NV = 512;
|
|
int tu [0:NV-1];
|
|
int tv [0:NV-1];
|
|
int tw [0:NV-1];
|
|
logic [ATTR_W-1:0] vuq [0:NV-1];
|
|
logic [ATTR_W-1:0] vvq [0:NV-1];
|
|
logic [Q_W-1:0] vq_ [0:NV-1];
|
|
int n;
|
|
|
|
initial begin
|
|
n = 0;
|
|
// sweep texel coords 0..63 across a range of w (1..16 = near..far)
|
|
for (int w = 1; w <= 16 && n < NV; w++) begin
|
|
for (int c = 0; c <= 63 && n < NV; c += 7) begin
|
|
tu[n] = c;
|
|
tv[n] = 63 - c;
|
|
tw[n] = w;
|
|
vuq[n] = ATTR_W'((c * (1 << FRAC)) / w);
|
|
vvq[n] = ATTR_W'(((63 - c) * (1 << FRAC)) / w);
|
|
vq_[n] = Q_W'((1 << FRAC) / w);
|
|
n++;
|
|
end
|
|
end
|
|
// an explicit over-range case to exercise the clamp (u/w*w would be 100>63)
|
|
tu[n]=100; tv[n]=0; tw[n]=1;
|
|
vuq[n]=ATTR_W'((100*(1<<FRAC))/1); vvq[n]='0; vq_[n]=Q_W'((1<<FRAC)/1); n++;
|
|
end
|
|
|
|
logic [TEXEL_W-1:0] ou [0:NV-1];
|
|
logic [TEXEL_W-1:0] ov [0:NV-1];
|
|
int oc;
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n && out_valid) begin
|
|
ou[oc] <= u;
|
|
ov[oc] <= v;
|
|
oc <= oc + 1;
|
|
end
|
|
end
|
|
|
|
int errors;
|
|
function automatic int iabs(input int x); return (x < 0) ? -x : x; endfunction
|
|
|
|
initial begin
|
|
errors = 0; oc = 0;
|
|
in_valid = 1'b0; uq = '0; vq = '0; q = '0;
|
|
rst_n = 1'b0;
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (2) @(posedge clk);
|
|
|
|
for (int i = 0; i < n; i++) begin
|
|
@(negedge clk);
|
|
in_valid = 1'b1; uq = vuq[i]; vq = vvq[i]; q = vq_[i];
|
|
end
|
|
@(negedge clk); in_valid = 1'b0; uq='0; vq='0; q='0;
|
|
repeat (8) @(posedge clk);
|
|
|
|
if (oc !== n) begin
|
|
$error("[persp_uv] throughput: expected %0d outputs got %0d", n, oc); errors++;
|
|
end
|
|
|
|
for (int i = 0; i < oc && i < n; i++) begin
|
|
int eu, ev;
|
|
// clamp expected to TEXEL_MAX (the over-range case)
|
|
eu = (tu[i] > TEXEL_MAX) ? TEXEL_MAX : tu[i];
|
|
ev = (tv[i] > TEXEL_MAX) ? TEXEL_MAX : tv[i];
|
|
if (iabs(int'(ou[i]) - eu) > 1) begin
|
|
$error("[persp_uv] i=%0d (u=%0d,w=%0d) got_u=%0d exp=%0d", i, tu[i], tw[i], ou[i], eu);
|
|
errors++;
|
|
end
|
|
if (iabs(int'(ov[i]) - ev) > 1) begin
|
|
$error("[persp_uv] i=%0d (v=%0d,w=%0d) got_v=%0d exp=%0d", i, tv[i], tw[i], ov[i], ev);
|
|
errors++;
|
|
end
|
|
end
|
|
|
|
$display("[tb_gs_persp_uv] checked %0d, errors=%0d", oc, errors);
|
|
if (errors == 0) $display("[tb_gs_persp_uv] PASS");
|
|
else $display("[tb_gs_persp_uv] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#2000000;
|
|
$error("[tb_gs_persp_uv] TIMEOUT");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_gs_persp_uv
|