use std::collections::BTreeMap;
use vj_abi::bit4::Bit4;
use vj_sdf::ast::{CondBinaryOp as SdfBin, CondExpr, CondUnaryOp as SdfUn};
use vj_sim::netlist::specify::{CondBinaryOp as SvBin, CondNode, CondUnaryOp as SvUn};
use vj_sim::symid::SymbolId;
pub(crate) const MAX_BITS: u32 = 16;
pub(crate) trait CondScope {
fn width_of(&self, name: &str) -> Option<u32>;
fn name_of(&self, uid: &SymbolId) -> Option<(String, u32)>;
fn specparam(&self, uid: &SymbolId) -> Option<u64>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Un {
LogicalNot,
BitNot,
Plus,
Minus,
RedAnd,
RedNand,
RedOr,
RedNor,
RedXor,
RedXnor,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Bin {
Add,
Sub,
Mul,
Div,
Mod,
Eq,
Ne,
LogicalAnd,
LogicalOr,
Lt,
Le,
Gt,
Ge,
BitAnd,
BitOr,
BitXor,
BitXnor,
Shl,
Shr,
}
#[derive(Clone, Debug)]
enum Ex {
Sig {
name: String,
lo: u32,
width: u32,
},
Const {
width: u32,
value: u64,
},
Un(Un, Box<Ex>),
Bin(Bin, Box<Ex>, Box<Ex>),
Tern(Box<Ex>, Box<Ex>, Box<Ex>),
Cat(Vec<Ex>),
Rep(u32, Box<Ex>),
}
fn mask(width: u32) -> u64 {
if width >= 64 {
u64::MAX
} else {
(1u64 << width) - 1
}
}
fn from_sdf(e: &CondExpr, scope: &dyn CondScope) -> Result<Ex, String> {
Ok(match e {
CondExpr::Port(p) => {
let [name] = p.path.as_slice() else {
return Err(format!(
"the hierarchical port `{}` in a COND expression (only ports of the \
annotated instance itself can be compared)",
p.path.join(".")
));
};
let full = scope.width_of(name).ok_or_else(|| {
format!("COND names `{name}`, which is not a signal of the instance")
})?;
let (lo, width) = match p.bit {
None => (0, full),
Some(vj_sdf::ast::PortBit::Bit(n)) => (bit_index(n, full, name)?, 1),
Some(vj_sdf::ast::PortBit::Range(a, b)) => {
let (lo, hi) = (a.min(b), a.max(b));
let lo = bit_index(lo, full, name)?;
let hi = bit_index(hi, full, name)?;
(lo, hi - lo + 1)
}
};
Ex::Sig {
name: name.clone(),
lo,
width,
}
}
CondExpr::Const { width, value } => {
let width = width.unwrap_or(32);
if width == 0 || width > 64 {
return Err(format!("a {width}-bit constant in a COND expression"));
}
Ex::Const {
width,
value: *value & mask(width),
}
}
CondExpr::Unary(op, a) => {
let op = match op {
SdfUn::LogicalNot => Un::LogicalNot,
SdfUn::BitNot => Un::BitNot,
SdfUn::Plus => Un::Plus,
SdfUn::Minus => Un::Minus,
SdfUn::RedAnd => Un::RedAnd,
SdfUn::RedNand => Un::RedNand,
SdfUn::RedOr => Un::RedOr,
SdfUn::RedNor => Un::RedNor,
SdfUn::RedXor => Un::RedXor,
SdfUn::RedXnor => Un::RedXnor,
};
Ex::Un(op, Box::new(from_sdf(a, scope)?))
}
CondExpr::Binary(op, a, b) => {
let op = match op {
SdfBin::Add => Bin::Add,
SdfBin::Sub => Bin::Sub,
SdfBin::Mul => Bin::Mul,
SdfBin::Div => Bin::Div,
SdfBin::Mod => Bin::Mod,
SdfBin::Eq | SdfBin::CaseEq => Bin::Eq,
SdfBin::Ne | SdfBin::CaseNe => Bin::Ne,
SdfBin::LogicalAnd => Bin::LogicalAnd,
SdfBin::LogicalOr => Bin::LogicalOr,
SdfBin::Lt => Bin::Lt,
SdfBin::Le => Bin::Le,
SdfBin::Gt => Bin::Gt,
SdfBin::Ge => Bin::Ge,
SdfBin::BitAnd => Bin::BitAnd,
SdfBin::BitOr => Bin::BitOr,
SdfBin::BitXor => Bin::BitXor,
SdfBin::BitXnor => Bin::BitXnor,
SdfBin::Shl => Bin::Shl,
SdfBin::Shr => Bin::Shr,
};
Ex::Bin(
op,
Box::new(from_sdf(a, scope)?),
Box::new(from_sdf(b, scope)?),
)
}
})
}
fn bit_index(n: i64, full: u32, name: &str) -> Result<u32, String> {
u32::try_from(n)
.ok()
.filter(|&i| i < full)
.ok_or_else(|| format!("COND bit index {n} is outside `{name}` ({full} bits)"))
}
fn from_sv(n: &CondNode, scope: &dyn CondScope) -> Result<Ex, String> {
Ok(match n {
CondNode::Signal((uid, window)) => {
let (name, full) = scope.name_of(uid).ok_or_else(|| {
format!("the path condition names `{uid}`, which has no name in its instance")
})?;
let (lo, width) = window.unwrap_or((0, full));
if lo + width > full {
return Err(format!(
"the path condition's `{name}[{lo}+:{width}]` is outside the signal"
));
}
Ex::Sig { name, lo, width }
}
CondNode::Specparam(uid) => {
let value = scope.specparam(uid).ok_or_else(|| {
format!("the path condition's specparam `{uid}` has no integer value")
})?;
Ex::Const { width: 64, value }
}
CondNode::Const(bits) => {
if bits.is_empty() || bits.len() > 64 {
return Err(format!("a {}-bit constant in a path condition", bits.len()));
}
let mut value = 0u64;
for (i, b) in bits.iter().enumerate() {
match b {
Bit4::Zero => {}
Bit4::One => value |= 1 << i,
Bit4::X | Bit4::Z => {
return Err(
"an x or z constant in a path condition (no two-state truth \
table)"
.to_string(),
);
}
}
}
Ex::Const {
width: bits.len() as u32,
value,
}
}
CondNode::Unary(op, a) => {
let op = match op {
SvUn::BitwiseNot => Un::BitNot,
SvUn::ReductionAnd => Un::RedAnd,
SvUn::ReductionOr => Un::RedOr,
SvUn::ReductionXor => Un::RedXor,
SvUn::ReductionNand => Un::RedNand,
SvUn::ReductionNor => Un::RedNor,
SvUn::ReductionXnor => Un::RedXnor,
SvUn::LogicalNot => Un::LogicalNot,
};
Ex::Un(op, Box::new(from_sv(a, scope)?))
}
CondNode::Binary(op, a, b) => {
let op = match op {
SvBin::BitwiseAnd => Bin::BitAnd,
SvBin::BitwiseOr => Bin::BitOr,
SvBin::BitwiseXor => Bin::BitXor,
SvBin::BitwiseXnor => Bin::BitXnor,
SvBin::Equality | SvBin::CaseEquality => Bin::Eq,
SvBin::Inequality | SvBin::CaseInequality => Bin::Ne,
SvBin::LogicalAnd => Bin::LogicalAnd,
SvBin::LogicalOr => Bin::LogicalOr,
};
Ex::Bin(
op,
Box::new(from_sv(a, scope)?),
Box::new(from_sv(b, scope)?),
)
}
CondNode::Ternary(c, t, f) => Ex::Tern(
Box::new(from_sv(c, scope)?),
Box::new(from_sv(t, scope)?),
Box::new(from_sv(f, scope)?),
),
CondNode::Concat(items) => Ex::Cat(
items
.iter()
.map(|i| from_sv(i, scope))
.collect::<Result<_, _>>()?,
),
CondNode::Replication(n, item) => Ex::Rep(*n, Box::new(from_sv(item, scope)?)),
})
}
type Env = BTreeMap<String, u64>;
fn eval(e: &Ex, env: &Env) -> Option<(u64, u32)> {
match e {
Ex::Sig { name, lo, width } => {
let v = env.get(name).copied().unwrap_or(0);
Some(((v >> lo) & mask(*width), *width))
}
Ex::Const { width, value } => Some((*value, *width)),
Ex::Un(op, a) => {
let (v, w) = eval(a, env)?;
let all = mask(w);
let bit = |b: bool| Some((u64::from(b), 1));
match op {
Un::LogicalNot => bit(v == 0),
Un::BitNot => Some((!v & all, w)),
Un::Plus => Some((v, w)),
Un::Minus => Some((v.wrapping_neg() & all, w)),
Un::RedAnd => bit(v == all),
Un::RedNand => bit(v != all),
Un::RedOr => bit(v != 0),
Un::RedNor => bit(v == 0),
Un::RedXor => bit(v.count_ones() % 2 == 1),
Un::RedXnor => bit(v.count_ones().is_multiple_of(2)),
}
}
Ex::Bin(op, a, b) => {
let (x, wa) = eval(a, env)?;
let (y, wb) = eval(b, env)?;
let w = wa.max(wb);
let all = mask(w);
let bit = |b: bool| Some((u64::from(b), 1));
match op {
Bin::Add => Some((x.wrapping_add(y) & all, w)),
Bin::Sub => Some((x.wrapping_sub(y) & all, w)),
Bin::Mul => Some((x.wrapping_mul(y) & all, w)),
Bin::Div => x.checked_div(y).map(|v| (v, w)),
Bin::Mod => x.checked_rem(y).map(|v| (v, w)),
Bin::Eq => bit(x == y),
Bin::Ne => bit(x != y),
Bin::LogicalAnd => bit(x != 0 && y != 0),
Bin::LogicalOr => bit(x != 0 || y != 0),
Bin::Lt => bit(x < y),
Bin::Le => bit(x <= y),
Bin::Gt => bit(x > y),
Bin::Ge => bit(x >= y),
Bin::BitAnd => Some((x & y, w)),
Bin::BitOr => Some((x | y, w)),
Bin::BitXor => Some((x ^ y, w)),
Bin::BitXnor => Some((!(x ^ y) & all, w)),
Bin::Shl => Some((x.checked_shl(y.min(64) as u32).unwrap_or(0) & mask(wa), wa)),
Bin::Shr => Some((x.checked_shr(y.min(64) as u32).unwrap_or(0), wa)),
}
}
Ex::Tern(c, t, f) => {
let (c, _) = eval(c, env)?;
let (tv, tw) = eval(t, env)?;
let (fv, fw) = eval(f, env)?;
Some((if c & 1 == 1 { tv } else { fv }, tw.max(fw)))
}
Ex::Cat(items) => {
let mut acc = 0u64;
let mut total = 0u32;
for i in items {
let (v, w) = eval(i, env)?;
total = total.checked_add(w).filter(|&t| t <= 64)?;
acc = acc.checked_shl(w).unwrap_or(0) | v;
}
Some((acc, total))
}
Ex::Rep(n, item) => {
let (v, w) = eval(item, env)?;
let mut acc = 0u64;
let mut total = 0u32;
for _ in 0..*n {
total = total.checked_add(w).filter(|&t| t <= 64)?;
acc = acc.checked_shl(w).unwrap_or(0) | v;
}
Some((acc, total))
}
}
}
fn referenced(e: &Ex, out: &mut BTreeMap<String, u64>) {
match e {
Ex::Sig { name, lo, width } => {
*out.entry(name.clone()).or_default() |= mask(*width) << lo;
}
Ex::Const { .. } => {}
Ex::Un(_, a) | Ex::Rep(_, a) => referenced(a, out),
Ex::Bin(_, a, b) => {
referenced(a, out);
referenced(b, out);
}
Ex::Tern(a, b, c) => {
referenced(a, out);
referenced(b, out);
referenced(c, out);
}
Ex::Cat(items) => items.iter().for_each(|i| referenced(i, out)),
}
}
pub(crate) fn equivalent(
sdf: &CondExpr,
path: &CondNode,
scope: &dyn CondScope,
) -> Result<bool, String> {
let a = from_sdf(sdf, scope)?;
let b = from_sv(path, scope)?;
let mut refs = BTreeMap::new();
referenced(&a, &mut refs);
referenced(&b, &mut refs);
let total: u32 = refs.values().map(|m| m.count_ones()).sum();
if total > MAX_BITS {
return Err(format!(
"the conditions reference {total} signal bits (at most {MAX_BITS} can be compared)"
));
}
let vars: Vec<(&String, u32)> = refs
.iter()
.flat_map(|(name, m)| {
(0..64)
.filter(move |b| m >> b & 1 == 1)
.map(move |b| (name, b))
})
.collect();
for assignment in 0u32..(1 << total) {
let mut env = Env::new();
for (k, (name, bit)) in vars.iter().enumerate() {
if assignment >> k & 1 == 1 {
*env.entry((*name).clone()).or_default() |= 1u64 << bit;
}
}
let truth = |e: &Ex| eval(e, &env).map(|(v, _)| v & 1 == 1);
if truth(&a) != truth(&b) {
return Ok(false);
}
}
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
use vj_sdf::ast::{Port, PortBit};
struct Scope {
sigs: HashMap<&'static str, u32>,
specs: HashMap<SymbolId, u64>,
}
fn uid(name: &str) -> SymbolId {
SymbolId::synthetic(&format!("m.{name}"))
}
impl CondScope for Scope {
fn width_of(&self, name: &str) -> Option<u32> {
self.sigs.get(name).copied()
}
fn name_of(&self, uid: &SymbolId) -> Option<(String, u32)> {
let name = uid.path().strip_prefix("m.")?;
self.sigs.get(name).map(|&w| (name.to_string(), w))
}
fn specparam(&self, uid: &SymbolId) -> Option<u64> {
self.specs.get(uid).copied()
}
}
fn scope() -> Scope {
Scope {
sigs: HashMap::from([("a", 1), ("b", 1), ("sel", 2), ("wide", 32)]),
specs: HashMap::from([(uid("K"), 1)]),
}
}
fn sdf_port(name: &str) -> CondExpr {
CondExpr::Port(Port {
path: vec![name.to_string()],
bit: None,
})
}
fn sdf_bit(name: &str, n: i64) -> CondExpr {
CondExpr::Port(Port {
path: vec![name.to_string()],
bit: Some(PortBit::Bit(n)),
})
}
fn sdf_const(width: Option<u32>, value: u64) -> CondExpr {
CondExpr::Const { width, value }
}
fn sv_sig(name: &str) -> CondNode {
CondNode::Signal((uid(name), None))
}
fn bx<T>(v: T) -> Box<T> {
Box::new(v)
}
fn check(sdf: &CondExpr, sv: &CondNode) -> bool {
equivalent(sdf, sv, &scope()).unwrap()
}
#[test]
fn a_port_matches_itself_and_not_another() {
assert!(check(&sdf_port("a"), &sv_sig("a")));
assert!(!check(&sdf_port("a"), &sv_sig("b")));
}
#[test]
fn different_spellings_of_one_condition_are_equivalent() {
let not_a = CondNode::Unary(SvUn::LogicalNot, bx(sv_sig("a")));
assert!(check(
&CondExpr::Unary(SdfUn::LogicalNot, bx(sdf_port("a"))),
¬_a
));
assert!(check(
&CondExpr::Unary(SdfUn::BitNot, bx(sdf_port("a"))),
¬_a
));
assert!(check(
&CondExpr::Binary(SdfBin::Eq, bx(sdf_port("a")), bx(sdf_const(Some(1), 0))),
¬_a
));
assert!(check(
&CondExpr::Binary(SdfBin::Ne, bx(sdf_port("a")), bx(sdf_const(None, 1))),
¬_a
));
assert!(!check(&sdf_port("a"), ¬_a));
}
#[test]
fn two_signal_conditions_compare_over_both() {
let sv = CondNode::Binary(SvBin::LogicalAnd, bx(sv_sig("a")), bx(sv_sig("b")));
assert!(check(
&CondExpr::Binary(SdfBin::LogicalAnd, bx(sdf_port("b")), bx(sdf_port("a"))),
&sv
));
assert!(check(
&CondExpr::Binary(SdfBin::BitAnd, bx(sdf_port("a")), bx(sdf_port("b"))),
&sv
));
assert!(!check(
&CondExpr::Binary(SdfBin::LogicalOr, bx(sdf_port("a")), bx(sdf_port("b"))),
&sv
));
}
#[test]
fn a_multi_bit_condition_is_true_by_its_least_significant_bit() {
let sv = sv_sig("sel");
assert!(check(&sdf_bit("sel", 0), &sv));
assert!(!check(
&CondExpr::Binary(SdfBin::Ne, bx(sdf_port("sel")), bx(sdf_const(Some(2), 0))),
&sv
));
}
#[test]
fn a_bit_select_terminal_matches_the_sdf_bit() {
let sv = CondNode::Signal((uid("sel"), Some((1, 1))));
assert!(check(&sdf_bit("sel", 1), &sv));
assert!(!check(&sdf_bit("sel", 0), &sv));
}
#[test]
fn a_wide_signal_costs_only_the_bits_referenced() {
let sv = CondNode::Signal((uid("wide"), Some((31, 1))));
assert!(check(&sdf_bit("wide", 31), &sv));
}
#[test]
fn a_specparam_is_its_value() {
let sv = CondNode::Binary(
SvBin::LogicalAnd,
bx(sv_sig("a")),
bx(CondNode::Specparam(uid("K"))),
);
assert!(check(&sdf_port("a"), &sv));
}
#[test]
fn concat_replication_ternary_and_constants() {
let sv = CondNode::Binary(
SvBin::Equality,
bx(CondNode::Concat(vec![sv_sig("a"), sv_sig("b")])),
bx(CondNode::Const(vec![Bit4::Zero, Bit4::One])),
);
let sdf = CondExpr::Binary(
SdfBin::LogicalAnd,
bx(sdf_port("a")),
bx(CondExpr::Unary(SdfUn::LogicalNot, bx(sdf_port("b")))),
);
assert!(check(&sdf, &sv));
let tern = CondNode::Ternary(
bx(sv_sig("a")),
bx(sv_sig("b")),
bx(CondNode::Const(vec![Bit4::Zero])),
);
assert!(check(
&CondExpr::Binary(SdfBin::LogicalAnd, bx(sdf_port("a")), bx(sdf_port("b"))),
&tern
));
let rep = CondNode::Replication(2, bx(sv_sig("a")));
assert!(check(&sdf_port("a"), &rep));
}
#[test]
fn reductions_and_arithmetic() {
let red = CondNode::Unary(SvUn::ReductionAnd, bx(sv_sig("sel")));
assert!(check(
&CondExpr::Binary(SdfBin::Eq, bx(sdf_port("sel")), bx(sdf_const(Some(2), 3))),
&red
));
assert!(check(
&CondExpr::Unary(SdfUn::RedAnd, bx(sdf_port("sel"))),
&red
));
let add = CondExpr::Binary(SdfBin::Add, bx(sdf_port("sel")), bx(sdf_const(Some(2), 1)));
let not_bit0 = CondNode::Unary(
SvUn::LogicalNot,
bx(CondNode::Signal((uid("sel"), Some((0, 1))))),
);
assert!(check(&add, ¬_bit0));
}
#[test]
fn a_division_by_zero_makes_both_sides_unevaluable_and_unequal() {
let sdf = CondExpr::Binary(SdfBin::Div, bx(sdf_port("a")), bx(sdf_const(Some(1), 0)));
assert!(!check(&sdf, &sv_sig("a")));
}
#[test]
fn unknown_names_and_hierarchical_ports_are_errors() {
let sc = scope();
let err = equivalent(&sdf_port("nope"), &sv_sig("a"), &sc).unwrap_err();
assert!(err.contains("`nope`"), "{err}");
let hier = CondExpr::Port(Port {
path: vec!["u".into(), "a".into()],
bit: None,
});
assert!(
equivalent(&hier, &sv_sig("a"), &sc)
.unwrap_err()
.contains("hierarchical")
);
assert!(
equivalent(&sdf_bit("a", 3), &sv_sig("a"), &sc)
.unwrap_err()
.contains("outside")
);
}
#[test]
fn an_x_constant_is_an_error() {
let sv = CondNode::Const(vec![Bit4::X]);
let err = equivalent(&sdf_port("a"), &sv, &scope()).unwrap_err();
assert!(err.contains("x or z"), "{err}");
}
#[test]
fn too_many_bits_is_an_error_not_a_guess() {
let wide = CondNode::Signal((uid("wide"), None));
let sdf = CondExpr::Unary(SdfUn::RedOr, bx(sdf_port("wide")));
let err = equivalent(&sdf, &wide, &scope()).unwrap_err();
assert!(err.contains("32 signal bits"), "{err}");
}
}