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}