use std::cell::RefCell;
use std::collections::HashMap;
use inkwell::AddressSpace;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::values::{FunctionValue, IntValue, PointerValue};
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use sv_lang::{Expression, SemNode, Symbol, SyntaxTree};
use sv_sim_runtime::covergroup_rt::{
CELL_WORDS, CovOp, ITEM_COVERPOINT, ITEM_CROSS, ITEM_GROUP, OPTION_NAMES, SV_RT_COV_SYMBOL,
TAG_LIST, TAG_REAL, TAG_SIGNED, TAG_STRING, TAG_UNSIGNED,
};
use sv_sim_sim::covergroup::decl::{
ArrayKind, BinBody, BinDecl, BinKind, Bound, CovGroupDecl, CoverpointDecl, CrossBinDecl,
CrossDecl, Matches, RangeItem, RepeatRange, SelectExpr, TransItem, TransRep,
};
use sv_sim_sim::covergroup::value::{Expr, KernelId, MAX_WIDTH, TypeInfo, Val};
use sv_sim_sim::symid::{SymbolId, SymbolUid};
use crate::accessor::AccessorFns;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, MemAccessorCtx, entry_alloca, lower_expr, store_to};
use crate::ops::OpBuilder;
use crate::process_lower::SignalSlot;
use crate::types::TypeWidth;
use crate::value::{PackedValue, RealValue, ValueRepr};
#[derive(Clone, Debug)]
pub struct CovKernel<'d> {
pub expr: Expression<'d>,
pub binds: Vec<Symbol<'d>>,
pub list: bool,
pub tuple: bool,
}
#[derive(Debug)]
pub struct CovExtract<'d> {
pub decl: CovGroupDecl,
pub kernels: Vec<CovKernel<'d>>,
pub formals: Vec<Symbol<'d>>,
pub sample_formals: Vec<Symbol<'d>>,
pub event: Option<SemNode<'d>>,
pub strobe: bool,
pub class_event: Option<ClassEvent>,
}
#[derive(Clone, Debug)]
pub struct ClassEvent {
pub terms: Vec<(KernelId, Option<sv_sim_sim::state::EdgeKind>)>,
pub gates: Vec<(u32, KernelId)>,
}
fn err(msg: &str, at: &Symbol<'_>) -> LoweringError {
LoweringError::unsupported_at(msg, at)
}
pub fn type_key(cg: &Symbol<'_>) -> String {
let mut names = vec![cg.name().to_string()];
let mut scope = cg.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::InstanceBody {
let def = s
.instance_body_definition()
.map(|d| d.name().to_string())
.unwrap_or_else(|| s.name().to_string());
names.push(def);
break;
}
if [
SymbolKind::Package,
SymbolKind::ClassType,
SymbolKind::CompilationUnit,
]
.contains(&s.kind())
{
names.push(s.name().to_string());
break;
}
if !s.name().is_empty() {
names.push(s.name().to_string());
}
scope = s.parent();
}
names.reverse();
names.join("::")
}
pub fn is_class_embedded(cg: &Symbol<'_>) -> bool {
let mut scope = cg.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::ClassType {
return true;
}
if [
SymbolKind::InstanceBody,
SymbolKind::Package,
SymbolKind::CompilationUnit,
]
.contains(&s.kind())
{
return false;
}
scope = s.parent();
}
false
}
pub fn class_covergroups<'d>(scope: Symbol<'d>) -> Vec<Symbol<'d>> {
let mut out = Vec::new();
let mut stack = vec![scope];
while let Some(s) = stack.pop() {
for m in s.members() {
if m.kind() == SymbolKind::ClassType {
for c in m.members() {
if c.kind() == SymbolKind::CovergroupType {
out.push(c);
}
}
stack.push(m);
} else if [SymbolKind::GenerateBlock, SymbolKind::GenerateBlockArray]
.contains(&m.kind())
{
stack.push(m);
}
}
}
out
}
pub fn declared_covergroups<'d>(body: Symbol<'d>) -> Vec<Symbol<'d>> {
sv_sim_sim::netlist::module_items(&body)
.into_iter()
.filter(|m| m.kind() == SymbolKind::CovergroupType)
.collect()
}
fn body_of<'d>(cg: Symbol<'d>) -> Option<Symbol<'d>> {
cg.members()
.find(|m| m.kind() == SymbolKind::CovergroupBody)
}
type OptionList = Vec<(String, Expr)>;
struct Extractor<'d, 't> {
cg: Symbol<'d>,
formals: Vec<Symbol<'d>>,
sample_formals: Vec<Symbol<'d>>,
kernels: Vec<CovKernel<'d>>,
trees: &'t [SyntaxTree],
}
impl<'d> Extractor<'d, '_> {
fn class_event(&mut self, ev: SemNode<'d>) -> Result<Option<ClassEvent>, LoweringError> {
use sv_lang::kinds::TimingControlKind as Tk;
let Some(tc) = ev.as_timing_control() else {
return Ok(None);
};
let members: Vec<sv_lang::TimingControl<'d>> = if tc.kind() == Tk::EventList {
tc.children()
.into_iter()
.filter_map(|n| n.as_timing_control())
.collect()
} else {
vec![tc]
};
let is_prop = |t: &sv_lang::TimingControl<'d>| {
t.kind() != Tk::BlockEventList
&& t.children()
.into_iter()
.find_map(|n| n.as_expression())
.and_then(|e| e.referenced_symbol())
.is_some_and(|s| s.kind() == SymbolKind::ClassProperty)
};
let n = members.iter().filter(|t| is_prop(t)).count();
if n == 0 {
return Ok(None);
}
if n != members.len() {
return Err(err(
&format!(
"covergroup {}'s sampling event mixes class properties with other \
signals (not supported; use one or the other) (LRM 19.4)",
self.cg.name()
),
&self.cg,
));
}
let mut terms = Vec::new();
let mut gates = Vec::new();
for t in members {
let mut exprs = t.children().into_iter().filter_map(|n| n.as_expression());
let sig = exprs.next().expect("is_prop saw the term's expression");
let Expr::Kernel(k) = self.kernel(sig, &[]) else {
unreachable!("kernel() returns a kernel")
};
for g in exprs {
if let Expr::Kernel(gk) = self.kernel(g, &[]) {
gates.push((terms.len() as u32, gk));
}
}
use sv_sim_sim::state::EdgeKind as Se;
let edge = match t.edge() {
sv_lang::EdgeKind::None => None,
sv_lang::EdgeKind::BothEdges => Some(Se::BothEdges),
sv_lang::EdgeKind::PosEdge => Some(Se::Posedge),
sv_lang::EdgeKind::NegEdge => Some(Se::Negedge),
};
terms.push((k, edge));
}
Ok(Some(ClassEvent { terms, gates }))
}
fn kernel(&mut self, expr: Expression<'d>, extra: &[Symbol<'d>]) -> Expr {
let mut binds = self.formals.clone();
binds.extend_from_slice(extra);
self.kernels.push(CovKernel {
expr,
binds,
list: false,
tuple: false,
});
Expr::Kernel((self.kernels.len() - 1) as KernelId)
}
fn list_kernel(&mut self, expr: Expression<'d>) -> Expr {
let e = self.kernel(expr, &[]);
if let Expr::Kernel(id) = e {
self.kernels[id as usize].list = true;
}
e
}
fn tuple_kernel(&mut self, expr: Expression<'d>) -> Expr {
let e = self.list_kernel(expr);
if let Expr::Kernel(id) = e {
self.kernels[id as usize].tuple = true;
}
e
}
fn ctor_value(&mut self, e: Expression<'d>, ty: Option<&TypeInfo>, wildcard: bool) -> Expr {
match const_val(&e, ty, wildcard) {
Some(v) => Expr::Const(v),
None => {
let mut e = e;
if ty.is_some_and(|t| !t.real) && !wildcard {
while e.kind() == ExpressionKind::Conversion {
match e.conversion_operand() {
Some(o)
if o.expr_type().is_some_and(|t| t.canonical().is_integral()) =>
{
e = o;
}
_ => break,
}
}
}
self.kernel(e, &[])
}
}
}
fn sample_value(&mut self, e: Expression<'d>) -> Expr {
let extra = self.sample_formals.clone();
self.kernel(e, &extra)
}
fn option_list(
&mut self,
opts: impl Iterator<Item = sv_lang::CoverageOption<'d>>,
strobe: &mut bool,
) -> Result<(OptionList, OptionList), LoweringError> {
let mut options = Vec::new();
let mut type_options = Vec::new();
for o in opts {
let name = o.name().to_string();
let e = o
.expression()
.ok_or_else(|| err(&format!("option `{name}` has no value"), &self.cg))?;
let e = if e.kind() == ExpressionKind::Assignment {
e.right()
.ok_or_else(|| err("option assignment has no value", &self.cg))?
} else {
e
};
let v = if matches!(name.as_str(), "name" | "comment") {
match const_string(&e) {
Some(s) => Expr::Const(Val::Str(s)),
None => self.kernel(e, &[]),
}
} else {
self.ctor_value(e, None, false)
};
if name == "strobe" && v != Expr::Const(Val::Int(0)) {
*strobe = true;
}
if o.is_type_option() {
type_options.push((name, v));
} else {
options.push((name, v));
}
}
Ok((options, type_options))
}
fn range_op(&self, v: Expression<'d>) -> Option<String> {
use sv_lang::AstNode;
use sv_lang::nodes::ValueRangeExpressionSyntax;
self.trees.iter().find_map(|tree| {
let node = v.syntax(tree)?;
let r = ValueRangeExpressionSyntax::cast(node)?;
Some(r.op()?.text().to_string())
})
}
fn tolerance_range(
&mut self,
v: Expression<'d>,
op: &str,
ty: &TypeInfo,
) -> Result<RangeItem, LoweringError> {
let (Some(l), Some(r)) = (v.left(), v.right()) else {
return Err(err("value range without bounds", &self.cg));
};
let (a, b) = (
const_val(&l, Some(ty), false),
const_val(&r, Some(ty), false),
);
let pct = op == "+%-";
let bounds = match (a, b) {
(Some(Val::Int(a)), Some(Val::Int(b))) if !ty.real => {
let d = if pct { a.wrapping_mul(b) / 100 } else { b };
Some((Val::Int(a - d), Val::Int(a + d)))
}
(Some(a), Some(b)) => {
let f = |v: &Val| match v {
Val::Int(i) => Some(*i as f64),
Val::Real(x) => Some(*x),
_ => None,
};
match (f(&a), f(&b)) {
(Some(a), Some(b)) => {
let d = if pct { a * b / 100.0 } else { b };
Some((Val::Real(a - d), Val::Real(a + d)))
}
_ => None,
}
}
_ => None,
};
match bounds {
Some((lo, hi)) => Ok(RangeItem::Range(
Bound::Expr(Expr::Const(lo)),
Bound::Expr(Expr::Const(hi)),
)),
None => Err(err(
&format!(
"a `{op}` value range (19.5.1, 11.4.13) with non-constant or `$` operands"
),
&self.cg,
)),
}
}
fn range_items(
&mut self,
values: impl Iterator<Item = Expression<'d>>,
ty: &TypeInfo,
wildcard: bool,
) -> Result<Vec<RangeItem>, LoweringError> {
let mut items = Vec::new();
for v in values {
if v.kind() == ExpressionKind::ValueRange {
if let Some(op @ ("+/-" | "+%-")) = self.range_op(v).as_deref() {
items.push(self.tolerance_range(v, op, ty)?);
continue;
}
let bound = |x: &mut Self, b: Option<Expression<'d>>| -> Bound {
match b {
Some(e) if e.kind() == ExpressionKind::UnboundedLiteral => Bound::Dollar,
Some(e) => Bound::Expr(x.ctor_value(e, Some(ty), false)),
None => Bound::Dollar,
}
};
let lo = bound(self, v.left());
let hi = bound(self, v.right());
items.push(RangeItem::Range(lo, hi));
} else {
items.push(RangeItem::Single(self.ctor_value(v, Some(ty), wildcard)));
}
}
Ok(items)
}
fn repeat(&mut self, from: Option<Expression<'d>>, to: Option<Expression<'d>>) -> RepeatRange {
let lo = from.map_or(Expr::int(1), |e| self.ctor_value(e, None, false));
let hi = to.map(|e| self.ctor_value(e, None, false));
RepeatRange { lo, hi }
}
fn bin(&mut self, b: Symbol<'d>, ty: &TypeInfo) -> Result<BinDecl, LoweringError> {
let kind = match b.coverage_bin_kind() {
sv_lang::CoverageBinKind::Bins => BinKind::Normal,
sv_lang::CoverageBinKind::IgnoreBins => BinKind::Ignore,
sv_lang::CoverageBinKind::IllegalBins => BinKind::Illegal,
};
let wildcard = b.coverage_bin_is_wildcard();
let array = if b.coverage_bin_is_array() {
match b.coverage_bin_number_of_bins_expr() {
Some(n) => ArrayKind::Fixed(self.ctor_value(n, None, false)),
None => ArrayKind::Open,
}
} else {
ArrayKind::None
};
let body = if b.coverage_bin_is_default_sequence() {
BinBody::DefaultSeq
} else if b.coverage_bin_is_default() {
BinBody::Default
} else if b.coverage_bin_trans_sets().next().is_some() {
let mut sets = Vec::new();
for set in b.coverage_bin_trans_sets() {
let mut seq = Vec::new();
for r in set.ranges() {
let values = self.range_items(r.items(), ty, wildcard)?;
let rep = match r.repeat_kind() {
sv_lang::RepeatKind::None => TransRep::Once,
sv_lang::RepeatKind::Consecutive => {
TransRep::Consecutive(self.repeat(r.repeat_from(), r.repeat_to()))
}
sv_lang::RepeatKind::Nonconsecutive => {
TransRep::NonConsecutive(self.repeat(r.repeat_from(), r.repeat_to()))
}
sv_lang::RepeatKind::GoTo => {
TransRep::Goto(self.repeat(r.repeat_from(), r.repeat_to()))
}
};
seq.push(TransItem { values, rep });
}
sets.push(seq);
}
BinBody::Trans(sets)
} else if let Some(set) = b.coverage_bin_set_coverage_expr() {
match const_list(&set, ty) {
Some(v) => BinBody::Set(Expr::Const(v)),
None if crate::collections::coll_of_expr(&set).is_some_and(|c| !c.assoc) => {
BinBody::Set(self.list_kernel(set))
}
None => {
return Err(err(
"a coverpoint bin set expression (19.5.1.2) that is neither constant \
nor a queue or dynamic array",
&b,
));
}
}
} else {
let mut items = self.range_items(b.coverage_bin_values(), ty, wildcard)?;
let with = match b.coverage_bin_with_expr() {
Some(w) => {
if items.is_empty() {
items.push(RangeItem::Range(Bound::Dollar, Bound::Dollar));
}
let item = iterator_in(&w).into_iter().collect::<Vec<_>>();
let item = if item.is_empty() {
vec![self.cg]
} else {
item
};
Some(self.kernel(w, &item))
}
None => None,
};
BinBody::Ranges { items, with }
};
let iff = b.coverage_bin_iff_expr().map(|e| self.sample_value(e));
Ok(BinDecl {
name: b.name().to_string(),
kind,
array,
wildcard,
iff,
body,
})
}
fn coverpoint(
&mut self,
cp: Symbol<'d>,
strobe: &mut bool,
) -> Result<CoverpointDecl, LoweringError> {
let e = cp
.coverpoint_coverage_expr()
.ok_or_else(|| err("coverpoint has no expression", &cp))?;
let ety = e
.expr_type()
.ok_or_else(|| err("coverpoint expression has no type", &cp))?;
let ty = if ety.is_floating() {
TypeInfo::real()
} else if ety.is_integral() {
let w = ety.bit_width() as u32;
if w > MAX_WIDTH {
return Err(err(
&format!(
"a coverpoint expression wider than {MAX_WIDTH} bits ({w} bits; D4 of the \
coverage plan)"
),
&cp,
));
}
TypeInfo {
width: w,
signed: ety.is_signed(),
real: false,
}
} else {
return Err(err(
"a coverpoint expression that is neither integral nor real (19.5)",
&cp,
));
};
let enum_values = enum_values_of(&e);
let expr = self.sample_value(e);
let iff = cp.coverpoint_iff_expr().map(|g| self.sample_value(g));
let mut bins = Vec::new();
for m in cp.members() {
if m.kind() == SymbolKind::CoverageBin {
bins.push(self.bin(m, &ty)?);
}
}
let (options, type_options) = self.option_list(cp.covergroup_options(), strobe)?;
Ok(CoverpointDecl {
name: cp.name().to_string(),
ty,
expr,
iff,
bins,
enum_values,
options,
type_options,
})
}
fn select(
&mut self,
node: SemNode<'d>,
items: &[Symbol<'d>],
at: &Symbol<'d>,
) -> Result<SelectExpr, LoweringError> {
use sv_lang::kinds::BinsSelectExprKind as K;
let Some(sel) = node.as_bins_select_expr() else {
return Err(err("cross bin select is not a select expression", at));
};
let children = sel.children();
let sub = |x: &mut Self, i: usize| -> Result<SelectExpr, LoweringError> {
let c = *children
.get(i)
.ok_or_else(|| err("cross select expression is missing an operand", at))?;
x.select(c, items, at)
};
Ok(match sel.kind() {
K::Condition => {
let (cp, bin) = self.binsof_target(&sel, at)?;
let ty = items
.iter()
.find(|s| s.name() == cp)
.and_then(|s| s.coverpoint_coverage_expr())
.and_then(|e| e.expr_type())
.map(|t| TypeInfo {
width: t.bit_width() as u32,
signed: t.is_signed(),
real: t.is_floating(),
})
.unwrap_or_else(|| TypeInfo::unsigned(64));
let values: Vec<Expression<'d>> =
children.iter().filter_map(|n| n.as_expression()).collect();
let intersect = if values.is_empty() {
None
} else {
Some(self.range_items(values.into_iter(), &ty, false)?)
};
SelectExpr::Binsof { cp, bin, intersect }
}
K::Unary => SelectExpr::Not(Box::new(sub(self, 0)?)),
K::Binary => {
let l = sub(self, 0)?;
let r = sub(self, 1)?;
if self.binary_is_or(&sel) {
SelectExpr::Or(Box::new(l), Box::new(r))
} else {
SelectExpr::And(Box::new(l), Box::new(r))
}
}
K::CrossId => SelectExpr::All,
K::WithFilter => {
let inner = children
.iter()
.find(|c| c.as_bins_select_expr().is_some())
.copied()
.ok_or_else(|| err("`with` select has no operand", at))?;
let inner = self.select(inner, items, at)?;
let exprs: Vec<Expression<'d>> =
children.iter().filter_map(|n| n.as_expression()).collect();
let filter = *exprs
.first()
.ok_or_else(|| err("`with` select has no filter expression", at))?;
let matches = match exprs.get(1) {
None => Matches::one(),
Some(m) if m.kind() == ExpressionKind::UnboundedLiteral => Matches::All,
Some(m) => Matches::Count(self.ctor_value(*m, None, false)),
};
let reads = crate::process_lower::named_reads(&[filter.as_sem_node()]);
let binds: Vec<Symbol<'d>> = items
.iter()
.map(|it| {
reads
.iter()
.find(|r| r.name() == it.name() && r.uid() != it.uid())
.copied()
.unwrap_or(*it)
})
.collect();
let expr = self.kernel(filter, &binds);
SelectExpr::With {
sel: Box::new(inner),
expr,
matches,
}
}
K::SetExpr => {
let exprs: Vec<Expression<'d>> =
children.iter().filter_map(|n| n.as_expression()).collect();
let set = *exprs
.first()
.ok_or_else(|| err("cross set expression has no expression", at))?;
let matches = match exprs.get(1) {
None => Matches::one(),
Some(m) if m.kind() == ExpressionKind::UnboundedLiteral => Matches::All,
Some(m) => Matches::Count(self.ctor_value(*m, None, false)),
};
let tuples = match set.constant_value().and_then(|c| const_tuple_list(&c)) {
Some(t) => Expr::Const(t),
None => self.tuple_kernel(set),
};
SelectExpr::Set {
expr: tuples,
matches,
}
}
other => {
return Err(err(
&format!("cross bin select expression of kind {other:?}"),
at,
));
}
})
}
fn binsof_target(
&self,
sel: &sv_lang::BinsSelectExpr<'d>,
at: &Symbol<'d>,
) -> Result<(String, Option<String>), LoweringError> {
use sv_lang::AstNode;
use sv_lang::nodes::BinsSelectConditionExprSyntax;
for tree in self.trees {
let Some(node) = sel.syntax(tree) else {
continue;
};
let Some(cond) = BinsSelectConditionExprSyntax::cast(node) else {
continue;
};
let text: String = cond
.name()
.syntax()
.text()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
let mut parts = text.splitn(2, '.');
let cp = parts.next().unwrap_or_default().to_string();
let bin = parts.next().map(str::to_string);
return Ok((cp, bin));
}
Err(err("cannot find the source of a `binsof` select", at))
}
fn binary_is_or(&self, sel: &sv_lang::BinsSelectExpr<'d>) -> bool {
use sv_lang::AstNode;
use sv_lang::nodes::BinaryBinsSelectExprSyntax;
for tree in self.trees {
if let Some(node) = sel.syntax(tree)
&& let Some(b) = BinaryBinsSelectExprSyntax::cast(node)
{
return b.op().is_some_and(|t| t.text() == "||");
}
}
false
}
fn cross(&mut self, x: Symbol<'d>, strobe: &mut bool) -> Result<CrossDecl, LoweringError> {
let items: Vec<Symbol<'d>> = x.cover_cross_targets().collect();
let iff = x.cover_cross_iff_expr().map(|g| self.sample_value(g));
let mut bins = Vec::new();
let body = x
.members()
.find(|m| m.kind() == SymbolKind::CoverCrossBody)
.unwrap_or(x);
for m in body.members() {
if m.kind() != SymbolKind::CoverageBin {
continue;
}
let kind = match m.coverage_bin_kind() {
sv_lang::CoverageBinKind::Bins => BinKind::Normal,
sv_lang::CoverageBinKind::IgnoreBins => BinKind::Ignore,
sv_lang::CoverageBinKind::IllegalBins => BinKind::Illegal,
};
let sel = m
.coverage_bin_cross_select_expr()
.ok_or_else(|| err("cross bin has no select expression", &m))?;
let select = self.select(sel, &items, &m)?;
let iff = m.coverage_bin_iff_expr().map(|g| self.sample_value(g));
bins.push(CrossBinDecl {
name: m.name().to_string(),
kind,
select,
iff,
});
}
let (options, type_options) = self.option_list(x.cover_cross_options(), strobe)?;
Ok(CrossDecl {
name: x.name().to_string(),
items: items.iter().map(|s| s.name().to_string()).collect(),
iff,
bins,
options,
type_options,
})
}
}
pub fn extract<'d>(cg: Symbol<'d>, trees: &[SyntaxTree]) -> Result<CovExtract<'d>, LoweringError> {
let body = body_of(cg).ok_or_else(|| err("covergroup has no body", &cg))?;
let base = match cg.as_type().and_then(|t| t.covergroup_base_group()) {
Some(b) if b.is_covergroup() => Some(extract(b.as_symbol(), trees)?),
Some(_) => {
return Err(err(
"`covergroup extends` names no covergroup of a base class",
&cg,
));
}
None => None,
};
let formals: Vec<Symbol<'d>> = match &base {
Some(b) => b.formals.clone(),
None => cg
.members()
.filter(|m| m.kind() == SymbolKind::FormalArgument)
.collect(),
};
let sample_formals: Vec<Symbol<'d>> = match &base {
Some(b) => b.sample_formals.clone(),
None => body
.members()
.find(|m| m.kind() == SymbolKind::Subroutine && m.name() == "sample")
.map(|s| s.subroutine_arguments().collect())
.unwrap_or_default(),
};
let mut x = Extractor {
cg,
formals: formals.clone(),
sample_formals: sample_formals.clone(),
kernels: base.as_ref().map(|b| b.kernels.clone()).unwrap_or_default(),
trees,
};
let mut strobe = base.as_ref().is_some_and(|b| b.strobe);
let mut decl = match &base {
Some(b) => b.decl.clone(),
None => CovGroupDecl::new(cg.name()),
};
let opts: Vec<sv_lang::CoverageOption<'d>> = body
.covergroup_options()
.chain(cg.covergroup_options())
.collect();
let (options, type_options) = x.option_list(opts.into_iter(), &mut strobe)?;
for (name, v) in options {
match decl.options.iter_mut().find(|(n, _)| *n == name) {
Some(slot) => slot.1 = v,
None => decl.options.push((name, v)),
}
}
for (name, v) in type_options {
match decl.type_options.iter_mut().find(|(n, _)| *n == name) {
Some(slot) => slot.1 = v,
None => decl.type_options.push((name, v)),
}
}
for m in body.members() {
if m.kind() == SymbolKind::Coverpoint {
let cp = x.coverpoint(m, &mut strobe)?;
match decl
.coverpoints
.iter_mut()
.find(|c| !cp.name.is_empty() && c.name == cp.name)
{
Some(slot) => *slot = cp,
None => decl.coverpoints.push(cp),
}
} else if m.kind() == SymbolKind::CoverCross {
let c = x.cross(m, &mut strobe)?;
match decl
.crosses
.iter_mut()
.find(|k| !c.name.is_empty() && k.name == c.name)
{
Some(slot) => *slot = c,
None => decl.crosses.push(c),
}
}
}
let event = match &base {
Some(b) => b.event,
None => cg.as_type().and_then(|t| t.covergroup_coverage_event()),
};
let class_event = match event {
Some(ev) if is_class_embedded(&cg) => x.class_event(ev)?,
_ => None,
};
Ok(CovExtract {
decl,
kernels: x.kernels,
formals,
sample_formals,
event,
strobe,
class_event,
})
}
fn iterator_in<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& let Some(s) = x.referenced_symbol()
&& s.kind() == SymbolKind::Iterator
{
return Some(s);
}
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
}
None
}
fn enum_values_of(e: &Expression<'_>) -> Option<Vec<(String, i128)>> {
let mut cur = *e;
loop {
if let Some(t) = cur.expr_type()
&& t.canonical().is_enum()
{
let t = t.canonical();
let mut out = Vec::new();
for m in t.enum_members() {
let v = m
.constant_value()
.and_then(|c| c.as_integer().map(|i| sv_int_i128(i, t.is_signed())))
.flatten()?;
out.push((m.name().to_string(), v));
}
return Some(out);
}
cur = if cur.kind() == ExpressionKind::Conversion {
cur.conversion_operand()?
} else {
return None;
};
}
}
fn sv_int_i128(i: &sv_lang::SVInt, signed: bool) -> Option<i128> {
if i.has_unknown() {
return None;
}
if signed {
i.as_i64().map(i128::from)
} else {
i.as_u64().map(i128::from)
}
}
fn const_tuple_list(c: &sv_lang::ConstantValue) -> Option<Val> {
fn scalar(c: &sv_lang::ConstantValue) -> Option<Val> {
if let Some(i) = c.as_integer() {
if i.has_unknown() {
return Some(Val::Unknown);
}
return sv_int_i128(i, i.is_signed()).map(Val::Int);
}
if let Some(f) = c.as_f64() {
return Some(Val::Real(f));
}
c.as_str().map(|s| Val::Str(s.to_string()))
}
let tuples = c
.as_array()?
.iter()
.map(|t| {
t.as_array()
.and_then(|fields| fields.iter().map(scalar).collect::<Option<Vec<_>>>())
.map(Val::List)
})
.collect::<Option<Vec<_>>>()?;
Some(Val::List(tuples))
}
fn const_val(e: &Expression<'_>, ty: Option<&TypeInfo>, wildcard: bool) -> Option<Val> {
let mut e = *e;
if ty.is_some_and(|t| !t.real) && !wildcard {
while e.kind() == ExpressionKind::Conversion {
match e.conversion_operand() {
Some(o)
if o.constant_value()
.is_some_and(|c| c.as_integer().is_some_and(|i| !i.has_unknown())) =>
{
e = o;
}
_ => break,
}
}
}
let signed_own = e
.constant_value()
.and_then(|c| c.as_integer().map(|i| i.is_signed()));
let cv = e.constant_value()?;
if let Some(f) = cv.as_f64()
&& cv.as_integer().is_none()
{
return Some(Val::Real(f));
}
if let Some(s) = cv.as_str() {
return Some(Val::Str(s.to_string()));
}
let i = cv.as_integer()?;
if i.has_unknown() {
if !wildcard {
return Some(Val::Unknown);
}
let mut bits = 0u128;
let mut care = 0u128;
for k in 0..(i.bit_width().min(128) as usize) {
let b = i.bit(k)?;
if !b.is_unknown() {
care |= 1 << k;
if b.logic_t_value() == 1 {
bits |= 1 << k;
}
}
}
return Some(Val::Pat { bits, care });
}
let signed = match (ty, signed_own) {
(Some(_), Some(own)) if !wildcard => own,
(Some(t), _) => t.signed,
(None, _) => i.is_signed(),
};
if ty.is_some_and(|t| t.real) {
return sv_int_i128(i, i.is_signed()).map(|v| Val::Real(v as f64));
}
if wildcard {
let v = sv_int_i128(i, false)?;
return Some(Val::Pat {
bits: v as u128,
care: u128::MAX,
});
}
sv_int_i128(i, signed).map(Val::Int)
}
fn const_list(e: &Expression<'_>, ty: &TypeInfo) -> Option<Val> {
let cv = e.constant_value()?;
let elems = cv.as_array()?;
let mut out = Vec::new();
for c in elems {
if let Some(i) = c.as_integer() {
out.push(Val::Int(sv_int_i128(i, ty.signed)?));
} else {
out.push(Val::Real(c.as_f64()?));
}
}
Some(Val::List(out))
}
fn const_string(e: &Expression<'_>) -> Option<String> {
let cv = e.constant_value()?;
if let Some(s) = cv.as_str() {
return Some(s.to_string());
}
let i = cv.as_integer()?;
let w = i.bit_width() as usize;
let mut bytes = Vec::new();
for byte in (0..w.div_ceil(8)).rev() {
let mut v = 0u8;
for k in 0..8 {
if let Some(b) = i.bit(byte * 8 + k)
&& b.logic_t_value() == 1
{
v |= 1 << k;
}
}
if v != 0 {
bytes.push(v);
}
}
Some(String::from_utf8_lossy(&bytes).into_owned())
}
pub fn ref_bindings<'d>(
cg: &Symbol<'d>,
formals: &[Symbol<'d>],
nodes: &[SemNode<'d>],
) -> Result<Vec<(Symbol<'d>, Symbol<'d>)>, LoweringError> {
let refs: Vec<(usize, Symbol<'d>)> = formals
.iter()
.enumerate()
.filter(|(_, f)| f.formal_argument_direction() == sv_lang::ArgumentDirection::Ref)
.map(|(i, f)| (i, *f))
.collect();
if refs.is_empty() {
return Ok(Vec::new());
}
let key = type_key(cg);
let mut bound: HashMap<usize, Symbol<'d>> = HashMap::new();
for &root in nodes {
let mut stack = vec![root];
while let Some(n) = stack.pop() {
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
let Some(e) = n.as_expression() else { continue };
if e.kind() != ExpressionKind::NewCovergroup {
continue;
}
let Some(ty) = e.expr_type() else { continue };
if type_key(&ty.as_symbol()) != key {
continue;
}
let args = e.new_covergroup_arguments();
for &(i, f) in &refs {
let actual = args
.get(i)
.and_then(|a| a.referenced_symbol())
.ok_or_else(|| {
err("a `ref` covergroup argument must be a plain variable", &f)
})?;
match bound.get(&i) {
Some(prev) if prev.uid() != actual.uid() => {
return Err(err(
"a `ref` covergroup argument bound to different variables by \
different `new` calls in one instance",
&f,
));
}
_ => {
bound.insert(i, actual);
}
}
}
}
}
Ok(refs
.into_iter()
.filter_map(|(i, f)| bound.get(&i).map(|a| (f, *a)))
.collect())
}
pub fn table_site_with_refs<'d>(
cg: &Symbol<'d>,
actuals: impl IntoIterator<Item = Symbol<'d>>,
) -> u32 {
let mut actuals = actuals.into_iter().peekable();
if actuals.peek().is_none() || is_class_embedded(cg) || sv_sim_sim::netlist::is_in_checker(cg) {
return table_site(cg);
}
let mut h: u32 = 0x811c_9dc5;
let mut mix = |s: &str| {
for b in s.bytes().chain(std::iter::once(0)) {
h = (h ^ u32::from(b)).wrapping_mul(0x0100_0193);
}
};
mix(cg.uid().path());
for a in actuals {
mix(a.uid().path());
}
h | 1
}
pub fn new_ref_actuals<'d>(cg: &Symbol<'d>, args: &[Expression<'d>]) -> Vec<Symbol<'d>> {
cg.members()
.filter(|m| m.kind() == SymbolKind::FormalArgument)
.enumerate()
.filter(|(_, f)| f.formal_argument_direction() == sv_lang::ArgumentDirection::Ref)
.filter_map(|(i, _)| args.get(i).and_then(|a| a.referenced_symbol()))
.collect()
}
pub fn ref_binding_sets<'d>(
cg: &Symbol<'d>,
formals: &[Symbol<'d>],
nodes: &[SemNode<'d>],
) -> Result<Vec<Vec<(Symbol<'d>, Symbol<'d>)>>, LoweringError> {
let refs: Vec<(usize, Symbol<'d>)> = formals
.iter()
.enumerate()
.filter(|(_, f)| f.formal_argument_direction() == sv_lang::ArgumentDirection::Ref)
.map(|(i, f)| (i, *f))
.collect();
if refs.is_empty() || is_class_embedded(cg) || sv_sim_sim::netlist::is_in_checker(cg) {
return Ok(vec![ref_bindings(cg, formals, nodes)?]);
}
let key = type_key(cg);
let mut sets: Vec<Vec<(Symbol<'d>, Symbol<'d>)>> = Vec::new();
for &root in nodes {
let mut stack = vec![root];
while let Some(n) = stack.pop() {
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
let Some(e) = n.as_expression() else { continue };
if e.kind() != ExpressionKind::NewCovergroup {
continue;
}
let Some(ty) = e.expr_type() else { continue };
if type_key(&ty.as_symbol()) != key {
continue;
}
let args = e.new_covergroup_arguments();
let mut set = Vec::new();
for &(i, f) in &refs {
let actual = args
.get(i)
.and_then(|a| a.referenced_symbol())
.ok_or_else(|| {
err("a `ref` covergroup argument must be a plain variable", &f)
})?;
set.push((f, actual));
}
if !sets.iter().any(|s| {
s.iter()
.zip(&set)
.all(|((_, a), (_, b))| a.uid() == b.uid())
}) {
sets.push(set);
}
}
}
if sets.is_empty() {
sets.push(Vec::new());
}
Ok(sets)
}
pub fn kernel_name(prefix: &str, k: usize) -> String {
format!("{prefix}_k{k}")
}
#[allow(clippy::too_many_arguments)]
pub fn compile_kernels<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, crate::process_lower::SignalSlot>,
cg: &Symbol<'d>,
prefix: &str,
kernels: &[CovKernel<'d>],
refs: &[(Symbol<'d>, Symbol<'d>)],
) -> Result<(), LoweringError> {
for (k, kernel) in kernels.iter().enumerate() {
compile_kernel(
ctx,
module,
fns,
signals,
cg,
&kernel_name(prefix, k),
kernel,
refs,
)?;
}
Ok(())
}
pub fn gate_name(prefix: &str, i: usize) -> String {
format!("{prefix}_g{i}")
}
pub fn compile_gates<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
cg: &Symbol<'d>,
prefix: &str,
gates: &[Expression<'d>],
) -> Result<(), LoweringError> {
for (i, gate) in gates.iter().enumerate() {
compile_event_fn(
ctx,
module,
fns,
signals,
cg,
&gate_name(prefix, i),
*gate,
|cx, e| {
let t = crate::expr::lower_condition(cx, e)?;
Ok(cx
.b
.build_int_z_extend_or_bit_cast(t, ctx.i64_type(), "gate")
.unwrap())
},
)?;
}
Ok(())
}
pub fn event_value_name(prefix: &str, i: usize) -> String {
format!("{prefix}_ev{i}")
}
pub fn event_value_supported(e: Expression<'_>, edge: bool) -> bool {
edge || e
.expr_type()
.is_some_and(|t| t.is_integral() && t.value_width() <= 32)
}
pub fn compile_event_values<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
cg: &Symbol<'d>,
prefix: &str,
terms: &[(Expression<'d>, bool)],
) -> Result<(), LoweringError> {
for (i, (expr, edge)) in terms.iter().enumerate() {
let edge = *edge;
compile_event_fn(
ctx,
module,
fns,
signals,
cg,
&event_value_name(prefix, i),
*expr,
|cx, e| {
let pv = crate::expr::lower_expr(cx, e)?.into_packed(
"a covergroup sampling event on a value that is not integral",
cg,
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let (val, unk) = (op.lane_val(pv), op.lane_unk(pv));
let i64_ty = cx.ctx.i64_type();
let widen = |v: inkwell::values::IntValue<'ctx>, n: &str| {
cx.b.build_int_cast_sign_flag(v, i64_ty, false, n).unwrap()
};
let (val, unk) = (widen(val, "ev_val"), widen(unk, "ev_unk"));
if edge {
let one = i64_ty.const_int(1, false);
let v0 = cx.b.build_and(val, one, "ev_v0").unwrap();
let u0 = cx.b.build_and(unk, one, "ev_u0").unwrap();
let u0 = cx.b.build_left_shift(u0, one, "ev_u0s").unwrap();
Ok(cx.b.build_or(v0, u0, "ev_state").unwrap())
} else {
let m = i64_ty.const_int(0xffff_ffff, false);
let v = cx.b.build_and(val, m, "ev_vm").unwrap();
let u = cx.b.build_and(unk, m, "ev_um").unwrap();
let u =
cx.b.build_left_shift(u, i64_ty.const_int(32, false), "ev_us")
.unwrap();
Ok(cx.b.build_or(v, u, "ev_state").unwrap())
}
},
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn compile_event_fn<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
cg: &Symbol<'d>,
name: &str,
expr: Expression<'d>,
body: impl FnOnce(
&LowerCtx<'ctx, 'd, '_>,
Expression<'d>,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError>,
) -> Result<(), LoweringError> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let f = module.add_function(name, ctx.i64_type().fn_type(&[ptr_ty.into()], false), None);
let entry = ctx.append_basic_block(f, "entry");
let b = ctx.create_builder();
b.position_at_end(entry);
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let mut cx = LowerCtx {
ctx,
b,
repr: ValueRepr::FourState,
locals: HashMap::new(),
fn_sym: *cg,
module,
mem_signals: crate::process_lower::mem_signals_of(signals),
mem_accessor: Some(MemAccessorCtx { handle, fns }),
lvalue_ref: Default::default(),
deferred_start: Default::default(),
unbounded: Default::default(),
iter: Default::default(),
loop_exits: Default::default(),
block_exits: Default::default(),
write_through: None,
cells: Default::default(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: Default::default(),
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
coll_temp: Default::default(),
};
let found = crate::process_lower::named_reads(&[expr.as_sem_node()]);
crate::process_lower::prime_signal_reads(&mut cx, fns, handle, signals, found, cg)?;
let t = body(&cx, expr)?;
cx.b.build_return(Some(&t)).unwrap();
if !f.verify(true) {
return Err(LoweringError {
message: format!("sampling-event function `{name}` failed LLVM verify"),
at: cg.hierarchical_path(),
});
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn compile_kernel<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
cg: &Symbol<'d>,
name: &str,
kernel: &CovKernel<'d>,
refs: &[(Symbol<'d>, Symbol<'d>)],
) -> Result<FunctionValue<'ctx>, LoweringError> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = ctx
.void_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into(), ptr_ty.into()], false);
let f = module.add_function(name, fn_ty, None);
let entry = ctx.append_basic_block(f, "entry");
let b = ctx.create_builder();
b.position_at_end(entry);
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let args = f.get_nth_param(1).unwrap().into_pointer_value();
let out = f.get_nth_param(2).unwrap().into_pointer_value();
let mut cx = LowerCtx {
ctx,
b,
repr: ValueRepr::FourState,
locals: HashMap::new(),
fn_sym: *cg,
module,
mem_signals: crate::process_lower::mem_signals_of(signals),
mem_accessor: Some(MemAccessorCtx { handle, fns }),
lvalue_ref: Default::default(),
deferred_start: Default::default(),
unbounded: Default::default(),
coll_temp: Default::default(),
iter: Default::default(),
loop_exits: Default::default(),
block_exits: Default::default(),
write_through: None,
cells: Default::default(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: Default::default(),
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
};
let mut found = crate::process_lower::named_reads(&[kernel.expr.as_sem_node()]);
found.extend(refs.iter().map(|(_, actual)| *actual));
crate::process_lower::prime_signal_reads(&mut cx, fns, handle, signals, found, cg)?;
for (formal, actual) in refs {
if let Some(&slot) = cx.locals.get(&actual.uid()) {
cx.locals.insert(formal.uid(), slot);
}
}
let i64t = ctx.i64_type();
let cell_word = |cx: &LowerCtx<'ctx, 'd, '_>, cell: usize, w: usize| -> IntValue<'ctx> {
let p = unsafe {
cx.b.build_gep(
i64t,
args,
&[i64t.const_int((cell * CELL_WORDS + w) as u64, false)],
"cov_arg",
)
}
.unwrap();
cx.b.build_load(i64t, p, "cov_word")
.unwrap()
.into_int_value()
};
let first = usize::from(is_class_embedded(cg));
if first == 1 {
cx.ctor_this.set(Some(cell_word(&cx, 0, 0)));
}
for (i, sym) in kernel.binds.iter().enumerate() {
if refs.iter().any(|(f, _)| f.uid() == sym.uid()) {
continue;
}
let (val, unk) = (cell_word(&cx, first + i, 0), cell_word(&cx, first + i, 1));
let v = bind_value(&cx, sym, val, unk)?;
cx.iter.borrow_mut().push((sym.uid(), v, v));
}
let (val, unk, tag) = if kernel.list {
let mode =
cx.b.build_load(i64t, out, "cov_mode")
.unwrap()
.into_int_value();
let (size, h) = crate::collections::size_and_handle(&cx, kernel.expr)?;
let c = crate::collections::coll_of_expr(&kernel.expr)
.ok_or_else(|| err("a bin set expression that is not a collection", cg))?;
let pos =
cx.b.build_int_sub(mode, i64t.const_int(1, false), "cov_pos")
.unwrap();
let pos =
cx.b.build_int_s_extend_or_bit_cast(pos, i64t, "cov_pos64")
.unwrap();
let (pos, tuple_n): (_, Option<(IntValue<'ctx>, IntValue<'ctx>)>) = if kernel.tuple {
tuple_position(&cx, &kernel.expr, pos)?
} else {
(pos, None)
};
let elem = crate::collections::read_at_coll(&cx, &c, h, pos);
let (ev, eu, et) = match tuple_n {
Some((_, f)) => tuple_field(&cx, &kernel.expr, elem, f)?,
None => to_cell(&cx, elem, &kernel.expr)?,
};
let is_count =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
mode,
i64t.const_zero(),
"cov_is_count",
)
.unwrap();
let size =
cx.b.build_int_z_extend_or_bit_cast(size, i64t, "cov_size")
.unwrap();
let sel = |a: IntValue<'ctx>, b: IntValue<'ctx>| {
cx.b.build_select(is_count, a, b, "cov_sel")
.unwrap()
.into_int_value()
};
(
sel(size, ev),
match tuple_n {
Some((n, _)) => sel(n, eu),
None => sel(i64t.const_zero(), eu),
},
sel(i64t.const_int(TAG_LIST, false), et),
)
} else {
let v = lower_expr(&cx, kernel.expr)?;
to_cell(&cx, v, &kernel.expr)?
};
for (w, x) in [val, unk, tag].into_iter().enumerate() {
let p = unsafe {
cx.b.build_gep(i64t, out, &[i64t.const_int(w as u64, false)], "cov_out")
}
.unwrap();
cx.b.build_store(p, x).unwrap();
}
cx.b.build_return(None).unwrap();
if f.verify(true) {
Ok(f)
} else {
Err(LoweringError {
message: format!("covergroup kernel `{name}` failed LLVM verify"),
at: cg.hierarchical_path(),
})
}
}
fn tuple_fields(expr: &Expression<'_>) -> Option<Vec<(u64, u64, bool)>> {
let ty = expr.expr_type()?;
let elem = ty.element_type()?;
let n = elem.fields().count();
(0..n)
.map(|i| {
let f = elem.fields().nth(i)?;
let ft = f.value_type()?;
let (lo, w) = if sv_sim_sim::state::storage::is_unpacked_aggregate(&elem) {
sv_sim_sim::state::storage::field_slice(&elem, i as u32)?
} else {
(f.field_bit_offset(), ft.bit_width())
};
Some((lo, w, ft.is_signed()))
})
.collect()
}
fn tuple_position<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
expr: &Expression<'_>,
pos: IntValue<'ctx>,
) -> Result<(IntValue<'ctx>, Option<(IntValue<'ctx>, IntValue<'ctx>)>), LoweringError> {
let n = tuple_fields(expr)
.filter(|f| !f.is_empty())
.ok_or_else(|| {
err(
"a cross set expression whose elements are not structs",
&cx.fn_sym,
)
})?
.len() as u64;
let i64t = cx.ctx.i64_type();
let nv = i64t.const_int(n, false);
let neg =
cx.b.build_int_compare(
inkwell::IntPredicate::SLT,
pos,
i64t.const_zero(),
"cov_neg",
)
.unwrap();
let safe =
cx.b.build_select(neg, i64t.const_zero(), pos, "cov_safe")
.unwrap()
.into_int_value();
let t = cx.b.build_int_unsigned_div(safe, nv, "cov_tuple").unwrap();
let f = cx.b.build_int_unsigned_rem(safe, nv, "cov_field").unwrap();
Ok((t, Some((nv, f))))
}
fn tuple_field<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
expr: &Expression<'_>,
elem: LoweredValue<'ctx>,
f: IntValue<'ctx>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>), LoweringError> {
let LoweredValue::Packed(base) = elem else {
return Err(err(
"a cross set expression tuple that is not integral",
&cx.fn_sym,
));
};
let fields = tuple_fields(expr).ok_or_else(|| {
err(
"a cross set expression whose elements are not structs",
&cx.fn_sym,
)
})?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let mut acc: Option<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>)> = None;
for (k, (lo, w, signed)) in fields.iter().enumerate() {
if *w == 0 || *w > 64 || lo + w > base.width as u64 {
return Err(err(
"a cross set expression tuple field that is not integral or wider than 64 bits",
&cx.fn_sym,
));
}
let lov = op.i_n(base.width).const_int(*lo, false);
let pv = PackedValue {
signed: *signed,
..op.part_select4(base, lov, cx.ctx.bool_type().const_zero(), *w as u32)
};
let cell = to_cell(cx, LoweredValue::Packed(pv), expr)?;
acc = Some(match acc {
None => cell,
Some(prev) => {
let is_k =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
f,
i64t.const_int(k as u64, false),
"cov_is_field",
)
.unwrap();
let pick = |a: IntValue<'ctx>, b: IntValue<'ctx>| {
cx.b.build_select(is_k, a, b, "cov_fsel")
.unwrap()
.into_int_value()
};
(
pick(cell.0, prev.0),
pick(cell.1, prev.1),
pick(cell.2, prev.2),
)
}
});
}
Ok(acc.expect("tuple_position checked for a non-empty field list"))
}
fn bind_value<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
sym: &Symbol<'_>,
val: IntValue<'ctx>,
unk: IntValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = sym.value_type();
if ty.is_some_and(|t| t.is_floating()) {
let f =
cx.b.build_bit_cast(val, cx.ctx.f64_type(), "cov_real")
.unwrap()
.into_float_value();
return Ok(LoweredValue::Real(RealValue { llvm: f }));
}
if ty.is_some_and(|t| t.is_string()) {
return Ok(LoweredValue::Str(crate::strings::StrValue { llvm: val }));
}
let (width, signed) = match ty {
Some(t) if crate::collections::coll_of(&t).is_some() => (64, false),
Some(t) => (t.value_width().max(1), t.is_signed()),
None => (64, true),
};
let (width, signed) = if sym.kind() == SymbolKind::Coverpoint {
sym.coverpoint_coverage_expr()
.and_then(|e| e.expr_type())
.map_or((width, signed), |t| (t.value_width().max(1), t.is_signed()))
} else {
(width, signed)
};
if width > 64 {
return Err(err("a covergroup argument wider than 64 bits", sym));
}
let (v, u) = if width < 64 {
(
cx.b.build_int_truncate(val, op.i_n(width), "cov_v")
.unwrap(),
cx.b.build_int_truncate(unk, op.i_n(width), "cov_u")
.unwrap(),
)
} else {
(val, unk)
};
Ok(LoweredValue::Packed(op.pack(
v,
u,
width,
ValueRepr::FourState,
signed,
)))
}
fn to_cell<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: LoweredValue<'ctx>,
e: &Expression<'_>,
) -> Result<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>), LoweringError> {
let i64t = cx.ctx.i64_type();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
match v {
LoweredValue::Real(r) => {
let f = crate::real::as_f64(cx, r.llvm);
let bits =
cx.b.build_bit_cast(f, i64t, "cov_bits")
.unwrap()
.into_int_value();
Ok((bits, i64t.const_zero(), i64t.const_int(TAG_REAL, false)))
}
LoweredValue::Str(s) => Ok((s.llvm, i64t.const_zero(), i64t.const_int(TAG_STRING, false))),
LoweredValue::Packed(pv) => {
if pv.width > 64 {
return Err(err(
&format!(
"a covergroup expression wider than 64 bits ({} bits)",
pv.width
),
&cx.fn_sym,
));
}
let _ = e;
let val = op.lane_val(pv);
let unk = op.lane_unk(pv);
let (val, unk) = if pv.width < 64 {
let v = if pv.signed {
cx.b.build_int_s_extend(val, i64t, "cov_sx").unwrap()
} else {
cx.b.build_int_z_extend(val, i64t, "cov_zx").unwrap()
};
(v, cx.b.build_int_z_extend(unk, i64t, "cov_ux").unwrap())
} else {
(val, unk)
};
let tag = if pv.signed { TAG_SIGNED } else { TAG_UNSIGNED };
Ok((val, unk, i64t.const_int(tag, false)))
}
LoweredValue::Wide(_) | LoweredValue::Huge(_) => Err(err(
"a covergroup expression wider than 64 bits",
&cx.fn_sym,
)),
}
}
thread_local! {
static TYPE_INDEX: RefCell<HashMap<String, u32>> = RefCell::new(HashMap::new());
}
thread_local! {
static BLOCK_TARGETS: RefCell<std::collections::HashSet<String>> =
RefCell::new(std::collections::HashSet::new());
}
thread_local! {
static DERIVED_GROUPS: RefCell<HashMap<String, Vec<(u32, String)>>> =
RefCell::new(HashMap::new());
}
pub fn register_derived_groups(classes: &[(sv_sim_sim::netlist::ClassTemplateId, Symbol<'_>)]) {
let mut map: HashMap<String, Vec<(u32, String)>> = HashMap::new();
for &(id, class_sym) in classes {
let Some(ty) = class_sym.as_type() else {
continue;
};
let mut levels: Vec<Symbol<'_>> = sv_sim_sim::netlist::base_chain(ty)
.into_iter()
.map(|t| t.as_symbol())
.collect();
levels.push(class_sym);
let mut chosen: HashMap<String, String> = HashMap::new();
for level in levels {
for m in level.members() {
if m.kind() != SymbolKind::CovergroupType {
continue;
}
if let Some(b) = m
.as_type()
.and_then(|t| t.covergroup_base_group())
.filter(|b| b.is_covergroup())
{
chosen.insert(type_key(&b.as_symbol()), type_key(&m));
}
}
}
let resolved: Vec<(String, String)> = chosen
.iter()
.map(|(base, derived)| {
let mut end = derived.clone();
for _ in 0..chosen.len() {
match chosen.get(&end) {
Some(next) if *next != end => end = next.clone(),
_ => break,
}
}
(base.clone(), end)
})
.collect();
for (base, derived) in resolved {
map.entry(base).or_default().push((id.0, derived));
}
}
DERIVED_GROUPS.with(|d| *d.borrow_mut() = map);
}
pub struct CovTypeScope(());
impl CovTypeScope {
pub fn enter(
index: HashMap<String, u32>,
block_targets: std::collections::HashSet<String>,
) -> Self {
TYPE_INDEX.with(|t| *t.borrow_mut() = index);
BLOCK_TARGETS.with(|t| *t.borrow_mut() = block_targets);
CovTypeScope(())
}
}
impl Drop for CovTypeScope {
fn drop(&mut self) {
TYPE_INDEX.with(|t| t.borrow_mut().clear());
DERIVED_GROUPS.with(|t| t.borrow_mut().clear());
BLOCK_TARGETS.with(|t| t.borrow_mut().clear());
}
}
pub fn block_event_targets<'d>(cg: &Symbol<'d>) -> Result<Vec<(Symbol<'d>, bool)>, LoweringError> {
let Some(tc) = cg
.as_type()
.and_then(|t| t.covergroup_coverage_event())
.and_then(|ev| ev.as_timing_control())
else {
return Ok(Vec::new());
};
if tc.kind() != sv_lang::kinds::TimingControlKind::BlockEventList {
return Ok(Vec::new());
}
let refuse = |m: String| {
Err(err(
&format!("covergroup {}: {m} (LRM 19.3)", cg.name()),
cg,
))
};
let home = cg.parent();
if home.is_none_or(|h| h.kind() != SymbolKind::InstanceBody) {
return refuse(
"a block event sampling a covergroup declared outside a module is not supported".into(),
);
}
let mut out = Vec::new();
for be in tc.block_events() {
let Some(sub) = be.target.referenced_symbol().filter(|s| {
matches!(
s.kind(),
SymbolKind::Subroutine | SymbolKind::StatementBlock
)
}) else {
return refuse("a block event must name a function, a task or a named block".into());
};
if sub.kind() == SymbolKind::StatementBlock {
if block_home(&sub).map(|h| h.uid()) != home.map(|h| h.uid()) {
return refuse(format!(
"the block event's block `{}` is not declared in the covergroup's module",
sub.name()
));
}
out.push((sub, !be.is_begin));
continue;
}
if sub.parent().map(|p| p.uid()) != home.map(|h| h.uid()) {
return refuse(format!(
"the block event's subroutine `{}` is not declared in the covergroup's module",
sub.name()
));
}
if sv_sim_sim::process::cfg::proc_body(&sub).is_none() {
return refuse(format!(
"the block event's subroutine `{}` has no body",
sub.name()
));
}
out.push((sub, !be.is_begin));
}
Ok(out)
}
pub fn block_event_key(sub: &Symbol<'_>, is_end: bool) -> Result<u64, LoweringError> {
Ok(block_key_of(&target_mangled_name(sub)?, is_end))
}
fn block_home<'d>(blk: &Symbol<'d>) -> Option<Symbol<'d>> {
let mut cur = blk.parent();
while let Some(p) = cur {
if p.kind() == SymbolKind::InstanceBody {
return Some(p);
}
cur = p.parent();
}
None
}
pub(crate) fn target_mangled_name(sub: &Symbol<'_>) -> Result<String, LoweringError> {
if sub.kind() != SymbolKind::StatementBlock {
return crate::func::mangled_subroutine_name(sub);
}
let home = block_home(sub).ok_or_else(|| {
LoweringError::unsupported_at("named block outside any module instance", sub)
})?;
let mut names = vec![sub.name().to_string()];
let mut cur = sub.parent();
while let Some(p) = cur.filter(|p| p.uid() != home.uid()) {
names.push(p.name().to_string());
cur = p.parent();
}
names.reverse();
Ok(crate::mangle::mangle_kernel(
home.name(),
crate::mangle::param_binding_hash(&home),
crate::mangle::EntityTag::Kernel,
&format!("blk:{}", names.join(".")),
))
}
fn block_key_of(mangled: &str, is_end: bool) -> u64 {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for b in mangled.bytes() {
h = (h ^ u64::from(b)).wrapping_mul(0x0100_0000_01b3);
}
(h << 1) | u64::from(is_end)
}
pub fn block_target_names<'d>(
cgs: impl IntoIterator<Item = Symbol<'d>>,
) -> Result<std::collections::HashSet<String>, LoweringError> {
let mut out = std::collections::HashSet::new();
for cg in cgs {
for (sub, _) in block_event_targets(&cg)? {
out.insert(target_mangled_name(&sub)?);
}
}
Ok(out)
}
pub(crate) fn is_block_target(sub: &Symbol<'_>) -> bool {
target_mangled_name(sub).is_ok_and(|m| BLOCK_TARGETS.with(|t| t.borrow().contains(&m)))
}
pub(crate) fn is_sample_call(e: &Expression<'_>) -> bool {
e.kind() == ExpressionKind::Call
&& e.call_subroutine()
.is_some_and(|s| s.name() == "sample" && s.parent().and_then(scope_owner).is_some())
}
pub(crate) fn emit_block_event<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
is_end: bool,
) -> Result<(), LoweringError> {
emit_block_event_of(cx, handle, &cx.fn_sym, is_end)
}
pub(crate) fn emit_block_event_of<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
sub: &Symbol<'_>,
is_end: bool,
) -> Result<(), LoweringError> {
let i64t = cx.ctx.i64_type();
let key = block_event_key(sub, is_end)?;
let h = cx.b.build_ptr_to_int(handle, i64t, "blk_h").unwrap();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
rt_cov(
cx,
CovOp::BlockEvent,
i64t.const_int(key, false),
h,
null,
0,
);
Ok(())
}
pub fn table_site(cg: &Symbol<'_>) -> u32 {
if !sv_sim_sim::netlist::is_in_checker(cg) {
return 0;
}
let mut h: u32 = 0x811c_9dc5;
for b in cg.uid().path().bytes() {
h = (h ^ u32::from(b)).wrapping_mul(0x0100_0193);
}
h | 1
}
fn type_index(cg: &Symbol<'_>, at: &Symbol<'_>) -> Result<u32, LoweringError> {
let key = type_key(cg);
TYPE_INDEX
.with(|t| t.borrow().get(&key).copied())
.ok_or_else(|| {
err(
&format!(
"covergroup `{key}` has no compiled kernels here (covergroups embedded \
in a class, and covergroups in the AOT backend, are not supported yet)"
),
at,
)
})
}
fn covergroup_of<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
let t = e.expr_type()?.canonical();
t.is_covergroup().then(|| t.as_symbol())
}
pub(crate) fn rt_cov<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: CovOp,
a: IntValue<'ctx>,
item: IntValue<'ctx>,
buf: PointerValue<'ctx>,
n: u64,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let i32t = cx.ctx.i32_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let f = cx.module.get_function(SV_RT_COV_SYMBOL).unwrap_or_else(|| {
cx.module.add_function(
SV_RT_COV_SYMBOL,
i64t.fn_type(
&[
i32t.into(),
i64t.into(),
i64t.into(),
ptr.into(),
i64t.into(),
],
false,
),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[
i32t.const_int(op as u64, false).into(),
a.into(),
item.into(),
buf.into(),
i64t.const_int(n, false).into(),
],
"cov",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
fn cells<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[Expression<'d>],
) -> Result<PointerValue<'ctx>, LoweringError> {
let i64t = cx.ctx.i64_type();
let n = (args.len() * CELL_WORDS).max(CELL_WORDS);
let buf = entry_alloca(cx, i64t.array_type(n as u32), "cov_cells");
for (i, a) in args.iter().enumerate() {
let v = lower_expr(cx, *a)?;
let (val, unk, tag) = to_cell(cx, v, a)?;
store_cell(cx, buf, i * CELL_WORDS, [val, unk, tag]);
}
Ok(buf)
}
fn store_cell<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
at: usize,
words: [IntValue<'ctx>; 3],
) {
let i64t = cx.ctx.i64_type();
for (w, x) in words.into_iter().enumerate() {
let p = unsafe {
cx.b.build_gep(
i64t,
buf,
&[i64t.const_int((at + w) as u64, false)],
"cov_w",
)
}
.unwrap();
cx.b.build_store(p, x).unwrap();
}
}
fn load_word<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
at: usize,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let p = unsafe {
cx.b.build_gep(i64t, buf, &[i64t.const_int(at as u64, false)], "cov_r")
}
.unwrap();
cx.b.build_load(i64t, p, "cov_word")
.unwrap()
.into_int_value()
}
fn handle_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let pv = lower_expr(cx, e)?.into_packed("a covergroup handle", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = op.lane_val(pv);
Ok(if pv.width < 64 {
cx.b.build_int_z_extend(v, cx.ctx.i64_type(), "cov_h")
.unwrap()
} else {
v
})
}
fn handle_as_value<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, h: IntValue<'ctx>) -> LoweredValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(op.pack(h, cx.ctx.i64_type().const_zero(), 64, cx.repr, false))
}
pub fn lower_new<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let cg = covergroup_of(&e)
.ok_or_else(|| err("`new` of a covergroup without its type", &cx.fn_sym))?;
let idx = type_index(&cg, &cx.fn_sym)?;
let args = e.new_covergroup_arguments();
let i64t = cx.ctx.i64_type();
let site = table_site_with_refs(&cg, new_ref_actuals(&cg, &args));
let mut new_code = i64t.const_int(u64::from(idx) | (u64::from(site) << 32), false);
let (buf, n) = if is_class_embedded(&cg) {
let this = crate::classes::current_this_handle(cx)?;
let derived = DERIVED_GROUPS.with(|d| d.borrow().get(&type_key(&cg)).cloned());
if let Some(derived) = derived.filter(|d| !d.is_empty()) {
let dyn_id = crate::classes::dynamic_template_call(cx, this);
let site = u64::from(table_site(&cg)) << 32;
for (tid, key) in derived {
let didx = TYPE_INDEX
.with(|t| t.borrow().get(&key).copied())
.ok_or_else(|| {
err(
&format!("covergroup `{key}` has no compiled kernels here"),
&cx.fn_sym,
)
})?;
let hit =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
dyn_id,
i64t.const_int(u64::from(tid), false),
"cov_ext_hit",
)
.unwrap();
new_code =
cx.b.build_select(
hit,
i64t.const_int(u64::from(didx) | site, false),
new_code,
"cov_ext_sel",
)
.unwrap()
.into_int_value();
}
}
let n = args.len() + 1;
let buf = entry_alloca(cx, i64t.array_type((n * CELL_WORDS) as u32), "cov_cells");
store_cell(
cx,
buf,
0,
[this, i64t.const_zero(), i64t.const_int(TAG_UNSIGNED, false)],
);
for (i, a) in args.iter().enumerate() {
let v = lower_expr(cx, *a)?;
let (val, unk, tag) = to_cell(cx, v, a)?;
store_cell(cx, buf, (i + 1) * CELL_WORDS, [val, unk, tag]);
}
(buf, n)
} else {
(cells(cx, &args)?, args.len())
};
let caller = match &cx.mem_accessor {
Some(m) => cx.b.build_ptr_to_int(m.handle, i64t, "cov_caller").unwrap(),
None => i64t.const_zero(),
};
let h = rt_cov(cx, CovOp::New, new_code, caller, buf, n as u64);
Ok(handle_as_value(cx, h))
}
struct Target<'ctx> {
handle: Option<IntValue<'ctx>>,
ty: u32,
item: u64,
}
fn item_code(cg: &Symbol<'_>, member: &Symbol<'_>) -> u64 {
let (cps, crosses) = item_names(cg);
let name = member.name().to_string();
let kind = member.kind();
if kind == SymbolKind::Coverpoint {
cps.iter()
.position(|n| *n == name)
.map_or(ITEM_GROUP, |i| ITEM_COVERPOINT + i as u64)
} else if kind == SymbolKind::CoverCross {
crosses
.iter()
.position(|n| *n == name)
.map_or(ITEM_GROUP, |i| ITEM_CROSS | i as u64)
} else {
ITEM_GROUP
}
}
fn item_names(cg: &Symbol<'_>) -> (Vec<String>, Vec<String>) {
let (mut cps, mut crosses) = match cg
.as_type()
.and_then(|t| t.covergroup_base_group())
.filter(|b| b.is_covergroup())
{
Some(b) => item_names(&b.as_symbol()),
None => (Vec::new(), Vec::new()),
};
if let Some(body) = body_of(*cg) {
for m in body.members() {
let list = if m.kind() == SymbolKind::Coverpoint {
&mut cps
} else if m.kind() == SymbolKind::CoverCross {
&mut crosses
} else {
continue;
};
if m.name().is_empty() || !list.iter().any(|n| n == m.name()) {
list.push(m.name().to_string());
}
}
}
(cps, crosses)
}
fn scope_owner<'d>(scope: Symbol<'d>) -> Option<(Symbol<'d>, Symbol<'d>)> {
if scope.kind() == SymbolKind::CovergroupBody {
return Some((scope.parent()?, scope));
}
if [SymbolKind::Coverpoint, SymbolKind::CoverCross].contains(&scope.kind()) {
let body = scope.parent()?;
return Some((body.parent()?, scope));
}
None
}
fn target<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
scope: Symbol<'d>,
recv: Option<Expression<'d>>,
) -> Result<Option<Target<'ctx>>, LoweringError> {
let Some((cg, item_sym)) = scope_owner(scope) else {
return Ok(None);
};
let ty = type_index(&cg, &cx.fn_sym)?;
let item = if item_sym.kind() == SymbolKind::CovergroupBody {
ITEM_GROUP
} else {
item_code(&cg, &item_sym)
};
let inst = match recv {
Some(r) if r.kind() == ExpressionKind::MemberAccess && covergroup_of(&r).is_none() => {
r.operand()
}
other => other,
};
let handle = match inst {
Some(i) if covergroup_of(&i).is_some() => Some(handle_value(cx, i)?),
_ => None,
};
Ok(Some(Target { handle, ty, item }))
}
pub fn lower_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
call: Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
let Some(sub) = call.call_subroutine() else {
return Ok(None);
};
let Some(scope) = sub.parent() else {
return Ok(None);
};
let recv = call.call_this_class();
let Some(t) = target(cx, scope, recv)? else {
return Ok(None);
};
let i64t = cx.ctx.i64_type();
let mut args: Vec<Expression<'d>> = call
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.collect();
if recv.is_some() && !args.is_empty() {
args.remove(0);
}
let name = sub.name();
let need_handle = |what: &str| -> Result<IntValue<'ctx>, LoweringError> {
t.handle.ok_or_else(|| {
err(
&format!("`{what}()` called on a covergroup type rather than an instance"),
&cx.fn_sym,
)
})
};
let item = i64t.const_int(t.item, false);
let zero = i64t.const_zero();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
let v = match name {
"sample" => {
let h = need_handle(name)?;
let buf = cells(cx, &args)?;
crate::func::sync_subroutine_writes(cx)?;
rt_cov(cx, CovOp::Sample, h, item, buf, args.len() as u64);
return Ok(Some(LoweredValue::Packed(PackedValue {
llvm: cx.ctx.i64_type().const_zero(),
width: 32,
repr: ValueRepr::TwoState,
signed: false,
})));
}
"start" | "stop" => {
let h = need_handle(name)?;
let op = if name == "start" {
CovOp::Start
} else {
CovOp::Stop
};
rt_cov(cx, op, h, item, null, 0);
return Ok(Some(void_value(cx)));
}
"set_inst_name" => {
let h = need_handle(name)?;
let a = *args
.first()
.ok_or_else(|| err("set_inst_name() needs a name", &cx.fn_sym))?;
let s = crate::strings::to_string(cx, lower_expr(cx, a)?)?;
let buf = entry_alloca(cx, i64t.array_type(1), "cov_name");
store_word(cx, buf, 0, s.llvm);
rt_cov(cx, CovOp::SetInstName, h, item, buf, 1);
return Ok(Some(void_value(cx)));
}
"get_coverage" | "get_inst_coverage" => {
let (op, a) = match (name, t.handle) {
("get_inst_coverage", Some(h)) => (CovOp::InstCoverage, h),
("get_inst_coverage", None) => {
return Err(err(
"`get_inst_coverage()` called on a covergroup type",
&cx.fn_sym,
));
}
(_, Some(h)) => (CovOp::Coverage, h),
(_, None) => (CovOp::TypeCoverage, i64t.const_int(u64::from(t.ty), false)),
};
let outs = entry_alloca(cx, i64t.array_type(2), "cov_outs");
let bits = rt_cov(cx, op, a, item, outs, 0);
for (k, target) in args.iter().take(2).enumerate() {
if !is_lvalue(target) {
continue;
}
let w = load_word(cx, outs, k);
let w32 =
cx.b.build_int_truncate(w, cx.ctx.i32_type(), "cov_n")
.unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let pv = op.pack(w32, cx.ctx.i32_type().const_zero(), 32, cx.repr, true);
store_to(cx, *target, LoweredValue::Packed(pv))?;
}
let f =
cx.b.build_bit_cast(bits, cx.ctx.f64_type(), "cov_pct")
.unwrap()
.into_float_value();
LoweredValue::Real(RealValue { llvm: f })
}
_ => {
return Err(err(
&format!("covergroup method `{name}` is not supported"),
&cx.fn_sym,
));
}
};
let _ = zero;
Ok(Some(v))
}
fn void_value<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> LoweredValue<'ctx> {
LoweredValue::Packed(PackedValue {
llvm: cx.ctx.i64_type().const_zero(),
width: 32,
repr: ValueRepr::TwoState,
signed: false,
})
}
fn store_word<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
buf: PointerValue<'ctx>,
at: usize,
v: IntValue<'ctx>,
) {
let i64t = cx.ctx.i64_type();
let p = unsafe {
cx.b.build_gep(i64t, buf, &[i64t.const_int(at as u64, false)], "cov_w")
}
.unwrap();
cx.b.build_store(p, v).unwrap();
}
fn is_lvalue(e: &Expression<'_>) -> bool {
matches!(
e.kind(),
ExpressionKind::NamedValue
| ExpressionKind::HierarchicalValue
| ExpressionKind::ElementSelect
| ExpressionKind::RangeSelect
| ExpressionKind::MemberAccess
)
}
fn option_access<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: &Expression<'d>,
) -> Result<Option<(Target<'ctx>, bool, u64, Symbol<'d>)>, LoweringError> {
if e.kind() != ExpressionKind::MemberAccess {
return Ok(None);
}
let Some(field) = e.referenced_symbol() else {
return Ok(None);
};
let Some(base) = e.operand() else {
return Ok(None);
};
let Some(prop) = base.referenced_symbol() else {
return Ok(None);
};
if prop.kind() != SymbolKind::ClassProperty || !matches!(prop.name(), "option" | "type_option")
{
return Ok(None);
}
let Some(scope) = prop.parent() else {
return Ok(None);
};
let recv = if base.kind() == ExpressionKind::MemberAccess {
base.operand()
} else {
None
};
let Some(t) = target(cx, scope, recv)? else {
return Ok(None);
};
let idx = OPTION_NAMES
.iter()
.position(|n| *n == field.name())
.ok_or_else(|| {
err(
&format!("unknown coverage option `{}`", field.name()),
&cx.fn_sym,
)
})?;
Ok(Some((t, prop.name() == "type_option", idx as u64, field)))
}
pub fn lower_option_read<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
let Some((t, is_type, idx, field)) = option_access(cx, &e)? else {
return Ok(None);
};
let i64t = cx.ctx.i64_type();
let buf = entry_alloca(cx, i64t.array_type((1 + CELL_WORDS) as u32), "cov_opt");
store_word(cx, buf, 0, i64t.const_int(idx, false));
let (op, a) = match (is_type, t.handle) {
(true, _) => (CovOp::GetTypeOption, i64t.const_int(u64::from(t.ty), false)),
(false, Some(h)) => (CovOp::GetOption, h),
(false, None) => {
return Err(err("`option` read through a covergroup type", &cx.fn_sym));
}
};
rt_cov(cx, op, a, i64t.const_int(t.item, false), buf, 0);
let val = load_word(cx, buf, 1);
let unk = load_word(cx, buf, 2);
Ok(Some(bind_value(cx, &field, val, unk)?))
}
pub fn lower_option_write<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
let Some((t, is_type, idx, _)) = option_access(cx, &lhs)? else {
return Ok(None);
};
let i64t = cx.ctx.i64_type();
let buf = entry_alloca(cx, i64t.array_type((1 + CELL_WORDS) as u32), "cov_opt");
store_word(cx, buf, 0, i64t.const_int(idx, false));
let (val, unk, tag) = to_cell(cx, value, &lhs)?;
store_cell(cx, buf, 1, [val, unk, tag]);
let (op, a) = match (is_type, t.handle) {
(true, _) => (CovOp::SetTypeOption, i64t.const_int(u64::from(t.ty), false)),
(false, Some(h)) => (CovOp::SetOption, h),
(false, None) => {
return Err(err(
"`option` written through a covergroup type",
&cx.fn_sym,
));
}
};
rt_cov(cx, op, a, i64t.const_int(t.item, false), buf, 0);
Ok(Some(value))
}
pub fn lower_system_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: &str,
args: &[Expression<'d>],
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
let i64t = cx.ctx.i64_type();
let null = cx.ctx.ptr_type(AddressSpace::default()).const_null();
let zero = i64t.const_zero();
let int_result = |cx: &LowerCtx<'ctx, 'd, '_>, v: IntValue<'ctx>| {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v32 =
cx.b.build_int_truncate(v, cx.ctx.i32_type(), "cov_i")
.unwrap();
LoweredValue::Packed(op.pack(v32, cx.ctx.i32_type().const_zero(), 32, cx.repr, true))
};
Ok(Some(match name {
"$get_coverage" => {
let bits = rt_cov(cx, CovOp::GlobalCoverage, zero, zero, null, 0);
let f =
cx.b.build_bit_cast(bits, cx.ctx.f64_type(), "cov_pct")
.unwrap()
.into_float_value();
LoweredValue::Real(RealValue { llvm: f })
}
"$set_coverage_db_name" | "$load_coverage_db" => {
let a = *args
.first()
.ok_or_else(|| err(&format!("{name} needs a file name"), &cx.fn_sym))?;
let s = crate::strings::to_string(cx, lower_expr(cx, a)?)?;
let op = if name == "$load_coverage_db" {
CovOp::LoadDb
} else {
CovOp::SetDbName
};
let r = rt_cov(cx, op, s.llvm, zero, null, 0);
int_result(cx, r)
}
"$coverage_control" | "$coverage_get_max" | "$coverage_get" | "$coverage_merge"
| "$coverage_save" => {
for a in args {
if a.kind() == ExpressionKind::ArbitrarySymbol {
continue;
}
lower_expr(cx, *a)?;
}
let valid = coverage_args_valid(name, args);
let ctl = args.first().and_then(|a| a.constant_value()?.as_i64());
if valid && let Some(r) = lower_toggle_call(cx, name, args)? {
return Ok(Some(int_result(cx, r)));
}
if name == "$coverage_control" && ctl.is_none() {
return Err(err(
"$coverage_control: the control constant must be a constant expression",
&cx.fn_sym,
));
}
let ok_ctl = name == "$coverage_control" && matches!(ctl, Some(1 | 2));
let code: i64 = if !valid || name == "$coverage_merge" {
-1
} else if ok_ctl {
1
} else {
0
};
int_result(cx, i64t.const_int(code as u64, true))
}
_ => return Ok(None),
}))
}
fn lower_toggle_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: &str,
args: &[Expression<'d>],
) -> Result<Option<IntValue<'ctx>>, LoweringError> {
let (kind, ty_at) = match name {
"$coverage_control" => (0u64, 1),
"$coverage_get" => (1, 0),
"$coverage_get_max" => (2, 0),
_ => return Ok(None),
};
let c = |i: usize| -> Option<i64> { args.get(i)?.constant_value()?.as_i64() };
let Some(ty) = c(ty_at).filter(|t| matches!(t, 20..=23)) else {
return Ok(None);
};
let i64t = cx.ctx.i64_type();
let ctl = if kind == 0 {
match c(0) {
Some(v) => v,
None => return Ok(None),
}
} else {
0
};
let Some(hier) = c(ty_at + 1).map(|v| v == 11) else {
return Ok(None);
};
let scope = *args
.get(ty_at + 2)
.ok_or_else(|| err(&format!("{name} needs a scope"), &cx.fn_sym))?;
let (a, is_root) = if scope.kind() == ExpressionKind::ArbitrarySymbol {
let sym = scope.arbitrary_symbol();
let is_root_sym = sym.as_ref().is_none_or(|s| s.name() == "$root");
let path = sym.map(|s| s.hierarchical_path()).unwrap_or_default();
let path = path.strip_prefix("$root.").unwrap_or(&path).to_owned();
if is_root_sym {
(i64t.const_zero(), true)
} else {
let g = cx.b.build_global_string_ptr(&path, "cov_scope").unwrap();
let len = i64t.const_int(path.len() as u64, false);
(
crate::strings::intern_bytes(cx, g.as_pointer_value(), len).llvm,
false,
)
}
} else {
let s = crate::strings::to_string(cx, lower_expr(cx, scope)?)?;
(s.llvm, false)
};
let buf = entry_alloca(cx, i64t.array_type(3), "cov_code");
store_word(cx, buf, 0, i64t.const_int(kind, false));
store_word(cx, buf, 1, i64t.const_int(ctl as u64, true));
store_word(cx, buf, 2, i64t.const_int(ty as u64, false));
let item = u64::from(is_root) | (u64::from(hier) << 1);
Ok(Some(rt_cov(
cx,
CovOp::CodeCov,
a,
i64t.const_int(item, false),
buf,
0,
)))
}
fn coverage_args_valid(name: &str, args: &[Expression<'_>]) -> bool {
let c = |i: usize| -> Option<i64> { args.get(i)?.constant_value()?.as_i64() };
let control_ok = |v: Option<i64>| v.is_none_or(|v| (0..=3).contains(&v));
let type_ok = |v: Option<i64>| v.is_none_or(|v| (20..=23).contains(&v));
let scope_ok = |v: Option<i64>| v.is_none_or(|v| v == 10 || v == 11);
match name {
"$coverage_control" => control_ok(c(0)) && type_ok(c(1)) && scope_ok(c(2)),
"$coverage_get_max" | "$coverage_get" => type_ok(c(0)) && scope_ok(c(1)),
"$coverage_merge" | "$coverage_save" => type_ok(c(0)),
_ => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
const SRC: &str = "module top;
bit clk;
bit [3:0] a, b;
covergroup cg (int lo) @(posedge clk);
option.per_instance = 1;
type_option.comment = \"hi\";
cpa: coverpoint a iff (b != 0) {
bins one = {1};
bins rng[] = {[2:4]};
bins fixed[3] = {[5:$]};
bins arg = {lo};
wildcard bins w = {4'b1?0?};
bins t = (1 => 2 => 3), (4 => 5);
bins rep = (1 [* 2:3]);
bins wit = {[0:15]} with (item % 2 == 0);
ignore_bins ig = {15};
illegal_bins il = {14};
bins d = default;
option.at_least = 2;
}
cpb: coverpoint b;
x: cross cpa, cpb {
bins sel = binsof(cpa.one) || binsof(cpb) intersect {[0:3]};
ignore_bins ig2 = !binsof(cpb) intersect {7};
}
endgroup
cg c1;
initial c1 = new(3);
endmodule
";
fn with_cg<R>(f: impl FnOnce(Symbol<'_>, &[SyntaxTree]) -> R) -> R {
let design = crate::frontend::compile_source(SRC).unwrap();
let trees = design.syntax_trees();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let cg = declared_covergroups(body).pop().unwrap();
f(cg, &trees)
}
#[test]
fn type_key_is_the_declaring_definition_path() {
with_cg(|cg, _| assert_eq!(type_key(&cg), "top::cg"));
}
#[test]
fn extracts_options_bins_and_kernels() {
with_cg(|cg, trees| {
let x = extract(cg, trees).unwrap();
let d = &x.decl;
assert_eq!(x.formals.len(), 1);
assert!(x.event.is_some());
assert!(!x.strobe);
assert_eq!(d.options, vec![("per_instance".to_string(), Expr::int(1))]);
assert_eq!(
d.type_options,
vec![("comment".to_string(), Expr::Const(Val::Str("hi".into())))]
);
let cpa = &d.coverpoints[0];
assert_eq!(cpa.ty, TypeInfo::unsigned(4));
assert!(matches!(cpa.expr, Expr::Kernel(_)));
assert!(matches!(cpa.iff, Some(Expr::Kernel(_))));
assert_eq!(cpa.options, vec![("at_least".to_string(), Expr::int(2))]);
let bin = |n: &str| cpa.bins.iter().find(|b| b.name == n).unwrap();
assert_eq!(
bin("one").body,
BinBody::Ranges {
items: vec![RangeItem::Single(Expr::int(1))],
with: None
}
);
assert_eq!(bin("rng").array, ArrayKind::Open);
assert_eq!(bin("fixed").array, ArrayKind::Fixed(Expr::int(3)));
assert_eq!(
bin("fixed").body,
BinBody::Ranges {
items: vec![RangeItem::Range(Bound::Expr(Expr::int(5)), Bound::Dollar)],
with: None
}
);
let BinBody::Ranges { items, .. } = &bin("arg").body else {
panic!("arg is a value bin")
};
assert!(matches!(items[0], RangeItem::Single(Expr::Kernel(_))));
let BinBody::Ranges { items, .. } = &bin("w").body else {
panic!("w is a value bin")
};
assert_eq!(
items[0],
RangeItem::Single(Expr::Const(Val::Pat {
bits: 0b1000,
care: 0b1010
}))
);
assert!(bin("w").wildcard);
let BinBody::Trans(sets) = &bin("t").body else {
panic!("t is a transition bin")
};
assert_eq!(sets.len(), 2);
assert_eq!(sets[0].len(), 3);
assert_eq!(sets[1].len(), 2);
let BinBody::Trans(rep) = &bin("rep").body else {
panic!("rep is a transition bin")
};
assert_eq!(
rep[0][0].rep,
TransRep::Consecutive(RepeatRange::between(2, 3))
);
assert_eq!(bin("ig").kind, BinKind::Ignore);
assert_eq!(bin("il").kind, BinKind::Illegal);
assert_eq!(bin("d").body, BinBody::Default);
let BinBody::Ranges {
with: Some(Expr::Kernel(k)),
..
} = &bin("wit").body
else {
panic!("wit is a value bin with a `with` kernel")
};
let names: Vec<&str> = x.kernels[*k as usize]
.binds
.iter()
.map(|s| s.name())
.collect();
assert_eq!(names, ["lo", "item"]);
});
}
#[test]
fn extracts_cross_selects_from_the_source() {
with_cg(|cg, trees| {
let x = extract(cg, trees).unwrap();
let cross = &x.decl.crosses[0];
assert_eq!(cross.items, ["cpa", "cpb"]);
let sel = &cross.bins[0];
assert_eq!(sel.name, "sel");
let SelectExpr::Or(l, r) = &sel.select else {
panic!("sel is an `||`: {:?}", sel.select)
};
assert_eq!(
**l,
SelectExpr::Binsof {
cp: "cpa".into(),
bin: Some("one".into()),
intersect: None
}
);
assert_eq!(
**r,
SelectExpr::Binsof {
cp: "cpb".into(),
bin: None,
intersect: Some(vec![RangeItem::Range(
Bound::Expr(Expr::int(0)),
Bound::Expr(Expr::int(3))
)])
}
);
let ig = &cross.bins[1];
assert_eq!(ig.kind, BinKind::Ignore);
assert!(matches!(ig.select, SelectExpr::Not(_)));
});
}
}