use std::collections::BTreeSet;
use sv_sim_sim::sva::ir::LocalVarId;
pub type VarSet = BTreeSet<LocalVarId>;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Flow {
pub sample: VarSet,
pub block: VarSet,
pub flow0: VarSet,
}
fn union(a: &VarSet, b: &VarSet) -> VarSet {
a.union(b).copied().collect()
}
fn minus(a: &VarSet, b: &VarSet) -> VarSet {
a.difference(b).copied().collect()
}
fn inter(a: &VarSet, b: &VarSet) -> VarSet {
a.intersection(b).copied().collect()
}
impl Flow {
pub fn identity() -> Flow {
Flow::default()
}
pub fn assign(v: LocalVarId) -> Flow {
Flow {
sample: VarSet::from([v]),
block: VarSet::new(),
flow0: VarSet::from([v]),
}
}
pub fn flow_out(&self, x: &VarSet) -> VarSet {
minus(&union(x, &self.flow0), &self.block)
}
pub fn concat(&self, rhs: &Flow) -> Flow {
Flow {
sample: union(&self.sample, &rhs.sample),
block: union(&minus(&self.block, &rhs.flow0), &rhs.block),
flow0: minus(&union(&self.flow0, &rhs.flow0), &rhs.block),
}
}
pub fn or(&self, rhs: &Flow) -> Flow {
Flow {
sample: union(&self.sample, &rhs.sample),
block: union(&self.block, &rhs.block),
flow0: inter(&self.flow0, &rhs.flow0),
}
}
pub fn intersect(&self, rhs: &Flow) -> Flow {
let block = union(
&union(&self.block, &rhs.block),
&inter(&self.sample, &rhs.sample),
);
Flow {
sample: union(&self.sample, &rhs.sample),
flow0: minus(&union(&self.flow0, &rhs.flow0), &block),
block,
}
}
pub fn scoped(&self, vars: &VarSet) -> Flow {
Flow {
sample: minus(&self.sample, vars),
block: minus(&self.block, vars),
flow0: minus(&self.flow0, vars),
}
}
pub fn or_empty(&self) -> Flow {
self.or(&Flow::identity())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn v(n: u32) -> LocalVarId {
LocalVarId(n)
}
fn set(ns: &[u32]) -> VarSet {
ns.iter().map(|&n| v(n)).collect()
}
#[test]
fn the_identity_is_neutral_for_concat_on_both_sides() {
let a = Flow::assign(v(0));
assert_eq!(Flow::identity().concat(&a), a);
assert_eq!(a.concat(&Flow::identity()), a);
}
#[test]
fn an_assignment_samples_and_flows_its_variable() {
let a = Flow::assign(v(3));
assert_eq!(a.sample, set(&[3]));
assert!(a.block.is_empty());
assert_eq!(a.flow_out(&VarSet::new()), set(&[3]));
assert_eq!(a.flow_out(&set(&[1])), set(&[1, 3]));
}
#[test]
fn a_boolean_leaf_passes_x_through() {
assert_eq!(Flow::identity().flow_out(&set(&[1, 2])), set(&[1, 2]));
}
#[test]
fn or_flows_only_what_both_branches_assign() {
let one = Flow::assign(v(0));
let none = Flow::identity();
let o = one.or(&none);
assert!(o.flow_out(&VarSet::new()).is_empty());
let both = one.or(&Flow::assign(v(0)));
assert_eq!(both.flow_out(&VarSet::new()), set(&[0]));
assert_eq!(o.flow_out(&set(&[0])), set(&[0]));
}
#[test]
fn intersect_blocks_a_variable_both_operands_assign() {
let l = Flow::assign(v(0));
let r = Flow::assign(v(0));
let i = l.intersect(&r);
assert_eq!(i.block, set(&[0]));
assert!(i.flow_out(&VarSet::new()).is_empty());
assert!(i.flow_out(&set(&[0])).is_empty());
}
#[test]
fn intersect_flows_a_variable_one_operand_assigns() {
let l = Flow::assign(v(0));
let r = Flow::identity();
let i = l.intersect(&r);
assert!(i.block.is_empty());
assert_eq!(i.flow_out(&VarSet::new()), set(&[0]));
assert_eq!(r.intersect(&l), i, "intersect is symmetric");
}
#[test]
fn intersect_samples_the_union() {
let l = Flow::assign(v(0));
let r = Flow::assign(v(1));
let i = l.intersect(&r);
assert_eq!(i.sample, set(&[0, 1]));
assert_eq!(i.flow_out(&VarSet::new()), set(&[0, 1]));
}
#[test]
fn concat_unblocks_a_variable_the_second_operand_reassigns() {
let blocked = Flow::assign(v(0)).intersect(&Flow::assign(v(0)));
assert_eq!(blocked.block, set(&[0]));
let after = blocked.concat(&Flow::assign(v(0)));
assert!(after.block.is_empty());
assert_eq!(after.flow_out(&VarSet::new()), set(&[0]));
let still = blocked.concat(&Flow::identity());
assert_eq!(still.block, set(&[0]));
}
#[test]
fn concat_applies_the_second_operands_block_to_the_first_operands_flow() {
let blocked = Flow::assign(v(0)).intersect(&Flow::assign(v(0)));
let c = Flow::assign(v(0)).concat(&blocked);
assert!(c.flow_out(&VarSet::new()).is_empty());
}
#[test]
fn concat_is_associative_on_representative_triples() {
let a = Flow::assign(v(0));
let b = Flow::assign(v(1)).or(&Flow::identity());
let c = Flow::assign(v(0)).intersect(&Flow::assign(v(0)));
assert_eq!(a.concat(&b).concat(&c), a.concat(&b.concat(&c)));
assert_eq!(b.concat(&c).concat(&a), b.concat(&c.concat(&a)));
assert_eq!(c.concat(&a).concat(&b), c.concat(&a.concat(&b)));
}
#[test]
fn or_is_commutative_and_intersect_is_commutative() {
let a = Flow::assign(v(0)).concat(&Flow::assign(v(1)));
let b = Flow::assign(v(1)).or(&Flow::identity());
assert_eq!(a.or(&b), b.or(&a));
assert_eq!(a.intersect(&b), b.intersect(&a));
}
#[test]
fn a_scoped_variable_leaves_every_set() {
let f = Flow::assign(v(0)).concat(&Flow::assign(v(1)));
let s = f.scoped(&set(&[0]));
assert_eq!(s.sample, set(&[1]));
assert_eq!(s.flow_out(&VarSet::new()), set(&[1]));
let blocked = Flow::assign(v(0)).intersect(&Flow::assign(v(0)));
assert!(blocked.scoped(&set(&[0])).block.is_empty());
}
#[test]
fn an_optional_body_does_not_flow_its_assignments() {
let r = Flow::assign(v(0));
let o = r.or_empty();
assert_eq!(o.sample, set(&[0]));
assert!(o.flow_out(&VarSet::new()).is_empty());
}
#[test]
fn the_remark_identity_holds_for_a_composite() {
let r = Flow::assign(v(0))
.concat(&Flow::assign(v(1)).or(&Flow::identity()))
.intersect(&Flow::assign(v(2)));
for x in [set(&[]), set(&[0]), set(&[1, 2]), set(&[0, 1, 2])] {
assert_eq!(r.flow_out(&x), minus(&union(&x, &r.flow0), &r.block));
}
}
}