1use std::fmt::Write;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
20pub enum Denormals {
21 Keep,
23 Flush,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
29pub enum Tininess {
30 BeforeRounding,
32 AfterRoundingAtDenormals,
35 AfterRoundingUnbounded,
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
41pub enum FlushSign {
42 Kept,
44 Positive,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub enum Nan {
51 Canonical(u32),
53 XorSigned(u32),
56 Propagate { invalid: u32 },
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
62pub struct Semantics {
63 pub denormal_inputs: Denormals,
64 pub denormal_results: Denormals,
65 pub tininess: Tininess,
67 pub flushed_sign: FlushSign,
69 pub nan: Nan,
70}
71
72impl Semantics {
73 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 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 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 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 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 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 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 fn flushed(&self, sign: u32) -> u32 {
220 match self.flushed_sign {
221 FlushSign::Kept => sign << 31,
222 FlushSign::Positive => 0,
223 }
224 }
225
226 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 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#[derive(Clone, Copy)]
269struct Operand {
270 sign: u32,
271 zero: bool,
272 denormal: bool,
274 inf: bool,
275 nan: bool,
276 significand: u32,
277 exponent: u8,
278}
279
280pub 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
288pub 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
301pub 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
322fn 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 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 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
354fn 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
365fn 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 writeln!(out, " wire cn = &pn[{top}:{below}] && pn[{}];", below - 1).unwrap();
386}
387
388fn 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
407fn 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
417pub 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 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 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 Denormals::Flush => out.push_str(" wire [5:0] lz = p[47] ? 6'd0 : 6'd1;\n"),
437 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
464pub 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 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;