vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Slang-free timing data shared by the planner (`netlist::specify`, behind the
//! `frontend` feature) and the executors (`vj-exec`, which a built `.so`
//! links): a module path's transition-delay table and the pulse style. Both
//! travel inside a design image, hence the serde derives.

use serde::{Deserialize, Serialize};
use vj_abi::bit4::Bit4;

/// A module path's own 12-entry transition-delay table (LRM 1800-2023
/// §30.5.1 Table 30-2, §30.5.2 Table 30-3) -- `docs/superpowers/plans/
/// 2026-10-03-sdf.md` Task 4a's `PathDelaySet`.
///
/// **Deviation from the plan's own §1.1 placement** ("new,
/// `crates/vj/src/specify.rs`"): this type lives here instead,
/// alongside [`PathEntry`], because [`PathEntry::delays`] below is a FIELD
/// of this type, and `vj` depends on `vj-sim` (never the other
/// way around) -- a type used as a field of a `vj-sim`-resident struct
/// cannot itself live in a downstream crate. This is also the layering
/// [`TimingModel`]'s own module doc already commits to: `$sdf_annotate`
/// (Track C) mutates a `PathEntry`'s delays in place, which means the
/// delays must live in `TimingModel` itself, not in a transient
/// `vj`-only plan-time structure.
///
/// Evaluating a path's own `path_delay_value` expressions into real
/// `u64` tick counts needs `sv_lang::EvalSession` (constant-folding) AND
/// `vj-codegen::timescale` (unit/precision conversion) -- the latter
/// this crate cannot depend on (see `PathEntry::delays`'s own doc comment).
/// So `PathDelaySet::from_list` takes already-evaluated, already
/// tick-converted `u64`s: evaluation+conversion is `vj`'s own job
/// (`crate::specify::discover`'s delay-attachment pass, Task 4a), calling
/// [`TimingModel::set_path_delays`] to attach the result -- mirroring
/// [`TimingModel::set_specparam`]'s own, already-established "no
/// `sv_lang`/`EvalSession` in the mutation path" shape exactly.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PathDelaySet {
    /// Indexed by [`transition_index`]: the 12 `from != to` pairs of
    /// [`Bit4`]'s four values, in LRM Table 30-2's own row order (`0->1`,
    /// `1->0`, `0->z`, `z->1`, `1->z`, `z->0`, `0->x`, `x->1`, `1->x`,
    /// `x->0`, `x->z`, `z->x`).
    t: [u64; 12],
}

/// The twelve `from -> to` transitions of a [`PathDelaySet`], in the order of its storage
/// (LRM Table 30-2's row order); `TRANSITIONS[transition_index(f, t)] == (f, t)`.
pub const TRANSITIONS: [(Bit4, Bit4); 12] = [
    (Bit4::Zero, Bit4::One),
    (Bit4::One, Bit4::Zero),
    (Bit4::Zero, Bit4::Z),
    (Bit4::Z, Bit4::One),
    (Bit4::One, Bit4::Z),
    (Bit4::Z, Bit4::Zero),
    (Bit4::Zero, Bit4::X),
    (Bit4::X, Bit4::One),
    (Bit4::One, Bit4::X),
    (Bit4::X, Bit4::Zero),
    (Bit4::X, Bit4::Z),
    (Bit4::Z, Bit4::X),
];

/// The index into [`PathDelaySet::t`] for transition `from -> to`
/// (`from != to`). [`Bit4`] has exactly four values, so its 16 `(from, to)`
/// pairs split into exactly 4 same-pairs (never a real transition -- every
/// caller already guards against calling this with `from == to`, see
/// [`PathDelaySet::of`]'s own doc comment) and these 12 real ones. Matched
/// exhaustively over all 16 combinations (no wildcard) so a future `Bit4`
/// variant would fail to compile here rather than silently falling into an
/// `other` arm.
fn transition_index(from: Bit4, to: Bit4) -> usize {
    use Bit4::{One, X, Z, Zero};
    match (from, to) {
        (Zero, One) => 0,
        (One, Zero) => 1,
        (Zero, Z) => 2,
        (Z, One) => 3,
        (One, Z) => 4,
        (Z, Zero) => 5,
        (Zero, X) => 6,
        (X, One) => 7,
        (One, X) => 8,
        (X, Zero) => 9,
        (X, Z) => 10,
        (Z, X) => 11,
        (Zero, Zero) | (One, One) | (X, X) | (Z, Z) => {
            unreachable!(
                "transition_index: from == to ({from:?}) is not a transition -- every caller \
                 must guard against this before calling"
            )
        }
    }
}

