1use std::collections::HashMap;
30use std::fmt::{self, Write};
31
32use super::fp_hypotheses::{fadd_verilog, family, fmul_verilog, Semantics};
33use crate::crypto_accel::sha256;
34use crate::spatial::decode_engine::ofs::{afu_sources, AfuConfig, AfuFile, LocalMemoryLine};
35use crate::spatial::decode_engine::rtl::engine::{bridge_verilog, EngineParams};
36use crate::spatial::decode_engine::rtl::ram::ram_ce_verilog;
37
38pub const MAGIC: u64 = 0x4f50_4653_4f47_4f4c;
40
41pub const TAPS: usize = 16;
43
44pub const STORE_BEATS: usize = 4096;
46
47pub const DRAIN: usize = TAPS + 1;
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum Source {
53 Vectors,
55 Random,
57 Near,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub struct Run {
69 pub source: Source,
70 pub record: bool,
71 pub tap: u8,
72 pub delays: [u8; 3],
73}
74
75impl Run {
76 pub fn word(&self) -> u32 {
77 let source = match self.source {
78 Source::Vectors => 0,
79 Source::Random => 1,
80 Source::Near => 2,
81 };
82 let [m, a, f] = self.delays.map(|d| u32::from(d) & 0xf);
83 source | (self.record as u32) << 2 | (u32::from(self.tap) & 0xf) << 4 | m << 8 | a << 12 | f << 16
84 }
85
86 pub fn aligned(source: Source, record: bool, latencies: [usize; 3]) -> Option<Run> {
90 if latencies.iter().any(|l| !(1..=TAPS).contains(l)) {
91 return None;
92 }
93 let tap = latencies.iter().max().expect("three") - 1;
94 Some(Run { source, record, tap: tap as u8, delays: latencies.map(|l| (tap + 1 - l) as u8) })
95 }
96
97 pub fn of(word: u32) -> Option<Run> {
98 let source = match word & 3 {
99 0 => Source::Vectors,
100 1 => Source::Random,
101 2 => Source::Near,
102 _ => return None,
103 };
104 let nibble = |at: u32| (word >> at & 0xf) as u8;
105 (word >> 20 == 0 && word & 8 == 0).then_some(Run { source, record: word & 4 != 0, tap: nibble(4), delays: [nibble(8), nibble(12), nibble(16)] })
106 }
107}
108
109#[derive(Clone, Debug, PartialEq)]
112pub struct Class {
113 pub members: Vec<String>,
114 pub semantics: Semantics,
115}
116
117#[derive(Clone, Debug, PartialEq)]
119pub struct Oracle {
120 pub products: Vec<Class>,
121 pub sums: Vec<Class>,
122 pub vectors: Vec<[u32; 3]>,
123}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
127pub enum OracleError {
128 Json(String),
129 Missing(&'static str),
131 Unknown(String),
133 Word(String),
135 TooMany(&'static str, usize),
137}
138
139impl fmt::Display for OracleError {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 match self {
142 OracleError::Json(e) => write!(f, "the discriminator ledger: {e}"),
143 OracleError::Missing(op) => write!(f, "the discriminator ledger has no {op} table"),
144 OracleError::Unknown(m) => write!(f, "no member of the family is named {m}"),
145 OracleError::Word(w) => write!(f, "not a binary32 word: {w}"),
146 OracleError::TooMany(what, n) => write!(f, "{n} {what}, more than the oracle holds"),
147 }
148 }
149}
150
151#[derive(serde::Deserialize)]
152struct LedgerClass {
153 members: Vec<String>,
154}
155
156#[derive(serde::Deserialize)]
157struct LedgerOp {
158 op: String,
159 vectors: Vec<[String; 2]>,
160 classes: Vec<LedgerClass>,
161}
162
163pub const MAX_PRODUCT_CLASSES: usize = 128;
166
167pub const MAX_SUM_CLASSES: usize = 96;
170
171pub fn fma_vectors() -> Vec<[u32; 3]> {
175 let mut state = 0x243f_6a88_85a3_08d3u64;
176 let mut next = move || {
177 state ^= state << 13;
178 state ^= state >> 7;
179 state ^= state << 17;
180 state
181 };
182 let mut out = Vec::new();
183 while out.len() < 64 {
184 let r = next();
185 let (a, b) = (0x3f80_0000 | (r as u32 & 0x7f_ffff), 0x3f80_0000 | ((r >> 32) as u32 & 0x7f_ffff));
186 let p = f32::from_bits(a) * f32::from_bits(b);
187 if f64::from(p) == f64::from(f32::from_bits(a)) * f64::from(f32::from_bits(b)) {
188 continue;
189 }
190 let c = if out.len() % 4 == 3 { (f32::from_bits(0x4000_0000) * if r & 1 == 0 { 1.0 } else { -1.0 }).to_bits() } else { (-p).to_bits() };
191 out.push([a, b, c]);
192 }
193 out
194}
195
196impl Oracle {
197 pub fn from_ledger(json: &str) -> Result<Oracle, OracleError> {
200 let ops: Vec<LedgerOp> = serde_json::from_str(json).map_err(|e| OracleError::Json(e.to_string()))?;
201 let named: HashMap<String, Semantics> = family().into_iter().map(|s| (s.name(), s)).collect();
202 let op = |name: &'static str| ops.iter().find(|o| o.op == name).ok_or(OracleError::Missing(name));
203 let classes = |o: &LedgerOp| -> Result<Vec<Class>, OracleError> {
204 o.classes
205 .iter()
206 .map(|c| {
207 let first = c.members.first().ok_or(OracleError::Missing("member"))?;
208 let semantics = *named.get(first).ok_or_else(|| OracleError::Unknown(first.clone()))?;
209 for m in &c.members {
210 named.get(m).ok_or_else(|| OracleError::Unknown(m.clone()))?;
211 }
212 Ok(Class { members: c.members.clone(), semantics })
213 })
214 .collect()
215 };
216 let word = |w: &String| u32::from_str_radix(w, 16).ok().filter(|_| w.len() == 8).ok_or_else(|| OracleError::Word(w.clone()));
217 let (mul, add) = (op("fmul")?, op("fadd")?);
218 let mut vectors = Vec::new();
219 for [a, b] in mul.vectors.iter().chain(&add.vectors) {
220 vectors.push([word(a)?, word(b)?, 0]);
221 }
222 vectors.extend(fma_vectors());
223 let oracle = Oracle { products: classes(mul)?, sums: classes(add)?, vectors };
224 for (what, n, most) in [("product classes", oracle.products.len(), MAX_PRODUCT_CLASSES), ("sum classes", oracle.sums.len(), MAX_SUM_CLASSES)] {
225 if n == 0 || n > most {
226 return Err(OracleError::TooMany(what, n));
227 }
228 }
229 if oracle.vectors.len() > 1 << 16 {
230 return Err(OracleError::TooMany("vectors", oracle.vectors.len()));
231 }
232 Ok(oracle)
233 }
234
235 pub fn counter_beats(&self) -> usize {
237 (self.products.len() + self.sums.len()).div_ceil(8)
238 }
239
240 pub fn records_at(&self) -> usize {
242 1 + self.counter_beats()
243 }
244
245 pub fn record_cap(&self) -> usize {
247 STORE_BEATS - self.records_at()
248 }
249
250 fn index_bits(&self) -> usize {
252 (usize::BITS - (self.vectors.len().max(2) - 1).leading_zeros()) as usize
253 }
254
255 pub fn core_verilog(&self) -> String {
258 let mut v = self.units_verilog();
259 v.push_str(&self.rom_verilog());
260 v.push_str(&ram_ce_verilog(&[512]));
261 v.push_str(&self.core_text());
262 v
263 }
264
265 pub fn own_verilog(&self) -> String {
269 let mut v = self.units_verilog();
270 v.push_str(&self.rom_verilog());
271 v.push_str(&self.core_text());
272 v
273 }
274
275 fn units_verilog(&self) -> String {
277 let mut v = String::new();
278 for (k, c) in self.products.iter().enumerate() {
279 v.push_str(&fmul_verilog(&format!("logos_fpo_mul{k}"), &c.semantics));
280 }
281 for (k, c) in self.sums.iter().enumerate() {
282 v.push_str(&fadd_verilog(&format!("logos_fpo_add{k}"), &c.semantics));
283 }
284 v
285 }
286
287 pub fn engine_verilog(&self) -> String {
290 format!("{}\n{}\n{}", bridge_verilog(), self.core_verilog(), top_text())
291 }
292
293 fn rom_verilog(&self) -> String {
294 let ib = self.index_bits();
295 let mut v = format!(
296 "// The oracle's vectors, (C, B, A) a word: the multiply's and the add's discriminators, then the multiply-add's.\n\
297 module logos_fpo_vectors(input [{}:0] i, output reg [95:0] v);\n always @* case (i)\n",
298 ib - 1
299 );
300 for (k, [a, b, c]) in self.vectors.iter().enumerate() {
301 writeln!(v, " {ib}'d{k}: v = 96'h{c:08x}_{b:08x}_{a:08x};").unwrap();
302 }
303 v.push_str(" default: v = 96'd0;\n endcase\nendmodule\n\n");
304 v.push_str(LATE);
305 v
306 }
307
308 fn core_text(&self) -> String {
309 let (nm, na) = (self.products.len(), self.sums.len());
310 let ib = self.index_bits();
311 let mut units = String::new();
312 for k in 0..nm {
313 writeln!(units, " wire [31:0] pm{k};\n logos_fpo_mul{k} um{k}(.a(t[31:0]), .b(t[63:32]), .y(pm{k}));").unwrap();
314 }
315 for k in 0..na {
316 writeln!(units, " wire [31:0] pa{k};\n logos_fpo_add{k} ua{k}(.a(t[31:0]), .b(t[63:32]), .y(pa{k}));").unwrap();
317 }
318 let agree = |n: usize, w: &str, hard: &str| (0..n).rev().map(|k| format!("{w}{k} == {hard}")).collect::<Vec<_>>().join(", ");
319 let mut counters = String::new();
320 let mut clear = String::new();
321 let mut bump = String::new();
322 for k in 0..nm {
323 writeln!(counters, " reg [47:0] cm{k};").unwrap();
324 write!(clear, " cm{k} <= 48'd0;").unwrap();
325 writeln!(bump, " if (!agree_m[{k}]) cm{k} <= cm{k} + 48'd1;").unwrap();
326 }
327 for k in 0..na {
328 writeln!(counters, " reg [47:0] ca{k};").unwrap();
329 write!(clear, " ca{k} <= 48'd0;").unwrap();
330 writeln!(bump, " if (!agree_a[{k}]) ca{k} <= ca{k} + 48'd1;").unwrap();
331 }
332 let all: Vec<String> = (0..nm).map(|k| format!("cm{k}")).chain((0..na).map(|k| format!("ca{k}"))).collect();
333 let mut dump = String::new();
334 for (b, chunk) in all.chunks(8).enumerate() {
335 let words: Vec<String> = (0..8).rev().map(|i| chunk.get(i).map_or("64'd0".to_string(), |c| format!("{{16'd0, {c}}}"))).collect();
336 writeln!(dump, " {}: dump_word = {{{}}};", b + 1, words.join(", ")).unwrap();
337 }
338 CORE.replace("@NM@", &nm.to_string())
339 .replace("@NA@", &na.to_string())
340 .replace("@NV@", &self.vectors.len().to_string())
341 .replace("@IB@", &ib.to_string())
342 .replace("@IBM@", &(ib - 1).to_string())
343 .replace("@BEATS@", &STORE_BEATS.to_string())
344 .replace("@AB@", &(STORE_BEATS.trailing_zeros()).to_string())
345 .replace("@RECORDS@", &self.records_at().to_string())
346 .replace("@RECORDS_M1@", &(self.records_at() - 1).to_string())
347 .replace("@NVM1@", &(self.vectors.len() - 1).to_string())
348 .replace("@CAP@", &self.record_cap().to_string())
349 .replace("@TAPS@", &TAPS.to_string())
350 .replace("@DRAIN@", &DRAIN.to_string())
351 .replace("@MAGIC@", &format!("64'h{MAGIC:016x}"))
352 .replace("@HARD@", &hard_blocks())
353 .replace("@UNITS@", &units)
354 .replace("@AGREE_M@", &agree(nm, "pm", "hm"))
355 .replace("@AGREE_A@", &agree(na, "pa", "ha"))
356 .replace("@COUNTERS@", &counters)
357 .replace("@CLEAR@", &clear)
358 .replace("@BUMP@", &bump)
359 .replace("@DUMP@", &dump)
360 }
361}
362
363const CLOCKS: [&str; 13] = [
365 "accumulate_clock",
366 "ax_clock",
367 "ay_clock",
368 "az_clock",
369 "accum_pipeline_clock",
370 "ax_chainin_pl_clock",
371 "mult_pipeline_clock",
372 "accum_2nd_pipeline_clock",
373 "ax_chainin_2nd_pl_clock",
374 "mult_2nd_pipeline_clock",
375 "accum_adder_clock",
376 "adder_input_clock",
377 "output_clock",
378];
379
380pub const MULTIPLY_REGISTERS: &[&str] = &["ay_clock", "az_clock", "mult_pipeline_clock", "mult_2nd_pipeline_clock", "output_clock"];
384pub const ADD_REGISTERS: &[&str] = &["ax_clock", "ay_clock", "adder_input_clock", "output_clock"];
385pub const MULTIPLY_ADD_REGISTERS: &[&str] = &[
386 "ax_clock",
387 "ay_clock",
388 "az_clock",
389 "ax_chainin_pl_clock",
390 "mult_pipeline_clock",
391 "ax_chainin_2nd_pl_clock",
392 "mult_2nd_pipeline_clock",
393 "adder_input_clock",
394 "output_clock",
395];
396
397pub const LEAST_REGISTERS: &[&str] = &["output_clock"];
399
400pub(crate) fn fp_mac(mode: &str, name: &str, used: &[&str], enable: &str, operands: [&str; 3], y: &str, flags: &str) -> String {
406 let params: Vec<String> = CLOCKS.iter().map(|c| format!(".{c}(\"{}\")", if used.contains(c) { "0" } else { "NONE" })).collect();
407 let read = MODE_OPERANDS.iter().find(|(m, _)| *m == mode).unwrap_or_else(|| panic!("no block mode {mode}")).1;
408 let ports: String = ["ax", "ay", "az"].iter().zip(operands).zip(read).filter(|(_, r)| *r).map(|((p, o), _)| format!(".{p}({o}), ")).collect();
409 format!(
410 " wire [31:0] {name}_chain;\n wire {name}_dft;\n fourteennm_fp_mac #({}, .clear_type(\"NONE\"), .use_chainin(\"false\"), \
411 .operation_mode(\"{mode}\"), .adder_subtract(\"false\")) {name}(\n .clk({{2'b00, clk}}), .ena({{2'b00, {enable}}}), .clr(2'b00), \
412 {ports}.accumulate(1'b0), .chainin(32'd0), .resulta({y}), .chainout({name}_chain),\n .mult_inexact({flags}[0]), \
413 .mult_invalid({flags}[1]), .mult_overflow({flags}[2]), .mult_underflow({flags}[3]),\n .adder_inexact({flags}[4]), \
414 .adder_invalid({flags}[5]), .adder_overflow({flags}[6]), .adder_underflow({flags}[7]), .dftout({name}_dft));\n",
415 params.join(", ")
416 )
417}
418
419const MODE_OPERANDS: [(&str, [bool; 3]); 3] = [("sp_mult", [false, true, true]), ("sp_add", [true, true, false]), ("sp_mult_add", [true, true, true])];
422
423fn hard_blocks() -> String {
426 let blocks: [(&str, &str, &[&str], [&str; 3], [&str; 2]); 5] = [
427 ("sp_mult", "hmul", MULTIPLY_REGISTERS, ["32'd0", "opr[31:0]", "opr[63:32]"], ["hm_now", "fm_now"]),
428 ("sp_add", "hadd", ADD_REGISTERS, ["opr[31:0]", "opr[63:32]", "32'd0"], ["ha_now", "fa_now"]),
429 ("sp_mult_add", "hfma", MULTIPLY_ADD_REGISTERS, ["opr[95:64]", "opr[31:0]", "opr[63:32]"], ["hf_now", "ff_now"]),
430 ("sp_mult", "hmul1", LEAST_REGISTERS, ["32'd0", "opr[31:0]", "opr[63:32]"], ["hm1_now", "fm1_now"]),
431 ("sp_add", "hadd1", LEAST_REGISTERS, ["opr[31:0]", "opr[63:32]", "32'd0"], ["ha1_now", "fa1_now"]),
432 ];
433 blocks.iter().map(|&(mode, name, used, operands, [y, flags])| fp_mac(mode, name, used, "step_en", operands, y, flags)).collect()
434}
435
436fn engine_ports() -> Vec<(bool, String, String)> {
438 let bridge = bridge_verilog();
439 let start = bridge.find("input fmid,").expect("the crossing's ports") + "input fmid,".len();
440 let end = start + bridge[start..].find(");").expect("their end");
441 bridge[start..end]
442 .split(",\n")
443 .map(|pair| {
444 let first = pair.split(", ").next().expect("a port pair").trim();
445 let input = first.starts_with("input");
446 let last = first.split_whitespace().last().expect("a name");
447 let name = last.strip_prefix("s_").expect("the shell's side of a port").to_string();
448 let width = first.find('[').map_or(String::new(), |a| first[a..=first.find(']').expect("a width's end")].to_string());
449 (input, width, name)
450 })
451 .collect()
452}
453
454fn top_text() -> String {
457 let ports = engine_ports();
458 let decls: Vec<String> = ports
459 .iter()
460 .map(|(input, width, name)| format!("{} {}{name}", if *input { "input" } else { "output" }, if width.is_empty() { String::new() } else { format!("{width} ") }))
461 .collect();
462 let mut wires = String::new();
463 let mut bridge = vec![".sclk(clk), .fclk(fclk), .frst(frst), .fedge(fedge), .fmid(fmid)".to_string()];
464 let mut core = vec![".clk(fclk), .step_en(fedge), .read_en(cr)".to_string()];
465 for (_, width, name) in &ports {
466 writeln!(wires, " wire {}f_{name};", if width.is_empty() { String::new() } else { format!("{width} ") }).unwrap();
467 bridge.push(format!(".s_{name}({name}), .f_{name}(f_{name})"));
468 core.push(format!(".{name}(f_{name})"));
469 }
470 format!(
471 "// The oracle as the shell's engine: its core stepping in the user clock (fclk) at the compute clock's rising edges\n\
472 // (fedge), its store read the user cycle after the falling edges, the shell's values and its own through the\n\
473 // engine's crossing.\n\
474 module logos_engine\n (input clk,\n {},\n input fclk, input frst, input fedge, input fmid);\n \
475 reg cr;\n always @(posedge fclk) cr <= fmid;\n{wires} logos_engine_bridge bridge({});\n logos_fp_oracle core({});\nendmodule\n",
476 decls.join(",\n "),
477 bridge.join(",\n "),
478 core.join(",\n ")
479 )
480}
481
482const LATE: &str = r#"// x delayed d steps, 0 to 15, at the compute step; every register clear from reset.
484module logos_fpo_late(input clk, input step_en, input rst, input [3:0] d, input [39:0] x, output [39:0] y);
485 reg [39:0] line [1:15];
486 integer i;
487 always @(posedge clk) if (step_en) begin
488 if (rst) for (i = 1; i < 16; i = i + 1) line[i] <= 40'd0;
489 else begin
490 line[1] <= x;
491 for (i = 2; i < 16; i = i + 1) line[i] <= line[i-1];
492 end
493 end
494 assign y = d == 4'd0 ? x : line[d];
495endmodule
496
497"#;
498
499const CORE: &str = r#"// The oracle's core: each step an operand triple (C, B, A) from the run's source into the three hard blocks and a delay
500// line, the soft classes reading the line at the run's tap, so each meets the hard results of its own operands; a counter
501// a class, one more for each result of it the block's differs from; the steps no class explains counted and their
502// records stored, or every step's in a recording run; then the header and the counters stored, and the core idle.
503module logos_fp_oracle
504 (input clk, input step_en, input read_en, input rst,
505 input start, input [31:0] job_at, input [31:0] job_end, input [15:0] rows, input state_f16, input cache_int8, input two_words, input per_block,
506 output busy, output reg fault, output reg [31:0] pc,
507 input host_wr, input [2:0] host_table, input [31:0] host_addr, input [511:0] host_wdata,
508 input [31:0] host_raddr, output [511:0] host_rdata,
509 output mem_rd, output [31:0] mem_rd_line, input mem_rd_valid, input [511:0] mem_rd_data,
510 output mem_wr, output [31:0] mem_wr_line, output [511:0] mem_wr_data, output [63:0] mem_wr_mask, input mem_wr_ready);
511 localparam NM = @NM@, NA = @NA@, NV = @NV@, TAPS = @TAPS@, RECORDS = @RECORDS@, CAP = @CAP@;
512 assign mem_rd = 1'b0;
513 assign mem_rd_line = 32'd0;
514 assign mem_wr = 1'b0;
515 assign mem_wr_line = 32'd0;
516 assign mem_wr_data = 512'd0;
517 assign mem_wr_mask = 64'd0;
518
519 localparam IDLE = 2'd0, RUN = 2'd1, DUMP = 2'd2;
520 reg [1:0] state;
521 assign busy = state != IDLE;
522 reg [31:0] word, left;
523 reg [5:0] drain;
524 reg [63:0] seed, s;
525 reg [@IBM@:0] at;
526 reg [63:0] steps, lone_m, lone_a, records;
527 reg [11:0] beat;
528
529 // The stream: two xorshift words a step.
530 function [63:0] xs(input [63:0] x);
531 reg [63:0] y;
532 begin
533 y = x ^ (x << 13);
534 y = y ^ (y >> 7);
535 xs = y ^ (y << 17);
536 end
537 endfunction
538 wire [63:0] r1 = xs(s), r2 = xs(r1);
539 wire [95:0] rom_v;
540 logos_fpo_vectors rom(.i(at), .v(rom_v));
541 // Near: a's exponent zero, one, two, 255, 254 or any. Half the time b's places the product within -4 to 3 binades of
542 // the least normal (127 - ea + d), a's then from the lower half when any, which b's can meet; a quarter, b's is a's
543 // on a coin or within as many binades of it (ea + d), its significand a's but for the low byte and its sign the
544 // other, so the sum cancels deep; else b's is any, or one of the ends.
545 wire [1:0] kb = r2[7:6];
546 wire [2:0] ka = r2[2:0];
547 wire [7:0] ea = ka == 3'd0 ? 8'd0 : ka == 3'd1 ? 8'd1 : ka == 3'd2 ? 8'd2 : ka == 3'd3 ? 8'd255 : ka == 3'd4 ? 8'd254
548 : kb[1] ? r1[30:23] : {1'b0, r1[29:23]};
549 wire signed [10:0] d = $signed({{8{r2[5]}}, r2[5:3]});
550 wire cancel = kb == 2'd2;
551 wire [7:0] eend = r2[9:8] == 2'd0 ? 8'd0 : r2[9:8] == 2'd1 ? 8'd1 : r2[9:8] == 2'd2 ? 8'd254 : 8'd255;
552 wire signed [10:0] eb_w = !kb[1] ? 11'sd127 - $signed({3'd0, ea}) + d : cancel ? $signed({3'd0, ea}) + (r2[11] ? 11'sd0 : d)
553 : r2[10] ? $signed({3'd0, r1[62:55]}) : $signed({3'd0, eend});
554 wire [7:0] eb = eb_w < 11'sd0 ? 8'd0 : eb_w > 11'sd255 ? 8'd255 : eb_w[7:0];
555 wire [22:0] mb = cancel ? r1[22:0] ^ {15'd0, r1[39:32]} : r1[54:32];
556 wire [95:0] near_v = {r2[63:32], cancel ? !r1[31] : r1[63], eb, mb, r1[31], ea, r1[22:0]};
557 wire [95:0] next_v = word[1:0] == 2'd0 ? rom_v : word[1:0] == 2'd1 ? {r2[31:0], r1} : near_v;
558
559 // The triple the hard blocks take this step, and the delay line the soft classes read at the tap.
560 reg [95:0] opr;
561 reg ov;
562 reg [95:0] dl [0:TAPS-1];
563 reg [TAPS-1:0] dv;
564 wire [3:0] tap = word[7:4];
565 wire [95:0] t = dl[tap];
566 wire tv = dv[tap];
567
568 // The hard blocks' results and flags as they leave them, then each delayed the run's steps for its block.
569 wire [31:0] hm_now, ha_now, hf_now, hm1_now, ha1_now;
570 wire [7:0] fm_now, fa_now, ff_now, fm1_now, fa1_now;
571@HARD@
572 wire [31:0] hm, ha, hf;
573 wire [7:0] fm, fa, ff;
574 logos_fpo_late lm(.clk(clk), .step_en(step_en), .rst(rst), .d(word[11:8]), .x({fm_now, hm_now}), .y({fm, hm}));
575 logos_fpo_late la(.clk(clk), .step_en(step_en), .rst(rst), .d(word[15:12]), .x({fa_now, ha_now}), .y({fa, ha}));
576 logos_fpo_late lf(.clk(clk), .step_en(step_en), .rst(rst), .d(word[19:16]), .x({ff_now, hf_now}), .y({ff, hf}));
577@UNITS@
578 wire [NM-1:0] agree_m = {@AGREE_M@};
579 wire [NA-1:0] agree_a = {@AGREE_A@};
580 wire lonely = !(|agree_m) || !(|agree_a);
581 wire keep = tv && records < 64'd@CAP@ && (word[2] || lonely);
582@COUNTERS@
583 // A record: (B, A), (the hard product, C), (the hard multiply-add, the hard sum), the three blocks' flags {underflow,
584 // overflow, invalid, inexact} of the multiply then the add with the least-registered multiply's result as it leaves
585 // it, the product classes' agreement, the sum classes' agreement and the least-registered add's result.
586 wire [127:0] am = agree_m;
587 wire [95:0] aa = agree_a;
588 wire [511:0] record = {ha1_now, aa, am, 8'd0, hm1_now, ff, fa, fm, hf, ha, hm, t[95:64], t[63:0]};
589 reg [511:0] dump_word;
590 always @* case (beat)
591 0: dump_word = {64'd@NA@, 64'd@NM@, {32'd0, word}, records, lone_a, lone_m, steps, @MAGIC@};
592@DUMP@ default: dump_word = 512'd0;
593 endcase
594 wire st_we = (state == RUN && keep) || state == DUMP;
595 wire [@AB@-1:0] st_wa = state == DUMP ? beat[@AB@-1:0] : RECORDS + records[@AB@-1:0];
596 wire [511:0] st_wd = state == DUMP ? dump_word : record;
597 logos_ram_ce_w512 #(.D(@BEATS@), .A(@AB@)) store(.clk(clk), .ce(step_en), .cr(read_en), .ra(host_raddr[@AB@-1:0]),
598 .rd(host_rdata), .we(st_we), .wa(st_wa), .wd(st_wd), .wm({64{1'b1}}));
599
600 integer i;
601 always @(posedge clk) if (step_en) begin
602 if (rst) begin
603 state <= IDLE; fault <= 1'b0; pc <= 32'd0; seed <= 64'd0; opr <= 96'd0; ov <= 1'b0; dv <= {TAPS{1'b0}};
604 end else begin
605 if (host_wr && host_table == 3'd0 && host_addr == 32'd0) seed <= host_wdata[63:0];
606 case (state)
607 IDLE: if (start) begin
608 state <= RUN; word <= job_at; left <= job_end; drain <= @DRAIN@;
609 s <= seed == 64'd0 ? 64'h9e37_79b9_7f4a_7c15 : seed; at <= 0;
610 steps <= 0; lone_m <= 0; lone_a <= 0; records <= 0; pc <= 32'd0; ov <= 1'b0; dv <= {TAPS{1'b0}};
611 @CLEAR@
612 end
613 RUN: begin
614 if (left != 32'd0) begin
615 opr <= next_v; ov <= 1'b1; left <= left - 32'd1;
616 if (word[1:0] == 2'd0) at <= at == @IB@'d@NVM1@ ? @IB@'d0 : at + 1'b1;
617 else s <= r2;
618 end else begin
619 ov <= 1'b0;
620 if (drain == 6'd0) begin state <= DUMP; beat <= 12'd0; end else drain <= drain - 6'd1;
621 end
622 dl[0] <= opr;
623 for (i = 1; i < TAPS; i = i + 1) dl[i] <= dl[i-1];
624 dv <= {dv[TAPS-2:0], ov};
625 if (tv) begin
626 steps <= steps + 64'd1;
627 pc <= pc + 32'd1;
628 if (!(|agree_m)) lone_m <= lone_m + 64'd1;
629 if (!(|agree_a)) lone_a <= lone_a + 64'd1;
630@BUMP@ end
631 if (keep) records <= records + 64'd1;
632 end
633 default: if (beat == 12'd@RECORDS_M1@) state <= IDLE; else beat <= beat + 12'd1;
634 endcase
635 end
636 end
637endmodule
638"#;
639
640const FP_MAC_HEADER: &str = "module fourteennm_fp_mac #(parameter accumulate_clock = \"none\", ax_clock = \"none\", ay_clock = \"none\",\n \
642 az_clock = \"none\", accum_pipeline_clock = \"none\", ax_chainin_pl_clock = \"none\", mult_pipeline_clock = \"none\",\n \
643 accum_2nd_pipeline_clock = \"none\", ax_chainin_2nd_pl_clock = \"none\", mult_2nd_pipeline_clock = \"none\",\n \
644 accum_adder_clock = \"none\", adder_input_clock = \"none\", output_clock = \"none\", clear_type = \"NONE\",\n \
645 use_chainin = \"false\", operation_mode = \"sp_mult\", adder_subtract = \"false\")\n \
646 (input [2:0] clk, input [2:0] ena, input [1:0] clr, input [31:0] ax, input [31:0] ay, input [31:0] az,\n \
647 input accumulate, input [31:0] chainin, input dfxlfsrena, input dfxmisrena, output [31:0] resulta,\n \
648 output [31:0] chainout, output adder_inexact, output adder_invalid, output adder_overflow, output adder_underflow,\n \
649 output mult_inexact, output mult_invalid, output mult_overflow, output mult_underflow, output dftout);\n";
650
651pub fn fp_mac_blackbox() -> String {
654 format!("(* blackbox, logos_box_area = \"0,0,2,0,0\" *)\n{FP_MAC_HEADER}endmodule\n")
655}
656
657#[derive(Clone, Copy, Debug, PartialEq, Eq)]
660pub struct Depths {
661 pub full: [usize; 3],
662 pub least: [usize; 2],
663}
664
665pub fn hard_model_verilog(product: &Semantics, sum: &Semantics, depths: Depths) -> String {
671 for latency in depths.full.iter().chain(&depths.least) {
672 assert!((1..=TAPS).contains(latency), "a block {latency} steps deep");
673 }
674 let ([lm, la, lf], [lm1, la1]) = (depths.full, depths.least);
675 format!(
676 "{}{}// A floating-point DSP block for simulation: the multiply ay·az ({lm} steps deep, {lm1} with its pipeline unregistered),\n\
677 // the add ax+ay ({la}, {la1}), the multiply-add ax+ay·az ({lf}), at clk[0] where ena[0].\n\
678 {FP_MAC_HEADER} wire [31:0] p, q, r;\n \
679 logos_fpo_hard_mul m(.a(ay), .b(az), .y(p));\n \
680 logos_fpo_hard_add s(.a(ax), .b(ay), .y(q));\n \
681 logos_fpo_hard_add f(.a(ax), .b(p), .y(r));\n \
682 wire [31:0] y = operation_mode == \"sp_mult\" ? p : operation_mode == \"sp_add\" ? q : r;\n \
683 localparam integer L = operation_mode == \"sp_mult\" ? (mult_pipeline_clock == \"NONE\" ? {lm1} : {lm})\n \
684 \x20 : operation_mode == \"sp_add\" ? (adder_input_clock == \"NONE\" ? {la1} : {la}) : {lf};\n \
685 // Every register zero from configuration, as the device's are.\n \
686 reg [31:0] line [0:{last}];\n \
687 integer i;\n \
688 initial for (i = 0; i <= {last}; i = i + 1) line[i] = 32'd0;\n \
689 always @(posedge clk[0]) if (ena[0]) begin\n line[0] <= y;\n for (i = 1; i <= {last}; i = i + 1) line[i] <= line[i-1];\n end\n \
690 assign resulta = line[L-1];\n \
691 assign chainout = 32'd0;\n \
692 assign {{adder_inexact, adder_invalid, adder_overflow, adder_underflow}} = 4'd0;\n \
693 assign {{mult_inexact, mult_invalid, mult_overflow, mult_underflow}} = 4'd0;\n \
694 assign dftout = 1'b0;\nendmodule\n",
695 fmul_verilog("logos_fpo_hard_mul", product),
696 fadd_verilog("logos_fpo_hard_add", sum),
697 last = TAPS - 1,
698 )
699}
700
701fn shell_engine() -> EngineParams {
704 EngineParams {
705 rows: 1,
706 vector_rows: 1,
707 side_rows: 1,
708 pair_rows: 1,
709 store_beats: STORE_BEATS,
710 job_words: 1,
711 tensors: 1,
712 layers: 1,
713 ropes: 1,
714 formats: Vec::new(),
715 delta: None,
716 attend: None,
717 function_units: 16,
718 folds: false,
719 streaming: None,
720 }
721}
722
723pub fn persona_config(oracle: &Oracle, divide: u32) -> AfuConfig {
726 let digest = sha256(oracle.engine_verilog().as_bytes());
727 AfuConfig {
728 name: "logos_d5005_fp_oracle",
729 build: u64::from_be_bytes(digest[..8].try_into().expect("eight bytes")),
730 compute_divide: divide,
731 ..AfuConfig::engine(shell_engine(), LocalMemoryLine::Data512)
732 }
733}
734
735pub fn persona_sources(oracle: &Oracle, divide: u32, model: &Semantics, depths: Depths) -> Vec<AfuFile> {
741 let engine = oracle.engine_verilog();
742 let mut files: Vec<AfuFile> = afu_sources(&persona_config(oracle, divide))
743 .into_iter()
744 .map(|f| match f.path {
745 "hw/rtl/logos_engine.v" => AfuFile { path: f.path, text: engine.clone() },
746 "sim/logos_engine_gated.v" => AfuFile { path: f.path, text: format!("{engine}\n{}", hard_model_verilog(model, model, depths)) },
747 p if p.ends_with("logos_d5005_domains.json") => {
749 assert_eq!(f.text.matches("\"logos_engine_core_ce\"").count(), 1, "the engine's core in the clock-domain map");
750 AfuFile { path: f.path, text: f.text.replace("\"logos_engine_core_ce\"", "\"logos_fp_oracle\"") }
751 }
752 _ => f,
753 })
754 .collect();
755 files.push(AfuFile { path: "sw/logos_fp_oracle.c", text: HOST_PROGRAM.to_string() });
756 files.push(AfuFile { path: "sw/logos_fp_oracle.h", text: host_header(oracle) });
757 files
758}
759
760#[derive(Clone, Copy, Debug, PartialEq, Eq)]
762pub struct Header {
763 pub magic: u64,
764 pub steps: u64,
766 pub lone_products: u64,
768 pub lone_sums: u64,
769 pub records: u64,
771 pub run: u32,
772 pub product_classes: u64,
773 pub sum_classes: u64,
774}
775
776#[derive(Clone, Debug, PartialEq, Eq)]
780pub struct Record {
781 pub a: u32,
782 pub b: u32,
783 pub c: u32,
784 pub product: u32,
785 pub sum: u32,
786 pub fma: u32,
787 pub flags: [u8; 3],
788 pub products_agree: Vec<bool>,
789 pub sums_agree: Vec<bool>,
790 pub least: [u32; 2],
792}
793
794#[derive(Clone, Debug, PartialEq, Eq)]
796pub struct Store {
797 pub header: Header,
798 pub products: Vec<u64>,
799 pub sums: Vec<u64>,
800 pub records: Vec<Record>,
801}
802
803impl Store {
804 pub fn read(oracle: &Oracle, beats: &[[u64; 8]]) -> Option<Store> {
807 let h = beats.first()?;
808 let header = Header {
809 magic: h[0],
810 steps: h[1],
811 lone_products: h[2],
812 lone_sums: h[3],
813 records: h[4],
814 run: h[5] as u32,
815 product_classes: h[6],
816 sum_classes: h[7],
817 };
818 let (nm, na) = (oracle.products.len(), oracle.sums.len());
819 if header.magic != MAGIC || header.product_classes != nm as u64 || header.sum_classes != na as u64 {
820 return None;
821 }
822 let counters: Vec<u64> = beats.get(1..oracle.records_at())?.iter().flatten().copied().collect();
823 let records = beats
824 .get(oracle.records_at()..oracle.records_at() + header.records as usize)?
825 .iter()
826 .map(|w| {
827 let bit = |base: usize, k: usize| w[base + k / 64] >> (k % 64) & 1 == 1;
828 Record {
829 a: w[0] as u32,
830 b: (w[0] >> 32) as u32,
831 c: w[1] as u32,
832 product: (w[1] >> 32) as u32,
833 sum: w[2] as u32,
834 fma: (w[2] >> 32) as u32,
835 flags: [w[3] as u8, (w[3] >> 8) as u8, (w[3] >> 16) as u8],
836 products_agree: (0..nm).map(|k| bit(4, k)).collect(),
837 sums_agree: (0..na).map(|k| bit(6, k)).collect(),
838 least: [(w[3] >> 24) as u32, (w[7] >> 32) as u32],
839 }
840 })
841 .collect();
842 Some(Store { header, products: counters[..nm].to_vec(), sums: counters[nm..nm + na].to_vec(), records })
843 }
844}
845
846pub fn host_header(oracle: &Oracle) -> String {
849 let mut h = String::from("/* The oracle the persona was built with: its classes, by their members' names, and its store's layout. */\n");
850 writeln!(h, "#define LOGOS_FPO_MAGIC 0x{MAGIC:016x}ull").unwrap();
851 writeln!(h, "#define LOGOS_FPO_PRODUCTS {}", oracle.products.len()).unwrap();
852 writeln!(h, "#define LOGOS_FPO_SUMS {}", oracle.sums.len()).unwrap();
853 writeln!(h, "#define LOGOS_FPO_VECTORS {}", oracle.vectors.len()).unwrap();
854 writeln!(h, "#define LOGOS_FPO_TAPS {TAPS}").unwrap();
855 writeln!(h, "#define LOGOS_FPO_STORE_BEATS {STORE_BEATS}").unwrap();
856 writeln!(h, "#define LOGOS_FPO_RECORDS_AT {}", oracle.records_at()).unwrap();
857 writeln!(h, "#define LOGOS_FPO_RECORD_CAP {}", oracle.record_cap()).unwrap();
858 for (name, classes) in [("logos_fpo_products", &oracle.products), ("logos_fpo_sums", &oracle.sums)] {
859 writeln!(h, "static const char *const {name}[] = {{").unwrap();
860 for c in classes {
861 writeln!(h, " \"{}\",", c.members.join(" ")).unwrap();
862 }
863 h.push_str("};\n");
864 }
865 h
866}
867
868pub const HOST_PROGRAM: &str = r##"/* Measure the card's floating-point DSP block with the oracle persona: find the multiply's and the add's latencies
876 as the taps at which their classes come nearest every vector, the multiply-add's as the shift at which its results with
877 C zero are the products; then, the three aligned, record every vector's results and count each class's
878 disagreements over <steps> triples of the random stream and of the near stream. One JSON object on stdout.
879
880 logos_fp_oracle [<steps>] [--seed <n>] */
881#include "logos_core.h"
882#include "logos_fp_oracle.h"
883
884#include <inttypes.h>
885#include <stdio.h>
886#include <stdlib.h>
887#include <string.h>
888
889#ifndef LOGOS_PROGRAM_MAIN
890#define LOGOS_PROGRAM_MAIN main
891#endif
892
893static uint64_t tag = 1;
894
895static int run(logos_dev *dev, uint8_t op, uint64_t a0, uint64_t a1, uint64_t a2) {
896 uint64_t t = tag++;
897 logos_record r = logos_core_run(dev, op, t, a0, a1, a2);
898 return (r.status >> 8) == t && (r.status & 0xff) == LOGOS_D5005_STATUS_OK;
899}
900
901/* A run's word: its source, whether it records every step, the soft tap, each block's delay. */
902static uint64_t run_word(unsigned source, int record, unsigned tap, const unsigned d[3]) {
903 return source | (uint64_t)(record != 0) << 2 | (uint64_t)(tap & 15) << 4 | (uint64_t)(d[0] & 15) << 8 |
904 (uint64_t)(d[1] & 15) << 12 | (uint64_t)(d[2] & 15) << 16;
905}
906
907/* One run of `steps` triples, its store read into `buf`: the header and the counters, then the records the header
908 counts. */
909static int oracle_run(logos_dev *dev, uint64_t *buf, uint64_t io, uint64_t word, uint64_t steps) {
910 if (!run(dev, LOGOS_D5005_OP_ENGINE_RUN, word, steps, 0)) return 0;
911 if (!run(dev, LOGOS_D5005_OP_STORE_READ, io >> 6, 0, LOGOS_FPO_RECORDS_AT)) return 0;
912 if (buf[0] != LOGOS_FPO_MAGIC || buf[6] != LOGOS_FPO_PRODUCTS || buf[7] != LOGOS_FPO_SUMS || buf[4] > LOGOS_FPO_RECORD_CAP) return 0;
913 return buf[4] == 0 || run(dev, LOGOS_D5005_OP_STORE_READ, (io >> 6) + LOGOS_FPO_RECORDS_AT, LOGOS_FPO_RECORDS_AT, buf[4]);
914}
915
916static uint64_t counter(const uint64_t *buf, unsigned k) { return buf[8 + k]; }
917
918static uint64_t least(const uint64_t *buf, unsigned from, unsigned n) {
919 uint64_t m = UINT64_MAX;
920 for (unsigned k = 0; k < n; ++k) if (counter(buf, from + k) < m) m = counter(buf, from + k);
921 return m;
922}
923
924/* Record r's words: (B, A), (the product, C), (the multiply-add, the sum), the flags with the least-registered
925 multiply's result above them, the product classes' agreement in two words, the sum classes' with the
926 least-registered add's result above it. */
927static const uint64_t *record(const uint64_t *buf, uint64_t r) { return buf + 8 * (LOGOS_FPO_RECORDS_AT + r); }
928
929/* The 32 bits of word `word` of record `r` from bit `at`. */
930static uint32_t field(const uint64_t *buf, uint64_t r, unsigned word, unsigned at) { return (uint32_t)(record(buf, r)[word] >> at); }
931
932/* The shift s at which field `seen` of record k is, most often, field `want` of record k + s, over the records whose C
933 is zero when `c_zero` (over all otherwise); -1 when no shift matches more than half of them. A result recorded as it
934 leaves a block of latency L, the run at tap t, is that of the record t + 1 - L on. */
935static int best_shift(const uint64_t *buf, const unsigned seen[2], const unsigned want[2], int c_zero, uint64_t *best, uint64_t *n) {
936 int shift = -1;
937 *best = 0;
938 *n = 0;
939 for (unsigned s = 0; s < LOGOS_FPO_TAPS; ++s) {
940 uint64_t matches = 0, of = 0;
941 for (uint64_t k = 0; k + s < buf[4]; ++k) {
942 if (c_zero && field(buf, k + s, 1, 0) != 0) continue;
943 ++of;
944 if (field(buf, k, seen[0], seen[1]) == field(buf, k + s, want[0], want[1])) ++matches;
945 }
946 if (matches > *best) *best = matches, *n = of, shift = (int)s;
947 }
948 return shift >= 0 && 2 * *best > *n ? shift : -1;
949}
950
951static void counters(const uint64_t *buf, const char *name) {
952 printf(" \"%s\": {\"steps\": %" PRIu64 ", \"lone_products\": %" PRIu64 ", \"lone_sums\": %" PRIu64 ", \"records\": %" PRIu64 ",\n",
953 name, buf[1], buf[2], buf[3], buf[4]);
954 printf(" \"products\": [");
955 for (unsigned k = 0; k < LOGOS_FPO_PRODUCTS; ++k) printf("%s%" PRIu64, k ? ", " : "", counter(buf, k));
956 printf("],\n \"sums\": [");
957 for (unsigned k = 0; k < LOGOS_FPO_SUMS; ++k) printf("%s%" PRIu64, k ? ", " : "", counter(buf, LOGOS_FPO_PRODUCTS + k));
958 printf("]}");
959}
960
961static void records(const uint64_t *buf) {
962 printf(" \"records\": [");
963 for (uint64_t r = 0; r < buf[4]; ++r) {
964 const uint64_t *w = record(buf, r);
965 printf("%s\n [\"%08" PRIx32 "\", \"%08" PRIx32 "\", \"%08" PRIx32 "\", \"%08" PRIx32 "\", \"%08" PRIx32 "\", \"%08" PRIx32 "\", \"%06" PRIx32 "\", \"%08" PRIx32 "\", \"%08" PRIx32 "\"]",
966 r ? "," : "", (uint32_t)w[0], (uint32_t)(w[0] >> 32), (uint32_t)w[1], (uint32_t)(w[1] >> 32), (uint32_t)w[2],
967 (uint32_t)(w[2] >> 32), (uint32_t)(w[3] & 0xffffff), (uint32_t)(w[3] >> 24), (uint32_t)(w[7] >> 32));
968 }
969 printf("]");
970}
971
972int LOGOS_PROGRAM_MAIN(int argc, char **argv) {
973 uint64_t steps = 1ull << 24, seed = 0x5eedf00du;
974 for (int i = 1; i < argc; ++i) {
975 if (strcmp(argv[i], "--seed") == 0 && i + 1 < argc) seed = strtoull(argv[++i], NULL, 0);
976 else steps = strtoull(argv[i], NULL, 0);
977 }
978 logos_dev *dev = logos_open(LOGOS_D5005_AFU_ID);
979 if (!dev) {
980 fprintf(stderr, "logos_fp_oracle: no card\n");
981 return 2;
982 }
983 uint64_t io = 0;
984 uint64_t *buf = logos_buffer(dev, (size_t)LOGOS_FPO_STORE_BEATS * 64, &io);
985 memset(buf, 0, (size_t)LOGOS_FPO_STORE_BEATS * 64);
986 buf[0] = seed;
987 int ok = run(dev, LOGOS_D5005_OP_ENGINE_TABLE, io >> 6, 0, 1);
988 const unsigned none[3] = {0, 0, 0};
989 int tm = -1, ta = -1, lf = -1;
990 uint64_t pmin = UINT64_MAX, smin = UINT64_MAX;
991 printf("{\n \"seed\": %" PRIu64 ",\n \"taps\": [", seed);
992 for (unsigned t = 0; ok && t < LOGOS_FPO_TAPS; ++t) {
993 ok = oracle_run(dev, buf, io, run_word(0, 0, t, none), LOGOS_FPO_VECTORS);
994 uint64_t p = ok ? least(buf, 0, LOGOS_FPO_PRODUCTS) : UINT64_MAX, s = ok ? least(buf, LOGOS_FPO_PRODUCTS, LOGOS_FPO_SUMS) : UINT64_MAX;
995 printf("%s{\"tap\": %u, \"least_product\": %" PRIu64 ", \"least_sum\": %" PRIu64 "}", t ? ", " : "", t, p, s);
996 if (ok && p < pmin) pmin = p, tm = (int)t;
997 if (ok && s < smin) smin = s, ta = (int)t;
998 }
999 printf("],\n");
1000 /* The multiply-add's results at the last tap undelayed: the shift at which they are the products of the triples
1001 whose C is zero. */
1002 uint64_t fbest = 0, fn = 0, mbest = 0, mn = 0, abest = 0, an = 0;
1003 const unsigned fma_at[2] = {2, 32}, product_at[2] = {1, 32}, sum_at[2] = {2, 0}, least_product[2] = {3, 24}, least_sum[2] = {7, 32};
1004 if (ok && tm >= 0 && ta >= 0) {
1005 const unsigned d[3] = {15u - (unsigned)tm, 15u - (unsigned)ta, 0};
1006 ok = oracle_run(dev, buf, io, run_word(0, 1, 15, d), LOGOS_FPO_VECTORS);
1007 int s = ok ? best_shift(buf, fma_at, product_at, 1, &fbest, &fn) : -1;
1008 if (s >= 0) lf = 16 - s;
1009 }
1010 int found = tm >= 0 && ta >= 0 && lf > 0, lm1 = -1, la1 = -1;
1011 unsigned tap = 0, d[3] = {0, 0, 0};
1012 if (ok && found) {
1013 int l[3] = {tm + 1, ta + 1, lf};
1014 int top = l[0] > l[1] ? l[0] : l[1];
1015 top = top > l[2] ? top : l[2];
1016 tap = (unsigned)(top - 1);
1017 for (int k = 0; k < 3; ++k) d[k] = tap + 1 - (unsigned)l[k];
1018 ok = oracle_run(dev, buf, io, run_word(0, 1, tap, d), LOGOS_FPO_VECTORS);
1019 /* The least-registered blocks' results as they leave them: the shift at which they are the aligned ones'. */
1020 int sm = ok ? best_shift(buf, least_product, product_at, 0, &mbest, &mn) : -1, sa = ok ? best_shift(buf, least_sum, sum_at, 0, &abest, &an) : -1;
1021 if (sm >= 0) lm1 = (int)tap + 1 - sm;
1022 if (sa >= 0) la1 = (int)tap + 1 - sa;
1023 }
1024 printf(" \"latency\": {\"product\": %d, \"sum\": %d, \"multiply_add\": %d, \"product_least\": %d, \"sum_least\": %d,\n", tm + 1, ta + 1, lf, lm1, la1);
1025 printf(" \"matches\": {\"multiply_add\": [%" PRIu64 ", %" PRIu64 "], \"product_least\": [%" PRIu64 ", %" PRIu64 "], \"sum_least\": [%" PRIu64 ", %" PRIu64 "]}},\n",
1026 fbest, fn, mbest, mn, abest, an);
1027 printf(" \"runs\": {\n");
1028 if (ok && found) {
1029 counters(buf, "vectors");
1030 printf(",\n");
1031 records(buf);
1032 const char *names[2] = {"random", "near"};
1033 for (unsigned source = 1; ok && source <= 2; ++source) {
1034 ok = oracle_run(dev, buf, io, run_word(source, 0, tap, d), steps);
1035 if (ok) {
1036 printf(",\n");
1037 counters(buf, names[source - 1]);
1038 }
1039 }
1040 printf("\n },\n \"run_word\": %" PRIu64 ",\n", run_word(0, 0, tap, d));
1041 } else {
1042 printf("\n },\n");
1043 }
1044 printf(" \"complete\": %s\n}\n", ok ? "true" : "false");
1045 logos_buffer_free(dev, buf);
1046 logos_close(dev);
1047 return !ok ? 2 : !found ? 1 : 0;
1048}
1049"##;
1050
1051#[cfg(test)]
1052#[path = "../../../logicaffeine_tests/unit/logicaffeine_compile/codegen_sva/fp_oracle/tests.rs"]
1053mod tests;