Files
retroDE_ps2/sim/tb/gif_gs/tb_gs_raster_pipeline.sv
T
thejayman77 ba74bbd5aa Snapshot: fog implementation + fidelity tooling baseline (pre bilinear-clamp fix)
Per-vertex GS fog end-to-end (gs_stub emit incl. persp_emit5, gs_prim_list_feeder
XYZ2->XYZF2 on PRIM.FGE, gs_make_sh3_scheduler_fixture.py F/FGE packing), new fog
TBs, fidelity attribution tooling. Functional baseline before removing the dead
bilinear lerp8 clamps (Codex: 161-node comb loop -> -0.042ns setup fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 19:56:46 -04:00

344 lines
14 KiB
Systemverilog
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// retroDE_ps2 — tb_gs_raster_pipeline (Ch88)
//
// White-box TB pinning down the Ch88 pixel-pipeline contract:
//
// * Throughput: 1 candidate pixel/cycle once the pipeline is
// primed. For a fully-inside primitive this means 1 emit/cycle
// for N consecutive cycles, where N is the bbox pixel count.
//
// * Latency: from the v2-close cycle (push) to first observed
// raster_pixel_emit, exactly 5 posedges:
// v_close v2 commits, push_ok=1 (FIFO 0→1)
// v_close+1 R_IDLE+nonempty → pop_ok=1, state ← R_SCAN
// v_close+2 state=R_SCAN, S0 produces pix(0,0); s1 ← (0,0)
// v_close+3 s2 ← (0,0)
// v_close+4 emit register fires (raster_pixel_emit ← 1)
// v_close+5 raster_pixel_emit visible to observer
// i.e., 3 stages of pipeline + 1 cycle for the FIFO turn-
// around + 1 cycle for the registered emit output.
//
// * After last S0 coord, the pipeline drains for 2 more cycles
// so all in-flight pixels still emit (no truncation).
//
// Flow: drive PRIM/FRAME_1/RGBAQ then a single 4×4 SPRITE
// (16 pixels). Capture the cycle index of every raster_pixel_emit
// pulse and assert:
// - exactly 16 pulses
// - every adjacent pair is on consecutive cycles (delta == 1)
// - first pulse lands EXACTLY 3 cycles after the pop_ok cycle
// - raster_overflow stays low
`timescale 1ns/1ps
module tb_gs_raster_pipeline #(
parameter bit USE_SUBPIXEL = 1'b0,
parameter int GRAD_CYCLES = 1
);
logic clk;
logic rst_n;
initial clk = 1'b0;
always #5 clk = ~clk;
// gs_stub inputs
logic gif_reg_wr_en;
logic [7:0] gif_reg_num;
logic [63:0] gif_reg_data;
// gs_stub outputs (most are tied off — we only watch raster_*)
logic [7:0] bg_r, bg_g, bg_b;
logic [63:0] prim_q, rgbaq_q, xyz2_q, xyzf2_q, frame_1_q, zbuf_1_q;
logic prim_complete;
logic [31:0] prim_complete_count;
logic [63:0] prim_v0_q, prim_v1_q, prim_v2_q;
logic [63:0] prim_color_q;
logic [63:0] prim_color_v0_q, prim_color_v1_q, prim_color_v2_q;
trace_pkg::vertex_t prim_v0_decoded_q, prim_v1_decoded_q, prim_v2_decoded_q;
trace_pkg::color_t prim_v0_color_decoded_q, prim_v1_color_decoded_q, prim_v2_color_decoded_q;
logic pixel_emit;
logic [31:0] pixel_emit_count;
logic [11:0] pixel_x_q, pixel_y_q;
logic [63:0] pixel_color_q;
logic [8:0] pixel_fbp_q;
logic [5:0] pixel_fbw_q, pixel_psm_q;
logic [31:0] pixel_fb_addr_q;
logic raster_pixel_emit;
logic [31:0] raster_pixel_emit_count;
logic [11:0] raster_pixel_x_q, raster_pixel_y_q;
logic [63:0] raster_pixel_color_q;
logic [31:0] raster_pixel_fb_addr_q;
logic [31:0] raster_pixel_z_q;
logic raster_active;
logic raster_overflow;
logic raster_degenerate;
logic ev_valid;
trace_pkg::subsys_e ev_subsys;
trace_pkg::event_e ev_event;
logic [63:0] ev_arg0, ev_arg1, ev_arg2, ev_arg3;
logic [31:0] ev_flags;
gs_stub #(
.SUBPIXEL_XY(USE_SUBPIXEL),
.GRAD_SEQ_DIVIDER(1'b0),
.GRAD_DIV_CYCLES(GRAD_CYCLES)
) u_gs (
.clk(clk), .rst_n(rst_n),
.reg_wr_en(1'b0), .reg_wr_addr(16'd0), .reg_wr_data(64'd0),
.gif_reg_wr_en(gif_reg_wr_en),
.gif_reg_num(gif_reg_num),
.gif_reg_data(gif_reg_data),
.bg_r(bg_r), .bg_g(bg_g), .bg_b(bg_b),
.prim_q(prim_q), .rgbaq_q(rgbaq_q),
.xyz2_q(xyz2_q), .xyzf2_q(xyzf2_q),
.frame_1_q(frame_1_q), .zbuf_1_q(zbuf_1_q),
.prim_complete(prim_complete),
.prim_complete_count(prim_complete_count),
.prim_v0_q(prim_v0_q), .prim_v1_q(prim_v1_q), .prim_v2_q(prim_v2_q),
.prim_color_q(prim_color_q),
.prim_color_v0_q(prim_color_v0_q),
.prim_color_v1_q(prim_color_v1_q),
.prim_color_v2_q(prim_color_v2_q),
.prim_v0_decoded_q(prim_v0_decoded_q),
.prim_v1_decoded_q(prim_v1_decoded_q),
.prim_v2_decoded_q(prim_v2_decoded_q),
.prim_v0_color_decoded_q(prim_v0_color_decoded_q),
.prim_v1_color_decoded_q(prim_v1_color_decoded_q),
.prim_v2_color_decoded_q(prim_v2_color_decoded_q),
.pixel_emit(pixel_emit),
.pixel_emit_count(pixel_emit_count),
.pixel_x_q(pixel_x_q), .pixel_y_q(pixel_y_q),
.pixel_color_q(pixel_color_q),
.pixel_fbp_q(pixel_fbp_q),
.pixel_fbw_q(pixel_fbw_q),
.pixel_psm_q(pixel_psm_q),
.pixel_fb_addr_q(pixel_fb_addr_q),
.raster_pixel_emit(raster_pixel_emit),
.raster_pixel_emit_count(raster_pixel_emit_count),
.raster_pixel_x_q(raster_pixel_x_q),
.raster_pixel_y_q(raster_pixel_y_q),
.raster_pixel_color_q(raster_pixel_color_q),
.raster_pixel_fb_addr_q(raster_pixel_fb_addr_q),
.raster_pixel_z_q(raster_pixel_z_q),
.raster_active(raster_active),
.raster_overflow(raster_overflow),
.raster_degenerate(raster_degenerate),
.ev_valid(ev_valid), .ev_subsys(ev_subsys), .ev_event(ev_event),
.ev_arg0(ev_arg0), .ev_arg1(ev_arg1),
.ev_arg2(ev_arg2), .ev_arg3(ev_arg3),
.ev_flags(ev_flags)
);
// Per-cycle index. Increments every posedge clk after rst_n.
int cycle_idx;
initial cycle_idx = 0;
always_ff @(posedge clk) if (rst_n) cycle_idx <= cycle_idx + 1;
// Capture the cycle of the v2 close (last drive_reg call) and
// the cycle every raster_pixel_emit pulse fires.
int v_close_cycle;
int emit_cycles [0:31];
int emit_count;
int errors;
logic [7:0] sub_r [0:3][0:3];
logic [31:0] sub_z [0:3][0:3];
logic sub_seen [0:3][0:3];
initial begin
v_close_cycle = -1;
emit_count = 0;
errors = 0;
for (int y = 0; y < 4; y++) begin
for (int x = 0; x < 4; x++) begin
sub_r[y][x] = 8'd0;
sub_z[y][x] = 32'd0;
sub_seen[y][x] = 1'b0;
end
end
end
always_ff @(posedge clk) begin
if (rst_n && raster_pixel_emit && emit_count < 32) begin
emit_cycles[emit_count] <= cycle_idx;
emit_count <= emit_count + 1;
if (USE_SUBPIXEL && raster_pixel_x_q < 4 && raster_pixel_y_q < 4) begin
sub_r[raster_pixel_y_q][raster_pixel_x_q] <= raster_pixel_color_q[7:0];
sub_z[raster_pixel_y_q][raster_pixel_x_q] <= raster_pixel_z_q;
sub_seen[raster_pixel_y_q][raster_pixel_x_q] <= 1'b1;
end
end
end
task automatic step_drive(input logic wr_en,
input logic [7:0] num,
input logic [63:0] data);
@(negedge clk);
gif_reg_wr_en = wr_en;
gif_reg_num = num;
gif_reg_data = data;
@(posedge clk);
endtask
task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
step_drive(1'b1, num, data);
endtask
task automatic drive_idle();
step_drive(1'b0, 8'd0, 64'd0);
endtask
function automatic logic [63:0] xyz2_data(input logic [11:0] x_int,
input logic [11:0] y_int);
return {32'd0, y_int, 4'd0, x_int, 4'd0};
endfunction
function automatic logic [63:0] xyz2_sub(input logic [15:0] x_12_4,
input logic [15:0] y_12_4);
return {32'd0, y_12_4, x_12_4};
endfunction
function automatic logic [63:0] xyz2_sub_z(input logic [15:0] x_12_4,
input logic [15:0] y_12_4,
input logic [31:0] z);
return {z, y_12_4, x_12_4};
endfunction
function automatic logic [63:0] rgbaq_r(input logic [7:0] r);
return {32'd0, 8'hff, 8'd0, 8'd0, r};
endfunction
localparam logic [7:0] R_PRIM = 8'h00;
localparam logic [7:0] R_RGBAQ = 8'h01;
localparam logic [7:0] R_XYZ2 = 8'h05;
localparam logic [7:0] R_FRAME_1 = 8'h4C;
localparam logic [63:0] PRIM_SPRITE = 64'd6;
localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_000A_0002;
localparam logic [63:0] RGBAQ_VAL = 64'h0000_0000_FF00_30FF;
initial begin
rst_n = 1'b0;
gif_reg_wr_en = 1'b0;
gif_reg_num = 8'd0;
gif_reg_data = 64'd0;
repeat (4) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
drive_reg(R_PRIM, USE_SUBPIXEL ? 64'd3 : PRIM_SPRITE);
drive_reg(R_FRAME_1, FRAME_1_VAL);
drive_reg(R_RGBAQ, RGBAQ_VAL);
if (USE_SUBPIXEL) begin
// Right triangle at (0.75,0.75),(3.75,0.75),(0.75,3.75).
// Pixel-center 12.4 coverage is exactly (1,1),(2,1),(1,2).
// R and Z form exact planes: dR/dx=32, dR/dy=64,
// dZ/dx=100, dZ/dy=200. This checks fractional gradient
// setup and +0.5 pixel-center evaluation as well as coverage.
drive_reg(R_RGBAQ, rgbaq_r(8'd0));
drive_reg(R_XYZ2, xyz2_sub_z(16'h000c,16'h000c,32'd1000));
drive_reg(R_RGBAQ, rgbaq_r(8'd96));
drive_reg(R_XYZ2, xyz2_sub_z(16'h003c,16'h000c,32'd1300));
drive_reg(R_RGBAQ, rgbaq_r(8'd192));
drive_reg(R_XYZ2, xyz2_sub_z(16'h000c,16'h003c,32'd1600));
end else begin
// 4×4 sprite — bbox=[0..3]×[0..3] = 16 pixels.
drive_reg(R_XYZ2, xyz2_data(12'd0, 12'd0));
drive_reg(R_XYZ2, xyz2_data(12'd3, 12'd3));
end
v_close_cycle = cycle_idx; // capture posedge index of v2 close
// Stop driving (deassert gif_reg_wr_en) and let the
// pipeline run. Without this idle, gif_reg_wr_en stays
// high and re-commits the v2 vertex every cycle, kicking
// off extra sprites and overflowing the FIFO.
drive_idle();
// A TRI cannot pop until all 14 affine gradients are ready. The
// production combinational-divider FSM spends GRAD_CYCLES settle
// cycles plus select/commit overhead per step, so scale this gate
// with the configured latency instead of falsely timing out at the
// legacy fixed 40 cycles.
repeat (USE_SUBPIXEL ? (14 * (GRAD_CYCLES + 2) + 20) : 40) @(posedge clk);
// ---- Assertions ----
$display("[tb_gs_raster_pipeline] v_close_cycle=%0d emit_count=%0d raster_pixel_emit_count=%0d raster_overflow=%b",
v_close_cycle, emit_count, raster_pixel_emit_count, raster_overflow);
for (int i = 0; i < emit_count; i++) begin
$display("[tb_gs_raster_pipeline] emit[%0d] @ cyc=%0d", i, emit_cycles[i]);
end
if (emit_count != (USE_SUBPIXEL ? 3 : 16)) begin
$error("emit_count=%0d (expected %0d)", emit_count, USE_SUBPIXEL ? 3 : 16);
errors = errors + 1;
end
if (raster_pixel_emit_count != (USE_SUBPIXEL ? 32'd3 : 32'd16)) begin
$error("raster_pixel_emit_count=%0d (expected %0d)", raster_pixel_emit_count,
USE_SUBPIXEL ? 3 : 16);
errors = errors + 1;
end
if (raster_overflow !== 1'b0) begin
$error("raster_overflow=%b (expected 0)", raster_overflow);
errors = errors + 1;
end
if (USE_SUBPIXEL) begin
if (!sub_seen[1][1] || sub_r[1][1] != 8'd72 || sub_z[1][1] != 32'd1225) begin
$error("subpixel attr (1,1): seen=%b R=%0d Z=%0d expected R=72 Z=1225",
sub_seen[1][1], sub_r[1][1], sub_z[1][1]);
errors = errors + 1;
end
if (!sub_seen[1][2] || sub_r[1][2] != 8'd104 || sub_z[1][2] != 32'd1325) begin
$error("subpixel attr (2,1): seen=%b R=%0d Z=%0d expected R=104 Z=1325",
sub_seen[1][2], sub_r[1][2], sub_z[1][2]);
errors = errors + 1;
end
if (!sub_seen[2][1] || sub_r[2][1] != 8'd136 || sub_z[2][1] != 32'd1425) begin
$error("subpixel attr (1,2): seen=%b R=%0d Z=%0d expected R=136 Z=1425",
sub_seen[2][1], sub_r[2][1], sub_z[2][1]);
errors = errors + 1;
end
end
// Throughput: every consecutive pair of emits must be on
// adjacent cycles (delta == 1). 1 pixel/cycle.
for (int i = 1; i < emit_count && !USE_SUBPIXEL; i++) begin
int d;
d = emit_cycles[i] - emit_cycles[i-1];
if (d != 1) begin
$error("throughput break: emit[%0d]@%0d vs emit[%0d]@%0d (delta=%0d, expected 1)",
i-1, emit_cycles[i-1], i, emit_cycles[i], d);
errors = errors + 1;
end
end
// Latency: 6 posedges from v_close to first observed raster_pixel_emit. Ch357 (Codex) pipelined the attr_ram
// WRITE (register the assembled word on push, commit to M20K the next cycle + attr_pending holds the slot until
// the write lands), so the assembled prim is poppable at push+2 instead of push+1 — +1 cycle vs the old
// v_close+5. Correctness is unchanged (emit_count/overflow identical); this pins the new pipeline depth.
// The TRI subpixel case waits for the shared gradient engine before
// pop; this latency assertion is specifically the legacy SPRITE pipe.
if (emit_count > 0 && !USE_SUBPIXEL) begin
int expected_first;
int actual_first;
expected_first = v_close_cycle + 6;
actual_first = emit_cycles[0];
if (actual_first != expected_first) begin
$error("first-emit latency: emit[0]@cyc=%0d (expected %0d = v_close+6, Ch357 attr-write pipeline)",
actual_first, expected_first);
errors = errors + 1;
end
end
if (errors == 0) $display("[tb_gs_raster_pipeline] PASS");
else $display("[tb_gs_raster_pipeline] FAIL");
$finish;
end
initial begin
#5000000;
$error("[tb_gs_raster_pipeline] timeout");
$finish;
end
endmodule : tb_gs_raster_pipeline