Ch444: serialize gradient numerator bank — reclaim ~half the FPGA's DSP

The per-triangle gradient engine already time-shared ONE divider across all
GRAD_STEPS attributes, but computed every grad_load_num[0:GRAD_STEPS-1] numerator
IN PARALLEL — ~44 wide multiplies (~100 physical DSP) for once-per-triangle setup
consumed one-at-a-time. Pure redundant hardware; the design was DSP-maxed
(187/188, 99%) so nothing new could fit (fog needed 191/188).

Replace the parallel bank + the grad_num_q[] pre-latch array with grad_num_step:
computes ONLY the current grad_step's numerator from ONE mux-selected pair of
signed multipliers (attribute triple by grad_step>>1, axis by grad_step[0]; shared
da1/da2, two shared products, signed subtract, <<<20). grad_word_q/grad_slot are
held stable the whole solve, so it is bit-identical to the old grad_num_q[grad_step].
Removed grad_num_dadx/dady (inlined once). FSM sequencing and throughput unchanged.

Width note: da1/da2 are 33-bit (products 50-bit), NOT operand-width 32-bit — the
original (a1-a0) lived in a signed-64-bit expression context and never wrapped;
full-32-bit Z with |a1-a0|>2^31 needs the wider intermediate. tb_gs_grad_num_equiv
(extreme signed corners + 200k random = 494770 checks, 0 errors) caught a 32-bit
first cut that f52's real data never exercised.

Resource (26.1 Seed-3 fit): DSP needed 168->83 / final placement 187->119, i.e.
99% -> 44%, ~85 blocks reclaimed (Codex gate >=70 met). ALM 40458->38784 (86->83%).
RAM 322/358 unchanged. Timing CLEAN: setup +0.077, all classes >=0, 0 violated.

