use bitloom_hir::{FrozenHir, PortValues};
use crate::{
AbstractionView, EquivStatus, check_functional_equiv, check_functional_equiv_generated,
check_generated_bridge_with,
};
#[derive(Debug, Clone)]
pub struct FormalEquivProduct {
pub seed: u64,
pub random_cycles: usize,
boolean_ports: Vec<String>,
exhaustive_depth: usize,
}
impl FormalEquivProduct {
pub fn new(seed: u64, random_cycles: usize) -> Self {
Self {
seed,
random_cycles,
boolean_ports: Vec::new(),
exhaustive_depth: 0,
}
}
pub fn with_boolean_ports(mut self, ports: &[&str]) -> Self {
self.boolean_ports = ports.iter().map(|p| (*p).to_string()).collect();
self
}
pub fn with_exhaustive_depth(mut self, depth: usize) -> Self {
self.exhaustive_depth = depth;
self
}
pub fn alphabet(&self) -> Vec<PortValues> {
if self.boolean_ports.is_empty() {
return vec![PortValues::default()];
}
let n = self.boolean_ports.len();
assert!(
n <= 12,
"FormalEquivProduct alphabet supports at most 12 boolean ports (got {n}); F4 MVP scale"
);
let combinations = 1usize << n;
let mut out = Vec::with_capacity(combinations);
for mask in 0..combinations {
let mut pv = PortValues::default();
for (i, name) in self.boolean_ports.iter().enumerate() {
let bit = ((mask >> i) & 1) as u64;
pv.set(name, bit);
}
out.push(pv);
}
out
}
pub fn random_stimuli(&self) -> Vec<PortValues> {
let alphabet = self.alphabet();
let mut state = self.seed;
let mut out = Vec::with_capacity(self.random_cycles.max(1));
let n = self.random_cycles.max(1);
for _ in 0..n {
state = state.wrapping_mul(1664525).wrapping_add(1013904223);
let idx = (state as usize) % alphabet.len();
out.push(alphabet[idx].clone());
}
out
}
pub fn check_random_compare(&self, hir: FrozenHir) -> EquivStatus {
check_functional_equiv_generated(hir, self.random_stimuli())
}
pub fn check_random_compare_with<A: AbstractionView>(
&self,
hir: FrozenHir,
abs: &mut A,
) -> EquivStatus {
check_generated_bridge_with(hir, abs, self.random_stimuli())
}
pub fn check_bounded_exhaustive(&self, hir: FrozenHir) -> EquivStatus {
let sequences = self.exhaustive_sequences();
let mut total_cycles = 0usize;
for seq in sequences {
match check_functional_equiv_generated(hir.clone(), seq) {
EquivStatus::Pass { cycles } => total_cycles = total_cycles.saturating_add(cycles),
fail @ EquivStatus::Fail { .. } => return fail,
}
}
EquivStatus::Pass {
cycles: total_cycles,
}
}
pub fn check_bounded_exhaustive_with<A: AbstractionView>(
&self,
hir: FrozenHir,
abs: &mut A,
) -> EquivStatus {
let sequences = self.exhaustive_sequences();
let mut total_cycles = 0usize;
for seq in sequences {
match check_functional_equiv(hir.clone(), abs, seq) {
EquivStatus::Pass { cycles } => total_cycles = total_cycles.saturating_add(cycles),
fail @ EquivStatus::Fail { .. } => return fail,
}
}
EquivStatus::Pass {
cycles: total_cycles,
}
}
pub fn exhaustive_sequences(&self) -> Vec<Vec<PortValues>> {
let alphabet = self.alphabet();
let depth = self.exhaustive_depth;
if depth == 0 {
return vec![Vec::new()];
}
let mut sequences: Vec<Vec<PortValues>> = vec![Vec::new()];
for _ in 0..depth {
let mut next = Vec::with_capacity(sequences.len() * alphabet.len());
for prefix in &sequences {
for frame in &alphabet {
let mut seq = prefix.clone();
seq.push(frame.clone());
next.push(seq);
}
}
sequences = next;
}
sequences
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alphabet_two_ports_has_four_frames() {
let p = FormalEquivProduct::new(0, 0).with_boolean_ports(&["rst", "en"]);
assert_eq!(p.alphabet().len(), 4);
}
#[test]
fn exhaustive_depth_two_bool_has_four_sequences() {
let p = FormalEquivProduct::new(0, 0)
.with_boolean_ports(&["rst"])
.with_exhaustive_depth(2);
assert_eq!(p.exhaustive_sequences().len(), 4);
}
#[test]
fn random_stimuli_reproducible() {
let p = FormalEquivProduct::new(42, 5).with_boolean_ports(&["rst"]);
assert_eq!(p.random_stimuli(), p.random_stimuli());
}
}