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>
127 lines
5.0 KiB
Systemverilog
127 lines
5.0 KiB
Systemverilog
// retroDE_ps2 — tb_gs_alpha_blend (Brick 2a)
|
|
//
|
|
// Unit TB for the gs_alpha_blend datapath. Drives (Cs, Cd, As) and
|
|
// checks Cv == ((Cs - Cd) * As) >> 7 + Cd per channel, with clamping,
|
|
// against an independent software reference computed in the TB.
|
|
//
|
|
// Covers:
|
|
// - As = 0 -> Cv == Cd (fully transparent source)
|
|
// - As = 128 -> Cv == Cs (fully opaque source)
|
|
// - As = 64 -> ~50% blend, mid value, NEITHER Cs nor Cd
|
|
// - dest brighter than source (negative diff path)
|
|
// - clamp-high and clamp-low edge values
|
|
// - randomized sweep cross-checked against the reference model
|
|
|
|
`timescale 1ns/1ps
|
|
|
|
module tb_gs_alpha_blend;
|
|
|
|
logic [7:0] cs_r, cs_g, cs_b, as;
|
|
logic [7:0] cd_r, cd_g, cd_b;
|
|
logic [7:0] cv_r, cv_g, cv_b, a_out;
|
|
|
|
gs_alpha_blend dut (
|
|
.cs_r(cs_r), .cs_g(cs_g), .cs_b(cs_b), .as(as),
|
|
.cd_r(cd_r), .cd_g(cd_g), .cd_b(cd_b),
|
|
.cv_r(cv_r), .cv_g(cv_g), .cv_b(cv_b), .a_out(a_out)
|
|
);
|
|
|
|
int errors = 0;
|
|
|
|
// Independent reference: signed math + clamp, As clamped at 128.
|
|
function automatic logic [7:0] ref_blend(input logic [7:0] cs,
|
|
input logic [7:0] cd,
|
|
input logic [7:0] alpha);
|
|
int a_eff;
|
|
int diff;
|
|
int prod;
|
|
int shifted;
|
|
int sum;
|
|
a_eff = (alpha > 128) ? 128 : alpha;
|
|
diff = int'(cs) - int'(cd);
|
|
prod = diff * a_eff;
|
|
// Arithmetic >> 7 (floor toward -inf for negatives, matching >>>).
|
|
shifted = prod >>> 7;
|
|
sum = shifted + int'(cd);
|
|
if (sum < 0) return 8'd0;
|
|
else if (sum > 255) return 8'd255;
|
|
else return 8'(sum);
|
|
endfunction
|
|
|
|
task automatic check(input logic [7:0] sr, sg, sb, a,
|
|
input logic [7:0] dr, dg, db,
|
|
input string label);
|
|
logic [7:0] er, eg, eb;
|
|
cs_r = sr; cs_g = sg; cs_b = sb; as = a;
|
|
cd_r = dr; cd_g = dg; cd_b = db;
|
|
#1;
|
|
er = ref_blend(sr, dr, a);
|
|
eg = ref_blend(sg, dg, a);
|
|
eb = ref_blend(sb, db, a);
|
|
if (cv_r !== er || cv_g !== eg || cv_b !== eb) begin
|
|
$error("[%s] Cs=(%02x,%02x,%02x) As=%0d Cd=(%02x,%02x,%02x): got (%02x,%02x,%02x) exp (%02x,%02x,%02x)",
|
|
label, sr, sg, sb, a, dr, dg, db, cv_r, cv_g, cv_b, er, eg, eb);
|
|
errors = errors + 1;
|
|
end
|
|
if (a_out !== a) begin
|
|
$error("[%s] a_out passthrough got %02x exp %02x", label, a_out, a);
|
|
errors = errors + 1;
|
|
end
|
|
endtask
|
|
|
|
initial begin
|
|
// As=0 -> Cv == Cd exactly.
|
|
check(8'hFF, 8'h80, 8'h10, 8'd0, 8'h11, 8'h22, 8'h33, "as0_eq_cd");
|
|
#1;
|
|
if (cv_r !== 8'h11 || cv_g !== 8'h22 || cv_b !== 8'h33) begin
|
|
$error("as=0 must equal Cd"); errors = errors + 1; end
|
|
|
|
// As=128 -> Cv == Cs exactly.
|
|
cs_r=8'hAB; cs_g=8'hCD; cs_b=8'hEF; as=8'd128;
|
|
cd_r=8'h00; cd_g=8'h7F; cd_b=8'hFF; #1;
|
|
if (cv_r !== 8'hAB || cv_g !== 8'hCD || cv_b !== 8'hEF) begin
|
|
$error("as=128 must equal Cs: got (%02x,%02x,%02x)", cv_r, cv_g, cv_b);
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// As=64 (~0.5): mid blend, must be strictly between Cs and Cd.
|
|
cs_r=8'd200; cs_g=8'd200; cs_b=8'd200; as=8'd64;
|
|
cd_r=8'd0; cd_g=8'd0; cd_b=8'd0; #1;
|
|
// (200-0)*64>>7 + 0 = 12800>>7 = 100.
|
|
if (cv_r !== 8'd100) begin
|
|
$error("as=64 mid blend: got %0d exp 100", cv_r); errors = errors + 1; end
|
|
if (cv_r === cs_r || cv_r === cd_r) begin
|
|
$error("as=64 blend collapsed to pure src or dest"); errors = errors + 1; end
|
|
|
|
// dest brighter than source -> negative diff path.
|
|
check(8'd10, 8'd10, 8'd10, 8'd64, 8'd250, 8'd250, 8'd250, "neg_diff");
|
|
|
|
// clamp edges and assorted values via reference cross-check.
|
|
check(8'hFF, 8'hFF, 8'hFF, 8'd128, 8'h00, 8'h00, 8'h00, "white_over_black");
|
|
check(8'h00, 8'h00, 8'h00, 8'd128, 8'hFF, 8'hFF, 8'hFF, "black_over_white");
|
|
check(8'h40, 8'h80, 8'hC0, 8'd40, 8'h90, 8'h20, 8'h10, "assorted1");
|
|
check(8'hF0, 8'h0F, 8'h55, 8'd96, 8'h0A, 8'hF5, 8'hAA, "assorted2");
|
|
|
|
// As > 128 must clamp to 128 (== Cs).
|
|
cs_r=8'h33; cs_g=8'h44; cs_b=8'h55; as=8'd200;
|
|
cd_r=8'h99; cd_g=8'hAA; cd_b=8'hBB; #1;
|
|
if (cv_r !== 8'h33 || cv_g !== 8'h44 || cv_b !== 8'h55) begin
|
|
$error("as>128 must clamp to Cs: got (%02x,%02x,%02x)", cv_r, cv_g, cv_b);
|
|
errors = errors + 1;
|
|
end
|
|
|
|
// Randomized sweep.
|
|
for (int i = 0; i < 2000; i++) begin
|
|
check(8'($urandom), 8'($urandom), 8'($urandom), 8'($urandom % 129),
|
|
8'($urandom), 8'($urandom), 8'($urandom), "rand");
|
|
end
|
|
|
|
if (errors == 0)
|
|
$display("[tb_gs_alpha_blend] PASS");
|
|
else
|
|
$display("[tb_gs_alpha_blend] FAIL errors=%0d", errors);
|
|
$finish;
|
|
end
|
|
|
|
endmodule : tb_gs_alpha_blend
|