vj-sim 0.3.0

Netlist, scheduler and process model of the vj SystemVerilog simulator.
Documentation
//! Global clocking (LRM 1800-2023 14.14): which `global clocking` declaration
//! the `$global_clock` function, and so every `*_gclk` function (16.9.4),
//! refers to at a point in the design.
//!
//! The lookup is the LRM's hierarchical one, not a lexical one (T8's
//! `default clocking` is lexical, 14.12): the declaration in the enclosing
//! module/interface/program/checker INSTANCE if there is one, else the one in
//! the instance that instantiated it, and so on up to the top, where finding
//! none is an error. The elaborated instance chain is walked through
//! `Symbol::instance_body_parent_instance`, so a module that is merely
//! declared inside another does not inherit its global clocking, while one
//! that is instantiated in it does.
//!
//! `sv-lang` 0.1.9 exposes no "is this clocking block global" accessor (nor
//! the C++ `Compilation::getGlobalClocking`), so the `global` keyword is read
//! from the declaration's syntax, the same way `default` already is for
//! `vj::clocking::default_clocking`.
//!
//! Slang itself already rejects the illegal shapes (a second global clocking in
//! one module, one in a generate block, a `$global_clock` with none in scope),
//! so this lookup returns `Ok(None)` only for a design slang let through
//! without one being needed.

use sv_lang::kinds::{SymbolKind, SyntaxKind, TokenKind};
use sv_lang::{AstNode, Symbol, SyntaxTree};

use super::sampled::SampledError;

/// A resolved global clocking declaration.
#[derive(Clone, Copy)]
pub struct GlobalClocking<'d> {
    /// The `ClockingBlock` symbol.
    pub block: Symbol<'d>,
    /// How many instantiation levels above the querying scope it was found:
    /// 0 is the querying instance's own declaration, which is the only one
    /// whose event signals live in the instance the simulator is lowering.
    pub levels_up: usize,
}

/// Whether `cb`, a `ClockingBlock` symbol, was declared `global clocking`.
pub fn is_global_clocking(cb: &Symbol<'_>, trees: &[SyntaxTree]) -> bool {
    if cb.kind() != SymbolKind::ClockingBlock {
        return false;
    }
    trees.iter().any(|tree| {
        let Some(mut node) = cb.syntax(tree) else {
            return false;
        };
        loop {
            if node.kind() == SyntaxKind::ClockingDeclaration {
                return sv_lang::nodes::ClockingDeclarationSyntax::cast(node)
                    .and_then(|d| d.global_or_default())
                    .is_some_and(|t| t.kind() == TokenKind::GlobalKeyword);
            }
            match node.parent() {
                Some(p) => node = p,
                None => return false,
            }
        }
    })
}

/// The global clocking declared directly in instance body `body`, if any.
fn declared_in<'d>(
    body: &Symbol<'d>,
    trees: &[SyntaxTree],
) -> Result<Option<Symbol<'d>>, SampledError> {
    let mut found = None;
    for cb in crate::netlist::module_items(body)
        .into_iter()
        .filter(|m| m.kind() == SymbolKind::ClockingBlock)
    {
        if is_global_clocking(&cb, trees) {
            if found.is_some() {
                return Err(SampledError(format!(
                    "more than one global clocking declaration in `{}` (LRM 14.14 allows at \
                     most one per module, interface, program or checker)",
                    body.name()
                )));
            }
            found = Some(cb);
        }
    }
    Ok(found)
}

/// The instance body that instantiates `body`, or `None` at the top of the
/// hierarchy.
pub(crate) fn parent_body<'d>(body: &Symbol<'d>) -> Option<Symbol<'d>> {
    let mut scope = body.instance_body_parent_instance()?.parent()?;
    loop {
        if scope.kind() == SymbolKind::InstanceBody {
            return Some(scope);
        }
        scope = scope.parent()?;
    }
}

