use std::collections::HashMap;
use sv_lang::kinds::SymbolKind;
use sv_lang::{Symbol, SyntaxTree};
use vj_codegen::error::LoweringError;
use vj_codegen::pipeline::CovergroupLoweringSpec;
use vj_codegen::process_lower::{SignalSlot, to_state_edge_kind};
use vj_sim::image::verif::{ClockingImage, SKEW_EDGE_OPP, SKEW_STEP};
use vj_sim::netlist::InstanceId;
use vj_sim::state::EdgeKind as StateEdgeKind;
use vj_sim::symid::{SymbolId, SymbolUid};
pub(crate) type PortSources = HashMap<(InstanceId, SymbolId), (InstanceId, SymbolId, u32)>;
fn through_ports(
sources: &PortSources,
mut inst: InstanceId,
mut sig: SymbolId,
mut slot: u32,
) -> (InstanceId, u32) {
while let Some((pi, ps, pslot)) = sources.get(&(inst, sig.clone())) {
(inst, sig, slot) = (*pi, ps.clone(), *pslot);
}
(inst, slot)
}
pub(crate) fn default_clocking<'d>(body: &Symbol<'d>, trees: &[SyntaxTree]) -> Option<Symbol<'d>> {
let tree = trees.iter().find(|t| {
vj_sim::netlist::module_items(body)
.iter()
.any(|m| m.syntax(t).is_some())
})?;
vj_sim::state::default_clocking_of(body, tree)
.ok()
.flatten()
}
pub(crate) fn checker_inferred_clock<'d>(
proc_sym: &Symbol<'d>,
trees: &[SyntaxTree],
) -> Result<Option<vj_sim::process::cfg::InferredClockEvent<'d>>, String> {
let Some(body) = proc_sym
.parent()
.filter(|p| p.kind() == SymbolKind::CheckerInstanceBody)
else {
return Ok(None);
};
let Some(scope) = body
.checker_instance_body_parent_instance()
.and_then(|inst| inst.parent())
else {
return Ok(None);
};
let Some(tree) = trees.iter().find(|t| proc_sym.syntax(t).is_some()) else {
return Err("no syntax tree for a checker procedure or clocking block".to_string());
};
let found = vj_sim::state::default_clocking_of(&scope, tree)
.map_err(|e| format!("default clocking (LRM 14.12): {e:?}"))?;
let Some(event) = found.and_then(|cb| cb.clocking_block_event()) else {
return Ok(None);
};
let Some(tc) = event.as_timing_control() else {
return Err("a default clocking whose event is not a timing control".to_string());
};
let clock = vj_sim::sva::sampled::clock_event_of(tc).map_err(|e| e.0)?;
Ok(Some((clock.signal, clock.edge, clock.iff)))
}
pub(crate) fn mentions_global_clock(proc_sym: &Symbol<'_>) -> bool {
let mut stack: Vec<sv_lang::SemNode<'_>> = vj_sim::process::cfg::proc_body(proc_sym)
.map(|b| b.as_sem_node())
.into_iter()
.collect();
while let Some(n) = stack.pop() {
if n.as_expression()
.is_some_and(|e| vj_sim::process::cfg::is_global_clock_call(&e))
{
return true;
}
stack.extend(vj_sim::process::cfg::live_children(&n));
}
false
}
pub(crate) fn enclosing_global_clock_event<'d>(
proc_sym: &Symbol<'d>,
trees: &[SyntaxTree],
) -> Option<sv_lang::SemNode<'d>> {
if !mentions_global_clock(proc_sym) && !calls_gclk_sampled(proc_sym) {
return None;
}
let body = vj_sim::netlist::enclosing_instance_body(proc_sym)?;
let global = vj_sim::sva::global_clock::global_clocking_of(&body, trees).ok()??;
if global.levels_up == 0 {
return None;
}
global.block.clocking_block_event()
}
fn calls_gclk_sampled(proc_sym: &Symbol<'_>) -> bool {
vj_sim::sva::sampled::sites_of_block(proc_sym)
.is_ok_and(|sites| sites.iter().any(|s| s.call.global))
}
pub(crate) fn enclosing_global_clock_for_assertions<'d>(
body: &Symbol<'d>,
trees: &[SyntaxTree],
) -> Option<sv_lang::SemNode<'d>> {
let items = vj_sim::netlist::module_items(body);
let has_assertion = |m: &Symbol<'d>| {
m.kind() == SymbolKind::ClockingBlock
|| (m.kind() == SymbolKind::ProceduralBlock
&& vj_sim::process::cfg::proc_body(m).is_some_and(|b| {
let mut stack = vec![b.as_sem_node()];
while let Some(n) = stack.pop() {
if n.as_statement().is_some_and(|s| {
s.kind() == sv_lang::kinds::StatementKind::ConcurrentAssertion
}) {
return true;
}
stack.extend(vj_sim::process::cfg::live_children(&n));
}
false
}))
};
if !items.iter().any(has_assertion) {
return None;
}
let global = vj_sim::sva::global_clock::global_clocking_of(body, trees).ok()??;
if global.levels_up == 0 {
return None;
}
global.block.clocking_block_event()
}
pub(crate) fn global_clock_event<'d>(
proc_sym: &Symbol<'d>,
trees: &[SyntaxTree],
) -> Result<Option<vj_sim::process::cfg::InferredClockEvent<'d>>, String> {
if !mentions_global_clock(proc_sym) {
return Ok(None);
}
let Some(body) = vj_sim::netlist::enclosing_instance_body(proc_sym) else {
return Ok(None);
};
let Some(global) =
vj_sim::sva::global_clock::global_clocking_of(&body, trees).map_err(|e| e.0)?
else {
return Ok(None);
};
let Some(tc) = global
.block
.clocking_block_event()
.and_then(|n| n.as_timing_control())
else {
return Err("the global clocking declaration has no event".to_string());
};
let clock = vj_sim::sva::sampled::clock_event_of(tc).map_err(|e| e.0)?;
if clock.iff.is_some() {
return Err(
"a global clocking event with an `iff` condition as a procedural `@$global_clock` \
is not supported"
.to_string(),
);
}
Ok(Some((clock.signal, clock.edge, None)))
}
pub(crate) fn samplers<'d>(
body: &Symbol<'d>,
instance: InstanceId,
signals: &HashMap<SymbolId, SignalSlot>,
trees: &[SyntaxTree],
port_sources: &PortSources,
mut intern: impl FnMut(&str) -> u32,
) -> Result<Vec<ClockingImage>, LoweringError> {
let planned = plan(body, instance, signals, port_sources, trees)
.map_err(|m| LoweringError::unsupported(&m, "clocking block"))?;
let mut out = Vec::with_capacity(planned.len());
for (k, p) in planned.into_iter().enumerate() {
let mut terms = Vec::with_capacity(p.terms.len());
let mut term_owners = Vec::with_capacity(p.terms.len());
let prefix = gate_prefix(instance, k);
let gates = p
.gates
.iter()
.enumerate()
.map(|(i, (term, _))| {
(
*term,
intern(&vj_codegen::covergroup::gate_name(&prefix, i)),
)
})
.collect();
for (slot, owner, edge, sig) in p.terms {
term_owners.push(owner);
terms.push((
slot,
match edge {
sv_lang::EdgeKind::None => None,
sv_lang::EdgeKind::BothEdges => Some(StateEdgeKind::BothEdges),
e => Some(to_state_edge_kind(e, &sig)?),
},
));
}
out.push(ClockingImage {
instance,
terms,
gates,
samples: p.samples,
parts: p.parts,
skews: p.skews,
counter: p.counter,
term_owners,
});
}
Ok(out)
}
fn gate_prefix(instance: InstanceId, k: usize) -> String {
format!("vj_ckgate_{}_{k}", instance.0)
}
pub(crate) fn gate_specs<'d>(
bodies: &[(InstanceId, Symbol<'d>)],
instance_signals: &HashMap<InstanceId, HashMap<SymbolId, SignalSlot>>,
trees: &[SyntaxTree],
) -> Result<Vec<CovergroupLoweringSpec<'d>>, LoweringError> {
let mut out = Vec::new();
for (id, body) in bodies {
let signals = &instance_signals[id];
let planned = plan(body, *id, signals, &PortSources::new(), trees)
.map_err(|m| LoweringError::unsupported(&m, "clocking block"))?;
for (k, p) in planned.into_iter().enumerate() {
if p.gates.is_empty() {
continue;
}
out.push(CovergroupLoweringSpec {
prefix: gate_prefix(*id, k),
cg: p.block,
type_index: 0,
kernels: Vec::new(),
refs: Vec::new(),
signals: signals.clone(),
gates: p.gates.into_iter().map(|g| g.1).collect(),
event_values: Vec::new(),
});
}
}
Ok(out)
}
struct Planned<'d> {
block: Symbol<'d>,
terms: Vec<(u32, InstanceId, sv_lang::EdgeKind, Symbol<'d>)>,
gates: Vec<(u32, sv_lang::Expression<'d>)>,
samples: Vec<(u32, u32, u32)>,
skews: Vec<u64>,
parts: Vec<Vec<(u32, u32, u32, u32)>>,
counter: u32,
}
fn derived_parts(
e: sv_lang::Expression<'_>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Result<Vec<(u32, u32, u32, u32)>, String> {
use sv_lang::kinds::ExpressionKind as K;
let mut e = e;
while e.kind() == K::Conversion
&& let Some(o) = e.operand()
{
e = o;
}
let none = || Ok(Vec::new());
if e.kind() == K::Concatenation {
let mut out = Vec::new();
for o in e.operands() {
let p = derived_parts(o, signals)?;
if p.is_empty() {
return none();
}
out.extend(p);
}
return Ok(out);
}
if vj_sim::symid::is_value_name(e.kind()) {
let Some(s) = e
.referenced_symbol()
.and_then(|s| signals.get(&s.uid()).copied())
else {
return none();
};
return Ok(vec![(s.slot, s.width, 0, s.width)]);
}
if !matches!(e.kind(), K::ElementSelect | K::RangeSelect) {
return none();
}
let Some(base) = e
.select_value()
.filter(|b| vj_sim::symid::is_value_name(b.kind()))
else {
return none();
};
let Some(s) = base
.referenced_symbol()
.and_then(|s| signals.get(&s.uid()).copied())
else {
return none();
};
if matches!(
s.class,
vj_codegen::process_lower::SignalSlotClass::Memory { .. }
) {
return none();
}
let (left, right) = match base.expr_type() {
Some(t) if t.is_integral() && !t.is_struct() && !t.is_union() => {
let r = t.bit_vector_range();
(i64::from(r.left), i64::from(r.right))
}
_ => (i64::from(s.width) - 1, 0),
};
let off = |x: sv_lang::Expression<'_>| -> Option<u32> {
let idx = x.constant_value()?.as_i64()?;
let o = if left >= right {
idx - right
} else {
right - idx
};
(0..i64::from(s.width)).contains(&o).then_some(o as u32)
};
if e.kind() == K::ElementSelect {
return match e.selector().and_then(off) {
Some(o) => Ok(vec![(s.slot, s.width, o, 1)]),
None => none(),
};
}
let simple = e.range_selection_kind().is_some_and(|k| {
vj_codegen::expr::classify_range_selection_kind(k)
== Ok(vj_codegen::expr::RangeSelectionKindClass::Simple)
});
match (e.range_left().and_then(off), e.range_right().and_then(off)) {
(Some(a), Some(b)) if simple => Ok(vec![(s.slot, s.width, a.min(b), a.abs_diff(b) + 1)]),
_ => none(),
}
}
fn input_skew(
cb: &Symbol<'_>,
v: &Symbol<'_>,
tick: i32,
event_edge: Option<sv_lang::EdgeKind>,
) -> Result<u64, String> {
let mut skew = v.clock_var_input_skew();
if !skew.has_value() {
skew = cb.clocking_block_default_input_skew();
}
if skew.edge != sv_lang::EdgeKind::None {
if skew.delay.is_some() {
return Err(format!(
"clock variable {}'s input skew combines an edge and a delay (LRM 14.4)",
v.name()
));
}
return match (event_edge, skew.edge) {
(Some(e), k @ (sv_lang::EdgeKind::PosEdge | sv_lang::EdgeKind::NegEdge)) => {
Ok(if e == k { 0 } else { SKEW_EDGE_OPP })
}
_ => Err(format!(
"clock variable {}'s edge input skew needs a clocking event of one `posedge` or \
`negedge` of a signal and a `posedge`/`negedge` skew (LRM 14.4)",
v.name()
)),
};
}
let Some(tc) = skew.delay.and_then(|n| n.as_timing_control()) else {
return Ok(SKEW_STEP);
};
if tc.kind() != sv_lang::kinds::TimingControlKind::Delay {
return Ok(SKEW_STEP);
}
let expr = tc
.children()
.into_iter()
.find_map(|n| n.as_expression())
.ok_or_else(|| format!("clock variable {}'s input skew has no value", v.name()))?;
let cv = expr.constant_value().ok_or_else(|| {
format!(
"clock variable {}'s input skew is not a constant (LRM 14.4)",
v.name()
)
})?;
let value =
match &cv {
sv_lang::ConstantValue::Real(f) => *f,
other => other.as_integer().and_then(|i| i.as_i64()).ok_or_else(|| {
format!("clock variable {}'s input skew is not a number", v.name())
})? as f64,
};
vj_codegen::timescale::delay_ticks(value, cb, tick).map_err(|e| format!("{e}"))
}
fn plan<'d>(
body: &Symbol<'d>,
instance: InstanceId,
signals: &HashMap<SymbolId, SignalSlot>,
port_sources: &PortSources,
trees: &[SyntaxTree],
) -> Result<Vec<Planned<'d>>, String> {
let mut out = Vec::new();
for (cb, vars) in vj_sim::state::clocking_blocks(body) {
let event = cb
.clocking_block_event()
.and_then(|n| n.as_timing_control())
.ok_or_else(|| format!("clocking block {} has no event", cb.name()))?;
let event = if vj_sim::sva::sampled::is_global_clock_event(event) {
let scope = vj_sim::netlist::enclosing_instance_body(&cb)
.ok_or_else(|| format!("clocking block {} is in no instance", cb.name()))?;
match vj_sim::sva::global_clock::global_clocking_of(&scope, trees).map_err(|e| e.0)? {
Some(g) => g
.block
.clocking_block_event()
.and_then(|n| n.as_timing_control())
.ok_or_else(|| "the global clocking declaration has no event".to_string())?,
None => {
return Err(format!(
"clocking block {}'s `$global_clock` has no global clocking declaration \
in scope (LRM 14.14)",
cb.name()
));
}
}
} else {
event
};
let mut terms = Vec::new();
let mut guards: Vec<(u32, sv_lang::Expression<'d>)> = Vec::new();
let members: Vec<sv_lang::TimingControl<'d>> =
if event.kind() == sv_lang::kinds::TimingControlKind::EventList {
event
.children()
.into_iter()
.filter_map(|n| n.as_timing_control())
.collect()
} else {
vec![event]
};
for tc in members {
let event_expr = tc.children().into_iter().find_map(|n| n.as_expression());
let (sig_expr, edge) = if event_expr
.as_ref()
.is_some_and(vj_sim::process::cfg::is_inferred_clock_call)
{
match checker_inferred_clock(&cb, trees)? {
Some((e, edge, iff)) => {
guards.extend(iff.into_iter().map(|g| (terms.len() as u32, g)));
(Some(e), edge)
}
None => {
terms.clear();
break;
}
}
} else {
if tc.kind() == sv_lang::kinds::TimingControlKind::SignalEvent {
let n = terms.len() as u32;
guards.extend(
vj_sim::sva::sampled::clock_event_of(tc)
.map_err(|e| e.0)?
.iff
.into_iter()
.map(|g| (n, g)),
);
}
(event_expr, tc.edge())
};
let sig = sig_expr
.and_then(|e| e.referenced_symbol())
.ok_or_else(|| format!("clocking block {}'s event is not a signal", cb.name()))?;
let slot = signals
.get(&sig.uid())
.ok_or_else(|| format!("clocking event signal {} has no slot", sig.name()))?;
let (owner, slot) = if guards.is_empty() {
through_ports(port_sources, instance, sig.uid(), slot.slot)
} else {
(instance, slot.slot)
};
terms.push((slot, owner, edge, sig));
}
if terms.is_empty() {
continue;
}
let gates = guards;
let mut samples = Vec::new();
let mut skews = Vec::new();
let mut parts = Vec::new();
let tick = vj_codegen::timescale::design_tick_exp(&cb);
let event_edge = (terms.len() == 1).then(|| terms[0].2);
for v in vars {
let init = v.initializer();
let dst = signals[&v.uid()];
let (src, derived) = match init.filter(|e| vj_sim::symid::is_value_name(e.kind())) {
Some(e) => (
e.referenced_symbol()
.and_then(|s| signals.get(&s.uid()).copied())
.map(|s| (s.slot, s.width)),
Vec::new(),
),
None => {
let ps = init.map(|e| derived_parts(e, signals)).transpose()?;
match ps.filter(|p| !p.is_empty()) {
Some(ps) => (Some((ps[0].0, dst.width)), ps),
None => (None, Vec::new()),
}
}
};
let src = src.ok_or_else(|| {
format!(
"clock variable {} is not bound to a signal, a constant select of one or a \
concatenation of those (only `input name`, `input name = signal`, \
`= sig[i]`, `= sig[a:b]` and `= {{..}}` are supported)",
v.name()
)
})?;
samples.push((src.0, dst.slot, src.1));
parts.push(derived);
skews.push(input_skew(&cb, &v, tick, event_edge)?);
}
out.push(Planned {
block: cb,
terms,
gates,
samples,
parts,
skews,
counter: signals[&cb.uid()].slot,
});
}
Ok(out)
}