Add FOG_ENABLE param, prune fog cone on the board profile (fix -6.165ns setup)

The fog blend's tex_color*F multiply landed on the texture->color critical path
(u_texcache RAM -> u_tex|tex_color -> mult_126 -> raster_pixel_color_q, 46ns,
-6.165ns setup). Fog is a proven no-op for the current board scene (96.5% F=255),
so pay zero for it here.

FOG_ENABLE (default 1, byte-identical fog) added to gs_stub; each fog mux gated
(FOG_ENABLE && ras_fge) so FOG_ENABLE=0 constant-folds the entire fog cone away
(multiply, FOGCOL adders, s2_fog_f/persp_fog_f5 interp). Threaded through
top_psmct32_raster_demo_bram; set FOG_ENABLE(1'b0) on the active GS_SH3_LPDDR_FB
board arm (elsif at :1086, the one the QSF profile compiles). Fog stays default-on
everywhere else.

Verified: FOG_ENABLE=1 fog TBs unchanged PASS; FOG_ENABLE=0 (new tb_gs_fog_disabled
+ board scene TB) emits raw color = pre-fog datapath; board elaborates clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 21:31:14 -04:00
parent dd75d78491
commit a72aaded22
6 changed files with 178 additions and 7 deletions
+12 -2
View File
File diff suppressed because one or more lines are too long
+137
View File
@@ -0,0 +1,137 @@
// retroDE_ps2 — tb_gs_fog_disabled
//
// Proves the FOG_ENABLE=0 compile gate: with gs_stub #(.FOG_ENABLE(1'b0)),
// a PRIM.FGE=1 primitive carrying real per-vertex fog + a nonzero FOGCOL emits
// the RAW (unfogged) post-texture/Gouraud color. Because FOG_ENABLE is a param
// constant, `(FOG_ENABLE && ras_fge)` folds to 0 and each fog mux selects the
// raw color — the exact pre-fog datapath the synthesizer keeps (fog cone,
// including s2_fog_f, pruned). Functional equivalence to the raw color here is
// the observable proof that the fold target is the pre-fog path.
`timescale 1ns/1ps
module tb_gs_fog_disabled;
logic clk; logic rst_n;
initial clk = 1'b0; always #5 clk = ~clk;
logic gif_reg_wr_en; logic [7:0] gif_reg_num; logic [63:0] gif_reg_data;
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, 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 [3:0] raster_pixel_be_q;
logic [31:0] raster_pixel_mask_q; logic [5:0] raster_pixel_psm_q;
logic raster_active, raster_overflow, raster_fifo_full, raster_degenerate;
logic tex_rd_en; logic [31:0] tex_rd_addr;
logic fb_rd_en; logic [31:0] fb_rd_addr;
logic z_rd_en; logic [31:0] z_rd_addr;
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;
// Fog compiled OUT.
gs_stub #(.FOG_ENABLE(1'b0)) 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_be_q(raster_pixel_be_q),
.raster_pixel_mask_q(raster_pixel_mask_q), .raster_pixel_psm_q(raster_pixel_psm_q),
.raster_active(raster_active), .raster_overflow(raster_overflow),
.raster_fifo_full(raster_fifo_full), .raster_degenerate(raster_degenerate),
.tex_rd_en(tex_rd_en), .tex_rd_addr(tex_rd_addr), .tex_rd_data(32'd0),
.fb_rd_en(fb_rd_en), .fb_rd_addr(fb_rd_addr), .fb_rd_data(32'd0),
.z_rd_en(z_rd_en), .z_rd_addr(z_rd_addr), .z_rd_data(32'd0),
.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));
task automatic drive_reg(input logic [7:0] num, input logic [63:0] data);
@(negedge clk); gif_reg_wr_en=1'b1; gif_reg_num=num; gif_reg_data=data; @(posedge clk);
endtask
task automatic drive_idle();
@(negedge clk); gif_reg_wr_en=1'b0; gif_reg_num=8'd0; gif_reg_data=64'd0; @(posedge clk);
endtask
function automatic logic [63:0] xyzf2(input int x, input int y, input int f);
return {8'(f), 24'd0, 12'(y), 4'd0, 12'(x), 4'd0};
endfunction
function automatic logic [63:0] rgbaq(input int r, input int g, input int b, input int a);
return {32'd0, 8'(a), 8'(b), 8'(g), 8'(r)};
endfunction
localparam logic [7:0] R_PRIM=8'h00, R_RGBAQ=8'h01, R_XYZF2=8'h04, R_FOGCOL=8'h3D, R_FRAME_1=8'h4C;
localparam logic [63:0] PRIM_TRI_FGE = 64'd3 | (64'd1<<5);
localparam logic [63:0] FRAME_1_VAL = 64'h0000_0000_0001_0000;
localparam int TC_R=8'hC0, TC_G=8'h80, TC_B=8'h30, TC_A=8'hFF;
bit covered [0:15][0:15];
logic [31:0] cap_c [0:15][0:15];
bit cap_armed;
always_ff @(posedge clk) if (rst_n && cap_armed && raster_pixel_emit
&& raster_pixel_x_q < 16 && raster_pixel_y_q < 16) begin
covered[raster_pixel_y_q][raster_pixel_x_q] <= 1'b1;
cap_c [raster_pixel_y_q][raster_pixel_x_q] <= raster_pixel_color_q[31:0];
end
int errors, checks;
task automatic chk_raw(input int x, input int y);
// With FOG_ENABLE=0, even FGE=1 + FOGCOL!=0 must emit the RAW flat color.
if (!covered[y][x]) begin $error("[fogoff] (%0d,%0d) not covered", x, y); errors++; end
else if (cap_c[y][x][7:0]!==TC_R[7:0] || cap_c[y][x][15:8]!==TC_G[7:0] ||
cap_c[y][x][23:16]!==TC_B[7:0] || cap_c[y][x][31:24]!==TC_A[7:0]) begin
$error("[fogoff] (%0d,%0d) got %08x expected RAW (%0d,%0d,%0d,a=%0d) — fog NOT pruned",
x, y, cap_c[y][x], TC_R, TC_G, TC_B, TC_A); errors++;
end else begin
$display("[fogoff] (%0d,%0d) RAW color (fog pruned) OK", x, y); checks++;
end
endtask
initial begin
errors=0; checks=0; cap_armed=0;
for (int y=0;y<16;y++) for (int x=0;x<16;x++) begin covered[y][x]=0; cap_c[y][x]=0; end
rst_n=0; gif_reg_wr_en=0; gif_reg_num=0; gif_reg_data=0;
repeat(4) @(posedge clk); rst_n=1; repeat(2) @(posedge clk);
// FGE=1 triangle with DISTINCT per-vertex F and a NONZERO FOGCOL: if fog
// were active these would be visibly blended; with FOG_ENABLE=0 they are not.
cap_armed=1;
drive_reg(R_FOGCOL, 64'h00_60_40_20); // nonzero FOGCOL (would change pixels if fog on)
drive_reg(R_PRIM, PRIM_TRI_FGE);
drive_reg(R_FRAME_1, FRAME_1_VAL);
drive_reg(R_RGBAQ, rgbaq(TC_R,TC_G,TC_B,TC_A));
drive_reg(R_XYZF2, xyzf2(2,1,8'h10));
drive_reg(R_RGBAQ, rgbaq(TC_R,TC_G,TC_B,TC_A));
drive_reg(R_XYZF2, xyzf2(13,2,8'hF0));
drive_reg(R_RGBAQ, rgbaq(TC_R,TC_G,TC_B,TC_A));
drive_reg(R_XYZF2, xyzf2(5,7,8'h80)); // closes
drive_idle();
repeat(300) @(posedge clk); cap_armed=0; @(posedge clk);
chk_raw(7,3); chk_raw(5,4); chk_raw(6,2);
if (checks==0) begin $error("[fogoff] no interior pixels verified"); errors++; end
$display("[tb_gs_fog_disabled] checks=%0d errors=%0d", checks, errors);
if (errors==0) $display("[tb_gs_fog_disabled] PASS");
else $display("[tb_gs_fog_disabled] FAIL");
$finish;
end
initial begin #3000000; $error("[tb_gs_fog_disabled] timeout"); $finish; end
endmodule : tb_gs_fog_disabled
@@ -116,6 +116,11 @@ module tb_top_psmct32_sh3_zint640_shared;
.BIN_BUFFER_ENABLE(1'b0), .HEARTBEAT_SPLICE_ENABLE(1'b0),
.FEEDER_ENABLE(1'b1), .FEEDER_STG_WORDS(STG_WORDS), .FEEDER_AUTOSTART(1'b0),
.PERSPECTIVE_CORRECT(1'b1), .PERSP_RECIP_IDX_BITS(`SH3_SHARED_RECIP_BITS),
// Mirror the fitted board config: the GS_SH3_LPDDR_FB profile prunes
// per-vertex fog (FOG_ENABLE=0) to keep the texel*F multiply off the
// texture->color critical path. With fog pruned the scene must match the
// non-fog reference this scoreboard already checks.
.FOG_ENABLE(1'b0),
`ifdef SH3_SUBPIXEL_XY
.SUBPIXEL_XY(1'b1),
`endif