Check thresholds for floating-point comparisons
Given those constraints the function collapses to a single comparison: check whether max_val - data <= threshold. Since all three are positive fp16 and max_val ≥ data is guaranteed, the subtraction never needs sign handling, borrow recovery, or NaN/Inf guards. The full pipeline still applies for the subtraction itself though.The preconditions let us eliminate a significant amount of logic compared to the general case:
What we dropped vs the general fp16_abs_diff:
- No sign-bit handling — sign bit is always 0, so
a_mag = adirectly. - No swap logic —
max_val ≥ datais guaranteed, sohi/loassignment is fixed. - No NaN/Inf guards — caller guarantees finite positive inputs.
- No borrow recovery — subtraction of non-negative aligned values never borrows.
- The final comparison skips the sign-strip step — both the computed difference and
thresholdare positive fp16, sopacked <= thresholdon their raw 16-bit unsigned values is numerically exact.
What remains is the irreducible minimum for any fp16 subtraction: exponent unpack, mantissa alignment via barrel shift with guard/round/sticky capture, subtract, leading-zero normalisation, round-to-nearest-even, repack, and one unsigned compare. The function is fully combinational and maps cleanly to a barrel shifter, a subtractor, a priority encoder (for leading-zero count), and a comparator — all standard synthesis primitives.
The one special case still worth handling explicitly is diff == 0 (when max_val == data exactly). Rather than letting it fall through the normalisation path and possibly produce a denormalised zero, it returns 1 immediately — 0.0 ≤ any positive threshold is always true.
// =============================================================================
// chk_meets_threshold.sv
//
// Synthesisable function:
// chk_meets_threshold(max_val, data, threshold)
//
// Returns 1 iff (max_val - data) <= threshold.
//
// Preconditions (caller's responsibility — no defensive logic needed):
// * All three inputs are positive finite fp16 (sign bit = 0, exp != 0x1F).
// * max_val >= data (subtraction result is always non-negative).
//
// Because every value is positive, the fp16 bit patterns are already
// monotonically ordered as plain unsigned integers (the property established
// earlier). This lets us:
// - Compare magnitudes with a simple unsigned >=/<=.
// - Skip all sign, NaN, Inf, and -0 handling entirely.
//
// Subtraction pipeline: max_val - data
// -------------------------------------
// 1. Unpack both operands into (exponent, full-mantissa).
// Full mantissa = {implicit_1, explicit_10_bits} = 11 bits.
// Subnormals use implicit_0 and effective exponent 1.
// 2. Align: right-shift the smaller (data) mantissa by exp_diff,
// collecting guard / round / sticky bits.
// 3. Subtract aligned mantissas. Result is non-negative by precondition.
// 4. Normalise: count leading zeros, left-shift, adjust exponent.
// If exponent underflows to 0 or below, produce a subnormal.
// 5. Round to nearest-even using guard/round/sticky.
// 6. Pack result into fp16.
// 7. Compare packed result <= threshold using unsigned bit comparison.
// =============================================================================
`timescale 1ns/1ps
`default_nettype none
package chk_thresh_pkg;
function automatic logic chk_meets_threshold(
input logic [15:0] max_val, // positive fp16, >= data
input logic [15:0] data, // positive fp16
input logic [15:0] threshold // positive fp16
);
// ------------------------------------------------------------------
// 1. Unpack
// ------------------------------------------------------------------
logic [4:0] hi_exp_raw, lo_exp_raw;
logic [10:0] hi_full, lo_full; // {implicit_bit, 10-bit mantissa}
logic [4:0] hi_exp, lo_exp; // effective exponents (subnormal → 1)
// max_val is the minuend (hi), data is the subtrahend (lo).
// Precondition guarantees max_val >= data, so no swap needed.
hi_exp_raw = max_val[14:10];
lo_exp_raw = data [14:10];
hi_full = {(hi_exp_raw != 5'h00), max_val[9:0]}; // implicit bit
lo_full = {(lo_exp_raw != 5'h00), data [9:0]};
hi_exp = (hi_exp_raw == 5'h00) ? 5'd1 : hi_exp_raw;
lo_exp = (lo_exp_raw == 5'h00) ? 5'd1 : lo_exp_raw;
// ------------------------------------------------------------------
// 2. Align lo to hi by right-shifting lo by exp_diff
// Capture guard (G), round (R), sticky (S) bits for rounding.
// ------------------------------------------------------------------
logic [4:0] exp_diff;
logic [10:0] lo_shifted;
logic G, R;
logic S; // sticky = OR of all bits shifted past R
exp_diff = hi_exp - lo_exp; // >= 0 by precondition
lo_shifted = lo_full;
G = 1'b0; R = 1'b0; S = 1'b0;
// Unroll shift: synthesis tools handle this as a barrel shifter.
// We shift up to 11 positions; beyond that lo is entirely sticky.
if (exp_diff >= 12) begin
S = |lo_full;
lo_shifted = '0;
end else begin
for (int i = 0; i < 11; i++) begin
if (5'(i) < exp_diff) begin
S = R | S; // old R falls into sticky
R = G; // old G falls into round
G = lo_shifted[0]; // LSB becomes new guard
lo_shifted = {1'b0, lo_shifted[10:1]};
end
end
end
// ------------------------------------------------------------------
// 3. Subtract aligned mantissas
// Result is non-negative (precondition: max_val >= data).
// ------------------------------------------------------------------
logic [11:0] diff; // 12 bits to safely hold 11-bit subtraction
diff = {1'b0, hi_full} - {1'b0, lo_shifted};
// ------------------------------------------------------------------
// 4. Exact zero check
// ------------------------------------------------------------------
if (diff == '0 && G == 1'b0) begin
// max_val == data exactly; difference is 0.0
// 0.0 <= any positive threshold → always true.
return 1'b1;
end
// ------------------------------------------------------------------
// 5. Normalise
// Find the position of the leading 1 in diff[10:0] and shift left
// to place it at bit 10 (the implicit-bit position for normals).
// ------------------------------------------------------------------
logic [4:0] lz; // leading zeros in diff[10:0]
logic [10:0] diff11;
logic [4:0] res_exp;
logic [9:0] res_mant;
diff11 = diff[10:0];
lz = '0;
begin
logic found;
found = 1'b0;
for (int i = 10; i >= 0; i--) begin
if (!found) begin
if (diff11[i]) found = 1'b1;
else lz = lz + 1'b1;
end
end
end
if (hi_exp <= lz) begin
// ---- Subnormal result ----
// Shift so that the leading 1 sits at or below bit 10 with exp = 0.
logic [4:0] sub_shift;
sub_shift = hi_exp; // shift that drains the exponent to 0
diff11 = diff11 << sub_shift;
res_exp = 5'h00;
res_mant = diff11[9:0];
// Rounding bits after subnormal shift (simplified: absorb into sticky)
G = diff11[0]; // bit just below the kept mantissa
// R and S unchanged
end else begin
// ---- Normal result ----
diff11 = diff11 << lz;
res_exp = hi_exp - lz; // guaranteed >= 1
res_mant = diff11[9:0]; // top bit (implicit 1) discarded
end
// ------------------------------------------------------------------
// 6. Round to nearest-even
// Increment res_mant (and propagate carry into res_exp) if needed.
// ------------------------------------------------------------------
logic rne_inc;
rne_inc = G & (R | S | res_mant[0]);
logic [15:0] packed;
packed = {1'b0, res_exp, res_mant} + {15'b0, rne_inc};
// Rounding can push exp to 0x1F (overflow to Inf); in that case the
// difference genuinely exceeds any finite fp16, so return 0.
if (packed[14:10] == 5'h1F) return 1'b0;
// ------------------------------------------------------------------
// 7. Compare: packed_diff <= threshold
// Both are positive fp16 → unsigned bit-pattern comparison is exact.
// ------------------------------------------------------------------
return (packed <= threshold);
endfunction : chk_meets_threshold
endpackage : chk_thresh_pkg