vj-codegen 0.1.0

LLVM code generator of the vj SystemVerilog simulator.
//! T18: where a concurrent assertion may use the global clocking FUTURE
//! sampled value functions (LRM 1800-2023 16.9.4: `$future_gclk`,
//! `$rising_gclk`, `$falling_gclk`, `$steady_gclk`, `$changing_gclk`).
//!
//! The LRM's restrictions are that they appear only in property and sequence
//! expressions, are not nested, and are not used "in assertions containing
//! sequence match items". `sv-lang`'s front end (slang) already rejects the
//! first two (`a1`'s nesting, a call in procedural code or an action block) as
//! a compile error; it does not know about the third. [`check`] is that one,
//! and it is also what tells the caller that the assertion HAS future
//! functions, so its action block must be delayed to the next global tick
//! (`sv_sim_sim::sched::maturation::report_at_global_tick`).
//!
//! The nesting rule is re-checked anyway: it costs nothing, and the day a
//! future slang lets a spelling through, the simulator stays right instead of
//! evaluating a future value of a future value.

use sv_lang::kinds::AssertionExprKind;
use sv_lang::{AssertionExpr, Expression, SemNode, Symbol};
use sv_sim_sim::sva::sampled::is_future_call;

use super::{collect_boolean_leaves, expand_assertion_instance};
use crate::error::LoweringError;

/// How deep an assertion's named sequence/property instances may nest before
/// the walk gives up, which a recursive property would otherwise never do.
const MAX_INSTANCE_DEPTH: usize = 256;

/// `(e contains a future function call, one is inside another's argument)`.
fn future_calls<'d>(e: Expression<'d>) -> (bool, bool) {
    let mut any = false;
    let mut nested = false;
    let mut stack = vec![(e.as_sem_node(), false)];
    while let Some((n, inside)) = stack.pop() {
        let mut inside_here = inside;
        if let Some(x) = n.as_expression()
            && is_future_call(x)
        {
            any = true;
            nested |= inside;
            inside_here = true;
        }
        for c in n.children() {
            stack.push((c, inside_here));
        }
    }
    (any, nested)
}

/// What a walk of the assertion tree found besides Boolean leaves.
#[derive(Default)]
struct Scan {
    /// A sequence match item exists.
    match_items: bool,
    /// A future function is used inside a match item.
    future_in_item: bool,
    /// The walk met [`MAX_INSTANCE_DEPTH`].
    too_deep: bool,
}

/// Looks for sequence match items under `node`, through named instances.
fn scan<'d>(node: SemNode<'d>, depth: usize, out: &mut Scan) {
    if depth > MAX_INSTANCE_DEPTH {
        out.too_deep = true;
        return;
    }
    let Some(a) = node.as_assertion_expr() else {
        return;
    };
    let children = a.children();
    if matches!(
        a.kind(),
        AssertionExprKind::SequenceWithMatch | AssertionExprKind::FirstMatch
    ) {
        // The sub-sequence is the only assertion-expression child; every
        // other child is a match item (see `assertion_expr_leaf_children`).
        for c in &children {
            if let Some(item) = c.as_expression()
                && c.as_assertion_expr().is_none()
            {
                out.match_items = true;
                out.future_in_item |= future_calls(item).0;
            }
        }
    }
    for c in children {
        for n in expand_assertion_instance(c) {
            scan(n, depth + 1, out);
        }
    }
}

/// Checks `root`, the sequence or property expression of one concurrent
/// assertion, against 16.9.4's restrictions on the future functions;
/// `Ok(true)` when it contains any (so its action block is delayed to the next
/// global clock tick), `Ok(false)` when it contains none.
pub(crate) fn check<'d>(
    root: AssertionExpr<'d>,
    scope: &Symbol<'d>,
) -> Result<bool, LoweringError> {
    let mut leaves = Vec::new();
    collect_boolean_leaves(root, &mut leaves);
    let mut any = false;
    for leaf in leaves {
        let (has, nested) = future_calls(leaf);
        if nested {
            return Err(LoweringError::unsupported_at(
                "global clocking future sampled value functions shall not be nested (LRM 16.9.4)",
                scope,
            ));
        }
        any |= has;
    }
    let mut found = Scan::default();
    scan(root.as_sem_node(), 0, &mut found);
    if found.future_in_item {
        any = true;
    }
    if !any {
        return Ok(false);
    }
    if found.too_deep {
        return Err(LoweringError::unsupported_at(
            "an assertion with global clocking future sampled value functions nests named \
             instances too deeply to check that it has no match items (LRM 16.9.4)",
            scope,
        ));
    }
    if found.match_items {
        return Err(LoweringError::unsupported_at(
            "a global clocking future sampled value function shall not be used in an assertion \
             containing sequence match items (LRM 16.9.4, 16.10, 16.11)",
            scope,
        ));
    }
    Ok(true)
}