vj-sim 0.2.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! `unique`/`unique0`/`priority` violation reports (LRM 1800-2023 §12.4.2.1,
//! §12.5.3.1).
//!
//! The check is made where the `if`/`case` executes, but a violation is not
//! reported there: the report is held on the executing process's violation
//! report queue and matures in the Observed region of the same time step, so a
//! zero-delay glitch in the active region set never reports. The queue is
//! cleared at a violation report flush point (an event control or `wait`
//! resumption, or an `always_comb`/`always_latch` triggered again), and every
//! execution of a checked statement reports independently
//! ([`maturation::report_independent`]).
//!
//! The report's text is captured when the check fails (compiled code prints it
//! with the ordinary severity framing between `sv_rt_deferred_begin` and
//! `sv_rt_deferred_end`, which hands it here).

use vj_abi::Region;

use crate::sched::assert_control::{self, SiteId};
use crate::sched::maturation::{self, ReportKind};

/// Which qualifier's violation report is raised: the three `assertion_type`
/// bits of Table 20-6 that name violation report types.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ViolationType {
    /// `unique if` / `unique case` (`assertion_type` 32).
    Unique,
    /// `unique0 if` / `unique0 case` (`assertion_type` 64).
    Unique0,
    /// `priority if` / `priority case` (`assertion_type` 128).
    Priority,
}

/// `sv_rt_deferred_end`'s `mode` for an `assert #0` report (Reactive region).
pub const DEFERRED_MODE_OBSERVED: u32 = 0;
/// `sv_rt_deferred_end`'s `mode` for an `assert final` report (Postponed
/// region).
pub const DEFERRED_MODE_FINAL: u32 = 1;
/// `sv_rt_deferred_end`'s `mode` for `disable <deferred assertion label>`
/// (LRM 16.4.4): cancels the running process's pending reports of the control
/// site instead of queueing one. (2..=4 are the violation modes.)
pub const DEFERRED_MODE_CANCEL: u32 = 5;

impl ViolationType {
    /// The Table 20-6 `assertion_type` bit.
    pub const fn assertion_type_bit(self) -> u32 {
        match self {
            ViolationType::Unique => 32,
            ViolationType::Unique0 => 64,
            ViolationType::Priority => 128,
        }
    }

    /// The `sv_rt_deferred_end` `mode` that hands a report of this type to
    /// [`violation_report`] (after [`DEFERRED_MODE_OBSERVED`] and
    /// [`DEFERRED_MODE_FINAL`]).
    pub const fn deferred_mode(self) -> u32 {
        match self {
            ViolationType::Unique => 2,
            ViolationType::Unique0 => 3,
            ViolationType::Priority => 4,
        }
    }

    /// The type a `sv_rt_deferred_end` `mode` names, `None` for an
    /// `assert #0`/`assert final` mode or a stray value.
    pub const fn from_deferred_mode(mode: u32) -> Option<ViolationType> {
        match mode {
            2 => Some(ViolationType::Unique),
            3 => Some(ViolationType::Unique0),
            4 => Some(ViolationType::Priority),
            _ => None,
        }
    }
}

/// Sets where matured violation reports are written (the run's output buffer).
pub fn set_violation_writer(f: fn(&[u8])) {
    maturation::set_writer(ReportKind::Violation, f);
}

/// Holds `text`, the violation report of a `ty` check the running process just
/// made, until the Observed region of this time step.
pub fn violation_report(ty: ViolationType, text: Vec<u8>) {
    violation_report_for(ty, text, None);
}

/// [`violation_report`] of the checker registered as the control site
/// `control` ([`crate::sched::assert_control`]): dropped when the site is not
/// checking (`$assertoff`/`$assertkill`: "no new violation reports", LRM
/// 20.11), else tagged with the site so that a later `$assertkill` can discard it
/// while it is pending.
pub fn violation_report_for(_ty: ViolationType, text: Vec<u8>, control: Option<SiteId>) {
    if let Some(id) = control
        && !assert_control::state(id).checking
    {
        return;
    }
    maturation::report_independent_for_site(ReportKind::Violation, Region::Observed, text, control);
}

/// Discards `proc`'s pending violation reports (it reached a violation report
/// flush point, §12.4.2.1).
pub(crate) fn flush_violation_reports(proc: crate::sched::ProcessId) {
    maturation::flush_violations(proc);
}

#[cfg(test)]
mod tests {
    use super::*;

    /// The Table 20-6 bits and the `sv_rt_deferred_end` modes round-trip, and
    /// the assert #0 / assert final modes and strays name no violation type.
    #[test]
    fn violation_types_round_trip_through_their_deferred_modes() {
        let all = [
            (ViolationType::Unique, 32),
            (ViolationType::Unique0, 64),
            (ViolationType::Priority, 128),
        ];
        for (ty, bit) in all {
            assert_eq!(ty.assertion_type_bit(), bit);
            assert_eq!(
                ViolationType::from_deferred_mode(ty.deferred_mode()),
                Some(ty)
            );
        }
        assert_eq!(
            ViolationType::from_deferred_mode(DEFERRED_MODE_OBSERVED),
            None
        );
        assert_eq!(ViolationType::from_deferred_mode(DEFERRED_MODE_FINAL), None);
        assert_eq!(ViolationType::from_deferred_mode(5), None);
    }

    /// LRM 20.11: while a violation site is off (`$assertoff`/`$assertkill`) no
    /// report is made; `$asserton` of the violation types restores reporting, and
    /// `$assertkill` discards a report still pending.
    #[test]
    fn a_report_of_a_site_that_is_off_is_dropped_and_kill_flushes_a_pending_one() {
        use crate::sched::assert_control::{self as ac, Control, Op, SiteDesc};
        ac::reset();
        let id = ac::intern(SiteDesc {
            assertion_type: ac::ASSERT_UNIQUE,
            directive_type: 0,
            scope: "top".into(),
            label: String::new(),
        });
        let op = |control| Op {
            control,
            assertion_type: ac::ASSERT_ALL,
            directive_type: ac::DIR_ALL,
            levels: 0,
            targets: Vec::new(),
        };
        let before = maturation::pending_len();
        violation_report_for(ViolationType::Unique, b"a\n".to_vec(), Some(id));
        assert_eq!(maturation::pending_len(), before + 1, "checking: reported");

        for killed in ac::control(op(Control::Kill)) {
            assert_eq!(killed, id);
            maturation::flush_sites(&[killed]);
        }
        assert_eq!(maturation::pending_len(), before, "the pending one is gone");
        violation_report_for(ViolationType::Unique, b"b\n".to_vec(), Some(id));
        assert_eq!(maturation::pending_len(), before, "off: not reported");

        ac::control(op(Control::On));
        violation_report_for(ViolationType::Unique, b"c\n".to_vec(), Some(id));
        assert_eq!(maturation::pending_len(), before + 1, "on again: reported");
        // A report with no control site is never gated.
        violation_report(ViolationType::Unique, b"d\n".to_vec());
        assert_eq!(maturation::pending_len(), before + 2);
    }
}