Files
retroDE_ps2/sim/tb/gif_gs/tb_gs_alpha_blend.sv
thejayman77 ec82764bef 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>
2026-06-29 20:10:50 -04:00

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