Skip to main content

vj_sim/sched/
assert_control.rs

1//! The assertion control system tasks' registry (LRM 1800-2023 §20.11:
2//! `$assertcontrol` and the `$asserton`/`$assertoff`/`$assertkill`/
3//! `$assertpasson`/... conveniences built on it).
4//!
5//! Every assertion the run can control is a *site*: a concurrent assertion
6//! statement, a simple/deferred immediate assertion, an `expect`, or one of
7//! the `unique`/`unique0`/`priority` violation checkers. A site is described by
8//! its Table 20-5 `assertion_type` bit, its Table 20-6 `directive_type` bit
9//! (0: the directive type is not checked -- it does not apply to `expect` and
10//! violation reports), the hierarchical scope that holds it and its label (the
11//! assertion's full hierarchical name; empty when it has none).
12//!
13//! The control tasks themselves are an append-only *log*. A site's state is
14//! the fold of the matching entries over [`SiteState::DEFAULT`], applied lazily
15//! the next time the site is asked about: the log order is the order the tasks
16//! executed in, so a `$assertlock` makes every later entry that matches the
17//! site a no-op until the matching unlock, exactly as §20.11 words it. A site
18//! the control tasks never name costs one empty-log test.
19//!
20//! Only `$assertkill`-class entries need an immediate effect (they flush
21//! queued reports and procedural instances of the sites they kill):
22//! [`control`] returns those sites. Everything else is *polled*: the
23//! assertion's executor reads its [`SiteState`] where it acts.
24
25use std::cell::RefCell;
26use std::collections::HashMap;
27
28/// Table 20-5 `assertion_type` bits.
29pub const ASSERT_CONCURRENT: u32 = 1;
30pub const ASSERT_SIMPLE_IMMEDIATE: u32 = 2;
31pub const ASSERT_OBSERVED_DEFERRED: u32 = 4;
32pub const ASSERT_FINAL_DEFERRED: u32 = 8;
33pub const ASSERT_EXPECT: u32 = 16;
34pub const ASSERT_UNIQUE: u32 = 32;
35pub const ASSERT_UNIQUE0: u32 = 64;
36pub const ASSERT_PRIORITY: u32 = 128;
37/// The violation report types (`unique`/`unique0`/`priority`).
38pub const ASSERT_VIOLATION: u32 = ASSERT_UNIQUE | ASSERT_UNIQUE0 | ASSERT_PRIORITY;
39/// `assertion_type` default of `$assertcontrol` (every type).
40pub const ASSERT_ALL: u32 = 255;
41
42/// Table 20-6 `directive_type` bits.
43pub const DIR_ASSERT: u32 = 1;
44pub const DIR_COVER: u32 = 2;
45pub const DIR_ASSUME: u32 = 4;
46/// `directive_type` default of `$assertcontrol` (every directive).
47pub const DIR_ALL: u32 = 7;
48
49/// [`SiteState::query_bits`]: reporting on, nonvacuous pass action on, vacuous
50/// pass action on, fail action on.
51pub const Q_CHECKING: u32 = 1;
52pub const Q_PASS_NONVACUOUS: u32 = 2;
53pub const Q_PASS_VACUOUS: u32 = 4;
54pub const Q_FAIL: u32 = 8;
55/// Every query bit set: no control task has touched the site.
56pub const Q_ALL: u32 = Q_CHECKING | Q_PASS_NONVACUOUS | Q_PASS_VACUOUS | Q_FAIL;
57
58/// The term an `expect`'s blocked process is resumed with when its property was
59/// decided but the control tasks switched off the action that would run (0: the
60/// pass statement, 1: the `else` statement).
61pub const EXPECT_NO_ACTION_TERM: i64 = 2;
62
63/// A registry index of one site.
64pub type SiteId = u32;
65
66/// `control_type` values of Table 20-5.
67#[derive(Clone, Copy, PartialEq, Eq, Debug)]
68pub enum Control {
69    Lock,
70    Unlock,
71    On,
72    Off,
73    Kill,
74    PassOn,
75    PassOff,
76    FailOn,
77    FailOff,
78    NonvacuousOn,
79    VacuousOff,
80}
81
82impl Control {
83    /// The `control_type` value `n` names, `None` for anything outside 1..=11
84    /// (LRM: an illegal value is an error).
85    pub const fn from_value(n: i64) -> Option<Control> {
86        Some(match n {
87            1 => Control::Lock,
88            2 => Control::Unlock,
89            3 => Control::On,
90            4 => Control::Off,
91            5 => Control::Kill,
92            6 => Control::PassOn,
93            7 => Control::PassOff,
94            8 => Control::FailOn,
95            9 => Control::FailOff,
96            10 => Control::NonvacuousOn,
97            11 => Control::VacuousOff,
98            _ => return None,
99        })
100    }
101}
102
103/// What one site is, as the registry sees it.
104#[derive(Clone, Debug, PartialEq, Eq)]
105pub struct SiteDesc {
106    /// Exactly one `ASSERT_*` bit.
107    pub assertion_type: u32,
108    /// Exactly one `DIR_*` bit, or 0: not checked.
109    pub directive_type: u32,
110    /// The hierarchical scope holding the site.
111    pub scope: String,
112    /// The assertion's full hierarchical name; empty when unlabeled (a
113    /// statement-level site then carries `#<source offset>`, which tells the
114    /// unlabeled assertions of one scope apart).
115    pub label: String,
116}
117
118impl SiteDesc {
119    /// The text compiled code carries for the site (`sv_rt_assert_ctl`'s query).
120    pub fn encode(&self) -> String {
121        format!(
122            "{}|{}|{}|{}",
123            self.assertion_type, self.directive_type, self.scope, self.label
124        )
125    }
126
127    /// The inverse of [`SiteDesc::encode`].
128    pub fn parse(text: &str) -> Result<SiteDesc, String> {
129        let mut parts = text.splitn(4, '|');
130        let (Some(a), Some(d), Some(scope), Some(label)) =
131            (parts.next(), parts.next(), parts.next(), parts.next())
132        else {
133            return Err(format!("assertion site descriptor `{text}` is malformed"));
134        };
135        let num = |s: &str| {
136            s.parse::<u32>()
137                .map_err(|_| format!("assertion site descriptor `{text}` is malformed"))
138        };
139        Ok(SiteDesc {
140            assertion_type: num(a)?,
141            directive_type: num(d)?,
142            scope: scope.to_string(),
143            label: label.to_string(),
144        })
145    }
146}
147
148/// One executed control task.
149#[derive(Clone, Debug, PartialEq, Eq)]
150pub struct Op {
151    pub control: Control,
152    /// OR of `ASSERT_*` bits.
153    pub assertion_type: u32,
154    /// OR of `DIR_*` bits.
155    pub directive_type: u32,
156    /// Scope depth below each named scope the task reaches; `<= 0`: all.
157    pub levels: i64,
158    /// The named scopes and assertions (hierarchical paths); empty: the whole
159    /// design.
160    pub targets: Vec<String>,
161}
162
163/// A site's control state.
164#[derive(Clone, Copy, PartialEq, Eq, Debug)]
165pub struct SiteState {
166    /// `$assertlock`ed: every control task but unlock is ignored.
167    pub locked: bool,
168    /// New attempts start / new reports are made (`$asserton`), as opposed to
169    /// `$assertoff`/`$assertkill`.
170    pub checking: bool,
171    /// The pass action of a nonvacuous success runs.
172    pub pass_nonvacuous: bool,
173    /// The pass action of a vacuous success runs.
174    pub pass_vacuous: bool,
175    /// The fail action (the default `$error` included) runs.
176    pub fail: bool,
177    /// How many `$assertkill`s have reached the site.
178    pub kills: u64,
179}
180
181impl SiteState {
182    pub const DEFAULT: SiteState = SiteState {
183        locked: false,
184        checking: true,
185        pass_nonvacuous: true,
186        pass_vacuous: true,
187        fail: true,
188        kills: 0,
189    };
190
191    /// The `Q_*` bits of this state.
192    pub fn query_bits(&self) -> u32 {
193        let mut bits = 0;
194        if self.checking {
195            bits |= Q_CHECKING;
196        }
197        if self.pass_nonvacuous {
198            bits |= Q_PASS_NONVACUOUS;
199        }
200        if self.pass_vacuous {
201            bits |= Q_PASS_VACUOUS;
202        }
203        if self.fail {
204            bits |= Q_FAIL;
205        }
206        bits
207    }
208}
209
210/// The target text naming the unlabeled assertion at source `offset` in the
211/// instance `scope` (`vpi_control` on an unnamed assertion, 39.5).
212pub fn anonymous_target(scope: &str, offset: u64) -> String {
213    format!("#{offset}@{scope}")
214}
215
216/// Path components of a hierarchical name.
217fn depth(path: &str) -> usize {
218    if path.is_empty() {
219        0
220    } else {
221        path.split('.').count()
222    }
223}
224
225/// Whether `target` (one name of the task's list) reaches `desc` within
226/// `levels`: it names the assertion itself, or a scope the assertion sits in at
227/// most `levels` scopes below (1: the named scope's own assertions; `<= 0`: no
228/// limit).
229fn target_reaches(target: &str, levels: i64, desc: &SiteDesc) -> bool {
230    if let Some(anon) = target.strip_prefix('#') {
231        // `#<offset>@<scope>` (`anonymous_target`): one unlabeled assertion.
232        let Some((offset, scope)) = anon.split_once('@') else {
233            return false;
234        };
235        return desc.label.strip_prefix('#') == Some(offset)
236            && (desc.scope == scope
237                || (desc.scope.starts_with(scope) && desc.scope[scope.len()..].starts_with('.')));
238    }
239    if !desc.label.is_empty() && desc.label == target {
240        return true;
241    }
242    let inside = desc.scope == target
243        || (desc.scope.starts_with(target) && desc.scope[target.len()..].starts_with('.'));
244    if !inside {
245        return false;
246    }
247    let below = (depth(&desc.scope) - depth(target) + 1) as i64;
248    levels <= 0 || below <= levels
249}
250
251/// Whether `op` applies to the site `desc` (before the lock is considered).
252fn op_reaches(op: &Op, desc: &SiteDesc) -> bool {
253    if op.assertion_type & desc.assertion_type == 0 {
254        return false;
255    }
256    if desc.directive_type != 0 && op.directive_type & desc.directive_type == 0 {
257        return false;
258    }
259    if op.targets.is_empty() {
260        // The whole design: the top modules are the first scope level.
261        let below = depth(&desc.scope) as i64;
262        return op.levels <= 0 || below <= op.levels;
263    }
264    op.targets
265        .iter()
266        .any(|t| target_reaches(t, op.levels, desc))
267}
268
269/// `state` after `op`, for the site `desc`.
270fn fold(mut state: SiteState, desc: &SiteDesc, op: &Op) -> SiteState {
271    if !op_reaches(op, desc) {
272        return state;
273    }
274    match op.control {
275        Control::Lock => state.locked = true,
276        Control::Unlock => state.locked = false,
277        _ if state.locked => {}
278        Control::On | Control::Off | Control::Kill => {
279            // §20.11: these do not affect `expect`.
280            if desc.assertion_type == ASSERT_EXPECT {
281                return state;
282            }
283            state.checking = op.control == Control::On;
284            if op.control == Control::Kill {
285                state.kills += 1;
286            }
287        }
288        Control::PassOn
289        | Control::PassOff
290        | Control::FailOn
291        | Control::FailOff
292        | Control::NonvacuousOn
293        | Control::VacuousOff => {
294            // §20.11: the action controls affect neither the violation report
295            // types nor statistics counters.
296            if desc.assertion_type & ASSERT_VIOLATION != 0 {
297                return state;
298            }
299            match op.control {
300                Control::PassOn => {
301                    state.pass_nonvacuous = true;
302                    state.pass_vacuous = true;
303                }
304                Control::PassOff => {
305                    state.pass_nonvacuous = false;
306                    state.pass_vacuous = false;
307                }
308                Control::FailOn => state.fail = true,
309                Control::FailOff => state.fail = false,
310                Control::NonvacuousOn => state.pass_nonvacuous = true,
311                Control::VacuousOff => state.pass_vacuous = false,
312                Control::Lock | Control::Unlock | Control::On | Control::Off | Control::Kill => {}
313            }
314        }
315    }
316    state
317}
318
319struct Site {
320    desc: SiteDesc,
321    state: SiteState,
322    /// How much of the log `state` already folds.
323    applied: usize,
324}
325
326#[derive(Default)]
327struct Registry {
328    log: Vec<Op>,
329    sites: Vec<Site>,
330    by_text: HashMap<String, SiteId>,
331}
332
333impl Registry {
334    fn sync(&mut self, id: SiteId) -> SiteState {
335        let Registry { log, sites, .. } = self;
336        let site = &mut sites[id as usize];
337        while site.applied < log.len() {
338            site.state = fold(site.state, &site.desc, &log[site.applied]);
339            site.applied += 1;
340        }
341        site.state
342    }
343}
344
345thread_local! {
346    static REGISTRY: RefCell<Registry> = RefCell::new(Registry::default());
347}
348
349/// Forgets every site and control task (the start of a run).
350pub fn reset() {
351    REGISTRY.with(|r| *r.borrow_mut() = Registry::default());
352}
353
354/// The id of the site `desc`, registering it the first time. Two descriptors
355/// that are equal are one site.
356pub fn intern(desc: SiteDesc) -> SiteId {
357    let text = desc.encode();
358    REGISTRY.with(|r| {
359        let mut r = r.borrow_mut();
360        if let Some(&id) = r.by_text.get(&text) {
361            return id;
362        }
363        let id = r.sites.len() as SiteId;
364        r.sites.push(Site {
365            desc,
366            state: SiteState::DEFAULT,
367            applied: 0,
368        });
369        r.by_text.insert(text, id);
370        id
371    })
372}
373
374/// The id of the site whose [`SiteDesc::encode`] text is `text`.
375pub fn intern_text(text: &str) -> Result<SiteId, String> {
376    let known = REGISTRY.with(|r| r.borrow().by_text.get(text).copied());
377    match known {
378        Some(id) => Ok(id),
379        None => Ok(intern(SiteDesc::parse(text)?)),
380    }
381}
382
383/// The current state of the site `id`.
384pub fn state(id: SiteId) -> SiteState {
385    REGISTRY.with(|r| r.borrow_mut().sync(id))
386}
387
388/// The `Q_*` bits of the site `text` names: [`Q_ALL`] without touching the
389/// registry while no control task has run.
390pub fn query_text(text: &str) -> Result<u32, String> {
391    if REGISTRY.with(|r| r.borrow().log.is_empty()) {
392        return Ok(Q_ALL);
393    }
394    Ok(state(intern_text(text)?).query_bits())
395}
396
397/// A change a control task made to one site's state, as the VPI assertion
398/// callbacks report it (IEEE 1800-2023 39.4.2).
399#[derive(Clone, Copy, Debug, PartialEq, Eq)]
400pub enum ControlEvent {
401    Lock,
402    Unlock,
403    Disable,
404    Enable,
405    Kill,
406    PassOn,
407    PassOff,
408    FailOn,
409    FailOff,
410    NonvacuousOn,
411    VacuousOff,
412}
413
414/// The function told of every site state change a control task makes.
415pub type ControlObserver = fn(SiteId, ControlEvent);
416
417thread_local! {
418    static OBSERVER: std::cell::Cell<Option<ControlObserver>> =
419        const { std::cell::Cell::new(None) };
420}
421
422/// Installs (or removes) the function told of every site state change a
423/// control task makes, after the registry is released.
424pub fn set_control_observer(f: Option<ControlObserver>) {
425    OBSERVER.with(|o| o.set(f));
426}
427
428/// The events of the change `a` -> `b` of one site's state.
429fn changes(a: SiteState, b: SiteState) -> Vec<ControlEvent> {
430    let mut out = Vec::new();
431    if a.locked != b.locked {
432        out.push(if b.locked {
433            ControlEvent::Lock
434        } else {
435            ControlEvent::Unlock
436        });
437    }
438    if b.kills != a.kills {
439        out.push(ControlEvent::Kill);
440    } else if a.checking != b.checking {
441        out.push(if b.checking {
442            ControlEvent::Enable
443        } else {
444            ControlEvent::Disable
445        });
446    }
447    match (
448        a.pass_nonvacuous != b.pass_nonvacuous,
449        a.pass_vacuous != b.pass_vacuous,
450    ) {
451        (true, true) => out.push(if b.pass_nonvacuous {
452            ControlEvent::PassOn
453        } else {
454            ControlEvent::PassOff
455        }),
456        (true, false) if b.pass_nonvacuous => out.push(ControlEvent::NonvacuousOn),
457        (false, true) if !b.pass_vacuous => out.push(ControlEvent::VacuousOff),
458        // The other halves: reported with the closest action event.
459        (true, false) => out.push(ControlEvent::PassOff),
460        (false, true) => out.push(ControlEvent::PassOn),
461        (false, false) => {}
462    }
463    if a.fail != b.fail {
464        out.push(if b.fail {
465            ControlEvent::FailOn
466        } else {
467            ControlEvent::FailOff
468        });
469    }
470    out
471}
472
473/// Executes a control task. Returns the registered sites an `$assertkill` (or an
474/// `$assertcontrol` Kill) just reached: the caller flushes what they have queued.
475/// The control observer ([`set_control_observer`]) is told of every change.
476pub fn control(op: Op) -> Vec<SiteId> {
477    let (killed, events) = REGISTRY.with(|r| {
478        let mut r = r.borrow_mut();
479        let ids: Vec<SiteId> = (0..r.sites.len() as SiteId).collect();
480        let before: Vec<SiteState> = ids.iter().map(|&id| r.sync(id)).collect();
481        r.log.push(op);
482        let mut killed = Vec::new();
483        let mut events = Vec::new();
484        for (&id, b) in ids.iter().zip(before) {
485            let a = r.sync(id);
486            if a.kills != b.kills {
487                killed.push(id);
488            }
489            events.extend(changes(b, a).into_iter().map(|e| (id, e)));
490        }
491        (killed, events)
492    });
493    if let Some(f) = OBSERVER.with(|o| o.get()) {
494        for (id, e) in events {
495            f(id, e);
496        }
497    }
498    killed
499}
500
501/// Executes a control task and carries out its immediate effects: the queued
502/// deferred reports, queued violation reports and pending procedural assertion
503/// instances of every site an `$assertkill` reached are discarded (20.11).
504/// Executing attempts are aborted by their runners, which notice the kill count
505/// ([`SiteState::kills`]) change the next time they run.
506pub fn execute(op: Op) {
507    let killed = control(op);
508    if !killed.is_empty() {
509        crate::sched::maturation::flush_sites(&killed);
510        crate::sched::proc_assert::clear_sites(&killed);
511    }
512}
513
514/// `sv_rt_assert_ctl`'s `op` for an assertion control task.
515pub const CTL_OP_CONTROL: u32 = 0;
516/// `sv_rt_assert_ctl`'s `op` for the state query of one site.
517pub const CTL_OP_QUERY: u32 = 1;
518
519/// `u64 sv_rt_assert_ctl(u32 op, i64 a0, i64 a1, i64 a2, i64 a3, ptr text)`, the
520/// one entry point compiled code reaches the registry through.
521///
522/// * [`CTL_OP_CONTROL`]: the control task `a0` (`control_type`) over the
523///   assertion types `a1`, directive types `a2` and `a3` levels, on the
524///   newline-separated hierarchical names in `text` (empty: the whole design).
525///   An illegal `control_type` is ignored with a warning on stderr (the LRM calls
526///   it an error; it cannot unwind out of compiled code). Returns 0.
527/// * [`CTL_OP_QUERY`]: the `Q_*` bits of the site whose descriptor is `text`.
528///
529/// # Safety
530/// `text` is a valid NUL-terminated C string, or null (then empty).
531#[cfg_attr(feature = "aot-exports", unsafe(export_name = "sv_rt_assert_ctl"))]
532pub unsafe extern "C" fn real_sv_rt_assert_ctl(
533    op: u32,
534    a0: i64,
535    a1: i64,
536    a2: i64,
537    a3: i64,
538    text: *const std::ffi::c_char,
539) -> u64 {
540    let text = if text.is_null() {
541        std::borrow::Cow::Borrowed("")
542    } else {
543        // SAFETY: the caller's contract.
544        unsafe { std::ffi::CStr::from_ptr(text) }.to_string_lossy()
545    };
546    match op {
547        CTL_OP_CONTROL => {
548            let Some(control) = Control::from_value(a0) else {
549                eprintln!("vj: warning: $assertcontrol: illegal control_type {a0}; ignored");
550                return 0;
551            };
552            execute(Op {
553                control,
554                assertion_type: (a1 & 0xFF) as u32,
555                directive_type: (a2 & 0x7) as u32,
556                levels: a3,
557                targets: text
558                    .split('\n')
559                    .filter(|t| !t.is_empty())
560                    .map(str::to_string)
561                    .collect(),
562            });
563            0
564        }
565        CTL_OP_QUERY => match query_text(&text) {
566            Ok(bits) => u64::from(bits),
567            Err(why) => {
568                eprintln!("vj: internal error: {why}");
569                u64::from(Q_ALL)
570            }
571        },
572        _ => {
573            eprintln!("vj: internal error: sv_rt_assert_ctl: unknown op {op}");
574            0
575        }
576    }
577}
578
579/// The descriptor the site `id` was registered with.
580pub fn site_desc(id: SiteId) -> SiteDesc {
581    REGISTRY.with(|r| r.borrow().sites[id as usize].desc.clone())
582}
583
584#[cfg(test)]
585mod tests {
586    use super::*;
587
588    fn desc(ty: u32, dir: u32, scope: &str, label: &str) -> SiteDesc {
589        SiteDesc {
590            assertion_type: ty,
591            directive_type: dir,
592            scope: scope.into(),
593            label: label.into(),
594        }
595    }
596
597    fn op(control: Control, ty: u32, dir: u32, levels: i64, targets: &[&str]) -> Op {
598        Op {
599            control,
600            assertion_type: ty,
601            directive_type: dir,
602            levels,
603            targets: targets.iter().map(|s| s.to_string()).collect(),
604        }
605    }
606
607    fn conc(scope: &str, label: &str) -> SiteId {
608        intern(desc(ASSERT_CONCURRENT, DIR_ASSERT, scope, label))
609    }
610
611    #[test]
612    fn site_descriptor_round_trips_and_rejects_garbage() {
613        let d = desc(ASSERT_CONCURRENT, DIR_COVER, "top.u1", "top.u1.c1");
614        assert_eq!(SiteDesc::parse(&d.encode()), Ok(d));
615        let unlabeled = desc(ASSERT_UNIQUE, 0, "top.chk", "");
616        assert_eq!(SiteDesc::parse(&unlabeled.encode()), Ok(unlabeled));
617        assert!(SiteDesc::parse("1|2|top").is_err());
618        assert!(SiteDesc::parse("x|2|top|a").is_err());
619    }
620
621    #[test]
622    fn control_types_one_to_eleven_are_named_and_the_rest_are_not() {
623        let all = [
624            Control::Lock,
625            Control::Unlock,
626            Control::On,
627            Control::Off,
628            Control::Kill,
629            Control::PassOn,
630            Control::PassOff,
631            Control::FailOn,
632            Control::FailOff,
633            Control::NonvacuousOn,
634            Control::VacuousOff,
635        ];
636        for (i, c) in all.iter().enumerate() {
637            assert_eq!(Control::from_value(i as i64 + 1), Some(*c));
638        }
639        assert_eq!(Control::from_value(0), None);
640        assert_eq!(Control::from_value(12), None);
641        assert_eq!(Control::from_value(-1), None);
642    }
643
644    #[test]
645    fn an_untouched_site_has_every_action_enabled_without_registering() {
646        reset();
647        assert_eq!(
648            query_text("1|1|top|top.a").unwrap(),
649            Q_ALL,
650            "an empty log answers the default"
651        );
652        let id = conc("top", "top.a");
653        assert_eq!(state(id), SiteState::DEFAULT);
654    }
655
656    #[test]
657    fn off_then_on_toggles_new_attempts() {
658        reset();
659        let id = conc("top", "top.a");
660        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
661        assert!(!state(id).checking);
662        control(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &[]));
663        assert!(state(id).checking);
664        assert_eq!(state(id).kills, 0);
665    }
666
667    #[test]
668    fn levels_count_scopes_below_the_named_one() {
669        reset();
670        let top = conc("top", "top.a");
671        let u1 = conc("top.u1", "top.u1.a");
672        let deep = conc("top.u1.v", "top.u1.v.a");
673        // Level 1: only the named scope's own assertions.
674        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 1, &["top"]));
675        assert!(!state(top).checking);
676        assert!(state(u1).checking);
677        assert!(state(deep).checking);
678        control(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &[]));
679        // Level 2: one scope deeper as well.
680        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 2, &["top"]));
681        assert!(!state(top).checking);
682        assert!(!state(u1).checking);
683        assert!(state(deep).checking);
684        control(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &[]));
685        // Level 0: all of them.
686        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top"]));
687        assert!(!state(deep).checking);
688    }
689
690    #[test]
691    fn a_scope_name_matches_whole_path_components_only() {
692        reset();
693        let a = conc("top.u1", "top.u1.a");
694        let b = conc("top.u10", "top.u10.a");
695        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top.u1"]));
696        assert!(!state(a).checking);
697        assert!(state(b).checking, "`top.u1` is not a prefix of `top.u10`");
698    }
699
700    #[test]
701    fn a_label_target_names_one_assertion_and_ignores_levels() {
702        reset();
703        let a = conc("top", "top.a");
704        let b = conc("top", "top.b");
705        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 1, &["top.a"]));
706        assert!(!state(a).checking);
707        assert!(state(b).checking);
708    }
709
710    #[test]
711    fn no_target_with_levels_counts_the_top_modules_as_level_one() {
712        reset();
713        let top = conc("top", "top.a");
714        let u1 = conc("top.u1", "top.u1.a");
715        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 1, &[]));
716        assert!(!state(top).checking);
717        assert!(state(u1).checking);
718    }
719
720    #[test]
721    fn assertion_and_directive_types_filter_the_sites() {
722        reset();
723        let assert_c = intern(desc(ASSERT_CONCURRENT, DIR_ASSERT, "top", "top.a"));
724        let cover_c = intern(desc(ASSERT_CONCURRENT, DIR_COVER, "top", "top.c"));
725        let assume_c = intern(desc(ASSERT_CONCURRENT, DIR_ASSUME, "top", "top.m"));
726        let assert_i = intern(desc(ASSERT_SIMPLE_IMMEDIATE, DIR_ASSERT, "top", "top.i"));
727        control(op(Control::Kill, ASSERT_CONCURRENT, DIR_ASSERT, 0, &[]));
728        assert!(!state(assert_c).checking);
729        assert!(state(cover_c).checking, "a cover is not an assert");
730        assert!(state(assume_c).checking);
731        assert!(state(assert_i).checking, "an immediate is not concurrent");
732    }
733
734    #[test]
735    fn lock_ignores_later_controls_until_unlock_and_unlock_always_applies() {
736        reset();
737        let a = conc("top", "top.a");
738        let b = conc("top", "top.b");
739        control(op(Control::Lock, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
740        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
741        assert!(state(a).checking, "locked: unaffected");
742        assert!(!state(b).checking);
743        control(op(Control::FailOff, ASSERT_ALL, DIR_ALL, 0, &[]));
744        assert!(state(a).fail);
745        assert!(!state(b).fail);
746        control(op(Control::Unlock, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
747        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
748        assert!(!state(a).checking, "unlocked: affected again");
749    }
750
751    #[test]
752    fn a_lock_reaches_a_site_registered_after_it() {
753        reset();
754        control(op(Control::Lock, ASSERT_ALL, DIR_ALL, 0, &["top.late"]));
755        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
756        let late = conc("top", "top.late");
757        assert!(state(late).checking);
758    }
759
760    #[test]
761    fn kill_reports_the_sites_it_reached_and_counts_per_site() {
762        reset();
763        let a = conc("top", "top.a");
764        let b = conc("top", "top.b");
765        let killed = control(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
766        assert_eq!(killed, vec![a]);
767        assert_eq!(state(a).kills, 1);
768        assert_eq!(state(b).kills, 0);
769        assert!(!state(a).checking);
770        // A locked site is not reached, so nothing is flushed for it.
771        control(op(Control::Lock, ASSERT_ALL, DIR_ALL, 0, &["top.b"]));
772        assert!(control(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &["top.b"])).is_empty());
773        // Off is not a kill.
774        assert!(control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top.a"])).is_empty());
775    }
776
777    #[test]
778    fn expect_ignores_on_off_kill_but_takes_the_action_controls() {
779        reset();
780        let e = intern(desc(ASSERT_EXPECT, 0, "top", ""));
781        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
782        assert!(control(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &[])).is_empty());
783        assert!(state(e).checking);
784        control(op(Control::FailOff, ASSERT_ALL, DIR_ALL, 0, &[]));
785        assert!(!state(e).fail);
786    }
787
788    #[test]
789    fn expect_and_violation_sites_ignore_the_directive_filter() {
790        reset();
791        let e = intern(desc(ASSERT_EXPECT, 0, "top", ""));
792        let u = intern(desc(ASSERT_UNIQUE, 0, "top.chk", ""));
793        control(op(Control::FailOff, ASSERT_EXPECT, DIR_COVER, 0, &[]));
794        assert!(
795            !state(e).fail,
796            "directive_type is checked only for assertions"
797        );
798        control(op(Control::Off, ASSERT_UNIQUE, DIR_COVER, 0, &[]));
799        assert!(!state(u).checking);
800    }
801
802    #[test]
803    fn violation_sites_ignore_the_action_controls_but_take_on_off_kill() {
804        reset();
805        let u = intern(desc(ASSERT_UNIQUE, 0, "top", ""));
806        let p = intern(desc(ASSERT_PRIORITY, 0, "top", ""));
807        control(op(Control::PassOff, ASSERT_ALL, DIR_ALL, 0, &[]));
808        control(op(Control::FailOff, ASSERT_ALL, DIR_ALL, 0, &[]));
809        assert_eq!(state(u), SiteState::DEFAULT);
810        assert_eq!(state(p), SiteState::DEFAULT);
811        control(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &[]));
812        assert!(!state(u).checking && !state(p).checking);
813        // `On` for the concurrent/immediate types alone does not re-enable them.
814        control(op(
815            Control::On,
816            ASSERT_CONCURRENT | ASSERT_SIMPLE_IMMEDIATE | ASSERT_OBSERVED_DEFERRED,
817            DIR_ALL,
818            0,
819            &[],
820        ));
821        assert!(!state(u).checking);
822        control(op(
823            Control::On,
824            ASSERT_UNIQUE | ASSERT_UNIQUE0 | ASSERT_PRIORITY,
825            DIR_ALL,
826            0,
827            &[],
828        ));
829        assert!(state(u).checking && state(p).checking);
830    }
831
832    #[test]
833    fn the_six_action_controls_set_their_own_flags() {
834        reset();
835        let a = conc("top", "top.a");
836        let all = |c| control(op(c, ASSERT_ALL, DIR_ALL, 0, &[]));
837        all(Control::PassOff);
838        assert_eq!(state(a).query_bits(), Q_CHECKING | Q_FAIL);
839        all(Control::NonvacuousOn);
840        assert_eq!(
841            state(a).query_bits(),
842            Q_CHECKING | Q_FAIL | Q_PASS_NONVACUOUS
843        );
844        all(Control::PassOn);
845        assert_eq!(state(a).query_bits(), Q_ALL);
846        all(Control::VacuousOff);
847        assert_eq!(state(a).query_bits(), Q_ALL & !Q_PASS_VACUOUS);
848        all(Control::FailOff);
849        assert_eq!(
850            state(a).query_bits(),
851            Q_CHECKING | Q_PASS_NONVACUOUS,
852            "vacuous pass and fail are off"
853        );
854        all(Control::FailOn);
855        assert_eq!(
856            state(a).query_bits(),
857            Q_CHECKING | Q_PASS_NONVACUOUS | Q_FAIL
858        );
859        assert_eq!(state(a).kills, 0, "action controls are not kills");
860    }
861
862    /// 20.11: with the assertion off no procedural instance is queued; Kill also
863    /// empties what is queued; On queues again. Other assertions' instances are
864    /// not touched.
865    #[test]
866    fn off_stops_procedural_instances_being_queued_and_kill_flushes_the_queue() {
867        use crate::sched::proc_assert;
868        reset();
869        proc_assert::reset();
870        let a = conc("top", "top.a");
871        let b = conc("top", "top.b");
872        proc_assert::register_site(0, a);
873        proc_assert::register_site(1, b);
874        let queue =
875            |key: i64| unsafe { proc_assert::real_sv_rt_proc_assert(key, std::ptr::null(), 0) };
876        queue(0);
877        queue(1);
878        execute(op(Control::Off, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
879        queue(0);
880        assert_eq!(
881            proc_assert::drain().len(),
882            2,
883            "off: the pending ones stay, no new one"
884        );
885        queue(0);
886        queue(1);
887        execute(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
888        queue(0);
889        execute(op(Control::Kill, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
890        let rest = proc_assert::drain();
891        assert_eq!(
892            rest.iter().map(|e| e.key).collect::<Vec<_>>(),
893            vec![1],
894            "kill flushed the pending instances of a, not b's"
895        );
896        // After the kill `a` is off until an On: a new instance is not queued.
897        queue(0);
898        assert!(proc_assert::drain().is_empty());
899        execute(op(Control::On, ASSERT_ALL, DIR_ALL, 0, &["top.a"]));
900        queue(0);
901        assert_eq!(proc_assert::drain().len(), 1);
902        proc_assert::reset();
903    }
904
905    /// The compiled-code entry point: control, then query, through C strings; an
906    /// illegal `control_type` changes nothing.
907    #[test]
908    fn the_runtime_entry_point_controls_and_queries() {
909        use std::ffi::CString;
910        reset();
911        let site = desc(ASSERT_CONCURRENT, DIR_ASSERT, "top.u1", "top.u1.a").encode();
912        let site = CString::new(site).unwrap();
913        let ctl = |op: u32, a: [i64; 4], text: &str| {
914            let text = CString::new(text).unwrap();
915            // SAFETY: `text` is a NUL-terminated C string.
916            unsafe { real_sv_rt_assert_ctl(op, a[0], a[1], a[2], a[3], text.as_ptr()) }
917        };
918        let q = || unsafe { real_sv_rt_assert_ctl(CTL_OP_QUERY, 0, 0, 0, 0, site.as_ptr()) };
919        assert_eq!(q(), u64::from(Q_ALL));
920        ctl(CTL_OP_CONTROL, [4, 255, 7, 0], "top.u1\ntop.u2");
921        assert_eq!(q(), u64::from(Q_ALL & !Q_CHECKING));
922        ctl(CTL_OP_CONTROL, [0, 255, 7, 0], "");
923        ctl(CTL_OP_CONTROL, [12, 255, 7, 0], "");
924        assert_eq!(q(), u64::from(Q_ALL & !Q_CHECKING), "illegal types ignored");
925        ctl(CTL_OP_CONTROL, [3, 255, 7, 0], "");
926        assert_eq!(q(), u64::from(Q_ALL));
927    }
928
929    #[test]
930    fn query_text_reads_the_state_of_the_named_site() {
931        reset();
932        let d = desc(ASSERT_SIMPLE_IMMEDIATE, DIR_ASSERT, "top", "top.i");
933        control(op(
934            Control::Off,
935            ASSERT_SIMPLE_IMMEDIATE,
936            DIR_ASSERT,
937            0,
938            &[],
939        ));
940        assert_eq!(query_text(&d.encode()).unwrap(), Q_ALL & !Q_CHECKING);
941        let other = desc(ASSERT_CONCURRENT, DIR_ASSERT, "top", "top.c");
942        assert_eq!(query_text(&other.encode()).unwrap(), Q_ALL);
943        assert!(query_text("garbage").is_err());
944    }
945}