Verification: tb_gs_grad_num_equiv 0/494770; f52 replay BYTE-IDENTICAL golden
d0047677 (drops=0, occupancy unchanged); gradient/perspective/texture regressions
(tri_interp, grad_divider, persp_uv, zbuffer, fog_persp, textured_triangle,
triangle/perspective/combined/gouraud demos) all PASS. Byte-identical => the
screen is unchanged; this is the resource unlock for fog + coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-25 05:28:12 -04:00
parent 7d68577c75
commit 4e8c3a16b8
3 changed files with 201 additions and 99 deletions
+85 -99
View File
@@ -2582,39 +2582,10 @@ module gs_stub
// truncation). Numerator width unchanged (signed 56-bit: see the
// SYNTHESIS-WIDTH NOTE above for the bound derivation; the <<<16 is
// applied here in the 56-bit numerator domain exactly as before).
function automatic logic signed [63:0] grad_num_dadx(
input logic signed [31:0] a0,
input logic signed [31:0] a1,
input logic signed [31:0] a2,
input logic [15:0] x0, input logic [15:0] y0,
input logic [15:0] x1, input logic [15:0] y1,
input logic [15:0] x2, input logic [15:0] y2);
logic signed [16:0] dy1, dy2;
logic signed [63:0] num;
dy1 = $signed({1'b0, y1}) - $signed({1'b0, y0});
dy2 = $signed({1'b0, y2}) - $signed({1'b0, y0});
num = (a1 - a0) * $signed({{47{dy2[16]}}, dy2})
- (a2 - a0) * $signed({{47{dy1[16]}}, dy1});
// Coordinates are 12.4. Convert dA/d(X/16) to Q16.16:
// raw numerator * 16 * 2^16 / det = numerator << 20 / det.
grad_num_dadx = num <<< 20;
endfunction
function automatic logic signed [63:0] grad_num_dady(
input logic signed [31:0] a0,
input logic signed [31:0] a1,
input logic signed [31:0] a2,
input logic [15:0] x0, input logic [15:0] y0,
input logic [15:0] x1, input logic [15:0] y1,
input logic [15:0] x2, input logic [15:0] y2);
logic signed [16:0] dx1, dx2;
logic signed [63:0] num;
dx1 = $signed({1'b0, x1}) - $signed({1'b0, x0});
dx2 = $signed({1'b0, x2}) - $signed({1'b0, x0});
num = (a2 - a0) * $signed({{47{dx1[16]}}, dx1})
- (a1 - a0) * $signed({{47{dx2[16]}}, dx2});
grad_num_dady = num <<< 20;
endfunction
// Ch444 — grad_num_dadx/grad_num_dady REMOVED. Their exact arithmetic (signed
// (a1-a0),(a2-a0) times the y/x vertex deltas, subtract, <<<20) is now inlined
// ONCE in grad_num_step's shared multiplier pair (operands mux-selected by axis)
// instead of being instantiated GRAD_STEPS times in parallel.
// Ch87 — raster command FIFO. Holds primitive contexts captured
// at close cycles while the FSM is busy. Effective concurrency =
@@ -3046,7 +3017,8 @@ module gs_stub
logic grad_writing; // Ch352 — write phase: grad_result_q -> fifo (single-cycle)
logic [FIFO_PTR_W-1:0] grad_slot;
logic signed [63:0] grad_det_q;
logic signed [63:0] grad_num_q [0:GRAD_STEPS-1];
// Ch444 — grad_num_q[] (the pre-latched 22-wide numerator array) REMOVED; the
// current step's numerator is computed on demand by grad_num_step.
// Ch357 (Codex) — the SOLE registered numerator feeding the combinational divider on the BOARD path
// (GRAD_SEQ_DIVIDER=0 && GRAD_DIV_CYCLES!=1). Registering grad_num_q[grad_step] here removes the 20-way dynamic
// numerator mux from the divide's launch cone: the multicycle -from set shrinks from ~1157 regs
@@ -3078,7 +3050,8 @@ module gs_stub
// by construction). Only ONE slot's worth is computed at a time (mux
// by grad_pending_slot), so this is one set of the existing functions,
// not FIFO_DEPTH copies — no new wide datapath.
logic signed [63:0] grad_load_num [0:GRAD_STEPS-1];
// Ch444 — grad_load_num[] (the parallel combinational numerator bank, ~44 wide
// multiplies) REMOVED; grad_num_step computes one numerator at a time.
logic signed [63:0] grad_load_det;
logic [63:0] grad_ld_c0, grad_ld_c1, grad_ld_c2;
logic [11:0] grad_ld_v0x, grad_ld_v0y, grad_ld_v1x, grad_ld_v1y, grad_ld_v2x, grad_ld_v2y;
@@ -3169,49 +3142,10 @@ module gs_stub
grad_ld_u2 = gw_u2v [10:0];
grad_ld_v2u = gw_u2v [26:16];
grad_load_det = $signed({{29{fifo_grad_det[grad_slot][34]}}, fifo_grad_det[grad_slot]});
grad_load_num[0] = grad_num_dadx({24'd0, grad_ld_c0[7:0]}, {24'd0, grad_ld_c1[7:0]}, {24'd0, grad_ld_c2[7:0]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[1] = grad_num_dady({24'd0, grad_ld_c0[7:0]}, {24'd0, grad_ld_c1[7:0]}, {24'd0, grad_ld_c2[7:0]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[2] = grad_num_dadx({24'd0, grad_ld_c0[15:8]}, {24'd0, grad_ld_c1[15:8]}, {24'd0, grad_ld_c2[15:8]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[3] = grad_num_dady({24'd0, grad_ld_c0[15:8]}, {24'd0, grad_ld_c1[15:8]}, {24'd0, grad_ld_c2[15:8]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[4] = grad_num_dadx({24'd0, grad_ld_c0[23:16]}, {24'd0, grad_ld_c1[23:16]}, {24'd0, grad_ld_c2[23:16]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[5] = grad_num_dady({24'd0, grad_ld_c0[23:16]}, {24'd0, grad_ld_c1[23:16]}, {24'd0, grad_ld_c2[23:16]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[6] = grad_num_dadx({24'd0, grad_ld_c0[31:24]}, {24'd0, grad_ld_c1[31:24]}, {24'd0, grad_ld_c2[31:24]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[7] = grad_num_dady({24'd0, grad_ld_c0[31:24]}, {24'd0, grad_ld_c1[31:24]}, {24'd0, grad_ld_c2[31:24]},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[8] = grad_num_dadx(grad_ld_z0, grad_ld_z1, grad_ld_z2,
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[9] = grad_num_dady(grad_ld_z0, grad_ld_z1, grad_ld_z2,
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
// Textured-triangle rung — U (steps 10/11) and V (steps 12/13).
// U/V are 11-bit unsigned integer texel coords; zero-extend to
// the signed[31:0] attribute input the existing numerator
// functions take. Same shared divisor (grad_load_det) → same
// affine plane solve as the colour/Z attributes.
grad_load_num[10] = grad_num_dadx({21'd0, grad_ld_u0}, {21'd0, grad_ld_u1}, {21'd0, grad_ld_u2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[11] = grad_num_dady({21'd0, grad_ld_u0}, {21'd0, grad_ld_u1}, {21'd0, grad_ld_u2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[12] = grad_num_dadx({21'd0, grad_ld_v0u}, {21'd0, grad_ld_v1u}, {21'd0, grad_ld_v2u},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[13] = grad_num_dady({21'd0, grad_ld_v0u}, {21'd0, grad_ld_v1u}, {21'd0, grad_ld_v2u},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
// GS per-vertex FOG — F (steps GRAD_F_DX/GRAD_F_DY). 8-bit fog coeff
// zero-extended to the signed[31:0] attribute input the numerator
// functions take. Same shared divisor (grad_load_det) → same affine
// plane solve as colour/Z. These indices are ALWAYS the last two of
// grad_load_num[0:GRAD_STEPS-1] regardless of the perspective param
// (the persp 14..19 block never overlaps them).
grad_load_num[GRAD_F_DX] = grad_num_dadx({24'd0, grad_ld_f0}, {24'd0, grad_ld_f1}, {24'd0, grad_ld_f2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[GRAD_F_DY] = grad_num_dady({24'd0, grad_ld_f0}, {24'd0, grad_ld_f1}, {24'd0, grad_ld_f2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
// Ch444 — the parallel grad_load_num[0:GRAD_STEPS-1] numerator bank is GONE.
// The current step's numerator is computed on-demand by grad_num_step (one
// shared multiplier pair) from these same grad_ld_* fields. grad_load_det
// (the shared divisor) still latches here into grad_det_q.
end
// Ch301 perspective — per-vertex S/T/Q load + the 6 extra gradient
@@ -3236,21 +3170,8 @@ module gs_stub
grad_ld_s2 = gw_stq2[23:0];
grad_ld_t2 = gw_stq2[47:24];
grad_ld_q2 = gw_q2;
// S (steps 14/15)
grad_load_num[14] = grad_num_dadx({8'd0, grad_ld_s0}, {8'd0, grad_ld_s1}, {8'd0, grad_ld_s2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[15] = grad_num_dady({8'd0, grad_ld_s0}, {8'd0, grad_ld_s1}, {8'd0, grad_ld_s2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
// T (steps 16/17)
grad_load_num[16] = grad_num_dadx({8'd0, grad_ld_t0}, {8'd0, grad_ld_t1}, {8'd0, grad_ld_t2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[17] = grad_num_dady({8'd0, grad_ld_t0}, {8'd0, grad_ld_t1}, {8'd0, grad_ld_t2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
// Q (steps 18/19)
grad_load_num[18] = grad_num_dadx({8'd0, grad_ld_q0}, {8'd0, grad_ld_q1}, {8'd0, grad_ld_q2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
grad_load_num[19] = grad_num_dady({8'd0, grad_ld_q0}, {8'd0, grad_ld_q1}, {8'd0, grad_ld_q2},
grad_ld_v0x4, grad_ld_v0y4, grad_ld_v1x4, grad_ld_v1y4, grad_ld_v2x4, grad_ld_v2y4);
// Ch444 — S/T/Q numerators (steps 14..19) now computed on-demand by
// grad_num_step from these grad_ld_s/t/q fields (parallel bank removed).
end
end else begin : g_no_grad_stq
// Tie the S/T/Q load wires low so they have a single driver
@@ -3261,6 +3182,72 @@ module gs_stub
end
endgenerate
// ============================================================================
// Ch444 (Codex) — SERIALIZED gradient numerator (DSP resource collapse).
// The per-triangle divider is already time-shared across all GRAD_STEPS
// attributes, but the numerator bank was NOT: grad_load_num[0:GRAD_STEPS-1]
// instantiated every attribute's two wide multiplies IN PARALLEL (~44 mults ->
// ~100 physical DSP) only to be consumed one-at-a-time by the shared divider.
// Compute ONLY the current grad_step's numerator from ONE mux-selected pair of
// signed multipliers. grad_word_q + grad_slot are held stable for the whole
// solve, so grad_num_step for grad_step is BIT-IDENTICAL to the old
// grad_num_q[grad_step] (same attribute triple a0/a1/a2, same axis, same signed
// subtract, same <<<20). Step->attribute map mirrors the old bank exactly:
// pair = grad_step>>1 : 0=R 1=G 2=B 3=A 4=Z 5=U 6=V [persp 7=S 8=T 9=Q] F=GRAD_F_DX>>1
// axis = grad_step[0] : 0=d/dx 1=d/dy
// ============================================================================
logic signed [31:0] gnum_a0, gnum_a1, gnum_a2;
always_comb begin
gnum_a0 = 32'sd0; gnum_a1 = 32'sd0; gnum_a2 = 32'sd0;
case (grad_step[4:1])
4'd0: begin gnum_a0={24'd0,grad_ld_c0[7:0]}; gnum_a1={24'd0,grad_ld_c1[7:0]}; gnum_a2={24'd0,grad_ld_c2[7:0]}; end
4'd1: begin gnum_a0={24'd0,grad_ld_c0[15:8]}; gnum_a1={24'd0,grad_ld_c1[15:8]}; gnum_a2={24'd0,grad_ld_c2[15:8]}; end
4'd2: begin gnum_a0={24'd0,grad_ld_c0[23:16]}; gnum_a1={24'd0,grad_ld_c1[23:16]}; gnum_a2={24'd0,grad_ld_c2[23:16]}; end
4'd3: begin gnum_a0={24'd0,grad_ld_c0[31:24]}; gnum_a1={24'd0,grad_ld_c1[31:24]}; gnum_a2={24'd0,grad_ld_c2[31:24]}; end
4'd4: begin gnum_a0=grad_ld_z0; gnum_a1=grad_ld_z1; gnum_a2=grad_ld_z2; end
4'd5: begin gnum_a0={21'd0,grad_ld_u0}; gnum_a1={21'd0,grad_ld_u1}; gnum_a2={21'd0,grad_ld_u2}; end
4'd6: begin gnum_a0={21'd0,grad_ld_v0u}; gnum_a1={21'd0,grad_ld_v1u}; gnum_a2={21'd0,grad_ld_v2u}; end
default: begin
if (PERSPECTIVE_CORRECT && grad_step[4:1]==4'd7) begin gnum_a0={8'd0,grad_ld_s0}; gnum_a1={8'd0,grad_ld_s1}; gnum_a2={8'd0,grad_ld_s2}; end
else if (PERSPECTIVE_CORRECT && grad_step[4:1]==4'd8) begin gnum_a0={8'd0,grad_ld_t0}; gnum_a1={8'd0,grad_ld_t1}; gnum_a2={8'd0,grad_ld_t2}; end
else if (PERSPECTIVE_CORRECT && grad_step[4:1]==4'd9) begin gnum_a0={8'd0,grad_ld_q0}; gnum_a1={8'd0,grad_ld_q1}; gnum_a2={8'd0,grad_ld_q2}; end
else if (grad_step[4:1]==4'(GRAD_F_DX>>1)) begin gnum_a0={24'd0,grad_ld_f0}; gnum_a1={24'd0,grad_ld_f1}; gnum_a2={24'd0,grad_ld_f2}; end
end
endcase
end
// ONE shared pair of signed multipliers (33 x 17), operands mux-selected by axis.
// NOTE: gnum_da1/da2 are 33-bit, NOT 32-bit. The original grad_num_dadx/dady
// evaluated (a1-a0) inside a signed 64-bit expression context, so the attribute
// difference never wrapped at 32 bits (matters only for full-32-bit Z whose
// a1-a0 can exceed +/-2^31). A 33-bit difference reproduces that exactly; a
// 32-bit one wraps and diverges on extreme Z (caught by tb_gs_grad_num_equiv).
logic signed [16:0] gnum_dy1, gnum_dy2, gnum_dx1, gnum_dx2;
logic signed [32:0] gnum_da1, gnum_da2;
logic signed [32:0] gnum_mA_p, gnum_mA_n;
logic signed [16:0] gnum_mB_p, gnum_mB_n;
logic signed [49:0] gnum_prodP, gnum_prodN;
logic signed [63:0] gnum_raw;
logic signed [63:0] grad_num_step;
always_comb begin
gnum_dy1 = $signed({1'b0, grad_ld_v1y4}) - $signed({1'b0, grad_ld_v0y4});
gnum_dy2 = $signed({1'b0, grad_ld_v2y4}) - $signed({1'b0, grad_ld_v0y4});
gnum_dx1 = $signed({1'b0, grad_ld_v1x4}) - $signed({1'b0, grad_ld_v0x4});
gnum_dx2 = $signed({1'b0, grad_ld_v2x4}) - $signed({1'b0, grad_ld_v0x4});
gnum_da1 = $signed(gnum_a1) - $signed(gnum_a0); // 33-bit, no 32-bit wrap
gnum_da2 = $signed(gnum_a2) - $signed(gnum_a0);
if (!grad_step[0]) begin // d/dx: num = da1*dy2 - da2*dy1
gnum_mA_p = gnum_da1; gnum_mB_p = gnum_dy2;
gnum_mA_n = gnum_da2; gnum_mB_n = gnum_dy1;
end else begin // d/dy: num = da2*dx1 - da1*dx2
gnum_mA_p = gnum_da2; gnum_mB_p = gnum_dx1;
gnum_mA_n = gnum_da1; gnum_mB_n = gnum_dx2;
end
gnum_prodP = gnum_mA_p * gnum_mB_p; // shared multiplier #1
gnum_prodN = gnum_mA_n * gnum_mB_n; // shared multiplier #2
gnum_raw = $signed(gnum_prodP) - $signed(gnum_prodN);
grad_num_step = gnum_raw <<< 20; // exact: matches num<<<20 in grad_num_dadx/dady
end
// Combinational divide of the currently-selected numerator by the
// captured determinant — the SINGLE divider in the triangle setup
// path. Mirrors the previous per-call expression exactly:
@@ -3278,7 +3265,7 @@ module gs_stub
if (!GRAD_SEQ_DIVIDER) begin : g_combdiv
// Numerator source: GRAD_DIV_CYCLES!=1 uses the REGISTERED grad_num_sel_q (20-way mux captured a cycle
// earlier -> tiny divide cone); GRAD_DIV_CYCLES==1 legacy sim reads grad_num_q[grad_step] BYTE-IDENTICAL.
wire signed [63:0] grad_div_num = (GRAD_DIV_CYCLES != 1) ? grad_num_sel_q : grad_num_q[grad_step];
wire signed [63:0] grad_div_num = (GRAD_DIV_CYCLES != 1) ? grad_num_sel_q : grad_num_step;
// Ch417b — keep Quartus' inferred lpm_divide numerator at its
// supported maximum of exactly 64 bits. A signed 64/64 `/`
// causes Quartus to prepend a sign-magnitude bit and request
@@ -3324,7 +3311,7 @@ module gs_stub
if (GRAD_SEQ_DIVIDER) begin : g_seqdiv
gs_grad_divider #(.W(64)) u_grad_div (
.clk(clk), .rst_n(rst_n), .start(div_start),
.num(grad_num_q[grad_step]),
.num(grad_num_step),
.den(grad_det_q),
.quo(div_quo), .busy(div_busy_w), .done(div_done_w)
);
@@ -6218,7 +6205,6 @@ module gs_stub
div_start <= 1'b0;
grad_slot <= '0;
grad_det_q <= 64'sd0;
for (int g = 0; g < GRAD_STEPS; g = g + 1) grad_num_q[g] <= 64'sd0;
for (int i = 0; i < FIFO_DEPTH; i = i + 1) begin
fifo_v1z [i] <= 32'd0;
fifo_v2z [i] <= 32'd0;
@@ -6707,7 +6693,7 @@ module gs_stub
// divide computing); at CYCLES = capture the settled quotient. grad_step advances only in the write
// phase below, so grad_num_q[grad_step] is stable when it is captured here.
if (grad_settle == 5'd0) begin
grad_num_sel_q <= grad_num_q[grad_step]; // capture the 20-way mux output out of the divide cone
grad_num_sel_q <= grad_num_step; // Ch444 — serialized on-demand numerator (was grad_num_q[grad_step])
grad_settle <= 5'd1;
end else if (grad_settle != 5'(GRAD_DIV_CYCLES)) begin
grad_settle <= grad_settle + 5'd1; // hold grad_num_sel_q + grad_det_q stable
@@ -6775,8 +6761,8 @@ module gs_stub
grad_writing <= 1'b0;
if (GRAD_SEQ_DIVIDER) div_start <= 1'b1; // kick the first step's sequential divide
grad_det_q <= grad_load_det;
for (int g = 0; g < GRAD_STEPS; g = g + 1)
grad_num_q[g] <= grad_load_num[g];
// Ch444 — no numerator bank to latch; grad_num_step computes each
// step's numerator on demand from the (stable) grad_word_q.
end else if (grad_prefetching) begin
// Ch328 1c — stage 2: the single read port issued attr_ram[grad_slot] last cycle, so
// attr_rd_q is valid now → capture it into grad_word_q.
+10
View File
@@ -1182,6 +1182,15 @@ tb_gs_grad_divider: dirs
@echo "=== run tb_gs_grad_divider ==="
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_grad_divider.vvp
tb_gs_grad_num_equiv: dirs
@echo "=== build tb_gs_grad_num_equiv ==="
$(IVERILOG) $(IVERILOG_FLGS) \
-o $(BUILD_DIR)/tb_gs_grad_num_equiv.vvp \
-s tb_gs_grad_num_equiv \
$(TB_ROOT)/gif_gs/tb_gs_grad_num_equiv.sv
@echo "=== run tb_gs_grad_num_equiv ==="
@cd $(TRACE_DIR) && $(VVP) $(BUILD_DIR)/tb_gs_grad_num_equiv.vvp
tb_gs_lpddr_scanout_fb: dirs
@echo "=== build tb_gs_lpddr_scanout_fb ==="
$(IVERILOG) $(IVERILOG_FLGS) \
@@ -6331,6 +6340,7 @@ run: tb_top_psmct32_sh3_zs640b24_cap tb_top_psmct32_sh3_zint640b24
.PHONY: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c sh3_zs640c24c_fixture sh3_zs640motionabc_bootlet
run: tb_top_psmct32_sh3_zs640c24c_cap tb_top_psmct32_sh3_zint640c24c tb_gs_axi_w_regbuf tb_gs_axi_aw_regbuf tb_gs_axi_r_regbuf
run: tb_gs_lpddr_scanout_lb tb_gs_scanout_binomial_lookahead tb_gs_scanout_diag tb_gs_scanout_restart tb_gs_scanout_cdc_qual
run: tb_gs_grad_num_equiv
run: tb_ee_fetch tb_gs tb_intc tb_platform_video tb_bgcolor_via_dma tb_sif_mailbox \
tb_sif_command_echo tb_sif_command_echo_rearm tb_sif_negative_path \
+106
View File
@@ -0,0 +1,106 @@
// ============================================================================
// tb_gs_grad_num_equiv — Ch444
//
// Focused equivalence proof for the SERIALIZED gradient numerator (gs_stub
// grad_num_step). The old design instantiated grad_num_dadx/grad_num_dady for
// every attribute in parallel; Ch444 replaced that bank with ONE mux-selected
// pair of 32x17 multipliers. This TB proves the new inlined arithmetic is
// BIT-IDENTICAL to the original functions for BOTH axes, across every attribute
// operand width AND extreme signed coordinate / overflow-boundary cases that the
// f52 real-data assertion may never exercise:
// - GOLDEN = the exact original grad_num_dadx/dady (verbatim, pre-Ch444).
// - DUT = the exact grad_num_step arithmetic (shared da1/da2 + axis-muxed
// 32x17 products, signed subtract, <<<20).
// Sweeps attributes over {0, 8/11/24/32-bit maxima, sign boundaries, random} and
// vertex coords over {0, 0xFFFF, 0x8000, boundary, random}. Any mismatch fails.
`timescale 1ns/1ps
module tb_gs_grad_num_equiv;
int errors = 0, checks = 0;
// ---- GOLDEN: the original functions, copied verbatim from pre-Ch444 gs_stub ----
function automatic logic signed [63:0] gold_dadx(
input logic signed [31:0] a0, a1, a2,
input logic [15:0] x0,y0,x1,y1,x2,y2);
logic signed [16:0] dy1, dy2; logic signed [63:0] num;
dy1 = $signed({1'b0, y1}) - $signed({1'b0, y0});
dy2 = $signed({1'b0, y2}) - $signed({1'b0, y0});
num = (a1 - a0) * $signed({{47{dy2[16]}}, dy2})
- (a2 - a0) * $signed({{47{dy1[16]}}, dy1});
gold_dadx = num <<< 20;
endfunction
function automatic logic signed [63:0] gold_dady(
input logic signed [31:0] a0, a1, a2,
input logic [15:0] x0,y0,x1,y1,x2,y2);
logic signed [16:0] dx1, dx2; logic signed [63:0] num;
dx1 = $signed({1'b0, x1}) - $signed({1'b0, x0});
dx2 = $signed({1'b0, x2}) - $signed({1'b0, x0});
num = (a2 - a0) * $signed({{47{dx1[16]}}, dx1})
- (a1 - a0) * $signed({{47{dx2[16]}}, dx2});
gold_dady = num <<< 20;
endfunction
// ---- DUT: the exact grad_num_step arithmetic (shared 32x17 pair, axis mux) ----
function automatic logic signed [63:0] dut_step(
input logic axis, // 0 = d/dx, 1 = d/dy
input logic signed [31:0] a0, a1, a2,
input logic [15:0] x0,y0,x1,y1,x2,y2);
logic signed [16:0] dy1,dy2,dx1,dx2;
logic signed [32:0] da1, da2;
logic signed [32:0] mA_p, mA_n; logic signed [16:0] mB_p, mB_n;
logic signed [49:0] prodP, prodN; logic signed [63:0] raw;
dy1 = $signed({1'b0,y1}) - $signed({1'b0,y0});
dy2 = $signed({1'b0,y2}) - $signed({1'b0,y0});
dx1 = $signed({1'b0,x1}) - $signed({1'b0,x0});
dx2 = $signed({1'b0,x2}) - $signed({1'b0,x0});
da1 = $signed(a1) - $signed(a0); da2 = $signed(a2) - $signed(a0);
if (!axis) begin mA_p=da1; mB_p=dy2; mA_n=da2; mB_n=dy1; end
else begin mA_p=da2; mB_p=dx1; mA_n=da1; mB_n=dx2; end
prodP = mA_p * mB_p; prodN = mA_n * mB_n;
raw = $signed(prodP) - $signed(prodN);
dut_step = raw <<< 20;
endfunction
task automatic chk(input logic signed [31:0] a0,a1,a2,
input logic [15:0] x0,y0,x1,y1,x2,y2);
logic signed [63:0] gx, gy, dx, dy;
gx = gold_dadx(a0,a1,a2,x0,y0,x1,y1,x2,y2);
gy = gold_dady(a0,a1,a2,x0,y0,x1,y1,x2,y2);
dx = dut_step(1'b0, a0,a1,a2,x0,y0,x1,y1,x2,y2);
dy = dut_step(1'b1, a0,a1,a2,x0,y0,x1,y1,x2,y2);
checks += 2;
if (dx !== gx) begin errors++; if (errors<20) $display("[equiv] DADX MISMATCH a=%h,%h,%h xy=%h,%h/%h,%h/%h,%h dut=%h gold=%h",a0,a1,a2,x0,y0,x1,y1,x2,y2,dx,gx); end
if (dy !== gy) begin errors++; if (errors<20) $display("[equiv] DADY MISMATCH a=%h,%h,%h xy=%h,%h/%h,%h/%h,%h dut=%h gold=%h",a0,a1,a2,x0,y0,x1,y1,x2,y2,dy,gy); end
endtask
// attribute corner values spanning every operand width (color8/uv11/stq24/z32)
logic signed [31:0] AV [];
logic [15:0] CV [];
initial begin
AV = new[9];
AV[0]=32'h00000000; AV[1]=32'h000000FF; AV[2]=32'h000007FF; AV[3]=32'h00FFFFFF;
AV[4]=32'h7FFFFFFF; AV[5]=32'h80000000; AV[6]=32'hFFFFFFFF; AV[7]=32'h00800000; AV[8]=32'h12345678;
CV = new[6];
CV[0]=16'h0000; CV[1]=16'hFFFF; CV[2]=16'h8000; CV[3]=16'h7FFF; CV[4]=16'h0001; CV[5]=16'hA5A5;
// 1) exhaustive over attribute corners with a fixed non-degenerate triangle
foreach (AV[i]) foreach (AV[j]) foreach (AV[k])
chk(AV[i],AV[j],AV[k], 16'h0100,16'h0080, 16'h0900,16'h0110, 16'h0300,16'h0A00);
// 2) exhaustive over coordinate corners with fixed spread attributes
foreach (CV[a]) foreach (CV[b]) foreach (CV[c]) foreach (CV[d]) foreach (CV[e]) foreach (CV[f])
chk(32'h00000000, 32'h00ABCDEF, 32'h7F123456, CV[a],CV[b],CV[c],CV[d],CV[e],CV[f]);
// 3) randomized wide sweep (attributes + coords), catches any residual case
for (int n=0; n<200000; n++) begin
logic signed [31:0] ra0,ra1,ra2;
ra0=$random; ra1=$random; ra2=$random;
chk(ra0,ra1,ra2, $random,$random,$random,$random,$random,$random);
end
$display("[equiv] checks=%0d errors=%0d", checks, errors);
if (errors==0) $display("[tb_gs_grad_num_equiv] PASS");
else $display("[tb_gs_grad_num_equiv] FAIL");
$finish;
end
endmodule