1use std::collections::{BTreeMap, HashMap, HashSet};
30
31use crate::aig::{Aig, AigLit, Init, Leaf};
32use crate::fraig;
33use crate::lrat::{self, Lrat};
34use crate::sweep::{MiterError, Verdict};
35
36#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct Unit {
40 pub name: String,
41 pub function: String,
42 pub latency: usize,
43 pub operands: Vec<String>,
44 pub results: Vec<String>,
45}
46
47#[derive(Clone, Copy, Debug)]
49pub struct Side<'a> {
50 pub aig: &'a Aig,
51 pub latency: usize,
52 pub units: &'a [Unit],
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
58pub enum Binding {
59 Shared(String),
60 Const(bool),
61 Fresh,
62}
63
64#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct Claim {
67 pub name: String,
68 pub output: String,
69 pub frame: usize,
70}
71
72pub struct Unrolled<'a> {
75 pub aig: &'a Aig,
76 pub frames: usize,
77 pub bind: &'a dyn Fn(&str, usize) -> Binding,
78 pub claims: &'a [Claim],
79 pub units: &'a [Unit],
80}
81
82fn of_a_unit(units: &[Unit], name: &str) -> bool {
84 units.iter().any(|u| name.strip_prefix(u.name.as_str()).is_some_and(|rest| rest.starts_with('.')))
85}
86
87fn aligned_claims(side: &Side) -> Vec<Claim> {
90 let operands: std::collections::HashSet<&str> = side.units.iter().flat_map(|u| u.operands.iter().map(String::as_str)).collect();
91 side.aig
92 .outputs()
93 .iter()
94 .filter_map(|o| o.name.clone())
95 .filter(|n| !operands.contains(n.as_str()) && !of_a_unit(side.units, n))
96 .map(|n| Claim { name: n.clone(), output: n, frame: side.latency })
97 .collect()
98}
99
100fn aligned_bind(name: &str, k: usize) -> Binding {
101 if k == 0 {
102 Binding::Shared(name.to_string())
103 } else {
104 Binding::Shared(format!("{name}@{k}"))
105 }
106}
107
108#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
110pub struct Application {
111 pub side: usize,
112 pub unit: usize,
113 pub frame: usize,
114}
115
116#[derive(Clone, Debug)]
120pub struct Round {
121 pub substituted: Vec<(Application, Application)>,
122 pub proof: Lrat,
123}
124
125#[derive(Clone, Debug)]
127pub struct Report {
128 pub verdicts: Vec<(String, Verdict)>,
129 pub rounds: Vec<Round>,
130 pub stats: fraig::Stats,
131}
132
133struct Miter {
135 aig: Aig,
136 outputs: Vec<(String, AigLit, AigLit)>,
137 operands: BTreeMap<Application, Vec<AigLit>>,
139}
140
141const TAGS: [&str; 2] = ["left", "right"];
142
143fn result_name(side: usize, bit: &str, k: usize) -> String {
145 format!("{}:{bit}@{k}", TAGS[side])
146}
147
148fn build(sides: [&Unrolled; 2], subst: &HashMap<String, String>) -> Result<Miter, MiterError> {
149 let mut aig = Aig::new();
150 let mut inputs: HashMap<String, AigLit> = HashMap::new();
151 let mut input = |aig: &mut Aig, name: String| -> AigLit { *inputs.entry(name.clone()).or_insert_with(|| aig.add_input(Some(name))) };
152 let mut own: [BTreeMap<String, AigLit>; 2] = [BTreeMap::new(), BTreeMap::new()];
153 let mut operands: BTreeMap<Application, Vec<AigLit>> = BTreeMap::new();
154 for (s, side) in sides.iter().enumerate() {
155 let g = side.aig;
156 let result_bits: HashMap<&str, ()> = side.units.iter().flat_map(|u| u.results.iter().map(|r| (r.as_str(), ()))).collect();
157 let operand_of: HashMap<&str, (usize, usize)> = side
158 .units
159 .iter()
160 .enumerate()
161 .flat_map(|(u, unit)| unit.operands.iter().enumerate().map(move |(i, o)| (o.as_str(), (u, i))))
162 .collect();
163 for unit in side.units {
164 for name in unit.operands.iter().chain(&unit.results) {
165 if g.input_index(name).is_none() && g.output_index(name).is_none() {
166 return Err(MiterError::Unmatched(format!("{}: {name}", TAGS[s])));
167 }
168 }
169 }
170 let mut claimed: HashMap<(&str, usize), Vec<&str>> = HashMap::new();
171 for c in side.claims {
172 if g.output_index(&c.output).is_none() || c.frame >= side.frames {
173 return Err(MiterError::Unmatched(format!("{}: {} at clock {}", TAGS[s], c.output, c.frame)));
174 }
175 claimed.entry((c.output.as_str(), c.frame)).or_default().push(c.name.as_str());
176 }
177 let mut state: Vec<AigLit> = g
178 .latches()
179 .iter()
180 .map(|l| match (l.init, &l.name) {
181 (Init::Zero, _) => AigLit::FALSE,
182 (Init::One, _) => AigLit::TRUE,
183 (Init::Free, Some(name)) => input(&mut aig, format!("{}:{name}@init", TAGS[s])),
184 (Init::Free, None) => aig.add_input(None),
185 })
186 .collect();
187 for k in 0..side.frames {
188 let frame_inputs: Vec<AigLit> = g
189 .inputs()
190 .iter()
191 .map(|p| {
192 let Some(name) = p.name.clone() else { return aig.add_input(None) };
193 if result_bits.contains_key(name.as_str()) {
194 return input(&mut aig, resolve(subst, &result_name(s, &name, k)));
195 }
196 match (side.bind)(&name, k) {
197 Binding::Shared(symbol) => input(&mut aig, symbol),
198 Binding::Const(b) => if b { AigLit::TRUE } else { AigLit::FALSE },
199 Binding::Fresh => aig.add_input(None),
200 }
201 })
202 .collect();
203 let map = aig.import(g, &mut |leaf| match leaf {
204 Leaf::Input(i) => frame_inputs[i],
205 Leaf::Latch(j) => state[j],
206 });
207 let at = |l: AigLit| map[l.node() as usize].with_phase(l.is_complemented());
208 for o in g.outputs() {
209 let name = o.name.clone().unwrap_or_default();
210 if let Some(&(u, i)) = operand_of.get(name.as_str()) {
211 let app = Application { side: s, unit: u, frame: k };
212 let width = side.units[u].operands.len();
213 operands.entry(app).or_insert_with(|| vec![AigLit::FALSE; width])[i] = at(o.lit);
214 }
215 for claim in claimed.get(&(name.as_str(), k)).into_iter().flatten() {
216 own[s].insert(claim.to_string(), at(o.lit));
217 }
218 }
219 state = g.latches().iter().map(|l| at(l.next)).collect();
220 }
221 operands.retain(|app, _| app.side != s || app.frame + side.units[app.unit].latency < side.frames);
222 }
223 let mut outputs = Vec::new();
224 for (name, &l) in &own[0] {
225 let &r = own[1].get(name).ok_or_else(|| MiterError::Unmatched(name.clone()))?;
226 outputs.push((name.clone(), l, r));
227 }
228 if let Some(name) = own[1].keys().find(|n| !own[0].contains_key(*n)) {
229 return Err(MiterError::Unmatched(name.clone()));
230 }
231 Ok(Miter { aig, outputs, operands })
232}
233
234fn resolve(subst: &HashMap<String, String>, name: &str) -> String {
236 let mut at = name;
237 while let Some(next) = subst.get(at) {
238 at = next;
239 }
240 at.to_string()
241}
242
243fn unit<'a>(sides: [&'a Unrolled; 2], app: Application) -> &'a Unit {
244 &sides[app.side].units[app.unit]
245}
246
247fn identify(sides: [&Unrolled; 2], subst: &mut HashMap<String, String>, kept: Application, made: Application) -> bool {
251 let (ku, mu) = (unit(sides, kept), unit(sides, made));
252 let bits: Vec<(String, String)> = ku
253 .results
254 .iter()
255 .zip(&mu.results)
256 .map(|(k, m)| (result_name(made.side, m, made.frame + mu.latency), resolve(subst, &result_name(kept.side, k, kept.frame + ku.latency))))
257 .collect();
258 if bits.iter().any(|(from, to)| from == to || subst.contains_key(from)) {
259 return false;
260 }
261 subst.extend(bits);
262 true
263}
264
265struct Classes(HashMap<(usize, String), usize>);
268
269impl Classes {
270 fn new(sides: [&Unrolled; 2], functions: &[(String, String)]) -> Classes {
271 let mut index: HashMap<(usize, String), usize> = HashMap::new();
272 for (s, side) in sides.iter().enumerate() {
273 for u in side.units {
274 let next = index.len();
275 index.entry((s, u.function.clone())).or_insert(next);
276 }
277 }
278 let mut parent: Vec<usize> = (0..index.len()).collect();
279 fn root(parent: &mut [usize], mut x: usize) -> usize {
280 while parent[x] != x {
281 parent[x] = parent[parent[x]];
282 x = parent[x];
283 }
284 x
285 }
286 for (l, r) in functions {
287 if let (Some(&a), Some(&b)) = (index.get(&(0, l.clone())), index.get(&(1, r.clone()))) {
288 let (ra, rb) = (root(&mut parent, a), root(&mut parent, b));
289 parent[ra.max(rb)] = ra.min(rb);
290 }
291 }
292 Classes(index.into_iter().map(|(k, i)| (k, root(&mut parent, i))).collect())
293 }
294
295 fn of(&self, sides: [&Unrolled; 2], app: Application) -> usize {
296 self.0[&(app.side, unit(sides, app).function.clone())]
297 }
298
299 fn one(&self, sides: [&Unrolled; 2], a: Application, b: Application) -> bool {
301 let (au, bu) = (unit(sides, a), unit(sides, b));
302 self.of(sides, a) == self.of(sides, b) && au.operands.len() == bu.operands.len() && au.results.len() == bu.results.len()
303 }
304}
305
306fn signatures(m: &Miter, words: usize, seed: u64) -> BTreeMap<Application, Vec<u64>> {
308 let mut state = seed | 1;
309 let mut next = move || {
310 state ^= state << 13;
311 state ^= state >> 7;
312 state ^= state << 17;
313 state
314 };
315 let inputs: Vec<u64> = (0..m.aig.inputs().len() * words).map(|_| next()).collect();
316 let values = m.aig.simulate(words, &inputs, &[]);
317 let word = |l: AigLit, w: usize| {
318 let v = values[l.node() as usize * words + w];
319 if l.is_complemented() {
320 !v
321 } else {
322 v
323 }
324 };
325 m.operands.iter().map(|(app, lits)| (*app, lits.iter().flat_map(|&l| (0..words).map(move |w| word(l, w))).collect())).collect()
326}
327
328pub fn decide(left: &Side, right: &Side, functions: &[(String, String)], opts: &fraig::Options) -> Result<Report, MiterError> {
331 let (lc, rc) = (aligned_claims(left), aligned_claims(right));
332 let l = Unrolled { aig: left.aig, frames: left.latency + 1, bind: &aligned_bind, claims: &lc, units: left.units };
333 let r = Unrolled { aig: right.aig, frames: right.latency + 1, bind: &aligned_bind, claims: &rc, units: right.units };
334 decide_unrolled(&l, &r, functions, opts)
335}
336
337fn groups(sides: [&Unrolled; 2], classes: &Classes, m: &Miter, made: &HashSet<Application>, opts: &fraig::Options) -> Vec<Vec<Application>> {
340 let sig = signatures(m, opts.sim_words.max(1), opts.seed);
341 let mut by: HashMap<(usize, usize, usize, &[u64]), Vec<Application>> = HashMap::new();
342 for (&app, words) in &sig {
343 if made.contains(&app) {
344 continue;
345 }
346 let u = unit(sides, app);
347 by.entry((classes.of(sides, app), u.operands.len(), u.results.len(), words.as_slice())).or_default().push(app);
348 }
349 let mut groups: Vec<Vec<Application>> = by.into_values().filter(|g| g.len() > 1).collect();
350 groups.sort();
351 groups
352}
353
354fn identifications(m: &Miter, groups: &[Vec<Application>], opts: &fraig::Options) -> Option<(Vec<(Application, Application)>, Lrat)> {
358 let mut apart: HashSet<(Application, Application)> = HashSet::new();
359 loop {
360 let mut candidates: Vec<(Application, Application)> = Vec::new();
361 for group in groups {
362 let mut kept: Vec<Application> = Vec::new();
363 for &app in group {
364 match kept.iter().find(|&&k| !apart.contains(&(k, app))) {
365 Some(&k) => candidates.push((k, app)),
366 None => kept.push(app),
367 }
368 }
369 }
370 if candidates.is_empty() {
371 return None;
372 }
373 let pairs: Vec<(AigLit, AigLit)> =
374 candidates.iter().flat_map(|(k, a)| m.operands[k].iter().copied().zip(m.operands[a].iter().copied())).collect();
375 let (verdicts, proof, _) = fraig::decide(&m.aig, &pairs, opts);
376 let mut proven: Vec<(Application, Application)> = Vec::new();
377 let mut at = 0;
378 for &(k, a) in &candidates {
379 let width = m.operands[&k].len();
380 if verdicts[at..at + width].iter().all(|v| *v == Verdict::Equal) {
381 proven.push((k, a));
382 } else {
383 apart.insert((k, a));
384 }
385 at += width;
386 }
387 if !proven.is_empty() {
388 return Some((proven, proof));
389 }
390 }
391}
392
393pub fn decide_unrolled(left: &Unrolled, right: &Unrolled, functions: &[(String, String)], opts: &fraig::Options) -> Result<Report, MiterError> {
395 let sides = [left, right];
396 let classes = Classes::new(sides, functions);
397 let mut subst: HashMap<String, String> = HashMap::new();
398 let mut made: HashSet<Application> = HashSet::new();
399 let mut rounds: Vec<Round> = Vec::new();
400 loop {
401 let m = build(sides, &subst)?;
402 let groups = groups(sides, &classes, &m, &made, opts);
403 let Some((proven, proof)) = identifications(&m, &groups, opts) else {
404 let pairs: Vec<(AigLit, AigLit)> = m.outputs.iter().map(|(_, l, r)| (*l, *r)).collect();
405 let (verdicts, proof, stats) = fraig::decide(&m.aig, &pairs, opts);
406 let verdicts = m
407 .outputs
408 .iter()
409 .zip(verdicts)
410 .map(|((name, _, _), v)| {
411 let v = match v {
412 Verdict::Differ { .. } => Verdict::Unknown("the outputs differ over the units as functions nothing is known of".into()),
413 v => v,
414 };
415 (name.clone(), v)
416 })
417 .collect();
418 rounds.push(Round { substituted: Vec::new(), proof });
419 return Ok(Report { verdicts, rounds, stats });
420 };
421 for &(k, a) in &proven {
422 let fresh = identify(sides, &mut subst, k, a);
423 debug_assert!(fresh, "an application of a group is made one once");
424 made.insert(a);
425 }
426 rounds.push(Round { substituted: proven, proof });
427 }
428}
429
430impl Report {
431 pub fn check(&self, left: &Side, right: &Side, functions: &[(String, String)]) -> bool {
436 let (lc, rc) = (aligned_claims(left), aligned_claims(right));
437 let l = Unrolled { aig: left.aig, frames: left.latency + 1, bind: &aligned_bind, claims: &lc, units: left.units };
438 let r = Unrolled { aig: right.aig, frames: right.latency + 1, bind: &aligned_bind, claims: &rc, units: right.units };
439 self.check_unrolled(&l, &r, functions)
440 }
441
442 pub fn check_unrolled(&self, left: &Unrolled, right: &Unrolled, functions: &[(String, String)]) -> bool {
444 let sides = [left, right];
445 let classes = Classes::new(sides, functions);
446 let mut subst: HashMap<String, String> = HashMap::new();
447 let Some((last, earlier)) = self.rounds.split_last() else { return false };
448 for round in earlier {
449 let Ok(m) = build(sides, &subst) else { return false };
450 let mut claims = Vec::new();
451 for &(k, a) in &round.substituted {
452 let (Some(ko), Some(ao)) = (m.operands.get(&k), m.operands.get(&a)) else { return false };
453 if ko.len() != ao.len() || !classes.one(sides, k, a) {
454 return false;
455 }
456 for (&x, &y) in ko.iter().zip(ao) {
457 if x != y {
458 claims.extend(fraig::claim(x, y));
459 }
460 }
461 }
462 if !lrat::check_derivation(m.aig.num_nodes(), &fraig::formula(&m.aig), &round.proof, &claims) {
463 return false;
464 }
465 for &(k, a) in &round.substituted {
466 if !identify(sides, &mut subst, k, a) {
467 return false;
468 }
469 }
470 }
471 let Ok(m) = build(sides, &subst) else { return false };
472 if self.verdicts.len() != m.outputs.len() {
473 return false;
474 }
475 let mut claims = Vec::new();
476 for ((name, a, b), (vname, v)) in m.outputs.iter().zip(&self.verdicts) {
477 if name != vname || matches!(v, Verdict::Differ { .. }) {
478 return false;
479 }
480 if *v == Verdict::Equal && a != b {
481 claims.extend(fraig::claim(*a, *b));
482 }
483 }
484 last.substituted.is_empty() && lrat::check_derivation(m.aig.num_nodes(), &fraig::formula(&m.aig), &last.proof, &claims)
485 }
486}
487
488#[cfg(test)]
489#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/units/tests.rs"]
490mod tests;