Skip to main content

vj_sim/constraints/
ir.rs

1//! The word-level constraint IR (plan section 1.2) and its direct evaluator.
2//!
3//! The IR is a small tree of 2-state bit-vector expressions. It is built once
4//! per constraint block by `build` (from the bound sv-lang AST), consumed
5//! structurally by the BDD bit-blaster, and *evaluated directly* by [`eval`]
6//! here, which is the semantic oracle every solver gadget is checked against
7//! (a bit-blasted BDD must agree with [`eval`] on every assignment).
8//!
9//! # Semantics (deliberately NOT codegen's 4-state semantics)
10//!
11//! LRM 18.3: constraints are 2-state only. There is no X/Z lane, no
12//! "any-unknown-propagates" rule: arithmetic is plain two's-complement
13//! modulo `2^width`. The one place a 4-state value can reach the IR is a
14//! state-variable read; a state value with an X/Z bit is the typed
15//! evaluation error [`ConstraintEvalError::StateUnknown`] (decision E9), never
16//! a silent 0.
17//!
18//! # Typing
19//!
20//! Every node carries its result [`Ty`] (width and signedness), so [`Expr::ty`]
21//! is O(1). The builder derives the types from sv-lang, whose context-
22//! determined-width rule (LRM 11.6) has already inserted the `Conversion`
23//! nodes that equalise operand types; [`Expr::validate`] re-checks the same
24//! rules so an ill-typed tree is a loud [`IrError`] at build time rather than
25//! a wrong answer at solve time. Operand rules:
26//!
27//! - `Add Sub Mul Div Mod And Or Xor Xnor`: both operands and the result share
28//!   one type.
29//! - `Shl Shr Ashr`: the result has the left operand's type; the shift
30//!   amount may be any width and is always read unsigned (LRM 11.4.10).
31//! - `Eq Ne Lt Le Gt Ge`: both operands share one type (signedness selects
32//!   signed vs unsigned ordering); the result is one unsigned bit.
33//! - `LogAnd LogOr LogImpl LogEquiv`, `LogNot`, reductions: operands of any
34//!   type (truthiness = "nonzero"), one unsigned result bit.
35//! - `AllDistinct`: every member shares one type; one unsigned result bit.
36
37use core::fmt;
38use num_bigint::{BigInt, BigUint};
39use num_traits::{One, Zero};
40
41/// Index of a random (solved) variable.
42pub type VarId = u32;
43/// Index of a state variable (a value fixed before the solve).
44pub type StateId = u32;
45
46/// A bit-vector type: `width >= 1` bits, interpreted signed or unsigned.
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
48pub struct Ty {
49    /// Number of bits, at least 1.
50    pub width: u32,
51    /// Two's-complement signed when set.
52    pub signed: bool,
53}
54
55impl Ty {
56    /// An unsigned type of `width` bits.
57    pub const fn unsigned(width: u32) -> Ty {
58        Ty {
59            width,
60            signed: false,
61        }
62    }
63    /// A signed type of `width` bits.
64    pub const fn signed(width: u32) -> Ty {
65        Ty {
66            width,
67            signed: true,
68        }
69    }
70    /// The one-bit unsigned type every boolean-valued node produces.
71    pub const BIT: Ty = Ty::unsigned(1);
72}
73
74impl fmt::Display for Ty {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        write!(f, "{}[{}]", if self.signed { "s" } else { "u" }, self.width)
77    }
78}
79
80/// Unary operators.
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
82pub enum UnOp {
83    /// Bitwise `~`. Result type = operand type.
84    BitNot,
85    /// Arithmetic negation `-`. Result type = operand type.
86    Neg,
87    /// Logical `!`. One-bit result.
88    LogNot,
89    /// Reduction `&`. One-bit result.
90    RedAnd,
91    /// Reduction `|`. One-bit result.
92    RedOr,
93    /// Reduction `^`. One-bit result.
94    RedXor,
95    /// Reduction `~&`. One-bit result.
96    RedNand,
97    /// Reduction `~|`. One-bit result.
98    RedNor,
99    /// Reduction `~^`. One-bit result.
100    RedXnor,
101}
102
103/// Binary operators.
104#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
105pub enum BinOp {
106    /// `+`.
107    Add,
108    /// `-`.
109    Sub,
110    /// `*`.
111    Mul,
112    /// `/` (truncating; signed when the operand type is).
113    Div,
114    /// `%` (sign of the dividend; signed when the operand type is).
115    Mod,
116    /// Bitwise `&`.
117    And,
118    /// Bitwise `|`.
119    Or,
120    /// Bitwise `^`.
121    Xor,
122    /// Bitwise `~^` / `^~`.
123    Xnor,
124    /// `<<` and `<<<` (identical).
125    Shl,
126    /// `>>` (logical).
127    Shr,
128    /// `>>>` (sign-filling when the left operand is signed).
129    Ashr,
130    /// `==`.
131    Eq,
132    /// `!=`.
133    Ne,
134    /// `<`.
135    Lt,
136    /// `<=`.
137    Le,
138    /// `>`.
139    Gt,
140    /// `>=`.
141    Ge,
142    /// `&&`.
143    LogAnd,
144    /// `||`.
145    LogOr,
146    /// `->` (logical implication: `!a || b`).
147    LogImpl,
148    /// `<->` (logical equivalence).
149    LogEquiv,
150}
151
152impl BinOp {
153    fn is_compare(self) -> bool {
154        matches!(
155            self,
156            BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge
157        )
158    }
159    fn is_logical(self) -> bool {
160        matches!(
161            self,
162            BinOp::LogAnd | BinOp::LogOr | BinOp::LogImpl | BinOp::LogEquiv
163        )
164    }
165    fn is_shift(self) -> bool {
166        matches!(self, BinOp::Shl | BinOp::Shr | BinOp::Ashr)
167    }
168}
169
170/// A word-level 2-state expression.
171#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
172pub enum Expr {
173    /// A constant; the value is below `2^width`.
174    Const(Ty, BigUint),
175    /// A random variable being solved for.
176    Var(VarId, Ty),
177    /// A state variable: a value fixed before the solve.
178    State(StateId, Ty),
179    /// A unary operation; the [`Ty`] is the result type.
180    Unary(UnOp, Box<Expr>, Ty),
181    /// A binary operation; the [`Ty`] is the result type.
182    Binary(BinOp, Box<Expr>, Box<Expr>, Ty),
183    /// `cond ? t : f` where `cond` is read as its truthiness.
184    Ite(Box<Expr>, Box<Expr>, Box<Expr>, Ty),
185    /// A constraint-level `if (pred) then else else_` or `pred -> then`
186    /// (LRM 18.5.5, 18.5.6): the same value as [`Expr::Ite`] on `pred`, but
187    /// marked so [`super::guard::resolve`] can treat `pred` as a *constraint
188    /// guard* (LRM 18.5.12) before the solve. An expression-level `->` or
189    /// `?:` is never a guard and keeps its own node. Evaluated and
190    /// bit-blasted exactly like `Ite`, so a tree that still holds one (a
191    /// builder test, a guard whose value is `RANDOM`) means what it says.
192    Guard(Box<Expr>, Box<Expr>, Box<Expr>, Ty),
193    /// Width/sign change: truncates, or extends using the *operand's*
194    /// signedness (LRM 11.8.2), then relabels with the result signedness.
195    Resize(Box<Expr>, Ty),
196    /// Concatenation, first element most significant. Always unsigned.
197    Concat(Vec<Expr>, Ty),
198    /// The constant window `[lo + width - 1 : lo]` of the operand
199    /// (physical bit positions, bit 0 least significant). Always unsigned.
200    Slice(Box<Expr>, u32, Ty),
201    /// `unique { m0, m1, ... }` (LRM 18.5.4): true iff no two members hold
202    /// the same value. Always `u[1]`. The members are random or state
203    /// variables of one type (checked by [`Expr::validate`]); a group of
204    /// fewer than two members is never built (the LRM makes it a no-op), but a
205    /// tree that holds one is simply true.
206    AllDistinct(Vec<Expr>),
207    /// An array-level node (LRM 18.4, 18.5.7): an array's size or element, a
208    /// `foreach` loop or an array reduction, and (LRM 18.5.11) a function
209    /// call, which is not about arrays but is resolved at the same point and
210    /// by the same rule. These exist only between the
211    /// builder and the run-time expansion (`super::array::expand`), which
212    /// replaces every one with plain scalar IR once the array sizes are known;
213    /// [`eval`] and the bit-blaster reject a tree that still holds one with
214    /// [`ConstraintEvalError::Unexpanded`].
215    Array(ArrayNode),
216}
217
218/// Index of an array in a program's array table.
219pub type ArrayId = u32;
220/// Identity of one `foreach` loop variable or reduction `item` binding.
221pub type LoopId = u32;
222
223/// An array reduction method (LRM 7.12.3).
224#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
225pub enum ReduceOp {
226    /// `sum()`: identity 0.
227    Sum,
228    /// `product()`: identity 1.
229    Product,
230    /// `and()`: identity all ones.
231    And,
232    /// `or()`: identity 0.
233    Or,
234    /// `xor()`: identity 0.
235    Xor,
236}
237
238/// The array-level IR nodes (see [`Expr::Array`]).
239#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
240pub enum ArrayNode {
241    /// `A.size()`: the array's length, `s[32]`.
242    Size(ArrayId),
243    /// `A[index]` (LRM 18.5.7.1). After loop-variable substitution the index
244    /// must be free of random variables (the builder rejects the rest).
245    Elem {
246        /// The indexed array.
247        array: ArrayId,
248        /// The index expression.
249        index: Box<Expr>,
250        /// The element type.
251        ty: Ty,
252    },
253    /// The current value of a `foreach` loop variable or reduction iterator.
254    Loop(LoopId, Ty),
255    /// `foreach (A[i]) body`: `body` holds for every index `i` of `A`. One
256    /// bit. The body reads the index as `Loop(var, s[32])`.
257    Foreach {
258        /// The iterated array.
259        array: ArrayId,
260        /// The loop variable.
261        var: LoopId,
262        /// The constraint body (a boolean expression).
263        body: Box<Expr>,
264    },
265    /// `A.op() with (body)`: the reduction of `body` over every element,
266    /// read as `Loop(var, elem_ty)` for the item and `Loop(idx, s[32])` for
267    /// `item.index`. The body's type is the result type.
268    Reduce {
269        /// The reduction.
270        op: ReduceOp,
271        /// The reduced array.
272        array: ArrayId,
273        /// The `item` binding.
274        var: LoopId,
275        /// The `item.index` binding.
276        index_var: LoopId,
277        /// The per-element expression (the item itself without a `with`).
278        body: Box<Expr>,
279    },
280    /// A call of a user function (LRM 18.5.11), `func` indexing the program's
281    /// function table ([`super::func`]). Like the nodes above it is resolved
282    /// before the solver sees the tree: once its arguments are constants the
283    /// call is evaluated and replaced by the constant it returns (the return
284    /// value is a state variable of the constraint that holds it).
285    Call {
286        /// The called function.
287        func: FuncId,
288        /// The actual arguments, one per formal, already converted to the
289        /// formal's type.
290        args: Vec<Expr>,
291        /// The return type.
292        ty: Ty,
293    },
294    /// `A[index]` over a fixed-size unpacked array held in a class property
295    /// (LRM 7.4, 18.4): every element is its own variable or state, and the
296    /// element read is the one the index names. The index is a constant, a state
297    /// or a loop variable, so it is known once the node is expanded; only the
298    /// chosen element is expanded, so an index outside the array is an error
299    /// only when this node is live (LRM 18.5.7.1: the user excludes such indices
300    /// with a guard).
301    Pick {
302        /// The index expression.
303        index: Box<Expr>,
304        /// The array's lowest declared index: `elems[i - low]` is element `i`.
305        low: i64,
306        /// The elements, lowest declared index first; all of type `ty`.
307        elems: Vec<Expr>,
308        /// The element type.
309        ty: Ty,
310    },
311    /// `unique { m0, m1, ... }` (LRM 18.5.4) whose group holds a whole
312    /// dynamic array or queue, whose length is known only at run time: true
313    /// iff no two of the `scalars` and of every element of every array in
314    /// `arrays` hold the same value. One bit. Expansion replaces it with the
315    /// [`Expr::AllDistinct`] over the scalars followed by each array's
316    /// elements (a group of fewer than two members is simply true).
317    Distinct {
318        /// The singular members (variables or states of the element type).
319        scalars: Vec<Expr>,
320        /// The dynamic arrays or queues whose every element is a member.
321        arrays: Vec<ArrayId>,
322    },
323    /// `body` read with the row array `template` standing for row `row` of the
324    /// array of arrays that `template` is the row template of (LRM 18.4, 7.4.5:
325    /// `a[i].size`, `a[i][j]`, `a[i].sum()` over `rand int a[][]`). The row
326    /// arrays of an array of dynamic arrays or queues are not known when the
327    /// constraints are built (the outer size is solved first, then every row
328    /// gets its own size and elements), so `body` is ordinary one-dimensional
329    /// array IR over `template`, and expansion rebinds `template` to the
330    /// concrete row array of the row `row` names once the outer size is fixed
331    /// ([`super::array::lower_rows`]). `row` is free of random variables, like
332    /// an element index.
333    RowScope {
334        /// The row template: a program array that only stands for "the row".
335        template: ArrayId,
336        /// The row index (an index into the outer array).
337        row: Box<Expr>,
338        /// The expression over `template`.
339        body: Box<Expr>,
340    },
341}
342
343/// Index of a function in a program's function table.
344pub type FuncId = u32;
345
346/// A malformed IR tree (a builder bug or an out-of-contract construction).
347#[derive(Clone, Debug, PartialEq, Eq)]
348pub enum IrError {
349    /// A node violates its operand/result typing rule.
350    IllTyped {
351        /// Which node and rule.
352        what: String,
353    },
354}
355
356impl fmt::Display for IrError {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        match self {
359            IrError::IllTyped { what } => write!(f, "ill-typed constraint IR: {what}"),
360        }
361    }
362}
363
364impl std::error::Error for IrError {}
365
366/// A failure while evaluating an [`Expr`] directly.
367#[derive(Clone, Debug, PartialEq, Eq)]
368pub enum ConstraintEvalError {
369    /// The state variable had an X/Z bit (LRM 18.3: 4-state values in a
370    /// constraint are an error; decision E9).
371    StateUnknown(StateId),
372    /// `/` or `%` with a zero divisor.
373    DivideByZero,
374    /// The assignment has no value for this random variable.
375    UnboundVar(VarId),
376    /// The assignment has no entry for this state variable.
377    UnboundState(StateId),
378    /// The tree still holds an array-level node ([`Expr::Array`]); the
379    /// run-time expansion must replace it before evaluation.
380    Unexpanded,
381}
382
383impl fmt::Display for ConstraintEvalError {
384    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385        match self {
386            ConstraintEvalError::StateUnknown(s) => write!(
387                f,
388                "state variable #{s} has an X/Z bit; a 4-state value in a constraint is an \
389                 error (IEEE 1800-2023 18.3)"
390            ),
391            ConstraintEvalError::DivideByZero => f.write_str("division by zero in a constraint"),
392            ConstraintEvalError::UnboundVar(v) => write!(f, "no value for random variable #{v}"),
393            ConstraintEvalError::UnboundState(s) => {
394                write!(f, "no value for state variable #{s}")
395            }
396            ConstraintEvalError::Unexpanded => {
397                f.write_str("an array-level constraint node was not expanded before evaluation")
398            }
399        }
400    }
401}
402
403impl std::error::Error for ConstraintEvalError {}
404
405/// Concrete values for [`eval`]. A `None` state entry means the state
406/// variable currently holds an X/Z bit.
407#[derive(Clone, Debug, Default, PartialEq, Eq)]
408pub struct Assignment {
409    /// Random-variable values, indexed by [`VarId`] (each below `2^width`).
410    pub vars: Vec<BigUint>,
411    /// State-variable values, indexed by [`StateId`]; `None` = has X/Z.
412    pub states: Vec<Option<BigUint>>,
413}
414
415/// `2^w - 1`.
416pub fn mask(w: u32) -> BigUint {
417    (BigUint::one() << w) - BigUint::one()
418}
419
420fn to_signed(v: &BigUint, w: u32) -> BigInt {
421    if v.bit(u64::from(w) - 1) {
422        BigInt::from(v.clone()) - (BigInt::one() << w)
423    } else {
424        BigInt::from(v.clone())
425    }
426}
427
428fn from_signed(i: &BigInt, w: u32) -> BigUint {
429    let m = BigInt::one() << w;
430    let r = ((i % &m) + &m) % &m;
431    r.to_biguint().expect("reduced into [0, 2^w)")
432}
433
434fn bit(b: bool) -> BigUint {
435    if b { BigUint::one() } else { BigUint::zero() }
436}
437
438impl Expr {
439    /// A constant of type `ty` holding `val` truncated to the width.
440    pub fn constant(ty: Ty, val: impl Into<BigUint>) -> Expr {
441        Expr::Const(ty, val.into() & mask(ty.width))
442    }
443
444    /// The result type of this node.
445    pub fn ty(&self) -> Ty {
446        match self {
447            Expr::Const(t, _)
448            | Expr::Var(_, t)
449            | Expr::State(_, t)
450            | Expr::Unary(_, _, t)
451            | Expr::Binary(_, _, _, t)
452            | Expr::Ite(_, _, _, t)
453            | Expr::Guard(_, _, _, t)
454            | Expr::Resize(_, t)
455            | Expr::Concat(_, t)
456            | Expr::Slice(_, _, t) => *t,
457            Expr::AllDistinct(_) => Ty::BIT,
458            Expr::Array(n) => n.ty(),
459        }
460    }
461
462    /// Checks every node against its typing rule (module docs).
463    pub fn validate(&self) -> Result<(), IrError> {
464        let bad = |what: String| Err(IrError::IllTyped { what });
465        match self {
466            Expr::Const(t, v) => {
467                if t.width == 0 {
468                    return bad("zero-width constant".into());
469                }
470                if v.bits() > u64::from(t.width) {
471                    return bad(format!("constant {v} does not fit {t}"));
472                }
473                Ok(())
474            }
475            Expr::Var(_, t) | Expr::State(_, t) => {
476                if t.width == 0 {
477                    return bad("zero-width variable".into());
478                }
479                Ok(())
480            }
481            Expr::Unary(op, a, t) => {
482                a.validate()?;
483                let at = a.ty();
484                let want = match op {
485                    UnOp::BitNot | UnOp::Neg => at,
486                    _ => Ty::BIT,
487                };
488                if *t != want {
489                    return bad(format!("{op:?} on {at} must produce {want}, has {t}"));
490                }
491                Ok(())
492            }
493            Expr::Binary(op, l, r, t) => {
494                l.validate()?;
495                r.validate()?;
496                let (lt, rt) = (l.ty(), r.ty());
497                if op.is_logical() {
498                    if *t != Ty::BIT {
499                        return bad(format!("{op:?} must produce u[1], has {t}"));
500                    }
501                } else if op.is_compare() {
502                    if lt != rt {
503                        return bad(format!("{op:?} operands differ: {lt} vs {rt}"));
504                    }
505                    if *t != Ty::BIT {
506                        return bad(format!("{op:?} must produce u[1], has {t}"));
507                    }
508                } else if op.is_shift() {
509                    if *t != lt {
510                        return bad(format!("{op:?} result {t} must equal left operand {lt}"));
511                    }
512                } else if lt != rt || *t != lt {
513                    return bad(format!("{op:?} operands/result differ: {lt}, {rt}, {t}"));
514                }
515                Ok(())
516            }
517            Expr::Ite(c, a, b, t) | Expr::Guard(c, a, b, t) => {
518                c.validate()?;
519                a.validate()?;
520                b.validate()?;
521                if a.ty() != *t || b.ty() != *t {
522                    return bad(format!(
523                        "ternary arms {} / {} must both equal result {t}",
524                        a.ty(),
525                        b.ty()
526                    ));
527                }
528                Ok(())
529            }
530            Expr::Resize(a, t) => {
531                a.validate()?;
532                if t.width == 0 {
533                    return bad("resize to zero width".into());
534                }
535                Ok(())
536            }
537            Expr::Concat(parts, t) => {
538                if parts.is_empty() {
539                    return bad("empty concatenation".into());
540                }
541                let mut w = 0u32;
542                for p in parts {
543                    p.validate()?;
544                    w += p.ty().width;
545                }
546                if *t != Ty::unsigned(w) {
547                    return bad(format!("concatenation of {w} bits has type {t}"));
548                }
549                Ok(())
550            }
551            Expr::Slice(a, lo, t) => {
552                a.validate()?;
553                if t.signed || t.width == 0 || lo + t.width > a.ty().width {
554                    return bad(format!(
555                        "slice [{}+:{}] of {} (result {t})",
556                        lo,
557                        t.width,
558                        a.ty()
559                    ));
560                }
561                Ok(())
562            }
563            Expr::AllDistinct(members) => {
564                let Some(first) = members.first() else {
565                    return bad("empty uniqueness group".into());
566                };
567                let t = first.ty();
568                for m in members {
569                    m.validate()?;
570                    if m.ty() != t {
571                        return bad(format!(
572                            "uniqueness members differ in type: {t} vs {}",
573                            m.ty()
574                        ));
575                    }
576                }
577                Ok(())
578            }
579            Expr::Array(n) => n.validate(),
580        }
581    }
582
583    /// Calls `f` on every random variable this expression reads.
584    pub fn for_each_var(&self, f: &mut impl FnMut(VarId, Ty)) {
585        match self {
586            Expr::Var(v, t) => f(*v, *t),
587            Expr::Const(..) | Expr::State(..) => {}
588            Expr::Unary(_, a, _) | Expr::Resize(a, _) | Expr::Slice(a, _, _) => {
589                a.for_each_var(f);
590            }
591            Expr::Binary(_, a, b, _) => {
592                a.for_each_var(f);
593                b.for_each_var(f);
594            }
595            Expr::Ite(c, a, b, _) | Expr::Guard(c, a, b, _) => {
596                c.for_each_var(f);
597                a.for_each_var(f);
598                b.for_each_var(f);
599            }
600            Expr::Concat(parts, _) | Expr::AllDistinct(parts) => {
601                parts.iter().for_each(|p| p.for_each_var(f));
602            }
603            Expr::Array(n) => n.for_each_child(&mut |c| c.for_each_var(f)),
604        }
605    }
606
607    /// Calls `f` on every state variable this expression reads.
608    pub fn for_each_state(&self, f: &mut impl FnMut(StateId)) {
609        match self {
610            Expr::State(s, _) => f(*s),
611            Expr::Const(..) | Expr::Var(..) => {}
612            Expr::Unary(_, a, _) | Expr::Resize(a, _) | Expr::Slice(a, _, _) => {
613                a.for_each_state(f);
614            }
615            Expr::Binary(_, a, b, _) => {
616                a.for_each_state(f);
617                b.for_each_state(f);
618            }
619            Expr::Ite(c, a, b, _) | Expr::Guard(c, a, b, _) => {
620                c.for_each_state(f);
621                a.for_each_state(f);
622                b.for_each_state(f);
623            }
624            Expr::Concat(parts, _) | Expr::AllDistinct(parts) => {
625                parts.iter().for_each(|p| p.for_each_state(f));
626            }
627            Expr::Array(n) => n.for_each_child(&mut |c| c.for_each_state(f)),
628        }
629    }
630
631    /// A copy of this expression with every random-variable read replaced by
632    /// `f(id, ty)`. The replacement must have type `ty` (the caller's
633    /// contract: a renumbered [`Expr::Var`] or a same-typed constant); the
634    /// debug assertion pins that so a mis-typed substitution fails loudly
635    /// instead of producing an ill-typed tree.
636    ///
637    /// This is how `rand_mode(0)` variables are turned into state before a
638    /// solve (LRM 18.8: an inactive random variable "is treated the same as
639    /// any other state variable"), and how the surviving active variables are
640    /// renumbered densely.
641    pub fn substitute_vars(&self, f: &mut impl FnMut(VarId, Ty) -> Expr) -> Expr {
642        match self {
643            Expr::Var(v, t) => {
644                let r = f(*v, *t);
645                debug_assert_eq!(r.ty(), *t, "substitute_vars: replacement type differs");
646                r
647            }
648            Expr::Const(..) | Expr::State(..) => self.clone(),
649            Expr::Unary(op, a, t) => Expr::Unary(*op, Box::new(a.substitute_vars(f)), *t),
650            Expr::Resize(a, t) => Expr::Resize(Box::new(a.substitute_vars(f)), *t),
651            Expr::Slice(a, lo, t) => Expr::Slice(Box::new(a.substitute_vars(f)), *lo, *t),
652            Expr::Binary(op, a, b, t) => {
653                let a = Box::new(a.substitute_vars(f));
654                let b = Box::new(b.substitute_vars(f));
655                Expr::Binary(*op, a, b, *t)
656            }
657            Expr::Ite(c, a, b, t) => {
658                let c = Box::new(c.substitute_vars(f));
659                let a = Box::new(a.substitute_vars(f));
660                let b = Box::new(b.substitute_vars(f));
661                Expr::Ite(c, a, b, *t)
662            }
663            Expr::Guard(c, a, b, t) => {
664                let c = Box::new(c.substitute_vars(f));
665                let a = Box::new(a.substitute_vars(f));
666                let b = Box::new(b.substitute_vars(f));
667                Expr::Guard(c, a, b, *t)
668            }
669            Expr::Concat(parts, t) => {
670                Expr::Concat(parts.iter().map(|p| p.substitute_vars(f)).collect(), *t)
671            }
672            Expr::AllDistinct(parts) => {
673                Expr::AllDistinct(parts.iter().map(|p| p.substitute_vars(f)).collect())
674            }
675            Expr::Array(n) => Expr::Array(n.map_children(&mut |c| c.substitute_vars(f))),
676        }
677    }
678}
679
680impl Expr {
681    /// A copy of this expression with every direct sub-expression replaced by
682    /// `f(child)` (the first error wins). Leaves are copied unchanged.
683    pub fn try_map_children<E>(
684        &self,
685        f: &mut impl FnMut(&Expr) -> Result<Expr, E>,
686    ) -> Result<Expr, E> {
687        Ok(match self {
688            Expr::Const(..) | Expr::Var(..) | Expr::State(..) => self.clone(),
689            Expr::Unary(op, a, t) => Expr::Unary(*op, Box::new(f(a)?), *t),
690            Expr::Binary(op, a, b, t) => Expr::Binary(*op, Box::new(f(a)?), Box::new(f(b)?), *t),
691            Expr::Ite(c, a, b, t) => {
692                Expr::Ite(Box::new(f(c)?), Box::new(f(a)?), Box::new(f(b)?), *t)
693            }
694            Expr::Guard(c, a, b, t) => {
695                Expr::Guard(Box::new(f(c)?), Box::new(f(a)?), Box::new(f(b)?), *t)
696            }
697            Expr::Resize(a, t) => Expr::Resize(Box::new(f(a)?), *t),
698            Expr::Concat(parts, t) => {
699                Expr::Concat(parts.iter().map(&mut *f).collect::<Result<_, _>>()?, *t)
700            }
701            Expr::Slice(a, lo, t) => Expr::Slice(Box::new(f(a)?), *lo, *t),
702            Expr::AllDistinct(parts) => {
703                Expr::AllDistinct(parts.iter().map(&mut *f).collect::<Result<_, _>>()?)
704            }
705            Expr::Array(n) => {
706                let mut err = None;
707                let r = n.map_children(&mut |c| match f(c) {
708                    Ok(x) => x,
709                    Err(e) => {
710                        err.get_or_insert(e);
711                        c.clone()
712                    }
713                });
714                if let Some(e) = err {
715                    return Err(e);
716                }
717                Expr::Array(r)
718            }
719        })
720    }
721
722    /// Whether any node of this expression satisfies `pred`.
723    pub fn any_node(&self, pred: &mut impl FnMut(&Expr) -> bool) -> bool {
724        if pred(self) {
725            return true;
726        }
727        let mut found = false;
728        let mut visit = |c: &Expr| {
729            if !found && c.any_node(pred) {
730                found = true;
731            }
732        };
733        match self {
734            Expr::Const(..) | Expr::Var(..) | Expr::State(..) => {}
735            Expr::Unary(_, a, _) | Expr::Resize(a, _) | Expr::Slice(a, _, _) => visit(a),
736            Expr::Binary(_, a, b, _) => {
737                visit(a);
738                visit(b);
739            }
740            Expr::Ite(c, a, b, _) | Expr::Guard(c, a, b, _) => {
741                visit(c);
742                visit(a);
743                visit(b);
744            }
745            Expr::Concat(parts, _) | Expr::AllDistinct(parts) => parts.iter().for_each(visit),
746            Expr::Array(n) => n.for_each_child(&mut visit),
747        }
748        found
749    }
750}
751
752impl ArrayNode {
753    /// The result type of this node.
754    pub fn ty(&self) -> Ty {
755        match self {
756            ArrayNode::Size(_) => Ty::signed(32),
757            ArrayNode::Elem { ty, .. } | ArrayNode::Loop(_, ty) => *ty,
758            ArrayNode::Foreach { .. } | ArrayNode::Distinct { .. } => Ty::BIT,
759            ArrayNode::Reduce { body, .. } | ArrayNode::RowScope { body, .. } => body.ty(),
760            ArrayNode::Call { ty, .. } | ArrayNode::Pick { ty, .. } => *ty,
761        }
762    }
763
764    fn validate(&self) -> Result<(), IrError> {
765        let bad = |what: String| Err(IrError::IllTyped { what });
766        match self {
767            ArrayNode::Size(_) => Ok(()),
768            ArrayNode::Loop(_, t) => {
769                if t.width == 0 {
770                    return bad("zero-width loop variable".into());
771                }
772                Ok(())
773            }
774            ArrayNode::Elem { index, ty, .. } => {
775                index.validate()?;
776                if ty.width == 0 {
777                    return bad("zero-width array element".into());
778                }
779                Ok(())
780            }
781            ArrayNode::Foreach { body, .. } => body.validate(),
782            ArrayNode::RowScope { row, body, .. } => {
783                row.validate()?;
784                body.validate()
785            }
786            ArrayNode::Distinct { scalars, arrays } => {
787                if scalars.is_empty() && arrays.is_empty() {
788                    return bad("a distinct group with no members".into());
789                }
790                scalars.iter().try_for_each(Expr::validate)
791            }
792            ArrayNode::Reduce { op, body, .. } => {
793                body.validate()?;
794                if matches!(op, ReduceOp::And | ReduceOp::Or | ReduceOp::Xor)
795                    && body.ty().width == 0
796                {
797                    return bad("zero-width reduction".into());
798                }
799                Ok(())
800            }
801            ArrayNode::Call { args, ty, .. } => {
802                if ty.width == 0 {
803                    return bad("zero-width function result".into());
804                }
805                args.iter().try_for_each(Expr::validate)
806            }
807            ArrayNode::Pick {
808                index, elems, ty, ..
809            } => {
810                index.validate()?;
811                if ty.width == 0 {
812                    return bad("zero-width array element".into());
813                }
814                if elems.is_empty() {
815                    return bad("a pick over no elements".into());
816                }
817                if elems.iter().any(|e| e.ty() != *ty) {
818                    return bad("a pick element of another type".into());
819                }
820                elems.iter().try_for_each(Expr::validate)
821            }
822        }
823    }
824
825    /// Calls `f` on every direct sub-expression.
826    pub fn for_each_child(&self, f: &mut impl FnMut(&Expr)) {
827        match self {
828            ArrayNode::Size(_) | ArrayNode::Loop(..) => {}
829            ArrayNode::Pick { index, elems, .. } => {
830                f(index);
831                elems.iter().for_each(f);
832            }
833            ArrayNode::Call { args, .. } => args.iter().for_each(f),
834            ArrayNode::Distinct { scalars, .. } => scalars.iter().for_each(f),
835            ArrayNode::Elem { index, .. } => f(index),
836            ArrayNode::Foreach { body, .. } | ArrayNode::Reduce { body, .. } => f(body),
837            ArrayNode::RowScope { row, body, .. } => {
838                f(row);
839                f(body);
840            }
841        }
842    }
843
844    /// A copy with every direct sub-expression replaced by `f(child)`.
845    pub fn map_children(&self, f: &mut impl FnMut(&Expr) -> Expr) -> ArrayNode {
846        match self {
847            ArrayNode::Size(_) | ArrayNode::Loop(..) => self.clone(),
848            ArrayNode::Pick {
849                index,
850                low,
851                elems,
852                ty,
853            } => ArrayNode::Pick {
854                index: Box::new(f(index)),
855                low: *low,
856                elems: elems.iter().map(&mut *f).collect(),
857                ty: *ty,
858            },
859            ArrayNode::Call { func, args, ty } => ArrayNode::Call {
860                func: *func,
861                args: args.iter().map(&mut *f).collect(),
862                ty: *ty,
863            },
864            ArrayNode::Distinct { scalars, arrays } => ArrayNode::Distinct {
865                scalars: scalars.iter().map(&mut *f).collect(),
866                arrays: arrays.clone(),
867            },
868            ArrayNode::Elem { array, index, ty } => ArrayNode::Elem {
869                array: *array,
870                index: Box::new(f(index)),
871                ty: *ty,
872            },
873            ArrayNode::Foreach { array, var, body } => ArrayNode::Foreach {
874                array: *array,
875                var: *var,
876                body: Box::new(f(body)),
877            },
878            ArrayNode::RowScope {
879                template,
880                row,
881                body,
882            } => ArrayNode::RowScope {
883                template: *template,
884                row: Box::new(f(row)),
885                body: Box::new(f(body)),
886            },
887            ArrayNode::Reduce {
888                op,
889                array,
890                var,
891                index_var,
892                body,
893            } => ArrayNode::Reduce {
894                op: *op,
895                array: *array,
896                var: *var,
897                index_var: *index_var,
898                body: Box::new(f(body)),
899            },
900        }
901    }
902}
903
904/// Directly evaluates `e` under `a`; the result is below `2^e.ty().width`.
905pub fn eval(e: &Expr, a: &Assignment) -> Result<BigUint, ConstraintEvalError> {
906    match e {
907        Expr::Const(_, v) => Ok(v.clone()),
908        Expr::Var(id, t) => a
909            .vars
910            .get(*id as usize)
911            .map(|v| v & mask(t.width))
912            .ok_or(ConstraintEvalError::UnboundVar(*id)),
913        Expr::State(id, t) => match a.states.get(*id as usize) {
914            None => Err(ConstraintEvalError::UnboundState(*id)),
915            Some(None) => Err(ConstraintEvalError::StateUnknown(*id)),
916            Some(Some(v)) => Ok(v & mask(t.width)),
917        },
918        Expr::Unary(op, x, t) => {
919            let v = eval(x, a)?;
920            let w = x.ty().width;
921            let r = match op {
922                UnOp::BitNot => mask(w) ^ v,
923                UnOp::Neg => ((BigUint::one() << w) - v) & mask(w),
924                UnOp::LogNot => bit(v.is_zero()),
925                UnOp::RedAnd => bit(v == mask(w)),
926                UnOp::RedNand => bit(v != mask(w)),
927                UnOp::RedOr => bit(!v.is_zero()),
928                UnOp::RedNor => bit(v.is_zero()),
929                UnOp::RedXor => bit(popcount(&v) % 2 == 1),
930                UnOp::RedXnor => bit(popcount(&v).is_multiple_of(2)),
931            };
932            Ok(r & mask(t.width))
933        }
934        Expr::Binary(op @ (BinOp::LogAnd | BinOp::LogOr | BinOp::LogImpl), l, r, _) => {
935            // `&&`, `||` and `->` short-circuit (LRM 11.4.7/11.4.8): the right
936            // operand is only evaluated when the left does not decide the
937            // result, so a division by zero in a decided-away arm is not an
938            // error. The bit-blaster's definedness gating mirrors this.
939            let l_true = !eval(l, a)?.is_zero();
940            let decided = match op {
941                BinOp::LogAnd | BinOp::LogImpl => !l_true,
942                _ => l_true,
943            };
944            if decided {
945                return Ok(bit(*op != BinOp::LogAnd));
946            }
947            Ok(bit(!eval(r, a)?.is_zero()))
948        }
949        Expr::Binary(op, l, r, t) => {
950            let lv = eval(l, a)?;
951            let rv = eval(r, a)?;
952            let lt = l.ty();
953            let w = t.width;
954            let m = mask(w);
955            Ok(match op {
956                BinOp::Add => (lv + rv) & m,
957                BinOp::Sub => ((BigUint::one() << w) + lv - rv) & m,
958                BinOp::Mul => (lv * rv) & m,
959                BinOp::Div | BinOp::Mod => {
960                    if rv.is_zero() {
961                        return Err(ConstraintEvalError::DivideByZero);
962                    }
963                    if lt.signed {
964                        let (x, y) = (to_signed(&lv, w), to_signed(&rv, w));
965                        from_signed(&if *op == BinOp::Div { x / y } else { x % y }, w)
966                    } else if *op == BinOp::Div {
967                        lv / rv
968                    } else {
969                        lv % rv
970                    }
971                }
972                BinOp::And => lv & rv,
973                BinOp::Or => lv | rv,
974                BinOp::Xor => lv ^ rv,
975                BinOp::Xnor => (lv ^ rv) ^ m,
976                BinOp::Shl | BinOp::Shr | BinOp::Ashr => {
977                    // The amount is unsigned; at or past the width it
978                    // shifts everything out (or fills with the sign).
979                    let amt = if rv < BigUint::from(w) {
980                        u32::try_from(&rv).expect("below w")
981                    } else {
982                        w
983                    };
984                    match op {
985                        BinOp::Shl => (lv << amt) & m,
986                        BinOp::Shr => lv >> amt,
987                        _ => {
988                            if lt.signed && lv.bit(u64::from(w) - 1) {
989                                let x = to_signed(&lv, w) >> amt;
990                                from_signed(&x, w)
991                            } else {
992                                lv >> amt
993                            }
994                        }
995                    }
996                }
997                BinOp::Eq => bit(lv == rv),
998                BinOp::Ne => bit(lv != rv),
999                BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
1000                    let ord = if lt.signed {
1001                        to_signed(&lv, lt.width).cmp(&to_signed(&rv, lt.width))
1002                    } else {
1003                        lv.cmp(&rv)
1004                    };
1005                    bit(match op {
1006                        BinOp::Lt => ord.is_lt(),
1007                        BinOp::Le => ord.is_le(),
1008                        BinOp::Gt => ord.is_gt(),
1009                        _ => ord.is_ge(),
1010                    })
1011                }
1012                BinOp::LogAnd => bit(!lv.is_zero() && !rv.is_zero()),
1013                BinOp::LogOr => bit(!lv.is_zero() || !rv.is_zero()),
1014                BinOp::LogImpl => bit(lv.is_zero() || !rv.is_zero()),
1015                BinOp::LogEquiv => bit(lv.is_zero() == rv.is_zero()),
1016            })
1017        }
1018        Expr::Ite(c, x, y, _) | Expr::Guard(c, x, y, _) => {
1019            if eval(c, a)?.is_zero() {
1020                eval(y, a)
1021            } else {
1022                eval(x, a)
1023            }
1024        }
1025        Expr::Resize(x, t) => {
1026            let v = eval(x, a)?;
1027            let from = x.ty();
1028            Ok(if t.width <= from.width {
1029                v & mask(t.width)
1030            } else if from.signed {
1031                from_signed(&to_signed(&v, from.width), t.width)
1032            } else {
1033                v
1034            })
1035        }
1036        Expr::Concat(parts, _) => {
1037            let mut acc = BigUint::zero();
1038            for p in parts {
1039                let v = eval(p, a)?;
1040                acc = (acc << p.ty().width) | v;
1041            }
1042            Ok(acc)
1043        }
1044        Expr::Slice(x, lo, t) => Ok((eval(x, a)? >> *lo) & mask(t.width)),
1045        Expr::AllDistinct(members) => {
1046            // Every member is read (an unreadable one is an error whatever
1047            // the others hold), then compared pairwise.
1048            let vals = members
1049                .iter()
1050                .map(|m| eval(m, a))
1051                .collect::<Result<Vec<_>, _>>()?;
1052            Ok(bit(vals
1053                .iter()
1054                .enumerate()
1055                .all(|(i, v)| vals[i + 1..].iter().all(|w| w != v))))
1056        }
1057        Expr::Array(_) => Err(ConstraintEvalError::Unexpanded),
1058    }
1059}
1060
1061/// Number of one bits.
1062pub fn popcount(v: &BigUint) -> u64 {
1063    v.iter_u64_digits().map(|d| u64::from(d.count_ones())).sum()
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068    use super::*;
1069
1070    fn u(w: u32) -> Ty {
1071        Ty::unsigned(w)
1072    }
1073    fn s(w: u32) -> Ty {
1074        Ty::signed(w)
1075    }
1076    fn c(t: Ty, v: i64) -> Expr {
1077        // Two's-complement truncation of a possibly negative literal.
1078        Expr::Const(t, from_signed(&BigInt::from(v), t.width))
1079    }
1080    fn var(id: u32, t: Ty) -> Expr {
1081        Expr::Var(id, t)
1082    }
1083    fn bin(op: BinOp, l: Expr, r: Expr) -> Expr {
1084        let t = if op.is_compare() || op.is_logical() {
1085            Ty::BIT
1086        } else {
1087            l.ty()
1088        };
1089        Expr::Binary(op, Box::new(l), Box::new(r), t)
1090    }
1091    fn asg(vars: &[u64]) -> Assignment {
1092        Assignment {
1093            vars: vars.iter().map(|v| BigUint::from(*v)).collect(),
1094            states: vec![],
1095        }
1096    }
1097    fn ev(e: &Expr, vars: &[u64]) -> u64 {
1098        e.validate().expect("well typed");
1099        u64::try_from(eval(e, &asg(vars)).expect("evaluates")).expect("fits u64")
1100    }
1101
1102    /// Array-level nodes (Task C14) are typed, validate through their
1103    /// sub-expressions, and are never evaluated directly: `eval` reports
1104    /// [`ConstraintEvalError::Unexpanded`] until `array::expand` has replaced
1105    /// them.
1106    #[test]
1107    fn array_nodes_validate_and_refuse_direct_evaluation() {
1108        let size = Expr::Array(ArrayNode::Size(0));
1109        assert_eq!(size.ty(), s(32));
1110        let elem = Expr::Array(ArrayNode::Elem {
1111            array: 0,
1112            index: Box::new(c(s(32), 1)),
1113            ty: u(8),
1114        });
1115        assert_eq!(elem.ty(), u(8));
1116        let each = Expr::Array(ArrayNode::Foreach {
1117            array: 0,
1118            var: 0,
1119            body: Box::new(bin(BinOp::Gt, elem.clone(), c(u(8), 0))),
1120        });
1121        assert_eq!(each.ty(), Ty::BIT);
1122        let sum = Expr::Array(ArrayNode::Reduce {
1123            op: ReduceOp::Sum,
1124            array: 0,
1125            var: 0,
1126            index_var: 1,
1127            body: Box::new(Expr::Array(ArrayNode::Loop(0, u(8)))),
1128        });
1129        assert_eq!(sum.ty(), u(8), "a reduction folds at the body's type");
1130        for e in [&size, &elem, &each, &sum] {
1131            e.validate().expect("well typed");
1132            assert_eq!(
1133                eval(e, &asg(&[])),
1134                Err(ConstraintEvalError::Unexpanded),
1135                "{e:?}"
1136            );
1137        }
1138        // A bad sub-expression is found through the node.
1139        let bad = Expr::Array(ArrayNode::Elem {
1140            array: 0,
1141            index: Box::new(Expr::Const(u(0), BigUint::default())),
1142            ty: u(8),
1143        });
1144        assert!(bad.validate().is_err());
1145        let zero = Expr::Array(ArrayNode::Elem {
1146            array: 0,
1147            index: Box::new(c(s(32), 0)),
1148            ty: u(0),
1149        });
1150        assert!(zero.validate().is_err());
1151    }
1152
1153    #[test]
1154    fn wraparound_arithmetic_is_modular() {
1155        let a = var(0, u(8));
1156        let b = var(1, u(8));
1157        assert_eq!(ev(&bin(BinOp::Add, a.clone(), b.clone()), &[200, 100]), 44);
1158        assert_eq!(ev(&bin(BinOp::Sub, a.clone(), b.clone()), &[3, 5]), 254);
1159        assert_eq!(ev(&bin(BinOp::Mul, a, b), &[16, 17]), 16);
1160    }
1161
1162    #[test]
1163    fn neg_and_not() {
1164        let a = var(0, u(4));
1165        let neg = Expr::Unary(UnOp::Neg, Box::new(a.clone()), u(4));
1166        assert_eq!(ev(&neg, &[1]), 15);
1167        assert_eq!(ev(&neg, &[0]), 0);
1168        let not = Expr::Unary(UnOp::BitNot, Box::new(a), u(4));
1169        assert_eq!(ev(&not, &[0b1010]), 0b0101);
1170    }
1171
1172    #[test]
1173    fn signed_compare_differs_from_unsigned() {
1174        // 8'hFF is -1 signed, 255 unsigned.
1175        let lt_s = bin(BinOp::Lt, var(0, s(8)), c(s(8), 0));
1176        let lt_u = bin(BinOp::Lt, var(0, u(8)), c(u(8), 0));
1177        assert_eq!(ev(&lt_s, &[0xFF]), 1);
1178        assert_eq!(ev(&lt_u, &[0xFF]), 0);
1179        let ge_s = bin(BinOp::Ge, var(0, s(8)), c(s(8), -2));
1180        assert_eq!(ev(&ge_s, &[0xFF]), 1);
1181        assert_eq!(ev(&ge_s, &[0xFD]), 0);
1182    }
1183
1184    #[test]
1185    fn signed_division_truncates_toward_zero() {
1186        let q = bin(BinOp::Div, var(0, s(8)), var(1, s(8)));
1187        let r = bin(BinOp::Mod, var(0, s(8)), var(1, s(8)));
1188        // -7 / 2 = -3, -7 % 2 = -1.
1189        assert_eq!(ev(&q, &[0xF9, 2]), 0xFD);
1190        assert_eq!(ev(&r, &[0xF9, 2]), 0xFF);
1191        // Unsigned: 249 / 2 = 124.
1192        let uq = bin(BinOp::Div, var(0, u(8)), var(1, u(8)));
1193        assert_eq!(ev(&uq, &[0xF9, 2]), 124);
1194    }
1195
1196    #[test]
1197    fn divide_by_zero_is_a_typed_error() {
1198        let q = bin(BinOp::Div, var(0, u(8)), var(1, u(8)));
1199        assert_eq!(
1200            eval(&q, &asg(&[1, 0])),
1201            Err(ConstraintEvalError::DivideByZero)
1202        );
1203    }
1204
1205    #[test]
1206    fn shifts() {
1207        let a = var(0, u(8));
1208        let sh = |op, n: u64| bin(op, a.clone(), c(u(4), n as i64));
1209        assert_eq!(ev(&sh(BinOp::Shl, 3), &[0b1011_0001]), 0b1000_1000);
1210        assert_eq!(ev(&sh(BinOp::Shr, 3), &[0b1011_0001]), 0b0001_0110);
1211        // Shifting by >= the width clears everything.
1212        assert_eq!(ev(&sh(BinOp::Shl, 9), &[0xFF]), 0);
1213        assert_eq!(ev(&sh(BinOp::Shr, 12), &[0xFF]), 0);
1214        // `>>>` on an unsigned operand is a logical shift (11.4.10) ...
1215        assert_eq!(ev(&sh(BinOp::Ashr, 2), &[0x80]), 0x20);
1216        // ... and on a signed one it fills with the sign.
1217        let sa = bin(BinOp::Ashr, var(0, s(8)), c(u(4), 2));
1218        assert_eq!(ev(&sa, &[0x80]), 0xE0);
1219        let sa_big = bin(BinOp::Ashr, var(0, s(8)), c(u(4), 12));
1220        assert_eq!(ev(&sa_big, &[0x80]), 0xFF);
1221        assert_eq!(ev(&sa_big, &[0x40]), 0x00);
1222    }
1223
1224    #[test]
1225    fn reductions_and_logical_ops() {
1226        let a = var(0, u(4));
1227        let red = |op| Expr::Unary(op, Box::new(a.clone()), Ty::BIT);
1228        assert_eq!(ev(&red(UnOp::RedAnd), &[0b1111]), 1);
1229        assert_eq!(ev(&red(UnOp::RedAnd), &[0b1110]), 0);
1230        assert_eq!(ev(&red(UnOp::RedNand), &[0b1110]), 1);
1231        assert_eq!(ev(&red(UnOp::RedOr), &[0]), 0);
1232        assert_eq!(ev(&red(UnOp::RedNor), &[0]), 1);
1233        assert_eq!(ev(&red(UnOp::RedXor), &[0b0111]), 1);
1234        assert_eq!(ev(&red(UnOp::RedXnor), &[0b0111]), 0);
1235        assert_eq!(ev(&red(UnOp::LogNot), &[0]), 1);
1236        // 8 -> 1, 0 -> 0 as truthiness; implication and equivalence.
1237        let imp = bin(BinOp::LogImpl, var(0, u(4)), var(1, u(4)));
1238        assert_eq!(ev(&imp, &[8, 0]), 0);
1239        assert_eq!(ev(&imp, &[0, 0]), 1);
1240        assert_eq!(ev(&imp, &[8, 3]), 1);
1241        let eqv = bin(BinOp::LogEquiv, var(0, u(4)), var(1, u(4)));
1242        assert_eq!(ev(&eqv, &[8, 1]), 1);
1243        assert_eq!(ev(&eqv, &[8, 0]), 0);
1244    }
1245
1246    #[test]
1247    fn resize_extends_by_the_operand_signedness() {
1248        // 4'b1000 sign-extends to 8'hF8 when signed, 8'h08 when unsigned.
1249        let zs = Expr::Resize(Box::new(var(0, s(4))), s(8));
1250        let zu = Expr::Resize(Box::new(var(0, u(4))), s(8));
1251        assert_eq!(ev(&zs, &[0b1000]), 0xF8);
1252        assert_eq!(ev(&zu, &[0b1000]), 0x08);
1253        let tr = Expr::Resize(Box::new(var(0, u(8))), u(4));
1254        assert_eq!(ev(&tr, &[0xAB]), 0xB);
1255    }
1256
1257    #[test]
1258    fn concat_and_slice() {
1259        let cat = Expr::Concat(vec![var(0, u(4)), var(1, u(4))], u(8));
1260        assert_eq!(ev(&cat, &[0xA, 0x5]), 0xA5);
1261        let sl = Expr::Slice(Box::new(var(0, u(8))), 2, u(3));
1262        assert_eq!(ev(&sl, &[0b1011_0100]), 0b101);
1263    }
1264
1265    #[test]
1266    fn all_distinct_is_every_pair_not_just_neighbours() {
1267        let e = Expr::AllDistinct(vec![var(0, u(2)), var(1, u(2)), var(2, u(2))]);
1268        assert_eq!(e.ty(), Ty::BIT);
1269        for a in 0..4u64 {
1270            for b in 0..4u64 {
1271                for d in 0..4u64 {
1272                    let want = u64::from(a != b && b != d && a != d);
1273                    assert_eq!(ev(&e, &[a, b, d]), want, "{a} {b} {d}");
1274                }
1275            }
1276        }
1277        // The same variable twice is two equal members.
1278        let twice = Expr::AllDistinct(vec![var(0, u(2)), var(0, u(2))]);
1279        assert_eq!(ev(&twice, &[1]), 0);
1280        // A group with one member (the builder never makes one) is trivially distinct.
1281        assert_eq!(ev(&Expr::AllDistinct(vec![var(0, u(2))]), &[1]), 1);
1282    }
1283
1284    #[test]
1285    fn all_distinct_reads_every_member_and_state() {
1286        let e = Expr::AllDistinct(vec![var(0, u(4)), Expr::State(0, u(4)), var(1, u(4))]);
1287        let st = |v: Option<u8>| Assignment {
1288            vars: vec![BigUint::from(1u8), BigUint::from(2u8)],
1289            states: vec![v.map(BigUint::from)],
1290        };
1291        assert_eq!(eval(&e, &st(Some(3))), Ok(BigUint::one()));
1292        assert_eq!(eval(&e, &st(Some(2))), Ok(BigUint::zero()));
1293        // An unreadable member is an error even when two others already
1294        // collide (the group is not short-circuited past it).
1295        let dup = Expr::AllDistinct(vec![var(0, u(4)), var(0, u(4)), Expr::State(0, u(4))]);
1296        assert_eq!(
1297            eval(&dup, &st(None)),
1298            Err(ConstraintEvalError::StateUnknown(0))
1299        );
1300        let mut vars = vec![];
1301        e.for_each_var(&mut |v, _| vars.push(v));
1302        assert_eq!(vars, [0, 1]);
1303        let mut states = vec![];
1304        e.for_each_state(&mut |s| states.push(s));
1305        assert_eq!(states, [0]);
1306    }
1307
1308    #[test]
1309    fn all_distinct_substitution_keeps_member_order() {
1310        let e = Expr::AllDistinct(vec![var(0, u(4)), var(1, u(4)), var(2, u(4))]);
1311        let sub = e.substitute_vars(&mut |v, t| match v {
1312            1 => Expr::constant(t, 9u8),
1313            other => Expr::Var(other + 10, t),
1314        });
1315        assert_eq!(
1316            sub,
1317            Expr::AllDistinct(vec![var(10, u(4)), c(u(4), 9), var(12, u(4))])
1318        );
1319    }
1320
1321    #[test]
1322    fn all_distinct_validation_demands_one_member_type() {
1323        assert!(
1324            Expr::AllDistinct(vec![var(0, u(4)), var(1, u(4))])
1325                .validate()
1326                .is_ok()
1327        );
1328        // The empty group has no type to agree on: a builder bug.
1329        assert!(Expr::AllDistinct(vec![]).validate().is_err());
1330        // Width and signedness must agree (6.22.1 equivalence).
1331        assert!(
1332            Expr::AllDistinct(vec![var(0, u(4)), var(1, u(8))])
1333                .validate()
1334                .is_err()
1335        );
1336        assert!(
1337            Expr::AllDistinct(vec![var(0, u(4)), var(1, s(4))])
1338                .validate()
1339                .is_err()
1340        );
1341        // A member's own tree is validated too.
1342        let bad_member = Expr::Const(u(4), BigUint::from(16u8));
1343        assert!(
1344            Expr::AllDistinct(vec![var(0, u(4)), bad_member])
1345                .validate()
1346                .is_err()
1347        );
1348    }
1349
1350    #[test]
1351    fn ite_reads_truthiness() {
1352        let e = Expr::Ite(
1353            Box::new(var(0, u(4))),
1354            Box::new(c(u(8), 7)),
1355            Box::new(c(u(8), 9)),
1356            u(8),
1357        );
1358        assert_eq!(ev(&e, &[0]), 9);
1359        assert_eq!(ev(&e, &[2]), 7);
1360    }
1361
1362    #[test]
1363    fn state_with_unknown_bits_is_an_error_never_zero() {
1364        let e = bin(BinOp::Add, Expr::State(0, u(8)), c(u(8), 1));
1365        let ok = Assignment {
1366            vars: vec![],
1367            states: vec![Some(BigUint::from(4u8))],
1368        };
1369        assert_eq!(eval(&e, &ok), Ok(BigUint::from(5u8)));
1370        let xz = Assignment {
1371            vars: vec![],
1372            states: vec![None],
1373        };
1374        assert_eq!(eval(&e, &xz), Err(ConstraintEvalError::StateUnknown(0)));
1375        let none = Assignment::default();
1376        assert_eq!(eval(&e, &none), Err(ConstraintEvalError::UnboundState(0)));
1377    }
1378
1379    #[test]
1380    fn wide_values_beyond_64_bits() {
1381        let w = 100;
1382        let a = Expr::Var(0, u(w));
1383        let e = bin(BinOp::Add, a, Expr::constant(u(w), 1u8));
1384        let all_ones = Assignment {
1385            vars: vec![mask(w)],
1386            states: vec![],
1387        };
1388        // 2^100 - 1 + 1 wraps to 0.
1389        assert_eq!(eval(&e, &all_ones), Ok(BigUint::zero()));
1390        let half = Assignment {
1391            vars: vec![BigUint::one() << 99],
1392            states: vec![],
1393        };
1394        assert_eq!(eval(&e, &half), Ok((BigUint::one() << 99) + BigUint::one()));
1395    }
1396
1397    #[test]
1398    fn validation_rejects_ill_typed_trees() {
1399        // Operand widths differ.
1400        let bad = Expr::Binary(
1401            BinOp::Add,
1402            Box::new(var(0, u(8))),
1403            Box::new(var(1, u(4))),
1404            u(8),
1405        );
1406        assert!(matches!(bad.validate(), Err(IrError::IllTyped { .. })));
1407        // A comparison must produce one bit.
1408        let bad = Expr::Binary(
1409            BinOp::Lt,
1410            Box::new(var(0, u(8))),
1411            Box::new(var(1, u(8))),
1412            u(8),
1413        );
1414        assert!(bad.validate().is_err());
1415        // Slice out of range.
1416        let bad = Expr::Slice(Box::new(var(0, u(8))), 6, u(4));
1417        assert!(bad.validate().is_err());
1418        // Constant too wide for its type.
1419        let bad = Expr::Const(u(4), BigUint::from(16u8));
1420        assert!(bad.validate().is_err());
1421        // Concat width mismatch.
1422        let bad = Expr::Concat(vec![var(0, u(4)), var(1, u(4))], u(9));
1423        assert!(bad.validate().is_err());
1424        // Mixed signedness in a comparison is a builder bug, not a coercion.
1425        let bad = Expr::Binary(
1426            BinOp::Lt,
1427            Box::new(var(0, s(8))),
1428            Box::new(var(1, u(8))),
1429            Ty::BIT,
1430        );
1431        assert!(bad.validate().is_err());
1432    }
1433
1434    #[test]
1435    fn for_each_var_visits_every_read() {
1436        let e = bin(
1437            BinOp::Add,
1438            Expr::Concat(vec![var(3, u(4)), var(1, u(4))], u(8)),
1439            Expr::Ite(
1440                Box::new(var(2, u(1))),
1441                Box::new(Expr::Const(u(8), BigUint::one())),
1442                Box::new(Expr::State(0, u(8))),
1443                u(8),
1444            ),
1445        );
1446        let mut seen = vec![];
1447        e.for_each_var(&mut |v, _| seen.push(v));
1448        seen.sort_unstable();
1449        assert_eq!(seen, [1, 2, 3]);
1450    }
1451
1452    #[test]
1453    fn substitute_vars_replaces_and_preserves_evaluation() {
1454        // x0 + concat(x1, x2)[7:0] with x1 replaced by the constant 5 and the
1455        // others renumbered (x0 -> 1, x2 -> 0): the substituted tree must
1456        // evaluate exactly like the original under the matching assignment.
1457        let e = bin(
1458            BinOp::Add,
1459            var(0, u(8)),
1460            Expr::Concat(vec![var(1, u(4)), var(2, u(4))], u(8)),
1461        );
1462        let sub = e.substitute_vars(&mut |v, t| match v {
1463            0 => Expr::Var(1, t),
1464            1 => Expr::constant(t, 5u8),
1465            _ => Expr::Var(0, t),
1466        });
1467        sub.validate().expect("still well-typed");
1468        let mut seen = vec![];
1469        sub.for_each_var(&mut |v, _| seen.push(v));
1470        seen.sort_unstable();
1471        assert_eq!(seen, [0, 1], "x1 is gone, the others renumbered");
1472        let full = Assignment {
1473            vars: vec![BigUint::from(10u8), BigUint::from(5u8), BigUint::from(3u8)],
1474            states: vec![],
1475        };
1476        let part = Assignment {
1477            vars: vec![BigUint::from(3u8), BigUint::from(10u8)],
1478            states: vec![],
1479        };
1480        assert_eq!(eval(&e, &full), eval(&sub, &part));
1481        assert_eq!(eval(&sub, &part), Ok(BigUint::from(10u8 + 0x53)));
1482    }
1483}