vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Values, expressions and errors of the functional-coverage model.
//!
//! The model is pure: it never touches the simulator. Everything that depends
//! on the running design (a coverpoint expression, a bin `iff`, a constructor
//! argument used in a bin range, a `with` clause) is an [`Expr`], either a
//! constant or an opaque [`KernelId`] that an [`Eval`] implementation runs.
//!
//! Kernel argument convention (fixed here, honoured by the codegen side):
//! * construction-time kernels (bin ranges, `with`, set expressions,
//!   `number_of_bins`, option values) receive the constructor arguments; a
//!   `with` kernel receives them followed by one extra value, `item`;
//! * sample-time kernels (coverpoint expression, coverpoint/bin `iff`) receive
//!   the constructor arguments followed by the `sample()` arguments.

use std::fmt;

/// A value crossing the model/design boundary.
#[derive(Clone, Debug, PartialEq)]
pub enum Val {
    /// A two-state integral value (sign-extended to `i128`).
    Int(i128),
    Real(f64),
    Str(String),
    /// An array-valued expression (a `set_covergroup_expression`).
    List(Vec<Val>),
    /// An integral value with an x or z bit. Never matches a non-wildcard bin.
    Unknown,
    /// A wildcard bin value: `care` has a 1 in every bit position that is a
    /// known 0/1 in `bits`; the other positions were x, z or `?`.
    Pat {
        bits: u128,
        care: u128,
    },
}

pub type KernelId = u32;

/// A model input: a constant, or a kernel to be run by an [`Eval`].
#[derive(Clone, Debug, PartialEq)]
pub enum Expr {
    Const(Val),
    Kernel(KernelId),
}

impl Expr {
    pub fn int(v: i128) -> Expr {
        Expr::Const(Val::Int(v))
    }

    pub fn real(v: f64) -> Expr {
        Expr::Const(Val::Real(v))
    }

    pub fn eval(&self, ev: &mut dyn Eval, args: &[Val]) -> Result<Val, CovError> {
        match self {
            Expr::Const(v) => Ok(v.clone()),
            Expr::Kernel(id) => ev.eval(*id, args),
        }
    }
}

/// Runs kernels. `args` follows the convention in the module docs.
pub trait Eval {
    fn eval(&mut self, id: KernelId, args: &[Val]) -> Result<Val, CovError>;
}

/// An [`Eval`] for models built only from constants: any kernel is an error.
pub struct NoKernels;

impl Eval for NoKernels {
    fn eval(&mut self, id: KernelId, _args: &[Val]) -> Result<Val, CovError> {
        Err(CovError::NoKernel(id))
    }
}

/// The type of a coverpoint expression.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TypeInfo {
    pub width: u32,
    pub signed: bool,
    pub real: bool,
}

/// The widest integral coverpoint the model represents (values fit `i128`).
pub const MAX_WIDTH: u32 = 64;

impl TypeInfo {
    pub fn unsigned(width: u32) -> TypeInfo {
        TypeInfo {
            width,
            signed: false,
            real: false,
        }
    }

    pub fn signed(width: u32) -> TypeInfo {
        TypeInfo {
            width,
            signed: true,
            real: false,
        }
    }

    pub fn real() -> TypeInfo {
        TypeInfo {
            width: 64,
            signed: true,
            real: true,
        }
    }

    /// Smallest value expressible by an integral type.
    pub fn dmin(&self) -> i128 {
        if self.signed {
            -(1i128 << (self.width - 1))
        } else {
            0
        }
    }

    /// Largest value expressible by an integral type.
    pub fn dmax(&self) -> i128 {
        if self.signed {
            (1i128 << (self.width - 1)) - 1
        } else {
            (1i128 << self.width) - 1
        }
    }

    /// The two's-complement bit pattern of `v` in this type's width.
    pub fn bits_of(&self, v: i128) -> u128 {
        (v as u128) & self.mask()
    }

