Skip to main content

vj_sim/
timing_types.rs

1//! Slang-free timing data shared by the planner (`netlist::specify`, behind the
2//! `frontend` feature) and the executors (`vj-exec`, which a built `.so`
3//! links): a module path's transition-delay table and the pulse style. Both
4//! travel inside a design image, hence the serde derives.
5
6use serde::{Deserialize, Serialize};
7use vj_abi::bit4::Bit4;
8
9/// A module path's own 12-entry transition-delay table (LRM 1800-2023
10/// §30.5.1 Table 30-2, §30.5.2 Table 30-3) -- `docs/superpowers/plans/
11/// 2026-10-03-sdf.md` Task 4a's `PathDelaySet`.
12///
13/// **Deviation from the plan's own §1.1 placement** ("new,
14/// `crates/vj/src/specify.rs`"): this type lives here instead,
15/// alongside [`PathEntry`], because [`PathEntry::delays`] below is a FIELD
16/// of this type, and `vj` depends on `vj-sim` (never the other
17/// way around) -- a type used as a field of a `vj-sim`-resident struct
18/// cannot itself live in a downstream crate. This is also the layering
19/// [`TimingModel`]'s own module doc already commits to: `$sdf_annotate`
20/// (Track C) mutates a `PathEntry`'s delays in place, which means the
21/// delays must live in `TimingModel` itself, not in a transient
22/// `vj`-only plan-time structure.
23///
24/// Evaluating a path's own `path_delay_value` expressions into real
25/// `u64` tick counts needs `sv_lang::EvalSession` (constant-folding) AND
26/// `vj-codegen::timescale` (unit/precision conversion) -- the latter
27/// this crate cannot depend on (see `PathEntry::delays`'s own doc comment).
28/// So `PathDelaySet::from_list` takes already-evaluated, already
29/// tick-converted `u64`s: evaluation+conversion is `vj`'s own job
30/// (`crate::specify::discover`'s delay-attachment pass, Task 4a), calling
31/// [`TimingModel::set_path_delays`] to attach the result -- mirroring
32/// [`TimingModel::set_specparam`]'s own, already-established "no
33/// `sv_lang`/`EvalSession` in the mutation path" shape exactly.
34#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
35pub struct PathDelaySet {
36    /// Indexed by [`transition_index`]: the 12 `from != to` pairs of
37    /// [`Bit4`]'s four values, in LRM Table 30-2's own row order (`0->1`,
38    /// `1->0`, `0->z`, `z->1`, `1->z`, `z->0`, `0->x`, `x->1`, `1->x`,
39    /// `x->0`, `x->z`, `z->x`).
40    t: [u64; 12],
41}
42
43/// The twelve `from -> to` transitions of a [`PathDelaySet`], in the order of its storage
44/// (LRM Table 30-2's row order); `TRANSITIONS[transition_index(f, t)] == (f, t)`.
45pub const TRANSITIONS: [(Bit4, Bit4); 12] = [
46    (Bit4::Zero, Bit4::One),
47    (Bit4::One, Bit4::Zero),
48    (Bit4::Zero, Bit4::Z),
49    (Bit4::Z, Bit4::One),
50    (Bit4::One, Bit4::Z),
51    (Bit4::Z, Bit4::Zero),
52    (Bit4::Zero, Bit4::X),
53    (Bit4::X, Bit4::One),
54    (Bit4::One, Bit4::X),
55    (Bit4::X, Bit4::Zero),
56    (Bit4::X, Bit4::Z),
57    (Bit4::Z, Bit4::X),
58];
59
60/// The index into [`PathDelaySet::t`] for transition `from -> to`
61/// (`from != to`). [`Bit4`] has exactly four values, so its 16 `(from, to)`
62/// pairs split into exactly 4 same-pairs (never a real transition -- every
63/// caller already guards against calling this with `from == to`, see
64/// [`PathDelaySet::of`]'s own doc comment) and these 12 real ones. Matched
65/// exhaustively over all 16 combinations (no wildcard) so a future `Bit4`
66/// variant would fail to compile here rather than silently falling into an
67/// `other` arm.
68fn transition_index(from: Bit4, to: Bit4) -> usize {
69    use Bit4::{One, X, Z, Zero};
70    match (from, to) {
71        (Zero, One) => 0,
72        (One, Zero) => 1,
73        (Zero, Z) => 2,
74        (Z, One) => 3,
75        (One, Z) => 4,
76        (Z, Zero) => 5,
77        (Zero, X) => 6,
78        (X, One) => 7,
79        (One, X) => 8,
80        (X, Zero) => 9,
81        (X, Z) => 10,
82        (Z, X) => 11,
83        (Zero, Zero) | (One, One) | (X, X) | (Z, Z) => {
84            unreachable!(
85                "transition_index: from == to ({from:?}) is not a transition -- every caller \
86                 must guard against this before calling"
87            )
88        }
89    }
90}
91
92impl PathDelaySet {
93    /// LRM Table 30-2's full 1/2/3/6/12-value `path_delay_value` forms (`d`
94    /// already evaluated and tick-converted by the caller -- see this
95    /// type's own doc comment).
96    ///
97    /// For the 1/2/3/6-value forms, once the six "known" transitions
98    /// (`0->1`, `1->0`, `0->z`, `z->1`, `1->z`, `z->0`) are set from `d` per
99    /// Table 30-2's own column for that count -- the 6-value form is simply
100    /// those six values given directly, in the SAME `t01,t10,t0z,tz1,t1z,
101    /// tz0` order Table 30-2's own 6-value column lists them, so it needs
102    /// no grouping logic of its own, unlike the 1/2/3-value forms' rise/
103    /// fall/z-shared derivation -- the six `x`-involving transitions are
104    /// filled in from THEM via Table 30-3's own general algorithm (`s->x` =
105    /// the shorter of the two delays INTO `x` from a known state; `x->s` =
106    /// the longer of the two delays OUT of `x` into a known state). This is
107    /// the table's own "general algorithm," not an approximation, and
108    /// applies identically to the 1/2/3-value and 6-value forms alike (one
109    /// shared `match` arm set feeds it).
110    ///
111    /// The 12-value form is different in kind, not just in count: Table
112    /// 30-2 lists an explicit column for all twelve transitions, INCLUDING
113    /// the six `x`-involving ones -- there are no "known" vs "unknown"
114    /// transitions left for Table 30-3's algorithm to derive from, so a
115    /// 12-value list is copied straight into `t`, in [`transition_index`]'s
116    /// 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`
117    /// order -- which is, verified directly against the LRM text, the
118    /// EXACT order Table 30-2's own 12-value column already lists them in,
119    /// so no reordering is needed, only the copy.
120    pub fn from_list(d: &[u64]) -> Result<PathDelaySet, String> {
121        if let [t01, t10, t0z, tz1, t1z, tz0, t0x, tx1, t1x, tx0, txz, tzx] = d {
122            return Ok(PathDelaySet {
123                t: [
124                    *t01, *t10, *t0z, *tz1, *t1z, *tz0, *t0x, *tx1, *t1x, *tx0, *txz, *tzx,
125                ],
126            });
127        }
128        let (r01, r10, r0z, rz1, r1z, rz0) = match d {
129            [] => return Err("a path delay with no values (unexpected AST shape)".to_string()),
130            [t] => (*t, *t, *t, *t, *t, *t),
131            [r, f] => (*r, *f, *r, *r, *f, *f),
132            [r, f, z] => (*r, *f, *z, *r, *z, *f),
133            [t01, t10, t0z, tz1, t1z, tz0] => (*t01, *t10, *t0z, *tz1, *t1z, *tz0),
134            _ => {
135                return Err(format!(
136                    "a {}-value path delay list (LRM Table 30-2 defines only the 1-, 2-, 3-, \
137                     6- and 12-value `path_delay_value` forms)",
138                    d.len()
139                ));
140            }
141        };
142        let mut t = [0u64; 12];
143        t[transition_index(Bit4::Zero, Bit4::One)] = r01;
144        t[transition_index(Bit4::One, Bit4::Zero)] = r10;
145        t[transition_index(Bit4::Zero, Bit4::Z)] = r0z;
146        t[transition_index(Bit4::Z, Bit4::One)] = rz1;
147        t[transition_index(Bit4::One, Bit4::Z)] = r1z;
148        t[transition_index(Bit4::Z, Bit4::Zero)] = rz0;
149        // Table 30-3's "specific transitions": s->x = min(s->other known
150        // states); x->s = max(other known states->s).
151        t[transition_index(Bit4::Zero, Bit4::X)] = r0z.min(r01);
152        t[transition_index(Bit4::One, Bit4::X)] = r1z.min(r10);
153        t[transition_index(Bit4::Z, Bit4::X)] = rz1.min(rz0);
154        t[transition_index(Bit4::X, Bit4::Zero)] = rz0.max(r10);
155        t[transition_index(Bit4::X, Bit4::One)] = rz1.max(r01);
156        t[transition_index(Bit4::X, Bit4::Z)] = r1z.max(r0z);
157        Ok(PathDelaySet { t })
158    }
159
160    /// A table from its twelve per-transition values, in [`TRANSITIONS`] order. The one
161    /// constructor that takes a table already expanded by someone other than
162    /// [`Self::from_list`] (SDF's own Table 32-4 expansion, `vj::sdf_apply`, which
163    /// is a different function from Table 30-3's and so must not go through it).
164    pub fn from_transitions(t: [u64; 12]) -> PathDelaySet {
165        PathDelaySet { t }
166    }
167
168    /// The twelve per-transition values, in [`TRANSITIONS`] order.
169    pub fn transitions(&self) -> [u64; 12] {
170        self.t
171    }
172
173    /// A table whose twelve transitions all hold `v` -- the shape a
174    /// `PATHPULSE$` limit takes (LRM 30.7.1 names ONE reject and ONE error
175    /// value for the whole path, never one per transition).
176    pub fn uniform(v: u64) -> PathDelaySet {
177        PathDelaySet { t: [v; 12] }
178    }
179
180    /// The delay for transition `from -> to`, in scheduler ticks. Callers
181    /// (`vj::specify::PathRetimer`) only ever call this after
182    /// proving `from != to` (a real transition) themselves -- see
183    /// [`transition_index`]'s own doc comment for why that guard lives at
184    /// the call site, not here.
185    pub fn of(&self, from: Bit4, to: Bit4) -> u64 {
186        self.t[transition_index(from, to)]
187    }
188
189    /// Every transition's delay raised to at least `floor` ticks (LRM 31.9.1: a timing check
190    /// signal delayed by more than a path's delay makes the output follow the delayed signal,
191    /// "as if its specify path delay were equal to the delay applied to the timing check
192    /// signal").
193    pub fn at_least(self, floor: u64) -> PathDelaySet {
194        PathDelaySet {
195            t: self.t.map(|d| d.max(floor)),
196        }
197    }
198
199    /// The shortest of all 12 transitions' delays -- the longest a
200    /// `vj::primitives::Delayer` reused for this path may sleep at
201    /// once without risking a later, shorter-delayed transition arriving
202    /// late (mirrors `primitives::Delays::min`'s own identical role).
203    pub fn min_all(&self) -> u64 {
204        self.t.iter().copied().min().unwrap_or(0)
205    }
206}
207
208/// A module path output's pulse style (LRM 30.7.4.1), the `pulsestyle_onevent`
209/// / `pulsestyle_ondetect` choice. The LRM's own default, absent a
210/// declaration, is on-event. (The `showcancelled`/`noshowcancelled` choice of
211/// 30.7.4.2 is a separate, independent setting: [`PathEntry::showcancelled`].)
212#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
213pub enum PulseStyle {
214    /// `pulsestyle_onevent`: a filtered pulse's `x` replaces the leading edge's
215    /// own schedule, so it begins when that edge was due.
216    #[default]
217    OnEvent,
218    /// `pulsestyle_ondetect`: the `x` is made immediately upon detection of
219    /// the pulse, ahead of the leading edge's scheduled time.
220    OnDetect,
221}
222
223/// LRM 1800-2023 Table 30-1's own eight UNARY operators valid in a
224/// state-dependent path's `if (cond)` conditional expression (30.4.4.1,
225/// Task 8). A one-to-one subset of [`sv_lang::UnaryOp`]'s own variants --
226/// [`Self::from_sv`] is the one, exhaustive translation point: every
227/// `UnaryOp` variant is named explicitly (never a wildcard `_` arm), so a
228/// future `UnaryOp` addition fails to compile here instead of silently
229/// being accepted or silently being rejected.
230#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
231pub enum CondUnaryOp {
232    /// `~` -- bitwise negation (per-bit, width-preserving; NOT a reduction).
233    BitwiseNot,
234    /// `&` -- reduction AND.
235    ReductionAnd,
236    /// `|` -- reduction OR.
237    ReductionOr,
238    /// `^` -- reduction XOR.
239    ReductionXor,
240    /// `~&` -- reduction NAND.
241    ReductionNand,
242    /// `~|` -- reduction NOR.
243    ReductionNor,
244    /// `^~`/`~^` -- reduction XNOR.
245    ReductionXnor,
246    /// `!` -- logical NOT.
247    LogicalNot,
248}
249
250/// LRM Table 30-1's own eight BINARY operators valid in a state-dependent
251/// path's conditional expression -- the binary-operator sibling of
252/// [`CondUnaryOp`], same exhaustive-translation discipline
253/// ([`Self::from_sv`]).
254#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
255pub enum CondBinaryOp {
256    /// `&` -- bitwise AND (two operands; distinct from [`CondUnaryOp::
257    /// ReductionAnd`]'s single-operand reduction form, Table 30-1 lists
258    /// both under the same glyph).
259    BitwiseAnd,
260    /// `|` -- bitwise OR.
261    BitwiseOr,
262    /// `^` -- bitwise XOR.
263    BitwiseXor,
264    /// `^~`/`~^` -- bitwise XNOR.
265    BitwiseXnor,
266    /// `==` -- logical equality.
267    Equality,
268    /// `!=` -- logical inequality.
269    Inequality,
270    /// `&&` -- logical AND.
271    LogicalAnd,
272    /// `||` -- logical OR.
273    LogicalOr,
274    /// `===` -- case equality. Table 30-1 does not list it, so a module path
275    /// condition never produces it ([`Self::from_sv`]); LRM 31.7's
276    /// `scalar_timing_check_condition` does ([`Self::from_sv_check`]), and a
277    /// timing check's `&&&` condition is the only producer.
278    CaseEquality,
279    /// `!==` -- case inequality (see [`Self::CaseEquality`]).
280    CaseInequality,
281}
282
283/// One of the six transitions an edge-control specifier can name (LRM 31.5):
284/// `01`, `10`, `0x`, `x0`, `1x`, `x1`. `z` is folded into `x` ("Edge
285/// transitions involving z are treated the same way as edge transitions
286/// involving x"), so there is no `0z`/`z1`/... variant, and a transition from
287/// `x` to `z` (or back) is none of the six.
288#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
289pub enum EdgeTransition {
290    ZeroOne,
291    OneZero,
292    ZeroX,
293    XZero,
294    OneX,
295    XOne,
296}
297
298impl EdgeTransition {
299    /// All six, in the order of 31.5's list.
300    pub const ALL: [EdgeTransition; 6] = [
301        EdgeTransition::ZeroOne,
302        EdgeTransition::ZeroX,
303        EdgeTransition::OneZero,
304        EdgeTransition::OneX,
305        EdgeTransition::XZero,
306        EdgeTransition::XOne,
307    ];
308
309    fn mask(self) -> u8 {
310        1 << (self as u8)
311    }
312
313    /// The transition in the other direction: `01`<->`10`, `0x`<->`x0`,
314    /// `x1`<->`1x`. `$width`'s data event is "reference event signal with
315    /// opposite edge" (LRM 31.4.4) and this is the one place that says what the
316    /// opposite of each of the six is; `posedge` (`01, 0x, x1`) and `negedge`
317    /// (`10, x0, 1x`) come out as each other's reversal.
318    pub fn reversed(self) -> EdgeTransition {
319        match self {
320            EdgeTransition::ZeroOne => EdgeTransition::OneZero,
321            EdgeTransition::OneZero => EdgeTransition::ZeroOne,
322            EdgeTransition::ZeroX => EdgeTransition::XZero,
323            EdgeTransition::XZero => EdgeTransition::ZeroX,
324            EdgeTransition::OneX => EdgeTransition::XOne,
325            EdgeTransition::XOne => EdgeTransition::OneX,
326        }
327    }
328
329    /// The transition a descriptor spells (`01`, `0x`, `Z1`, ...), `None` if
330    /// `d` is not one of 31.5's `edge_descriptor` productions
331    /// (`01 | 10 | z_or_x zero_or_one | zero_or_one z_or_x`, case-insensitive
332    /// `x`/`z`, no embedded space).
333    pub fn of_descriptor(d: &str) -> Option<EdgeTransition> {
334        let &[a, b] = d.as_bytes() else { return None };
335        let unknown = |c: u8| matches!(c, b'x' | b'X' | b'z' | b'Z');
336        match (a, b) {
337            (b'0', b'1') => Some(EdgeTransition::ZeroOne),
338            (b'1', b'0') => Some(EdgeTransition::OneZero),
339            (b'0', c) if unknown(c) => Some(EdgeTransition::ZeroX),
340            (b'1', c) if unknown(c) => Some(EdgeTransition::OneX),
341            (c, b'0') if unknown(c) => Some(EdgeTransition::XZero),
342            (c, b'1') if unknown(c) => Some(EdgeTransition::XOne),
343            _ => None,
344        }
345    }
346}
347
348/// A set of [`EdgeTransition`]s: what an `edge [...]` list, or `posedge` /
349/// `negedge`, accepts.
350#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
351pub struct EdgeSet(u8);
352
353impl EdgeSet {
354    pub const EMPTY: EdgeSet = EdgeSet(0);
355    /// All six transitions.
356    pub const ALL: EdgeSet = EdgeSet(0b11_1111);
357    /// `posedge` is `edge[01, 0x, x1]` (LRM 31.5).
358    pub const POSEDGE: EdgeSet = EdgeSet(
359        (1 << EdgeTransition::ZeroOne as u8)
360            | (1 << EdgeTransition::ZeroX as u8)
361            | (1 << EdgeTransition::XOne as u8),
362    );
363    /// `negedge` is `edge[10, x0, 1x]` (LRM 31.5).
364    pub const NEGEDGE: EdgeSet = EdgeSet(
365        (1 << EdgeTransition::OneZero as u8)
366            | (1 << EdgeTransition::XZero as u8)
367            | (1 << EdgeTransition::OneX as u8),
368    );
369
370    pub fn with(self, t: EdgeTransition) -> EdgeSet {
371        EdgeSet(self.0 | t.mask())
372    }
373
374    pub fn contains(self, t: EdgeTransition) -> bool {
375        self.0 & t.mask() != 0
376    }
377
378    /// Every member reversed ([`EdgeTransition::reversed`]).
379    pub fn reversed(self) -> EdgeSet {
380        EdgeTransition::ALL
381            .into_iter()
382            .filter(|&t| self.contains(t))
383            .fold(EdgeSet::EMPTY, |set, t| set.with(t.reversed()))
384    }
385}
386
387/// What makes a timing-check event occur (LRM 31.5): the resolved form of one
388/// event's `posedge` / `negedge` / `edge` / `edge [descriptors]` / nothing.
389/// Whether a particular `(from, to)` value change is such an event is
390/// `vj::timingcheck::edge_matches`'s job -- the one place that
391/// interprets this.
392#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
393pub enum EdgeControl {
394    /// No edge control: ANY value change of the terminal is the event
395    /// (`from != to`; an `x`<->`z` change counts, it is a change of the
396    /// signal even though no edge descriptor names it).
397    AnyChange,
398    /// Only a change that is one of these transitions is the event.
399    Edges(EdgeSet),
400}