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>
145 lines
4.7 KiB
Systemverilog
145 lines
4.7 KiB
Systemverilog
// retroDE_ps2 — tb_gs_reciprocal (Ch301)
|
|
//
|
|
// Focused TB for gs_reciprocal_stub — the pipelined range-reduced reciprocal
|
|
// unit that backs PERSPECTIVE-CORRECT texture interpolation (recip = 1/q with
|
|
// NO divider). Proves:
|
|
//
|
|
// PROOF 1 — CORRECTNESS vs software reference: for a sweep of q over the
|
|
// perspective demo range (and powers of two + edges), the hardware
|
|
// recip matches floor(2**SCALE / q) within the 8-bit-mantissa
|
|
// relative tolerance (~1 part in 256).
|
|
//
|
|
// PROOF 2 — PIPELINE THROUGHPUT: one result per cycle, back-to-back inputs
|
|
// with no stall (the rasterizer needs ~1 pixel/cycle). Inputs are
|
|
// streamed continuously; outputs are collected in order and matched
|
|
// to their originating q.
|
|
//
|
|
// PROOF 3 — q==0 saturates (1/0 -> all-ones), not X / wraparound.
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_reciprocal;
|
|
|
|
localparam int Q_W = 24;
|
|
localparam int SCALE = 24;
|
|
localparam int OUT_W = 25;
|
|
|
|
logic clk, rst_n;
|
|
initial clk = 1'b0;
|
|
always #5 clk = ~clk;
|
|
|
|
logic in_valid;
|
|
logic [Q_W-1:0] q;
|
|
logic out_valid;
|
|
logic [OUT_W-1:0] recip;
|
|
|
|
gs_reciprocal_stub #(.Q_W(Q_W), .IDX_BITS(8), .SCALE(SCALE), .OUT_W(OUT_W)) dut (
|
|
.clk(clk), .rst_n(rst_n),
|
|
.in_valid(in_valid), .q(q),
|
|
.out_valid(out_valid), .recip(recip)
|
|
);
|
|
|
|
// golden reference
|
|
function automatic logic [OUT_W-1:0] ref_recip(input logic [Q_W-1:0] qq);
|
|
if (qq == 0) return {OUT_W{1'b1}};
|
|
else return OUT_W'((64'd1 << SCALE) / qq);
|
|
endfunction
|
|
|
|
// build the input vector: edges + powers of two + a dense sweep of the
|
|
// perspective demo range [256 .. 65536] (q = (1/w)*2**16 for w in [1..256]).
|
|
localparam int NV = 600;
|
|
logic [Q_W-1:0] qin [0:NV-1];
|
|
int n;
|
|
|
|
initial begin
|
|
n = 0;
|
|
qin[n++] = 24'd1;
|
|
qin[n++] = 24'd2;
|
|
qin[n++] = 24'd3;
|
|
qin[n++] = 24'd255;
|
|
qin[n++] = 24'd256;
|
|
qin[n++] = 24'd257;
|
|
for (int p = 0; p < Q_W; p++) qin[n++] = 24'(24'd1 << p); // powers of two
|
|
// dense sweep across the demo 1/w range
|
|
for (int w = 1; w <= 256 && n < NV; w++)
|
|
qin[n++] = 24'((65536) / w); // q = floor(2**16 / w)
|
|
// a few extra mid-range values
|
|
while (n < NV) begin qin[n] = 24'(1000 + n*37); n++; end
|
|
end
|
|
|
|
// collected outputs, in order
|
|
logic [OUT_W-1:0] rout [0:NV-1];
|
|
int oc; // output count
|
|
|
|
always_ff @(posedge clk) begin
|
|
if (rst_n && out_valid) begin
|
|
rout[oc] <= recip;
|
|
oc <= oc + 1;
|
|
end
|
|
end
|
|
|
|
int errors;
|
|
|
|
initial begin
|
|
errors = 0;
|
|
oc = 0;
|
|
in_valid = 1'b0;
|
|
q = '0;
|
|
rst_n = 1'b0;
|
|
repeat (4) @(posedge clk);
|
|
rst_n = 1'b1;
|
|
repeat (2) @(posedge clk);
|
|
|
|
// PROOF 2: stream all NV inputs back-to-back (in_valid held high).
|
|
for (int i = 0; i < NV; i++) begin
|
|
@(negedge clk);
|
|
in_valid = 1'b1;
|
|
q = qin[i];
|
|
end
|
|
@(negedge clk);
|
|
in_valid = 1'b0;
|
|
q = '0;
|
|
|
|
// drain the pipeline
|
|
repeat (8) @(posedge clk);
|
|
|
|
if (oc !== NV) begin
|
|
$error("[recip] throughput: expected %0d outputs, got %0d (pipeline stalled / dropped)", NV, oc);
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// PROOF 1 + 3: compare each output to the software reference.
|
|
for (int i = 0; i < oc && i < NV; i++) begin
|
|
logic [OUT_W-1:0] exp;
|
|
logic [OUT_W:0] diff;
|
|
logic [OUT_W:0] tol;
|
|
exp = ref_recip(qin[i]);
|
|
diff = (rout[i] > exp) ? ({1'b0,rout[i]} - {1'b0,exp})
|
|
: ({1'b0,exp} - {1'b0,rout[i]});
|
|
// 8-bit mantissa -> relative error ~1/256; allow exp/128 + 1.
|
|
tol = (exp >> 7) + 2;
|
|
if (qin[i] == 0) begin
|
|
if (rout[i] !== {OUT_W{1'b1}}) begin
|
|
$error("[recip] q=0 not saturated: got %0d", rout[i]); errors++;
|
|
end
|
|
end else if (diff > tol) begin
|
|
$error("[recip] q=%0d got=%0d exp=%0d diff=%0d tol=%0d",
|
|
qin[i], rout[i], exp, diff, tol);
|
|
errors++;
|
|
end
|
|
end
|
|
|
|
$display("[tb_gs_reciprocal] checked %0d values, errors=%0d", oc, errors);
|
|
if (errors == 0) $display("[tb_gs_reciprocal] PASS");
|
|
else $display("[tb_gs_reciprocal] FAIL");
|
|
$finish;
|
|
end
|
|
|
|
initial begin
|
|
#2000000;
|
|
$error("[tb_gs_reciprocal] TIMEOUT");
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_gs_reciprocal
|