use std::collections::HashMap;
use vj_abi::ProcessFn;
use vj_codegen::assertion::{AssertionSpec, LeafRole};
use vj_codegen::error::LoweringError;
use vj_codegen::pipeline::CompiledPipeline;
use vj_codegen::process_lower::{SignalSlot, SignalSlotClass};
use vj_exec::KernelResolver;
use vj_sim::image::verif::{
AssertionImage, CasePairImage, FreeGroupImage, FreeVarImage, SampledImage, SampledSiteImage,
SpawnCallImage, VerifImage,
};
use vj_sim::netlist::InstanceId;
use vj_sim::symid::{SymbolId, SymbolUid};
pub(crate) struct KernelInfo<'a> {
pub name: &'a str,
pub frame_words: u32,
pub instance: InstanceId,
}
pub(crate) struct BodyInfo<'a, 'd> {
pub instance: InstanceId,
pub body: &'a sv_lang::Symbol<'d>,
pub signals: &'a HashMap<SymbolId, SignalSlot>,
pub port_sources: &'a crate::clocking::PortSources,
}
pub(crate) fn build<'a, 'd>(
assertions: &[AssertionSpec],
kernel: impl Fn(usize) -> Option<KernelInfo<'a>>,
bodies: &[BodyInfo<'_, 'd>],
trees: &[sv_lang::SyntaxTree],
covergroups: &crate::covergroups::CovPlan<'d>,
mut intern: impl FnMut(&str) -> u32,
) -> Result<VerifImage, LoweringError> {
let mut out = VerifImage {
covergroups: crate::covergroups::image(covergroups, &mut intern)?,
..VerifImage::default()
};
for spec in assertions {
let at = format!("assertion `{}`", spec.label);
let k = kernel(spec.plan_index)
.ok_or_else(|| LoweringError::unsupported("an assertion plan with no kernel", &at))?;
let name = &spec.name;
let ticks = (0..spec.n_clocks + spec.hist_clocks)
.map(|c| intern(&format!("{name}_t{c}")))
.collect();
let leaves: Vec<Option<u32>> = spec
.roles
.iter()
.enumerate()
.map(|(i, role)| match role {
LeafRole::Unused => None,
LeafRole::Bool | LeafRole::Value => Some(intern(&format!("{name}_l{i}"))),
})
.collect();
let mut live_leaves: Vec<Option<u32>> = vec![None; leaves.len()];
for &l in &spec.live_calls {
if let Some(slot) = live_leaves.get_mut(l as usize) {
*slot = Some(intern(&format!("{name}_lv{l}")));
}
}
let cases = spec
.case_pairs
.iter()
.enumerate()
.map(|(i, (selector, label))| CasePairImage {
selector: *selector,
label: *label,
symbol: intern(&format!("{name}_c{i}")),
})
.collect();
out.assertions.push(AssertionImage {
plan_index: spec.plan_index as u32,
instance: k.instance,
kernel: intern(k.name),
frame_words: k.frame_words,
label: spec.label.clone(),
kind: spec.kind,
ir: spec.ir.clone(),
n_clocks: spec.n_clocks as u32,
hist_clocks: spec.hist_clocks as u32,
leaf_reads: spec.leaf_reads.clone(),
leaf_writes: spec.leaf_writes.clone(),
activated: spec.activated,
disable_mixed: spec.disable_mixed,
tick_history: spec.tick_history,
captured: spec.captured.clone(),
history_depth: spec.history_depth as u32,
future: spec.future,
future_clock: spec.future_clock,
src_offset: spec.src_offset,
leaf_src: spec.leaf_src.clone(),
row_len: spec.row_len as u32,
snap: intern(&format!("{name}_snap")),
ticks,
leaves,
live_leaves,
cases,
site: spec.site.clone(),
end_cell: spec.end_cell,
current_words: spec
.current_words
.iter()
.map(|(id, first, n)| (id.key_string(), *first as u32, *n as u32))
.collect(),
observed_words: spec
.observed_words
.iter()
.map(|&(first, n)| (first as u32, n as u32))
.collect(),
coll_words: spec.coll_words.iter().map(|&w| w as u32).collect(),
coll_nests: spec.coll_nests.clone(),
spawn_calls: spec
.spawn_calls
.iter()
.map(|c| SpawnCallImage {
call: c.call,
kernel: intern(&c.kernel),
frame_words: c.frame_words,
entry_pc: c.entry_pc,
})
.collect(),
});
}
for b in bodies {
out.clocking.extend(crate::clocking::samplers(
b.body,
b.instance,
b.signals,
trees,
b.port_sources,
&mut intern,
)?);
for (_, vars) in vj_sim::netlist::free_var_groups(b.body) {
let rng = vj_sim::netlist::free_var_seed(&vars[0]) ^ 0x5eed;
let vars = vars
.iter()
.filter_map(|v| {
let info = b.signals.get(&v.uid())?;
Some(FreeVarImage {
id: v.uid().key_string(),
slot: info.slot,
width: info.width,
wide: info.class == SignalSlotClass::Wide,
})
})
.collect();
out.free_groups.push(FreeGroupImage {
instance: b.instance,
vars,
rng,
});
}
let at = b.body.hierarchical_path();
let hidden = vj_sim::sva::sampled::hidden_slots(b.body)
.map_err(|e| LoweringError::unsupported(&e.0, &at))?;
if hidden.is_empty() {
continue;
}
let slot_of = |id: &SymbolId| -> Result<u32, LoweringError> {
b.signals.get(id).map(|s| s.slot).ok_or_else(|| {
LoweringError::unsupported(
&format!(
"a sampled value function's hidden cell `{}` has no slot",
id.path()
),
&at,
)
})
};
let mut sites = Vec::new();
for site in &hidden.sites {
let mut shifts = Vec::with_capacity(site.shifts.len());
for step in &site.shifts {
shifts.push((slot_of(&step.from)?, slot_of(&step.to)?));
}
sites.push(SampledSiteImage {
guard: intern(&site.guard_name),
shifts,
});
}
let mut p_copies = Vec::with_capacity(hidden.p.len());
for p in &hidden.p {
if let Some(shape) = &p.foreign {
let Some(src) = b.signals.get(&p.source) else {
if shape.required {
return Err(LoweringError::unsupported(
&format!(
"a sampled value function over `{}`, a signal of another \
instance that this instance has no reference to",
p.source.path()
),
&at,
));
}
continue;
};
if b.signals.get(&p.id).is_none_or(|c| c.width != src.width) {
return Err(LoweringError::unsupported(
&format!(
"the global clocking's event signal `{}` has a different width in \
two instances of one module; a sampled value function's clock \
history needs one width (LRM 14.14)",
p.source.path()
),
&at,
));
}
}
p_copies.push((slot_of(&p.source)?, slot_of(&p.id)?));
}
out.sampled.push(SampledImage {
instance: b.instance,
sites,
p_copies,
});
}
Ok(out)
}
#[derive(Default)]
pub(crate) struct ScratchSymbols {
pub names: Vec<String>,
index: HashMap<String, u32>,
}
impl ScratchSymbols {
pub(crate) fn intern(&mut self, name: &str) -> u32 {
if let Some(&i) = self.index.get(name) {
return i;
}
let i = self.names.len() as u32;
self.names.push(name.to_owned());
self.index.insert(name.to_owned(), i);
i
}
}
pub(crate) struct PipelineResolver<'p>(pub &'p CompiledPipeline);
impl KernelResolver for PipelineResolver<'_> {
fn resolve(&self, _index: u32, name: &str) -> Option<ProcessFn> {
self.0.get_process_fn(name)
}
fn address(&self, _index: u32, name: &str) -> Option<usize> {
self.0.get_global_ptr(name).map(|p| p as usize)
}
}