1use std::collections::HashMap;
25
26use crate::aig::{Aig, AigLit, Init, Leaf, Node};
27use crate::fraig;
28use crate::lrat::{self, Lrat};
29use crate::sweep::{self, MiterError, Options, Outcome, Report};
30
31pub fn unroll(aig: &Aig, frames: usize) -> Aig {
34 let mut out = Aig::new();
35 let mut frame = Frames::new(aig, &mut out, "@init");
36 for k in 0..frames {
37 let map = frame.step(aig, &mut out, k);
38 for o in aig.outputs() {
39 let lit = at(&map, o.lit);
40 out.add_output(lit, o.name.as_ref().map(|n| format!("{n}@{k}")));
41 }
42 frame.advance(aig, &map);
43 }
44 out
45}
46
47pub fn at_latency(aig: &Aig, latency: usize) -> Aig {
50 at_latency_with(aig, latency, "@init")
51}
52
53fn at_latency_with(aig: &Aig, latency: usize, init: &str) -> Aig {
54 let mut out = Aig::new();
55 let mut frame = Frames::new(aig, &mut out, init);
56 for k in 0..=latency {
57 let map = frame.step(aig, &mut out, k);
58 if k == latency {
59 for o in aig.outputs() {
60 let lit = at(&map, o.lit);
61 out.add_output(lit, o.name.clone());
62 }
63 } else {
64 frame.advance(aig, &map);
65 }
66 }
67 out
68}
69
70fn at(map: &[AigLit], l: AigLit) -> AigLit {
71 map[l.node() as usize].with_phase(l.is_complemented())
72}
73
74struct Frames {
77 first: Vec<AigLit>,
78 state: Vec<AigLit>,
79}
80
81impl Frames {
82 fn new(aig: &Aig, out: &mut Aig, init: &str) -> Frames {
83 let first = aig.inputs().iter().map(|p| out.add_input(p.name.clone())).collect();
84 let state = aig
85 .latches()
86 .iter()
87 .map(|l| match l.init {
88 Init::Zero => AigLit::FALSE,
89 Init::One => AigLit::TRUE,
90 Init::Free => out.add_input(l.name.as_ref().map(|n| format!("{n}{init}"))),
91 })
92 .collect();
93 Frames { first, state }
94 }
95
96 fn step(&mut self, aig: &Aig, out: &mut Aig, k: usize) -> Vec<AigLit> {
98 let inputs: Vec<AigLit> = if k == 0 {
99 self.first.clone()
100 } else {
101 aig.inputs().iter().map(|p| out.add_input(p.name.as_ref().map(|n| format!("{n}@{k}")))).collect()
102 };
103 let state = &self.state;
104 out.import(aig, &mut |leaf| match leaf {
105 Leaf::Input(i) => inputs[i],
106 Leaf::Latch(j) => state[j],
107 })
108 }
109
110 fn advance(&mut self, aig: &Aig, map: &[AigLit]) {
111 self.state = aig.latches().iter().map(|l| at(map, l.next)).collect();
112 }
113}
114
115pub fn aligned(
118 left: &Aig,
119 left_latency: usize,
120 right: &Aig,
121 right_latency: usize,
122 opts: &Options,
123) -> Result<Report, MiterError> {
124 let (l, r) = aligned_pair(left, left_latency, right, right_latency);
125 sweep::equivalent(&l, &r, opts)
126}
127
128pub fn aligned_pair(left: &Aig, left_latency: usize, right: &Aig, right_latency: usize) -> (Aig, Aig) {
131 (at_latency_with(left, left_latency, "@init.left"), at_latency_with(right, right_latency, "@init.right"))
132}
133
134pub fn latency_equivalent(pipeline: &Aig, reference: &Aig, latency: usize, opts: &Options) -> Result<Report, MiterError> {
136 aligned(pipeline, latency, reference, 0, opts)
137}
138
139pub fn aligned_by_fraig(
141 left: &Aig,
142 left_latency: usize,
143 right: &Aig,
144 right_latency: usize,
145 opts: &fraig::Options,
146) -> Result<fraig::Report, MiterError> {
147 let (l, r) = aligned_pair(left, left_latency, right, right_latency);
148 fraig::equivalent(&l, &r, opts)
149}
150
151pub fn latency_equivalent_by_fraig(
153 pipeline: &Aig,
154 reference: &Aig,
155 latency: usize,
156 opts: &fraig::Options,
157) -> Result<fraig::Report, MiterError> {
158 aligned_by_fraig(pipeline, latency, reference, 0, opts)
159}
160
161#[derive(Clone, Debug)]
164pub struct Product {
165 pub aig: Aig,
166 pub pairs: Vec<(String, AigLit, AigLit)>,
167}
168
169pub fn product(left: &Aig, right: &Aig) -> Result<Product, MiterError> {
171 for g in [left, right] {
172 let mut seen = std::collections::HashSet::new();
173 for o in g.outputs() {
174 let name = o.name.as_ref().ok_or(MiterError::Unnamed)?;
175 if !seen.insert(name) {
176 return Err(MiterError::Duplicate(name.clone()));
177 }
178 }
179 }
180 let mut aig = Aig::new();
181 let mut by_name: HashMap<String, AigLit> = HashMap::new();
182 let left_inputs: Vec<AigLit> = left
183 .inputs()
184 .iter()
185 .map(|p| {
186 let l = aig.add_input(p.name.clone());
187 if let Some(n) = &p.name {
188 by_name.insert(n.clone(), l);
189 }
190 l
191 })
192 .collect();
193 let right_inputs: Vec<AigLit> = right
194 .inputs()
195 .iter()
196 .map(|p| match p.name.as_ref().and_then(|n| by_name.get(n)) {
197 Some(&l) => l,
198 None => aig.add_input(p.name.clone()),
199 })
200 .collect();
201 let side = |g: &Aig, inputs: &[AigLit], tag: &str, aig: &mut Aig| -> Vec<AigLit> {
202 let base = aig.latches().len();
203 let states: Vec<AigLit> = g
204 .latches()
205 .iter()
206 .map(|l| {
207 let k = aig.add_latch(l.name.as_ref().map(|n| format!("{tag}.{n}")), l.init);
208 aig.latch_lit(k)
209 })
210 .collect();
211 let map = aig.import(g, &mut |leaf| match leaf {
212 Leaf::Input(i) => inputs[i],
213 Leaf::Latch(j) => states[j],
214 });
215 for (j, l) in g.latches().iter().enumerate() {
216 aig.set_next(base + j, at(&map, l.next));
217 }
218 map
219 };
220 let lmap = side(left, &left_inputs, "left", &mut aig);
221 let rmap = side(right, &right_inputs, "right", &mut aig);
222 let mut pairs = Vec::with_capacity(left.outputs().len());
223 for o in left.outputs() {
224 let name = o.name.clone().expect("named above");
225 let k = right.output_index(&name).ok_or_else(|| MiterError::Unmatched(name.clone()))?;
226 pairs.push((name, at(&lmap, o.lit), at(&rmap, right.outputs()[k].lit)));
227 }
228 if let Some(o) = right.outputs().iter().find(|o| left.output_index(o.name.as_deref().unwrap()).is_none()) {
229 return Err(MiterError::Unmatched(o.name.clone().unwrap()));
230 }
231 for (name, a, b) in &pairs {
232 let d = aig.xor(*a, *b);
233 aig.add_output(d, Some(name.clone()));
234 }
235 Ok(Product { aig, pairs })
236}
237
238#[derive(Clone, Debug)]
240pub struct SeqOptions {
241 pub sim_cycles: usize,
243 pub sim_words: usize,
245 pub seed: u64,
246 pub bmc_depth: usize,
248 pub budget: u64,
250}
251
252impl Default for SeqOptions {
253 fn default() -> SeqOptions {
254 SeqOptions { sim_cycles: 64, sim_words: 4, seed: 0x5eed_c0de_f00d_a16e, bmc_depth: 32, budget: 20_000 }
255 }
256}
257
258#[derive(Clone, Debug, PartialEq, Eq)]
261pub struct Trace {
262 pub init: Vec<bool>,
263 pub frames: Vec<Vec<bool>>,
264}
265
266#[derive(Clone, Debug)]
271pub struct InductionCertificate {
272 pub invariant: Vec<(AigLit, AigLit)>,
273 pub steps: Vec<Lrat>,
274 pub outputs: Vec<Option<Lrat>>,
275}
276
277#[derive(Clone, Debug)]
278pub enum SeqVerdict {
279 Equal(InductionCertificate),
280 Differ(Trace),
281 Unknown(String),
282}
283
284#[derive(Clone, Debug)]
285pub struct SeqReport {
286 pub product: Product,
287 pub verdict: SeqVerdict,
288}
289
290fn reset_value(aig: &Aig, l: AigLit) -> Option<bool> {
292 let v = match aig.node(l.node()) {
293 Node::False => false,
294 Node::Latch(k) => match aig.latches()[k].init {
295 Init::Zero => false,
296 Init::One => true,
297 Init::Free => return None,
298 },
299 _ => return None,
300 };
301 Some(v ^ l.is_complemented())
302}
303
304fn next(aig: &Aig, l: AigLit) -> AigLit {
306 match aig.node(l.node()) {
307 Node::Latch(k) => aig.latches()[k].next.with_phase(l.is_complemented()),
308 _ => l,
309 }
310}
311
312pub fn check_always(product: &Product, cert: &InductionCertificate) -> bool {
314 let aig = &product.aig;
315 let n = aig.num_nodes();
316 let register = |l: AigLit| (l.node() as usize) < n && matches!(aig.node(l.node()), Node::Latch(_) | Node::False);
317 if cert.steps.len() != cert.invariant.len() || cert.outputs.len() != product.pairs.len() {
318 return false;
319 }
320 for &(a, b) in &cert.invariant {
321 if !register(a) || !register(b) {
322 return false;
323 }
324 match (reset_value(aig, a), reset_value(aig, b)) {
325 (Some(x), Some(y)) if x == y => {}
326 _ => return false,
327 }
328 }
329 for (&(a, b), proof) in cert.invariant.iter().zip(&cert.steps) {
330 let (na, nb) = (next(aig, a), next(aig, b));
331 if na == nb {
332 continue;
333 }
334 let q = sweep::query(aig, na, nb, &cert.invariant);
335 if !lrat::check(q.num_vars, &q.clauses, proof) {
336 return false;
337 }
338 }
339 for ((_, l, r), proof) in product.pairs.iter().zip(&cert.outputs) {
340 match proof {
341 None if l == r => {}
342 None => return false,
343 Some(proof) => {
344 let q = sweep::query(aig, *l, *r, &cert.invariant);
345 if !lrat::check(q.num_vars, &q.clauses, proof) {
346 return false;
347 }
348 }
349 }
350 }
351 true
352}
353
354pub fn replays(product: &Product, trace: &Trace) -> bool {
356 let aig = &product.aig;
357 if trace.frames.is_empty() || trace.init.len() != aig.latches().len() {
358 return false;
359 }
360 let mut state: Vec<bool> = aig
361 .latches()
362 .iter()
363 .zip(&trace.init)
364 .map(|(l, &free)| match l.init {
365 Init::Zero => false,
366 Init::One => true,
367 Init::Free => free,
368 })
369 .collect();
370 for (f, inputs) in trace.frames.iter().enumerate() {
371 if inputs.len() != aig.inputs().len() {
372 return false;
373 }
374 let v = aig.eval(inputs, &state);
375 if f + 1 == trace.frames.len() {
376 return product.pairs.iter().any(|(_, l, r)| Aig::value(&v, *l) != Aig::value(&v, *r));
377 }
378 state = aig.latches().iter().map(|l| Aig::value(&v, l.next)).collect();
379 }
380 false
381}
382
383fn register_signatures(aig: &Aig, opts: &SeqOptions) -> Vec<Vec<u64>> {
385 let words = opts.sim_words.max(1);
386 let mut rng = opts.seed;
387 let mut next_word = || {
388 rng = rng.wrapping_add(0x9e37_79b9_7f4a_7c15);
389 let mut z = rng;
390 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
391 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
392 z ^ (z >> 31)
393 };
394 let mut state: Vec<u64> = Vec::with_capacity(aig.latches().len() * words);
395 for l in aig.latches() {
396 for _ in 0..words {
397 state.push(match l.init {
398 Init::Zero => 0,
399 Init::One => !0,
400 Init::Free => next_word(),
401 });
402 }
403 }
404 let mut sigs = vec![Vec::with_capacity(opts.sim_cycles * words); aig.num_nodes()];
405 for _ in 0..opts.sim_cycles.max(1) {
406 let inputs: Vec<u64> = (0..aig.inputs().len() * words).map(|_| next_word()).collect();
407 let sim = aig.simulate(words, &inputs, &state);
408 for (k, l) in aig.latches().iter().enumerate() {
409 sigs[l.node as usize].extend_from_slice(&state[k * words..(k + 1) * words]);
410 }
411 let mut next_state = Vec::with_capacity(state.len());
412 for l in aig.latches() {
413 for w in 0..words {
414 next_state.push(Aig::word(&sim, words, l.next, w));
415 }
416 }
417 state = next_state;
418 }
419 sigs
420}
421
422fn candidates(aig: &Aig, sigs: &[Vec<u64>]) -> Vec<(AigLit, AigLit)> {
425 let mut classes: HashMap<Vec<u64>, AigLit> = HashMap::new();
426 let len = sigs.iter().map(|s| s.len()).max().unwrap_or(0);
427 classes.insert(vec![0; len], AigLit::FALSE);
428 let mut pairs = Vec::new();
429 for l in aig.latches() {
430 if l.init == Init::Free {
431 continue;
432 }
433 let sig = &sigs[l.node as usize];
434 let phase = sig.first().is_some_and(|w| w & 1 == 1);
435 let key: Vec<u64> = sig.iter().map(|&w| if phase { !w } else { w }).collect();
436 let lit = AigLit::new(l.node, false);
437 match classes.get(&key) {
438 Some(&rep) => pairs.push((lit, rep.with_phase(phase))),
439 None => {
440 classes.insert(key, lit.with_phase(phase));
441 }
442 }
443 }
444 pairs
445}
446
447pub fn always_equal(left: &Aig, right: &Aig, opts: &SeqOptions) -> Result<SeqReport, MiterError> {
449 let product = product(left, right)?;
450 let verdict = decide_always(&product, opts);
451 Ok(SeqReport { product, verdict })
452}
453
454fn decide_always(product: &Product, opts: &SeqOptions) -> SeqVerdict {
455 let aig = &product.aig;
456 let sigs = register_signatures(aig, opts);
457 let mut invariant = candidates(aig, &sigs);
458 let steps = 'fixpoint: loop {
459 let mut steps = Vec::with_capacity(invariant.len());
460 for (i, &(a, b)) in invariant.iter().enumerate() {
461 let (na, nb) = (next(aig, a), next(aig, b));
462 if na == nb {
463 steps.push(Lrat { steps: Vec::new() });
464 continue;
465 }
466 match sweep::prove(aig, na, nb, &invariant, opts.budget) {
467 Outcome::Equal(proof) => steps.push(proof),
468 Outcome::Differ(..) | Outcome::Unknown(_) => {
469 invariant.remove(i);
470 continue 'fixpoint;
471 }
472 }
473 }
474 break steps;
475 };
476 let mut outputs = Vec::with_capacity(product.pairs.len());
477 let mut inductive = true;
478 for (_, l, r) in &product.pairs {
479 if l == r {
480 outputs.push(None);
481 continue;
482 }
483 match sweep::prove(aig, *l, *r, &invariant, opts.budget) {
484 Outcome::Equal(proof) => outputs.push(Some(proof)),
485 _ => {
486 inductive = false;
487 break;
488 }
489 }
490 }
491 if inductive {
492 let cert = InductionCertificate { invariant, steps, outputs };
493 if check_always(product, &cert) {
494 return SeqVerdict::Equal(cert);
495 }
496 return SeqVerdict::Unknown("the induction certificate did not replay".into());
497 }
498 bmc(product, opts)
499}
500
501fn bmc(product: &Product, opts: &SeqOptions) -> SeqVerdict {
504 let aig = &product.aig;
505 let free: Vec<usize> = (0..aig.latches().len()).filter(|&k| aig.latches()[k].init == Init::Free).collect();
506 let n_in = aig.inputs().len();
507 let pairs = product.pairs.len();
508 let mut unrolled = unroll(aig, opts.bmc_depth);
509 let frames: Vec<AigLit> = (0..opts.bmc_depth)
510 .map(|f| {
511 let outs: Vec<AigLit> = unrolled.outputs()[f * pairs..(f + 1) * pairs].iter().map(|o| o.lit).collect();
512 outs.into_iter().fold(AigLit::FALSE, |acc, d| unrolled.or(acc, d))
513 })
514 .collect();
515 for (f, &any) in frames.iter().enumerate() {
516 match sweep::witness(&unrolled, any, opts.budget) {
517 Ok(Some((inputs, _))) => {
518 let mut init = vec![false; aig.latches().len()];
519 for (j, &k) in free.iter().enumerate() {
520 init[k] = inputs[n_in + j];
521 }
522 let trace_frames = (0..=f)
523 .map(|c| {
524 let base = if c == 0 { 0 } else { n_in + free.len() + (c - 1) * n_in };
525 inputs[base..base + n_in].to_vec()
526 })
527 .collect();
528 let trace = Trace { init, frames: trace_frames };
529 return if replays(product, &trace) {
530 SeqVerdict::Differ(trace)
531 } else {
532 SeqVerdict::Unknown("a difference the product does not replay".into())
533 };
534 }
535 Ok(None) => {}
536 Err(why) => return SeqVerdict::Unknown(why),
537 }
538 }
539 SeqVerdict::Unknown(format!(
540 "the register correspondence does not prove the outputs, and no difference shows within {} clocks",
541 opts.bmc_depth
542 ))
543}
544
545#[cfg(test)]
546#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/seq_equiv/tests.rs"]
547mod tests;