impl PathDelaySet {
    /// LRM Table 30-2's full 1/2/3/6/12-value `path_delay_value` forms (`d`
    /// already evaluated and tick-converted by the caller -- see this
    /// type's own doc comment).
    ///
    /// For the 1/2/3/6-value forms, once the six "known" transitions
    /// (`0->1`, `1->0`, `0->z`, `z->1`, `1->z`, `z->0`) are set from `d` per
    /// Table 30-2's own column for that count -- the 6-value form is simply
    /// those six values given directly, in the SAME `t01,t10,t0z,tz1,t1z,
    /// tz0` order Table 30-2's own 6-value column lists them, so it needs
    /// no grouping logic of its own, unlike the 1/2/3-value forms' rise/
    /// fall/z-shared derivation -- the six `x`-involving transitions are
    /// filled in from THEM via Table 30-3's own general algorithm (`s->x` =
    /// the shorter of the two delays INTO `x` from a known state; `x->s` =
    /// the longer of the two delays OUT of `x` into a known state). This is
    /// the table's own "general algorithm," not an approximation, and
    /// applies identically to the 1/2/3-value and 6-value forms alike (one
    /// shared `match` arm set feeds it).
    ///
    /// The 12-value form is different in kind, not just in count: Table
    /// 30-2 lists an explicit column for all twelve transitions, INCLUDING
    /// the six `x`-involving ones -- there are no "known" vs "unknown"
    /// transitions left for Table 30-3's algorithm to derive from, so a
    /// 12-value list is copied straight into `t`, in [`transition_index`]'s
    /// own `0->1,1->0,0->z,z->1,1->z,z->0,0->x,x->1,1->x,x->0,x->z,z->x`
    /// order -- which is, verified directly against the LRM text, the
    /// EXACT order Table 30-2's own 12-value column already lists them in,
    /// so no reordering is needed, only the copy.
    pub fn from_list(d: &[u64]) -> Result<PathDelaySet, String> {
        if let [t01, t10, t0z, tz1, t1z, tz0, t0x, tx1, t1x, tx0, txz, tzx] = d {
            return Ok(PathDelaySet {
                t: [
                    *t01, *t10, *t0z, *tz1, *t1z, *tz0, *t0x, *tx1, *t1x, *tx0, *txz, *tzx,
                ],
            });
        }
        let (r01, r10, r0z, rz1, r1z, rz0) = match d {
            [] => return Err("a path delay with no values (unexpected AST shape)".to_string()),
            [t] => (*t, *t, *t, *t, *t, *t),
            [r, f] => (*r, *f, *r, *r, *f, *f),
            [r, f, z] => (*r, *f, *z, *r, *z, *f),
            [t01, t10, t0z, tz1, t1z, tz0] => (*t01, *t10, *t0z, *tz1, *t1z, *tz0),
            _ => {
                return Err(format!(
                    "a {}-value path delay list (LRM Table 30-2 defines only the 1-, 2-, 3-, \
                     6- and 12-value `path_delay_value` forms)",
                    d.len()
                ));
            }
        };
        let mut t = [0u64; 12];
        t[transition_index(Bit4::Zero, Bit4::One)] = r01;
        t[transition_index(Bit4::One, Bit4::Zero)] = r10;
        t[transition_index(Bit4::Zero, Bit4::Z)] = r0z;
        t[transition_index(Bit4::Z, Bit4::One)] = rz1;
        t[transition_index(Bit4::One, Bit4::Z)] = r1z;
        t[transition_index(Bit4::Z, Bit4::Zero)] = rz0;
        // Table 30-3's "specific transitions": s->x = min(s->other known
        // states); x->s = max(other known states->s).
        t[transition_index(Bit4::Zero, Bit4::X)] = r0z.min(r01);
        t[transition_index(Bit4::One, Bit4::X)] = r1z.min(r10);
        t[transition_index(Bit4::Z, Bit4::X)] = rz1.min(rz0);
        t[transition_index(Bit4::X, Bit4::Zero)] = rz0.max(r10);
        t[transition_index(Bit4::X, Bit4::One)] = rz1.max(r01);
        t[transition_index(Bit4::X, Bit4::Z)] = r1z.max(r0z);
        Ok(PathDelaySet { t })
    }

    /// A table from its twelve per-transition values, in [`TRANSITIONS`] order. The one
    /// constructor that takes a table already expanded by someone other than
    /// [`Self::from_list`] (SDF's own Table 32-4 expansion, `vj::sdf_apply`, which
    /// is a different function from Table 30-3's and so must not go through it).
    pub fn from_transitions(t: [u64; 12]) -> PathDelaySet {
        PathDelaySet { t }
    }

    /// The twelve per-transition values, in [`TRANSITIONS`] order.
    pub fn transitions(&self) -> [u64; 12] {
        self.t
    }

    /// A table whose twelve transitions all hold `v` -- the shape a
    /// `PATHPULSE$` limit takes (LRM 30.7.1 names ONE reject and ONE error
    /// value for the whole path, never one per transition).
    pub fn uniform(v: u64) -> PathDelaySet {
        PathDelaySet { t: [v; 12] }
    }

