Skip to main content

sv_sim_sim/sva/
sampled.rs

1//! Sampled value functions (sva T16/T17/T18, `docs/superpowers/plans/
2//! 2026-10-03-sva.md`): LRM 1800-2023 16.5.1 (sampling), 16.9.3 (`$sampled`,
3//! `$rose`, `$fell`, `$stable`, `$changed`, `$past`) and 16.9.4 (the
4//! `*_gclk` families). This module is the elaboration-side half: it
5//! recognises a sampled-value call in the AST ([`decode`]), enumerates every
6//! call site of a module body ([`sites_of_block`]) and names the hidden state
7//! the simulator keeps for them ([`hidden_slots`]). `sv-sim-codegen::sampled`
8//! lowers the uses, and `sv-sim-bin` shifts the history at the end of each
9//! time step.
10//!
11//! ## The model, and the one place it deviates from plan 1.3
12//!
13//! Plan 1.3 sketched a whole-arena snapshot ring pushed in a new Preponed
14//! region. That ring is not built, and a whole-arena copy per time step cannot
15//! serve a per-call-site clock anyway: `$past(x, 2, en, @(negedge c))` needs
16//! the value of `x` at the second strictly earlier time step in which
17//! `negedge c iff en` occurred, a different set of steps for every call. The
18//! state kept instead is exactly what the LRM defines (16.9.3):
19//!
20//! - per signal, one hidden **P** cell holding the value the signal had at the
21//!   END OF THE PREVIOUS TIME STEP, which is its Preponed value in the step
22//!   being executed (16.5.1: the sampled value of a variable at time > 0 is
23//!   its value in the Preponed region; at time 0 its default sampled value,
24//!   which is the cell's initial value because a hidden cell is created as a
25//!   copy of its source's initial value);
26//! - per call site and per signal the call reads, a history of **S** cells:
27//!   `S_0` is the signal's sampled value at the most recent strictly earlier
28//!   time step in which the site's clock ticked (and its `iff`/gate held),
29//!   `S_1` the tick before that, and so on up to the call's depth.
30//!
31//! A call is then pure re-evaluation of its argument with its signal reads
32//! redirected: `$sampled(e)` is `e` over P cells; `$rose/$fell/$stable/
33//! $changed(e)` compare `e` over P cells with `e` over S_0 cells (so the
34//! comparison is of the CURRENT time step's sampled value with the one from
35//! the previous tick, 16.9.3); `$past(e, n, ...)` is `e` over S_{n-1} cells.
36//! Re-evaluating the expression (rather than snapshotting its value) is what
37//! makes `$past(b[i])` of a loop's automatic `i` work (16.9.3: the loop
38//! variable's CURRENT value indexes the PAST value of `b`, 16.5.1). Before
39//! `n` ticks have happened an S cell still holds its initial value, which is
40//! the default sampled value the LRM asks for ("the default sampled value of
41//! an expression is the expression evaluated on the default sampled values of
42//! its variables").
43//!
44//! At the end of every time step the engine first shifts the history of every
45//! site whose clock ticked in that step (`S_k <- S_{k-1}`, then `S_0 <- P`),
46//! then copies every signal into its P cell. Whether a site ticked is decided
47//! by a compiled per-site guard evaluated over the P cells (the clocking
48//! event's old values) and the signals' current values (its new ones), before
49//! the P cells are overwritten; see `sv-sim-codegen::sampled`.
50//!
51//! A checker's `always_ff` (LRM 17.5) samples every expression but its event
52//! control: it needs no call site, only the P cell of each signal it reads
53//! ([`checker_ff_sampled_signals`]).
54//!
55//! Hidden cells are real state slots, appended after the clocking-block slots
56//! by BOTH `state::layout::build_template_layout` and
57//! `sv-sim-codegen::module::signal_slots_of`, in the order [`hidden_slots`]
58//! returns them, which is why that enumeration is deterministic (ordered
59//! sets, source order) and shared.
60
61use crate::symid::{SymbolId, SymbolUid};
62use std::collections::BTreeSet;
63use sv_lang::{EdgeKind, Expression, KnownSystemName, SemNode, Symbol, TimingControl};
64use sv_lang_kinds::{ExpressionKind, SymbolKind, TimingControlKind};
65
66/// Why a sampled-value call cannot be handled.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct SampledError(pub String);
69
70impl core::fmt::Display for SampledError {
71    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72        f.write_str(&self.0)
73    }
74}
75
76fn err<T>(msg: impl Into<String>) -> Result<T, SampledError> {
77    Err(SampledError(msg.into()))
78}
79
80/// Which sampled value function a call is.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum SampledOp {
83    Sampled,
84    Rose,
85    Fell,
86    Stable,
87    Changed,
88    Past,
89    /// `$future_gclk` (16.9.4): the sampled value at the NEXT global clock
90    /// tick. Legal only in a property/sequence expression.
91    Future,
92    Rising,
93    Falling,
94    Steady,
95    Changing,
96}
97
98impl SampledOp {
99    /// Whether this function needs the value at a FUTURE tick (16.9.4); it
100    /// has no procedural form.
101    pub fn is_future(self) -> bool {
102        matches!(
103            self,
104            Self::Future | Self::Rising | Self::Falling | Self::Steady | Self::Changing
105        )
106    }
107
108    /// Whether the call uses a clocking event (everything but `$sampled`).
109    pub fn needs_clock(self) -> bool {
110        self != Self::Sampled
111    }
112
113    /// How many past ticks of history the call reads: 0 for `$sampled`, 1 for
114    /// the value-change functions, `ticks` for `$past`.
115    pub fn depth(self, ticks: u32) -> u32 {
116        match self {
117            // The future family reads the next tick ([`n_id`]), no past one.
118            Self::Sampled
119            | Self::Future
120            | Self::Rising
121            | Self::Falling
122            | Self::Steady
123            | Self::Changing => 0,
124            Self::Past => ticks,
125            _ => 1,
126        }
127    }
128}
129
130/// `(op, is the *_gclk spelling)` for a system subroutine, `None` for any
131/// other.
132pub fn op_of(id: KnownSystemName) -> Option<(SampledOp, bool)> {
133    use SampledOp as O;
134    Some(match id {
135        KnownSystemName::Sampled => (O::Sampled, false),
136        KnownSystemName::Rose => (O::Rose, false),
137        KnownSystemName::Fell => (O::Fell, false),
138        KnownSystemName::Stable => (O::Stable, false),
139        KnownSystemName::Changed => (O::Changed, false),
140        KnownSystemName::Past => (O::Past, false),
141        KnownSystemName::PastGclk => (O::Past, true),
142        KnownSystemName::RoseGclk => (O::Rose, true),
143        KnownSystemName::FellGclk => (O::Fell, true),
144        KnownSystemName::StableGclk => (O::Stable, true),
145        KnownSystemName::ChangedGclk => (O::Changed, true),
146        KnownSystemName::FutureGclk => (O::Future, true),
147        KnownSystemName::RisingGclk => (O::Rising, true),
148        KnownSystemName::FallingGclk => (O::Falling, true),
149        KnownSystemName::SteadyGclk => (O::Steady, true),
150        KnownSystemName::ChangingGclk => (O::Changing, true),
151        _ => return None,
152    })
153}
154
155/// A clocking event the simulator can evaluate: one signal event, with an
156/// optional `iff` (LRM 9.4.2). `edge == EdgeKind::None` is the bare
157/// `@(sig)`, any change of the signal's value.
158#[derive(Clone, Copy)]
159pub struct ClockEvent<'d> {
160    pub edge: EdgeKind,
161    pub signal: Expression<'d>,
162    pub iff: Option<Expression<'d>>,
163}
164
165/// The [`ClockEvent`] of a `SignalEvent` timing control. An event list
166/// (`@(a or b)`), a delay or any other control is a named error: a clock the
167/// guard cannot evaluate must not be guessed at.
168pub fn clock_event_of<'d>(tc: TimingControl<'d>) -> Result<ClockEvent<'d>, SampledError> {
169    let ev = assertion_clock_event_of(tc)?;
170    // A sequence instance as the clock (`@seq`, LRM 16.5.2, 9.4.2.4) ticks at
171    // the sequence's end points; only an assertion's own clocking event is
172    // driven that way ([`assertion_clock_event_of`]), so say so by name rather
173    // than fail inside expression lowering.
174    if ev.signal.kind() == ExpressionKind::AssertionInstance {
175        return err(
176            "a sequence instance as a clocking event here (`@seq`, LRM 16.5.2) is only              supported as the clocking event of an assertion"
177                .to_string(),
178        );
179    }
180    Ok(ev)
181}
182
183/// [`clock_event_of`] for the clocking event of an assertion, which may also be
184/// a sequence instance (`@seq [iff cond]`, LRM 16.5.2): `signal` is then the
185/// `AssertionInstance` expression, and the clock ticks where the sequence has
186/// an end point (`crate::sva::endpoint` watches it).
187pub fn assertion_clock_event_of<'d>(tc: TimingControl<'d>) -> Result<ClockEvent<'d>, SampledError> {
188    if tc.kind() != TimingControlKind::SignalEvent {
189        return err(format!(
190            "a sampled value function's clocking event is a {:?} control; only a single \
191             `@(edge signal [iff cond])` event is supported",
192            tc.kind()
193        ));
194    }
195    let children = tc.children();
196    let signal = children
197        .first()
198        .and_then(|c| c.as_expression())
199        .ok_or_else(|| SampledError("a clocking event with no signal expression".into()))?;
200    // `iff` is positional (see `process::cfg::extract_iff`): the second child.
201    let iff = if children.len() == 2 {
202        children[1].as_expression()
203    } else {
204        None
205    };
206    Ok(ClockEvent {
207        edge: tc.edge(),
208        signal,
209        iff,
210    })
211}
212
213/// Whether `tc` is the bare `@$global_clock` event control (16.9.4's
214/// definition of the `*_gclk` functions is written with it).
215pub fn is_global_clock_event(tc: TimingControl<'_>) -> bool {
216    tc.kind() == TimingControlKind::SignalEvent
217        && tc.edge() == EdgeKind::None
218        && tc
219            .children()
220            .first()
221            .and_then(|c| c.as_expression())
222            .is_some_and(|x| {
223                x.kind() == ExpressionKind::Call
224                    && x.is_system_call() == Some(true)
225                    && x.system_subroutine()
226                        .is_some_and(|s| s.known_name_id() == KnownSystemName::GlobalClock)
227            })
228}
229
230/// Whether `tc` is `@$global_clock` or an event list with such a member
231/// (`@($global_clock or posedge rst)`, 14.14). Each member of a list is
232/// resolved to the global clocking's event on its own (`process::cfg`).
233pub fn mentions_global_clock_event(tc: TimingControl<'_>) -> bool {
234    if tc.kind() == TimingControlKind::EventList {
235        return tc
236            .children()
237            .into_iter()
238            .filter_map(|c| c.as_timing_control())
239            .any(mentions_global_clock_event);
240    }
241    is_global_clock_event(tc)
242}
243
244/// Whether `e` is a call of one of the global clocking FUTURE functions
245/// (`$future_gclk` and friends, 16.9.4).
246pub fn is_future_call(e: Expression<'_>) -> bool {
247    e.kind() == ExpressionKind::Call
248        && e.is_system_call() == Some(true)
249        && e.system_subroutine()
250            .and_then(|s| op_of(s.known_name_id()))
251            .is_some_and(|(op, _)| op.is_future())
252}
253
254/// One decoded sampled-value call.
255#[derive(Clone, Copy)]
256pub struct SampledCall<'d> {
257    pub op: SampledOp,
258    /// The `*_gclk` spelling or an explicit `@$global_clock`: the clock is the
259    /// global clocking's event.
260    pub global: bool,
261    /// The whole `Call` expression.
262    pub call: Expression<'d>,
263    /// `expression` / `expression1`.
264    pub arg: Expression<'d>,
265    /// `number_of_ticks` (`$past` only; 1 otherwise).
266    pub ticks: u32,
267    /// `expression2`, the gating expression (`$past` only).
268    pub gate: Option<Expression<'d>>,
269    /// The explicit `clocking_event` argument.
270    pub clock: Option<ClockEvent<'d>>,
271}
272
273fn is_empty_arg(e: &Expression<'_>) -> bool {
274    e.kind() == ExpressionKind::EmptyArgument
275}
276
277/// Decodes `e` when it is a sampled-value call; `Ok(None)` for any other
278/// expression.
279pub fn decode<'d>(e: Expression<'d>) -> Result<Option<SampledCall<'d>>, SampledError> {
280    if e.kind() != ExpressionKind::Call || e.is_system_call() != Some(true) {
281        return Ok(None);
282    }
283    let Some(sub) = e.system_subroutine() else {
284        return Ok(None);
285    };
286    let Some((op, mut global)) = op_of(sub.known_name_id()) else {
287        return Ok(None);
288    };
289    let operands = e.operands();
290    let name = sub.name();
291    let Some(&arg) = operands.first() else {
292        return err(format!("{name} with no argument expression"));
293    };
294    let mut ticks = 1u32;
295    let mut gate = None;
296    let mut clock = None;
297    for (i, o) in operands.iter().enumerate().skip(1) {
298        if o.kind() == ExpressionKind::ClockingEvent {
299            if clock.is_some() {
300                return err(format!("{name} with two clocking events"));
301            }
302            let tc = o
303                .as_sem_node()
304                .children()
305                .into_iter()
306                .find_map(|c| c.as_timing_control())
307                .ok_or_else(|| SampledError(format!("{name}: a clocking event with no control")))?;
308            if is_global_clock_event(tc) {
309                // `$past(v,,,@$global_clock)` is `$past_gclk(v)` (16.9.4).
310                if global {
311                    return err(format!("{name} with two clocking events"));
312                }
313                global = true;
314            } else {
315                clock = Some(clock_event_of(tc)?);
316            }
317        } else if is_empty_arg(o) {
318            // An omitted argument ("$past(in1, , enable)", 16.9.3).
319        } else if op == SampledOp::Past && i == 1 {
320            let n = o
321                .constant_value()
322                .and_then(|cv| cv.as_i64())
323                .ok_or_else(|| {
324                    SampledError(
325                        "$past's number_of_ticks is not an elaboration-time constant".into(),
326                    )
327                })?;
328            if n < 1 {
329                return err(format!(
330                    "$past's number_of_ticks is {n}; it shall be 1 or greater"
331                ));
332            }
333            ticks = u32::try_from(n)
334                .map_err(|_| SampledError(format!("$past's number_of_ticks {n} is too large")))?;
335        } else if op == SampledOp::Past && i == 2 {
336            gate = Some(*o);
337        } else {
338            return err(format!("{name}: unexpected argument {i}"));
339        }
340    }
341    Ok(Some(SampledCall {
342        op,
343        global,
344        call: e,
345        arg,
346        ticks,
347        gate,
348        clock,
349    }))
350}
351
352/// Whether `sym` is storage the simulator keeps in a state slot and that the
353/// sampled value functions can snapshot: a static integral variable or a net.
354/// What `$inferred_disable` stands for in a static checker (LRM 17.4, 16.15):
355/// `None` for a symbol that is no static checker's; else the signal of the
356/// `default disable iff` of the scope instantiating the checker and whether it
357/// is negated, the inner `None` being the `1'b0` of a scope with no default
358/// (16.15 rule c). Reading the declaration needs the design's syntax, which
359/// only `sv-sim-codegen` holds, so it installs the resolver
360/// ([`set_inferred_disable_resolver`]) for the length of a lowering.
361pub type InferredDisable<'d> = Option<Option<(Symbol<'d>, bool)>>;
362
363/// A resolver of [`InferredDisable`] for any symbol of a checker's body.
364pub type InferredDisableResolver = for<'d> fn(&Symbol<'d>) -> Result<InferredDisable<'d>, String>;
365
366thread_local! {
367    static INFERRED_DISABLE_RESOLVER: std::cell::Cell<Option<InferredDisableResolver>> =
368        const { std::cell::Cell::new(None) };
369}
370
371/// Installs (or, with `None`, removes) the [`InferredDisableResolver`],
372/// returning the one it replaces.
373pub fn set_inferred_disable_resolver(
374    f: Option<InferredDisableResolver>,
375) -> Option<InferredDisableResolver> {
376    INFERRED_DISABLE_RESOLVER.with(|c| c.replace(f))
377}
378
379/// The `$inferred_disable` of the static checker `sym` belongs to, per the
380/// installed resolver; an error names why it cannot be known (no resolver, or
381/// a `default disable iff` that is not a signal).
382pub fn resolve_inferred_disable<'d>(sym: &Symbol<'d>) -> Result<InferredDisable<'d>, String> {
383    match INFERRED_DISABLE_RESOLVER.with(std::cell::Cell::get) {
384        Some(f) => f(sym),
385        None => Err("the design's syntax is not available here".into()),
386    }
387}
388
389fn is_inferred_disable_call(e: &Expression<'_>) -> bool {
390    e.kind() == ExpressionKind::Call
391        && e.call_subroutine_name().as_deref() == Some("$inferred_disable")
392}
393
394/// Whether `root`, or a user subroutine it calls (transitively), reads
395/// `$inferred_disable`.
396fn reads_inferred_disable(root: SemNode<'_>) -> bool {
397    let mut visited = BTreeSet::new();
398    let mut work = vec![root];
399    while let Some(node) = work.pop() {
400        let mut found = false;
401        walk(node, &mut |n| {
402            if let Some(e) = n.as_expression() {
403                if is_inferred_disable_call(&e) {
404                    found = true;
405                } else if e.kind() == ExpressionKind::Call
406                    && e.is_system_call() != Some(true)
407                    && let Some(callee) = crate::netlist::itf::call_target(&e)
408                    && visited.insert(callee.uid())
409                    && let Some(body) = crate::process::cfg::proc_body(&callee)
410                {
411                    work.push(body.as_sem_node());
412                }
413            }
414            !found
415        });
416        if found {
417            return true;
418        }
419    }
420    false
421}
422
423/// An automatic variable is deliberately not one (16.5.1: its sampled value is
424/// its current value, also when a past value of it is asked for).
425pub fn sampleable_symbol(sym: &Symbol<'_>) -> bool {
426    let kind = sym.kind();
427    if kind != SymbolKind::Net
428        && !(kind == SymbolKind::Variable
429            && sym.variable_lifetime() == sv_lang::VariableLifetime::Static)
430    {
431        return false;
432    }
433    sym.value_type().is_some_and(|t| t.is_integral())
434}
435
436/// A static unpacked array (of arrays) of integral elements: the memory a
437/// concurrent assertion's sample row holds element by element.
438fn is_integral_memory(sym: &Symbol<'_>) -> bool {
439    if sym.kind() != SymbolKind::Variable && sym.kind() != SymbolKind::Net {
440        return false;
441    }
442    let Some(ty) = sym.value_type() else {
443        return false;
444    };
445    if !ty.is_unpacked_array() || crate::state::storage::is_collection(&ty) {
446        return false;
447    }
448    let mut t = ty;
449    while t.is_unpacked_array() {
450        match t.element_type() {
451            Some(e) => t = e,
452            None => return false,
453        }
454    }
455    t.is_integral()
456}
457
458/// Whether `sym` is a state-slot variable or net of ANY type (the symbols
459/// `sampleable_symbol` accepts, plus the ones whose type is not supported, so
460/// the use-site lowering can say why).
461pub fn is_state_symbol(sym: &Symbol<'_>) -> bool {
462    let kind = sym.kind();
463    kind == SymbolKind::Net
464        || (kind == SymbolKind::Variable
465            && sym.variable_lifetime() == sv_lang::VariableLifetime::Static)
466}
467
468fn walk<'d>(node: SemNode<'d>, f: &mut impl FnMut(SemNode<'d>) -> bool) {
469    let mut stack = vec![node];
470    while let Some(n) = stack.pop() {
471        if f(n) {
472            let mut kids = crate::process::cfg::live_children(&n);
473            kids.reverse();
474            stack.extend(kids);
475        }
476    }
477}
478
479/// The sampleable signals `node` reads, in a deterministic order.
480pub fn signals_in(node: SemNode<'_>) -> BTreeSet<SymbolId> {
481    let mut out = BTreeSet::new();
482    walk(node, &mut |n| {
483        if let Some(e) = n.as_expression()
484            && e.kind() == ExpressionKind::NamedValue
485            && let Some(sym) = e.referenced_symbol()
486            && sampleable_symbol(&sym)
487        {
488            out.insert(sym.uid());
489        }
490        true
491    });
492    out
493}
494
495/// [`signals_in`] for an expression the simulator will actually re-evaluate
496/// over sampled values (a call's argument, gate, clock signal or `iff`): any
497/// shape that would silently read a CURRENT value instead of the sampled one
498/// is an error here rather than a wrong answer later.
499///
500/// - a state symbol of a type with no hidden-cell support (a `real`,
501///   `string`, array, event, collection, handle);
502/// - a hierarchical reference to anything but an integral variable or net;
503/// - a user subroutine call (its body reads module signals this walk cannot
504///   redirect);
505/// - another sampled value function (LRM 16.9.3 allows them nested in
506///   assertion expressions only, and only through the matched/sequence form).
507pub fn strict_signals_in(e: Expression<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
508    // A concurrent assertion's row holds every signal it reads, hierarchical
509    // ones included, so a hierarchical integral signal is sampleable here.
510    strict_signals(e, true, true)
511}
512
513/// [`strict_signals_in`] for a PROCEDURAL call site, where a hierarchical
514/// reference to an integral variable or net is allowed: the signal of another
515/// instance gets this instance's own P/S history (`hidden_slots`).
516pub fn strict_site_signals_in(e: Expression<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
517    strict_signals(e, true, true)
518}
519
520/// [`strict_site_signals_in`] for the enumeration of a block's hidden P/S cells
521/// ([`sites_of_block`]): a memory has no hidden cell, so a procedural call over
522/// one is refused here by name (only a concurrent assertion's rows hold memories).
523fn strict_hidden_signals_in(e: Expression<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
524    strict_signals(e, true, false)
525}
526
527fn strict_signals(
528    e: Expression<'_>,
529    allow_hier: bool,
530    allow_mem: bool,
531) -> Result<BTreeSet<SymbolId>, SampledError> {
532    let mut failure = None;
533    let mut hier = BTreeSet::new();
534    walk(e.as_sem_node(), &mut |n| {
535        if failure.is_some() {
536            return false;
537        }
538        let Some(x) = n.as_expression() else {
539            return true;
540        };
541        // Only these three kinds are inspected; every other expression kind
542        // is fine to re-evaluate over sampled cells, so this is a kind test,
543        // not a match with a catch-all arm.
544        let kind = x.kind();
545        if kind == ExpressionKind::HierarchicalValue {
546            // A signal of another instance: this instance keeps its own P/S
547            // history of it (`hidden_slots`, `foreign_args`).
548            match x.referenced_symbol() {
549                Some(sym) if allow_hier && sampleable_symbol(&sym) => {
550                    hier.insert(sym.uid());
551                }
552                _ => {
553                    failure = Some(SampledError(
554                        "a hierarchical reference inside a sampled value function's expression \
555                         is not supported unless it names an integral variable or net"
556                            .into(),
557                    ));
558                }
559            }
560        } else if kind == ExpressionKind::NamedValue {
561            if let Some(sym) = x.referenced_symbol()
562                && is_state_symbol(&sym)
563                && !sampleable_symbol(&sym)
564            {
565                // A concurrent assertion's row holds every element of an
566                // unpacked array of integral elements (16.5.1 samples them
567                // all), with the same per-tick history as any other signal.
568                if allow_mem && is_integral_memory(&sym) {
569                    hier.insert(sym.uid());
570                    return true;
571                }
572                failure = Some(SampledError(format!(
573                    "`{}` inside a sampled value function's expression has a type \
574                     (real/string/array/event/collection/handle) whose sampled value is not \
575                     supported",
576                    sym.name()
577                )));
578            }
579        } else if kind == ExpressionKind::Call {
580            match decode(x) {
581                Ok(Some(_)) => {
582                    failure = Some(SampledError(
583                        "a sampled value function nested inside the expression of another is \
584                         not supported"
585                            .into(),
586                    ));
587                }
588                Ok(None) => {
589                    if x.is_system_call() == Some(false) {
590                        failure = Some(SampledError(
591                            "a user subroutine call inside a sampled value function's expression \
592                             is not supported"
593                                .into(),
594                        ));
595                    }
596                }
597                Err(e) => failure = Some(e),
598            }
599        }
600        failure.is_none()
601    });
602    match failure {
603        Some(e) => Err(e),
604        None => {
605            let mut out = signals_in(e.as_sem_node());
606            out.extend(hier);
607            Ok(out)
608        }
609    }
610}
611
612/// Whether `block` is an `always_ff` of a checker (LRM 17.5), whose
613/// expressions are all sampled.
614pub fn is_checker_always_ff(block: &Symbol<'_>) -> bool {
615    if block.kind() != SymbolKind::ProceduralBlock
616        || block.procedural_block_procedure_kind() != sv_lang::ProceduralBlockKind::AlwaysFF
617    {
618        return false;
619    }
620    let mut scope = block.parent();
621    while let Some(s) = scope {
622        let kind = s.kind();
623        if kind == SymbolKind::CheckerInstanceBody {
624            return true;
625        }
626        if kind == SymbolKind::InstanceBody {
627            return false;
628        }
629        scope = s.parent();
630    }
631    false
632}
633
634/// The signals a checker `always_ff` reads at their SAMPLED value (LRM 17.5:
635/// "except for the variables used in the event control, all other expressions
636/// in always_ff procedures are sampled"), empty for any other block. The
637/// block's statements read each of them through its P cell
638/// (`sv-sim-codegen::process_lower::prime_external_signals`), so every one
639/// gets a P cell ([`hidden_slots`]).
640///
641/// The signals of the event control keep their current value, and the
642/// properties of a concurrent assertion are the assertion engine's (it
643/// samples them itself). A read the P cells cannot serve is an error, not a
644/// silent current-value read: a hierarchical reference, or a state symbol of
645/// a type with no hidden-cell support.
646pub fn checker_ff_sampled_signals(block: &Symbol<'_>) -> Result<BTreeSet<SymbolId>, SampledError> {
647    if !is_checker_always_ff(block) {
648        return Ok(BTreeSet::new());
649    }
650    let Some(body) = crate::process::cfg::proc_body(block) else {
651        return Ok(BTreeSet::new());
652    };
653    let mut event = BTreeSet::new();
654    let mut read = BTreeSet::new();
655    let mut failure = None;
656    walk(body.as_sem_node(), &mut |n| {
657        if failure.is_some() {
658            return false;
659        }
660        if n.as_assertion_expr().is_some() {
661            return false;
662        }
663        if let Some(tc) = n.as_timing_control() {
664            event.extend(signals_in(tc.as_sem_node()));
665            return false;
666        }
667        if let Some(e) = n.as_expression() {
668            let kind = e.kind();
669            if kind == ExpressionKind::Assignment && e.is_nonblocking() != Some(true) {
670                failure = Some(SampledError(
671                    "a blocking assignment in a checker's `always_ff` (LRM 17.5 allows \
672                     nonblocking assignments only there)"
673                        .into(),
674                ));
675            } else if kind == ExpressionKind::HierarchicalValue {
676                failure = Some(SampledError(
677                    "a hierarchical reference inside a checker's `always_ff` cannot be \
678                     sampled (LRM 17.5): its P cell lives in another instance"
679                        .into(),
680                ));
681            } else if kind == ExpressionKind::NamedValue
682                && let Some(sym) = e.referenced_symbol()
683            {
684                if sampleable_symbol(&sym) {
685                    read.insert(sym.uid());
686                } else if is_state_symbol(&sym) {
687                    failure = Some(SampledError(format!(
688                        "`{}` in a checker's `always_ff` has a type \
689                         (real/string/array/event/collection/handle) whose sampled value (LRM \
690                         17.5) is not supported",
691                        sym.name()
692                    )));
693                }
694            }
695        }
696        failure.is_none()
697    });
698    if let Some(e) = failure {
699        return Err(e);
700    }
701    let mut sampled = &read - &event;
702    // 17.4, 17.5: `$inferred_disable` (here or in a function the block calls)
703    // is the SAMPLED value of the instantiating scope's `default disable iff`
704    // signal, even where that signal is also in the event control.
705    if reads_inferred_disable(body.as_sem_node()) {
706        match resolve_inferred_disable(block) {
707            Ok(Some(Some((sig, _)))) if sampleable_symbol(&sig) => {
708                sampled.insert(sig.uid());
709            }
710            Ok(Some(None)) => {}
711            Ok(Some(Some((sig, _)))) => {
712                return err(format!(
713                    "`$inferred_disable` in a checker's `always_ff` stands for `{}`, whose type \
714                     has no sampled value (LRM 17.5)",
715                    sig.name()
716                ));
717            }
718            Ok(None) => {
719                return err(
720                    "`$inferred_disable` in an `always_ff` of a checker that is not a static \
721                     instance has no sampled `default disable iff` (LRM 17.4)",
722                );
723            }
724            Err(why) => {
725                return err(format!(
726                    "`$inferred_disable` in a checker's `always_ff`: {why}"
727                ));
728            }
729        }
730    }
731    Ok(sampled)
732}
733
734/// The clocking event of `block` (written in its body, or the event of a
735/// clocking block of its scope) that is the same source text as `ev`, which
736/// was inferred for the same procedure of another instance of the module
737/// (slang's analysis tracks one instance of each set of identical ones).
738/// Both instances were elaborated from one syntax node, so the clock signal's
739/// source span identifies the event.
740pub fn relocate_clock<'d>(
741    block: &Symbol<'d>,
742    ev: &ClockEvent<'_>,
743) -> Result<ClockEvent<'d>, SampledError> {
744    let want = ev.signal.source_range();
745    let mut found = None;
746    let mut look = |root: SemNode<'d>| {
747        walk(root, &mut |n| {
748            if found.is_none()
749                && let Some(tc) = n.as_timing_control()
750                && tc.kind() == TimingControlKind::SignalEvent
751                && let Ok(cand) = clock_event_of(tc)
752                && cand.edge == ev.edge
753                && cand.signal.source_range() == want
754            {
755                found = Some(cand);
756            }
757            found.is_none()
758        });
759    };
760    if let Some(body) = crate::process::cfg::proc_body(block) {
761        look(body.as_sem_node());
762    }
763    if let Some(scope) = block.parent() {
764        for cb in crate::netlist::module_items(&scope)
765            .into_iter()
766            .filter(|m| m.kind() == SymbolKind::ClockingBlock)
767        {
768            if let Some(cev) = cb.clocking_block_event() {
769                look(cev);
770            }
771        }
772    }
773    found.ok_or_else(|| {
774        SampledError(
775            "the inferred clocking event of another instance of this module has no counterpart \
776             in this instance"
777                .into(),
778        )
779    })
780}
781
782/// The timing control written in `block`'s body that is the same source text as
783/// `tc` (an event inferred for `block`, or for the same procedure of another
784/// instance): a `SignalEvent` of the same edge, signal text and `iff` presence.
785/// `None` when the body has no such control (an inferred default clocking event,
786/// which lives outside the block).
787pub fn relocate_clock_node<'d>(block: &Symbol<'d>, tc: &TimingControl<'_>) -> Option<SemNode<'d>> {
788    if tc.kind() != TimingControlKind::SignalEvent {
789        return None;
790    }
791    let kids = tc.children();
792    let want = kids.first()?.as_expression()?.source_range();
793    let edge = tc.edge();
794    let arity = kids.len();
795    let mut found = None;
796    walk(
797        crate::process::cfg::proc_body(block)?.as_sem_node(),
798        &mut |n| {
799            if found.is_none()
800                && let Some(cand) = n.as_timing_control()
801                && cand.kind() == TimingControlKind::SignalEvent
802                && cand.edge() == edge
803                && cand.children().len() == arity
804                && cand
805                    .children()
806                    .first()
807                    .and_then(|c| c.as_expression())
808                    .is_some_and(|e| e.source_range() == want)
809            {
810                found = Some(cand.as_sem_node());
811            }
812            found.is_none()
813        },
814    );
815    found
816}
817
818/// One call site, with the hidden state it needs.
819pub struct Site<'d> {
820    pub call: SampledCall<'d>,
821    /// The signals the call's argument reads (each gets an S history).
822    pub arg_signals: BTreeSet<SymbolId>,
823    /// Every signal that needs a P cell for this site: the argument's, the
824    /// gate's and the clock candidates'.
825    pub p_signals: BTreeSet<SymbolId>,
826    /// The signals among those named by a hierarchical reference to ANOTHER
827    /// instance (LRM 23.6), with their cell shapes: this instance keeps its
828    /// own history of each ([`ForeignShape::required`]).
829    pub foreign: std::collections::BTreeMap<SymbolId, ForeignShape>,
830}
831
832/// A site's identity within its owning process: `{buffer}.{offset}` of the
833/// call expression's first token. Two generate-loop copies of one block share
834/// it, which is why every derived id also carries the owner's uid.
835pub fn site_loc(call: &Expression<'_>) -> String {
836    let start = call.source_range().start;
837    format!("{}.{}", start.buffer, start.offset)
838}
839
840/// Every sampled-value call under `block`'s body, in source order, each with
841/// the signals it needs hidden cells for. A sampled call nested in another
842/// one's argument, or one with no usable clock candidate, is an error. The calls
843/// inside the property of a concurrent assertion are not sites (see above).
844pub fn sites_of_block<'d>(block: &Symbol<'d>) -> Result<Vec<Site<'d>>, SampledError> {
845    let Some(body) = crate::process::cfg::proc_body(block) else {
846        return Ok(Vec::new());
847    };
848    let mut calls = Vec::new();
849    let mut failure = None;
850    walk(body.as_sem_node(), &mut |n| {
851        if failure.is_some() {
852            return false;
853        }
854        // The expressions of an assertion's property are not the block's own:
855        // the assertion engine evaluates them over its position rows
856        // (`sv-sim-codegen`'s `sva::emit`, which finds those calls itself), so
857        // they get no hidden P/S cells, and no tick guard that could not infer a
858        // clock for a static assertion's wrapper block. The action blocks are
859        // statements, not assertion expressions: their calls stay sites.
860        if n.as_assertion_expr().is_some() {
861            return false;
862        }
863        if let Some(e) = n.as_expression() {
864            match decode(e) {
865                Ok(Some(c)) => {
866                    calls.push(c);
867                    // A nested sampled call is checked below, per site.
868                    return false;
869                }
870                Ok(None) => {}
871                Err(e) => {
872                    failure = Some(e);
873                    return false;
874                }
875            }
876        }
877        true
878    });
879    if let Some(e) = failure {
880        return Err(e);
881    }
882    let mut sites = Vec::new();
883    let own_body = crate::netlist::enclosing_instance_body(block).map(|b| b.uid());
884    for call in calls {
885        let arg_signals = strict_hidden_signals_in(call.arg)?;
886        let mut p_signals = arg_signals.clone();
887        let mut roots = vec![call.arg];
888        if let Some(g) = call.gate {
889            p_signals.extend(strict_hidden_signals_in(g)?);
890            roots.push(g);
891        }
892        if call.op.needs_clock() {
893            match call.clock {
894                Some(ev) => {
895                    p_signals.extend(strict_hidden_signals_in(ev.signal)?);
896                    roots.push(ev.signal);
897                    if let Some(iff) = ev.iff {
898                        p_signals.extend(strict_hidden_signals_in(iff)?);
899                        roots.push(iff);
900                    }
901                }
902                None => {
903                    // The clock is inferred (16.14.6 / the global or default
904                    // clocking), resolved by `sv-sim-codegen` with the full
905                    // analysis, which this crate-level enumeration cannot
906                    // see. Over-approximate its signals: every signal event
907                    // of the process, and every clocking block's event of
908                    // the module (a default or global clocking is one).
909                    walk(body.as_sem_node(), &mut |n| {
910                        if let Some(tc) = n.as_timing_control()
911                            && tc.kind() == TimingControlKind::SignalEvent
912                        {
913                            p_signals.extend(signals_in(tc.as_sem_node()));
914                        }
915                        true
916                    });
917                    if let Some(scope) = block.parent() {
918                        for cb in crate::netlist::module_items(&scope)
919                            .into_iter()
920                            .filter(|m| m.kind() == SymbolKind::ClockingBlock)
921                        {
922                            if let Some(ev) = cb.clocking_block_event() {
923                                p_signals.extend(signals_in(ev));
924                            }
925                        }
926                    }
927                }
928            }
929        }
930        // The signals of other instances (hierarchical references): their
931        // cells have no local slot to clone.
932        let mut foreign = std::collections::BTreeMap::new();
933        for root in roots {
934            walk(root.as_sem_node(), &mut |n| {
935                if let Some(x) = n.as_expression()
936                    && x.kind() == ExpressionKind::HierarchicalValue
937                    && let Some(sym) = x.referenced_symbol()
938                    && sampleable_symbol(&sym)
939                    && crate::netlist::enclosing_instance_body(&sym).map(|b| b.uid()) != own_body
940                    && let Some(shape) = foreign_shape(&sym)
941                {
942                    foreign.insert(sym.uid(), shape);
943                }
944                true
945            });
946        }
947        sites.push(Site {
948            call,
949            arg_signals,
950            p_signals,
951            foreign,
952        });
953    }
954    Ok(sites)
955}
956
957/// The id of signal `sig`'s hidden P cell (its value at the end of the
958/// previous time step).
959pub fn p_id(sig: &SymbolId) -> SymbolId {
960    sig.derive("p")
961}
962
963/// The id of the cell holding `sig`'s sampled value at the NEXT tick of the
964/// global clock (16.9.4), which an assertion's position row carries.
965pub fn n_id(sig: &SymbolId) -> SymbolId {
966    sig.derive("n")
967}
968
969/// The id of the S_`k` cell of `sig` for the call at `loc` in `owner`.
970pub fn s_id(owner: &SymbolId, loc: &str, sig: &SymbolId, k: u32) -> SymbolId {
971    owner.derive(&format!("s{loc}@{}#{k}", sig.key_string()))
972}
973
974/// FNV-1a: a stable (run-independent) hash for generated symbol names.
975fn fnv1a(s: &str) -> u64 {
976    let mut h = 0xcbf2_9ce4_8422_2325u64;
977    for b in s.bytes() {
978        h ^= u64::from(b);
979        h = h.wrapping_mul(0x0100_0000_01b3);
980    }
981    h
982}
983
984/// The name of the compiled tick guard of the call at `loc` in `owner`.
985/// `sv-sim-codegen` emits a function of this name and `sv-sim-bin` looks it
986/// up by it, so the two never exchange a table.
987pub fn guard_name(owner: &SymbolId, loc: &str) -> String {
988    format!(
989        "sv_sim_sva_guard_{:016x}",
990        fnv1a(&format!("{}@{loc}", owner.key_string()))
991    )
992}
993
994/// One hidden cell: a clone of `source`'s slot (class, width, initial value).
995#[derive(Debug, Clone, PartialEq)]
996pub struct HiddenSlot {
997    pub id: SymbolId,
998    pub source: SymbolId,
999    /// Set when `source` is NOT a signal of this body but of an enclosing
1000    /// instance (the event signal of a global clocking declared there, LRM
1001    /// 14.14): the cell then has no local slot to clone, so it carries the
1002    /// shape to build instead.
1003    pub foreign: Option<ForeignShape>,
1004}
1005
1006/// The shape of a hidden cell whose source lives in another instance.
1007#[derive(Debug, Clone, PartialEq)]
1008pub struct ForeignShape {
1009    pub width: u32,
1010    pub signed: bool,
1011    /// The source's initial value (its declared initializer, else 0 for a
1012    /// 2-state type); `None` is all-X.
1013    pub initial: Option<crate::state::FourStateValue>,
1014    /// The source is named by the code itself (a hierarchical reference), so
1015    /// it MUST be a foreign slot of the instance: a missing one is an error.
1016    /// False for the candidate event signals of enclosing clocking blocks,
1017    /// of which only the one the lookup picks is registered.
1018    pub required: bool,
1019}
1020
1021/// `from <- to` copy of one site's history shift.
1022#[derive(Debug, Clone, PartialEq, Eq)]
1023pub struct ShiftStep {
1024    pub from: SymbolId,
1025    pub to: SymbolId,
1026}
1027
1028/// A clocked call site's end-of-step work.
1029#[derive(Debug, Clone, PartialEq, Eq)]
1030pub struct SiteShift {
1031    /// The compiled guard (`i8 f(handle)`, 1 when the site ticked).
1032    pub guard_name: String,
1033    /// Executed in order when the guard returns 1: `S_{d-1} <- S_{d-2}`, ...,
1034    /// `S_1 <- S_0`, then `S_0 <- P`, per signal.
1035    pub shifts: Vec<ShiftStep>,
1036}
1037
1038/// Everything hidden a module body needs.
1039#[derive(Debug, Clone, Default, PartialEq)]
1040pub struct Hidden {
1041    /// P cells, in `SymbolId` order.
1042    pub p: Vec<HiddenSlot>,
1043    /// S cells, in site order x signal order x k.
1044    pub s: Vec<HiddenSlot>,
1045    /// One entry per clocked site, in site order.
1046    pub sites: Vec<SiteShift>,
1047}
1048
1049impl Hidden {
1050    /// P cells then S cells: the order they are appended after the other
1051    /// slots of a template.
1052    pub fn slots(&self) -> impl Iterator<Item = &HiddenSlot> {
1053        self.p.iter().chain(self.s.iter())
1054    }
1055
1056    pub fn is_empty(&self) -> bool {
1057        self.p.is_empty() && self.s.is_empty()
1058    }
1059}
1060
1061/// The event signals of every clocking block declared in an instance ENCLOSING
1062/// `body` (any of them may be the global clocking LRM 14.14's lookup finds;
1063/// `sv-sim-bin` copies only those the lookup picks), with their cell shapes.
1064fn enclosing_clock_signals(body: &Symbol<'_>) -> Vec<(SymbolId, ForeignShape)> {
1065    let mut out = Vec::new();
1066    let mut cur = *body;
1067    while let Some(parent) = super::global_clock::parent_body(&cur) {
1068        for cb in crate::netlist::module_items(&parent)
1069            .into_iter()
1070            .filter(|m| m.kind() == SymbolKind::ClockingBlock)
1071        {
1072            let Some(ev) = cb.clocking_block_event() else {
1073                continue;
1074            };
1075            let mut syms = Vec::new();
1076            walk(ev, &mut |n| {
1077                if let Some(e) = n.as_expression()
1078                    && e.kind() == ExpressionKind::NamedValue
1079                    && let Some(sym) = e.referenced_symbol()
1080                    && sampleable_symbol(&sym)
1081                {
1082                    syms.push(sym);
1083                }
1084                true
1085            });
1086            for sym in syms {
1087                if let Some(shape) = foreign_shape(&sym) {
1088                    out.push((sym.uid(), shape));
1089                }
1090            }
1091        }
1092        cur = parent;
1093    }
1094    out
1095}
1096
1097/// The cell shape of the integral variable or net `sym`.
1098fn foreign_shape(sym: &Symbol<'_>) -> Option<ForeignShape> {
1099    use crate::state::{FourStateValue, value};
1100    let ty = sym.value_type()?;
1101    let width = u32::try_from(ty.bit_width()).ok().filter(|w| *w > 0)?;
1102    let signed = ty.is_signed();
1103    let initial = sym
1104        .initializer()
1105        .and_then(|init| init.constant_value())
1106        .and_then(|cv| value::four_state_of_constant(&cv))
1107        .and_then(|fv| {
1108            value::owned_svint_to_four_state(&value::four_state_to_owned_svint(&fv, width, signed))
1109        })
1110        .or_else(|| {
1111            (!ty.is_four_state()).then(|| {
1112                if width <= 64 {
1113                    FourStateValue::Narrow(0)
1114                } else {
1115                    let words = width.div_ceil(64) as usize;
1116                    FourStateValue::Wide {
1117                        aval: vec![0; words],
1118                        bval: vec![0; words],
1119                    }
1120                }
1121            })
1122        });
1123    Some(ForeignShape {
1124        width,
1125        signed,
1126        initial,
1127        required: false,
1128    })
1129}
1130
1131/// The hidden cells of every procedural block of `body` (the enumeration both
1132/// slot builders append, in this order).
1133pub fn hidden_slots(body: &Symbol<'_>) -> Result<Hidden, SampledError> {
1134    let mut p: BTreeSet<SymbolId> = BTreeSet::new();
1135    let mut foreign: std::collections::BTreeMap<SymbolId, ForeignShape> = Default::default();
1136    let mut out = Hidden::default();
1137    let mut seen = BTreeSet::new();
1138    for item in crate::netlist::module_items(body) {
1139        // A module-level subroutine owns the sites of its own body: its value
1140        // history depends only on the clock (16.9.3), never on the caller, so
1141        // every call shares it (`SymbolKind::Subroutine`).
1142        if !matches!(
1143            item.kind(),
1144            SymbolKind::ProceduralBlock | SymbolKind::Subroutine
1145        ) {
1146            continue;
1147        }
1148        let owner = item.uid();
1149        // A checker `always_ff` reads its signals through their P cells.
1150        if item.kind() == SymbolKind::ProceduralBlock {
1151            p.extend(checker_ff_sampled_signals(&item)?);
1152        }
1153        for site in sites_of_block(&item)? {
1154            let loc = site_loc(&site.call.call);
1155            if !seen.insert((owner.clone(), loc.clone())) {
1156                return err(format!(
1157                    "two sampled value calls share one source position ({loc}); a macro that \
1158                     expands to several of them is not supported"
1159                ));
1160            }
1161            p.extend(site.p_signals.iter().cloned());
1162            for (id, shape) in &site.foreign {
1163                foreign.insert(
1164                    id.clone(),
1165                    ForeignShape {
1166                        required: true,
1167                        ..shape.clone()
1168                    },
1169                );
1170            }
1171            // `$past_gclk` & co. whose global clocking may be an enclosing
1172            // instance's (14.14): this instance keeps the history of that
1173            // clock's event signals itself, a P cell per candidate that a
1174            // copy from the instance owning the signal keeps current.
1175            if site.call.global && site.call.clock.is_none() && !site.call.op.is_future() {
1176                for (id, shape) in enclosing_clock_signals(body) {
1177                    p.insert(id.clone());
1178                    foreign.insert(id, shape);
1179                }
1180            }
1181            let depth = site.call.op.depth(site.call.ticks);
1182            if depth == 0 || site.call.op.is_future() {
1183                continue;
1184            }
1185            let mut shifts = Vec::new();
1186            for sig in &site.arg_signals {
1187                for k in 0..depth {
1188                    out.s.push(HiddenSlot {
1189                        id: s_id(&owner, &loc, sig, k),
1190                        source: sig.clone(),
1191                        foreign: site.foreign.get(sig).cloned(),
1192                    });
1193                }
1194                for k in (1..depth).rev() {
1195                    shifts.push(ShiftStep {
1196                        from: s_id(&owner, &loc, sig, k - 1),
1197                        to: s_id(&owner, &loc, sig, k),
1198                    });
1199                }
1200                shifts.push(ShiftStep {
1201                    from: p_id(sig),
1202                    to: s_id(&owner, &loc, sig, 0),
1203                });
1204            }
1205            out.sites.push(SiteShift {
1206                guard_name: guard_name(&owner, &loc),
1207                shifts,
1208            });
1209        }
1210    }
1211    out.p = p
1212        .into_iter()
1213        .map(|sig| HiddenSlot {
1214            id: p_id(&sig),
1215            foreign: foreign.get(&sig).cloned(),
1216            source: sig,
1217        })
1218        .collect();
1219    Ok(out)
1220}
1221
1222/// The hidden cells one call READS at its use site, as `(signal, cell)` pairs:
1223/// the argument is evaluated once over `cur` and, for the value-change
1224/// functions, once more over `prev`. `sv-sim-codegen` primes exactly these and
1225/// lowers the call from them, so the two agree by construction.
1226#[derive(Debug, Clone, Default, PartialEq, Eq)]
1227pub struct UseViews {
1228    pub cur: Vec<(SymbolId, SymbolId)>,
1229    pub prev: Vec<(SymbolId, SymbolId)>,
1230    /// The future functions' value at the next global clock tick.
1231    pub next: Vec<(SymbolId, SymbolId)>,
1232}
1233
1234/// The [`UseViews`] of `call` (whose argument reads `arg_signals`) in the
1235/// procedural block `owner`.
1236pub fn use_views(
1237    owner: &SymbolId,
1238    call: &SampledCall<'_>,
1239    arg_signals: &BTreeSet<SymbolId>,
1240) -> UseViews {
1241    let loc = site_loc(&call.call);
1242    let mut v = UseViews::default();
1243    for sig in arg_signals {
1244        match call.op {
1245            SampledOp::Sampled => v.cur.push((sig.clone(), p_id(sig))),
1246            SampledOp::Rose | SampledOp::Fell | SampledOp::Stable | SampledOp::Changed => {
1247                v.cur.push((sig.clone(), p_id(sig)));
1248                v.prev.push((sig.clone(), s_id(owner, &loc, sig, 0)));
1249            }
1250            SampledOp::Past => v
1251                .cur
1252                .push((sig.clone(), s_id(owner, &loc, sig, call.ticks - 1))),
1253            // Only in an assertion, whose position row holds the next tick's
1254            // values (16.9.4); anywhere else the use-site lowering rejects them.
1255            SampledOp::Future
1256            | SampledOp::Rising
1257            | SampledOp::Falling
1258            | SampledOp::Steady
1259            | SampledOp::Changing => {
1260                v.cur.push((sig.clone(), p_id(sig)));
1261                v.next.push((sig.clone(), n_id(sig)));
1262            }
1263        }
1264    }
1265    v
1266}
1267
1268/// A pure executable specification of the semantics in the module doc, on a
1269/// per-time-step trace of ONE 4-state scalar (`b'0'`, `b'1'`, `b'x'`,
1270/// `b'z'`). The simulator's own tests compare against it, and its unit tests
1271/// pin the LRM's Figure 16-3 and `$past` statements.
1272#[cfg(test)]
1273pub(crate) mod model {
1274    /// `preponed[t]` is the expression's sampled (Preponed) value in time
1275    /// step `t`; `tick[t]` whether the (gated) clocking event occurred in it.
1276    pub struct Trace<'a> {
1277        pub preponed: &'a [u8],
1278        pub tick: &'a [bool],
1279        /// The default sampled value (16.5.1).
1280        pub init: u8,
1281    }
1282
1283    impl Trace<'_> {
1284        /// `$sampled` in time step `t`.
1285        pub fn sampled(&self, t: usize) -> u8 {
1286            self.preponed[t]
1287        }
1288
1289        /// The sampled value `k` ticks strictly before time step `t`
1290        /// (`k >= 1`): `$past(e, k)`, or the default when fewer exist.
1291        pub fn past(&self, t: usize, k: usize) -> u8 {
1292            let ticks: Vec<usize> = (0..t).filter(|&s| self.tick[s]).collect();
1293            if ticks.len() >= k {
1294                self.preponed[ticks[ticks.len() - k]]
1295            } else {
1296                self.init
1297            }
1298        }
1299
1300        pub fn rose(&self, t: usize) -> bool {
1301            self.sampled(t) == b'1' && self.past(t, 1) != b'1'
1302        }
1303
1304        pub fn fell(&self, t: usize) -> bool {
1305            self.sampled(t) == b'0' && self.past(t, 1) != b'0'
1306        }
1307
1308        pub fn stable(&self, t: usize) -> bool {
1309            self.sampled(t) == self.past(t, 1)
1310        }
1311
1312        pub fn changed(&self, t: usize) -> bool {
1313            !self.stable(t)
1314        }
1315    }
1316
1317    #[cfg(test)]
1318    mod tests {
1319        use super::*;
1320
1321        fn bits(s: &str) -> Vec<u8> {
1322            s.bytes().collect()
1323        }
1324
1325        /// Figure 16-3: one clock tick per time step, `e1 = $rose(req)`
1326        /// occurs at tick 3 ("the value of req at clock tick 2 was low and
1327        /// the value at clock tick 3 is high") and `e2 = $fell(ack)` at tick 6
1328        /// ("ack was sampled as high at clock tick 5 and sampled as low at
1329        /// clock tick 6"). Index 0 is tick 1.
1330        #[test]
1331        fn figure_16_3_value_change_expressions() {
1332            let req = bits("00110001110000");
1333            let ack = bits("00001000110000");
1334            let tick = vec![true; 14];
1335            let rq = Trace {
1336                preponed: &req,
1337                tick: &tick,
1338                init: b'0',
1339            };
1340            let ak = Trace {
1341                preponed: &ack,
1342                tick: &tick,
1343                init: b'0',
1344            };
1345            let e1: Vec<usize> = (0..14).filter(|&t| rq.rose(t)).map(|t| t + 1).collect();
1346            let e2: Vec<usize> = (0..14).filter(|&t| ak.fell(t)).map(|t| t + 1).collect();
1347            assert_eq!(e1, [3, 8]);
1348            assert_eq!(e2, [6, 11]);
1349            assert!(e1.contains(&3) && e2.contains(&6));
1350        }
1351
1352        /// 16.9.3: the clock ticks only in some time steps, so the previous
1353        /// value is the one at the previous TICK, not the previous step.
1354        #[test]
1355        fn value_change_compares_with_the_previous_tick_not_the_previous_step() {
1356            // x: 0 0 1 1 0 ; ticks at steps 0, 3, 4
1357            let x = bits("00110");
1358            let tick = [true, false, false, true, true];
1359            let t = Trace {
1360                preponed: &x,
1361                tick: &tick,
1362                init: b'0',
1363            };
1364            // step 3: x=1 vs the value at step 0 (0): rose.
1365            assert!(t.rose(3));
1366            // step 4: x=0 vs the value at step 3 (1): fell, changed.
1367            assert!(t.fell(4) && t.changed(4));
1368            // step 2 (no tick): still compares with the tick at step 0.
1369            assert!(t.rose(2));
1370        }
1371
1372        /// 16.9.3: before the first tick the previous value is the default
1373        /// sampled value (X for a `logic`), so `$rose` of a first-seen 1 and
1374        /// `$fell` of a first-seen 0 are both true, `$stable` of X is true.
1375        #[test]
1376        fn before_the_first_tick_the_default_sampled_value_is_used() {
1377            let x = bits("1x0");
1378            let tick = [true, true, true];
1379            let t = Trace {
1380                preponed: &x,
1381                tick: &tick,
1382                init: b'x',
1383            };
1384            assert!(t.rose(0));
1385            assert!(!t.stable(0));
1386            // x vs 1: not stable, neither rose nor fell (the LSB is x).
1387            assert!(!t.rose(1) && !t.fell(1) && t.changed(1));
1388            assert!(t.fell(2));
1389        }
1390
1391        /// `$past(e, k)` is the value at the k-th strictly earlier tick; the
1392        /// gate removes ticks (`posedge clk iff enable`, 16.9.3's `$past(q,
1393        /// 2, enable)`).
1394        #[test]
1395        fn past_counts_gated_ticks() {
1396            let q = bits("0101100101");
1397            // The gate is open at steps 1, 2, 4, 7.
1398            let tick = [
1399                false, true, true, false, true, false, false, true, false, false,
1400            ];
1401            let t = Trace {
1402                preponed: &q,
1403                tick: &tick,
1404                init: b'x',
1405            };
1406            // At step 9, the ticks before it are 1, 2, 4, 7: past 1 = q[7],
1407            // past 2 = q[4], past 4 = q[1]; past 5 does not exist.
1408            assert_eq!(t.past(9, 1), q[7]);
1409            assert_eq!(t.past(9, 2), q[4]);
1410            assert_eq!(t.past(9, 4), q[1]);
1411            assert_eq!(t.past(9, 5), b'x');
1412            // At step 4 the tick at step 4 itself is not strictly prior.
1413            assert_eq!(t.past(4, 1), q[2]);
1414            assert_eq!(t.past(4, 3), b'x');
1415        }
1416    }
1417}
1418
1419#[cfg(test)]
1420mod tests {
1421    use super::*;
1422    use crate::state::compile_source;
1423
1424    fn hidden_of(src: &str) -> Result<Hidden, SampledError> {
1425        let design = compile_source(src).expect("valid SV source compiles");
1426        let body = design
1427            .top_instances()
1428            .next()
1429            .expect("one top instance")
1430            .instance_body()
1431            .expect("instance has a body");
1432        hidden_slots(&body)
1433    }
1434
1435    fn names(slots: &[HiddenSlot]) -> Vec<String> {
1436        slots.iter().map(|s| s.id.path().to_owned()).collect()
1437    }
1438
1439    /// `$rose(b)` needs a P cell for `b` and the clock `clk` (the inferred
1440    /// clock's candidates), plus one S_0 cell and one site for `b`.
1441    #[test]
1442    fn rose_gets_p_cells_for_arg_and_clock_and_one_s_cell() {
1443        let h = hidden_of(
1444            "module top; logic clk, b, r;\n\
1445             always @(posedge clk) r <= $rose(b);\nendmodule\n",
1446        )
1447        .unwrap();
1448        assert_eq!(names(&h.p), ["top.b$p", "top.clk$p"]);
1449        assert_eq!(h.s.len(), 1);
1450        assert_eq!(h.s[0].source.path(), "top.b");
1451        assert_eq!(h.sites.len(), 1);
1452        // The shift is P -> S_0 only.
1453        assert_eq!(h.sites[0].shifts.len(), 1);
1454        assert_eq!(h.sites[0].shifts[0].from, p_id(&h.s[0].source));
1455        assert_eq!(h.sites[0].shifts[0].to, h.s[0].id);
1456    }
1457
1458    /// `$past(x, 3)` keeps three S cells and shifts oldest-first.
1459    #[test]
1460    fn past_depth_n_keeps_n_cells_shifted_oldest_first() {
1461        let h = hidden_of(
1462            "module top; logic clk; logic [3:0] x, y;\n\
1463             always @(posedge clk) y <= $past(x, 3);\nendmodule\n",
1464        )
1465        .unwrap();
1466        assert_eq!(h.s.len(), 3);
1467        let sh = &h.sites[0].shifts;
1468        assert_eq!(sh.len(), 3);
1469        assert_eq!(sh[0].to, h.s[2].id);
1470        assert_eq!(sh[0].from, h.s[1].id);
1471        assert_eq!(sh[1].to, h.s[1].id);
1472        assert_eq!(sh[1].from, h.s[0].id);
1473        assert_eq!(sh[2].to, h.s[0].id);
1474        assert_eq!(sh[2].from, p_id(&h.s[0].source));
1475    }
1476
1477    /// `$sampled` is pure P: no history and no site.
1478    #[test]
1479    fn sampled_needs_only_a_p_cell() {
1480        let h = hidden_of(
1481            "module top; logic clk, a, r;\n\
1482             always @(posedge clk) r <= $sampled(a);\nendmodule\n",
1483        )
1484        .unwrap();
1485        assert_eq!(names(&h.p), ["top.a$p"]);
1486        assert!(h.s.is_empty() && h.sites.is_empty());
1487    }
1488
1489    /// The gate's signals need P cells (the guard reads them).
1490    #[test]
1491    fn past_gate_signals_get_p_cells() {
1492        let h = hidden_of(
1493            "module top; logic clk, en, q, r;\n\
1494             always @(posedge clk) r <= $past(q, 2, en);\nendmodule\n",
1495        )
1496        .unwrap();
1497        assert_eq!(names(&h.p), ["top.clk$p", "top.en$p", "top.q$p"]);
1498    }
1499
1500    /// A design with no sampled call has no hidden cells.
1501    #[test]
1502    fn no_sampled_calls_no_hidden_cells() {
1503        let h = hidden_of("module top; logic clk, a; always @(posedge clk) a <= ~a;\nendmodule\n")
1504            .unwrap();
1505        assert!(h.is_empty() && h.sites.is_empty());
1506    }
1507
1508    /// The enumeration is deterministic: two runs agree.
1509    #[test]
1510    fn enumeration_is_deterministic() {
1511        let src = "module top; logic clk, a, b, r;\n\
1512                   always @(posedge clk) r <= $rose(a) ^ $fell(b) ^ $past(a, 2);\nendmodule\n";
1513        assert_eq!(hidden_of(src).unwrap(), hidden_of(src).unwrap());
1514    }
1515
1516    /// A sampled call inside another's argument is a named error.
1517    #[test]
1518    fn nested_sampled_calls_are_rejected() {
1519        let e = hidden_of(
1520            "module top; logic clk, a, r;\n\
1521             always @(posedge clk) r <= $past($rose(a));\nendmodule\n",
1522        )
1523        .map(|_| ())
1524        .unwrap_err();
1525        assert!(e.0.contains("nested"), "{e}");
1526    }
1527
1528    /// An automatic loop variable is not a state slot: no cell for `i` (16.5.1:
1529    /// its sampled value is its current value).
1530    #[test]
1531    fn automatic_loop_variable_gets_no_cell() {
1532        let h = hidden_of(
1533            "module top; logic clk; logic [3:0] b, r1;\n\
1534             always @(posedge clk) for (int i = 0; i < 4; i++) r1[i] <= $past(b[i]);\n\
1535             endmodule\n",
1536        )
1537        .unwrap();
1538        assert_eq!(names(&h.p), ["top.b$p", "top.clk$p"]);
1539        assert_eq!(h.s.len(), 1);
1540    }
1541
1542    /// `$past`'s tick count shall be 1 or greater (16.9.3).
1543    #[test]
1544    fn past_zero_ticks_is_rejected() {
1545        let e = hidden_of(
1546            "module top; logic clk, a, r;\n\
1547             always @(posedge clk) r <= $past(a, 0);\nendmodule\n",
1548        )
1549        .map(|_| ())
1550        .unwrap_err();
1551        assert!(e.0.contains("number_of_ticks"), "{e}");
1552    }
1553
1554    /// A signal whose type has no hidden-cell support is a named error, never
1555    /// a silent read of its current value.
1556    #[test]
1557    fn unsampleable_signal_types_are_rejected() {
1558        for (decl, call) in [
1559            ("logic [3:0] m [0:3]; logic r;", "$past(m[1])"),
1560            ("real x; logic r;", "$rose(x > 1.0)"),
1561            ("string s; logic r;", "$changed(s == \"a\")"),
1562        ] {
1563            let src = format!(
1564                "module top; logic clk; {decl}\n\
1565                 always @(posedge clk) r <= {call};\nendmodule\n"
1566            );
1567            let e = hidden_of(&src).map(|_| ()).unwrap_err();
1568            assert!(
1569                e.0.contains("sampled value is not supported"),
1570                "{call}: {e}"
1571            );
1572        }
1573    }
1574
1575    /// A user function's body reads module signals the sampled views cannot
1576    /// redirect, so a call inside the argument is rejected.
1577    #[test]
1578    fn user_function_call_in_the_argument_is_rejected() {
1579        let e = hidden_of(
1580            "module top; logic clk, a, r;\n\
1581             function logic f(logic x); return x; endfunction\n\
1582             always @(posedge clk) r <= $rose(f(a));\nendmodule\n",
1583        )
1584        .map(|_| ())
1585        .unwrap_err();
1586        assert!(e.0.contains("user subroutine call"), "{e}");
1587    }
1588
1589    /// A hierarchical reference's source lives in another instance: this one
1590    /// keeps its own P and S cells of it, built from a shape (LRM 23.6).
1591    #[test]
1592    fn hierarchical_reference_in_the_argument_gets_foreign_cells() {
1593        let h = hidden_of(
1594            "module top; logic clk, r;\n\
1595             sub u();\n\
1596             always @(posedge clk) r <= $rose(u.v);\nendmodule\n\
1597             module sub; logic v; endmodule\n",
1598        )
1599        .expect("accepted");
1600        assert!(
1601            h.p.iter()
1602                .any(|c| c.foreign.as_ref().is_some_and(|f| f.required))
1603        );
1604        assert!(h.s.iter().all(|c| c.foreign.is_some()), "{:?}", h.s);
1605    }
1606
1607    /// The views a call reads: `$sampled` the P cell, a value change function
1608    /// P (now) and S_0 (before), `$past(x, n)` the n-th history cell.
1609    #[test]
1610    fn use_views_pick_the_cell_each_function_reads() {
1611        let design = compile_source(
1612            "module top; logic clk, a, b, c, r;\n\
1613             always @(posedge clk) begin\n\
1614               r <= $sampled(a); r <= $rose(b); r <= $past(c, 3);\n\
1615             end\nendmodule\n",
1616        )
1617        .expect("compiles");
1618        let body = design
1619            .top_instances()
1620            .next()
1621            .unwrap()
1622            .instance_body()
1623            .unwrap();
1624        let block = crate::netlist::module_items(&body)
1625            .into_iter()
1626            .find(|m| m.kind() == SymbolKind::ProceduralBlock)
1627            .expect("one procedural block");
1628        let owner = block.uid();
1629        let sites = sites_of_block(&block).unwrap();
1630        assert_eq!(sites.len(), 3);
1631        let views: Vec<UseViews> = sites
1632            .iter()
1633            .map(|s| use_views(&owner, &s.call, &s.arg_signals))
1634            .collect();
1635        let a = body.find("a").unwrap().uid();
1636        let b = body.find("b").unwrap().uid();
1637        let c = body.find("c").unwrap().uid();
1638        let loc = |i: usize| site_loc(&sites[i].call.call);
1639        assert_eq!(views[0].cur, [(a.clone(), p_id(&a))]);
1640        assert!(views[0].prev.is_empty());
1641        assert_eq!(views[1].cur, [(b.clone(), p_id(&b))]);
1642        assert_eq!(views[1].prev, [(b.clone(), s_id(&owner, &loc(1), &b, 0))]);
1643        assert_eq!(views[2].cur, [(c.clone(), s_id(&owner, &loc(2), &c, 2))]);
1644        assert!(views[2].prev.is_empty());
1645    }
1646
1647    /// A clock inferred for one instance is found again, by source text, in
1648    /// the same procedure of another instance of the module.
1649    #[test]
1650    fn relocate_clock_finds_the_same_event_in_another_instance() {
1651        let design = compile_source(
1652            "module sub(input logic clk, input logic d, output logic r);\n\
1653               always @(negedge clk) r <= $rose(d);\nendmodule\n\
1654             module top; logic clk, d, r1, r2;\n\
1655               sub u1(clk, d, r1); sub u2(clk, d, r2);\nendmodule\n",
1656        )
1657        .expect("compiles");
1658        let top = design
1659            .top_instances()
1660            .next()
1661            .unwrap()
1662            .instance_body()
1663            .unwrap();
1664        let block_of = |inst: &str| {
1665            let body = top.find(inst).unwrap().instance_body().unwrap();
1666            crate::netlist::module_items(&body)
1667                .into_iter()
1668                .find(|m| m.kind() == SymbolKind::ProceduralBlock)
1669                .unwrap()
1670        };
1671        let (b1, b2) = (block_of("u1"), block_of("u2"));
1672        let ev1 = {
1673            let mut found = None;
1674            walk(
1675                crate::process::cfg::proc_body(&b1).unwrap().as_sem_node(),
1676                &mut |n| {
1677                    if found.is_none() {
1678                        found = n.as_timing_control();
1679                    }
1680                    found.is_none()
1681                },
1682            );
1683            clock_event_of(found.expect("the always has an event control")).unwrap()
1684        };
1685        let ev2 = relocate_clock(&b2, &ev1).unwrap();
1686        assert_eq!(ev2.edge, EdgeKind::NegEdge);
1687        assert_eq!(
1688            ev2.signal.referenced_symbol().unwrap().uid().path(),
1689            "top.u2.clk"
1690        );
1691        assert_eq!(
1692            ev1.signal.referenced_symbol().unwrap().uid().path(),
1693            "top.u1.clk"
1694        );
1695    }
1696
1697    /// 16.9.4: `$past_gclk(v)` is defined as `$past(v,,,@$global_clock)` and
1698    /// `$rose_gclk(v)` as `$rose(v, @$global_clock)`: both spellings decode to
1699    /// the same call (`global`, no explicit clock event of its own).
1700    #[test]
1701    fn gclk_spellings_and_explicit_global_clock_event_decode_alike() {
1702        let design = compile_source(
1703            "module top; logic gclk; logic [3:0] v, r;\n\
1704             global clocking sys @(posedge gclk); endclocking\n\
1705             always @(posedge gclk) begin\n\
1706               r <= $past_gclk(v); r <= $past(v, , , @$global_clock);\n\
1707               r <= $rose_gclk(v); r <= $rose(v, @$global_clock);\n\
1708               r <= $past_gclk(v) + $past(v, , , @(posedge gclk));\n\
1709             end\nendmodule\n",
1710        )
1711        .expect("compiles");
1712        let body = design
1713            .top_instances()
1714            .next()
1715            .unwrap()
1716            .instance_body()
1717            .unwrap();
1718        let block = crate::netlist::module_items(&body)
1719            .into_iter()
1720            .find(|m| m.kind() == SymbolKind::ProceduralBlock)
1721            .unwrap();
1722        let sites = sites_of_block(&block).unwrap();
1723        let shape: Vec<(SampledOp, bool, bool)> = sites
1724            .iter()
1725            .map(|s| (s.call.op, s.call.global, s.call.clock.is_some()))
1726            .collect();
1727        assert_eq!(
1728            shape,
1729            [
1730                (SampledOp::Past, true, false),
1731                (SampledOp::Past, true, false),
1732                (SampledOp::Rose, true, false),
1733                (SampledOp::Rose, true, false),
1734                (SampledOp::Past, true, false),
1735                // An ordinary explicit event is not global.
1736                (SampledOp::Past, false, true),
1737            ]
1738        );
1739        // The global clocking's signal needs a P cell for the guard.
1740        let h = hidden_slots(&body).unwrap();
1741        assert!(names(&h.p).contains(&"top.gclk$p".to_owned()), "{:?}", h.p);
1742    }
1743
1744    /// The explicit clock's signals get P cells.
1745    #[test]
1746    fn explicit_clock_signals_get_p_cells() {
1747        let h = hidden_of(
1748            "module top; logic clk, clk2, b, r;\n\
1749             always @(posedge clk) r <= $rose(b, @(posedge clk2));\nendmodule\n",
1750        )
1751        .unwrap();
1752        assert!(names(&h.p).contains(&"top.clk2$p".to_owned()));
1753    }
1754}