1use std::fmt::{self, Write};
19
20use super::fp_hypotheses::{Denormals, FlushSign, Nan, Semantics, Tininess};
21use super::fp_oracle::{fp_mac, fma_vectors, Oracle, ADD_REGISTERS, LEAST_REGISTERS, MULTIPLY_REGISTERS};
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub enum Op {
26 Multiply,
27 Add,
28}
29
30impl Op {
31 fn mode(self) -> &'static str {
32 match self {
33 Op::Multiply => "sp_mult",
34 Op::Add => "sp_add",
35 }
36 }
37
38 fn name(self) -> &'static str {
39 match self {
40 Op::Multiply => "multiply",
41 Op::Add => "add",
42 }
43 }
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct Block {
50 pub semantics: Semantics,
51 pub full: usize,
52 pub least: usize,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum Registering {
58 Full,
59 Least,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
64pub struct Plan {
65 pub registering: Registering,
66 pub pads: usize,
67 pub flush_operands: bool,
68 pub flush_results: bool,
69 pub canonical_nan: bool,
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
74pub enum HardError {
75 TooDeep { op: &'static str, latency: usize, least: usize },
77 Semantics { op: &'static str, semantics: String, why: String },
79}
80
81impl fmt::Display for HardError {
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 match self {
84 HardError::TooDeep { op, latency, least } => {
85 write!(f, "the block's {op} is {least} clocks deep with its output register alone, past the unit's {latency}")
86 }
87 HardError::Semantics { op, semantics, why } => write!(f, "the block's {op} ({semantics}) {why}"),
88 }
89 }
90}
91
92pub fn plan(op: Op, block: &Block, latency: usize) -> Result<Plan, HardError> {
95 plan_for(op, block, latency, &Semantics::ENGINE)
96}
97
98fn tinier(t: Tininess) -> u8 {
101 match t {
102 Tininess::AfterRoundingAtDenormals => 0,
103 Tininess::AfterRoundingUnbounded => 1,
104 Tininess::BeforeRounding => 2,
105 }
106}
107
108pub fn plan_for(op: Op, block: &Block, latency: usize, target: &Semantics) -> Result<Plan, HardError> {
115 let s = &block.semantics;
116 let who = if *target == Semantics::ENGINE { "the engine".to_string() } else { target.name() };
117 let refuse = |why: String| Err(HardError::Semantics { op: op.name(), semantics: s.name(), why });
118 if s.denormal_inputs == Denormals::Flush && target.denormal_inputs == Denormals::Keep {
119 return refuse(format!("reads a denormal operand as a zero, which {who} reads as itself"));
120 }
121 match (s.denormal_results, target.denormal_results) {
122 (Denormals::Flush, Denormals::Keep) => return refuse(format!("flushes a result {who} keeps as a denormal")),
123 (Denormals::Flush, Denormals::Flush) => {
124 if tinier(s.tininess) > tinier(target.tininess) {
125 return refuse(format!("decides a result tiny before {who} does, and flushes what {who} would round into the least normal"));
126 }
127 if tinier(s.tininess) < tinier(target.tininess) {
128 return refuse(format!("rounds into the least normal a result {who} decides tiny and flushes"));
129 }
130 if s.flushed_sign != target.flushed_sign {
131 return refuse(match target.flushed_sign {
132 FlushSign::Kept => format!("flushes a tiny result to +0 whatever its sign, which {who} keeps"),
133 FlushSign::Positive => format!("flushes a tiny result with its sign, which {who} drops"),
134 });
135 }
136 }
137 (Denormals::Keep, Denormals::Flush) => {
138 if target.tininess != Tininess::AfterRoundingAtDenormals || target.flushed_sign != FlushSign::Kept {
139 return refuse(format!("keeps a denormal result, which flushed after it is no result {who} decides tiny"));
140 }
141 }
142 (Denormals::Keep, Denormals::Keep) => {}
143 }
144 if s.nan != target.nan && target.nan != Nan::Canonical(0x7fc0_0000) {
145 return refuse(format!("returns NaNs {who} does not, and the logic after it writes only 0x7fc00000"));
146 }
147 let (registering, depth) = if block.full <= latency {
148 (Registering::Full, block.full)
149 } else if block.least <= latency {
150 (Registering::Least, block.least)
151 } else {
152 return Err(HardError::TooDeep { op: op.name(), latency, least: block.least });
153 };
154 Ok(Plan {
155 registering,
156 pads: latency - depth,
157 flush_operands: s.denormal_inputs == Denormals::Keep && target.denormal_inputs == Denormals::Flush,
158 flush_results: s.denormal_results == Denormals::Keep && target.denormal_results == Denormals::Flush,
159 canonical_nan: s.nan != target.nan,
160 })
161}
162
163pub fn hard_verilog(op: Op, module: &str, block: &Block, latency: usize) -> Result<String, HardError> {
165 wrapper(op, module, block, latency, false, &Semantics::ENGINE)
166}
167
168pub fn hard_verilog_ce(op: Op, module: &str, block: &Block, latency: usize) -> Result<String, HardError> {
171 wrapper(op, module, block, latency, true, &Semantics::ENGINE)
172}
173
174pub fn hard_verilog_for(op: Op, module: &str, block: &Block, latency: usize, target: &Semantics) -> Result<String, HardError> {
176 wrapper(op, module, block, latency, false, target)
177}
178
179pub fn hard_verilog_ce_for(op: Op, module: &str, block: &Block, latency: usize, target: &Semantics) -> Result<String, HardError> {
182 wrapper(op, module, block, latency, true, target)
183}
184
185fn target_nan(target: &Semantics) -> String {
187 let word = |v: u32| format!("32'h{:04x}_{:04x}", v >> 16, v & 0xffff);
188 match target.nan {
189 Nan::Canonical(v) => word(v),
190 Nan::XorSigned(v) => format!("{{a[31] ^ b[31], 31'h{:08x}}}", v & 0x7fff_ffff),
191 Nan::Propagate { invalid } => format!("(na ? {{a[31], 8'hff, 1'b1, a[21:0]}} : nb ? {{b[31], 8'hff, 1'b1, b[21:0]}} : {})", word(invalid)),
192 }
193}
194
195fn wrapper(op: Op, module: &str, block: &Block, latency: usize, ce: bool, target: &Semantics) -> Result<String, HardError> {
196 let p = plan_for(op, block, latency, target)?;
197 let (ports, enable, step) = if ce {
198 ("input clk, input step_en, input [31:0] a, input [31:0] b, output [31:0] y", "step_en", "always @(posedge clk) if (step_en) begin")
199 } else {
200 ("input clk, input [31:0] a, input [31:0] b, output [31:0] y", "1'b1", "always @(posedge clk) begin")
201 };
202 let (registered, depth) = match p.registering {
203 Registering::Full => ("registered as the IP registers it", block.full),
204 Registering::Least => ("with its output register alone", block.least),
205 };
206 let (what, is) = if *target == Semantics::ENGINE {
207 (format!("The engine's f32 {}", op.name()), format!("the engine's {}", op.name()))
208 } else {
209 (format!("The f32 {} of {}", op.name(), target.name()), format!("that {}", op.name()))
210 };
211 let mut doc = format!(
212 "// {what} on the card's floating-point DSP block, {latency} {}: y is {is} of the operands\n\
213 // {latency} {} before. The block {registered}, {depth} deep{}",
214 if ce { "steps" } else { "clocks" },
215 if ce { "steps" } else { "clocks" },
216 match p.pads {
217 0 => String::new(),
218 1 => ", then a register".to_string(),
219 n => format!(", then {n} registers"),
220 }
221 );
222 let fixes: Vec<&str> = [
223 (p.flush_operands, "a denormal operand read as a zero of its sign"),
224 (p.flush_results, "a denormal result written as a zero of its sign"),
225 (p.canonical_nan, "every NaN result 0x7fc00000"),
226 ]
227 .iter()
228 .filter(|(on, _)| *on)
229 .map(|(_, what)| *what)
230 .collect();
231 if fixes.is_empty() {
232 doc.push_str(".\n");
233 } else {
234 write!(doc, ".\n// Around it: {}.\n", fixes.join(", ")).unwrap();
235 }
236 let mut v = format!("{doc}module {module}({ports});\n");
237 let beside = p.flush_operands && op == Op::Multiply;
241 let operand = |x: &str| if p.flush_operands && !beside { format!("(({x}[30:23] == 8'd0) ? {{{x}[31], 31'd0}} : {x})") } else { x.to_string() };
242 writeln!(v, " wire [31:0] ia = {}, ib = {};", operand("a"), operand("b")).unwrap();
243 if beside {
244 v.push_str(
245 " wire da = (a[30:23] == 8'd0) && (a[22:0] != 23'd0), db = (b[30:23] == 8'd0) && (b[22:0] != 23'd0);\n\
246 \x20 wire za = (a[30:23] == 8'd0), zb = (b[30:23] == 8'd0);\n\
247 \x20 wire na = (a[30:23] == 8'hff) && (a[22:0] != 23'd0), nb = (b[30:23] == 8'hff) && (b[22:0] != 23'd0);\n\
248 \x20 wire fa = (a[30:23] == 8'hff) && (a[22:0] == 23'd0), fb = (b[30:23] == 8'hff) && (b[22:0] == 23'd0);\n\
249 \x20 wire [32:0] side_c = {da || db, (na || nb || (fa && zb) || (fb && za)) ? NAN : {a[31] ^ b[31], 31'd0}};\n"
250 .replace("NAN", &target_nan(target))
251 .as_str(),
252 );
253 writeln!(
254 v,
255 " // The decision as deep as the block, beside its result.\n reg [32:0] side [0:{last}];\n integer j;\n {step}\n side[0] <= side_c;\n for (j = 1; j < {depth}; j = j + 1) side[j] <= side[j-1];\n end",
256 last = depth - 1
257 )
258 .unwrap();
259 }
260 v.push_str(" wire [31:0] h;\n wire [7:0] flags;\n");
261 let (used, operands) = match (op, p.registering) {
262 (Op::Multiply, Registering::Full) => (MULTIPLY_REGISTERS, ["32'd0", "ia", "ib"]),
263 (Op::Add, Registering::Full) => (ADD_REGISTERS, ["ia", "ib", "32'd0"]),
264 (Op::Multiply, Registering::Least) => (LEAST_REGISTERS, ["32'd0", "ia", "ib"]),
265 (Op::Add, Registering::Least) => (LEAST_REGISTERS, ["ia", "ib", "32'd0"]),
266 };
267 v.push_str(&fp_mac(op.mode(), "u", used, enable, operands, "h", "flags"));
268 v.push_str(" wire nan = (h[30:23] == 8'hff) && (h[22:0] != 23'd0);\n");
269 let fixed = match (p.canonical_nan, p.flush_results) {
270 (false, false) => "h".to_string(),
271 (true, false) => "nan ? 32'h7fc0_0000 : h".to_string(),
272 (false, true) => "(h[30:23] == 8'd0) ? {h[31], 31'd0} : h".to_string(),
273 (true, true) => "nan ? 32'h7fc0_0000 : (h[30:23] == 8'd0) ? {h[31], 31'd0} : h".to_string(),
274 };
275 if beside {
276 writeln!(v, " wire [31:0] c = side[{}][32] ? side[{}][31:0] : {fixed};", depth - 1, depth - 1).unwrap();
277 } else {
278 writeln!(v, " wire [31:0] c = {fixed};").unwrap();
279 }
280 if p.pads == 0 {
281 v.push_str(" assign y = c;\n");
282 } else {
283 writeln!(v, " reg [31:0] pad [0:{}];\n integer i;\n {step}\n pad[0] <= c;\n for (i = 1; i < {}; i = i + 1) pad[i] <= pad[i-1];\n end\n assign y = pad[{}];", p.pads - 1, p.pads, p.pads - 1).unwrap();
284 }
285 v.push_str("endmodule\n");
286 Ok(v)
287}
288
289#[derive(Clone, Copy, Debug, PartialEq, Eq)]
291pub enum Rounding {
292 Once,
294 Twice,
296 Other,
298}
299
300#[derive(Clone, Copy, Debug, PartialEq, Eq)]
302pub struct Measurement {
303 pub product: Block,
304 pub sum: Block,
305 pub multiply_add_latency: usize,
306 pub multiply_add: Rounding,
307}
308
309#[derive(Clone, Debug, PartialEq, Eq)]
312pub struct Tally {
313 pub differing: Vec<u64>,
314 pub unexplained: u64,
315}
316
317#[derive(Clone, Debug, PartialEq, Eq)]
319pub enum MeasureError {
320 Json(String),
321 Incomplete(&'static str),
323 Class { op: &'static str, agreeing: usize, unexplained: u64 },
325}
326
327impl fmt::Display for MeasureError {
328 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
329 match self {
330 MeasureError::Json(e) => write!(f, "the measurement: {e}"),
331 MeasureError::Incomplete(what) => write!(f, "the measurement has no {what}"),
332 MeasureError::Class { op, agreeing: 0, unexplained } => {
333 write!(f, "the block's {op} is no member of the family: every class differs somewhere ({unexplained} steps no class explains)")
334 }
335 MeasureError::Class { op, agreeing, .. } => write!(f, "{agreeing} classes agree with the block's {op} everywhere, not one"),
336 }
337 }
338}
339
340impl Measurement {
341 pub fn read(oracle: &Oracle, json: &str) -> Result<Measurement, MeasureError> {
346 let v = finished(json)?;
347 let product = block(&v, oracle, Op::Multiply)?;
348 let sum = block(&v, oracle, Op::Add)?;
349 let (multiply_add_latency, multiply_add) = multiply_add(&v)?;
350 Ok(Measurement { product, sum, multiply_add_latency, multiply_add })
351 }
352
353 pub fn read_multiply_add(json: &str) -> Result<(usize, Rounding), MeasureError> {
355 multiply_add(&finished(json)?)
356 }
357
358 pub fn read_product(oracle: &Oracle, json: &str) -> Result<Block, MeasureError> {
360 block(&finished(json)?, oracle, Op::Multiply)
361 }
362
363 pub fn read_sum(oracle: &Oracle, json: &str) -> Result<Block, MeasureError> {
365 block(&finished(json)?, oracle, Op::Add)
366 }
367
368 pub fn read_tally(oracle: &Oracle, json: &str, op: Op) -> Result<Tally, MeasureError> {
370 tally(&finished(json)?, oracle, op)
371 }
372
373 pub fn read_depths(json: &str, op: Op) -> Result<(usize, usize), MeasureError> {
375 let v = finished(json)?;
376 let keys = Keys::of(op);
377 Ok((latency(&v, keys.full)?, latency(&v, keys.least)?))
378 }
379
380 pub fn read_runs(json: &str) -> Result<(u64, [u64; 3]), MeasureError> {
382 let v = finished(json)?;
383 let seed = v["seed"].as_u64().ok_or(MeasureError::Incomplete("seed"))?;
384 let mut steps = [0; 3];
385 for (n, run) in steps.iter_mut().zip(RUNS) {
386 *n = v["runs"][run]["steps"].as_u64().ok_or(MeasureError::Incomplete("steps"))?;
387 }
388 Ok((seed, steps))
389 }
390}
391
392fn finished(json: &str) -> Result<serde_json::Value, MeasureError> {
394 let v: serde_json::Value = serde_json::from_str(json).map_err(|e| MeasureError::Json(e.to_string()))?;
395 if v["complete"] != true {
396 return Err(MeasureError::Incomplete("finished run"));
397 }
398 Ok(v)
399}
400
401fn multiply_add(v: &serde_json::Value) -> Result<(usize, Rounding), MeasureError> {
405 let records = v["runs"]["records"].as_array().ok_or(MeasureError::Incomplete("records"))?;
406 let word = |r: &serde_json::Value, i: usize| r[i].as_str().and_then(|w| u32::from_str_radix(w, 16).ok());
407 let mut once = true;
408 let mut twice = true;
409 for (k, &[a, b, c]) in fma_vectors().iter().enumerate() {
410 let r = records.iter().find(|r| word(r, 0) == Some(a) && word(r, 1) == Some(b) && word(r, 2) == Some(c)).ok_or(MeasureError::Incomplete("multiply-add records"))?;
411 let got = word(r, 5).ok_or(MeasureError::Incomplete("multiply-add result"))?;
412 let (x, y, z) = (f32::from_bits(a), f32::from_bits(b), f32::from_bits(c));
413 once &= got == ((f64::from(x) * f64::from(y) + f64::from(z)) as f32).to_bits();
414 twice &= got == (x * y + z).to_bits();
415 if k % 4 != 3 {
416 twice &= got == 0;
417 }
418 }
419 let rounding = match (once, twice) {
420 (true, false) => Rounding::Once,
421 (false, true) => Rounding::Twice,
422 _ => Rounding::Other,
423 };
424 Ok((latency(v, "multiply_add")?, rounding))
425}
426
427fn latency(v: &serde_json::Value, key: &'static str) -> Result<usize, MeasureError> {
429 v["latency"][key].as_i64().filter(|l| *l > 0).map(|l| l as usize).ok_or(MeasureError::Incomplete(key))
430}
431
432const RUNS: [&str; 3] = ["vectors", "random", "near"];
434
435struct Keys {
438 name: &'static str,
439 counters: &'static str,
440 lone: &'static str,
441 full: &'static str,
442 least: &'static str,
443}
444
445impl Keys {
446 fn of(op: Op) -> Keys {
447 match op {
448 Op::Multiply => Keys { name: "multiply", counters: "products", lone: "lone_products", full: "product", least: "product_least" },
449 Op::Add => Keys { name: "add", counters: "sums", lone: "lone_sums", full: "sum", least: "sum_least" },
450 }
451 }
452}
453
454fn tally(v: &serde_json::Value, oracle: &Oracle, op: Op) -> Result<Tally, MeasureError> {
457 let keys = Keys::of(op);
458 let classes = match op {
459 Op::Multiply => oracle.products.len(),
460 Op::Add => oracle.sums.len(),
461 };
462 let mut tally = Tally { differing: vec![0; classes], unexplained: 0 };
463 for run in RUNS {
464 let counters = v["runs"][run][keys.counters].as_array().filter(|c| c.len() == classes).ok_or(MeasureError::Incomplete("counters"))?;
465 for (sum, c) in tally.differing.iter_mut().zip(counters) {
466 *sum += c.as_u64().ok_or(MeasureError::Incomplete("counters"))?;
467 }
468 tally.unexplained += v["runs"][run][keys.lone].as_u64().ok_or(MeasureError::Incomplete("unexplained steps"))?;
469 }
470 Ok(tally)
471}
472
473fn block(v: &serde_json::Value, oracle: &Oracle, op: Op) -> Result<Block, MeasureError> {
476 let keys = Keys::of(op);
477 let classes = match op {
478 Op::Multiply => &oracle.products,
479 Op::Add => &oracle.sums,
480 };
481 let tally = tally(v, oracle, op)?;
482 let agreeing: Vec<usize> = (0..classes.len()).filter(|&k| tally.differing[k] == 0).collect();
483 let [one] = agreeing[..] else {
484 return Err(MeasureError::Class { op: keys.name, agreeing: agreeing.len(), unexplained: tally.unexplained });
485 };
486 Ok(Block { semantics: classes[one].semantics, full: latency(v, keys.full)?, least: latency(v, keys.least)? })
487}
488
489#[cfg(test)]
490#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fp_hard/tests.rs"]
491mod tests;