    /// The delay for transition `from -> to`, in scheduler ticks. Callers
    /// (`vj::specify::PathRetimer`) only ever call this after
    /// proving `from != to` (a real transition) themselves -- see
    /// [`transition_index`]'s own doc comment for why that guard lives at
    /// the call site, not here.
    pub fn of(&self, from: Bit4, to: Bit4) -> u64 {
        self.t[transition_index(from, to)]
    }

    /// Every transition's delay raised to at least `floor` ticks (LRM 31.9.1: a timing check
    /// signal delayed by more than a path's delay makes the output follow the delayed signal,
    /// "as if its specify path delay were equal to the delay applied to the timing check
    /// signal").
    pub fn at_least(self, floor: u64) -> PathDelaySet {
        PathDelaySet {
            t: self.t.map(|d| d.max(floor)),
        }
    }

    /// The shortest of all 12 transitions' delays -- the longest a
    /// `vj::primitives::Delayer` reused for this path may sleep at
    /// once without risking a later, shorter-delayed transition arriving
    /// late (mirrors `primitives::Delays::min`'s own identical role).
    pub fn min_all(&self) -> u64 {
        self.t.iter().copied().min().unwrap_or(0)
    }
}

/// A module path output's pulse style (LRM 30.7.4.1), the `pulsestyle_onevent`
/// / `pulsestyle_ondetect` choice. The LRM's own default, absent a
/// declaration, is on-event. (The `showcancelled`/`noshowcancelled` choice of
/// 30.7.4.2 is a separate, independent setting: [`PathEntry::showcancelled`].)
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum PulseStyle {
    /// `pulsestyle_onevent`: a filtered pulse's `x` replaces the leading edge's
    /// own schedule, so it begins when that edge was due.
    #[default]
    OnEvent,
    /// `pulsestyle_ondetect`: the `x` is made immediately upon detection of
    /// the pulse, ahead of the leading edge's scheduled time.
    OnDetect,
}

/// LRM 1800-2023 Table 30-1's own eight UNARY operators valid in a
/// state-dependent path's `if (cond)` conditional expression (30.4.4.1,
/// Task 8). A one-to-one subset of [`sv_lang::UnaryOp`]'s own variants --
/// [`Self::from_sv`] is the one, exhaustive translation point: every
/// `UnaryOp` variant is named explicitly (never a wildcard `_` arm), so a
/// future `UnaryOp` addition fails to compile here instead of silently
/// being accepted or silently being rejected.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CondUnaryOp {
    /// `~` -- bitwise negation (per-bit, width-preserving; NOT a reduction).
    BitwiseNot,
    /// `&` -- reduction AND.
    ReductionAnd,
    /// `|` -- reduction OR.
    ReductionOr,
    /// `^` -- reduction XOR.
    ReductionXor,
    /// `~&` -- reduction NAND.
    ReductionNand,
    /// `~|` -- reduction NOR.
    ReductionNor,
    /// `^~`/`~^` -- reduction XNOR.
    ReductionXnor,
    /// `!` -- logical NOT.
    LogicalNot,
}

/// LRM Table 30-1's own eight BINARY operators valid in a state-dependent
/// path's conditional expression -- the binary-operator sibling of
/// [`CondUnaryOp`], same exhaustive-translation discipline
/// ([`Self::from_sv`]).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CondBinaryOp {
    /// `&` -- bitwise AND (two operands; distinct from [`CondUnaryOp::
    /// ReductionAnd`]'s single-operand reduction form, Table 30-1 lists
    /// both under the same glyph).
    BitwiseAnd,
    /// `|` -- bitwise OR.
    BitwiseOr,
    /// `^` -- bitwise XOR.
    BitwiseXor,
    /// `^~`/`~^` -- bitwise XNOR.
    BitwiseXnor,
    /// `==` -- logical equality.
    Equality,
    /// `!=` -- logical inequality.
    Inequality,
    /// `&&` -- logical AND.
    LogicalAnd,
    /// `||` -- logical OR.
    LogicalOr,
    /// `===` -- case equality. Table 30-1 does not list it, so a module path
    /// condition never produces it ([`Self::from_sv`]); LRM 31.7's
    /// `scalar_timing_check_condition` does ([`Self::from_sv_check`]), and a
    /// timing check's `&&&` condition is the only producer.
    CaseEquality,
    /// `!==` -- case inequality (see [`Self::CaseEquality`]).
    CaseInequality,
}

/// One of the six transitions an edge-control specifier can name (LRM 31.5):
/// `01`, `10`, `0x`, `x0`, `1x`, `x1`. `z` is folded into `x` ("Edge
/// transitions involving z are treated the same way as edge transitions
/// involving x"), so there is no `0z`/`z1`/... variant, and a transition from
/// `x` to `z` (or back) is none of the six.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EdgeTransition {
    ZeroOne,
    OneZero,
    ZeroX,
    XZero,
    OneX,
    XOne,
}