/// The effective global clocking for a reference inside instance body `body`
/// (LRM 14.14 rules a and b): `None` when the whole chain up to the top has
/// none.
pub fn global_clocking_of<'d>(
    body: &Symbol<'d>,
    trees: &[SyntaxTree],
) -> Result<Option<GlobalClocking<'d>>, SampledError> {
    let mut cur = *body;
    let mut levels_up = 0;
    loop {
        if let Some(block) = declared_in(&cur, trees)? {
            return Ok(Some(GlobalClocking { block, levels_up }));
        }
        match parent_body(&cur) {
            Some(p) => {
                cur = p;
                levels_up += 1;
            }
            None => return Ok(None),
        }
    }
}

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

    /// The global clocking `(name, levels_up)` seen from instance `path` (a
    /// dot-separated chain of instance names below the top), `None` for none.
    fn lookup(src: &str, path: &str) -> Option<(String, usize)> {
        let design = compile_source(src).expect("compiles");
        let trees = design.syntax_trees();
        let mut body = design
            .top_instances()
            .next()
            .expect("a top instance")
            .instance_body()
            .expect("a body");
        for name in path.split('.').filter(|s| !s.is_empty()) {
            body = body
                .find(name)
                .unwrap_or_else(|| panic!("no instance `{name}`"))
                .instance_body()
                .expect("an instance");
        }
        global_clocking_of(&body, &trees)
            .expect("a legal lookup")
            .map(|g| (g.block.name().to_string(), g.levels_up))
    }

    /// 14.14 rule a: the enclosing instance's own declaration.
    #[test]
    fn own_instance_declaration_is_found_at_level_zero() {
        let src = "module top; logic clk; global clocking sys @(posedge clk); endclocking\n\
                   endmodule\n";
        assert_eq!(lookup(src, ""), Some(("sys".to_string(), 0)));
    }

    /// 14.14's own two-subsystem example: each `$global_clock` resolves to the
    /// declaration of its own subsystem, never the other's.
    #[test]
    fn lrm_example_each_subsystem_resolves_to_its_own() {
        let src = "module top; subsystem1 sub1(); subsystem2 sub2(); endmodule\n\
                   module subsystem1; logic c1; global clocking sub_sys1 @(posedge c1); \
                   endclocking common u(); endmodule\n\
                   module subsystem2; logic c2; global clocking sub_sys2 @(posedge c2); \
                   endclocking common u(); endmodule\n\
                   module common; endmodule\n";
        assert_eq!(lookup(src, "sub1.u"), Some(("sub_sys1".to_string(), 1)));
        assert_eq!(lookup(src, "sub2.u"), Some(("sub_sys2".to_string(), 1)));
        assert_eq!(lookup(src, "sub1"), Some(("sub_sys1".to_string(), 0)));
    }

    /// Rule b climbs through every instantiation level, nearest first.
    #[test]
    fn lookup_climbs_to_the_nearest_enclosing_declaration() {
        let src = "module top; logic c; global clocking g @(posedge c); endclocking mid m(); \
                   endmodule\n\
                   module mid; leaf l(); endmodule\n\
                   module leaf; endmodule\n";
        assert_eq!(lookup(src, "m.l"), Some(("g".to_string(), 2)));
        // A nearer declaration shadows the outer one.
        let src2 = "module top; logic c; global clocking g @(posedge c); endclocking mid m(); \
                    endmodule\n\
                    module mid; logic d; global clocking h @(posedge d); endclocking leaf l(); \
                    endmodule\n\
                    module leaf; endmodule\n";
        assert_eq!(lookup(src2, "m.l"), Some(("h".to_string(), 1)));
        assert_eq!(lookup(src2, ""), Some(("g".to_string(), 0)));
    }

    /// No declaration anywhere above: `None` (a `$global_clock` there is the
    /// error slang reports).
    #[test]
    fn no_declaration_in_the_chain_is_none() {
        let src = "module top; logic clk; endmodule\n";
        assert_eq!(lookup(src, ""), None);
    }

    /// A `default clocking` and a plain clocking block are not global
    /// clocking: the keyword decides, not the clocking block's existence.
    #[test]
    fn default_and_plain_clocking_blocks_are_not_global() {
        let src = "module top; logic clk;\n\
                   default clocking d @(posedge clk); endclocking\n\
                   clocking p @(negedge clk); endclocking\n\
                   endmodule\n";
        assert_eq!(lookup(src, ""), None);
        let src2 = "module top; logic clk;\n\
                    default clocking d @(posedge clk); endclocking\n\
                    global clocking g @(negedge clk); endclocking\n\
                    endmodule\n";
        assert_eq!(lookup(src2, ""), Some(("g".to_string(), 0)));
    }

    /// An anonymous global clocking (the name is optional, 14.14) is found.
    #[test]
    fn anonymous_global_clocking_is_found() {
        let src = "module top; logic clk; global clocking @(posedge clk); endclocking\n\
                   endmodule\n";
        assert_eq!(lookup(src, ""), Some((String::new(), 0)));
    }
}