Skip to main content

logicaffeine_compile/codegen_sva/
fp_hypotheses.rs

1//! Binary32 multiply and add under a family of semantics: the hypotheses a hard floating-point block is told apart by.
2//!
3//! IEEE 754 fixes round-to-nearest-even and leaves an implementation the rest: whether a denormal operand is read as
4//! itself or as a zero, whether a result below the normals is kept as a denormal or flushed to zero, where tininess
5//! is decided (on the exact result, after rounding to the normals' precision with the exponent unbounded, or, as the
6//! engine's numerics do, after rounding at the denormals' precision), the sign a flushed zero carries, and the NaN an
7//! operation returns. A [`Semantics`] names one choice of each; [`fmul_verilog`] and [`fadd_verilog`] print the
8//! combinational unit computing it, every member of [`family`] from one generator, so two members differ exactly
9//! where their semantics do, and [`Semantics::mul`] and [`Semantics::add`] compute it in software, bit for bit what
10//! its unit computes. [`Semantics::ENGINE`] is the engine's own arithmetic
11//! ([`crate::spatial::decode_engine::numerics`]); [`Semantics::IEEE`] is IEEE 754 with gradual underflow and the
12//! payload-propagating NaNs of an x86 core. [`logicaffeine_proof::sweep::distinguish`] tells the family apart from
13//! the pairs of [`SPECIALS`]: the members that compute one function, proven, and operand pairs on which every two
14//! that differ give different results, the vectors a hard block is measured on to say which member it is.
15
16use std::fmt::Write;
17
18/// How a denormal is read or written.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20pub enum Denormals {
21    /// Kept as a denormal.
22    Keep,
23    /// Read or written as a zero.
24    Flush,
25}
26
27/// Where a result below the least normal is decided to be one.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub enum Tininess {
30    /// On the exact result: anything below `2^-126` flushes, even what would round up to it.
31    BeforeRounding,
32    /// After rounding the significand at the denormals' precision: only a result that rounds up into the least
33    /// normal survives (the engine's numerics).
34    AfterRoundingAtDenormals,
35    /// After rounding to 24 bits with the exponent unbounded: what rounds up to `2^-126` survives as the least normal.
36    AfterRoundingUnbounded,
37}
38
39/// The sign of a result flushed to zero.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub enum FlushSign {
42    /// The sign the result would have had.
43    Kept,
44    /// Always `+0`.
45    Positive,
46}
47
48/// The NaN an operation returns.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub enum Nan {
51    /// One encoding for every NaN result.
52    Canonical(u32),
53    /// One encoding's exponent and significand, its sign the exclusive or of the operands' signs, as the D5005's
54    /// floating-point DSP block multiplies.
55    XorSigned(u32),
56    /// The first NaN operand's payload, quieted; `invalid` for an invalid operation of non-NaN operands.
57    Propagate { invalid: u32 },
58}
59
60/// One choice of everything IEEE 754 leaves to an implementation of binary32 multiply and add.
61#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62pub struct Semantics {
63    pub denormal_inputs: Denormals,
64    pub denormal_results: Denormals,
65    /// Read only when results flush.
66    pub tininess: Tininess,
67    /// Read only when results flush.
68    pub flushed_sign: FlushSign,
69    pub nan: Nan,
70}
71
72impl Semantics {
73    /// The engine's arithmetic: denormal operands read as zeros of their sign, a result rounded at the denormals'
74    /// precision and flushed unless it reaches the least normal, every NaN `0x7fc00000`.
75    pub const ENGINE: Semantics = Semantics {
76        denormal_inputs: Denormals::Flush,
77        denormal_results: Denormals::Flush,
78        tininess: Tininess::AfterRoundingAtDenormals,
79        flushed_sign: FlushSign::Kept,
80        nan: Nan::Canonical(0x7fc0_0000),
81    };
82
83    /// IEEE 754 with gradual underflow and an x86 core's NaNs: the first NaN operand's payload quieted, the invalid
84    /// operation's `0xffc00000`.
85    pub const IEEE: Semantics = Semantics {
86        denormal_inputs: Denormals::Keep,
87        denormal_results: Denormals::Keep,
88        tininess: Tininess::AfterRoundingAtDenormals,
89        flushed_sign: FlushSign::Kept,
90        nan: Nan::Propagate { invalid: 0xffc0_0000 },
91    };
92
93    /// A short name: `din-keep.dout-flush.tiny-before.sign-pos.nan-7fc00000`.
94    pub fn name(&self) -> String {
95        let d = |x: Denormals| match x {
96            Denormals::Keep => "keep",
97            Denormals::Flush => "flush",
98        };
99        let mut n = format!("din-{}.dout-{}", d(self.denormal_inputs), d(self.denormal_results));
100        if self.denormal_results == Denormals::Flush {
101            n.push_str(match self.tininess {
102                Tininess::BeforeRounding => ".tiny-before",
103                Tininess::AfterRoundingAtDenormals => ".tiny-after-denormals",
104                Tininess::AfterRoundingUnbounded => ".tiny-after-unbounded",
105            });
106            n.push_str(match self.flushed_sign {
107                FlushSign::Kept => ".sign-kept",
108                FlushSign::Positive => ".sign-pos",
109            });
110        }
111        match self.nan {
112            Nan::Canonical(v) => write!(n, ".nan-{v:08x}").unwrap(),
113            Nan::XorSigned(v) => write!(n, ".nan-xor-{:08x}", v & 0x7fff_ffff).unwrap(),
114            Nan::Propagate { invalid } => write!(n, ".nan-prop-{invalid:08x}").unwrap(),
115        }
116        n
117    }
118
119    /// `a × b` under these semantics, bit for bit what [`fmul_verilog`]'s unit computes.
120    pub fn mul(&self, a: u32, b: u32) -> u32 {
121        let (x, y) = (self.operand(a), self.operand(b));
122        let s = x.sign ^ y.sign;
123        if x.nan || y.nan || (x.inf && y.zero) || (y.inf && x.zero) {
124            return self.nan_result(a, b);
125        }
126        if x.inf || y.inf {
127            return s << 31 | 0x7f80_0000;
128        }
129        if x.zero || y.zero {
130            return s << 31;
131        }
132        let p = u128::from(x.significand) * u128::from(y.significand);
133        let lz = match self.denormal_inputs {
134            Denormals::Flush => u32::from(p >> 47 & 1 == 0),
135            Denormals::Keep if x.denormal || y.denormal => if p == 0 { 48 } else { 47 - (127 - p.leading_zeros()) },
136            Denormals::Keep => u32::from(p >> 47 & 1 == 0),
137        };
138        let pn = (p << lz) & ((1u128 << 48) - 1);
139        let e = i32::from(x.exponent) + i32::from(y.exponent) - 126 - lz as i32;
140        self.assemble(s, pn, 48, e)
141    }
142
143    /// `a + b` under these semantics, bit for bit what [`fadd_verilog`]'s unit computes.
144    pub fn add(&self, a: u32, b: u32) -> u32 {
145        let (x, y) = (self.operand(a), self.operand(b));
146        if x.nan || y.nan || (x.inf && y.inf && x.sign != y.sign) {
147            return self.nan_result(a, b);
148        }
149        if x.inf {
150            return x.sign << 31 | 0x7f80_0000;
151        }
152        if y.inf {
153            return y.sign << 31 | 0x7f80_0000;
154        }
155        if x.zero && y.zero {
156            return (x.sign & y.sign) << 31;
157        }
158        let pass = |v: u32, sign: u32| match (self.denormal_inputs, self.denormal_results) {
159            (Denormals::Keep, Denormals::Flush) if v >> 23 & 0xff == 0 => self.flushed(sign),
160            _ => v,
161        };
162        if x.zero {
163            return pass(b, y.sign);
164        }
165        if y.zero {
166            return pass(a, x.sign);
167        }
168        let swap = a & 0x7fff_ffff < b & 0x7fff_ffff;
169        let (big, small) = if swap { (y, x) } else { (x, y) };
170        let f2 = match self.denormal_inputs {
171            Denormals::Flush if small.zero => 0,
172            _ => small.significand,
173        };
174        let d = big.exponent.wrapping_sub(small.exponent);
175        let aligned = (u64::from(f2) << 27) >> d.min(50);
176        let g2 = (aligned >> 25) << 1 | u64::from(aligned & ((1 << 25) - 1) != 0);
177        let g1 = u64::from(big.significand) << 3;
178        let sum = if big.sign != small.sign { g1.wrapping_sub(g2) & ((1 << 28) - 1) } else { g1 + g2 };
179        if sum == 0 {
180            return 0;
181        }
182        let low = sum & ((1 << 27) - 1);
183        let lz = if low == 0 { 27 } else { 26 - (63 - low.leading_zeros()) };
184        let carry = sum >> 27 & 1 == 1;
185        let e = if carry { i32::from(big.exponent) + 1 } else { i32::from(big.exponent) - lz as i32 };
186        let pn = if carry { (sum >> 2) << 1 | u64::from(sum & 3 != 0) } else { (low << lz) & ((1 << 27) - 1) };
187        self.assemble(big.sign, u128::from(pn), 27, e)
188    }
189
190    /// An operand as the datapath reads it: its sign, its class, and (a number) the significand and exponent it is
191    /// read with, a flushed denormal a zero, a kept one `0.m × 2^-126`.
192    fn operand(&self, v: u32) -> Operand {
193        let (sign, exponent, m) = (v >> 31, (v >> 23 & 0xff) as u8, v & 0x7f_ffff);
194        let denormal = exponent == 0 && m != 0;
195        let zero = match self.denormal_inputs {
196            Denormals::Flush => exponent == 0,
197            Denormals::Keep => exponent == 0 && m == 0,
198        };
199        let (significand, exponent) = match self.denormal_inputs {
200            Denormals::Keep if denormal => (m, 1),
201            _ => (1 << 23 | m, exponent),
202        };
203        Operand { sign, zero, denormal: denormal && self.denormal_inputs == Denormals::Keep, inf: v & 0x7fff_ffff == 0x7f80_0000, nan: v & 0x7fff_ffff > 0x7f80_0000, significand, exponent }
204    }
205
206    /// The NaN an operation of `a` and `b` returns.
207    fn nan_result(&self, a: u32, b: u32) -> u32 {
208        let nan = |v: u32| v & 0x7fff_ffff > 0x7f80_0000;
209        match self.nan {
210            Nan::Canonical(v) => v,
211            Nan::XorSigned(v) => (a ^ b) & 0x8000_0000 | v & 0x7fff_ffff,
212            Nan::Propagate { invalid } if nan(a) => a | 0x40_0000,
213            Nan::Propagate { invalid } if nan(b) => b | 0x40_0000,
214            Nan::Propagate { invalid } => invalid,
215        }
216    }
217
218    /// A zero a result is flushed to, under `sign`.
219    fn flushed(&self, sign: u32) -> u32 {
220        match self.flushed_sign {
221            FlushSign::Kept => sign << 31,
222            FlushSign::Positive => 0,
223        }
224    }
225
226    /// The result of `sign` and the significand `pn` normalized with its leading one at bit `width - 1`, its biased
227    /// exponent `e`: rounded at the denormals' place to nearest even, overflowing to infinity, and below the least normal
228    /// as these semantics underflow.
229    fn assemble(&self, sign: u32, pn: u128, width: u32, e: i32) -> u32 {
230        let sh = if e < 1 { (1 - e) as u32 } else { 0 }.min(width);
231        let wide = (pn << width) >> sh;
232        let keep = (wide >> (2 * width - 24)) as u32 & 0xff_ffff;
233        let guard = wide >> (2 * width - 25) & 1 == 1;
234        let sticky = wide & ((1u128 << (2 * width - 25)) - 1) != 0;
235        let r = keep + u32::from(guard && (sticky || keep & 1 == 1));
236        let carry = r >> 24 & 1 == 1;
237        let ef = if e < 1 { i32::from(r >> 23 & 1 == 1) } else if carry { e + 1 } else { e };
238        let mf = if carry { r >> 1 & 0x7f_ffff } else { r & 0x7f_ffff };
239        let below = width - 24;
240        let cn = (pn >> below) as u32 & 0xff_ffff == 0xff_ffff && pn >> (below - 1) & 1 == 1;
241        if ef >= 255 {
242            return sign << 31 | 0x7f80_0000;
243        }
244        match (self.denormal_results, self.tininess) {
245            (Denormals::Keep, _) => {}
246            (Denormals::Flush, Tininess::AfterRoundingAtDenormals) if ef <= 0 => return self.flushed(sign),
247            (Denormals::Flush, Tininess::BeforeRounding) if e < 1 => return self.flushed(sign),
248            (Denormals::Flush, Tininess::AfterRoundingUnbounded) if e < 1 => {
249                return if e == 0 && cn { sign << 31 | 0x0080_0000 } else { self.flushed(sign) };
250            }
251            _ => {}
252        }
253        sign << 31 | (ef as u32 & 0xff) << 23 | mf
254    }
255
256    /// The members whose tininess and flushed sign mean nothing (results kept) take the engine's, so no two members
257    /// of [`family`] describe one function by construction.
258    fn canonical(mut self) -> Semantics {
259        if self.denormal_results == Denormals::Keep {
260            self.tininess = Tininess::AfterRoundingAtDenormals;
261            self.flushed_sign = FlushSign::Kept;
262        }
263        self
264    }
265}
266
267/// An operand as a member's datapath reads it.
268#[derive(Clone, Copy)]
269struct Operand {
270    sign: u32,
271    zero: bool,
272    /// A denormal read as itself (operands kept), `0.m × 2^-126`.
273    denormal: bool,
274    inf: bool,
275    nan: bool,
276    significand: u32,
277    exponent: u8,
278}
279
280/// The operands every pair of which a hard block is first measured on: signed zeros, the denormals' ends, the normals'
281/// ends, one, infinities, quiet and signalling NaNs, a mid denormal and one above one.
282pub const SPECIALS: [u32; 20] = [
283    0x0000_0000, 0x8000_0000, 0x0000_0001, 0x8000_0001, 0x007f_ffff, 0x807f_ffff, 0x0080_0000, 0x8080_0000,
284    0x3f80_0000, 0xbf80_0000, 0x7f7f_ffff, 0xff7f_ffff, 0x7f80_0000, 0xff80_0000, 0x7fc0_0000, 0xffc0_0001,
285    0x7f80_0001, 0x7fbf_ffff, 0x0040_0000, 0x3400_0001,
286];
287
288/// The NaN encodings the family tries: four canonical NaNs, the first NaN operand's payload with either default NaN
289/// for an invalid operation (an ARM core's positive one, an x86 core's negative one), and the default quiet NaN signed
290/// by the operands' signs' exclusive or (the D5005's floating-point DSP block's multiply).
291pub const NANS: [Nan; 7] = [
292    Nan::Canonical(0x7fc0_0000),
293    Nan::Canonical(0xffc0_0000),
294    Nan::Canonical(0x7fff_ffff),
295    Nan::Canonical(0xffff_ffff),
296    Nan::Propagate { invalid: 0x7fc0_0000 },
297    Nan::Propagate { invalid: 0xffc0_0000 },
298    Nan::XorSigned(0x7fc0_0000),
299];
300
301/// Every member: each choice of operand denormals, result denormals, tininess, flushed sign and NaN, the
302/// meaningless combinations folded away; [`Semantics::ENGINE`] first.
303pub fn family() -> Vec<Semantics> {
304    let mut out = vec![Semantics::ENGINE];
305    for denormal_inputs in [Denormals::Flush, Denormals::Keep] {
306        for denormal_results in [Denormals::Flush, Denormals::Keep] {
307            for tininess in [Tininess::AfterRoundingAtDenormals, Tininess::BeforeRounding, Tininess::AfterRoundingUnbounded] {
308                for flushed_sign in [FlushSign::Kept, FlushSign::Positive] {
309                    for nan in NANS {
310                        let s = Semantics { denormal_inputs, denormal_results, tininess, flushed_sign, nan }.canonical();
311                        if !out.contains(&s) {
312                            out.push(s);
313                        }
314                    }
315                }
316            }
317        }
318    }
319    out
320}
321
322/// The operand classes: zero, infinity, NaN, and (for kept denormals) the significand and exponent a denormal is
323/// read with.
324fn operands(out: &mut String, s: &Semantics) {
325    out.push_str(
326        "  wire        sa = a[31], sb = b[31];\n\
327         \x20 wire [7:0]  ea = a[30:23], eb = b[30:23];\n\
328         \x20 wire [22:0] ma = a[22:0], mb = b[22:0];\n",
329    );
330    match s.denormal_inputs {
331        Denormals::Flush => out.push_str("  wire        za = (ea == 8'd0), zb = (eb == 8'd0);\n"),
332        Denormals::Keep => out.push_str("  wire        za = (ea == 8'd0) && (ma == 23'd0), zb = (eb == 8'd0) && (mb == 23'd0);\n"),
333    }
334    out.push_str(
335        "  wire        ia = (ea == 8'hff) && (ma == 23'd0), ib = (eb == 8'hff) && (mb == 23'd0);\n\
336         \x20 wire        na = (ea == 8'hff) && (ma != 23'd0), nb = (eb == 8'hff) && (mb != 23'd0);\n",
337    );
338    match s.denormal_inputs {
339        // A flushed denormal is a zero, decided before the datapath: every operand the datapath reads is normal.
340        Denormals::Flush => out.push_str(
341            "  wire [23:0] fa = {1'b1, ma}, fb = {1'b1, mb};\n\
342             \x20 wire [7:0]  xa = ea, xb = eb;\n",
343        ),
344        // A denormal operand reads as 0.m x 2^-126: no leading one, the least normal's exponent. A zero is decided before
345        // the datapath, which reads it as a normal one, so with no denormal operand the datapath is the engine's.
346        Denormals::Keep => out.push_str(
347            "  wire        da = (ea == 8'd0) && (ma != 23'd0), db = (eb == 8'd0) && (mb != 23'd0);\n\
348             \x20 wire [23:0] fa = {!da, ma}, fb = {!db, mb};\n\
349             \x20 wire [7:0]  xa = da ? 8'd1 : ea, xb = db ? 8'd1 : eb;\n",
350        ),
351    }
352}
353
354/// The NaN result.
355fn nan_result(s: &Semantics) -> String {
356    match s.nan {
357        Nan::Canonical(v) => format!("32'h{v:08x}"),
358        Nan::XorSigned(v) => format!("{{sa ^ sb, 31'h{:08x}}}", v & 0x7fff_ffff),
359        Nan::Propagate { invalid } => format!(
360            "(na ? {{sa, 8'hff, 1'b1, ma[21:0]}} : nb ? {{sb, 8'hff, 1'b1, mb[21:0]}} : 32'h{invalid:08x})"
361        ),
362    }
363}
364
365/// The rounded result from a significand normalized with its leading one at bit `top` of `pn`, its biased exponent
366/// `e` (signed, 12 bits), the sign `s1`: the denormals' place, the rounding, the exponent and significand it lands
367/// at (`ef`, `mf`), and for tininess after unbounded rounding whether the 24-bit rounding carries (`cn`).
368fn round(out: &mut String, width: usize) {
369    let top = width - 1;
370    let below = width - 24;
371    writeln!(out, "  wire [11:0] sh_raw = (e < 12'sd1) ? (12'd1 - e) : 12'd0;").unwrap();
372    writeln!(out, "  wire [6:0]  sh = (sh_raw > 12'd{width}) ? 7'd{width} : sh_raw[6:0];").unwrap();
373    writeln!(out, "  wire [{}:0] wide = {{pn, {width}'d0}} >> sh;", 2 * width - 1).unwrap();
374    writeln!(out, "  wire [23:0] keep = wide[{}:{}];", 2 * width - 1, 2 * width - 24).unwrap();
375    writeln!(out, "  wire        guard = wide[{}];", 2 * width - 25).unwrap();
376    writeln!(out, "  wire        sticky = |wide[{}:0];", 2 * width - 26).unwrap();
377    out.push_str(
378        "  wire        up = guard && (sticky || keep[0]);\n\
379         \x20 wire [24:0] r = {1'b0, keep} + {24'd0, up};\n\
380         \x20 wire        carry = r[24];\n\
381         \x20 wire signed [11:0] ef = (e < 12'sd1) ? (r[23] ? 12'sd1 : 12'sd0) : (carry ? e + 12'sd1 : e);\n\
382         \x20 wire [22:0] mf = carry ? r[23:1] : r[22:0];\n",
383    );
384    // All 24 kept bits set and the guard set: the round up carries (the kept bit 0 is set, so ties go up too).
385    writeln!(out, "  wire        cn = &pn[{top}:{below}] && pn[{}];", below - 1).unwrap();
386}
387
388/// The result's cases after the special ones, each `(condition, value)` in its order: overflow to infinity, the
389/// semantics' underflow; and the normal case's value, the last.
390fn assemble(cases: &mut Vec<(String, String)>, s: &Semantics, sign: &str) -> String {
391    let zero = match s.flushed_sign {
392        FlushSign::Kept => format!("{{{sign}, 31'd0}}"),
393        FlushSign::Positive => "32'd0".to_string(),
394    };
395    cases.push(("ef >= 12'sd255".into(), format!("{{{sign}, 8'hff, 23'd0}}")));
396    match (s.denormal_results, s.tininess) {
397        (Denormals::Keep, _) => {}
398        (Denormals::Flush, Tininess::AfterRoundingAtDenormals) => cases.push(("ef <= 12'sd0".into(), zero)),
399        (Denormals::Flush, Tininess::BeforeRounding) => cases.push(("e < 12'sd1".into(), zero)),
400        (Denormals::Flush, Tininess::AfterRoundingUnbounded) => {
401            cases.push(("e < 12'sd1".into(), format!("((e == 12'sd0 && cn) ? {{{sign}, 8'd1, 23'd0}} : {zero})")))
402        }
403    }
404    format!("{{{sign}, ef[7:0], mf}}")
405}
406
407/// `y` as the first of `cases` whose condition holds, `last` when none does: one chain of choices, so no bit of it is
408/// left undefined by a path that assigns nothing.
409fn result(out: &mut String, cases: &[(String, String)], last: &str) {
410    out.push_str("  assign y =");
411    for (k, (condition, value)) in cases.iter().enumerate() {
412        write!(out, "{}({condition}) ? {value}", if k == 0 { " " } else { "\n    : " }).unwrap();
413    }
414    writeln!(out, "\n    : {last};").unwrap();
415}
416
417/// A combinational binary32 multiply under `s`, ports `a`, `b`, `y`.
418pub fn fmul_verilog(module: &str, s: &Semantics) -> String {
419    let mut out = format!("module {module}(input [31:0] a, input [31:0] b, output [31:0] y);\n");
420    operands(&mut out, s);
421    // The product written as the engine writes it: Verilog sizes it by its 48-bit context, and two spellings of one
422    // product synthesize to two different multipliers.
423    match s.denormal_inputs {
424        Denormals::Flush => out.push_str("  wire        s = sa ^ sb;\n  wire [47:0] p = {1'b1, ma} * {1'b1, mb};\n"),
425        // A denormal operand's significand lacks its leading one: (A - 2^23 da)(B - 2^23 db) = AB - 2^23 (da B + db A)
426        // + 2^46 da db over the significands with their leading ones, so the one product AB is the engine's.
427        Denormals::Keep => out.push_str(
428            "  wire        s = sa ^ sb;\n\
429             \x20 wire [47:0] whole = {1'b1, ma} * {1'b1, mb};\n\
430             \x20 wire [47:0] p = whole - (da ? {24'd0, 1'b1, mb} << 23 : 48'd0) - (db ? {24'd0, 1'b1, ma} << 23 : 48'd0)\n\
431             \x20                  + ((da && db) ? 48'd1 << 46 : 48'd0);\n",
432        ),
433    }
434    match s.denormal_inputs {
435        // Two normal significands make a product in [2^46, 2^48): its leading one is bit 47 or bit 46.
436        Denormals::Flush => out.push_str("  wire [5:0]  lz = p[47] ? 6'd0 : 6'd1;\n"),
437        // A denormal operand leaves up to 47 leading zeros; two normal ones, as above.
438        Denormals::Keep => out.push_str(
439            "  reg  [5:0]  lz_d;\n\
440             \x20 integer     i;\n\
441             \x20 always @* begin\n\
442             \x20   lz_d = 6'd48;\n\
443             \x20   for (i = 0; i <= 47; i = i + 1) if (p[i]) lz_d = 6'd47 - i[5:0];\n\
444             \x20 end\n\
445             \x20 wire [5:0]  lz = (da || db) ? lz_d : (p[47] ? 6'd0 : 6'd1);\n",
446        ),
447    }
448    out.push_str(
449        "  wire [47:0] pn = p << lz;\n\
450         \x20 wire signed [11:0] e = $signed({4'd0, xa}) + $signed({4'd0, xb}) - 12'sd126 - $signed({6'd0, lz});\n",
451    );
452    round(&mut out, 48);
453    let mut cases = vec![
454        ("na || nb || (ia && zb) || (ib && za)".to_string(), nan_result(s)),
455        ("ia || ib".to_string(), "{s, 8'hff, 23'd0}".to_string()),
456        ("za || zb".to_string(), "{s, 31'd0}".to_string()),
457    ];
458    let last = assemble(&mut cases, s, "s");
459    result(&mut out, &cases, &last);
460    out.push_str("endmodule\n");
461    out
462}
463
464/// A combinational binary32 add under `s`, ports `a`, `b`, `y`.
465pub fn fadd_verilog(module: &str, s: &Semantics) -> String {
466    let mut out = format!("module {module}(input [31:0] a, input [31:0] b, output [31:0] y);\n");
467    operands(&mut out, s);
468    let small = match s.denormal_inputs {
469        Denormals::Flush => "z2 ? 24'd0 : (swap ? fa : fb)",
470        Denormals::Keep => "swap ? fa : fb",
471    };
472    writeln!(
473        out,
474        "  // The larger magnitude first.\n\
475         \x20 wire        swap = {{ea, ma}} < {{eb, mb}};\n\
476         \x20 wire        s1 = swap ? sb : sa, s2 = swap ? sa : sb;\n\
477         \x20 wire [7:0]  x1 = swap ? xb : xa, x2 = swap ? xa : xb;\n\
478         \x20 wire        z2 = swap ? za : zb;\n\
479         \x20 wire [23:0] f1 = swap ? fb : fa;\n\
480         \x20 wire [23:0] f2 = {small};\n\
481         \x20 wire [7:0]  d = x1 - x2;\n\
482         \x20 // Three places below the significand: guard, round, and a sticky of everything shifted past them.\n\
483         \x20 wire [50:0] aligned = {{f2, 27'd0}} >> ((d > 8'd50) ? 8'd50 : d);\n\
484         \x20 wire [26:0] g2 = {{aligned[50:25], |aligned[24:0]}};\n\
485         \x20 wire [26:0] g1 = {{f1, 3'd0}};\n\
486         \x20 wire        sub = s1 ^ s2;\n\
487         \x20 wire [27:0] sum = sub ? {{1'b0, g1}} - {{1'b0, g2}} : {{1'b0, g1}} + {{1'b0, g2}};\n\
488         \x20 reg  [4:0]  lz;\n\
489         \x20 integer     i;\n\
490         \x20 always @* begin\n\
491         \x20   lz = 5'd27;\n\
492         \x20   for (i = 0; i <= 26; i = i + 1) if (sum[i]) lz = 5'd26 - i[4:0];\n\
493         \x20 end\n\
494         \x20 wire        c = sum[27];\n\
495         \x20 wire signed [11:0] e = c ? $signed({{4'd0, x1}}) + 12'sd1 : $signed({{4'd0, x1}}) - $signed({{7'd0, lz}});\n\
496         \x20 // Normalized with the leading one at bit 26, the sticky kept through a right shift.\n\
497         \x20 wire [26:0] pn = c ? {{sum[27:2], sum[1] | sum[0]}} : (sum[26:0] << lz);"
498    )
499    .unwrap();
500    round(&mut out, 27);
501    out.push_str("  wire        zero = (sum == 28'd0);\n");
502    let mut cases = vec![
503        ("na || nb || (ia && ib && (sa != sb))".to_string(), nan_result(s)),
504        ("ia".to_string(), "{sa, 8'hff, 23'd0}".to_string()),
505        ("ib".to_string(), "{sb, 8'hff, 23'd0}".to_string()),
506        ("za && zb".to_string(), "{sa & sb, 31'd0}".to_string()),
507    ];
508    // A zero plus the other operand is that operand, flushed when it is a denormal results do not keep.
509    let pass = |x: &str, sx: &str, ex: &str| match (s.denormal_inputs, s.denormal_results) {
510        (Denormals::Keep, Denormals::Flush) => {
511            let zero = match s.flushed_sign {
512                FlushSign::Kept => format!("{{{sx}, 31'd0}}"),
513                FlushSign::Positive => "32'd0".to_string(),
514            };
515            format!("({ex} == 8'd0) ? {zero} : {x}")
516        }
517        _ => x.to_string(),
518    };
519    cases.push(("za".into(), format!("({})", pass("b", "sb", "eb"))));
520    cases.push(("zb".into(), format!("({})", pass("a", "sa", "ea"))));
521    cases.push(("zero".into(), "32'd0".into()));
522    let last = assemble(&mut cases, s, "s1");
523    result(&mut out, &cases, &last);
524    out.push_str("endmodule\n");
525    out
526}
527
528#[cfg(test)]
529#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fp_hypotheses/tests.rs"]
530mod tests;