impl EdgeTransition {
    /// All six, in the order of 31.5's list.
    pub const ALL: [EdgeTransition; 6] = [
        EdgeTransition::ZeroOne,
        EdgeTransition::ZeroX,
        EdgeTransition::OneZero,
        EdgeTransition::OneX,
        EdgeTransition::XZero,
        EdgeTransition::XOne,
    ];

    fn mask(self) -> u8 {
        1 << (self as u8)
    }

    /// The transition in the other direction: `01`<->`10`, `0x`<->`x0`,
    /// `x1`<->`1x`. `$width`'s data event is "reference event signal with
    /// opposite edge" (LRM 31.4.4) and this is the one place that says what the
    /// opposite of each of the six is; `posedge` (`01, 0x, x1`) and `negedge`
    /// (`10, x0, 1x`) come out as each other's reversal.
    pub fn reversed(self) -> EdgeTransition {
        match self {
            EdgeTransition::ZeroOne => EdgeTransition::OneZero,
            EdgeTransition::OneZero => EdgeTransition::ZeroOne,
            EdgeTransition::ZeroX => EdgeTransition::XZero,
            EdgeTransition::XZero => EdgeTransition::ZeroX,
            EdgeTransition::OneX => EdgeTransition::XOne,
            EdgeTransition::XOne => EdgeTransition::OneX,
        }
    }

    /// The transition a descriptor spells (`01`, `0x`, `Z1`, ...), `None` if
    /// `d` is not one of 31.5's `edge_descriptor` productions
    /// (`01 | 10 | z_or_x zero_or_one | zero_or_one z_or_x`, case-insensitive
    /// `x`/`z`, no embedded space).
    pub fn of_descriptor(d: &str) -> Option<EdgeTransition> {
        let &[a, b] = d.as_bytes() else { return None };
        let unknown = |c: u8| matches!(c, b'x' | b'X' | b'z' | b'Z');
        match (a, b) {
            (b'0', b'1') => Some(EdgeTransition::ZeroOne),
            (b'1', b'0') => Some(EdgeTransition::OneZero),
            (b'0', c) if unknown(c) => Some(EdgeTransition::ZeroX),
            (b'1', c) if unknown(c) => Some(EdgeTransition::OneX),
            (c, b'0') if unknown(c) => Some(EdgeTransition::XZero),
            (c, b'1') if unknown(c) => Some(EdgeTransition::XOne),
            _ => None,
        }
    }
}

/// A set of [`EdgeTransition`]s: what an `edge [...]` list, or `posedge` /
/// `negedge`, accepts.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct EdgeSet(u8);

impl EdgeSet {
    pub const EMPTY: EdgeSet = EdgeSet(0);
    /// All six transitions.
    pub const ALL: EdgeSet = EdgeSet(0b11_1111);
    /// `posedge` is `edge[01, 0x, x1]` (LRM 31.5).
    pub const POSEDGE: EdgeSet = EdgeSet(
        (1 << EdgeTransition::ZeroOne as u8)
            | (1 << EdgeTransition::ZeroX as u8)
            | (1 << EdgeTransition::XOne as u8),
    );
    /// `negedge` is `edge[10, x0, 1x]` (LRM 31.5).
    pub const NEGEDGE: EdgeSet = EdgeSet(
        (1 << EdgeTransition::OneZero as u8)
            | (1 << EdgeTransition::XZero as u8)
            | (1 << EdgeTransition::OneX as u8),
    );

    pub fn with(self, t: EdgeTransition) -> EdgeSet {
        EdgeSet(self.0 | t.mask())
    }

    pub fn contains(self, t: EdgeTransition) -> bool {
        self.0 & t.mask() != 0
    }

    /// Every member reversed ([`EdgeTransition::reversed`]).
    pub fn reversed(self) -> EdgeSet {
        EdgeTransition::ALL
            .into_iter()
            .filter(|&t| self.contains(t))
            .fold(EdgeSet::EMPTY, |set, t| set.with(t.reversed()))
    }
}

/// What makes a timing-check event occur (LRM 31.5): the resolved form of one
/// event's `posedge` / `negedge` / `edge` / `edge [descriptors]` / nothing.
/// Whether a particular `(from, to)` value change is such an event is
/// `vj::timingcheck::edge_matches`'s job -- the one place that
/// interprets this.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EdgeControl {
    /// No edge control: ANY value change of the terminal is the event
    /// (`from != to`; an `x`<->`z` change counts, it is a change of the
    /// signal even though no edge descriptor names it).
    AnyChange,
    /// Only a change that is one of these transitions is the event.
    Edges(EdgeSet),
}