    /// The value whose bit pattern is `bits`.
    pub fn value_of(&self, bits: u128) -> i128 {
        let bits = bits & self.mask();
        if self.signed && ((bits >> (self.width - 1)) & 1) == 1 {
            (bits as i128) - (1i128 << self.width)
        } else {
            bits as i128
        }
    }

    pub fn mask(&self) -> u128 {
        (1u128 << self.width) - 1
    }
}

/// Everything the model rejects, loudly.
#[derive(Clone, Debug, PartialEq)]
pub enum CovError {
    /// A kernel was needed but the evaluator has none.
    NoKernel(KernelId),
    /// An expression evaluated to a value of the wrong kind.
    BadValue {
        ctx: String,
        got: String,
    },
    /// A bin would need more individual values than the model expands.
    TooManyValues {
        ctx: String,
        limit: u128,
    },
    /// `bins b[N]` with `N` not positive.
    NonPositiveBins {
        bin: String,
        n: i128,
    },
    /// `with`/wildcard/transition bins on a real coverpoint (19.5.1, 19.5.2).
    RealUnsupported {
        bin: String,
        what: &'static str,
    },
    /// A real coverpoint without a single explicit bin (19.5).
    RealNeedsBins {
        coverpoint: String,
    },
    /// A default bin of a real coverpoint with `[]` or `[N]` (19.5.1).
    RealDefaultArray {
        bin: String,
    },
    /// A coverpoint wider than [`MAX_WIDTH`].
    WidthTooLarge {
        coverpoint: String,
        width: u32,
    },
    /// A transition bin that needs a bounded sequence but has a goto or
    /// nonconsecutive repetition (19.5.2, 19.5.5, 19.5.6).
    TransUnbounded {
        bin: String,
        what: &'static str,
    },
    /// A transition bin of length 0 (19.5.2).
    TransZeroLength {
        bin: String,
    },
    /// A bad repeat range in a transition bin (negative, reversed, zero goto).
    TransRepeat {
        bin: String,
        detail: String,
    },
    /// A malformed cross or select expression (an operand that is not crossed,
    /// a bin that does not exist, a non-positive `matches`), 19.6.
    CrossSelect {
        cross: String,
        detail: String,
    },
    /// A handle that names no covergroup instance or type.
    BadHandle(u64),
    /// A coverpoint/cross name the covergroup does not declare.
    UnknownItem {
        group: String,
        item: String,
    },
    /// Two coverpoints/crosses of one covergroup with the same name.
    DuplicateItem {
        group: String,
        item: String,
    },
    /// An option that 19.7/19.7.1 allow only in the covergroup definition,
    /// assigned procedurally.
    DefinitionOnlyOption {
        option: String,
    },
    /// A coverage database file that does not parse (19.9), by 1-based line.
    BadDb {
        line: usize,
        detail: String,
    },
    UnknownOption(String),
    /// An option used at a level where Table 19-2/19-3 does not allow it.
    OptionLevel {
        option: String,
        level: &'static str,
    },
    OptionType {
        option: String,
    },
    OptionValue {
        option: String,
        detail: String,
    },
    /// Valid SystemVerilog the model does not implement (named, never silent).
    Unsupported(String),
}

