use sv_lang::kinds::{SymbolKind, SyntaxKind, TokenKind};
use sv_lang::{AstNode, Symbol, SyntaxTree};
use super::sampled::SampledError;
#[derive(Clone, Copy)]
pub struct GlobalClocking<'d> {
pub block: Symbol<'d>,
pub levels_up: usize,
}
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,
}
}
})
}
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)
}
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()?;
}
}
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;
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))
}
#[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)));
}
#[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)));
}
#[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)));
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)));
}
#[test]
fn no_declaration_in_the_chain_is_none() {
let src = "module top; logic clk; endmodule\n";
assert_eq!(lookup(src, ""), None);
}
#[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)));
}
#[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)));
}
}