Initial commit: retroDE_ps2 — first-of-its-kind PS2 GS FPGA core (DE25-Nano / Agilex 5)
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>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// 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
|
||||
Reference in New Issue
Block a user