impl fmt::Display for CovError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CovError::NoKernel(id) => write!(f, "coverage kernel {id} has no evaluator"),
            CovError::BadValue { ctx, got } => write!(f, "{ctx}: unexpected value {got}"),
            CovError::TooManyValues { ctx, limit } => {
                write!(f, "{ctx}: more than {limit} individual values to expand")
            }
            CovError::NonPositiveBins { bin, n } => {
                write!(f, "bin {bin}: number of bins must be positive, got {n}")
            }
            CovError::RealUnsupported { bin, what } => {
                write!(f, "bin {bin}: {what} is not allowed for real coverpoints")
            }
            CovError::RealNeedsBins { coverpoint } => write!(
                f,
                "coverpoint {coverpoint}: real coverpoints need at least one explicit bins construct"
            ),
            CovError::RealDefaultArray { bin } => write!(
                f,
                "bin {bin}: a default bin of a real coverpoint cannot be an array"
            ),
            CovError::WidthTooLarge { coverpoint, width } => write!(
                f,
                "coverpoint {coverpoint}: {width}-bit coverpoints are not supported (max {MAX_WIDTH})"
            ),
            CovError::TransUnbounded { bin, what } => write!(
                f,
                "bin {bin}: a transition of unbounded or varying length (goto or nonconsecutive repetition) cannot be used for {what}"
            ),
            CovError::TransZeroLength { bin } => write!(
                f,
                "bin {bin}: a transition bin of length 0 is illegal (19.5.2)"
            ),
            CovError::TransRepeat { bin, detail } => {
                write!(f, "bin {bin}: bad repeat range: {detail}")
            }
            CovError::CrossSelect { cross, detail } => write!(f, "cross {cross}: {detail}"),
            CovError::BadHandle(h) => write!(f, "{h:#x} is not a covergroup handle"),
            CovError::UnknownItem { group, item } => {
                write!(f, "covergroup {group} has no coverpoint or cross '{item}'")
            }
            CovError::DuplicateItem { group, item } => write!(
                f,
                "covergroup {group} declares the coverpoint or cross '{item}' twice"
            ),
            CovError::DefinitionOnlyOption { option } => write!(
                f,
                "coverage option '{option}' can only be set in the covergroup definition (19.7)"
            ),
            CovError::BadDb { line, detail } => {
                write!(f, "coverage database line {line}: {detail}")
            }
            CovError::UnknownOption(n) => write!(f, "unknown coverage option '{n}'"),
            CovError::OptionLevel { option, level } => {
                write!(
                    f,
                    "coverage option '{option}' is not allowed at {level} level"
                )
            }
            CovError::OptionType { option } => {
                write!(
                    f,
                    "coverage option '{option}' assigned a value of the wrong type"
                )
            }
            CovError::OptionValue { option, detail } => {
                write!(f, "coverage option '{option}': {detail}")
            }
            CovError::Unsupported(what) => write!(f, "unsupported covergroup construct: {what}"),
        }
    }
}

impl std::error::Error for CovError {}

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

    #[test]
    fn domain_of_signed_and_unsigned() {
        let u = TypeInfo::unsigned(3);
        assert_eq!((u.dmin(), u.dmax()), (0, 7));
        let s = TypeInfo::signed(3);
        assert_eq!((s.dmin(), s.dmax()), (-4, 3));
        assert_eq!(TypeInfo::unsigned(64).dmax(), u64::MAX as i128);
        assert_eq!(TypeInfo::signed(64).dmin(), i64::MIN as i128);
    }

    #[test]
    fn bits_roundtrip_for_negative_values() {
        let s = TypeInfo::signed(4);
        assert_eq!(s.bits_of(-1), 0b1111);
        assert_eq!(s.value_of(0b1000), -8);
        assert_eq!(s.value_of(s.bits_of(-5)), -5);
        let u = TypeInfo::unsigned(4);
        assert_eq!(u.value_of(0b1000), 8);
    }

    #[test]
    fn expr_const_and_kernel() {
        struct Doubler;
        impl Eval for Doubler {
            fn eval(&mut self, id: KernelId, args: &[Val]) -> Result<Val, CovError> {
                assert_eq!(id, 7);
                match args {
                    [Val::Int(v)] => Ok(Val::Int(v * 2)),
                    _ => Err(CovError::NoKernel(id)),
                }
            }
        }
        assert_eq!(Expr::int(5).eval(&mut NoKernels, &[]), Ok(Val::Int(5)));
        assert_eq!(
            Expr::Kernel(7).eval(&mut Doubler, &[Val::Int(21)]),
            Ok(Val::Int(42))
        );
        assert_eq!(
            Expr::Kernel(3).eval(&mut NoKernels, &[]),
            Err(CovError::NoKernel(3))
        );
    }
}