Skip to main content

sv_sim_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}
324
325/// Index of a function in a program's function table.
326pub type FuncId = u32;
327
328/// A malformed IR tree (a builder bug or an out-of-contract construction).
329#[derive(Clone, Debug, PartialEq, Eq)]
330pub enum IrError {
331    /// A node violates its operand/result typing rule.
332    IllTyped {
333        /// Which node and rule.
334        what: String,
335    },
336}
337
338impl fmt::Display for IrError {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        match self {
341            IrError::IllTyped { what } => write!(f, "ill-typed constraint IR: {what}"),
342        }
343    }
344}
345
346impl std::error::Error for IrError {}
347
348/// A failure while evaluating an [`Expr`] directly.
349#[derive(Clone, Debug, PartialEq, Eq)]
350pub enum ConstraintEvalError {
351    /// The state variable had an X/Z bit (LRM 18.3: 4-state values in a
352    /// constraint are an error; decision E9).
353    StateUnknown(StateId),
354    /// `/` or `%` with a zero divisor.
355    DivideByZero,
356    /// The assignment has no value for this random variable.
357    UnboundVar(VarId),
358    /// The assignment has no entry for this state variable.
359    UnboundState(StateId),
360    /// The tree still holds an array-level node ([`Expr::Array`]); the
361    /// run-time expansion must replace it before evaluation.
362    Unexpanded,
363}
364
365impl fmt::Display for ConstraintEvalError {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        match self {
368            ConstraintEvalError::StateUnknown(s) => write!(
369                f,
370                "state variable #{s} has an X/Z bit; a 4-state value in a constraint is an \
371                 error (IEEE 1800-2023 18.3)"
372            ),
373            ConstraintEvalError::DivideByZero => f.write_str("division by zero in a constraint"),
374            ConstraintEvalError::UnboundVar(v) => write!(f, "no value for random variable #{v}"),
375            ConstraintEvalError::UnboundState(s) => {
376                write!(f, "no value for state variable #{s}")
377            }
378            ConstraintEvalError::Unexpanded => {
379                f.write_str("an array-level constraint node was not expanded before evaluation")
380            }
381        }
382    }
383}
384
385impl std::error::Error for ConstraintEvalError {}
386
387/// Concrete values for [`eval`]. A `None` state entry means the state
388/// variable currently holds an X/Z bit.
389#[derive(Clone, Debug, Default, PartialEq, Eq)]
390pub struct Assignment {
391    /// Random-variable values, indexed by [`VarId`] (each below `2^width`).
392    pub vars: Vec<BigUint>,
393    /// State-variable values, indexed by [`StateId`]; `None` = has X/Z.
394    pub states: Vec<Option<BigUint>>,
395}
396
397/// `2^w - 1`.
398pub fn mask(w: u32) -> BigUint {
399    (BigUint::one() << w) - BigUint::one()
400}
401
402fn to_signed(v: &BigUint, w: u32) -> BigInt {
403    if v.bit(u64::from(w) - 1) {
404        BigInt::from(v.clone()) - (BigInt::one() << w)
405    } else {
406        BigInt::from(v.clone())
407    }
408}
409
410fn from_signed(i: &BigInt, w: u32) -> BigUint {
411    let m = BigInt::one() << w;
412    let r = ((i % &m) + &m) % &m;
413    r.to_biguint().expect("reduced into [0, 2^w)")
414}
415
416fn bit(b: bool) -> BigUint {
417    if b { BigUint::one() } else { BigUint::zero() }
418}
419
420impl Expr {
421    /// A constant of type `ty` holding `val` truncated to the width.
422    pub fn constant(ty: Ty, val: impl Into<BigUint>) -> Expr {
423        Expr::Const(ty, val.into() & mask(ty.width))
424    }
425
426    /// The result type of this node.
427    pub fn ty(&self) -> Ty {
428        match self {
429            Expr::Const(t, _)
430            | Expr::Var(_, t)
431            | Expr::State(_, t)
432            | Expr::Unary(_, _, t)
433            | Expr::Binary(_, _, _, t)
434            | Expr::Ite(_, _, _, t)
435            | Expr::Guard(_, _, _, t)
436            | Expr::Resize(_, t)
437            | Expr::Concat(_, t)
438            | Expr::Slice(_, _, t) => *t,
439            Expr::AllDistinct(_) => Ty::BIT,
440            Expr::Array(n) => n.ty(),
441        }
442    }
443
444    /// Checks every node against its typing rule (module docs).
445    pub fn validate(&self) -> Result<(), IrError> {
446        let bad = |what: String| Err(IrError::IllTyped { what });
447        match self {
448            Expr::Const(t, v) => {
449                if t.width == 0 {
450                    return bad("zero-width constant".into());
451                }
452                if v.bits() > u64::from(t.width) {
453                    return bad(format!("constant {v} does not fit {t}"));
454                }
455                Ok(())
456            }
457            Expr::Var(_, t) | Expr::State(_, t) => {
458                if t.width == 0 {
459                    return bad("zero-width variable".into());
460                }
461                Ok(())
462            }
463            Expr::Unary(op, a, t) => {
464                a.validate()?;
465                let at = a.ty();
466                let want = match op {
467                    UnOp::BitNot | UnOp::Neg => at,
468                    _ => Ty::BIT,
469                };
470                if *t != want {
471                    return bad(format!("{op:?} on {at} must produce {want}, has {t}"));
472                }
473                Ok(())
474            }
475            Expr::Binary(op, l, r, t) => {
476                l.validate()?;
477                r.validate()?;
478                let (lt, rt) = (l.ty(), r.ty());
479                if op.is_logical() {
480                    if *t != Ty::BIT {
481                        return bad(format!("{op:?} must produce u[1], has {t}"));
482                    }
483                } else if op.is_compare() {
484                    if lt != rt {
485                        return bad(format!("{op:?} operands differ: {lt} vs {rt}"));
486                    }
487                    if *t != Ty::BIT {
488                        return bad(format!("{op:?} must produce u[1], has {t}"));
489                    }
490                } else if op.is_shift() {
491                    if *t != lt {
492                        return bad(format!("{op:?} result {t} must equal left operand {lt}"));
493                    }
494                } else if lt != rt || *t != lt {
495                    return bad(format!("{op:?} operands/result differ: {lt}, {rt}, {t}"));
496                }
497                Ok(())
498            }
499            Expr::Ite(c, a, b, t) | Expr::Guard(c, a, b, t) => {
500                c.validate()?;
501                a.validate()?;
502                b.validate()?;
503                if a.ty() != *t || b.ty() != *t {
504                    return bad(format!(
505                        "ternary arms {} / {} must both equal result {t}",
506                        a.ty(),
507                        b.ty()
508                    ));
509                }
510                Ok(())
511            }
512            Expr::Resize(a, t) => {
513                a.validate()?;
514                if t.width == 0 {
515                    return bad("resize to zero width".into());
516                }
517                Ok(())
518            }
519            Expr::Concat(parts, t) => {
520                if parts.is_empty() {
521                    return bad("empty concatenation".into());
522                }
523                let mut w = 0u32;
524                for p in parts {
525                    p.validate()?;
526                    w += p.ty().width;
527                }
528                if *t != Ty::unsigned(w) {
529                    return bad(format!("concatenation of {w} bits has type {t}"));
530                }
531                Ok(())
532            }
533            Expr::Slice(a, lo, t) => {
534                a.validate()?;
535                if t.signed || t.width == 0 || lo + t.width > a.ty().width {
536                    return bad(format!(
537                        "slice [{}+:{}] of {} (result {t})",
538                        lo,
539                        t.width,
540                        a.ty()
541                    ));
542                }
543                Ok(())
544            }
545            Expr::AllDistinct(members) => {
546                let Some(first) = members.first() else {
547                    return bad("empty uniqueness group".into());
548                };
549                let t = first.ty();
550                for m in members {
551                    m.validate()?;
552                    if m.ty() != t {
553                        return bad(format!(
554                            "uniqueness members differ in type: {t} vs {}",
555                            m.ty()
556                        ));
557                    }
558                }
559                Ok(())
560            }
561            Expr::Array(n) => n.validate(),
562        }
563    }
564
565    /// Calls `f` on every random variable this expression reads.
566    pub fn for_each_var(&self, f: &mut impl FnMut(VarId, Ty)) {
567        match self {
568            Expr::Var(v, t) => f(*v, *t),
569            Expr::Const(..) | Expr::State(..) => {}
570            Expr::Unary(_, a, _) | Expr::Resize(a, _) | Expr::Slice(a, _, _) => {
571                a.for_each_var(f);
572            }
573            Expr::Binary(_, a, b, _) => {
574                a.for_each_var(f);
575                b.for_each_var(f);
576            }
577            Expr::Ite(c, a, b, _) | Expr::Guard(c, a, b, _) => {
578                c.for_each_var(f);
579                a.for_each_var(f);
580                b.for_each_var(f);
581            }
582            Expr::Concat(parts, _) | Expr::AllDistinct(parts) => {
583                parts.iter().for_each(|p| p.for_each_var(f));
584            }
585            Expr::Array(n) => n.for_each_child(&mut |c| c.for_each_var(f)),
586        }
587    }
588
589    /// Calls `f` on every state variable this expression reads.
590    pub fn for_each_state(&self, f: &mut impl FnMut(StateId)) {
591        match self {
592            Expr::State(s, _) => f(*s),
593            Expr::Const(..) | Expr::Var(..) => {}
594            Expr::Unary(_, a, _) | Expr::Resize(a, _) | Expr::Slice(a, _, _) => {
595                a.for_each_state(f);
596            }
597            Expr::Binary(_, a, b, _) => {
598                a.for_each_state(f);
599                b.for_each_state(f);
600            }
601            Expr::Ite(c, a, b, _) | Expr::Guard(c, a, b, _) => {
602                c.for_each_state(f);
603                a.for_each_state(f);
604                b.for_each_state(f);
605            }
606            Expr::Concat(parts, _) | Expr::AllDistinct(parts) => {
607                parts.iter().for_each(|p| p.for_each_state(f));
608            }
609            Expr::Array(n) => n.for_each_child(&mut |c| c.for_each_state(f)),
610        }
611    }
612
613    /// A copy of this expression with every random-variable read replaced by
614    /// `f(id, ty)`. The replacement must have type `ty` (the caller's
615    /// contract: a renumbered [`Expr::Var`] or a same-typed constant); the
616    /// debug assertion pins that so a mis-typed substitution fails loudly
617    /// instead of producing an ill-typed tree.
618    ///
619    /// This is how `rand_mode(0)` variables are turned into state before a
620    /// solve (LRM 18.8: an inactive random variable "is treated the same as
621    /// any other state variable"), and how the surviving active variables are
622    /// renumbered densely.
623    pub fn substitute_vars(&self, f: &mut impl FnMut(VarId, Ty) -> Expr) -> Expr {
624        match self {
625            Expr::Var(v, t) => {
626                let r = f(*v, *t);
627                debug_assert_eq!(r.ty(), *t, "substitute_vars: replacement type differs");
628                r
629            }
630            Expr::Const(..) | Expr::State(..) => self.clone(),
631            Expr::Unary(op, a, t) => Expr::Unary(*op, Box::new(a.substitute_vars(f)), *t),
632            Expr::Resize(a, t) => Expr::Resize(Box::new(a.substitute_vars(f)), *t),
633            Expr::Slice(a, lo, t) => Expr::Slice(Box::new(a.substitute_vars(f)), *lo, *t),
634            Expr::Binary(op, a, b, t) => {
635                let a = Box::new(a.substitute_vars(f));
636                let b = Box::new(b.substitute_vars(f));
637                Expr::Binary(*op, a, b, *t)
638            }
639            Expr::Ite(c, a, b, t) => {
640                let c = Box::new(c.substitute_vars(f));
641                let a = Box::new(a.substitute_vars(f));
642                let b = Box::new(b.substitute_vars(f));
643                Expr::Ite(c, a, b, *t)
644            }
645            Expr::Guard(c, a, b, t) => {
646                let c = Box::new(c.substitute_vars(f));
647                let a = Box::new(a.substitute_vars(f));
648                let b = Box::new(b.substitute_vars(f));
649                Expr::Guard(c, a, b, *t)
650            }
651            Expr::Concat(parts, t) => {
652                Expr::Concat(parts.iter().map(|p| p.substitute_vars(f)).collect(), *t)
653            }
654            Expr::AllDistinct(parts) => {
655                Expr::AllDistinct(parts.iter().map(|p| p.substitute_vars(f)).collect())
656            }
657            Expr::Array(n) => Expr::Array(n.map_children(&mut |c| c.substitute_vars(f))),
658        }
659    }
660}
661
662impl Expr {
663    /// A copy of this expression with every direct sub-expression replaced by
664    /// `f(child)` (the first error wins). Leaves are copied unchanged.
665    pub fn try_map_children<E>(
666        &self,
667        f: &mut impl FnMut(&Expr) -> Result<Expr, E>,
668    ) -> Result<Expr, E> {
669        Ok(match self {
670            Expr::Const(..) | Expr::Var(..) | Expr::State(..) => self.clone(),
671            Expr::Unary(op, a, t) => Expr::Unary(*op, Box::new(f(a)?), *t),
672            Expr::Binary(op, a, b, t) => Expr::Binary(*op, Box::new(f(a)?), Box::new(f(b)?), *t),
673            Expr::Ite(c, a, b, t) => {
674                Expr::Ite(Box::new(f(c)?), Box::new(f(a)?), Box::new(f(b)?), *t)
675            }
676            Expr::Guard(c, a, b, t) => {
677                Expr::Guard(Box::new(f(c)?), Box::new(f(a)?), Box::new(f(b)?), *t)
678            }
679            Expr::Resize(a, t) => Expr::Resize(Box::new(f(a)?), *t),
680            Expr::Concat(parts, t) => {
681                Expr::Concat(parts.iter().map(&mut *f).collect::<Result<_, _>>()?, *t)
682            }
683            Expr::Slice(a, lo, t) => Expr::Slice(Box::new(f(a)?), *lo, *t),
684            Expr::AllDistinct(parts) => {
685                Expr::AllDistinct(parts.iter().map(&mut *f).collect::<Result<_, _>>()?)
686            }
687            Expr::Array(n) => {
688                let mut err = None;
689                let r = n.map_children(&mut |c| match f(c) {
690                    Ok(x) => x,
691                    Err(e) => {
692                        err.get_or_insert(e);
693                        c.clone()
694                    }
695                });
696                if let Some(e) = err {
697                    return Err(e);
698                }
699                Expr::Array(r)
700            }
701        })
702    }
703
704    /// Whether any node of this expression satisfies `pred`.
705    pub fn any_node(&self, pred: &mut impl FnMut(&Expr) -> bool) -> bool {
706        if pred(self) {
707            return true;
708        }
709        let mut found = false;
710        let mut visit = |c: &Expr| {
711            if !found && c.any_node(pred) {
712                found = true;
713            }
714        };
715        match self {
716            Expr::Const(..) | Expr::Var(..) | Expr::State(..) => {}
717            Expr::Unary(_, a, _) | Expr::Resize(a, _) | Expr::Slice(a, _, _) => visit(a),
718            Expr::Binary(_, a, b, _) => {
719                visit(a);
720                visit(b);
721            }
722            Expr::Ite(c, a, b, _) | Expr::Guard(c, a, b, _) => {
723                visit(c);
724                visit(a);
725                visit(b);
726            }
727            Expr::Concat(parts, _) | Expr::AllDistinct(parts) => parts.iter().for_each(visit),
728            Expr::Array(n) => n.for_each_child(&mut visit),
729        }
730        found
731    }
732}
733
734impl ArrayNode {
735    /// The result type of this node.
736    pub fn ty(&self) -> Ty {
737        match self {
738            ArrayNode::Size(_) => Ty::signed(32),
739            ArrayNode::Elem { ty, .. } | ArrayNode::Loop(_, ty) => *ty,
740            ArrayNode::Foreach { .. } | ArrayNode::Distinct { .. } => Ty::BIT,
741            ArrayNode::Reduce { body, .. } => body.ty(),
742            ArrayNode::Call { ty, .. } | ArrayNode::Pick { ty, .. } => *ty,
743        }
744    }
745
746    fn validate(&self) -> Result<(), IrError> {
747        let bad = |what: String| Err(IrError::IllTyped { what });
748        match self {
749            ArrayNode::Size(_) => Ok(()),
750            ArrayNode::Loop(_, t) => {
751                if t.width == 0 {
752                    return bad("zero-width loop variable".into());
753                }
754                Ok(())
755            }
756            ArrayNode::Elem { index, ty, .. } => {
757                index.validate()?;
758                if ty.width == 0 {
759                    return bad("zero-width array element".into());
760                }
761                Ok(())
762            }
763            ArrayNode::Foreach { body, .. } => body.validate(),
764            ArrayNode::Distinct { scalars, arrays } => {
765                if scalars.is_empty() && arrays.is_empty() {
766                    return bad("a distinct group with no members".into());
767                }
768                scalars.iter().try_for_each(Expr::validate)
769            }
770            ArrayNode::Reduce { op, body, .. } => {
771                body.validate()?;
772                if matches!(op, ReduceOp::And | ReduceOp::Or | ReduceOp::Xor)
773                    && body.ty().width == 0
774                {
775                    return bad("zero-width reduction".into());
776                }
777                Ok(())
778            }
779            ArrayNode::Call { args, ty, .. } => {
780                if ty.width == 0 {
781                    return bad("zero-width function result".into());
782                }
783                args.iter().try_for_each(Expr::validate)
784            }
785            ArrayNode::Pick {
786                index, elems, ty, ..
787            } => {
788                index.validate()?;
789                if ty.width == 0 {
790                    return bad("zero-width array element".into());
791                }
792                if elems.is_empty() {
793                    return bad("a pick over no elements".into());
794                }
795                if elems.iter().any(|e| e.ty() != *ty) {
796                    return bad("a pick element of another type".into());
797                }
798                elems.iter().try_for_each(Expr::validate)
799            }
800        }
801    }
802
803    /// Calls `f` on every direct sub-expression.
804    pub fn for_each_child(&self, f: &mut impl FnMut(&Expr)) {
805        match self {
806            ArrayNode::Size(_) | ArrayNode::Loop(..) => {}
807            ArrayNode::Pick { index, elems, .. } => {
808                f(index);
809                elems.iter().for_each(f);
810            }
811            ArrayNode::Call { args, .. } => args.iter().for_each(f),
812            ArrayNode::Distinct { scalars, .. } => scalars.iter().for_each(f),
813            ArrayNode::Elem { index, .. } => f(index),
814            ArrayNode::Foreach { body, .. } | ArrayNode::Reduce { body, .. } => f(body),
815        }
816    }
817
818    /// A copy with every direct sub-expression replaced by `f(child)`.
819    pub fn map_children(&self, f: &mut impl FnMut(&Expr) -> Expr) -> ArrayNode {
820        match self {
821            ArrayNode::Size(_) | ArrayNode::Loop(..) => self.clone(),
822            ArrayNode::Pick {
823                index,
824                low,
825                elems,
826                ty,
827            } => ArrayNode::Pick {
828                index: Box::new(f(index)),
829                low: *low,
830                elems: elems.iter().map(&mut *f).collect(),
831                ty: *ty,
832            },
833            ArrayNode::Call { func, args, ty } => ArrayNode::Call {
834                func: *func,
835                args: args.iter().map(&mut *f).collect(),
836                ty: *ty,
837            },
838            ArrayNode::Distinct { scalars, arrays } => ArrayNode::Distinct {
839                scalars: scalars.iter().map(&mut *f).collect(),
840                arrays: arrays.clone(),
841            },
842            ArrayNode::Elem { array, index, ty } => ArrayNode::Elem {
843                array: *array,
844                index: Box::new(f(index)),
845                ty: *ty,
846            },
847            ArrayNode::Foreach { array, var, body } => ArrayNode::Foreach {
848                array: *array,
849                var: *var,
850                body: Box::new(f(body)),
851            },
852            ArrayNode::Reduce {
853                op,
854                array,
855                var,
856                index_var,
857                body,
858            } => ArrayNode::Reduce {
859                op: *op,
860                array: *array,
861                var: *var,
862                index_var: *index_var,
863                body: Box::new(f(body)),
864            },
865        }
866    }
867}
868
869/// Directly evaluates `e` under `a`; the result is below `2^e.ty().width`.
870pub fn eval(e: &Expr, a: &Assignment) -> Result<BigUint, ConstraintEvalError> {
871    match e {
872        Expr::Const(_, v) => Ok(v.clone()),
873        Expr::Var(id, t) => a
874            .vars
875            .get(*id as usize)
876            .map(|v| v & mask(t.width))
877            .ok_or(ConstraintEvalError::UnboundVar(*id)),
878        Expr::State(id, t) => match a.states.get(*id as usize) {
879            None => Err(ConstraintEvalError::UnboundState(*id)),
880            Some(None) => Err(ConstraintEvalError::StateUnknown(*id)),
881            Some(Some(v)) => Ok(v & mask(t.width)),
882        },
883        Expr::Unary(op, x, t) => {
884            let v = eval(x, a)?;
885            let w = x.ty().width;
886            let r = match op {
887                UnOp::BitNot => mask(w) ^ v,
888                UnOp::Neg => ((BigUint::one() << w) - v) & mask(w),
889                UnOp::LogNot => bit(v.is_zero()),
890                UnOp::RedAnd => bit(v == mask(w)),
891                UnOp::RedNand => bit(v != mask(w)),
892                UnOp::RedOr => bit(!v.is_zero()),
893                UnOp::RedNor => bit(v.is_zero()),
894                UnOp::RedXor => bit(popcount(&v) % 2 == 1),
895                UnOp::RedXnor => bit(popcount(&v).is_multiple_of(2)),
896            };
897            Ok(r & mask(t.width))
898        }
899        Expr::Binary(op @ (BinOp::LogAnd | BinOp::LogOr | BinOp::LogImpl), l, r, _) => {
900            // `&&`, `||` and `->` short-circuit (LRM 11.4.7/11.4.8): the right
901            // operand is only evaluated when the left does not decide the
902            // result, so a division by zero in a decided-away arm is not an
903            // error. The bit-blaster's definedness gating mirrors this.
904            let l_true = !eval(l, a)?.is_zero();
905            let decided = match op {
906                BinOp::LogAnd | BinOp::LogImpl => !l_true,
907                _ => l_true,
908            };
909            if decided {
910                return Ok(bit(*op != BinOp::LogAnd));
911            }
912            Ok(bit(!eval(r, a)?.is_zero()))
913        }
914        Expr::Binary(op, l, r, t) => {
915            let lv = eval(l, a)?;
916            let rv = eval(r, a)?;
917            let lt = l.ty();
918            let w = t.width;
919            let m = mask(w);
920            Ok(match op {
921                BinOp::Add => (lv + rv) & m,
922                BinOp::Sub => ((BigUint::one() << w) + lv - rv) & m,
923                BinOp::Mul => (lv * rv) & m,
924                BinOp::Div | BinOp::Mod => {
925                    if rv.is_zero() {
926                        return Err(ConstraintEvalError::DivideByZero);
927                    }
928                    if lt.signed {
929                        let (x, y) = (to_signed(&lv, w), to_signed(&rv, w));
930                        from_signed(&if *op == BinOp::Div { x / y } else { x % y }, w)
931                    } else if *op == BinOp::Div {
932                        lv / rv
933                    } else {
934                        lv % rv
935                    }
936                }
937                BinOp::And => lv & rv,
938                BinOp::Or => lv | rv,
939                BinOp::Xor => lv ^ rv,
940                BinOp::Xnor => (lv ^ rv) ^ m,
941                BinOp::Shl | BinOp::Shr | BinOp::Ashr => {
942                    // The amount is unsigned; at or past the width it
943                    // shifts everything out (or fills with the sign).
944                    let amt = if rv < BigUint::from(w) {
945                        u32::try_from(&rv).expect("below w")
946                    } else {
947                        w
948                    };
949                    match op {
950                        BinOp::Shl => (lv << amt) & m,
951                        BinOp::Shr => lv >> amt,
952                        _ => {
953                            if lt.signed && lv.bit(u64::from(w) - 1) {
954                                let x = to_signed(&lv, w) >> amt;
955                                from_signed(&x, w)
956                            } else {
957                                lv >> amt
958                            }
959                        }
960                    }
961                }
962                BinOp::Eq => bit(lv == rv),
963                BinOp::Ne => bit(lv != rv),
964                BinOp::Lt | BinOp::Le | BinOp::Gt | BinOp::Ge => {
965                    let ord = if lt.signed {
966                        to_signed(&lv, lt.width).cmp(&to_signed(&rv, lt.width))
967                    } else {
968                        lv.cmp(&rv)
969                    };
970                    bit(match op {
971                        BinOp::Lt => ord.is_lt(),
972                        BinOp::Le => ord.is_le(),
973                        BinOp::Gt => ord.is_gt(),
974                        _ => ord.is_ge(),
975                    })
976                }
977                BinOp::LogAnd => bit(!lv.is_zero() && !rv.is_zero()),
978                BinOp::LogOr => bit(!lv.is_zero() || !rv.is_zero()),
979                BinOp::LogImpl => bit(lv.is_zero() || !rv.is_zero()),
980                BinOp::LogEquiv => bit(lv.is_zero() == rv.is_zero()),
981            })
982        }
983        Expr::Ite(c, x, y, _) | Expr::Guard(c, x, y, _) => {
984            if eval(c, a)?.is_zero() {
985                eval(y, a)
986            } else {
987                eval(x, a)
988            }
989        }
990        Expr::Resize(x, t) => {
991            let v = eval(x, a)?;
992            let from = x.ty();
993            Ok(if t.width <= from.width {
994                v & mask(t.width)
995            } else if from.signed {
996                from_signed(&to_signed(&v, from.width), t.width)
997            } else {
998                v
999            })
1000        }
1001        Expr::Concat(parts, _) => {
1002            let mut acc = BigUint::zero();
1003            for p in parts {
1004                let v = eval(p, a)?;
1005                acc = (acc << p.ty().width) | v;
1006            }
1007            Ok(acc)
1008        }
1009        Expr::Slice(x, lo, t) => Ok((eval(x, a)? >> *lo) & mask(t.width)),
1010        Expr::AllDistinct(members) => {
1011            // Every member is read (an unreadable one is an error whatever
1012            // the others hold), then compared pairwise.
1013            let vals = members
1014                .iter()
1015                .map(|m| eval(m, a))
1016                .collect::<Result<Vec<_>, _>>()?;
1017            Ok(bit(vals
1018                .iter()
1019                .enumerate()
1020                .all(|(i, v)| vals[i + 1..].iter().all(|w| w != v))))
1021        }
1022        Expr::Array(_) => Err(ConstraintEvalError::Unexpanded),
1023    }
1024}
1025
1026/// Number of one bits.
1027pub fn popcount(v: &BigUint) -> u64 {
1028    v.iter_u64_digits().map(|d| u64::from(d.count_ones())).sum()
1029}
1030
1031#[cfg(test)]
1032mod tests {
1033    use super::*;
1034
1035    fn u(w: u32) -> Ty {
1036        Ty::unsigned(w)
1037    }
1038    fn s(w: u32) -> Ty {
1039        Ty::signed(w)
1040    }
1041    fn c(t: Ty, v: i64) -> Expr {
1042        // Two's-complement truncation of a possibly negative literal.
1043        Expr::Const(t, from_signed(&BigInt::from(v), t.width))
1044    }
1045    fn var(id: u32, t: Ty) -> Expr {
1046        Expr::Var(id, t)
1047    }
1048    fn bin(op: BinOp, l: Expr, r: Expr) -> Expr {
1049        let t = if op.is_compare() || op.is_logical() {
1050            Ty::BIT
1051        } else {
1052            l.ty()
1053        };
1054        Expr::Binary(op, Box::new(l), Box::new(r), t)
1055    }
1056    fn asg(vars: &[u64]) -> Assignment {
1057        Assignment {
1058            vars: vars.iter().map(|v| BigUint::from(*v)).collect(),
1059            states: vec![],
1060        }
1061    }
1062    fn ev(e: &Expr, vars: &[u64]) -> u64 {
1063        e.validate().expect("well typed");
1064        u64::try_from(eval(e, &asg(vars)).expect("evaluates")).expect("fits u64")
1065    }
1066
1067    /// Array-level nodes (Task C14) are typed, validate through their
1068    /// sub-expressions, and are never evaluated directly: `eval` reports
1069    /// [`ConstraintEvalError::Unexpanded`] until `array::expand` has replaced
1070    /// them.
1071    #[test]
1072    fn array_nodes_validate_and_refuse_direct_evaluation() {
1073        let size = Expr::Array(ArrayNode::Size(0));
1074        assert_eq!(size.ty(), s(32));
1075        let elem = Expr::Array(ArrayNode::Elem {
1076            array: 0,
1077            index: Box::new(c(s(32), 1)),
1078            ty: u(8),
1079        });
1080        assert_eq!(elem.ty(), u(8));
1081        let each = Expr::Array(ArrayNode::Foreach {
1082            array: 0,
1083            var: 0,
1084            body: Box::new(bin(BinOp::Gt, elem.clone(), c(u(8), 0))),
1085        });
1086        assert_eq!(each.ty(), Ty::BIT);
1087        let sum = Expr::Array(ArrayNode::Reduce {
1088            op: ReduceOp::Sum,
1089            array: 0,
1090            var: 0,
1091            index_var: 1,
1092            body: Box::new(Expr::Array(ArrayNode::Loop(0, u(8)))),
1093        });
1094        assert_eq!(sum.ty(), u(8), "a reduction folds at the body's type");
1095        for e in [&size, &elem, &each, &sum] {
1096            e.validate().expect("well typed");
1097            assert_eq!(
1098                eval(e, &asg(&[])),
1099                Err(ConstraintEvalError::Unexpanded),
1100                "{e:?}"
1101            );
1102        }
1103        // A bad sub-expression is found through the node.
1104        let bad = Expr::Array(ArrayNode::Elem {
1105            array: 0,
1106            index: Box::new(Expr::Const(u(0), BigUint::default())),
1107            ty: u(8),
1108        });
1109        assert!(bad.validate().is_err());
1110        let zero = Expr::Array(ArrayNode::Elem {
1111            array: 0,
1112            index: Box::new(c(s(32), 0)),
1113            ty: u(0),
1114        });
1115        assert!(zero.validate().is_err());
1116    }
1117
1118    #[test]
1119    fn wraparound_arithmetic_is_modular() {
1120        let a = var(0, u(8));
1121        let b = var(1, u(8));
1122        assert_eq!(ev(&bin(BinOp::Add, a.clone(), b.clone()), &[200, 100]), 44);
1123        assert_eq!(ev(&bin(BinOp::Sub, a.clone(), b.clone()), &[3, 5]), 254);
1124        assert_eq!(ev(&bin(BinOp::Mul, a, b), &[16, 17]), 16);
1125    }
1126
1127    #[test]
1128    fn neg_and_not() {
1129        let a = var(0, u(4));
1130        let neg = Expr::Unary(UnOp::Neg, Box::new(a.clone()), u(4));
1131        assert_eq!(ev(&neg, &[1]), 15);
1132        assert_eq!(ev(&neg, &[0]), 0);
1133        let not = Expr::Unary(UnOp::BitNot, Box::new(a), u(4));
1134        assert_eq!(ev(&not, &[0b1010]), 0b0101);
1135    }
1136
1137    #[test]
1138    fn signed_compare_differs_from_unsigned() {
1139        // 8'hFF is -1 signed, 255 unsigned.
1140        let lt_s = bin(BinOp::Lt, var(0, s(8)), c(s(8), 0));
1141        let lt_u = bin(BinOp::Lt, var(0, u(8)), c(u(8), 0));
1142        assert_eq!(ev(&lt_s, &[0xFF]), 1);
1143        assert_eq!(ev(&lt_u, &[0xFF]), 0);
1144        let ge_s = bin(BinOp::Ge, var(0, s(8)), c(s(8), -2));
1145        assert_eq!(ev(&ge_s, &[0xFF]), 1);
1146        assert_eq!(ev(&ge_s, &[0xFD]), 0);
1147    }
1148
1149    #[test]
1150    fn signed_division_truncates_toward_zero() {
1151        let q = bin(BinOp::Div, var(0, s(8)), var(1, s(8)));
1152        let r = bin(BinOp::Mod, var(0, s(8)), var(1, s(8)));
1153        // -7 / 2 = -3, -7 % 2 = -1.
1154        assert_eq!(ev(&q, &[0xF9, 2]), 0xFD);
1155        assert_eq!(ev(&r, &[0xF9, 2]), 0xFF);
1156        // Unsigned: 249 / 2 = 124.
1157        let uq = bin(BinOp::Div, var(0, u(8)), var(1, u(8)));
1158        assert_eq!(ev(&uq, &[0xF9, 2]), 124);
1159    }
1160
1161    #[test]
1162    fn divide_by_zero_is_a_typed_error() {
1163        let q = bin(BinOp::Div, var(0, u(8)), var(1, u(8)));
1164        assert_eq!(
1165            eval(&q, &asg(&[1, 0])),
1166            Err(ConstraintEvalError::DivideByZero)
1167        );
1168    }
1169
1170    #[test]
1171    fn shifts() {
1172        let a = var(0, u(8));
1173        let sh = |op, n: u64| bin(op, a.clone(), c(u(4), n as i64));
1174        assert_eq!(ev(&sh(BinOp::Shl, 3), &[0b1011_0001]), 0b1000_1000);
1175        assert_eq!(ev(&sh(BinOp::Shr, 3), &[0b1011_0001]), 0b0001_0110);
1176        // Shifting by >= the width clears everything.
1177        assert_eq!(ev(&sh(BinOp::Shl, 9), &[0xFF]), 0);
1178        assert_eq!(ev(&sh(BinOp::Shr, 12), &[0xFF]), 0);
1179        // `>>>` on an unsigned operand is a logical shift (11.4.10) ...
1180        assert_eq!(ev(&sh(BinOp::Ashr, 2), &[0x80]), 0x20);
1181        // ... and on a signed one it fills with the sign.
1182        let sa = bin(BinOp::Ashr, var(0, s(8)), c(u(4), 2));
1183        assert_eq!(ev(&sa, &[0x80]), 0xE0);
1184        let sa_big = bin(BinOp::Ashr, var(0, s(8)), c(u(4), 12));
1185        assert_eq!(ev(&sa_big, &[0x80]), 0xFF);
1186        assert_eq!(ev(&sa_big, &[0x40]), 0x00);
1187    }
1188
1189    #[test]
1190    fn reductions_and_logical_ops() {
1191        let a = var(0, u(4));
1192        let red = |op| Expr::Unary(op, Box::new(a.clone()), Ty::BIT);
1193        assert_eq!(ev(&red(UnOp::RedAnd), &[0b1111]), 1);
1194        assert_eq!(ev(&red(UnOp::RedAnd), &[0b1110]), 0);
1195        assert_eq!(ev(&red(UnOp::RedNand), &[0b1110]), 1);
1196        assert_eq!(ev(&red(UnOp::RedOr), &[0]), 0);
1197        assert_eq!(ev(&red(UnOp::RedNor), &[0]), 1);
1198        assert_eq!(ev(&red(UnOp::RedXor), &[0b0111]), 1);
1199        assert_eq!(ev(&red(UnOp::RedXnor), &[0b0111]), 0);
1200        assert_eq!(ev(&red(UnOp::LogNot), &[0]), 1);
1201        // 8 -> 1, 0 -> 0 as truthiness; implication and equivalence.
1202        let imp = bin(BinOp::LogImpl, var(0, u(4)), var(1, u(4)));
1203        assert_eq!(ev(&imp, &[8, 0]), 0);
1204        assert_eq!(ev(&imp, &[0, 0]), 1);
1205        assert_eq!(ev(&imp, &[8, 3]), 1);
1206        let eqv = bin(BinOp::LogEquiv, var(0, u(4)), var(1, u(4)));
1207        assert_eq!(ev(&eqv, &[8, 1]), 1);
1208        assert_eq!(ev(&eqv, &[8, 0]), 0);
1209    }
1210
1211    #[test]
1212    fn resize_extends_by_the_operand_signedness() {
1213        // 4'b1000 sign-extends to 8'hF8 when signed, 8'h08 when unsigned.
1214        let zs = Expr::Resize(Box::new(var(0, s(4))), s(8));
1215        let zu = Expr::Resize(Box::new(var(0, u(4))), s(8));
1216        assert_eq!(ev(&zs, &[0b1000]), 0xF8);
1217        assert_eq!(ev(&zu, &[0b1000]), 0x08);
1218        let tr = Expr::Resize(Box::new(var(0, u(8))), u(4));
1219        assert_eq!(ev(&tr, &[0xAB]), 0xB);
1220    }
1221
1222    #[test]
1223    fn concat_and_slice() {
1224        let cat = Expr::Concat(vec![var(0, u(4)), var(1, u(4))], u(8));
1225        assert_eq!(ev(&cat, &[0xA, 0x5]), 0xA5);
1226        let sl = Expr::Slice(Box::new(var(0, u(8))), 2, u(3));
1227        assert_eq!(ev(&sl, &[0b1011_0100]), 0b101);
1228    }
1229
1230    #[test]
1231    fn all_distinct_is_every_pair_not_just_neighbours() {
1232        let e = Expr::AllDistinct(vec![var(0, u(2)), var(1, u(2)), var(2, u(2))]);
1233        assert_eq!(e.ty(), Ty::BIT);
1234        for a in 0..4u64 {
1235            for b in 0..4u64 {
1236                for d in 0..4u64 {
1237                    let want = u64::from(a != b && b != d && a != d);
1238                    assert_eq!(ev(&e, &[a, b, d]), want, "{a} {b} {d}");
1239                }
1240            }
1241        }
1242        // The same variable twice is two equal members.
1243        let twice = Expr::AllDistinct(vec![var(0, u(2)), var(0, u(2))]);
1244        assert_eq!(ev(&twice, &[1]), 0);
1245        // A group with one member (the builder never makes one) is trivially distinct.
1246        assert_eq!(ev(&Expr::AllDistinct(vec![var(0, u(2))]), &[1]), 1);
1247    }
1248
1249    #[test]
1250    fn all_distinct_reads_every_member_and_state() {
1251        let e = Expr::AllDistinct(vec![var(0, u(4)), Expr::State(0, u(4)), var(1, u(4))]);
1252        let st = |v: Option<u8>| Assignment {
1253            vars: vec![BigUint::from(1u8), BigUint::from(2u8)],
1254            states: vec![v.map(BigUint::from)],
1255        };
1256        assert_eq!(eval(&e, &st(Some(3))), Ok(BigUint::one()));
1257        assert_eq!(eval(&e, &st(Some(2))), Ok(BigUint::zero()));
1258        // An unreadable member is an error even when two others already
1259        // collide (the group is not short-circuited past it).
1260        let dup = Expr::AllDistinct(vec![var(0, u(4)), var(0, u(4)), Expr::State(0, u(4))]);
1261        assert_eq!(
1262            eval(&dup, &st(None)),
1263            Err(ConstraintEvalError::StateUnknown(0))
1264        );
1265        let mut vars = vec![];
1266        e.for_each_var(&mut |v, _| vars.push(v));
1267        assert_eq!(vars, [0, 1]);
1268        let mut states = vec![];
1269        e.for_each_state(&mut |s| states.push(s));
1270        assert_eq!(states, [0]);
1271    }
1272
1273    #[test]
1274    fn all_distinct_substitution_keeps_member_order() {
1275        let e = Expr::AllDistinct(vec![var(0, u(4)), var(1, u(4)), var(2, u(4))]);
1276        let sub = e.substitute_vars(&mut |v, t| match v {
1277            1 => Expr::constant(t, 9u8),
1278            other => Expr::Var(other + 10, t),
1279        });
1280        assert_eq!(
1281            sub,
1282            Expr::AllDistinct(vec![var(10, u(4)), c(u(4), 9), var(12, u(4))])
1283        );
1284    }
1285
1286    #[test]
1287    fn all_distinct_validation_demands_one_member_type() {
1288        assert!(
1289            Expr::AllDistinct(vec![var(0, u(4)), var(1, u(4))])
1290                .validate()
1291                .is_ok()
1292        );
1293        // The empty group has no type to agree on: a builder bug.
1294        assert!(Expr::AllDistinct(vec![]).validate().is_err());
1295        // Width and signedness must agree (6.22.1 equivalence).
1296        assert!(
1297            Expr::AllDistinct(vec![var(0, u(4)), var(1, u(8))])
1298                .validate()
1299                .is_err()
1300        );
1301        assert!(
1302            Expr::AllDistinct(vec![var(0, u(4)), var(1, s(4))])
1303                .validate()
1304                .is_err()
1305        );
1306        // A member's own tree is validated too.
1307        let bad_member = Expr::Const(u(4), BigUint::from(16u8));
1308        assert!(
1309            Expr::AllDistinct(vec![var(0, u(4)), bad_member])
1310                .validate()
1311                .is_err()
1312        );
1313    }
1314
1315    #[test]
1316    fn ite_reads_truthiness() {
1317        let e = Expr::Ite(
1318            Box::new(var(0, u(4))),
1319            Box::new(c(u(8), 7)),
1320            Box::new(c(u(8), 9)),
1321            u(8),
1322        );
1323        assert_eq!(ev(&e, &[0]), 9);
1324        assert_eq!(ev(&e, &[2]), 7);
1325    }
1326
1327    #[test]
1328    fn state_with_unknown_bits_is_an_error_never_zero() {
1329        let e = bin(BinOp::Add, Expr::State(0, u(8)), c(u(8), 1));
1330        let ok = Assignment {
1331            vars: vec![],
1332            states: vec![Some(BigUint::from(4u8))],
1333        };
1334        assert_eq!(eval(&e, &ok), Ok(BigUint::from(5u8)));
1335        let xz = Assignment {
1336            vars: vec![],
1337            states: vec![None],
1338        };
1339        assert_eq!(eval(&e, &xz), Err(ConstraintEvalError::StateUnknown(0)));
1340        let none = Assignment::default();
1341        assert_eq!(eval(&e, &none), Err(ConstraintEvalError::UnboundState(0)));
1342    }
1343
1344    #[test]
1345    fn wide_values_beyond_64_bits() {
1346        let w = 100;
1347        let a = Expr::Var(0, u(w));
1348        let e = bin(BinOp::Add, a, Expr::constant(u(w), 1u8));
1349        let all_ones = Assignment {
1350            vars: vec![mask(w)],
1351            states: vec![],
1352        };
1353        // 2^100 - 1 + 1 wraps to 0.
1354        assert_eq!(eval(&e, &all_ones), Ok(BigUint::zero()));
1355        let half = Assignment {
1356            vars: vec![BigUint::one() << 99],
1357            states: vec![],
1358        };
1359        assert_eq!(eval(&e, &half), Ok((BigUint::one() << 99) + BigUint::one()));
1360    }
1361
1362    #[test]
1363    fn validation_rejects_ill_typed_trees() {
1364        // Operand widths differ.
1365        let bad = Expr::Binary(
1366            BinOp::Add,
1367            Box::new(var(0, u(8))),
1368            Box::new(var(1, u(4))),
1369            u(8),
1370        );
1371        assert!(matches!(bad.validate(), Err(IrError::IllTyped { .. })));
1372        // A comparison must produce one bit.
1373        let bad = Expr::Binary(
1374            BinOp::Lt,
1375            Box::new(var(0, u(8))),
1376            Box::new(var(1, u(8))),
1377            u(8),
1378        );
1379        assert!(bad.validate().is_err());
1380        // Slice out of range.
1381        let bad = Expr::Slice(Box::new(var(0, u(8))), 6, u(4));
1382        assert!(bad.validate().is_err());
1383        // Constant too wide for its type.
1384        let bad = Expr::Const(u(4), BigUint::from(16u8));
1385        assert!(bad.validate().is_err());
1386        // Concat width mismatch.
1387        let bad = Expr::Concat(vec![var(0, u(4)), var(1, u(4))], u(9));
1388        assert!(bad.validate().is_err());
1389        // Mixed signedness in a comparison is a builder bug, not a coercion.
1390        let bad = Expr::Binary(
1391            BinOp::Lt,
1392            Box::new(var(0, s(8))),
1393            Box::new(var(1, u(8))),
1394            Ty::BIT,
1395        );
1396        assert!(bad.validate().is_err());
1397    }
1398
1399    #[test]
1400    fn for_each_var_visits_every_read() {
1401        let e = bin(
1402            BinOp::Add,
1403            Expr::Concat(vec![var(3, u(4)), var(1, u(4))], u(8)),
1404            Expr::Ite(
1405                Box::new(var(2, u(1))),
1406                Box::new(Expr::Const(u(8), BigUint::one())),
1407                Box::new(Expr::State(0, u(8))),
1408                u(8),
1409            ),
1410        );
1411        let mut seen = vec![];
1412        e.for_each_var(&mut |v, _| seen.push(v));
1413        seen.sort_unstable();
1414        assert_eq!(seen, [1, 2, 3]);
1415    }
1416
1417    #[test]
1418    fn substitute_vars_replaces_and_preserves_evaluation() {
1419        // x0 + concat(x1, x2)[7:0] with x1 replaced by the constant 5 and the
1420        // others renumbered (x0 -> 1, x2 -> 0): the substituted tree must
1421        // evaluate exactly like the original under the matching assignment.
1422        let e = bin(
1423            BinOp::Add,
1424            var(0, u(8)),
1425            Expr::Concat(vec![var(1, u(4)), var(2, u(4))], u(8)),
1426        );
1427        let sub = e.substitute_vars(&mut |v, t| match v {
1428            0 => Expr::Var(1, t),
1429            1 => Expr::constant(t, 5u8),
1430            _ => Expr::Var(0, t),
1431        });
1432        sub.validate().expect("still well-typed");
1433        let mut seen = vec![];
1434        sub.for_each_var(&mut |v, _| seen.push(v));
1435        seen.sort_unstable();
1436        assert_eq!(seen, [0, 1], "x1 is gone, the others renumbered");
1437        let full = Assignment {
1438            vars: vec![BigUint::from(10u8), BigUint::from(5u8), BigUint::from(3u8)],
1439            states: vec![],
1440        };
1441        let part = Assignment {
1442            vars: vec![BigUint::from(3u8), BigUint::from(10u8)],
1443            states: vec![],
1444        };
1445        assert_eq!(eval(&e, &full), eval(&sub, &part));
1446        assert_eq!(eval(&sub, &part), Ok(BigUint::from(10u8 + 0x53)));
1447    }
1448}