#[cfg(not(kani))]
use std::collections::HashMap;
#[cfg(not(kani))]
type Strash = HashMap<(u32, u32), Lit>;
#[cfg(kani)]
#[derive(Clone, Debug, Default)]
struct Strash;
#[cfg(kani)]
impl Strash {
fn get(&self, _key: &(u32, u32)) -> Option<&Lit> {
None
}
fn insert(&mut self, _key: (u32, u32), _lit: Lit) {}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Lit(u32);
impl Lit {
pub const FALSE: Lit = Lit(0);
pub const TRUE: Lit = Lit(1);
fn new(var: u32, complement: bool) -> Lit {
Lit(var << 1 | complement as u32)
}
#[allow(clippy::should_implement_trait)]
#[must_use]
pub fn not(self) -> Lit {
Lit(self.0 ^ 1)
}
pub fn var(self) -> u32 {
self.0 >> 1
}
pub fn is_complement(self) -> bool {
self.0 & 1 == 1
}
pub fn raw(self) -> u32 {
self.0
}
}
impl std::ops::Not for Lit {
type Output = Lit;
fn not(self) -> Lit {
Lit::not(self)
}
}
#[derive(Clone, Debug)]
enum Node {
Const,
Input,
And(Lit, Lit),
}
#[derive(Clone, Debug, Default)]
pub struct Aig {
nodes: Vec<Node>,
strash: Strash,
num_inputs: u32,
}
impl Aig {
pub fn new() -> Self {
Aig {
nodes: vec![Node::Const],
strash: Strash::default(),
num_inputs: 0,
}
}
pub fn num_vars(&self) -> u32 {
self.nodes.len() as u32
}
pub fn num_inputs(&self) -> u32 {
self.num_inputs
}
pub fn num_ands(&self) -> u32 {
self.num_vars() - self.num_inputs - 1
}
pub fn input(&mut self) -> Lit {
let var = self.nodes.len() as u32;
self.nodes.push(Node::Input);
self.num_inputs += 1;
Lit::new(var, false)
}
pub fn and(&mut self, a: Lit, b: Lit) -> Lit {
if a == Lit::FALSE || b == Lit::FALSE || a == b.not() {
return Lit::FALSE;
}
if a == Lit::TRUE {
return b;
}
if b == Lit::TRUE || a == b {
return a;
}
let (x, y) = if a.raw() <= b.raw() { (a, b) } else { (b, a) };
if let Some(&lit) = self.strash.get(&(x.raw(), y.raw())) {
return lit;
}
let var = self.nodes.len() as u32;
self.nodes.push(Node::And(x, y));
let lit = Lit::new(var, false);
self.strash.insert((x.raw(), y.raw()), lit);
lit
}
pub fn or(&mut self, a: Lit, b: Lit) -> Lit {
self.and(a.not(), b.not()).not()
}
pub fn xor(&mut self, a: Lit, b: Lit) -> Lit {
let l = self.and(a, b.not());
let r = self.and(a.not(), b);
self.or(l, r)
}
pub fn xnor(&mut self, a: Lit, b: Lit) -> Lit {
self.xor(a, b).not()
}
pub fn mux(&mut self, sel: Lit, t: Lit, e: Lit) -> Lit {
let then_b = self.and(sel, t);
let else_b = self.and(sel.not(), e);
self.or(then_b, else_b)
}
pub fn simulate(&self, inputs: &[bool]) -> Vec<bool> {
assert_eq!(inputs.len() as u32, self.num_inputs, "input count");
let mut values = vec![false; self.nodes.len()];
let mut next_input = 0usize;
for (i, node) in self.nodes.iter().enumerate() {
values[i] = match node {
Node::Const => false,
Node::Input => {
let v = inputs[next_input];
next_input += 1;
v
}
Node::And(a, b) => {
Self::lit_value_in(&values, *a) && Self::lit_value_in(&values, *b)
}
};
}
values
}
fn lit_value_in(values: &[bool], lit: Lit) -> bool {
values[lit.var() as usize] ^ lit.is_complement()
}
pub fn lit_value(&self, values: &[bool], lit: Lit) -> bool {
Self::lit_value_in(values, lit)
}
pub fn and_gates(&self) -> impl Iterator<Item = (u32, Lit, Lit)> + '_ {
self.nodes.iter().enumerate().filter_map(|(v, n)| match n {
Node::And(a, b) => Some((v as u32, *a, *b)),
_ => None,
})
}
}
pub type Word = Vec<Lit>;
pub fn word_const(value: u128, width: u32) -> Word {
(0..width)
.map(|i| {
if (value >> i) & 1 == 1 {
Lit::TRUE
} else {
Lit::FALSE
}
})
.collect()
}
pub fn word_input(aig: &mut Aig, width: u32) -> Word {
(0..width).map(|_| aig.input()).collect()
}
pub fn word_value(aig: &Aig, values: &[bool], word: &Word) -> u128 {
word.iter().enumerate().fold(0u128, |acc, (i, lit)| {
acc | ((aig.lit_value(values, *lit) as u128) << i)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constant_folding_identities() {
let mut g = Aig::new();
let x = g.input();
assert_eq!(g.and(x, Lit::FALSE), Lit::FALSE);
assert_eq!(g.and(Lit::FALSE, x), Lit::FALSE);
assert_eq!(g.and(x, Lit::TRUE), x);
assert_eq!(g.and(Lit::TRUE, x), x);
assert_eq!(g.and(x, x), x);
assert_eq!(g.and(x, x.not()), Lit::FALSE);
assert_eq!(g.num_ands(), 0, "all folded, no gate created");
}
#[test]
fn structural_hashing_collapses_duplicates() {
let mut g = Aig::new();
let x = g.input();
let y = g.input();
let a = g.and(x, y);
let b = g.and(y, x); let c = g.and(x, y); assert_eq!(a, b);
assert_eq!(a, c);
assert_eq!(g.num_ands(), 1);
}
#[test]
fn complement_involution_and_constants() {
let mut g = Aig::new();
let x = g.input();
assert_eq!(x.not().not(), x);
assert_eq!(Lit::FALSE.not(), Lit::TRUE);
assert!(Lit::TRUE.is_complement());
assert_eq!(Lit::TRUE.var(), 0);
}
#[test]
fn derived_gates_simulate_correctly() {
let mut g = Aig::new();
let x = g.input();
let y = g.input();
let s = g.input();
let and = g.and(x, y);
let or = g.or(x, y);
let xor = g.xor(x, y);
let xnor = g.xnor(x, y);
let mux = g.mux(s, x, y);
for bits in 0..8u32 {
let (xv, yv, sv) = (bits & 1 == 1, bits & 2 == 2, bits & 4 == 4);
let vals = g.simulate(&[xv, yv, sv]);
assert_eq!(g.lit_value(&vals, and), xv && yv);
assert_eq!(g.lit_value(&vals, or), xv || yv);
assert_eq!(g.lit_value(&vals, xor), xv ^ yv);
assert_eq!(g.lit_value(&vals, xnor), !(xv ^ yv));
assert_eq!(g.lit_value(&vals, mux), if sv { xv } else { yv });
assert!(!g.lit_value(&vals, Lit::FALSE));
assert!(g.lit_value(&vals, Lit::TRUE));
}
}
#[test]
fn word_helpers_roundtrip() {
let mut g = Aig::new();
let w = word_const(0xAB, 8);
let vals = g.simulate(&[]);
assert_eq!(word_value(&g, &vals, &w), 0xAB);
let inp = word_input(&mut g, 8);
let vals = g.simulate(&[true, false, false, false, false, false, false, false]);
assert_eq!(word_value(&g, &vals, &inp), 0x01);
}
}