use crate::types::TypeWidth;
use std::collections::{HashMap, HashSet};
use std::num::NonZeroU32;
use sv_sim_sim::symid::SymbolUid;
use inkwell::AddressSpace;
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::basic_block::BasicBlock as LlvmBasicBlock;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::types::{AnyType, IntType};
use inkwell::values::{FunctionValue, GlobalValue, IntValue, PointerValue, StructValue};
use sv_lang::kinds::{ExpressionKind, StatementKind};
use sv_lang::{
Expression, KnownSystemName, SemNode, Statement, StatementBlockKind, Symbol, UnaryOp,
};
use sv_sim_sim::symid::SymbolId;
use sv_sim_sim::analysis::DpiVpiTag;
use sv_sim_sim::process::Effect;
use sv_sim_sim::process::cfg::{
RepeatCounterId, SegTerminator, Segment, SuspendPoint, SwitchOn, lower_to_segments,
};
use sv_sim_sim::process::liveness::{FrameLayout, FrameSlotSource, compute_liveness};
use sv_sim_sim::sched::{
JoinKind, ResolvedSuspend, SpawnSpec, WaitTableEntry, build_eval_result, mark_rechecks_on_wake,
};
use sv_sim_sim::state::{EdgeKind as StateEdgeKind, StateCellId};
use crate::ops::OpBuilder;
use crate::accessor::{
AccessorFns, build_get_scalar, build_get_wide, build_set_scalar, build_set_wide,
reposition_width_tight_to_fixed,
};
use crate::error::LoweringError;
use crate::expr::{
LowerCtx, MemAccessorCtx, MemorySignalInfo, lower_condition, lower_expr,
lower_negated_condition,
};
use crate::stmt_shared::{
lower_case, lower_do_while_loop, lower_for_loop, lower_foreach_loop, lower_forever_loop,
lower_loop_jump, lower_repeat_loop, lower_while_loop,
};
use crate::value::{PackedValue, ValueRepr};
pub const SEG_FINISHED: i32 = -1;
pub use sv_sim_exec::timing::place::{LocClass as SignalSlotClass, SlotRef as SignalSlot};
pub(crate) fn mem_signals_of(
signals: &HashMap<SymbolId, SignalSlot>,
) -> HashMap<SymbolId, MemorySignalInfo> {
signals
.iter()
.filter_map(|(id, slot)| match slot.class {
SignalSlotClass::Memory {
num_rows,
lower_index,
} => Some((
id.clone(),
MemorySignalInfo {
slot: slot.slot,
elem_width: slot.width,
signed: slot.signed,
num_rows,
lower_index,
},
)),
SignalSlotClass::Inline | SignalSlotClass::Wide => None,
})
.collect()
}
pub struct DisableScopeKeys {
pub instance: u32,
pub own_path: String,
pub ancestors: Vec<(u32, String)>,
pub ext_aliases: std::sync::Arc<HashMap<String, Vec<String>>>,
}
impl DisableScopeKeys {
pub fn expand(&self, resume: u32, label: &str, exit: u32) -> Vec<(u32, String, u32)> {
if sv_sim_sim::netlist::itf::is_scope_tag(label) {
return self
.ext_aliases
.get(label)
.into_iter()
.flatten()
.map(|k| (resume, k.clone(), exit))
.collect();
}
let mut out = vec![
(resume, label.to_string(), exit),
(resume, format!("/{}.{label}", self.own_path), exit),
];
if label.contains('.') {
out.push((resume, format!("{}/{label}", self.instance), exit));
}
out.extend(
self.ancestors
.iter()
.map(|(aid, rest)| (resume, format!("{aid}/{rest}.{label}"), exit)),
);
out
}
}
pub struct ProcessLoweringPlan<'d> {
pub fn_name: String,
pub fn_sym: Symbol<'d>,
pub segments: Vec<Segment<'d>>,
pub frame: FrameLayout,
pub frame_signed: Vec<bool>,
pub local_syms: HashMap<SymbolId, Symbol<'d>>,
pub signals: HashMap<SymbolId, SignalSlot>,
pub region_id: u32,
pub owning_instance: sv_sim_sim::netlist::InstanceId,
pub specify_dest_redirect: HashMap<SymbolId, SignalSlot>,
pub hosted_assertion: Option<crate::sva::proc::HostedAssertion<'d>>,
pub inferred_disable: Option<Option<(SymbolId, bool)>>,
pub nested_assertions: HashMap<String, usize>,
pub disable_keys: HashMap<String, Vec<usize>>,
pub fork_branch_names: Vec<String>,
pub disable_broadcast: Vec<(String, bool)>,
pub write_back_at_end: bool,
pub disable_scope_keys: Option<std::sync::Arc<DisableScopeKeys>>,
pub assertion_calls: std::cell::RefCell<Vec<crate::sva::actions::CallSite<'d>>>,
}
fn note_symbol<'d>(
sign_by_id: &mut HashMap<SymbolId, bool>,
sym_by_id: &mut HashMap<SymbolId, Symbol<'d>>,
sym: Symbol<'d>,
) {
sign_by_id
.entry(sym.uid())
.or_insert_with(|| sym.value_type().is_some_and(|t| t.is_signed()));
sym_by_id.entry(sym.uid()).or_insert(sym);
}
pub fn build_lowering_plan<'d>(
fn_name: String,
fn_sym: Symbol<'d>,
segments: Vec<Segment<'d>>,
frame: FrameLayout,
signals: HashMap<SymbolId, SignalSlot>,
) -> ProcessLoweringPlan<'d> {
let mut sign_by_id: HashMap<SymbolId, bool> = HashMap::new();
let mut sym_by_id: HashMap<SymbolId, Symbol<'d>> = HashMap::new();
for seg in &segments {
for b in &seg.bindings {
let mut found = Vec::new();
match b {
sv_sim_sim::process::cfg::ArgBinding::In { formal, actual } => {
found.push(*formal);
collect_named_value_symbols(actual.as_sem_node(), &mut found);
}
sv_sim_sim::process::cfg::ArgBinding::Init { var }
| sv_sim_sim::process::cfg::ArgBinding::Const { var, .. }
| sv_sim_sim::process::cfg::ArgBinding::ThisOf { var }
| sv_sim_sim::process::cfg::ArgBinding::Release { var }
| sv_sim_sim::process::cfg::ArgBinding::Step { var, .. } => found.push(*var),
sv_sim_sim::process::cfg::ArgBinding::Out { actual, formal }
| sv_sim_sim::process::cfg::ArgBinding::RefOpen { actual, formal, .. }
| sv_sim_sim::process::cfg::ArgBinding::RefClose { actual, formal, .. } => {
found.push(*formal);
collect_named_value_symbols(actual.as_sem_node(), &mut found);
}
sv_sim_sim::process::cfg::ArgBinding::Capture { value: e, .. }
| sv_sim_sim::process::cfg::ArgBinding::AssignCaptured { target: e, .. } => {
collect_named_value_symbols(e.as_sem_node(), &mut found);
}
sv_sim_sim::process::cfg::ArgBinding::FromCaptured { formal, .. }
| sv_sim_sim::process::cfg::ArgBinding::ToCaptured { formal, .. } => {
found.push(*formal);
}
sv_sim_sim::process::cfg::ArgBinding::CaptureThis { .. }
| sv_sim_sim::process::cfg::ArgBinding::CallResult { .. }
| sv_sim_sim::process::cfg::ArgBinding::ClassEventLeave => {}
sv_sim_sim::process::cfg::ArgBinding::AssocKey { var, arr, .. } => {
found.push(*var);
collect_named_value_symbols(arr.as_sem_node(), &mut found);
}
}
for sym in found {
note_symbol(&mut sign_by_id, &mut sym_by_id, sym);
}
}
for stmt in &seg.body {
let mut found = Vec::new();
collect_named_value_symbols(stmt.as_sem_node(), &mut found);
for sym in found {
note_symbol(&mut sign_by_id, &mut sym_by_id, sym);
}
}
if let Some((_, count)) = &seg.repeat_init
&& let Some(count_expr) = count.expr()
{
let mut found = Vec::new();
collect_named_value_symbols(count_expr.as_sem_node(), &mut found);
for sym in found {
note_symbol(&mut sign_by_id, &mut sym_by_id, sym);
}
}
let mut term_found = Vec::new();
match &seg.terminator {
SegTerminator::SwitchGoto { on, .. } => {
for e in on.reads() {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::CondGoto { cond, .. } => {
collect_named_value_symbols(cond.as_sem_node(), &mut term_found);
}
SegTerminator::AssertGoto { stmt, .. } => {
if let Some(e) = sv_sim_sim::process::cfg::assertion_cond(stmt) {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::CaseGoto { stmt, .. } => {
for e in sv_sim_sim::process::liveness::case_reads(stmt) {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Delay(e, _) | SuspendPoint::Level(e),
..
} => {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
SegTerminator::Suspend {
point: SuspendPoint::EdgeList(entries, iffs),
..
} => {
for (e, _) in entries {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
for g in iffs.iter().flatten() {
collect_named_value_symbols(g.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { signal, iff: Some(g), .. },
..
} => {
collect_named_value_symbols(g.as_sem_node(), &mut term_found);
if !sv_sim_sim::symid::is_value_name(signal.kind()) {
collect_named_value_symbols(signal.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::ImplicitComb(reads),
..
} => {
for e in reads {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { signal, .. },
..
} if !sv_sim_sim::symid::is_value_name(signal.kind()) => {
collect_named_value_symbols(signal.as_sem_node(), &mut term_found);
}
SegTerminator::Suspend {
point:
SuspendPoint::Edge { .. }
| SuspendPoint::CycleDelay { .. }
| SuspendPoint::OneStep
| SuspendPoint::WaitFork
| SuspendPoint::ByRequest,
..
}
| SegTerminator::Goto(_)
| SegTerminator::CondGotoCounter { .. }
| SegTerminator::ForkJoin { .. }
| SegTerminator::End { .. } => {}
}
for sym in term_found {
note_symbol(&mut sign_by_id, &mut sym_by_id, sym);
}
for step in &seg.step_exprs {
let mut found = Vec::new();
collect_named_value_symbols(step.as_sem_node(), &mut found);
for sym in found {
note_symbol(&mut sign_by_id, &mut sym_by_id, sym);
}
}
}
let frame_signed = frame
.slots
.iter()
.map(|slot| match &slot.source {
FrameSlotSource::DeclaredVar(id) => sign_by_id.get(id).copied().unwrap_or(false),
FrameSlotSource::Temporary(_) => false,
FrameSlotSource::SyntheticCounter(_) => false,
})
.collect();
let local_syms = frame
.slots
.iter()
.filter_map(|slot| match &slot.source {
FrameSlotSource::DeclaredVar(id) => sym_by_id.get(id).map(|s| (id.clone(), *s)),
FrameSlotSource::Temporary(_) | FrameSlotSource::SyntheticCounter(_) => None,
})
.collect();
ProcessLoweringPlan {
fn_name,
fn_sym,
segments,
frame,
frame_signed,
local_syms,
signals,
region_id: 0,
owning_instance: sv_sim_sim::netlist::InstanceId(0),
specify_dest_redirect: HashMap::new(),
hosted_assertion: None,
inferred_disable: None,
nested_assertions: HashMap::new(),
disable_keys: HashMap::new(),
fork_branch_names: Vec::new(),
disable_broadcast: Vec::new(),
write_back_at_end: false,
disable_scope_keys: None,
assertion_calls: std::cell::RefCell::new(Vec::new()),
}
}
pub fn find_named_value_symbol<'d>(segments: &[Segment<'d>], name: &str) -> Option<Symbol<'d>> {
for seg in segments {
for stmt in &seg.body {
let mut found = Vec::new();
collect_named_value_symbols(stmt.as_sem_node(), &mut found);
if let Some(sym) = found.into_iter().find(|s| s.name() == name) {
return Some(sym);
}
}
if let Some((_, count)) = &seg.repeat_init
&& let Some(count_expr) = count.expr()
{
let mut found = Vec::new();
collect_named_value_symbols(count_expr.as_sem_node(), &mut found);
if let Some(sym) = found.into_iter().find(|s| s.name() == name) {
return Some(sym);
}
}
let mut term_found = Vec::new();
match &seg.terminator {
SegTerminator::SwitchGoto { on, .. } => {
for e in on.reads() {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::CondGoto { cond, .. } => {
collect_named_value_symbols(cond.as_sem_node(), &mut term_found)
}
SegTerminator::AssertGoto { stmt, .. } => {
if let Some(e) = sv_sim_sim::process::cfg::assertion_cond(stmt) {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::CaseGoto { stmt, .. } => {
for e in sv_sim_sim::process::liveness::case_reads(stmt) {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Delay(e, _) | SuspendPoint::Level(e),
..
} => collect_named_value_symbols(e.as_sem_node(), &mut term_found),
SegTerminator::Suspend {
point: SuspendPoint::EdgeList(entries, iffs),
..
} => {
for (e, _) in entries {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
for g in iffs.iter().flatten() {
collect_named_value_symbols(g.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { signal, iff: Some(g), .. },
..
} => {
collect_named_value_symbols(g.as_sem_node(), &mut term_found);
if !sv_sim_sim::symid::is_value_name(signal.kind()) {
collect_named_value_symbols(signal.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::ImplicitComb(reads),
..
} => {
for e in reads {
collect_named_value_symbols(e.as_sem_node(), &mut term_found);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { signal, .. },
..
} if !sv_sim_sim::symid::is_value_name(signal.kind()) => {
collect_named_value_symbols(signal.as_sem_node(), &mut term_found);
}
SegTerminator::Suspend {
point:
SuspendPoint::Edge { .. }
| SuspendPoint::CycleDelay { .. }
| SuspendPoint::OneStep
| SuspendPoint::WaitFork
| SuspendPoint::ByRequest,
..
}
| SegTerminator::Goto(_)
| SegTerminator::CondGotoCounter { .. }
| SegTerminator::ForkJoin { .. }
| SegTerminator::End { .. } => {}
}
if let Some(sym) = term_found.into_iter().find(|s| s.name() == name) {
return Some(sym);
}
}
None
}
pub fn effect_of_statement(stmt: &Statement<'_>) -> Effect {
if stmt.kind() == StatementKind::ExpressionStatement
&& let Some(e) = stmt.expr()
&& e.kind() == ExpressionKind::Assignment
{
Effect::StateWrite
} else {
Effect::Pure
}
}
pub(crate) fn collect_named_value_symbols<'d>(node: SemNode<'d>, out: &mut Vec<Symbol<'d>>) {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& sv_sim_sim::symid::is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
{
if sym.kind() == sv_lang::kinds::SymbolKind::ModportPort
&& let Some(x) = sym.modport_port_explicit_connection()
{
stack.push(x.as_sem_node());
}
out.push(sym);
}
stack.extend(n.children());
}
}
fn vif_event<'d>(point: &SuspendPoint<'d>) -> Option<(Expression<'d>, sv_lang::EdgeKind)> {
let (signal, edge) = match point {
SuspendPoint::Edge { signal, edge, .. } => (signal, *edge),
SuspendPoint::CycleDelay { via: Some(via), .. } => {
return crate::vif::event_base(via).map(|_| (*via, sv_lang::EdgeKind::BothEdges));
}
SuspendPoint::EdgeList(entries, ..) if entries.len() == 1 => (&entries[0].0, entries[0].1),
SuspendPoint::EdgeList(entries, ..) if vif_event_list(point).is_some() => {
let (t, e) = entries
.iter()
.find(|(t, _)| vif_term_member(t).is_some())
.expect("a vif list has a vif term");
(t, *e)
}
_ => return None,
};
if crate::vif::event_base(signal).is_some() {
return Some((*signal, edge));
}
if edge == sv_lang::EdgeKind::None {
return crate::vif::event_elem_root(signal).map(|root| (root, edge));
}
None
}
fn vif_term_member<'d>(term: &Expression<'d>) -> Option<Expression<'d>> {
if crate::vif::event_base(term).is_some() {
Some(*term)
} else {
crate::vif::event_elem_root(term)
}
}
fn vif_event_list<'d>(
point: &SuspendPoint<'d>,
) -> Option<Vec<(Expression<'d>, sv_lang::EdgeKind)>> {
let SuspendPoint::EdgeList(entries, _) = point else {
return None;
};
if entries.len() < 2 {
return None;
}
let mut any = false;
for (term, _) in entries {
let Some(member) = vif_term_member(term) else {
continue;
};
let base = crate::vif::event_base(&member)?;
vif_var_key(&base)?;
any = true;
}
any.then(|| entries.clone())
}
fn vif_var_key(base: &Expression<'_>) -> Option<String> {
let text = sv_sim_sim::process::cfg::source_text_range(base.source_range())?;
Some(text.split_whitespace().collect())
}
fn vif_event_vars<'d>(point: &SuspendPoint<'d>) -> Vec<Expression<'d>> {
if let Some(terms) = vif_event_list(point) {
let mut seen: Vec<String> = Vec::new();
let mut out = Vec::new();
for (term, _) in terms {
let Some(member) = vif_term_member(&term) else {
continue;
};
let Some(base) = crate::vif::event_base(&member) else {
continue;
};
let Some(key) = vif_var_key(&base) else {
continue;
};
if !seen.contains(&key) {
seen.push(key);
out.push(member);
}
}
return out;
}
vif_event(point)
.map(|(signal, _)| vec![signal])
.unwrap_or_default()
}
fn vif_point_key<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
point: &SuspendPoint<'d>,
resume: u32,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
vif_vars_key(cx, &vif_event_vars(point), resume)
}
fn vif_vars_key<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
vars: &[Expression<'d>],
resume: u32,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
if let [signal] = vars {
return crate::vif::suspend_key(
cx,
crate::vif::event_base(signal).expect("vif_event checked it"),
resume,
vif_candidates(signal).len(),
);
}
let bases: Vec<(Expression<'d>, usize)> = vars
.iter()
.map(|s| {
(
crate::vif::event_base(s).expect("a vif term"),
vif_candidates(s).len(),
)
})
.collect();
crate::vif::suspend_key_multi(cx, &bases, resume)
}
fn vif_event_term<'d>(point: &SuspendPoint<'d>) -> Option<Expression<'d>> {
vif_event(point)?;
match point {
SuspendPoint::Edge { signal, .. } => Some(*signal),
SuspendPoint::CycleDelay { via, .. } => *via,
SuspendPoint::EdgeList(entries, ..) => entries
.iter()
.map(|(e, _)| *e)
.find(|e| vif_term_member(e).is_some())
.or_else(|| entries.first().map(|(e, _)| *e)),
_ => None,
}
}
pub(crate) fn vif_candidates<'d>(e: &Expression<'d>) -> Vec<Symbol<'d>> {
sv_sim_sim::netlist::vif::member_targets(e)
.map(|t| t.into_iter().flatten().collect())
.unwrap_or_default()
}
fn external_refs_in_segment<'d>(
seg: &Segment<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
owner: &SymbolId,
extra: &[Expression<'d>],
) -> (Vec<Symbol<'d>>, HashSet<SymbolId>, Vec<SymbolId>) {
let mut order = Vec::new();
let mut seen = HashSet::new();
let mut written = HashSet::new();
let mut hidden: Vec<SymbolId> = Vec::new();
let mut hidden_seen: HashSet<SymbolId> = HashSet::new();
let is_memory_signal = |id: &SymbolId| {
matches!(
signals.get(id).map(|s| s.class),
Some(SignalSlotClass::Memory { .. })
)
};
let mut record = |node: SemNode<'d>,
order: &mut Vec<Symbol<'d>>,
seen: &mut HashSet<SymbolId>| {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::ProceduralChecker
{
for instance in s.procedural_checker_instances() {
for block in sv_sim_sim::netlist::module_items(&instance) {
let assertion = crate::module::static_concurrent_assertion_of(&block)
.or_else(|| crate::module::static_deferred_assertion_of(&block));
stack.extend(assertion.map(|a| a.as_sem_node()));
}
}
}
if let Some(s) = n.as_statement()
&& matches!(s.kind(), StatementKind::List | StatementKind::Block)
{
for var in sv_sim_sim::process::cfg::block_automatic_vars(&s, false) {
if let Some(init) = var.initializer() {
stack.push(init.as_sem_node());
}
}
}
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::EventTrigger
&& s.is_nonblocking_trigger() != Some(true)
&& let Some(target) = s.expressions().into_iter().next()
&& let Some(()) = {
for sym in vif_candidates(&target) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
Some(())
}
&& let Some(sym) = target.referenced_symbol()
&& signals.contains_key(&sym.uid())
&& !is_memory_signal(&sym.uid())
{
written.insert(sym.uid());
}
if let Some(s) = n.as_statement()
&& matches!(
s.kind(),
StatementKind::ProceduralAssign | StatementKind::ProceduralDeassign
)
{
for e in s.expressions() {
if e.kind() == ExpressionKind::Assignment {
stack.extend(e.left().map(|x| x.as_sem_node()));
stack.extend(e.right().map(|x| x.as_sem_node()));
} else {
stack.push(e.as_sem_node());
}
}
continue;
}
if let Some(e) = n.as_expression() {
if let Some(inner) = crate::expr::modport_expr_target(&e) {
stack.push(inner.as_sem_node());
}
if sv_sim_sim::symid::is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
&& signals.contains_key(&sym.uid())
&& !is_memory_signal(&sym.uid())
&& seen.insert(sym.uid())
{
order.push(sym);
}
if let Some(inst) = sv_sim_sim::sva::endpoint::watched_call(&e) {
let id = sv_sim_sim::sva::endpoint::watcher_id(owner, &inst);
if signals.contains_key(&id) && hidden_seen.insert(id.clone()) {
hidden.push(id);
}
}
if e.call_subroutine_name().as_deref() == Some("$inferred_disable")
&& let Some(id) = crate::sva::emit::inferred_disable_signal()
&& signals.contains_key(&id)
&& !is_memory_signal(&id)
&& !seen.contains(&id)
&& hidden_seen.insert(id.clone())
{
hidden.push(id);
}
if let Ok(Some(call)) = sv_sim_sim::sva::sampled::decode(e) {
let args = sv_sim_sim::sva::sampled::strict_site_signals_in(call.arg)
.unwrap_or_default();
let views = sv_sim_sim::sva::sampled::use_views(owner, &call, &args);
for (_, cell) in views.cur.into_iter().chain(views.prev) {
if signals.contains_key(&cell) && hidden_seen.insert(cell.clone()) {
hidden.push(cell);
}
}
}
for sym in vif_candidates(&e) {
if signals.contains_key(&sym.uid())
&& !is_memory_signal(&sym.uid())
&& seen.insert(sym.uid())
{
order.push(sym);
}
}
if e.kind() == ExpressionKind::Assignment
&& e.is_nonblocking() != Some(true)
&& let Some(lhs) = e.left()
{
for sym in vif_candidates(&lhs) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
}
if e.kind() == ExpressionKind::Assignment
&& e.is_nonblocking() != Some(true)
&& let Some(lhs) = e.left()
{
for sym in crate::select::lvalue_root_symbols(lhs) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
}
if e.kind() == ExpressionKind::UnaryOp
&& matches!(
e.unary_op(),
Some(
UnaryOp::Preincrement
| UnaryOp::Predecrement
| UnaryOp::Postincrement
| UnaryOp::Postdecrement
)
)
&& let Some(operand) = e.operand()
{
for sym in crate::select::lvalue_root_symbols(operand) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
&& matches!(
sub.known_name_id(),
KnownSystemName::ValuePlusArgs
| KnownSystemName::Random
| KnownSystemName::URandom
)
{
let out_arg = if sub.known_name_id() == KnownSystemName::ValuePlusArgs {
e.operands().get(1).copied()
} else {
e.operands().first().copied()
};
if let Some(arg) = out_arg {
let target = if arg.kind() == ExpressionKind::Assignment {
arg.left().unwrap_or(arg)
} else {
arg
};
for sym in crate::select::lvalue_root_symbols(target) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
}
}
if e.kind() == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& let Some(sub) = e.system_subroutine()
{
let first_out = match sub.known_name_id() {
KnownSystemName::QInitialize | KnownSystemName::QAdd => Some(3),
KnownSystemName::QRemove | KnownSystemName::QExam => Some(2),
KnownSystemName::QFull | KnownSystemName::CountDrivers => Some(1),
_ => None,
};
let outs = e.operands();
let outs = first_out.map_or(&[][..], |i| {
let i = i.min(outs.len());
&outs[i..]
});
let outs = if sub.known_name_id() == KnownSystemName::QRemove {
let all = e.operands();
all.get(1..).unwrap_or(&[]).to_vec()
} else {
outs.to_vec()
};
for arg in outs {
let target = if arg.kind() == ExpressionKind::Assignment {
arg.left().unwrap_or(arg)
} else {
arg
};
for sym in crate::select::lvalue_root_symbols(target) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
}
}
for callee in crate::func::user_callees(&e) {
let abi = crate::func::dispatch_abi(&callee);
for sig in abi.signals {
let id = sig.uid();
if !signals.contains_key(&id) || is_memory_signal(&id) {
continue;
}
if abi.written.contains(&id) {
written.insert(id.clone());
}
if seen.insert(id) {
order.push(sig);
}
}
}
}
stack.extend(sv_sim_sim::process::cfg::live_children(&n));
}
};
let mut binding_written: Vec<SymbolId> = Vec::new();
let set_signal = |var: &Symbol<'d>,
order: &mut Vec<Symbol<'d>>,
seen: &mut HashSet<SymbolId>,
binding_written: &mut Vec<SymbolId>| {
let id = var.uid();
if signals.contains_key(&id) && !is_memory_signal(&id) {
binding_written.push(id.clone());
if seen.insert(id) {
order.push(*var);
}
}
};
for b in &seg.bindings {
match b {
sv_sim_sim::process::cfg::ArgBinding::In { actual, formal } => {
record(actual.as_sem_node(), &mut order, &mut seen);
set_signal(formal, &mut order, &mut seen, &mut binding_written);
}
sv_sim_sim::process::cfg::ArgBinding::Init { var } => {
if let Some(init) = var.initializer() {
record(init.as_sem_node(), &mut order, &mut seen);
}
set_signal(var, &mut order, &mut seen, &mut binding_written);
}
sv_sim_sim::process::cfg::ArgBinding::Const { .. }
| sv_sim_sim::process::cfg::ArgBinding::Step { .. }
| sv_sim_sim::process::cfg::ArgBinding::ThisOf { .. }
| sv_sim_sim::process::cfg::ArgBinding::Release { .. } => {}
sv_sim_sim::process::cfg::ArgBinding::Capture { value, .. } => {
record(value.as_sem_node(), &mut order, &mut seen);
}
sv_sim_sim::process::cfg::ArgBinding::RefOpen { actual, .. }
| sv_sim_sim::process::cfg::ArgBinding::RefClose { actual, .. } => {
record(actual.as_sem_node(), &mut order, &mut seen);
}
sv_sim_sim::process::cfg::ArgBinding::FromCaptured { .. }
| sv_sim_sim::process::cfg::ArgBinding::CaptureThis { .. }
| sv_sim_sim::process::cfg::ArgBinding::ToCaptured { .. }
| sv_sim_sim::process::cfg::ArgBinding::CallResult { .. }
| sv_sim_sim::process::cfg::ArgBinding::ClassEventLeave => {}
sv_sim_sim::process::cfg::ArgBinding::AssocKey { arr, .. } => {
record(arr.as_sem_node(), &mut order, &mut seen);
}
sv_sim_sim::process::cfg::ArgBinding::Out {
actual: target,
formal,
} => {
let id = formal.uid();
if signals.contains_key(&id) && !is_memory_signal(&id) && seen.insert(id) {
order.push(*formal);
}
record(target.as_sem_node(), &mut order, &mut seen);
for sym in crate::select::lvalue_root_symbols(*target) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
binding_written.push(sym.uid());
}
}
}
sv_sim_sim::process::cfg::ArgBinding::AssignCaptured { target, nba, .. } => {
record(target.as_sem_node(), &mut order, &mut seen);
if !*nba {
for sym in crate::select::lvalue_root_symbols(*target) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
binding_written.push(sym.uid());
}
}
}
}
}
}
for stmt in &seg.body {
record(stmt.as_sem_node(), &mut order, &mut seen);
}
for e in extra {
record(e.as_sem_node(), &mut order, &mut seen);
}
for step in &seg.step_exprs {
record(step.as_sem_node(), &mut order, &mut seen);
}
if let Some((_, count)) = &seg.repeat_init
&& let Some(count_expr) = count.expr()
{
record(count_expr.as_sem_node(), &mut order, &mut seen);
}
match &seg.terminator {
SegTerminator::SwitchGoto { on, .. } => {
for e in on.reads() {
record(e.as_sem_node(), &mut order, &mut seen);
}
}
SegTerminator::CondGoto { cond, .. } => record(cond.as_sem_node(), &mut order, &mut seen),
SegTerminator::AssertGoto { stmt, .. } => {
if let Some(e) = sv_sim_sim::process::cfg::assertion_cond(stmt) {
record(e.as_sem_node(), &mut order, &mut seen);
}
}
SegTerminator::CaseGoto { stmt, .. } => {
for e in sv_sim_sim::process::liveness::case_reads(stmt) {
record(e.as_sem_node(), &mut order, &mut seen);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Delay(e, _) | SuspendPoint::Level(e),
..
} => record(e.as_sem_node(), &mut order, &mut seen),
SegTerminator::Suspend {
point: SuspendPoint::EdgeList(entries, iffs),
..
} => {
for (e, _) in entries {
record(e.as_sem_node(), &mut order, &mut seen);
}
for g in iffs.iter().flatten() {
record(g.as_sem_node(), &mut order, &mut seen);
}
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { signal, iff: Some(g), .. },
..
} => {
record(g.as_sem_node(), &mut order, &mut seen);
if !sv_sim_sim::symid::is_value_name(signal.kind()) {
record(signal.as_sem_node(), &mut order, &mut seen);
}
}
SegTerminator::Suspend {
point: SuspendPoint::ImplicitComb(reads),
..
} => {
for e in reads {
record(e.as_sem_node(), &mut order, &mut seen);
}
}
SegTerminator::Suspend { point, .. } if vif_event(point).is_some() => {
if let Some(signal) = vif_event_term(point) {
record(signal.as_sem_node(), &mut order, &mut seen)
}
for signal in vif_event_vars(point) {
record(signal.as_sem_node(), &mut order, &mut seen)
}
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { signal, .. },
..
} if !sv_sim_sim::symid::is_value_name(signal.kind()) => {
record(signal.as_sem_node(), &mut order, &mut seen)
}
SegTerminator::Suspend {
point:
SuspendPoint::Edge { .. }
| SuspendPoint::CycleDelay { .. }
| SuspendPoint::OneStep
| SuspendPoint::WaitFork
| SuspendPoint::ByRequest,
..
}
| SegTerminator::Goto(_)
| SegTerminator::CondGotoCounter { .. }
| SegTerminator::ForkJoin { .. }
| SegTerminator::End { .. } => {}
}
written.extend(binding_written);
(order, written, hidden)
}
fn int_type(ctx: &Context, w: u32) -> IntType<'_> {
ctx.custom_width_int_type(NonZeroU32::new(w).expect("zero-width int type"))
.expect("custom_width_int_type")
}
fn slot_storage_type<'ctx>(
ctx: &'ctx Context,
slot: &sv_sim_sim::process::liveness::FrameSlotDesc,
) -> inkwell::types::BasicTypeEnum<'ctx> {
match slot.repr {
sv_sim_sim::process::FrameSlot::Array { rows, row_words } => ctx
.i64_type()
.array_type(rows * u32::from(row_words))
.into(),
_ => int_type(ctx, slot.width_bits * 2).into(),
}
}
fn reject_huge_frame_slots(plan: &ProcessLoweringPlan<'_>) -> Result<(), LoweringError> {
for slot in &plan.frame.slots {
if !matches!(slot.repr, sv_sim_sim::process::FrameSlot::Array { .. })
&& slot.width_bits > crate::value::WIDE_THRESHOLD
{
return Err(LoweringError::unsupported_at(
&format!(
"a process-local variable of {} bits (LRM 1800-2023 6.9.1): a vector wider \
than {} bits is supported only as a module-level signal",
slot.width_bits,
crate::value::WIDE_THRESHOLD,
),
&plan.fn_sym,
));
}
}
Ok(())
}
fn copy_frame_slot<'ctx>(
ctx: &'ctx Context,
b: &Builder<'ctx>,
slot: &sv_sim_sim::process::liveness::FrameSlotDesc,
dst: PointerValue<'ctx>,
src: PointerValue<'ctx>,
) {
match slot.repr {
sv_sim_sim::process::FrameSlot::Array { rows, row_words } => {
let bytes = u64::from(rows) * u64::from(row_words) * 8;
b.build_memcpy(dst, 8, src, 8, ctx.i64_type().const_int(bytes, false))
.unwrap();
}
_ => {
let ty = int_type(ctx, slot.width_bits * 2);
let v = b.build_load(ty, src, "frame_ld").unwrap();
b.build_store(dst, v).unwrap();
}
}
}
fn frame_slot_ptr<'ctx>(
ctx: &'ctx Context,
b: &Builder<'ctx>,
local: PointerValue<'ctx>,
word_offset: u32,
) -> PointerValue<'ctx> {
let byte_offset = u64::from(word_offset) * 8;
let i8_ty = ctx.i8_type();
let idx = ctx.i64_type().const_int(byte_offset, false);
unsafe {
b.build_gep(i8_ty, local, &[idx], "frame_slot_ptr")
.expect("in-bounds GEP into the caller-sized frame buffer")
}
}
fn declare_process_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
name: &str,
) -> FunctionValue<'ctx> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let fn_ty = i32_ty.fn_type(&[ptr_ty.into(), ptr_ty.into(), i32_ty.into()], false);
let f = module.add_function(name, fn_ty, None);
f.get_nth_param(0).unwrap().set_name("handle");
f.get_nth_param(1).unwrap().set_name("local");
f.get_nth_param(2).unwrap().set_name("resume_pc");
f
}
fn lower_step_expr<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
if e.kind() == ExpressionKind::Call && e.is_system_call() == Some(false) {
return crate::func::lower_task_call(cx, e);
}
lower_expr(cx, e)?;
Ok(())
}
fn lower_segment_statement<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
) -> Result<(), LoweringError> {
let unwrapped = sv_sim_sim::process::cfg::unwrap_invalid(*stmt);
let stmt = &unwrapped;
crate::vpi_dyn::emit_entry_probe(cx, stmt);
match stmt.kind() {
StatementKind::List | StatementKind::Block => {
for var in sv_sim_sim::process::cfg::block_automatic_vars(stmt, false) {
init_local_var(cx, &var)?;
}
let blk = stmt
.block_symbol()
.filter(crate::covergroup::is_block_target);
if let Some(b) = &blk {
crate::covergroup::emit_block_event_of(cx, handle, b, false)?;
}
let exit = if sv_sim_sim::process::cfg::self_disabling_assertion_block(stmt) {
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
let bb = cx.ctx.append_basic_block(f, "disable_exit");
cx.block_exits
.push((sv_sim_sim::process::cfg::block_name(stmt), bb));
Some(bb)
} else {
None
};
for child in sv_sim_sim::process::cfg::live_statements(stmt) {
if crate::stmt_shared::current_block_terminated(cx) {
break;
}
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &child)?;
}
if let Some(bb) = exit {
cx.block_exits.pop();
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(bb).unwrap();
}
cx.b.position_at_end(bb);
}
if let Some(b) = &blk
&& !crate::stmt_shared::current_block_terminated(cx)
{
crate::covergroup::emit_block_event_of(cx, handle, b, true)?;
}
if stmt.block_kind() == Some(StatementBlockKind::Sequential)
&& !crate::stmt_shared::current_block_terminated(cx)
{
for var in sv_sim_sim::process::cfg::block_automatic_vars(stmt, false)
.iter()
.rev()
{
release_task_local(cx, var);
}
}
Ok(())
}
StatementKind::Empty => Ok(()),
StatementKind::Conditional => crate::stmt_shared::lower_if(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::VariableDeclaration => Ok(()),
StatementKind::ExpressionStatement => {
let e = stmt.expr().ok_or_else(|| {
LoweringError::unsupported_at("expression statement missing expr", &cx.fn_sym)
})?;
if e.kind() == ExpressionKind::Call && e.is_system_call() == Some(true) {
lower_system_call_statement(cx, handle, signals, sys_fns, e)
} else if e.kind() == ExpressionKind::Assignment && e.is_nonblocking() == Some(true) {
lower_nonblocking_assignment(cx, handle, signals, nba_fn, e)
} else if e.kind() == ExpressionKind::Call && e.is_system_call() == Some(false) {
crate::func::lower_task_call(cx, e)
} else {
crate::huge::discard(lower_expr(cx, e)?);
Ok(())
}
}
StatementKind::Case => lower_case(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::PatternCase => {
crate::stmt_shared::lower_pattern_case(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
})
}
StatementKind::ImmediateAssertion => {
let env = crate::deferred_call::DeferredEnv { handle, signals };
lower_immediate_assertion(cx, sys_fns, stmt, Some(&env), &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, s)
})
}
StatementKind::ConcurrentAssertion => {
lower_concurrent_assertion(cx, handle, signals, nba_fn, sys_fns, stmt, &[])
}
StatementKind::ProceduralChecker => {
lower_procedural_checker(cx, handle, signals, nba_fn, sys_fns, stmt)
}
StatementKind::RandCase => crate::stmt_shared::lower_randcase(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::RepeatLoop => lower_repeat_loop(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::WhileLoop => lower_while_loop(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::DoWhileLoop => lower_do_while_loop(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::ForLoop => lower_for_loop(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::ForeverLoop => lower_forever_loop(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::ForeachLoop => lower_foreach_loop(cx, *stmt, &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &s)
}),
StatementKind::ProceduralAssign => {
crate::force::lower_procedural_assign(cx, handle, signals, *stmt)
}
StatementKind::ProceduralDeassign => {
crate::force::lower_procedural_deassign(cx, handle, signals, *stmt)
}
StatementKind::Break => lower_loop_jump(cx, true),
StatementKind::Continue => lower_loop_jump(cx, false),
StatementKind::EventTrigger => lower_event_trigger_statement(cx, handle, signals, stmt),
StatementKind::Timed => crate::class_events::lower_wait(cx, stmt),
StatementKind::DisableFork => {
let f = cx
.module
.get_function(SV_RT_DISABLE_FORK_SYMBOL)
.unwrap_or_else(|| {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let fn_ty = cx.ctx.void_type().fn_type(&[ptr_ty.into()], false);
cx.module.add_function(
SV_RT_DISABLE_FORK_SYMBOL,
fn_ty,
Some(Linkage::External),
)
});
cx.b.build_call(f, &[handle.into()], "rt_disable_fork_call")
.unwrap();
Ok(())
}
StatementKind::Disable => {
let mut label = sv_sim_sim::process::cfg::disable_leaf_label(stmt)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &cx.fn_sym))?;
if lower_deferred_disable(cx, &label)? {
return Ok(());
}
if let Some(&(_, bb)) = cx.block_exits.iter().rev().find(|(n, _)| *n == label) {
cx.b.build_unconditional_branch(bb).unwrap();
return Ok(());
}
if let Some(path) = sv_sim_sim::process::cfg::disable_path(stmt) {
let dotted = path.join(".");
if ASSERTION_LOWERING.with(|c| {
c.borrow()
.as_ref()
.is_some_and(|a| a.disable_broadcast.iter().any(|(n, _)| *n == dotted))
}) {
label = dotted;
}
}
let keys = ASSERTION_LOWERING.with(|c| {
c.borrow()
.as_ref()
.and_then(|a| a.disable_keys.get(&label).cloned())
});
let broadcast = ASSERTION_LOWERING.with(|c| {
c.borrow().as_ref().and_then(|a| {
a.disable_broadcast
.iter()
.find(|(n, _)| *n == label)
.map(|(_, whole)| *whole)
})
});
if let Some(keys) = keys {
lower_assertion_disable(cx, &keys)?;
if broadcast.is_none() {
return Ok(());
}
}
let is_cross_process = broadcast.is_some()
|| ASSERTION_LOWERING.with(|c| {
c.borrow()
.as_ref()
.is_some_and(|a| a.fork_branch_names.contains(&label))
});
if is_cross_process {
let f = cx
.module
.get_function(SV_RT_DISABLE_NAMED_SYMBOL)
.unwrap_or_else(|| {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let fn_ty = cx
.ctx
.void_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into()], false);
cx.module.add_function(
SV_RT_DISABLE_NAMED_SYMBOL,
fn_ty,
Some(Linkage::External),
)
});
let label_ptr =
cx.b.build_global_string_ptr(&label, "disable_named_label")
.unwrap()
.as_pointer_value();
cx.b.build_call(
f,
&[handle.into(), label_ptr.into()],
"rt_disable_named_call",
)
.unwrap();
if broadcast == Some(true) {
crate::vpi_dyn::emit_disable_probe(cx, stmt);
}
return crate::stmt_shared::leave_if_aborted(cx);
}
let f = cx
.module
.get_function(SV_RT_DISABLE_FORK_BLOCK_SYMBOL)
.unwrap_or_else(|| {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let fn_ty = cx.ctx.void_type().fn_type(&[ptr_ty.into()], false);
cx.module.add_function(
SV_RT_DISABLE_FORK_BLOCK_SYMBOL,
fn_ty,
Some(Linkage::External),
)
});
cx.b.build_call(f, &[handle.into()], "rt_disable_fork_block_call")
.unwrap();
crate::vpi_dyn::emit_disable_probe(cx, stmt);
Ok(())
}
other => Err(LoweringError::unsupported_at(
&format!(
"statement kind {other:?} cannot appear in a Segment's own body under the A2 \
slice's grammar -- if/else and suspend points are already extracted into \
SegTerminator::CondGoto/Suspend by process::cfg::build_cfg (Task S3a); this \
is process::cfg's own \"ordinary straight-line statement\" fallback reaching \
an un-decomposed construct this crate's codegen does not lower"
),
&cx.fn_sym,
)),
}
}
fn lower_event_trigger_statement<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
stmt: &Statement<'d>,
) -> Result<(), LoweringError> {
let target = stmt.expressions().into_iter().next().ok_or_else(|| {
LoweringError::unsupported_at(
"EventTrigger statement has no target expression",
&cx.fn_sym,
)
})?;
if stmt.is_nonblocking_trigger() != Some(true) {
return crate::expr::lower_event_trigger(cx, target);
}
let mut delay = cx.ctx.i64_type().const_zero();
if let Some(tc) = stmt.timing().and_then(|n| n.as_timing_control())
&& tc.kind() == sv_lang::kinds::TimingControlKind::Delay
{
let d = tc
.children()
.into_iter()
.find_map(|n| n.as_expression())
.ok_or_else(|| LoweringError::unsupported_at("delay with no value", &cx.fn_sym))?;
delay = crate::timescale::lower_delay_ticks(cx, d)?;
}
if crate::class_events::lower_trigger_nba(cx, target, delay)? {
return Ok(());
}
let i32_ty = cx.ctx.i32_type();
let (slot, row, has_row, valid) =
if let Some(el) = crate::arrays::resolve_event_element(cx, target)? {
(el.info.slot, el.row, 1u64, Some(el.valid))
} else {
let sym = target.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
"->> (nonblocking event trigger): unresolved event name",
&cx.fn_sym,
)
})?;
crate::expr::event_cell_ptr(cx, target, "->> (nonblocking event trigger)")?;
let info = signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"->> (nonblocking event trigger): the event is not a module-level signal",
&sym,
)
})?;
(info.slot, cx.ctx.i64_type().const_zero(), 0u64, None)
};
let stage = || -> Result<(), LoweringError> {
let f = rt_nba_pulse_fn(cx);
cx.b.build_call(
f,
&[
handle.into(),
i32_ty.const_int(u64::from(slot), false).into(),
row.into(),
i32_ty.const_int(has_row, false).into(),
delay.into(),
],
"rt_nba_pulse_call",
)
.unwrap();
Ok(())
};
match valid {
Some(valid) => crate::arrays::when_valid(cx, valid, stage),
None => stage(),
}
}
fn rt_nba_pulse_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
cx.module
.get_function(SV_RT_NBA_PULSE_SYMBOL)
.unwrap_or_else(|| {
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let fn_ty = cx.ctx.void_type().fn_type(
&[
ptr.into(),
i32_ty.into(),
i64_ty.into(),
i32_ty.into(),
i64_ty.into(),
],
false,
);
cx.module
.add_function(SV_RT_NBA_PULSE_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn clocking_output<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
signals: &HashMap<SymbolId, SignalSlot>,
lhs: sv_lang::Expression<'d>,
) -> Result<Option<ClockDrive<'ctx, 'd>>, LoweringError> {
let Some(cv) = lhs
.referenced_symbol()
.filter(|s| s.kind() == sv_lang::kinds::SymbolKind::ClockVar)
else {
return Ok(None);
};
let target = cv
.initializer()
.ok_or_else(|| LoweringError::unsupported_at("a clock variable with no signal", &cv))?;
let mut skew = cv.clock_var_output_skew();
if !skew.has_value()
&& let Some(cb) = cv.parent()
{
skew = cb.clocking_block_default_output_skew();
}
let ticks = match skew
.delay
.and_then(|n| n.as_timing_control())
.and_then(|tc| tc.children().into_iter().find_map(|n| n.as_expression()))
{
Some(d) => crate::timescale::lower_delay_ticks(cx, d)?,
None => cx.ctx.i64_type().const_zero(),
};
let edge = match skew.edge {
sv_lang::EdgeKind::None => None,
kind @ (sv_lang::EdgeKind::PosEdge
| sv_lang::EdgeKind::NegEdge
| sv_lang::EdgeKind::BothEdges) => {
let ev = cv
.parent()
.and_then(|cb| cb.clocking_block_event())
.and_then(|n| n.as_timing_control())
.ok_or_else(|| {
LoweringError::unsupported_at("a clocking block with no event", &cv)
})?;
let clock = sv_sim_sim::sva::sampled::clock_event_of(ev)
.map_err(|e| LoweringError::unsupported_at(&e.0, &cv))?;
Some((clock.signal, kind))
}
};
let vif_lhs = sv_sim_sim::netlist::vif::is_member_access(&lhs).then_some(lhs);
let counter = if edge.is_none() && vif_lhs.is_none() {
cv.parent()
.and_then(|cb| signals.get(&cb.uid()))
.map(|s| s.slot)
} else {
None
};
Ok(Some(ClockDrive {
target,
ticks,
edge,
counter,
vif_lhs,
}))
}
struct ClockDrive<'ctx, 'd> {
target: sv_lang::Expression<'d>,
ticks: IntValue<'ctx>,
edge: Option<(sv_lang::Expression<'d>, sv_lang::EdgeKind)>,
counter: Option<u32>,
vif_lhs: Option<sv_lang::Expression<'d>>,
}
impl ClockDrive<'_, '_> {
fn vif_counter(&self) -> bool {
self.edge.is_none() && self.vif_lhs.is_some()
}
}
const EDGE_DELAY: u64 = u64::MAX;
fn emit_reactive_drive<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
drive: &ClockDrive<'ctx, '_>,
open: bool,
) -> Result<(), LoweringError> {
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let f = cx
.module
.get_function(SV_RT_REACTIVE_DRIVE_SYMBOL)
.unwrap_or_else(|| {
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let fn_ty = cx.ctx.void_type().fn_type(
&[
ptr.into(),
i32_ty.into(),
i32_ty.into(),
i32_ty.into(),
i64_ty.into(),
],
false,
);
cx.module
.add_function(SV_RT_REACTIVE_DRIVE_SYMBOL, fn_ty, Some(Linkage::External))
});
let (mut slot, mut code, mut after) = (i32_ty.const_zero(), 0u64, i64_ty.const_zero());
if open && let Some((sig, kind)) = drive.edge {
slot = if let Some(l) = drive.vif_lhs {
crate::vif::edge_clock_slot(cx, signals, l, false).ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})??
} else {
let sym = sig.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
"an edge output skew whose clocking event is not a plain signal",
&cx.fn_sym,
)
})?;
let info = signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"an edge output skew whose clock is not a module-level signal",
&sym,
)
})?;
i32_ty.const_int(u64::from(info.slot), false)
};
code = match kind {
sv_lang::EdgeKind::PosEdge => 1,
sv_lang::EdgeKind::NegEdge => 2,
sv_lang::EdgeKind::BothEdges => 3,
sv_lang::EdgeKind::None => unreachable!("drive.edge is only set for an edge skew"),
};
after = drive.ticks;
} else if open && drive.vif_counter() {
slot = crate::vif::edge_clock_slot(cx, signals, drive.vif_lhs.expect("vif_counter"), true)
.ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member access", &cx.fn_sym)
})??;
code = 4;
after = drive.ticks;
} else if open && let Some(c) = drive.counter {
slot = i32_ty.const_int(u64::from(c), false);
code = 4;
after = drive.ticks;
}
cx.b.build_call(
f,
&[
handle.into(),
i32_ty.const_int(u64::from(open), false).into(),
slot.into(),
i32_ty.const_int(code, false).into(),
after.into(),
],
"rt_reactive_drive",
)
.unwrap();
Ok(())
}
fn drive_delay<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, drive: &ClockDrive<'ctx, '_>) -> IntValue<'ctx> {
if drive.edge.is_some() || drive.counter.is_some() || drive.vif_counter() {
cx.ctx.i64_type().const_int(EDGE_DELAY, false)
} else {
drive.ticks
}
}
fn is_clock_var(lhs: sv_lang::Expression<'_>) -> bool {
!matches!(
lhs.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) && lhs
.referenced_symbol()
.is_some_and(|s| s.kind() == sv_lang::kinds::SymbolKind::ClockVar)
}
fn clock_var_root<'d>(mut lhs: sv_lang::Expression<'d>) -> Option<sv_lang::Expression<'d>> {
while matches!(
lhs.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
lhs = lhs.select_value()?;
}
is_clock_var(lhs).then_some(lhs)
}
pub(crate) fn nba_intra_delay<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
e: sv_lang::Expression<'_>,
) -> Result<Option<IntValue<'ctx>>, LoweringError> {
let Some(node) = e.assignment_timing() else {
return Ok(None);
};
let tc = node.as_timing_control().ok_or_else(|| {
LoweringError::unsupported_at("assignment timing is not a timing control", &cx.fn_sym)
})?;
if tc.kind() != sv_lang::kinds::TimingControlKind::Delay {
return Err(LoweringError::unsupported_at(
&format!(
"a nonblocking assignment with an intra-assignment {:?} control (only a delay, \
`q <= #d v`)",
tc.kind()
),
&cx.fn_sym,
));
}
let d = tc
.children()
.into_iter()
.find_map(|n| n.as_expression())
.ok_or_else(|| LoweringError::unsupported_at("delay with no value", &cx.fn_sym))?;
crate::timescale::lower_delay_ticks(cx, d).map(Some)
}
pub(crate) fn lower_nonblocking_assignment<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let drive = match e.left() {
Some(l) => match clock_var_root(l) {
Some(root) => clocking_output(cx, signals, root)?,
None => None,
},
None => None,
};
let Some(drive) = drive else {
return lower_nonblocking_assignment_inner(cx, handle, signals, nba_fn, e, None);
};
emit_reactive_drive(cx, handle, signals, &drive, true)?;
lower_nonblocking_assignment_inner(cx, handle, signals, nba_fn, e, Some(&drive))?;
emit_reactive_drive(cx, handle, signals, &drive, false)
}
fn lower_nonblocking_assignment_inner<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
e: sv_lang::Expression<'d>,
drive: Option<&ClockDrive<'ctx, 'd>>,
) -> Result<(), LoweringError> {
let lhs = e.left().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing lvalue", &cx.fn_sym)
})?;
let delay = nba_intra_delay(cx, e)?;
let (lhs, delay) = match drive {
Some(d) if sv_sim_sim::netlist::vif::is_member_access(&lhs) => {
(lhs, delay.or(Some(drive_delay(cx, d))))
}
Some(d) if !is_clock_var(lhs) => (lhs, delay.or(Some(drive_delay(cx, d)))),
Some(d) => (d.target, delay.or(Some(drive_delay(cx, d)))),
None => (lhs, delay),
};
if crate::huge::rooted_in_huge(lhs) && crate::huge::huge_local_root(cx, lhs) {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::assigned_value(cx, lhs, rhs_expr)?;
return crate::huge::lower_nba(
cx,
handle,
signals,
lhs,
rhs,
delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero()),
);
}
if crate::huge::rooted_in_huge(lhs)
&& crate::huge::class_prop_root(lhs)
&& !crate::huge::huge_local_root(cx, lhs)
{
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::assigned_value(cx, lhs, rhs_expr)?;
return crate::huge::lower_nba_class_prop(
cx,
lhs,
rhs,
delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero()),
);
}
if crate::huge::mem_elem_root(cx, lhs) {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::assigned_value(cx, lhs, rhs_expr)?;
return crate::huge::lower_nba_mem_elem(
cx,
handle,
lhs,
rhs,
delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero()),
);
}
if lhs.kind() == ExpressionKind::Streaming
&& let Some(rhs_expr) = e.right()
&& crate::stream::nonblocking_needs_runtime(lhs, rhs_expr)
{
let delay = delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero());
return crate::stream::unpack_runtime_source(
cx,
lhs,
rhs_expr,
&mut |operand, with, bits| {
if let Some(w) = with {
return crate::stream_with::unpack_with(cx, operand, w, bits, &mut |row, v| {
crate::arrays::nba_stage_ref(cx, handle, row, v, delay)
});
}
crate::select::lower_nba_to(cx, handle, signals, operand, bits, delay)
},
Some(&mut |operand, new| {
let h = crate::collections::handle_value(cx, new);
crate::select::lower_nba_to(cx, handle, signals, operand, h, delay)
}),
Some(&mut |row, v| crate::arrays::nba_stage_ref(cx, handle, row, v, delay)),
);
}
if let Some((dst, shape)) = crate::arrays::array_target(cx, lhs)? {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing rvalue", &cx.fn_sym)
})?;
return crate::arrays::nba_array(
cx,
handle,
dst,
&shape,
rhs_expr,
delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero()),
);
}
if let Some(rhs_expr) = e.right()
&& let Some(r) = crate::collections::nba_element(
cx,
handle,
signals,
lhs,
rhs_expr,
delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero()),
)
{
return r;
}
let plain_inline = sv_sim_sim::symid::is_value_name(lhs.kind())
&& lhs.referenced_symbol().is_some_and(|s| {
signals
.get(&s.uid())
.is_some_and(|info| info.class == SignalSlotClass::Inline)
&& !cx.specify_dest.contains_key(&s.uid())
});
if !plain_inline || delay.is_some() {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing rvalue", &cx.fn_sym)
})?;
let width = lhs.expr_type().map(|t| t.value_width()).unwrap_or(0);
let rhs_pv = crate::real::to_storage(
cx,
crate::collections::assigned_value(cx, lhs, rhs_expr)?,
width,
"nonblocking assignment of a >128-bit rvalue",
)?;
return crate::select::lower_nba_to(
cx,
handle,
signals,
lhs,
rhs_pv,
delay.unwrap_or_else(|| cx.ctx.i64_type().const_zero()),
);
}
let sym = lhs.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment lvalue unresolved name", &cx.fn_sym)
})?;
let info = *signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"PR-065/CG-119: a nonblocking assignment target must be an external (module-level) \
signal in the A2 slice -- there is no meaningful NBA staging target among \
process-local frame variables",
&sym,
)
})?;
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("nonblocking assignment missing rvalue", &cx.fn_sym)
})?;
let rhs_pv = crate::real::to_storage(
cx,
crate::collections::assigned_value(cx, lhs, rhs_expr)?,
info.width,
"nonblocking assignment of a >128-bit rvalue",
)?;
let fixed = reposition_width_tight_to_fixed(cx, rhs_pv.llvm, info.width);
let slot_const = cx.ctx.i32_type().const_int(u64::from(info.slot), false);
let source_const = cx.ctx.i32_type().const_int(0, false);
cx.b.build_call(
nba_fn,
&[
handle.into(),
slot_const.into(),
fixed.into(),
source_const.into(),
],
"nba_enqueue_call",
)
.unwrap();
Ok(())
}
pub(crate) fn declare_nba_enqueue<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> FunctionValue<'ctx> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let i128_ty = ctx.i128_type();
let void_ty = ctx.void_type();
let fn_ty = void_ty.fn_type(
&[ptr_ty.into(), i32_ty.into(), i128_ty.into(), i32_ty.into()],
false,
);
let f = module.add_function(
sv_sim_abi::process::NBA_ENQUEUE_SYMBOL,
fn_ty,
Some(Linkage::External),
);
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
f.add_attribute(
AttributeLoc::Function,
ctx.create_enum_attribute(kind_id, 0),
);
f
}
pub(crate) struct SysCallFns<'ctx> {
pub(crate) display: FunctionValue<'ctx>,
pub(crate) write: FunctionValue<'ctx>,
pub(crate) finish: FunctionValue<'ctx>,
dumpfile: FunctionValue<'ctx>,
dumpflush: FunctionValue<'ctx>,
fclose: FunctionValue<'ctx>,
fflush: FunctionValue<'ctx>,
fwrite: FunctionValue<'ctx>,
fdisplay: FunctionValue<'ctx>,
#[allow(dead_code)]
fmonitor: FunctionValue<'ctx>,
fstrobe: FunctionValue<'ctx>,
}
pub const SV_RT_FINISH_SYMBOL: &str = "sv_rt_finish";
pub const SV_RT_DISABLE_FORK_SYMBOL: &str = "sv_rt_disable_fork";
pub const SV_RT_DISABLE_FORK_BLOCK_SYMBOL: &str = "sv_rt_disable_fork_block";
pub const SV_RT_DISABLE_NAMED_SYMBOL: &str = "sv_rt_disable_named";
pub const SV_RT_DUMP_VARS_SYMBOL: &str = "sv_rt_dump_vars";
pub const SV_RT_DUMP_CTL_SYMBOL: &str = "sv_rt_dump_ctl";
fn lower_dumpvars_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: Expression<'d>,
) -> Result<(), LoweringError> {
let operands = call.operands();
let i32_ty = cx.ctx.i32_type();
let (levels, scopes) = match operands.split_first() {
None => (i32_ty.const_zero(), &[][..]),
Some((first, rest)) => {
let constant = first
.constant_value()
.as_ref()
.and_then(sv_lang::ConstantValue::as_integer)
.and_then(sv_lang::SVInt::as_i64);
let levels = if let Some(c) = constant {
i32_ty.const_int(c as u64, true)
} else {
let pv = lower_expr(cx, *first)?.into_packed(
"$dumpvars's levels argument must be an integral value",
&cx.fn_sym,
)?;
let op_b = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val = op_b.lane_val(pv);
cx.b.build_int_cast_sign_flag(val, i32_ty, pv.signed, "dumpvars_levels")
.unwrap()
};
(levels, rest)
}
};
let mut paths = Vec::new();
for scope in scopes {
let sym = scope
.arbitrary_symbol()
.or_else(|| scope.referenced_symbol())
.ok_or_else(|| {
LoweringError::unsupported_at(
"$dumpvars scope argument must name a module instance or a variable",
&cx.fn_sym,
)
})?;
paths.push(sym.hierarchical_path());
}
let text = paths.join("\n");
let scopes_ptr =
cx.b.build_global_string_ptr(&text, "dumpvars_scopes")
.unwrap()
.as_pointer_value();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let f = cx
.module
.get_function(SV_RT_DUMP_VARS_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DUMP_VARS_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[i32_ty.into(), ptr_ty.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(f, &[levels.into(), scopes_ptr.into()], "rt_dump_vars")
.unwrap();
Ok(())
}
pub const SV_RT_DUMP_PORTS_SYMBOL: &str = "sv_rt_dump_ports";
fn lower_dumpports_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: Expression<'d>,
op: u64,
) -> Result<(), LoweringError> {
let operands = call.operands();
let is_instance = |e: &Expression<'d>| {
e.arbitrary_symbol()
.or_else(|| e.referenced_symbol())
.is_some_and(|sym| sym.kind() == sv_lang::kinds::SymbolKind::Instance)
};
let mut scope_exprs: Vec<Expression<'d>> = Vec::new();
let (arg_expr, file_expr) = match (op, operands.as_slice()) {
(0, ops) => {
let n = ops.iter().take_while(|e| is_instance(e)).count();
scope_exprs.extend(ops[..n].iter().copied());
match &ops[n..] {
[] => (None, None),
[file] => (None, Some(*file)),
_ => {
return Err(LoweringError::unsupported_at(
"$dumpports scope argument must name a module instance (LRM 21.7.3.1)",
&cx.fn_sym,
));
}
}
}
(4, [size]) => (Some(*size), None),
(4, [size, file]) => (Some(*size), Some(*file)),
(1 | 2 | 3 | 5, []) => (None, None),
(1 | 2 | 3 | 5, [file]) => (None, Some(*file)),
_ => {
return Err(LoweringError::unsupported_at(
"$dumpports-family control call with an argument list this simulator does not \
support (LRM 21.7.3 gives one file name)",
&cx.fn_sym,
));
}
};
let i64_ty = cx.ctx.i64_type();
let arg_v = match arg_expr {
None => i64_ty.const_zero(),
Some(e) => {
let pv = lower_expr(cx, e)?.into_packed(
"$dumpportslimit's byte count must be an integral value",
&cx.fn_sym,
)?;
let op_b = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val = op_b.lane_val(pv);
cx.b.build_int_z_extend_or_bit_cast(val, i64_ty, "dumpportslimit_arg")
.unwrap()
}
};
let scope_path = if !scope_exprs.is_empty() {
scope_exprs
.iter()
.map(|s| {
s.arbitrary_symbol()
.or_else(|| s.referenced_symbol())
.map(|sym| sym.hierarchical_path())
.expect("is_instance checked the symbol")
})
.collect::<Vec<_>>()
.join("\n")
} else if op == 0 {
enclosing_instance_body(&cx.fn_sym)
.and_then(|b| b.parent())
.map(|i| i.hierarchical_path())
.ok_or_else(|| {
LoweringError::unsupported_at(
"$dumpports outside a module has no scope to dump",
&cx.fn_sym,
)
})?
} else {
String::new()
};
let scopes_ptr =
cx.b.build_global_string_ptr(&scope_path, "dumpports_scope")
.unwrap()
.as_pointer_value();
let file_ptr = match file_expr {
Some(f) => crate::systask::lower_format_expr(cx, f)?,
None => {
cx.b.build_global_string_ptr("", "dumpports_nofile")
.unwrap()
.as_pointer_value()
}
};
let i32_ty = cx.ctx.i32_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let f = cx
.module
.get_function(SV_RT_DUMP_PORTS_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DUMP_PORTS_SYMBOL,
cx.ctx.void_type().fn_type(
&[i32_ty.into(), i64_ty.into(), ptr_ty.into(), ptr_ty.into()],
false,
),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[
i32_ty.const_int(op, false).into(),
arg_v.into(),
scopes_ptr.into(),
file_ptr.into(),
],
"rt_dump_ports",
)
.unwrap();
Ok(())
}
fn enclosing_instance_body<'d>(sym: &sv_lang::Symbol<'d>) -> Option<sv_lang::Symbol<'d>> {
if sym.kind() == sv_lang::kinds::SymbolKind::Instance {
return sym.instance_body();
}
let mut cur = Some(*sym);
while let Some(s) = cur {
if s.kind() == sv_lang::kinds::SymbolKind::InstanceBody {
return Some(s);
}
cur = s.parent();
}
None
}
pub(crate) fn lower_printtimescale_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
display: FunctionValue<'ctx>,
e: Expression<'d>,
) -> Result<(), LoweringError> {
use sv_lang::kinds::SymbolKind;
let (target, start) = match e.operands().first() {
Some(a) => {
let s = a.arbitrary_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
"$printtimescale argument must name a module or instance",
&cx.fn_sym,
)
})?;
(s, s)
}
None => {
let start = e.system_call_scope().unwrap_or(cx.fn_sym);
let mut cur = start;
while !matches!(
cur.kind(),
SymbolKind::InstanceBody | SymbolKind::CompilationUnit | SymbolKind::Package
) {
match cur.parent() {
Some(p) => cur = p,
None => break,
}
}
(cur, start)
}
};
let name = if target.kind() == SymbolKind::CompilationUnit {
"$unit::".to_string()
} else if target.kind() == SymbolKind::Package {
format!("{}::", target.name())
} else {
sv_sim_sim::process::cfg::shown_path(&target)
};
let line = format!(
"Time scale of ({}) is {}",
name.replace('%', "%%"),
crate::timescale::scale_text_at(&start)
);
let fmt = build_c_string_global(cx.ctx, cx.module, &line, "printtimescale_fmt");
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let (args_ptr, n) = crate::systask::build_rtarg_array(cx, rtarg_ty, Vec::new())?;
cx.b.build_call(
display,
&[fmt.into(), args_ptr.into(), n.into()],
"rt_printtimescale",
)
.unwrap();
Ok(())
}
pub(crate) fn lower_stacktrace_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
display: FunctionValue<'ctx>,
) -> Result<(), LoweringError> {
let line = format!(
"Stack trace:\n at {}",
sv_sim_sim::process::cfg::shown_path(&cx.fn_sym).replace('%', "%%")
);
let fmt = build_c_string_global(cx.ctx, cx.module, &line, "stacktrace_fmt");
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let (args_ptr, n) = crate::systask::build_rtarg_array(cx, rtarg_ty, Vec::new())?;
cx.b.build_call(
display,
&[fmt.into(), args_ptr.into(), n.into()],
"rt_stacktrace",
)
.unwrap();
Ok(())
}
fn lower_exit_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let in_program = enclosing_instance_body(&cx.fn_sym)
.and_then(|b| b.instance_body_definition())
.is_some_and(|d| d.definition_kind() == Some(sv_lang::DefinitionKind::Program));
if !in_program {
return Ok(());
}
lower_finish_diag(cx, sys_fns, e, 1, "$finish")?;
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let f = cx
.module
.get_function(SV_RT_DISABLE_NAMED_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DISABLE_NAMED_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into()], false),
Some(Linkage::External),
)
});
let label_ptr =
cx.b.build_global_string_ptr(sv_sim_sim::sched::EXIT_PROGRAM_LABEL, "exit_label")
.unwrap()
.as_pointer_value();
cx.b.build_call(f, &[handle.into(), label_ptr.into()], "rt_exit_program")
.unwrap();
crate::stmt_shared::leave_if_aborted(cx)
}
fn lower_timeformat_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: Expression<'d>,
) -> Result<(), LoweringError> {
let i32_ty = cx.ctx.i32_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let args = e.operands();
let int_arg = |k: usize, default: i64| -> Result<IntValue<'ctx>, LoweringError> {
match args
.get(k)
.filter(|a| a.kind() != ExpressionKind::EmptyArgument)
{
Some(a) => {
let pv = lower_expr(cx, *a)?
.into_packed("a $timeformat argument must be integral", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(crate::expr::packed_to_plain_i32(&op, pv))
}
None => Ok(i32_ty.const_int(default as u64, true)),
}
};
let (units, precision, suffix, width) = if args.is_empty() {
(
i32_ty.const_int(i32::MIN as u64, true),
i32_ty.const_zero(),
ptr_ty.const_null(),
i32_ty.const_int(20, false),
)
} else {
let suffix = match args
.get(2)
.filter(|a| a.kind() != ExpressionKind::EmptyArgument)
{
Some(s) => crate::systask::lower_format_expr(cx, *s)?,
None => ptr_ty.const_null(),
};
(int_arg(0, 0)?, int_arg(1, 0)?, suffix, int_arg(3, 20)?)
};
let f = cx
.module
.get_function(sv_sim_runtime::timefmt::SV_RT_TIMEFORMAT_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
sv_sim_runtime::timefmt::SV_RT_TIMEFORMAT_SYMBOL,
cx.ctx.void_type().fn_type(
&[i32_ty.into(), i32_ty.into(), ptr_ty.into(), i32_ty.into()],
false,
),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[units.into(), precision.into(), suffix.into(), width.into()],
"rt_timeformat",
)
.unwrap();
Ok(())
}
fn lower_dump_ctl_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
op: u64,
arg: Option<Expression<'d>>,
) -> Result<(), LoweringError> {
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let arg_v = match arg {
None => i64_ty.const_zero(),
Some(e) => {
let pv = lower_expr(cx, e)?.into_packed(
"$dumplimit's byte count must be an integral value",
&cx.fn_sym,
)?;
let op_b = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val = op_b.lane_val(pv);
cx.b.build_int_z_extend_or_bit_cast(val, i64_ty, "dumplimit_arg")
.unwrap()
}
};
let f = cx
.module
.get_function(SV_RT_DUMP_CTL_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DUMP_CTL_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[i32_ty.into(), i64_ty.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[i32_ty.const_int(op, false).into(), arg_v.into()],
"rt_dump_ctl",
)
.unwrap();
Ok(())
}
pub const SV_RT_NBA_MASKED_SYMBOL: &str = "sv_rt_nba_masked";
pub const SV_RT_NBA_PULSE_SYMBOL: &str = "sv_rt_nba_pulse";
pub const SV_RT_REACTIVE_DRIVE_SYMBOL: &str = "sv_rt_reactive_drive";
pub const SV_RT_NBA_COLL_SYMBOL: &str = "sv_rt_nba_coll";
pub const SV_RT_SET_DELAY_SYMBOL: &str = "sv_rt_set_delay";
pub const SV_RT_FIRED_TERM_SYMBOL: &str = "sv_rt_fired_term";
pub const SV_RT_FIRED_MASK_SYMBOL: &str = "sv_rt_fired_mask";
pub const SV_RT_PROC_ASSERT_SYMBOL: &str = "sv_rt_proc_assert";
pub const SV_RT_PROC_ASSERT_DISABLE_SYMBOL: &str = "sv_rt_proc_assert_disable";
pub const SV_RT_SLOT_OWNER_SYMBOL: &str = "sv_rt_slot_owner";
pub const SV_RT_ASSERT_INPUT_SYMBOL: &str = "sv_rt_assert_input";
pub const SV_RT_CELL_ALLOC_SYMBOL: &str = "sv_rt_cell_alloc";
pub type BranchFrame = (u32, u32, Vec<(u32, u32)>, Vec<(u32, String, u32)>);
pub const SV_RT_CELL_STORE_SYMBOL: &str = "sv_rt_cell_store";
pub const SV_RT_EDGE_SNAPSHOT_SYMBOL: &str = "sv_rt_edge_snapshot";
pub const SV_RT_EDGE_CHECK_SYMBOL: &str = "sv_rt_edge_check";
fn fired_now_value<'ctx>(
b: &inkwell::builder::Builder<'ctx>,
fired_fn: FunctionValue<'ctx>,
) -> inkwell::values::IntValue<'ctx> {
b.build_call(fired_fn, &[], "fired_now")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
pub const SV_RT_EDGE_LOAD_SYMBOL: &str = "sv_rt_edge_load";
pub const SV_RT_SET_FIRED_TERM_SYMBOL: &str = "sv_rt_set_fired_term";
pub const SV_RT_ABORTED_SYMBOL: &str = "sv_rt_aborted";
pub const SV_RT_DEFERRED_BEGIN_SYMBOL: &str = "sv_rt_deferred_begin";
pub const SV_RT_DEFERRED_END_SYMBOL: &str = "sv_rt_deferred_end";
pub(crate) fn fetch_or_declare_sys_call_fns<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> SysCallFns<'ctx> {
let display = module
.get_function(sv_sim_runtime::rtarg::SV_RT_DISPLAY_SYMBOL)
.unwrap_or_else(|| {
declare_rt_display_like(ctx, module, sv_sim_runtime::rtarg::SV_RT_DISPLAY_SYMBOL)
});
let write = module
.get_function(sv_sim_runtime::rtarg::SV_RT_WRITE_SYMBOL)
.unwrap_or_else(|| {
declare_rt_display_like(ctx, module, sv_sim_runtime::rtarg::SV_RT_WRITE_SYMBOL)
});
let finish = module
.get_function(SV_RT_FINISH_SYMBOL)
.unwrap_or_else(|| declare_rt_finish(ctx, module));
let dumpfile = module
.get_function(sv_sim_runtime::fst::SV_RT_DUMPFILE_SYMBOL)
.unwrap_or_else(|| {
declare_dump_control_fn(
ctx,
module,
sv_sim_runtime::fst::SV_RT_DUMPFILE_SYMBOL,
true,
)
});
let dumpflush = module
.get_function(sv_sim_runtime::fst::SV_RT_DUMPFLUSH_SYMBOL)
.unwrap_or_else(|| {
declare_dump_control_fn(
ctx,
module,
sv_sim_runtime::fst::SV_RT_DUMPFLUSH_SYMBOL,
false,
)
});
let fclose = module
.get_function(sv_sim_runtime::file_io::SV_RT_FCLOSE_SYMBOL)
.unwrap_or_else(|| {
declare_file_io_void_fn(ctx, module, sv_sim_runtime::file_io::SV_RT_FCLOSE_SYMBOL)
});
let fflush = module
.get_function(sv_sim_runtime::file_io::SV_RT_FFLUSH_SYMBOL)
.unwrap_or_else(|| {
declare_file_io_void_fn(ctx, module, sv_sim_runtime::file_io::SV_RT_FFLUSH_SYMBOL)
});
let fwrite = module
.get_function(sv_sim_runtime::file_io::SV_RT_FWRITE_SYMBOL)
.unwrap_or_else(|| {
declare_rt_fdisplay_like(ctx, module, sv_sim_runtime::file_io::SV_RT_FWRITE_SYMBOL)
});
let fdisplay = module
.get_function(sv_sim_runtime::file_io::SV_RT_FDISPLAY_SYMBOL)
.unwrap_or_else(|| {
declare_rt_fdisplay_like(ctx, module, sv_sim_runtime::file_io::SV_RT_FDISPLAY_SYMBOL)
});
let fmonitor = module
.get_function(sv_sim_runtime::file_io::SV_RT_FMONITOR_SYMBOL)
.unwrap_or_else(|| {
declare_rt_fdisplay_like(ctx, module, sv_sim_runtime::file_io::SV_RT_FMONITOR_SYMBOL)
});
let fstrobe = module
.get_function(sv_sim_runtime::file_io::SV_RT_FSTROBE_SYMBOL)
.unwrap_or_else(|| {
declare_rt_fdisplay_like(ctx, module, sv_sim_runtime::file_io::SV_RT_FSTROBE_SYMBOL)
});
SysCallFns {
display,
write,
finish,
dumpfile,
dumpflush,
fclose,
fflush,
fwrite,
fdisplay,
fmonitor,
fstrobe,
}
}
fn declare_rt_fdisplay_like<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
symbol: &str,
) -> FunctionValue<'ctx> {
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let void_ty = ctx.void_type();
let fn_ty = void_ty.fn_type(
&[i32_ty.into(), ptr_ty.into(), ptr_ty.into(), i32_ty.into()],
false,
);
module.add_function(symbol, fn_ty, Some(Linkage::External))
}
fn declare_file_io_void_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
symbol: &str,
) -> FunctionValue<'ctx> {
let void_ty = ctx.void_type();
let i32_ty = ctx.i32_type();
let fn_ty = void_ty.fn_type(&[i32_ty.into()], false);
module.add_function(symbol, fn_ty, Some(Linkage::External))
}
fn declare_dump_control_fn<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
symbol: &str,
has_path_arg: bool,
) -> FunctionValue<'ctx> {
let void_ty = ctx.void_type();
let fn_ty = if has_path_arg {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
void_ty.fn_type(&[ptr_ty.into(), ptr_ty.into()], false)
} else {
void_ty.fn_type(&[], false)
};
module.add_function(symbol, fn_ty, Some(Linkage::External))
}
fn declare_rt_display_like<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
symbol: &str,
) -> FunctionValue<'ctx> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let void_ty = ctx.void_type();
let fn_ty = void_ty.fn_type(&[ptr_ty.into(), ptr_ty.into(), i32_ty.into()], false);
module.add_function(symbol, fn_ty, Some(Linkage::External))
}
fn declare_rt_finish<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) -> FunctionValue<'ctx> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let void_ty = ctx.void_type();
let fn_ty = void_ty.fn_type(&[ptr_ty.into(), i32_ty.into()], false);
module.add_function(SV_RT_FINISH_SYMBOL, fn_ty, Some(Linkage::External))
}
pub(crate) fn build_c_string_global<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
s: &str,
name: &str,
) -> PointerValue<'ctx> {
let mut bytes = s.as_bytes().to_vec();
bytes.push(0);
let i8_ty = ctx.i8_type();
let const_bytes: Vec<IntValue<'ctx>> = bytes
.iter()
.map(|byte| i8_ty.const_int(u64::from(*byte), false))
.collect();
let arr = i8_ty.const_array(&const_bytes);
let global = module.add_global(arr.get_type(), None, name);
global.set_initializer(&arr);
global.set_constant(true);
global.set_linkage(Linkage::Private);
global.as_pointer_value()
}
fn lower_system_call_statement<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
sys_fns: &SysCallFns<'ctx>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let sub = e.system_subroutine().ok_or_else(|| {
LoweringError::unsupported_at("system call missing its SystemMethod handle", &cx.fn_sym)
})?;
if crate::vpi_systf::is_registered(&sub.name()) {
crate::huge::discard(lower_expr(cx, e)?);
return Ok(());
}
match sub.known_name_id() {
KnownSystemName::Display
| KnownSystemName::DisplayB
| KnownSystemName::DisplayH
| KnownSystemName::DisplayO => lower_display_write_call(cx, sys_fns.display, e),
KnownSystemName::Write
| KnownSystemName::WriteB
| KnownSystemName::WriteH
| KnownSystemName::WriteO => lower_display_write_call(cx, sys_fns.write, e),
KnownSystemName::ReadMemH => lower_readmem_call(cx, handle, e, false, false),
KnownSystemName::ReadMemB => lower_readmem_call(cx, handle, e, true, false),
KnownSystemName::WriteMemH => lower_readmem_call(cx, handle, e, false, true),
KnownSystemName::WriteMemB => lower_readmem_call(cx, handle, e, true, true),
KnownSystemName::FScanf
| KnownSystemName::SScanf
| KnownSystemName::FGets
| KnownSystemName::FGetC
| KnownSystemName::FRead
| KnownSystemName::UngetC
| KnownSystemName::FTell
| KnownSystemName::FError
| KnownSystemName::Rewind
| KnownSystemName::FSeek
| KnownSystemName::FEof
| KnownSystemName::System
| KnownSystemName::CoverageControl
| KnownSystemName::CoverageGetMax
| KnownSystemName::CoverageGet
| KnownSystemName::CoverageMerge
| KnownSystemName::CoverageSave
| KnownSystemName::GetCoverage
| KnownSystemName::SetCoverageDbName
| KnownSystemName::LoadCoverageDb => {
lower_expr(cx, e)?;
Ok(())
}
KnownSystemName::Strobe
| KnownSystemName::StrobeB
| KnownSystemName::StrobeH
| KnownSystemName::StrobeO => {
lower_postponed_display_call(cx, handle, signals, e, PostponedDisplay::Strobe)
}
KnownSystemName::Monitor
| KnownSystemName::MonitorB
| KnownSystemName::MonitorH
| KnownSystemName::MonitorO => {
lower_postponed_display_call(cx, handle, signals, e, PostponedDisplay::Monitor)
}
KnownSystemName::MonitorOn | KnownSystemName::MonitorOff => {
let on = u64::from(sub.known_name_id() == KnownSystemName::MonitorOn);
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let i32_ty = cx.ctx.i32_type();
let f = cx
.module
.get_function(SV_RT_MONITOR_ENABLE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_MONITOR_ENABLE_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[ptr_ty.into(), i32_ty.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[handle.into(), i32_ty.const_int(on, false).into()],
"rt_monitor_enable",
)
.unwrap();
Ok(())
}
KnownSystemName::Finish | KnownSystemName::Stop => {
let which = if matches!(e.system_subroutine().map(|s| s.known_name_id()), Some(KnownSystemName::Stop)) {
KnownSystemName::Stop
} else {
KnownSystemName::Finish
};
lower_finish_call(cx, handle, sys_fns, e, which)?;
end_process_here(cx)
}
KnownSystemName::Info
| KnownSystemName::Warning
| KnownSystemName::Error
| KnownSystemName::Fatal => {
lower_severity_call(cx, handle, sys_fns, e, sub.known_name_id())?;
if sub.known_name_id() == KnownSystemName::Fatal {
end_process_here(cx)?;
}
Ok(())
}
KnownSystemName::DumpFile => lower_dumpfile_call(cx, sys_fns.dumpfile, e),
KnownSystemName::DumpFlush => lower_dumpflush_call(cx, sys_fns.dumpflush, e),
KnownSystemName::DumpVars => lower_dumpvars_call(cx, e),
KnownSystemName::TimeFormat => lower_timeformat_call(cx, e),
KnownSystemName::PrintTimeScale => lower_printtimescale_call(cx, sys_fns.display, e),
KnownSystemName::Exit => lower_exit_call(cx, handle, sys_fns, e),
KnownSystemName::Stacktrace => lower_stacktrace_call(cx, sys_fns.display),
KnownSystemName::DumpOn => lower_dump_ctl_call(cx, 0, None),
KnownSystemName::DumpOff => lower_dump_ctl_call(cx, 1, None),
KnownSystemName::DumpAll => lower_dump_ctl_call(cx, 2, None),
KnownSystemName::DumpLimit => lower_dump_ctl_call(cx, 4, e.operands().first().copied()),
KnownSystemName::DumpPorts => lower_dumpports_call(cx, e, 0),
KnownSystemName::DumpPortsOff => lower_dumpports_call(cx, e, 1),
KnownSystemName::DumpPortsOn => lower_dumpports_call(cx, e, 2),
KnownSystemName::DumpPortsAll => lower_dumpports_call(cx, e, 3),
KnownSystemName::DumpPortsLimit => lower_dumpports_call(cx, e, 4),
KnownSystemName::DumpPortsFlush => lower_dumpports_call(cx, e, 5),
KnownSystemName::AssertControl
| KnownSystemName::AssertOn
| KnownSystemName::AssertOff
| KnownSystemName::AssertKill
| KnownSystemName::AssertPassOn
| KnownSystemName::AssertPassOff
| KnownSystemName::AssertFailOn
| KnownSystemName::AssertFailOff
| KnownSystemName::AssertNonvacuousOn
| KnownSystemName::AssertVacuousOff => {
lower_assert_control_call(cx, sub.known_name_id(), e)
}
KnownSystemName::FClose => lower_file_io_void_call(cx, sys_fns.fclose, false, e),
KnownSystemName::FFlush => lower_file_io_void_call(cx, sys_fns.fflush, true, e),
KnownSystemName::FOpen => {
lower_expr(cx, e)?;
Ok(())
}
KnownSystemName::TestPlusArgs | KnownSystemName::ValuePlusArgs => {
lower_expr(cx, e)?;
Ok(())
}
KnownSystemName::FWrite
| KnownSystemName::FWriteB
| KnownSystemName::FWriteH
| KnownSystemName::FWriteO => lower_fdisplay_write_call(cx, sys_fns.fwrite, e),
KnownSystemName::FDisplay
| KnownSystemName::FDisplayB
| KnownSystemName::FDisplayH
| KnownSystemName::FDisplayO => lower_fdisplay_write_call(cx, sys_fns.fdisplay, e),
KnownSystemName::FMonitor
| KnownSystemName::FMonitorB
| KnownSystemName::FMonitorH
| KnownSystemName::FMonitorO => lower_postponed_display_call(
cx,
handle,
signals,
e,
PostponedDisplay::FMonitor(sys_fns.fstrobe),
),
KnownSystemName::FStrobe
| KnownSystemName::FStrobeB
| KnownSystemName::FStrobeH
| KnownSystemName::FStrobeO => {
lower_file_io_void_call(cx, sys_fns.fflush, false, e)?;
lower_postponed_display_call(
cx,
handle,
signals,
e,
PostponedDisplay::FStrobe(sys_fns.fstrobe),
)
}
KnownSystemName::SdfAnnotate => crate::systask::lower_sdf_annotate_call(cx, e),
KnownSystemName::Cast => crate::sysfunc::lower_cast_stmt(cx, e),
_ => {
if let Some(sub) = e.system_subroutine() {
let name = sub.name();
if crate::pla::is_pla_task(&name) {
return crate::pla::lower_pla_call(cx, &name, e);
}
}
crate::huge::discard(lower_expr(cx, e)?);
Ok(())
}
}
}
fn lower_file_io_void_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
rt_fn: FunctionValue<'ctx>,
default_zero: bool,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let children = e.children();
let fd_i32 = match children.first() {
Some(node) => {
let arg_expr = node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
"$fclose/$fflush's argument is not an expression",
&cx.fn_sym,
)
})?;
let pv = lower_expr(cx, arg_expr)?.into_packed(
"$fclose/$fflush's fd argument must be an integral value",
&cx.fn_sym,
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
crate::expr::packed_to_plain_i32(&op, pv)
}
None if default_zero => cx.ctx.i32_type().const_int(
u64::from(sv_sim_runtime::file_io_sink::FLUSH_ALL_FD as u32),
false,
),
None => {
return Err(LoweringError::unsupported_at(
"$fclose requires an fd argument",
&cx.fn_sym,
));
}
};
cx.b.build_call(rt_fn, &[fd_i32.into()], "rt_file_io_void_call")
.unwrap();
Ok(())
}
fn lower_display_write_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
rt_fn: FunctionValue<'ctx>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let (fmt_ptr, args_ptr, nargs_const) = lower_display_args(cx, e)?;
mark_deferred_args_done(cx);
cx.b.build_call(
rt_fn,
&[fmt_ptr.into(), args_ptr.into(), nargs_const.into()],
"rt_display_call",
)
.unwrap();
Ok(())
}
fn lower_display_args<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<(PointerValue<'ctx>, PointerValue<'ctx>, IntValue<'ctx>), LoweringError> {
let (fmt_ptr, packed_args) = crate::systask::display_format_and_values(cx, &e.operands(), e)?;
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let (args_ptr, nargs_const) = crate::systask::build_rtarg_array(cx, rtarg_ty, packed_args)?;
Ok((fmt_ptr, args_ptr, nargs_const))
}
#[derive(Clone, Copy)]
pub(crate) enum PostponedDisplay<'ctx> {
Strobe,
FStrobe(FunctionValue<'ctx>),
Monitor,
FMonitor(FunctionValue<'ctx>),
}
pub(crate) fn lower_postponed_display_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
e: sv_lang::Expression<'d>,
which: PostponedDisplay<'ctx>,
) -> Result<(), LoweringError> {
let fns = crate::accessor::accessor_fns_of(cx.module).ok_or_else(|| {
LoweringError::unsupported_at(
"internal error: $strobe/$monitor thunk emitted into a module with no accessor \
declarations",
&cx.fn_sym,
)
})?;
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let void_ty = cx.ctx.void_type();
let mut n = 0u32;
let name = loop {
let candidate = format!("sv_sim_postponed_thunk_{n}");
if cx.module.get_function(&candidate).is_none() {
break candidate;
}
n += 1;
};
let thunk = cx
.module
.add_function(&name, void_ty.fn_type(&[ptr_ty.into()], false), None);
let thunk_handle = thunk.get_nth_param(0).unwrap().into_pointer_value();
let b = cx.ctx.create_builder();
b.position_at_end(cx.ctx.append_basic_block(thunk, "entry"));
let mut tcx = LowerCtx {
ctx: cx.ctx,
b,
repr: cx.repr,
locals: HashMap::new(),
fn_sym: cx.fn_sym,
module: cx.module,
mem_signals: mem_signals_of(signals),
mem_accessor: Some(MemAccessorCtx {
handle: thunk_handle,
fns: &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 (refs, written) = external_refs_in_expr(e, signals);
for sym in refs {
let info = signals[&sym.uid()];
if crate::huge::is_huge_width(info.width) {
let ptr = if written.contains(&sym.uid()) {
let ptr = crate::huge::shadow_alloca(tcx.ctx, &tcx.b, info.width, "ext_shadow");
crate::accessor::build_get_huge(
&tcx,
&fns,
thunk_handle,
info.slot,
info.width,
ptr,
);
ptr
} else {
crate::huge::rt_prime(&tcx, thunk_handle, info.slot, info.width, false)
};
tcx.locals.insert(sym.uid(), (ptr, info.width, info.signed));
continue;
}
let raw = match info.class {
SignalSlotClass::Inline => {
build_get_scalar(&tcx, &fns, thunk_handle, info.slot, info.width)
}
SignalSlotClass::Wide => {
build_get_wide(&tcx, &fns, thunk_handle, info.slot, info.width)
}
SignalSlotClass::Memory { .. } => {
unreachable!("external_refs_in_expr excludes SignalSlotClass::Memory symbols")
}
};
let ptr =
crate::types::entry_alloca_in(&tcx.b, int_type(tcx.ctx, info.width * 2), "ext_shadow");
tcx.b.build_store(ptr, raw).unwrap();
tcx.locals.insert(sym.uid(), (ptr, info.width, info.signed));
}
{
let owner = cx.fn_sym.uid();
let mut stack = vec![e.as_sem_node()];
let mut done: HashSet<SymbolId> = HashSet::new();
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& let Ok(Some(call)) = sv_sim_sim::sva::sampled::decode(x)
{
let args =
sv_sim_sim::sva::sampled::strict_site_signals_in(call.arg).unwrap_or_default();
let views = sv_sim_sim::sva::sampled::use_views(&owner, &call, &args);
for (_, cell) in views.cur.into_iter().chain(views.prev) {
let Some(info) = signals.get(&cell).copied() else {
continue;
};
if crate::huge::is_huge_width(info.width)
|| matches!(info.class, SignalSlotClass::Memory { .. })
|| !done.insert(cell.clone())
{
continue;
}
let raw = match info.class {
SignalSlotClass::Inline => {
build_get_scalar(&tcx, &fns, thunk_handle, info.slot, info.width)
}
_ => build_get_wide(&tcx, &fns, thunk_handle, info.slot, info.width),
};
let ptr = crate::types::entry_alloca_in(
&tcx.b,
int_type(tcx.ctx, info.width * 2),
"ext_shadow",
);
tcx.b.build_store(ptr, raw).unwrap();
tcx.locals.insert(cell, (ptr, info.width, info.signed));
}
}
stack.extend(n.children());
}
}
let mut time_flags: Option<Vec<bool>> = None;
if let PostponedDisplay::FStrobe(rt_fn) | PostponedDisplay::FMonitor(rt_fn) = which {
let ops = e.operands();
let (fd_expr, rest) = ops.split_first().ok_or_else(|| {
LoweringError::unsupported_at("$fstrobe call has no fd argument", &cx.fn_sym)
})?;
let fd_pv = lower_expr(&tcx, *fd_expr)?.into_packed(
"$fstrobe's fd argument must be an integral value",
&cx.fn_sym,
)?;
let op = OpBuilder {
ctx: tcx.ctx,
b: &tcx.b,
};
let fd_i32 = crate::expr::packed_to_plain_i32(&op, fd_pv);
let (fmt_ptr, packed_args) = crate::systask::display_format_and_values(&tcx, rest, e)?;
let rtarg_ty = crate::systask::rtarg_llvm_type(tcx.ctx);
let (args_ptr, nargs) = crate::systask::build_rtarg_array(&tcx, rtarg_ty, packed_args)?;
tcx.b
.build_call(
rt_fn,
&[fd_i32.into(), fmt_ptr.into(), args_ptr.into(), nargs.into()],
"postponed_fdisplay",
)
.unwrap();
} else {
let (fmt_ptr, packed_args, flags) =
crate::systask::display_format_and_values_flagged(&tcx, &e.operands(), e)?;
let rtarg_ty = crate::systask::rtarg_llvm_type(tcx.ctx);
let (args_ptr, nargs) = crate::systask::build_rtarg_array(&tcx, rtarg_ty, packed_args)?;
time_flags = Some(flags);
let display = cx
.module
.get_function(sv_sim_runtime::rtarg::SV_RT_DISPLAY_SYMBOL)
.unwrap_or_else(|| {
declare_rt_display_like(
cx.ctx,
cx.module,
sv_sim_runtime::rtarg::SV_RT_DISPLAY_SYMBOL,
)
});
tcx.b
.build_call(
display,
&[fmt_ptr.into(), args_ptr.into(), nargs.into()],
"postponed_display",
)
.unwrap();
}
tcx.b.build_return(None).unwrap();
let thunk_ptr = thunk.as_global_value().as_pointer_value();
match which {
PostponedDisplay::Strobe | PostponedDisplay::FStrobe(_) => {
let f = cx
.module
.get_function(SV_RT_STROBE_POSTPONED_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_STROBE_POSTPONED_SYMBOL,
void_ty.fn_type(&[ptr_ty.into(), ptr_ty.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(f, &[handle.into(), thunk_ptr.into()], "strobe_schedule")
.unwrap();
}
PostponedDisplay::Monitor | PostponedDisplay::FMonitor(_) => {
let mut slots: Vec<u32> = Vec::new();
let mut stack = vec![e.as_sem_node()];
while let Some(node) = stack.pop() {
if let Some(x) = node.as_expression()
&& sv_sim_sim::symid::is_value_name(x.kind())
&& let Some(sym) = x.referenced_symbol()
&& let Some(info) = signals.get(&sym.uid())
&& !slots.contains(&info.slot)
{
slots.push(info.slot);
}
stack.extend(node.children());
}
let i32_ty = cx.ctx.i32_type();
let mut entries: Vec<u32> = slots.clone();
let mut n_field = slots.len() as u64;
let needs_expr_test = e.operands().iter().any(|&op| {
let mut p = op;
while p.kind() == ExpressionKind::Conversion {
match p.conversion_operand() {
Some(inner) => p = inner,
None => break,
}
}
if sv_sim_sim::symid::is_value_name(p.kind()) {
return false;
}
let mut stack = vec![p.as_sem_node()];
while let Some(node) = stack.pop() {
if node
.as_expression()
.is_some_and(|x| sv_sim_sim::symid::is_value_name(x.kind()))
{
return true;
}
stack.extend(node.children());
}
false
});
if let Some(flags) = time_flags
.as_ref()
.filter(|f| f.len() <= 32 && needs_expr_test)
{
let mask = flags
.iter()
.enumerate()
.fold(0u32, |m, (i, &t)| if t { m | (1 << i) } else { m });
entries.push(mask);
n_field |= u64::from(sv_sim_sim::sched::MONITOR_DEDUPE_N);
}
let arr_ty = i32_ty.array_type(entries.len() as u32);
let global = cx.module.add_global(arr_ty, None, "monitor_watch_slots");
global.set_constant(true);
global.set_linkage(Linkage::Private);
global.set_initializer(
&i32_ty.const_array(
&entries
.iter()
.map(|&s| i32_ty.const_int(u64::from(s), false))
.collect::<Vec<_>>(),
),
);
let f = cx
.module
.get_function(SV_RT_MONITOR_START_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_MONITOR_START_SYMBOL,
void_ty.fn_type(
&[ptr_ty.into(), ptr_ty.into(), ptr_ty.into(), i32_ty.into()],
false,
),
Some(Linkage::External),
)
});
cx.b.build_call(
f,
&[
handle.into(),
thunk_ptr.into(),
global.as_pointer_value().into(),
i32_ty.const_int(n_field, false).into(),
],
"monitor_start",
)
.unwrap();
}
}
Ok(())
}
pub const SV_RT_STROBE_POSTPONED_SYMBOL: &str = "sv_rt_strobe_postponed";
pub const SV_RT_MONITOR_START_SYMBOL: &str = "sv_rt_monitor_start";
pub const SV_RT_MONITOR_ENABLE_SYMBOL: &str = "sv_rt_monitor_enable";
fn lower_fdisplay_write_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
rt_fn: FunctionValue<'ctx>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let children = e.children();
let (fd_node, rest) = children.split_first().ok_or_else(|| {
LoweringError::unsupported_at("$fwrite-family call has no fd argument", &cx.fn_sym)
})?;
let fd_expr = fd_node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
"$fwrite-family's fd argument is not an expression",
&cx.fn_sym,
)
})?;
let fd_pv = lower_expr(cx, fd_expr)?.into_packed(
"$fwrite-family's fd argument must be an integral value",
&cx.fn_sym,
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let fd_i32 = crate::expr::packed_to_plain_i32(&op, fd_pv);
let rest: Vec<sv_lang::Expression<'d>> =
rest.iter().filter_map(|n| n.as_expression()).collect();
let (fmt_ptr, packed_args) = crate::systask::display_format_and_values(cx, &rest, e)?;
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let (args_ptr, nargs_const) = crate::systask::build_rtarg_array(cx, rtarg_ty, packed_args)?;
cx.b.build_call(
rt_fn,
&[
fd_i32.into(),
fmt_ptr.into(),
args_ptr.into(),
nargs_const.into(),
],
"rt_fdisplay_call",
)
.unwrap();
Ok(())
}
fn lower_dumpfile_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
rt_fn: FunctionValue<'ctx>,
e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let children = e.children();
let Some((path_node, _rest)) = children.split_first() else {
return Ok(());
};
let path_expr = path_node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at("$dumpfile's argument is not an expression", &cx.fn_sym)
})?;
let path_ptr = crate::systask::lower_format_expr(cx, path_expr)?;
let src_text =
sv_sim_sim::process::cfg::source_text_range(path_expr.source_range()).unwrap_or_default();
let src_ptr =
cx.b.build_global_string_ptr(&src_text, "dumpfile_src")
.unwrap()
.as_pointer_value();
cx.b.build_call(
rt_fn,
&[path_ptr.into(), src_ptr.into()],
"rt_dumpfile_call",
)
.unwrap();
Ok(())
}
fn lower_dumpflush_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
rt_fn: FunctionValue<'ctx>,
_e: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
cx.b.build_call(rt_fn, &[], "rt_dumpflush_call").unwrap();
Ok(())
}
fn lower_readmem_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
e: sv_lang::Expression<'d>,
binary: bool,
write: bool,
) -> Result<(), LoweringError> {
let task = match (write, binary) {
(false, true) => "$readmemb",
(false, false) => "$readmemh",
(true, true) => "$writememb",
(true, false) => "$writememh",
};
let operands = e.operands();
if !(2..=4).contains(&operands.len()) {
return Err(LoweringError::unsupported_at(
&format!("{task} takes 2 to 4 arguments (file, memory [, start [, end]])"),
&cx.fn_sym,
));
}
let mem_expr = if operands[1].kind() == ExpressionKind::Assignment {
operands[1].left()
} else {
Some(operands[1])
};
let refuse = |what: &str| {
LoweringError::unsupported_at(
&format!(
"{task}'s second argument must name a fixed-size unpacked array, queue, \
dynamic or associative array, or a part of one, of this module ({what})"
),
&cx.fn_sym,
)
};
let mem_expr = mem_expr.ok_or_else(|| refuse("not an array reference"))?;
let i64_ty = cx.ctx.i64_type();
let i32_ty = cx.ctx.i32_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let mut flags = 0u32;
let (slot, lower, rows, sub, base, width, elem, coll, key_width) =
if let Some(c) = crate::collections::coll_of_expr(&mem_expr) {
if c.obj || c.leaky || (c.assoc && c.key_kind >= 2) {
return Err(refuse(
"a collection of class handles or with a string / class-handle index",
));
}
flags |= sv_sim_runtime::readmem::READMEM_COLL;
if c.assoc {
flags |= sv_sim_runtime::readmem::READMEM_ASSOC;
if c.key_kind == 1 {
flags |= sv_sim_runtime::readmem::READMEM_KEY_SIGNED;
}
}
let h = crate::collections::handle_of(cx, mem_expr)?;
(
i32_ty.const_zero(),
i64_ty.const_zero(),
i32_ty.const_zero(),
i32_ty.const_int(1, false),
i64_ty.const_zero(),
c.width,
c.elem,
h,
c.key_width,
)
} else {
if mem_expr.kind() == ExpressionKind::RangeSelect {
return Err(refuse(
"a range select of a fixed-size array is not a memory name",
));
}
let (aref, shape) = crate::arrays::resolve_array_ref(cx, mem_expr)?
.ok_or_else(|| refuse(&format!("got a {:?} expression", operands[1].kind())))?;
let crate::arrays::Backing::Memory(info) = aref.backing else {
return Err(refuse("a local array is not a module memory"));
};
if shape.dims.is_empty() {
return Err(refuse("a single element, not an array"));
}
let top = shape.size(0);
let rows =
cx.b.build_select(
aref.valid,
i32_ty.const_int(top, false),
i32_ty.const_zero(),
"rm_rows",
)
.unwrap()
.into_int_value();
(
i32_ty.const_int(u64::from(info.slot), false),
i64_ty.const_int(shape.dims[0].0.min(shape.dims[0].1) as u64, true),
rows,
i32_ty.const_int(shape.stride(0), false),
aref.base,
info.elem_width,
shape.elem,
i64_ty.const_zero(),
0,
)
};
if !elem.is_integral() {
return Err(refuse("its elements are not integral"));
}
if !elem.is_four_state() {
flags |= sv_sim_runtime::readmem::READMEM_TWO_STATE;
}
let (enum_ptr, enum_count) = if elem.is_enum() && elem.value_width() <= 64 {
flags |= sv_sim_runtime::readmem::READMEM_ENUM;
let mask = if elem.value_width() >= 64 {
u64::MAX
} else {
(1u64 << elem.value_width()) - 1
};
let vals: Vec<u64> = elem
.enum_members()
.filter_map(|m| {
m.constant_value()
.as_ref()
.and_then(|c| c.as_integer())
.and_then(|i| i.as_i64())
.map(|v| (v as u64) & mask)
})
.collect();
let arr =
crate::expr::entry_alloca(cx, i64_ty.array_type(vals.len().max(1) as u32), "rm_enum");
for (k, v) in vals.iter().enumerate() {
let p = unsafe {
cx.b.build_in_bounds_gep(
i64_ty,
arr,
&[i64_ty.const_int(k as u64, false)],
"rm_enum_p",
)
.unwrap()
};
cx.b.build_store(p, i64_ty.const_int(*v, false)).unwrap();
}
(arr, vals.len() as u64)
} else {
(ptr_ty.const_null(), 0)
};
let path_ptr = crate::systask::lower_format_expr(cx, operands[0])?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let bound = |i: usize| -> Result<IntValue<'ctx>, LoweringError> {
match operands.get(i) {
None => Ok(i64_ty.const_zero()),
Some(expr) => {
let pv = match lower_expr(cx, *expr)? {
crate::expr::LoweredValue::Real(r) => crate::real::real_to_count(cx, r),
other => other
.into_packed(&format!("{task} address must be integral"), &cx.fn_sym)?,
};
Ok(crate::expr::mem_row_index_i64(&op, cx, pv))
}
}
};
let start = bound(2)?;
let end = bound(3)?;
if binary {
flags |= sv_sim_runtime::readmem::READMEM_BINARY;
}
if write {
flags |= sv_sim_runtime::readmem::READMEM_WRITE;
}
if operands.len() >= 3 {
flags |= sv_sim_runtime::readmem::READMEM_HAS_START;
}
if operands.len() == 4 {
flags |= sv_sim_runtime::readmem::READMEM_HAS_END;
}
let rt_fn = cx
.module
.get_function(sv_sim_runtime::readmem::SV_RT_READMEM_ROWS_SYMBOL)
.unwrap_or_else(|| {
let fn_ty = i64_ty.fn_type(
&[
ptr_ty.into(),
i32_ty.into(),
i64_ty.into(),
i32_ty.into(),
i32_ty.into(),
ptr_ty.into(),
i64_ty.into(),
i64_ty.into(),
i32_ty.into(),
i64_ty.into(),
i32_ty.into(),
ptr_ty.into(),
i32_ty.into(),
i64_ty.into(),
i32_ty.into(),
],
false,
);
cx.module.add_function(
sv_sim_runtime::readmem::SV_RT_READMEM_ROWS_SYMBOL,
fn_ty,
Some(Linkage::External),
)
});
let ret =
cx.b.build_call(
rt_fn,
&[
handle.into(),
slot.into(),
lower.into(),
rows.into(),
i32_ty.const_int(u64::from(width), false).into(),
path_ptr.into(),
start.into(),
end.into(),
i32_ty.const_int(u64::from(flags), false).into(),
base.into(),
sub.into(),
enum_ptr.into(),
i32_ty.const_int(enum_count, false).into(),
coll.into(),
i32_ty.const_int(u64::from(key_width), false).into(),
],
"rt_readmem_rows_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
if !write && flags & sv_sim_runtime::readmem::READMEM_COLL != 0 {
crate::collections::set_handle(cx, mem_expr, ret)?;
}
Ok(())
}
fn end_process_here(cx: &LowerCtx<'_, '_, '_>) -> Result<(), LoweringError> {
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
crate::stmt_shared::leave_now(cx, f);
let rest = cx.ctx.append_basic_block(f, "after_finish");
cx.b.position_at_end(rest);
Ok(())
}
pub(crate) fn lower_severity_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
e: sv_lang::Expression<'d>,
which: KnownSystemName,
) -> Result<(), LoweringError> {
let tag = match which {
KnownSystemName::Info => "INFO: ",
KnownSystemName::Warning => "WARNING: ",
KnownSystemName::Error => "ERROR: ",
_ => "FATAL: ",
};
let mut args = e.operands();
let mut code: i64 = 1;
let mut dynamic_code: Option<IntValue<'ctx>> = None;
if which == KnownSystemName::Fatal
&& let Some(first) = args.first()
&& !first.expr_type().is_some_and(|t| t.is_string())
&& first.kind() != ExpressionKind::StringLiteral
{
let first = *first;
if let Some(v) = first.constant_value().and_then(|c| c.as_i64()) {
code = v;
args.remove(0);
} else if first.expr_type().is_some_and(|t| t.is_integral()) {
let pv = lower_expr(cx, first)?.into_packed(
"$fatal's finish number must be an integral value",
&cx.fn_sym,
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
dynamic_code = Some(crate::expr::packed_to_plain_i32(&op, pv));
args.remove(0);
}
}
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let emit = |f: FunctionValue<'ctx>,
fmt: PointerValue<'ctx>,
values: Vec<crate::expr::LoweredValue<'ctx>>|
-> Result<(), LoweringError> {
let (args_ptr, n) = crate::systask::build_rtarg_array(cx, rtarg_ty, values)?;
cx.b.build_call(f, &[fmt.into(), args_ptr.into(), n.into()], "rt_severity")
.unwrap();
Ok(())
};
let scope = sv_sim_sim::process::cfg::shown_path(&e.system_call_scope().unwrap_or(cx.fn_sym));
let mut message = if args.is_empty() {
None
} else {
Some(crate::systask::display_format_and_values(cx, &args, e)?)
};
mark_deferred_args_done(cx);
severity_frame(
cx,
sys_fns,
tag,
e.source_range().start,
&scope,
&mut || {
if let Some((fmt, values)) = message.take() {
emit(sys_fns.write, fmt, values)?;
}
Ok(())
},
)?;
if which == KnownSystemName::Fatal {
let code_const =
dynamic_code.unwrap_or_else(|| cx.ctx.i32_type().const_int(code as u64, true));
cx.b.build_call(
sys_fns.finish,
&[handle.into(), code_const.into()],
"rt_fatal_finish",
)
.unwrap();
}
Ok(())
}
pub(crate) fn severity_frame<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
sys_fns: &SysCallFns<'ctx>,
tag: &str,
at: sv_lang::SourceLoc,
scope: &str,
body: &mut dyn FnMut() -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let emit = |f: FunctionValue<'ctx>,
fmt: PointerValue<'ctx>,
values: Vec<crate::expr::LoweredValue<'ctx>>|
-> Result<(), LoweringError> {
let (args_ptr, n) = crate::systask::build_rtarg_array(cx, rtarg_ty, values)?;
cx.b.build_call(f, &[fmt.into(), args_ptr.into(), n.into()], "rt_severity")
.unwrap();
Ok(())
};
let loc = sv_sim_sim::process::cfg::source_location(at).unwrap_or_else(|| "?".to_string());
let mark = if tag.starts_with("ERROR") || tag.starts_with("FATAL") {
char::from(sv_sim_runtime::display::SEVERITY_ERROR_MARK).to_string()
} else {
String::new()
};
let prefix = crate::systask::build_c_string_global(
cx.ctx,
cx.module,
&format!("{mark}{tag}{loc}: "),
"severity_prefix",
);
emit(sys_fns.write, prefix, Vec::new())?;
body()?;
let suffix = crate::systask::build_c_string_global(
cx.ctx,
cx.module,
&format!("\n{}Time: %0d Scope: {scope}", " ".repeat(tag.len())),
"severity_suffix",
);
let now = crate::expr::lower_time_system_call(cx, KnownSystemName::Time, "$time", &cx.fn_sym)?;
emit(sys_fns.display, suffix, vec![now])
}
struct AssertionLowering {
hosted: Option<String>,
nested: HashMap<String, usize>,
disable_keys: HashMap<String, Vec<usize>>,
fork_branch_names: Vec<String>,
disable_broadcast: Vec<(String, bool)>,
}
thread_local! {
static ASSERTION_LOWERING: std::cell::RefCell<Option<AssertionLowering>> =
const { std::cell::RefCell::new(None) };
}
struct AssertionLoweringGuard(Option<AssertionLowering>);
impl AssertionLowering {
fn enter(plan: &ProcessLoweringPlan<'_>) -> AssertionLoweringGuard {
let mine = AssertionLowering {
hosted: plan
.hosted_assertion
.map(|h| crate::sva::proc::assertion_key(&plan.fn_sym, &h.stmt)),
nested: plan.nested_assertions.clone(),
disable_keys: plan.disable_keys.clone(),
fork_branch_names: plan.fork_branch_names.clone(),
disable_broadcast: plan.disable_broadcast.clone(),
};
AssertionLoweringGuard(ASSERTION_LOWERING.with(|c| c.borrow_mut().replace(mine)))
}
}
impl Drop for AssertionLoweringGuard {
fn drop(&mut self) {
ASSERTION_LOWERING.with(|c| *c.borrow_mut() = self.0.take());
}
}
fn lower_concurrent_assertion<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
calls: &[crate::sva::actions::CallSite<'d>],
) -> Result<(), LoweringError> {
let kind = stmt
.assertion_kind()
.ok_or_else(|| LoweringError::unsupported_at("an assertion of unknown kind", &cx.fn_sym))?;
let key = crate::sva::proc::assertion_key(&cx.fn_sym, stmt);
let (hosted, queued) = ASSERTION_LOWERING.with(|c| {
c.borrow()
.as_ref()
.map(|a| {
(
a.hosted.as_deref() == Some(key.as_str()),
a.nested.get(&key).copied(),
)
})
.unwrap_or((false, None))
});
if !hosted {
return match queued {
Some(plan_index) => queue_procedural_assertion(cx, signals, stmt, plan_index),
None => Err(LoweringError::unsupported_at(
"a concurrent assertion that the process discovery did not host (a build mode \
without the assertion engine, or a context the discovery does not walk) is \
not supported",
&cx.fn_sym,
)),
};
}
if kind == sv_lang::AssertionKind::Expect {
return Ok(());
}
prime_assertion_inputs(cx, signals, stmt)?;
if calls.is_empty() {
return lower_assertion_action(cx, handle, signals, nba_fn, sys_fns, stmt, kind);
}
lower_assertion_calls_and_action(cx, handle, signals, nba_fn, sys_fns, stmt, kind, calls)
}
fn lower_procedural_checker<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
) -> Result<(), LoweringError> {
for instance in stmt.procedural_checker_instances() {
if instance.kind() != sv_lang::kinds::SymbolKind::CheckerInstance {
return Err(LoweringError::unsupported_at(
"an array of procedural checker instances: the grammar has no such construct \
(17.3 instantiates one checker per statement), so this is an internal error",
&cx.fn_sym,
));
}
for block in sv_sim_sim::netlist::module_items(&instance) {
if let Some(assertion) = crate::module::static_concurrent_assertion_of(&block) {
let key =
crate::sva::proc::queued_assertion_key(&cx.fn_sym, Some(&instance), &assertion);
let plan_index = ASSERTION_LOWERING
.with(|c| {
c.borrow()
.as_ref()
.and_then(|a| a.nested.get(&key).copied())
})
.ok_or_else(|| {
LoweringError::unsupported_at(
"a procedural checker instance whose assertion has no hosting plan",
&cx.fn_sym,
)
})?;
queue_procedural_assertion(cx, signals, &assertion, plan_index)?;
} else if let Some(deferred) = crate::module::static_deferred_assertion_of(&block) {
let env = crate::deferred_call::DeferredEnv { handle, signals };
lower_immediate_assertion(cx, sys_fns, &deferred, Some(&env), &mut |cx, s| {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, s)
})?;
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn lower_assertion_calls_and_action<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
kind: sv_lang::AssertionKind,
calls: &[crate::sva::actions::CallSite<'d>],
) -> Result<(), LoweringError> {
let i64t = cx.ctx.i64_type();
let fired = read_fired_term(cx);
let is_call =
cx.b.build_int_compare(
inkwell::IntPredicate::UGE,
fired,
i64t.const_int(FIRED_TERM_FIRST_CALL, false),
"assert_is_call",
)
.unwrap();
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
let call_bb = cx.ctx.append_basic_block(f, "cassert_calls");
let action_bb = cx.ctx.append_basic_block(f, "cassert_action");
let done_bb = cx.ctx.append_basic_block(f, "cassert_calls_done");
cx.b.build_conditional_branch(is_call, call_bb, action_bb)
.unwrap();
cx.b.position_at_end(action_bb);
lower_assertion_action(cx, handle, signals, nba_fn, sys_fns, stmt, kind)?;
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(done_bb).unwrap();
}
let captured = crate::sva::proc::capture_words(&crate::sva::proc::capture_items(
signals, *stmt, &cx.fn_sym,
)?);
cx.b.position_at_end(call_bb);
let which =
cx.b.build_int_sub(
fired,
i64t.const_int(FIRED_TERM_FIRST_CALL, false),
"assert_call_index",
)
.unwrap();
let case_bbs: Vec<LlvmBasicBlock<'ctx>> = (0..calls.len())
.map(|k| cx.ctx.append_basic_block(f, &format!("cassert_call{k}")))
.collect();
let cases: Vec<(IntValue<'ctx>, LlvmBasicBlock<'ctx>)> = case_bbs
.iter()
.enumerate()
.map(|(k, bb)| (i64t.const_int(k as u64, false), *bb))
.collect();
cx.b.build_switch(which, done_bb, &cases).unwrap();
for (call, bb) in calls.iter().zip(case_bbs) {
cx.b.position_at_end(bb);
if !call.spawns {
lower_match_item_call(cx, handle, signals, sys_fns, call, captured)?;
}
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(done_bb).unwrap();
}
}
cx.b.position_at_end(done_bb);
Ok(())
}
const FIRED_TERM_FIRST_CALL: u64 = 2;
fn lower_match_item_call<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
sys_fns: &SysCallFns<'ctx>,
site: &crate::sva::actions::CallSite<'d>,
first_input: usize,
) -> Result<(), LoweringError> {
let _scope = crate::sva::proc::CallArgScope::enter();
let mut keys = Vec::new();
let mut next = first_input;
for (arg, actual) in &site.args {
if !matches!(arg, sv_sim_sim::sva::ir::CallArg::Value(_)) {
continue;
}
let t = actual.expr_type().ok_or_else(|| {
LoweringError::unsupported_at(
"a by-value argument of a sequence match-item call has no type",
&cx.fn_sym,
)
})?;
let width = t.value_width();
let ptr = assert_input_slot(cx, next, width);
let key = crate::sva::proc::call_arg_key(actual);
cx.locals.insert(key.clone(), (ptr, width, t.is_signed()));
keys.push(key);
next += if width > 64 {
2 * width.div_ceil(64) as usize
} else {
1
};
}
let result = if site.call.is_system_call() == Some(true) {
lower_system_call_statement(cx, handle, signals, sys_fns, site.call)
} else {
crate::func::lower_task_call(cx, site.call)
};
for key in keys {
cx.locals.remove(&key);
}
result
}
fn lower_segment_body<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
seg: &sv_sim_sim::process::cfg::Segment<'d>,
calls: &[crate::sva::actions::CallSite<'d>],
) -> Result<(), LoweringError> {
let emit_probes = |cx: &LowerCtx<'ctx, 'd, 'm>, at: usize| {
let vpi = crate::vpi_dyn::enabled();
if !(vpi || crate::stmtcov::enabled()) || crate::stmt_shared::current_block_terminated(cx) {
return;
}
for (_, fp) in seg.frame_probes.iter().filter(|(i, _)| vpi && *i == at) {
match fp {
sv_sim_sim::process::cfg::FrameProbe::Enter { task, vars, bound } => {
let mut refs: Vec<sv_sim_exec::vpi_data::FrameRef> = Vec::new();
let mut actuals = Vec::new();
for f in task.subroutine_arguments() {
if let Some((_, a)) =
seg.ref_aliases.iter().find(|(x, _)| x.uid() == f.uid())
{
refs.push(sv_sim_exec::vpi_data::FrameRef {
name: f.name().to_owned(),
actual: a.hierarchical_path(),
store: None,
});
} else if let Some((_, a)) = bound.iter().find(|(x, _)| x.uid() == f.uid())
{
refs.push(sv_sim_exec::vpi_data::FrameRef {
name: f.name().to_owned(),
actual: crate::vpi_dyn::ref_actual_of(*a),
store: None,
});
actuals.push(*a);
}
}
if !bound.is_empty() {
crate::vpi_dyn::emit_bind(cx, task, refs.clone(), &actuals);
}
crate::vpi_dyn::emit_frame_enter(cx, task, vars, &refs);
}
sv_sim_sim::process::cfg::FrameProbe::Exit { task, vars } => {
let site = crate::vpi_dyn::frame_site(cx, task, vars);
crate::vpi_dyn::emit_frame_exit(cx, site);
}
}
}
for (_, st) in seg.probes.iter().filter(|(i, _)| *i == at) {
crate::vpi_dyn::emit_stmt_probe(cx, st);
}
for (_, st) in seg.disable_probes.iter().filter(|(i, _)| vpi && *i == at) {
crate::vpi_dyn::emit_disable_probe(cx, st);
}
};
let emit_block_events = |cx: &LowerCtx<'ctx, 'd, 'm>, at: usize| -> Result<(), LoweringError> {
for (_, task, is_end) in seg.block_events.iter().filter(|(i, _, _)| *i == at) {
if crate::covergroup::is_block_target(task) {
crate::covergroup::emit_block_event_of(cx, handle, task, *is_end)?;
}
}
Ok(())
};
for (k, stmt) in seg.body.iter().enumerate() {
cx.decl_scale.set(
seg.stmt_scopes
.iter()
.find(|(i, _)| *i == k)
.map(|(_, s)| crate::timescale::unit_and_precision(s)),
);
emit_block_events(cx, k)?;
emit_probes(cx, k);
if seg.expect_resume && k == 0 && is_expect_statement(stmt) {
lower_expect_action(cx, handle, signals, nba_fn, sys_fns, stmt)?;
} else if stmt.kind() == StatementKind::ConcurrentAssertion {
lower_concurrent_assertion(cx, handle, signals, nba_fn, sys_fns, stmt, calls)?;
} else {
crate::vpi_dyn::mark_probed_by_cfg();
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, stmt)?;
}
}
emit_block_events(cx, seg.body.len())?;
emit_probes(cx, seg.body.len());
Ok(())
}
fn is_expect_statement(stmt: &Statement<'_>) -> bool {
stmt.kind() == StatementKind::ConcurrentAssertion
&& stmt.assertion_kind() == Some(sv_lang::AssertionKind::Expect)
}
fn lower_expect_action<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
) -> Result<(), LoweringError> {
lower_assertion_action(
cx,
handle,
signals,
nba_fn,
sys_fns,
stmt,
sv_lang::AssertionKind::Expect,
)
}
fn read_fired_term<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let rt = cx
.module
.get_function(SV_RT_FIRED_TERM_SYMBOL)
.unwrap_or_else(|| {
cx.module
.add_function(SV_RT_FIRED_TERM_SYMBOL, i64t.fn_type(&[], false), None)
});
cx.b.build_call(rt, &[], "assert_fired")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
fn lower_assertion_action<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
kind: sv_lang::AssertionKind,
) -> Result<(), LoweringError> {
let i64t = cx.ctx.i64_type();
let fired = read_fired_term(cx);
let is_fail =
cx.b.build_int_compare(
inkwell::IntPredicate::NE,
fired,
i64t.const_zero(),
"assert_is_fail",
)
.unwrap();
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
let pass_bb = cx.ctx.append_basic_block(f, "cassert_pass");
let fail_bb = cx.ctx.append_basic_block(f, "cassert_fail");
let done_bb = cx.ctx.append_basic_block(f, "cassert_done");
if kind == sv_lang::AssertionKind::Expect {
let none =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
fired,
i64t.const_int(
sv_sim_sim::sched::assert_control::EXPECT_NO_ACTION_TERM as u64,
false,
),
"expect_no_action",
)
.unwrap();
let decided_bb = cx.ctx.append_basic_block(f, "cassert_decided");
cx.b.build_conditional_branch(none, done_bb, decided_bb)
.unwrap();
cx.b.position_at_end(decided_bb);
}
cx.b.build_conditional_branch(is_fail, fail_bb, pass_bb)
.unwrap();
cx.b.position_at_end(pass_bb);
if let Some(t) = stmt.assertion_if_true() {
lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &t)?;
}
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(done_bb).unwrap();
}
cx.b.position_at_end(fail_bb);
match stmt.assertion_if_false() {
Some(e) => lower_segment_statement(cx, handle, signals, nba_fn, sys_fns, &e)?,
None if matches!(
kind,
sv_lang::AssertionKind::Assert
| sv_lang::AssertionKind::Assume
| sv_lang::AssertionKind::Expect
) =>
{
let scope = sv_sim_sim::process::cfg::shown_path(&cx.fn_sym);
severity_frame(
cx,
sys_fns,
"ERROR: ",
stmt.source_range().start,
&scope,
&mut || Ok(()),
)?;
}
None => {}
}
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(done_bb).unwrap();
}
cx.b.position_at_end(done_bb);
Ok(())
}
fn packed_as_i64<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: PackedValue<'ctx>,
) -> Result<IntValue<'ctx>, LoweringError> {
if v.width > 64 {
return Err(LoweringError::unsupported_at(
"internal error: a wide captured value reached the one-word capture path",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val = op.lane_val(v);
let i64t = cx.ctx.i64_type();
Ok(if v.width == 64 {
val
} else if v.signed {
cx.b.build_int_s_extend(val, i64t, "cap_sext").unwrap()
} else {
cx.b.build_int_z_extend(val, i64t, "cap_zext").unwrap()
})
}
fn capture_words<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: PackedValue<'ctx>,
width: u32,
) -> Result<Vec<IntValue<'ctx>>, LoweringError> {
if width <= 64 {
return Ok(vec![packed_as_i64(cx, v)?]);
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = match v.width.cmp(&width) {
std::cmp::Ordering::Less => op.extend4(v, width, v.signed),
std::cmp::Ordering::Equal => v,
std::cmp::Ordering::Greater => op.truncate4(v, width),
};
let i64t = cx.ctx.i64_type();
let plane_ty = op.i_n(width);
let limbs = width.div_ceil(64);
let mut out = Vec::with_capacity(2 * limbs as usize);
for plane in [op.lane_val(v), op.lane_unk(v)] {
for k in 0..limbs {
let shifted =
cx.b.build_right_shift(
plane,
plane_ty.const_int(64 * u64::from(k), false),
false,
"cap_shr",
)
.unwrap();
out.push(cx.b.build_int_truncate(shifted, i64t, "cap_limb").unwrap());
}
}
Ok(out)
}
fn queue_procedural_assertion<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
signals: &HashMap<SymbolId, SignalSlot>,
stmt: &Statement<'d>,
plan_index: usize,
) -> Result<(), LoweringError> {
let items = crate::sva::proc::capture_items(signals, *stmt, &cx.fn_sym)?;
let i64t = cx.ctx.i64_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let words = crate::sva::proc::capture_words(&items);
let n = words.max(1) as u32;
let buf = crate::types::entry_alloca_in(&cx.b, i64t.array_type(n), "assert_capture");
let mut k = 0usize;
for item in &items {
let v = lower_expr(cx, item.expr)?.into_packed(
"a captured real, string or >128-bit value in a procedural concurrent assertion",
&cx.fn_sym,
)?;
for word in capture_words(cx, v, item.width)? {
let p = unsafe {
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(k as u64, false)],
"assert_capture_slot",
)
.unwrap()
};
cx.b.build_store(p, word).unwrap();
k += 1;
}
}
let f = cx
.module
.get_function(SV_RT_PROC_ASSERT_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_PROC_ASSERT_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[i64t.into(), ptr_ty.into(), i64t.into()], false),
None,
)
});
cx.b.build_call(
f,
&[
i64t.const_int(plan_index as u64, false).into(),
buf.into(),
i64t.const_int(words as u64, false).into(),
],
"proc_assert",
)
.unwrap();
Ok(())
}
fn lower_assertion_disable<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
keys: &[usize],
) -> Result<(), LoweringError> {
if keys.is_empty() {
return Ok(());
}
let i64t = cx.ctx.i64_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let buf = crate::types::entry_alloca_in(
&cx.b,
i64t.array_type(keys.len() as u32),
"assert_disable_keys",
);
for (k, key) in keys.iter().enumerate() {
let p = unsafe {
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(k as u64, false)],
"assert_disable_slot",
)
.unwrap()
};
cx.b.build_store(p, i64t.const_int(*key as u64, false))
.unwrap();
}
let f = cx
.module
.get_function(SV_RT_PROC_ASSERT_DISABLE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_PROC_ASSERT_DISABLE_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[ptr_ty.into(), i64t.into()], false),
None,
)
});
cx.b.build_call(
f,
&[buf.into(), i64t.const_int(keys.len() as u64, false).into()],
"proc_assert_disable",
)
.unwrap();
Ok(())
}
fn prime_assertion_inputs<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
signals: &HashMap<SymbolId, SignalSlot>,
stmt: &Statement<'d>,
) -> Result<(), LoweringError> {
let items = crate::sva::proc::capture_items(signals, *stmt, &cx.fn_sym)?;
if items.is_empty() {
return Ok(());
}
let mut k = 0usize;
for item in &items {
let ptr = assert_input_slot(cx, k, item.width);
cx.locals
.insert(item.key.clone(), (ptr, item.width, item.signed));
k += item.words();
}
Ok(())
}
pub(crate) fn assert_input_slot<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
k: usize,
width: u32,
) -> PointerValue<'ctx> {
let i64t = cx.ctx.i64_type();
let f = cx
.module
.get_function(SV_RT_ASSERT_INPUT_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_ASSERT_INPUT_SYMBOL,
i64t.fn_type(&[i64t.into()], false),
None,
)
});
let input = |j: usize| -> IntValue<'ctx> {
cx.b.build_call(f, &[i64t.const_int(j as u64, false).into()], "assert_input")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if width > 64 {
let limbs = width.div_ceil(64) as usize;
let plane_ty = op.i_n(width);
let assemble = |first: usize| -> IntValue<'ctx> {
let mut acc = plane_ty.const_zero();
for j in 0..limbs {
let limb = input(first + j);
let limb = if (width as usize) < 64 * (j + 1) {
cx.b.build_int_truncate(limb, op.i_n(width - 64 * j as u32), "input_top")
.unwrap()
} else {
limb
};
let ext =
cx.b.build_int_z_extend(limb, plane_ty, "input_limb")
.unwrap();
let moved =
cx.b.build_left_shift(
ext,
plane_ty.const_int(64 * j as u64, false),
"input_limb_shl",
)
.unwrap();
acc = cx.b.build_or(acc, moved, "input_plane").unwrap();
}
acc
};
let val = assemble(k);
let stored = if cx.repr == ValueRepr::FourState {
op.pack(val, assemble(k + limbs), width, ValueRepr::FourState, false)
.llvm
} else {
val
};
let ptr = crate::types::entry_alloca_in(&cx.b, stored.get_type(), "assert_input_slot");
cx.b.build_store(ptr, stored).unwrap();
return ptr;
}
let raw = input(k);
let val = if width < 64 {
cx.b.build_int_truncate(raw, op.i_n(width), "input_val")
.unwrap()
} else {
raw
};
let stored = if cx.repr == ValueRepr::FourState {
cx.b.build_int_z_extend(val, op.i_n(width * 2), "input_packed")
.unwrap()
} else {
val
};
let ptr = crate::types::entry_alloca_in(&cx.b, stored.get_type(), "assert_input_slot");
cx.b.build_store(ptr, stored).unwrap();
ptr
}
pub(crate) fn lower_immediate_assertion<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
sys_fns: &SysCallFns<'ctx>,
stmt: &Statement<'d>,
deferred_env: Option<&crate::deferred_call::DeferredEnv<'_, 'ctx>>,
action: &mut dyn FnMut(
&mut LowerCtx<'ctx, 'd, 'm>,
&Statement<'d>,
) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let cond = stmt
.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_expression())
.ok_or_else(|| {
LoweringError::unsupported_at("an assertion with no expression", &cx.fn_sym)
})?;
let kind = stmt
.immediate_assertion_kind()
.ok_or_else(|| LoweringError::unsupported_at("an assertion of unknown kind", &cx.fn_sym))?;
let site = crate::assert_site::describe(&cx.fn_sym, *stmt).map(|d| d.encode());
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
let controlled = match &site {
Some(text) => {
let bits = assert_ctl_query(cx, text);
let checking = query_bit(
cx,
bits,
sv_sim_sim::sched::assert_control::Q_CHECKING,
"assert_checking",
);
let pass_on = query_bit(
cx,
bits,
sv_sim_sim::sched::assert_control::Q_PASS_NONVACUOUS,
"assert_pass_on",
);
let fail_on = query_bit(
cx,
bits,
sv_sim_sim::sched::assert_control::Q_FAIL,
"assert_fail_on",
);
let run_bb = cx.ctx.append_basic_block(f, "assert_run");
let off_bb = cx.ctx.append_basic_block(f, "assert_off");
cx.b.build_conditional_branch(checking, run_bb, off_bb)
.unwrap();
cx.b.position_at_end(run_bb);
Some((pass_on, fail_on, off_bb))
}
None => None,
};
let pass = crate::expr::lower_condition(cx, cond)?;
let deferred = stmt.is_deferred_assertion() == Some(true);
let capture_start = deferred.then(|| deferred_begin_call(cx));
let capture_slot = deferred.then(|| {
let slot = crate::types::entry_alloca_in(&cx.b, cx.ctx.i64_type(), "deferred_start_slot");
cx.b.build_store(slot, cx.ctx.i64_type().const_all_ones())
.unwrap();
slot
});
let outer_slot = cx.deferred_start.replace(capture_slot);
let pass_bb = cx.ctx.append_basic_block(f, "assert_pass");
let fail_bb = cx.ctx.append_basic_block(f, "assert_fail");
let done_bb = cx.ctx.append_basic_block(f, "assert_done");
cx.b.build_conditional_branch(pass, pass_bb, fail_bb)
.unwrap();
cx.b.position_at_end(pass_bb);
if let Some((pass_on, _, _)) = &controlled {
let run = cx.ctx.append_basic_block(f, "assert_pass_run");
cx.b.build_conditional_branch(*pass_on, run, done_bb)
.unwrap();
cx.b.position_at_end(run);
}
if let Some(t) = stmt.immediate_assertion_if_true() {
run_assertion_action(
cx,
stmt,
deferred,
deferred_env,
site.as_deref(),
&t,
action,
)?;
}
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(done_bb).unwrap();
}
cx.b.position_at_end(fail_bb);
if let Some((_, fail_on, _)) = &controlled {
let run = cx.ctx.append_basic_block(f, "assert_fail_run");
cx.b.build_conditional_branch(*fail_on, run, done_bb)
.unwrap();
cx.b.position_at_end(run);
}
match stmt.immediate_assertion_if_false() {
Some(e) => run_assertion_action(
cx,
stmt,
deferred,
deferred_env,
site.as_deref(),
&e,
action,
)?,
None if matches!(
kind,
sv_lang::AssertionKind::Assert | sv_lang::AssertionKind::Assume
) =>
{
let scope = sv_sim_sim::process::cfg::shown_path(&cx.fn_sym);
severity_frame(
cx,
sys_fns,
"ERROR: ",
stmt.source_range().start,
&scope,
&mut || Ok(()),
)?;
}
None => {}
}
if !crate::stmt_shared::current_block_terminated(cx) {
cx.b.build_unconditional_branch(done_bb).unwrap();
}
cx.b.position_at_end(done_bb);
cx.deferred_start.set(outer_slot);
if let (Some(first), Some(slot)) = (capture_start, capture_slot) {
let i64t = cx.ctx.i64_type();
let marked =
cx.b.build_load(i64t, slot, "deferred_start_marked")
.unwrap()
.into_int_value();
let unset =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
marked,
i64t.const_all_ones(),
"deferred_nothing_printed",
)
.unwrap();
let start =
cx.b.build_select(unset, first, marked, "deferred_capture_start")
.unwrap()
.into_int_value();
let mode = if stmt.is_final_assertion() == Some(true) {
sv_sim_sim::sched::DEFERRED_MODE_FINAL
} else {
sv_sim_sim::sched::DEFERRED_MODE_OBSERVED
};
deferred_end_call(
cx,
start,
stmt.source_range().start.offset,
mode,
site.as_deref(),
);
}
if let Some((_, _, off_bb)) = controlled {
cx.b.build_unconditional_branch(off_bb).unwrap();
cx.b.position_at_end(off_bb);
}
Ok(())
}
fn run_assertion_action<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
stmt: &Statement<'d>,
deferred: bool,
deferred_env: Option<&crate::deferred_call::DeferredEnv<'_, 'ctx>>,
site: Option<&str>,
act: &Statement<'d>,
action: &mut dyn FnMut(
&mut LowerCtx<'ctx, 'd, 'm>,
&Statement<'d>,
) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
if deferred && let Some(call) = crate::deferred_call::action_call(act) {
let env = deferred_env.ok_or_else(|| {
LoweringError::unsupported_at(
"a deferred assertion in a subroutine body whose action calls a user task or \
function (LRM 16.4.1: the call is made when the report matures, which needs \
the calling process's instance)",
&cx.fn_sym,
)
})?;
return crate::deferred_call::lower_deferred_call(
cx,
env,
call,
stmt.is_final_assertion() == Some(true),
site,
);
}
action(cx, act)
}
pub(crate) fn lower_deferred_disable<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
label: &str,
) -> Result<bool, LoweringError> {
let Some(body) = sv_sim_sim::process::cfg::proc_body(&cx.fn_sym) else {
return Ok(false);
};
let Some((_, assertion)) = sv_sim_sim::process::cfg::deferred_assertion_labels(&body)
.into_iter()
.find(|(n, _)| n == label)
else {
return Ok(false);
};
let Some(site) = crate::assert_site::describe(&cx.fn_sym, assertion).map(|d| d.encode()) else {
return Ok(false);
};
let start = deferred_begin_call(cx);
deferred_end_call(
cx,
start,
0,
sv_sim_sim::sched::DEFERRED_MODE_CANCEL,
Some(&site),
);
Ok(true)
}
pub(crate) fn mark_deferred_args_done<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) {
let Some(slot) = cx.deferred_start.get() else {
return;
};
let i64t = cx.ctx.i64_type();
let cur =
cx.b.build_load(i64t, slot, "deferred_start")
.unwrap()
.into_int_value();
let unset =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
cur,
i64t.const_all_ones(),
"deferred_unset",
)
.unwrap();
let now = deferred_begin_call(cx);
let next =
cx.b.build_select(unset, now, cur, "deferred_start_next")
.unwrap();
cx.b.build_store(slot, next).unwrap();
}
pub(crate) fn deferred_begin_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
) -> inkwell::values::IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let begin = cx
.module
.get_function(SV_RT_DEFERRED_BEGIN_SYMBOL)
.unwrap_or_else(|| {
cx.module
.add_function(SV_RT_DEFERRED_BEGIN_SYMBOL, i64t.fn_type(&[], false), None)
});
cx.b.build_call(begin, &[], "deferred_begin")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
pub(crate) fn deferred_end_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
start: inkwell::values::IntValue<'ctx>,
site: u64,
mode: u32,
control: Option<&str>,
) {
let i64t = cx.ctx.i64_type();
let i32t = cx.ctx.i32_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let end = cx
.module
.get_function(SV_RT_DEFERRED_END_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_DEFERRED_END_SYMBOL,
cx.ctx.void_type().fn_type(
&[i64t.into(), i64t.into(), i32t.into(), ptr_ty.into()],
false,
),
None,
)
});
let control = match control {
Some(text) => build_c_string_global(cx.ctx, cx.module, text, "assert_site"),
None => ptr_ty.const_null(),
};
cx.b.build_call(
end,
&[
start.into(),
i64t.const_int(site, false).into(),
i32t.const_int(u64::from(mode), false).into(),
control.into(),
],
"deferred_end",
)
.unwrap();
}
pub const SV_RT_ASSERT_CTL_SYMBOL: &str = "sv_rt_assert_ctl";
fn assert_ctl_fn<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> FunctionValue<'ctx> {
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
cx.module
.get_function(SV_RT_ASSERT_CTL_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_ASSERT_CTL_SYMBOL,
i64_ty.fn_type(
&[
i32_ty.into(),
i64_ty.into(),
i64_ty.into(),
i64_ty.into(),
i64_ty.into(),
ptr_ty.into(),
],
false,
),
Some(Linkage::External),
)
})
}
fn assert_ctl_query<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, site: &str) -> IntValue<'ctx> {
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let text = build_c_string_global(cx.ctx, cx.module, site, "assert_site");
cx.b.build_call(
assert_ctl_fn(cx),
&[
i32_ty
.const_int(
u64::from(sv_sim_sim::sched::assert_control::CTL_OP_QUERY),
false,
)
.into(),
i64_ty.const_zero().into(),
i64_ty.const_zero().into(),
i64_ty.const_zero().into(),
i64_ty.const_zero().into(),
text.into(),
],
"assert_query",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
fn query_bit<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
bits: IntValue<'ctx>,
bit: u32,
name: &str,
) -> IntValue<'ctx> {
let masked =
cx.b.build_and(bits, bits.get_type().const_int(u64::from(bit), false), name)
.unwrap();
cx.b.build_int_compare(
inkwell::IntPredicate::NE,
masked,
bits.get_type().const_zero(),
name,
)
.unwrap()
}
fn lower_assert_control_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
name: KnownSystemName,
e: Expression<'d>,
) -> Result<(), LoweringError> {
use sv_sim_sim::sched::assert_control as ac;
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let args = e.operands();
let fixed = |control: i64, atype: i64| (control, atype, i64::from(ac::DIR_ALL));
let fixed_args = match name {
KnownSystemName::AssertControl => None,
KnownSystemName::AssertOn => Some(fixed(3, 15)),
KnownSystemName::AssertOff => Some(fixed(4, 15)),
KnownSystemName::AssertKill => Some(fixed(5, 15)),
KnownSystemName::AssertPassOn => Some(fixed(6, 31)),
KnownSystemName::AssertPassOff => Some(fixed(7, 31)),
KnownSystemName::AssertFailOn => Some(fixed(8, 31)),
KnownSystemName::AssertFailOff => Some(fixed(9, 31)),
KnownSystemName::AssertNonvacuousOn => Some(fixed(10, 31)),
KnownSystemName::AssertVacuousOff => Some(fixed(11, 31)),
other => {
return Err(LoweringError::unsupported_at(
&format!("{other:?} is not an assertion control task (internal invariant)"),
&cx.fn_sym,
));
}
};
let scalar = |arg: Option<&Expression<'d>>,
default: i64,
what: &str|
-> Result<IntValue<'ctx>, LoweringError> {
match arg.filter(|a| a.kind() != ExpressionKind::EmptyArgument) {
Some(a) => {
let pv = lower_expr(cx, *a)?.into_packed(
&format!("the {what} argument of an assertion control task must be integral"),
&cx.fn_sym,
)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = crate::expr::packed_to_plain_i32(&op, pv);
Ok(cx.b.build_int_s_extend(v, i64_ty, what).unwrap())
}
None => Ok(i64_ty.const_int(default as u64, true)),
}
};
let (control, atype, directive, levels, targets) = match fixed_args {
Some((control, atype, directive)) => (
i64_ty.const_int(control as u64, false),
i64_ty.const_int(atype as u64, false),
i64_ty.const_int(directive as u64, false),
scalar(args.first(), 0, "levels")?,
args.get(1..).unwrap_or(&[]),
),
None => {
if args.is_empty() {
return Err(LoweringError::unsupported_at(
"$assertcontrol needs a control_type argument",
&cx.fn_sym,
));
}
(
scalar(args.first(), 0, "control_type")?,
scalar(args.get(1), i64::from(ac::ASSERT_ALL), "assertion_type")?,
scalar(args.get(2), i64::from(ac::DIR_ALL), "directive_type")?,
scalar(args.get(3), 0, "levels")?,
args.get(4..).unwrap_or(&[]),
)
}
};
let mut paths = Vec::new();
for target in targets {
let sym = target
.arbitrary_symbol()
.or_else(|| target.referenced_symbol())
.ok_or_else(|| {
LoweringError::unsupported_at(
"an assertion control task's scope or assertion argument must name a \
module instance, a scope or an assertion",
&cx.fn_sym,
)
})?;
paths.push(sv_sim_sim::process::cfg::shown_path(&sym));
}
let text = build_c_string_global(cx.ctx, cx.module, &paths.join("\n"), "assert_targets");
cx.b.build_call(
assert_ctl_fn(cx),
&[
i32_ty
.const_int(u64::from(ac::CTL_OP_CONTROL), false)
.into(),
control.into(),
atype.into(),
directive.into(),
levels.into(),
text.into(),
],
"rt_assert_control",
)
.unwrap();
Ok(())
}
fn lower_finish_diag<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
sys_fns: &SysCallFns<'ctx>,
e: sv_lang::Expression<'d>,
code: i64,
name: &str,
) -> Result<(), LoweringError> {
let scope = e.system_call_scope().unwrap_or(cx.fn_sym);
let loc = sv_sim_sim::process::cfg::source_location(e.source_range().start)
.unwrap_or_else(|| "?".to_string());
let unit = match crate::timescale::scale_at(&scope) {
Some(ts) => crate::timescale::value_text(ts.base),
None => "1s".to_string(),
};
let fmt = crate::systask::build_c_string_global(
cx.ctx,
cx.module,
&format!(
"{}{code}{loc}: {name} called at %0d ({unit})",
char::from(sv_sim_runtime::display::FINISH_DIAG_MARK)
),
"finish_diag",
);
let now = crate::expr::lower_time_system_call(cx, KnownSystemName::Time, "$time", &scope)?;
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let (args_ptr, n) = crate::systask::build_rtarg_array(cx, rtarg_ty, vec![now])?;
cx.b.build_call(
sys_fns.write,
&[fmt.into(), args_ptr.into(), n.into()],
"rt_finish_diag",
)
.unwrap();
Ok(())
}
pub(crate) fn lower_finish_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
handle: PointerValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
e: sv_lang::Expression<'d>,
which: KnownSystemName,
) -> Result<(), LoweringError> {
let finish_fn = sys_fns.finish;
let children = e.children();
let name = if which == KnownSystemName::Stop {
"$stop"
} else {
"$finish"
};
let arg_expr = match children.first() {
None => None,
Some(node) => Some(node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at("$finish's argument is not an expression", &cx.fn_sym)
})?),
};
let constant: Option<i64> = match arg_expr {
None => Some(1),
Some(a) => match a.constant_value() {
Some(cv) => {
let iv = cv.as_integer().ok_or_else(|| {
LoweringError::unsupported_at(
"$finish's argument constant is not an integer",
&cx.fn_sym,
)
})?;
Some(iv.as_i64().ok_or_else(|| {
LoweringError::unsupported_at(
"$finish's argument constant does not fit in an i64",
&cx.fn_sym,
)
})?)
}
None => None,
},
};
match constant {
Some(code) => {
if !(0..=2).contains(&code) {
return Err(LoweringError::unsupported_at(
"$finish/$stop argument must be 0, 1 or 2 (LRM 20.2)",
&cx.fn_sym,
));
}
if code > 0 {
lower_finish_diag(cx, sys_fns, e, code, name)?;
}
}
None => {
let a = arg_expr.expect("a non-constant argument exists");
let pv = lower_expr(cx, a)?
.into_packed("$finish's argument must be integral", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let v = crate::expr::packed_to_plain_i32(&op, pv);
let (_, f) = crate::stmt_shared::current_block_and_fn(cx)?;
for level in [1_i64, 2] {
let hit = cx.ctx.append_basic_block(f, "finish_level");
let next = cx.ctx.append_basic_block(f, "finish_next");
let is =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
v,
v.get_type().const_int(level as u64, false),
"finish_is_level",
)
.unwrap();
cx.b.build_conditional_branch(is, hit, next).unwrap();
cx.b.position_at_end(hit);
lower_finish_diag(cx, sys_fns, e, level, name)?;
cx.b.build_unconditional_branch(next).unwrap();
cx.b.position_at_end(next);
}
}
}
let code_const = cx.ctx.i32_type().const_int(0, true);
cx.b.build_call(
finish_fn,
&[handle.into(), code_const.into()],
"rt_finish_call",
)
.unwrap();
Ok(())
}
pub(crate) fn seg_signals<'a>(
signals: &'a HashMap<SymbolId, SignalSlot>,
seg: &Segment<'_>,
) -> Result<std::borrow::Cow<'a, HashMap<SymbolId, SignalSlot>>, LoweringError> {
if seg.ref_aliases.is_empty() {
return Ok(std::borrow::Cow::Borrowed(signals));
}
let mut out = signals.clone();
for (formal, actual) in &seg.ref_aliases {
let slot = *signals.get(&actual.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"`ref` argument `{}` is bound to `{}`, which is not storage of this instance",
formal.name(),
actual.name()
),
formal,
)
})?;
out.insert(formal.uid(), slot);
}
Ok(std::borrow::Cow::Owned(out))
}
fn all_seg_signals<'a>(
signals: &'a HashMap<SymbolId, SignalSlot>,
segments: &[Segment<'_>],
) -> Result<std::borrow::Cow<'a, HashMap<SymbolId, SignalSlot>>, LoweringError> {
let mut out = std::borrow::Cow::Borrowed(signals);
for seg in segments.iter().filter(|s| !s.ref_aliases.is_empty()) {
let with = seg_signals(signals, seg)?;
for (formal, _) in &seg.ref_aliases {
if !out.contains_key(&formal.uid()) {
out.to_mut().insert(formal.uid(), with[&formal.uid()]);
}
}
}
Ok(out)
}
fn region_arena_bias(region_id: u32, signals: &HashMap<SymbolId, SignalSlot>) -> u32 {
sv_sim_sim::process::arena_base_offset_for_region(region_id, signals.len() as u32)
}
#[allow(clippy::too_many_arguments)]
fn prime_external_signals<'ctx, 'd, 'm>(
cx: &mut LowerCtx<'ctx, 'd, 'm>,
fns: &AccessorFns<'ctx>,
handle: PointerValue<'ctx>,
seg: &Segment<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
region_id: u32,
ext_ptrs: Option<&HashMap<SymbolId, PointerValue<'ctx>>>,
extra: &[Expression<'d>],
) -> HashSet<SymbolId> {
let owner = cx.fn_sym.uid();
let (refs, written, hidden) = external_refs_in_segment(seg, signals, &owner, extra);
let bias = region_arena_bias(region_id, signals);
let mut primed = HashSet::new();
let ids = refs
.iter()
.map(SymbolUid::uid)
.chain(hidden)
.filter(|id| primed.insert(id.clone()))
.collect::<Vec<_>>();
let sampled = sv_sim_sim::sva::sampled::checker_ff_sampled_signals(&cx.fn_sym)
.expect("a checker always_ff's sampled signals were validated by the layout");
let mut by_slot: HashMap<u32, (PointerValue<'ctx>, u32, bool)> = HashMap::new();
let mut inline_reads = crate::accessor::InlineReads::new(
ids.iter()
.filter(|id| {
signals[*id].class == SignalSlotClass::Inline
&& !crate::huge::is_huge_width(signals[*id].width)
})
.count(),
);
for sym_id in ids {
let info = signals[&sym_id];
if let Some(&shared) = by_slot.get(&info.slot) {
cx.locals.insert(sym_id, shared);
continue;
}
let info = if sampled.contains(&sym_id) {
assert!(
!written.contains(&sym_id),
"a checker always_ff blocking-writes a signal it samples"
);
signals[&sv_sim_sim::sva::sampled::p_id(&sym_id)]
} else {
info
};
let biased_slot = info.slot + bias;
if crate::huge::is_huge_width(info.width) {
let prime_slot = if written.contains(&sym_id) {
cx.specify_dest
.get(&sym_id)
.map_or(biased_slot, |r| r.slot + bias)
} else {
biased_slot
};
let ptr = crate::huge::rt_prime(&*cx, handle, prime_slot, info.width, false);
cx.locals
.insert(sym_id.clone(), (ptr, info.width, info.signed));
by_slot.insert(signals[&sym_id].slot, (ptr, info.width, info.signed));
continue;
}
let raw = match info.class {
SignalSlotClass::Inline => inline_reads.get(&*cx, fns, handle, biased_slot, info.width),
SignalSlotClass::Wide => build_get_wide(&*cx, fns, handle, biased_slot, info.width),
SignalSlotClass::Memory { .. } => {
unreachable!("external_refs_in_segment excludes SignalSlotClass::Memory symbols")
}
};
let ptr = match ext_ptrs.and_then(|m| m.get(&sym_id)) {
Some(&hoisted) => hoisted,
None => {
let packed_ty = int_type(cx.ctx, info.width * 2);
crate::types::entry_alloca_in(&cx.b, packed_ty, "ext_shadow")
}
};
cx.b.build_store(ptr, raw).unwrap();
cx.locals
.insert(sym_id.clone(), (ptr, info.width, info.signed));
by_slot.insert(signals[&sym_id].slot, (ptr, info.width, info.signed));
}
written
}
fn external_symbols_in_plan<'d>(
segments: &[Segment<'d>],
signals: &HashMap<SymbolId, SignalSlot>,
owner: &SymbolId,
extra: &[Expression<'d>],
) -> Result<HashSet<SymbolId>, LoweringError> {
let mut all = HashSet::new();
for seg in segments {
let signals = seg_signals(signals, seg)?;
let (refs, written, hidden) = external_refs_in_segment(seg, &signals, owner, extra);
all.extend(refs.iter().map(SymbolUid::uid));
all.extend(written);
all.extend(hidden);
}
Ok(all)
}
fn flush_external_signals<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
fns: &AccessorFns<'ctx>,
handle: PointerValue<'ctx>,
written: &HashSet<SymbolId>,
signals: &HashMap<SymbolId, SignalSlot>,
region_id: u32,
) {
let bias = region_arena_bias(region_id, signals);
let mut written: Vec<&SymbolId> = written.iter().collect();
written.sort();
for id in written {
let (ptr, width, _signed) = cx.locals[id];
let info = signals[id];
let biased_slot = cx.specify_dest.get(id).map_or(info.slot, |r| r.slot) + bias;
if crate::huge::is_huge_width(width) {
crate::huge::rt_commit(cx, handle, biased_slot, width);
continue;
}
let packed_ty = int_type(cx.ctx, width * 2);
let cur = cx.b.build_load(packed_ty, ptr, "ext_flush_ld").unwrap();
match info.class {
SignalSlotClass::Inline => build_set_scalar(
cx,
fns,
handle,
biased_slot,
cur.into_int_value(),
info.width,
),
SignalSlotClass::Wide => build_set_wide(
cx,
fns,
handle,
biased_slot,
cur.into_int_value(),
info.width,
),
SignalSlotClass::Memory { .. } => unreachable!(
"external_refs_in_segment excludes SignalSlotClass::Memory symbols from `written`"
),
}
}
}
fn emit_local_inits<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
plan: &ProcessLoweringPlan<'d>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let mut ids: Vec<&SymbolId> = plan.local_syms.keys().collect();
ids.sort_by(|a, b| a.path().cmp(b.path()));
for id in ids {
let sym = plan.local_syms[id];
if is_subroutine_owned(&sym) || sv_sim_sim::process::cfg::is_automatic_block_var(&sym) {
continue;
}
let Some(&(ptr, width, signed)) = cx.locals.get(id) else {
continue;
};
if plan
.frame
.slots
.iter()
.any(|s| s.captured && matches!(&s.source, FrameSlotSource::DeclaredVar(d) if d == id))
{
continue;
}
fresh_cell(cx, &sym);
if crate::class_events::init_local(cx, &sym)? {
continue;
}
if crate::events::is_pool_event_var(cx, &sym) {
crate::events::init_event_var(cx, &sym)?;
continue;
}
if let Some(shape) = sym
.value_type()
.as_ref()
.and_then(crate::arrays::array_shape)
{
crate::arrays::init_array_var(cx, ptr, &shape, sym.initializer())?;
continue;
}
let value = local_init_value(cx, &sym, width, signed)?;
cx.b.build_store(ptr, value.llvm).unwrap();
}
let _ = &op;
Ok(())
}
fn write_through_for<'ctx, 'm>(
cx: &LowerCtx<'ctx, '_, 'm>,
fns: &'m AccessorFns<'ctx>,
handle: PointerValue<'ctx>,
plan: &ProcessLoweringPlan<'_>,
seg_signals: &HashMap<SymbolId, SignalSlot>,
) -> crate::expr::WriteThrough<'ctx, 'm> {
let bias = region_arena_bias(plan.region_id, &plan.signals);
let signals = seg_signals
.iter()
.filter(|(_, info)| !matches!(info.class, SignalSlotClass::Memory { .. }))
.filter_map(|(id, info)| {
let (ptr, _, _) = *cx.locals.get(id)?;
Some((
ptr,
(
cx.specify_dest.get(id).map_or(info.slot, |r| r.slot) + bias,
info.width,
info.class == SignalSlotClass::Wide,
),
))
})
.collect();
crate::expr::WriteThrough {
handle,
fns,
signals,
reload: Default::default(),
}
}
fn is_subroutine_owned(sym: &Symbol<'_>) -> bool {
let mut cur = sym.parent();
while let Some(p) = cur {
let kind = p.kind();
if kind == sv_lang::kinds::SymbolKind::Subroutine {
return true;
}
if kind == sv_lang::kinds::SymbolKind::InstanceBody {
return false;
}
cur = p.parent();
}
false
}
pub(crate) fn init_local_var<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
var: &Symbol<'d>,
) -> Result<(), LoweringError> {
if cx.locals.contains_key(&var.uid()) && crate::class_events::init_local(cx, var)? {
return Ok(());
}
if crate::events::is_pool_event_var(cx, var) {
if cx.locals.contains_key(&var.uid()) {
fresh_cell(cx, var);
}
return crate::events::init_event_var(cx, var);
}
let (ptr, width, signed) =
cx.locals.get(&var.uid()).copied().ok_or_else(|| {
LoweringError::unsupported_at("a local variable has no frame slot", var)
})?;
fresh_cell(cx, var);
if let Some(shape) = var
.value_type()
.as_ref()
.and_then(crate::arrays::array_shape)
{
return crate::arrays::init_array_var(cx, ptr, &shape, var.initializer());
}
let v = local_init_value(cx, var, width, signed)?;
cx.b.build_store(ptr, v.llvm).unwrap();
crate::expr::write_through_store(cx, ptr);
Ok(())
}
fn release_task_local<'ctx, 'd>(cx: &LowerCtx<'ctx, 'd, '_>, var: &Symbol<'d>) {
let Some(t) = var.value_type() else { return };
if crate::collections::handle_offsets(&t).is_empty() || crate::arrays::array_shape(&t).is_some()
{
return;
}
if cx.cells.contains_key(&var.uid()) {
return;
}
let Some(&(ptr, width, signed)) = cx.locals.get(&var.uid()) else {
return;
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let old =
cx.b.build_load(op.packed_type(width, cx.repr), ptr, "task_local_old")
.unwrap()
.into_int_value();
let pv = crate::value::PackedValue {
llvm: old,
width,
repr: cx.repr,
signed,
};
crate::collections::release_handles(cx, &t, crate::expr::LoweredValue::Packed(pv));
cx.b.build_store(ptr, op.packed_type(width, cx.repr).const_zero())
.unwrap();
}
fn local_init_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
sym: &Symbol<'d>,
width: u32,
signed: bool,
) -> Result<crate::value::PackedValue<'ctx>, LoweringError> {
match sym.initializer() {
Some(init)
if sym
.value_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t)) =>
{
Ok(PackedValue {
llvm: crate::collections::owned_arg(cx, init)?,
width,
repr: cx.repr,
signed,
})
}
Some(init) => {
let v = crate::real::to_storage(
cx,
crate::collections::owned(cx, init)?,
width,
"a >128-bit local initializer",
)?;
if v.width != width {
return Err(LoweringError::unsupported_at(
&format!(
"local initializer is {} bits for a {width}-bit variable",
v.width
),
sym,
));
}
Ok(v)
}
None => {
let ty = sym
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("local has no type", sym))?;
Ok(PackedValue {
llvm: crate::arrays::default_value(cx, &ty),
width,
repr: cx.repr,
signed,
})
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub(crate) enum SyntheticSlot {
Counter(RepeatCounterId),
Temp(u32),
}
fn lower_segment_bindings<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
bindings: &[sv_sim_sim::process::cfg::ArgBinding<'d>],
synthetic: &HashMap<SyntheticSlot, (PointerValue<'ctx>, u32, bool)>,
) -> Result<(), LoweringError> {
use sv_sim_sim::process::cfg::ArgBinding;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let local = |sym: &Symbol<'d>| {
cx.locals.get(&sym.uid()).copied().ok_or_else(|| {
LoweringError::unsupported_at("inlined task formal/local has no frame slot", sym)
})
};
let array_of = |sym: &Symbol<'d>| {
sym.value_type()
.as_ref()
.and_then(crate::arrays::array_shape)
};
let temp_slot = |temp: u32| {
synthetic
.get(&SyntheticSlot::Temp(temp))
.copied()
.ok_or_else(|| {
LoweringError::unsupported_at(
"intra-assignment temporary has no frame slot (internal invariant)",
&cx.fn_sym,
)
})
};
for b in bindings {
match b {
ArgBinding::In { formal, actual } => {
if crate::class_events::bind_formal(cx, formal, *actual)? {
continue;
}
if crate::events::is_pool_event_var(cx, formal) {
if cx.locals.contains_key(&formal.uid()) {
fresh_cell(cx, formal);
}
crate::events::bind_event_formal(cx, formal, *actual)?;
continue;
}
let (ptr, width, _) = local(formal)?;
fresh_cell(cx, formal);
if let Some(shape) = array_of(formal) {
crate::arrays::assign_array_into_fresh(
cx,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(ptr)),
&shape,
*actual,
)?;
continue;
}
if formal
.value_type()
.is_some_and(|t| sv_sim_sim::state::storage::is_collection(&t))
{
let h = crate::collections::owned_arg(cx, *actual)?;
cx.b.build_store(ptr, h).unwrap();
continue;
}
let v = crate::real::to_storage(
cx,
crate::collections::owned(cx, *actual)?,
width,
"a >128-bit task argument",
)?;
let v = crate::select::fit_width(&op, v, width);
cx.b.build_store(ptr, v.llvm).unwrap();
crate::expr::write_through_store(cx, ptr);
}
ArgBinding::Init { var } => init_local_var(cx, var)?,
ArgBinding::Release { var } => release_task_local(cx, var),
ArgBinding::ThisOf { var } => {
let (ptr, width, _) = local(var)?;
fresh_cell(cx, var);
let h = crate::classes::current_this_handle(cx)?;
crate::classes::retain_handle(cx, h);
let v = crate::collections::handle_value(cx, h);
let v = crate::select::fit_width(&op, v, width);
cx.b.build_store(ptr, v.llvm).unwrap();
}
ArgBinding::CaptureThis { temp } => {
let (ptr, width, _) = temp_slot(*temp)?;
let h = crate::classes::current_this_handle(cx)?;
let v = crate::collections::handle_value(cx, h);
let v = crate::select::fit_width(&op, v, width);
cx.b.build_store(ptr, v.llvm).unwrap();
}
ArgBinding::Const { var, value } => {
let (ptr, width, signed) = local(var)?;
fresh_cell(cx, var);
let v = op.pack(
op.i_n(width).const_int(*value as u64, true),
op.i_n(width).const_zero(),
width,
cx.repr,
signed,
);
cx.b.build_store(ptr, v.llvm).unwrap();
}
ArgBinding::Step { var, by } => {
let (ptr, width, signed) = local(var)?;
let llvm =
cx.b.build_load(op.packed_type(width, cx.repr), ptr, "foreach_i")
.unwrap()
.into_int_value();
let cur = crate::value::PackedValue {
llvm,
width,
repr: cx.repr,
signed,
};
let next =
cx.b.build_int_add(
op.lane_val(cur),
op.i_n(width).const_int(*by as u64, true),
"foreach_next",
)
.unwrap();
let v = op.pack(next, op.i_n(width).const_zero(), width, cx.repr, signed);
cx.b.build_store(ptr, v.llvm).unwrap();
}
ArgBinding::AssocKey {
var,
arr,
path,
next,
} => {
let (ptr, width, signed) = local(var)?;
let llvm =
cx.b.build_load(op.packed_type(width, cx.repr), ptr, "foreach_key")
.unwrap()
.into_int_value();
let cur = crate::value::PackedValue {
llvm,
width,
repr: cx.repr,
signed,
};
let k = crate::collections::assoc_step(cx, *arr, path, cur, *next)?;
let k = crate::select::fit_width(&op, k, width);
cx.b.build_store(ptr, k.llvm).unwrap();
}
ArgBinding::Capture { temp, value } => {
let (ptr, width, _) = temp_slot(*temp)?;
if let Some(shape) = value
.expr_type()
.filter(|t| t.is_unpacked_array())
.and_then(|t| crate::arrays::array_shape(&t))
.filter(|sh| !sh.dims.is_empty())
{
if !crate::collections::handle_offsets(&shape.elem).is_empty() {
return Err(LoweringError::unsupported_at(
"an intra-assignment-timed unpacked array whose elements hold \
class handles or collections (LRM 1800-2023 10.4.2)",
&cx.fn_sym,
));
}
crate::arrays::assign_array_into_fresh(
cx,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(ptr)),
&shape,
*value,
)?;
continue;
}
let v = crate::real::to_storage(
cx,
lower_expr(cx, *value)?,
width,
"an intra-assignment-timed value wider than 128 bits",
)?;
let v = crate::select::fit_width(&op, v, width);
cx.b.build_store(ptr, v.llvm).unwrap();
}
ArgBinding::AssignCaptured { temp, target, nba } => {
let (ptr, width, _) = temp_slot(*temp)?;
if let Some((dst, dst_shape)) = crate::arrays::array_target(cx, *target)? {
if *nba {
let delay = cx.ctx.i64_type().const_zero();
crate::arrays::nba_array_from_local(
cx, handle, dst, &dst_shape, ptr, &dst_shape, delay,
)?;
} else {
crate::arrays::copy_array(
cx,
dst,
&dst_shape,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(ptr)),
&dst_shape,
)?;
}
continue;
}
let llvm =
cx.b.build_load(op.packed_type(width, cx.repr), ptr, "captured")
.unwrap()
.into_int_value();
let signed = target.expr_type().is_some_and(|t| t.is_signed());
let v = crate::value::PackedValue {
llvm,
width,
repr: cx.repr,
signed,
};
if *nba {
if crate::huge::rooted_in_huge(*target)
|| crate::arrays::array_target(cx, *target)?.is_some()
{
return Err(LoweringError::unsupported_at(
"a nonblocking assignment with an event control to a memory, \
unpacked array or >128-bit vector (LRM 1800-2023 10.4.2)",
&cx.fn_sym,
));
}
let drive = match clock_var_root(*target) {
Some(root) => clocking_output(cx, signals, root)?,
None => None,
};
let (target, delay) = match &drive {
Some(d) if sv_sim_sim::netlist::vif::is_member_access(target) => {
(*target, drive_delay(cx, d))
}
Some(d) if !is_clock_var(*target) => (*target, drive_delay(cx, d)),
Some(d) => (d.target, drive_delay(cx, d)),
None => (*target, cx.ctx.i64_type().const_zero()),
};
if let Some(d) = &drive {
emit_reactive_drive(cx, handle, signals, d, true)?;
}
crate::select::lower_nba_to(cx, handle, signals, target, v, delay)?;
if let Some(d) = &drive {
emit_reactive_drive(cx, handle, signals, d, false)?;
}
} else {
crate::select::lower_assign_to(cx, *target, v)?;
}
}
ArgBinding::FromCaptured { temp, formal } => {
let (tptr, twidth, _) = temp_slot(*temp)?;
let (ptr, width, signed) = local(formal)?;
fresh_cell(cx, formal);
let llvm =
cx.b.build_load(op.packed_type(twidth, cx.repr), tptr, "task_arg")
.unwrap()
.into_int_value();
let v = crate::value::PackedValue {
llvm,
width: twidth,
repr: cx.repr,
signed,
};
let v = crate::select::fit_width(&op, v, width);
cx.b.build_store(ptr, v.llvm).unwrap();
crate::expr::write_through_store(cx, ptr);
}
ArgBinding::CallResult { .. } => {}
ArgBinding::ClassEventLeave => crate::class_events::lower_leave(cx),
ArgBinding::ToCaptured { temp, formal } => {
let (tptr, twidth, _) = temp_slot(*temp)?;
let (ptr, width, signed) = local(formal)?;
let llvm =
cx.b.build_load(op.packed_type(width, cx.repr), ptr, "task_result")
.unwrap()
.into_int_value();
let v = crate::value::PackedValue {
llvm,
width,
repr: cx.repr,
signed,
};
let v = crate::select::fit_width(&op, v, twidth);
cx.b.build_store(tptr, v.llvm).unwrap();
}
ArgBinding::RefOpen {
temp,
formal,
actual,
} => {
let (ptr, _, _) = local(formal)?;
let (tptr, twidth, _) = temp_slot(*temp)?;
fresh_cell(cx, formal);
let id = crate::collections::open_ref(cx, *actual)?;
let idv = op.pack(id, op.i_n(64).const_zero(), 64, cx.repr, false);
let idv = crate::select::fit_width(&op, idv, twidth);
cx.b.build_store(tptr, idv.llvm).unwrap();
crate::collections::elem_ref_refresh(cx, ptr);
}
ArgBinding::RefClose { temp, .. } => {
let (tptr, twidth, _) = temp_slot(*temp)?;
crate::collections::close_ref(cx, tptr, twidth);
}
ArgBinding::Out { actual, formal } => {
if crate::events::copy_out_formal(cx, formal, *actual)? {
continue;
}
let (ptr, width, signed) = local(formal)?;
if let Some(shape) = array_of(formal) {
let (dst, dst_shape) =
crate::arrays::array_target(cx, *actual)?.ok_or_else(|| {
LoweringError::unsupported_at(
"an array output argument whose actual is not an array variable",
&cx.fn_sym,
)
})?;
crate::arrays::copy_array(
cx,
dst,
&dst_shape,
crate::arrays::ArrayRef::whole(cx, crate::arrays::Backing::Local(ptr)),
&shape,
)?;
continue;
}
let llvm =
cx.b.build_load(op.packed_type(width, cx.repr), ptr, "task_out")
.unwrap()
.into_int_value();
let v = crate::value::PackedValue {
llvm,
width,
repr: cx.repr,
signed,
};
let target_width = actual
.expr_type()
.map(|t| t.value_width())
.filter(|w| *w > 0)
.unwrap_or(width);
let v = crate::select::fit_width(&op, v, target_width);
crate::select::lower_assign_to(cx, *actual, v)?;
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn lower_fast_path<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
f: FunctionValue<'ctx>,
value_repr: ValueRepr,
) -> Result<(), LoweringError> {
let seg = &plan.segments[0];
let entry_bb = ctx.append_basic_block(f, "entry");
let b = ctx.create_builder();
b.position_at_end(entry_bb);
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let mut cx = LowerCtx {
ctx,
b,
repr: value_repr,
locals: HashMap::new(),
fn_sym: plan.fn_sym,
module,
mem_signals: mem_signals_of(&plan.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: plan.specify_dest_redirect.clone(),
elem_refs: Default::default(),
};
reject_huge_frame_slots(plan)?;
for (idx, slot) in plan.frame.slots.iter().enumerate() {
let id = match &slot.source {
FrameSlotSource::DeclaredVar(id) => id.clone(),
FrameSlotSource::Temporary(_) => continue,
FrameSlotSource::SyntheticCounter(counter) => {
return Err(LoweringError::unsupported_at(
&format!(
"internal invariant violated: a segment_count==1 process carries a \
repeat-loop synthetic counter slot ({counter:?}) -- a suspend-\
containing repeat loop always splits into more than one segment"
),
&plan.fn_sym,
));
}
};
if slot.cell {
let local = f.get_nth_param(1).unwrap().into_pointer_value();
let (shadow, cell_ptr, bytes) = open_cell(cx.ctx, module, &cx.b, local, slot);
cx.locals.insert(
id.clone(),
(shadow, slot.width_bits, plan.frame_signed[idx]),
);
cx.cells.insert(id, (cell_ptr, bytes));
continue;
}
let ptr =
crate::types::entry_alloca_in(&cx.b, slot_storage_type(cx.ctx, slot), "decl_local");
cx.locals
.insert(id, (ptr, slot.width_bits, plan.frame_signed[idx]));
}
let calls = plan.assertion_calls.borrow();
let call_exprs: Vec<Expression<'d>> = calls.iter().map(|c| c.call).collect();
let written = prime_external_signals(
&mut cx,
fns,
handle,
seg,
&plan.signals,
plan.region_id,
None,
&call_exprs,
);
cx.write_through = Some(write_through_for(&cx, fns, handle, plan, &plan.signals));
emit_local_inits(&cx, plan)?;
cx.inline_this = seg.this_var;
lower_segment_bindings(&cx, handle, &plan.signals, &seg.bindings, &HashMap::new())?;
lower_segment_body(&mut cx, handle, &plan.signals, nba_fn, sys_fns, seg, &calls)?;
for step in &seg.step_exprs {
lower_step_expr(&mut cx, *step)?;
}
flush_external_signals(&cx, fns, handle, &written, &plan.signals, plan.region_id);
flush_cells(&cx);
match &seg.terminator {
SegTerminator::End { loop_back: None } => {
let ret = cx
.ctx
.i32_type()
.const_int(SEG_FINISHED as u32 as u64, false);
cx.b.build_return(Some(&ret)).unwrap();
Ok(())
}
other => Err(LoweringError::unsupported_at(
&format!(
"a segment_count==1 process's only segment terminated with {other:?}, not \
End{{loop_back: None}} -- a single segment can only arise from a suspend-free \
process body, which process::cfg::build_cfg always closes with a plain End \
(internal invariant)"
),
&plan.fn_sym,
)),
}
}
#[allow(clippy::too_many_lines)]
#[allow(clippy::too_many_arguments)]
fn lower_switch_path<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
nba_fn: FunctionValue<'ctx>,
sys_fns: &SysCallFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
f: FunctionValue<'ctx>,
value_repr: ValueRepr,
) -> Result<(), LoweringError> {
let entry_bb = ctx.append_basic_block(f, "entry");
let eb = ctx.create_builder();
eb.position_at_end(entry_bb);
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let local = f.get_nth_param(1).unwrap().into_pointer_value();
let resume_pc = f.get_nth_param(2).unwrap().into_int_value();
let mut frame_locals: HashMap<SymbolId, (PointerValue<'ctx>, u32, bool)> = HashMap::new();
let mut counter_locals: HashMap<SyntheticSlot, (PointerValue<'ctx>, u32, bool)> =
HashMap::new();
let mut cells: HashMap<SymbolId, (PointerValue<'ctx>, u32)> = HashMap::new();
reject_huge_frame_slots(plan)?;
for (idx, slot) in plan.frame.slots.iter().enumerate() {
if slot.cell
&& let FrameSlotSource::DeclaredVar(id) = &slot.source
{
let (shadow, cell_ptr, bytes) = open_cell(ctx, module, &eb, local, slot);
frame_locals.insert(
id.clone(),
(shadow, slot.width_bits, plan.frame_signed[idx]),
);
cells.insert(id.clone(), (cell_ptr, bytes));
continue;
}
let src = frame_slot_ptr(ctx, &eb, local, slot.offset);
let ptr = crate::types::entry_alloca_in(&eb, slot_storage_type(ctx, slot), "decl_local");
copy_frame_slot(ctx, &eb, slot, ptr, src);
match &slot.source {
FrameSlotSource::DeclaredVar(id) => {
frame_locals.insert(id.clone(), (ptr, slot.width_bits, plan.frame_signed[idx]));
}
FrameSlotSource::SyntheticCounter(counter) => {
counter_locals.insert(
SyntheticSlot::Counter(*counter),
(ptr, slot.width_bits, plan.frame_signed[idx]),
);
}
FrameSlotSource::Temporary(temp) => {
counter_locals.insert(
SyntheticSlot::Temp(*temp),
(ptr, slot.width_bits, plan.frame_signed[idx]),
);
}
};
}
let mut call_result_locals: Vec<(SymbolId, (PointerValue<'ctx>, u32, bool))> = Vec::new();
for seg in &plan.segments {
for b in &seg.bindings {
if let sv_sim_sim::process::cfg::ArgBinding::CallResult { temp, call } = b
&& let Some(&(ptr, width, _)) = counter_locals.get(&SyntheticSlot::Temp(*temp))
{
let signed = call.expr_type().is_some_and(|t| t.is_signed());
call_result_locals.push((
sv_sim_sim::process::cfg::call_result_key(call),
(ptr, width, signed),
));
}
}
}
let mut elem_refs: HashMap<PointerValue<'ctx>, crate::collections::ElemRefSlot<'ctx>> =
HashMap::new();
for seg in &plan.segments {
for b in &seg.bindings {
if let sv_sim_sim::process::cfg::ArgBinding::RefOpen { temp, formal, .. } = b
&& let Some(&(ptr, width, signed)) = frame_locals.get(&formal.uid())
&& let Some(&(id, id_width, _)) = counter_locals.get(&SyntheticSlot::Temp(*temp))
{
elem_refs.insert(
ptr,
crate::collections::ElemRefSlot {
id,
id_width,
width,
signed,
part: None,
},
);
}
}
}
{
let mut vars: Vec<(SymbolId, PointerValue<'ctx>)> = frame_locals
.iter()
.map(|(id, &(p, _, _))| (id.clone(), p))
.collect();
vars.sort_by(|a, b| a.0.cmp(&b.0));
crate::vpi_dyn::emit_frame_rebind(ctx, module, &eb, &vars);
}
let calls = plan.assertion_calls.borrow();
let call_exprs: Vec<Expression<'d>> = calls.iter().map(|c| c.call).collect();
let all_signals = all_seg_signals(&plan.signals, &plan.segments)?;
let mut ext_symbols: Vec<SymbolId> = external_symbols_in_plan(
&plan.segments,
&plan.signals,
&plan.fn_sym.uid(),
&call_exprs,
)?
.into_iter()
.collect();
ext_symbols.sort();
let ext_ptrs: HashMap<SymbolId, PointerValue<'ctx>> = ext_symbols
.into_iter()
.filter_map(|sym_id| {
let info = all_signals[&sym_id];
if crate::huge::is_huge_width(info.width) {
return None;
}
let packed_ty = int_type(ctx, info.width * 2);
let ptr = crate::types::entry_alloca_in(&eb, packed_ty, "ext_shadow");
Some((sym_id, ptr))
})
.collect();
let seg_blocks: Vec<LlvmBasicBlock<'ctx>> = (0..plan.segments.len())
.map(|i| ctx.append_basic_block(f, &format!("seg{i}")))
.collect();
let trap_bb = ctx.append_basic_block(f, "resume_pc_trap");
let i32_ty = ctx.i32_type();
let start_bb = ctx.append_basic_block(f, "process_start");
let cases: Vec<(IntValue<'ctx>, LlvmBasicBlock<'ctx>)> = (0..plan.segments.len())
.map(|i| {
let target = if i == 0 { start_bb } else { seg_blocks[i] };
(i32_ty.const_int(i as u64, false), target)
})
.collect();
eb.build_switch(resume_pc, trap_bb, &cases).unwrap();
eb.position_at_end(trap_bb);
eb.build_unreachable().unwrap();
let resume_guards = build_resume_guards(ctx, module, fns, plan, value_repr)?;
let jump_blocks: Vec<LlvmBasicBlock<'ctx>> = (0..plan.segments.len())
.map(|i| {
if resume_guards.contains_key(&(i as u32)) {
ctx.append_basic_block(f, &format!("seg{i}_guard_ok"))
} else {
seg_blocks[i]
}
})
.collect();
let mem_signals = mem_signals_of(&plan.signals);
{
let ib = ctx.create_builder();
ib.position_at_end(start_bb);
let icx = LowerCtx {
ctx,
b: ib,
repr: value_repr,
locals: frame_locals.clone(),
fn_sym: plan.fn_sym,
module,
mem_signals: mem_signals.clone(),
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: cells.clone(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: Default::default(),
end_flags: Default::default(),
specify_dest: HashMap::new(),
elem_refs: Default::default(),
};
emit_local_inits(&icx, plan)?;
icx.b.build_unconditional_branch(seg_blocks[0]).unwrap();
}
for (i, seg) in plan.segments.iter().enumerate() {
let sb = ctx.create_builder();
sb.position_at_end(seg_blocks[i]);
let signals = seg_signals(&plan.signals, seg)?;
let mut cx = LowerCtx {
ctx,
b: sb,
repr: value_repr,
locals: {
let mut l = frame_locals.clone();
l.extend(call_result_locals.iter().cloned());
l
},
fn_sym: plan.fn_sym,
module,
mem_signals: if seg.ref_aliases.is_empty() {
mem_signals.clone()
} else {
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: cells.clone(),
copy_out: Default::default(),
ctor_this: Default::default(),
decl_scale: Default::default(),
inline_this: seg.this_var,
end_flags: Default::default(),
specify_dest: plan.specify_dest_redirect.clone(),
elem_refs: elem_refs.clone(),
};
if let Some(guard) = resume_guards.get(&(i as u32)) {
let call =
cx.b.build_call(guard.guard_fn, &[handle.into(), local.into()], "guard_call")
.unwrap();
let ret_i8 = call.try_as_basic_value().unwrap_basic().into_int_value();
let zero = cx.ctx.i8_type().const_int(0, false);
let passed =
cx.b.build_int_compare(inkwell::IntPredicate::NE, ret_i8, zero, "guard_passed")
.unwrap();
let guard_ok_bb = jump_blocks[i];
let guard_resuspend_bb = ctx.append_basic_block(f, &format!("seg{i}_guard_resuspend"));
cx.b.build_conditional_branch(passed, guard_ok_bb, guard_resuspend_bb)
.unwrap();
cx.b.position_at_end(guard_resuspend_bb);
let key = if guard.vif_vars.is_empty() {
None
} else {
for signal in &guard.vif_vars {
let base = crate::vif::event_base(signal).expect("a vif event");
let mut named = Vec::new();
collect_named_value_symbols(base.as_sem_node(), &mut named);
for sym in named {
if cx.locals.contains_key(&sym.uid()) {
continue;
}
let Some(info) = signals.get(&sym.uid()) else {
continue;
};
let raw = build_get_scalar(&cx, fns, handle, info.slot, info.width);
let ptr = crate::types::entry_alloca_in(
&cx.b,
int_type(ctx, info.width * 2),
"vif_guard_shadow",
);
cx.b.build_store(ptr, raw).unwrap();
cx.locals.insert(sym.uid(), (ptr, info.width, info.signed));
}
}
Some(vif_vars_key(&cx, &guard.vif_vars, i as u32)?)
};
write_live_out_and_return(
&cx,
local,
&plan.frame,
&frame_locals,
&counter_locals,
&guard.live_out,
i as u32,
key,
)?;
cx.b.position_at_end(guard_ok_bb);
}
let written = prime_external_signals(
&mut cx,
fns,
handle,
seg,
&signals,
plan.region_id,
Some(&ext_ptrs),
&call_exprs,
);
cx.write_through = Some(write_through_for(&cx, fns, handle, plan, &signals));
cx.inline_this = seg.this_var;
lower_segment_bindings(&cx, handle, &signals, &seg.bindings, &counter_locals)?;
lower_segment_body(&mut cx, handle, &signals, nba_fn, sys_fns, seg, &calls)?;
for step in &seg.step_exprs {
lower_step_expr(&mut cx, *step)?;
}
if let Some((counter, count)) = &seg.repeat_init {
let (ptr, width, _signed) = *counter_locals
.get(&SyntheticSlot::Counter(*counter))
.ok_or_else(|| {
LoweringError::unsupported_at(
"repeat_init's counter has no matching alloca (internal invariant)",
&plan.fn_sym,
)
})?;
match count {
sv_sim_sim::process::cfg::RepeatCount::Expr(count_expr) => {
lower_repeat_counter_init(&cx, *count_expr, ptr, width)?
}
sv_sim_sim::process::cfg::RepeatCount::Const(n) => {
let op = OpBuilder { ctx, b: &cx.b };
store_repeat_counter(&cx, &op, ptr, width, op.i_n(width).const_int(*n, false));
}
sv_sim_sim::process::cfg::RepeatCount::Size(arr) => {
let (size, _) = crate::collections::size_and_handle(&cx, *arr)?;
let op = OpBuilder { ctx, b: &cx.b };
let size =
cx.b.build_int_truncate_or_bit_cast(size, op.i_n(width), "foreach_size")
.unwrap();
store_repeat_counter(&cx, &op, ptr, width, size);
}
sv_sim_sim::process::cfg::RepeatCount::NestedSize { arr, path } => {
let size = crate::collections::nested_size(&cx, *arr, path)?;
let op = OpBuilder { ctx, b: &cx.b };
let size =
cx.b.build_int_truncate_or_bit_cast(size, op.i_n(width), "foreach_size")
.unwrap();
store_repeat_counter(&cx, &op, ptr, width, size);
}
sv_sim_sim::process::cfg::RepeatCount::Inherited(_) => {}
}
}
if let Some(counter) = &seg.repeat_decrement {
let (ptr, width, _signed) = *counter_locals
.get(&SyntheticSlot::Counter(*counter))
.ok_or_else(|| {
LoweringError::unsupported_at(
"repeat_decrement's counter has no matching alloca (internal invariant)",
&plan.fn_sym,
)
})?;
let op = OpBuilder { ctx, b: &cx.b };
let cur_val = load_repeat_counter(&cx, &op, ptr, width);
let one = op.i_n(width).const_int(1, false);
let dec = cx.b.build_int_sub(cur_val, one, "repeat_dec").unwrap();
store_repeat_counter(&cx, &op, ptr, width, dec);
}
flush_external_signals(&cx, fns, handle, &written, &signals, plan.region_id);
match &seg.terminator {
SegTerminator::Goto(target) => {
cx.b.build_unconditional_branch(jump_blocks[target.0 as usize])
.unwrap();
}
SegTerminator::SwitchGoto {
on,
arms,
default,
report,
} => {
let i64t = ctx.i64_type();
let idx = match on {
SwitchOn::FiredTerm => {
let rt =
module
.get_function(SV_RT_FIRED_TERM_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_FIRED_TERM_SYMBOL,
i64t.fn_type(&[], false),
None,
)
});
cx.b.build_call(rt, &[], "fired_term")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
SwitchOn::VifId(e) => {
let pv = crate::expr::lower_expr(&cx, *e)?
.into_packed("a virtual interface", &plan.fn_sym)?;
let op = OpBuilder { ctx, b: &cx.b };
let v =
cx.b.build_int_z_extend_or_bit_cast(op.lane_val(pv), i64t, "vif_id")
.unwrap();
cx.b.build_int_sub(v, i64t.const_int(1, false), "vif_arm")
.unwrap()
}
SwitchOn::ObjClass { receiver, ids } => {
let h = match receiver {
Some(r) => crate::classes::receiver_handle(&cx, *r)?,
None => crate::classes::current_this_handle(&cx)?,
};
let dyn_id = crate::classes::dynamic_template_call(&cx, h);
let mut idx = i64t.const_all_ones();
for (k, set) in ids.iter().enumerate() {
for id in set {
let hit =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
dyn_id,
i64t.const_int(u64::from(*id), false),
"obj_class_hit",
)
.unwrap();
idx =
cx.b.build_select(
hit,
i64t.const_int(k as u64, false),
idx,
"obj_class_arm",
)
.unwrap()
.into_int_value();
}
}
idx
}
SwitchOn::RandCase(stmt) => {
let weights: Vec<Expression<'_>> =
stmt.randcase_items().iter().map(|i| i.expr()).collect();
crate::stmt_shared::rand_pick(
&cx,
&weights,
Some(stmt.source_range().start),
)?
}
SwitchOn::NoTarget { .. } => i64t.const_all_ones(),
SwitchOn::FnDisable { labels } => {
let rt = module
.get_function(crate::dpi::SV_RT_DPI_FN_DISABLE_INDEX_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
crate::dpi::SV_RT_DPI_FN_DISABLE_INDEX_SYMBOL,
i64t.fn_type(
&[ctx.ptr_type(inkwell::AddressSpace::default()).into()],
false,
),
None,
)
});
let names =
cx.b.build_global_string_ptr(&labels.join("\n"), "fn_disable_labels")
.unwrap()
.as_pointer_value();
cx.b.build_call(rt, &[names.into()], "fn_disable_idx")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
}
};
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
for (k, arm) in arms.iter().enumerate() {
let hit =
cx.b.build_int_compare(
inkwell::IntPredicate::EQ,
idx,
i64t.const_int(k as u64, false),
"switch_hit",
)
.unwrap();
let next = ctx.append_basic_block(func, "switch_next");
cx.b.build_conditional_branch(hit, jump_blocks[arm.0 as usize], next)
.unwrap();
cx.b.position_at_end(next);
}
if let Some(at) = report {
let scope = sv_sim_sim::process::cfg::shown_path(&plan.fn_sym);
severity_frame(&cx, sys_fns, "ERROR: ", *at, &scope, &mut || {
let SwitchOn::NoTarget { what } = on else {
return Ok(());
};
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let fmt = crate::systask::build_c_string_global(
cx.ctx,
cx.module,
&what.replace('%', "%%"),
"no_target_msg",
);
let (args_ptr, n) =
crate::systask::build_rtarg_array(&cx, rtarg_ty, Vec::new())?;
cx.b.build_call(
sys_fns.write,
&[fmt.into(), args_ptr.into(), n.into()],
"rt_no_target",
)
.unwrap();
Ok(())
})?;
}
cx.b.build_unconditional_branch(jump_blocks[default.0 as usize])
.unwrap();
}
SegTerminator::CondGoto {
cond,
t,
f: else_seg,
} => {
let bit = lower_condition(&cx, *cond)?;
cx.b.build_conditional_branch(
bit,
jump_blocks[t.0 as usize],
jump_blocks[else_seg.0 as usize],
)
.unwrap();
}
SegTerminator::AssertGoto {
stmt,
pass,
fail,
done,
} => {
let has_pass = stmt.immediate_assertion_if_true().is_some();
let mut seen = 0;
lower_immediate_assertion(&mut cx, sys_fns, stmt, None, &mut |cx, _| {
let is_pass = has_pass && seen == 0;
seen += 1;
let target = if is_pass { pass } else { fail };
cx.b.build_unconditional_branch(jump_blocks[target.0 as usize])
.unwrap();
Ok(())
})?;
cx.b.build_unconditional_branch(jump_blocks[done.0 as usize])
.unwrap();
}
SegTerminator::CaseGoto {
stmt,
arms,
default,
} if sv_sim_sim::process::cfg::violation_check(stmt).is_some() => {
let arm_blocks: Vec<_> = arms.iter().map(|a| jump_blocks[a.0 as usize]).collect();
crate::stmt_shared::lower_checked_dispatch(
&cx,
*stmt,
&arm_blocks,
jump_blocks[default.0 as usize],
)?;
}
SegTerminator::CaseGoto {
stmt,
arms,
default,
} if matches!(
stmt.kind(),
StatementKind::Conditional | StatementKind::PatternCase
) =>
{
let arm_blocks: Vec<_> = arms.iter().map(|a| jump_blocks[a.0 as usize]).collect();
crate::stmt_shared::lower_clause_dispatch(
&cx,
*stmt,
&arm_blocks,
jump_blocks[default.0 as usize],
)?;
}
SegTerminator::CaseGoto {
stmt,
arms,
default,
} => {
let (cond_kind, sel) = crate::stmt_shared::case_selector(&cx, *stmt)?;
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
for (item, arm) in stmt.case_items().iter().zip(arms) {
let m = crate::stmt_shared::case_item_matches(&cx, cond_kind, sel, item)?;
let next = ctx.append_basic_block(func, "case_next");
cx.b.build_conditional_branch(m, jump_blocks[arm.0 as usize], next)
.unwrap();
cx.b.position_at_end(next);
}
cx.b.build_unconditional_branch(jump_blocks[default.0 as usize])
.unwrap();
}
SegTerminator::CondGotoCounter { counter, t, f } => {
let (ptr, width, _signed) = *counter_locals
.get(&SyntheticSlot::Counter(*counter))
.ok_or_else(|| {
LoweringError::unsupported_at(
"CondGotoCounter's counter has no matching alloca (internal invariant)",
&plan.fn_sym,
)
})?;
let op = OpBuilder { ctx, b: &cx.b };
let cur_val = load_repeat_counter(&cx, &op, ptr, width);
let zero = op.i_n(width).const_zero();
let cont =
cx.b.build_int_compare(inkwell::IntPredicate::NE, cur_val, zero, "repeat_cont")
.unwrap();
cx.b.build_conditional_branch(
cont,
jump_blocks[t.0 as usize],
jump_blocks[f.0 as usize],
)
.unwrap();
}
SegTerminator::Suspend {
resume,
live_out,
point,
..
} => {
if let SuspendPoint::Delay(expr, scope) = point
&& !crate::timescale::delay_is_static(expr)
{
let ticks = crate::timescale::lower_delay_ticks_in(&cx, *expr, scope.as_ref())?;
let name = SV_RT_SET_DELAY_SYMBOL;
let f = module.get_function(name).unwrap_or_else(|| {
let ty = ctx.void_type().fn_type(&[ctx.i64_type().into()], false);
module.add_function(name, ty, None)
});
cx.b.build_call(f, &[ticks.into()], "set_delay").unwrap();
}
for t in local_event_terms(point, &signals) {
let Some(&(cell_ptr, bytes)) = cx.cells.get(&t.var.uid()) else {
continue;
};
let _ = cell_ptr;
let (shadow, _, _) = cx.locals[&t.var.uid()];
edge_snapshot_call(
&cx,
local,
local_event_site(resume.0, t.index),
shadow,
bytes,
);
}
for t in event_elem_terms(point, &signals) {
event_elem_snapshot(&cx, local, event_elem_site(resume.0, t.index), t.expr)?;
}
for t in expr_event_terms(point, &signals) {
let (image, width) = event_value_image(&cx, t.expr)?;
let words = event_value_words(width);
let i64t = ctx.i64_type();
let buf =
crate::types::entry_alloca_in(&cx.b, i64t.array_type(words), "ev_value");
cx.b.build_store(buf, image).unwrap();
edge_snapshot_call(
&cx,
local,
local_event_site(resume.0, t.index),
buf,
words * 8,
);
}
let key = match vif_event(point) {
Some(_) => Some(vif_point_key(&cx, point, resume.0)?),
None => None,
};
write_live_out_and_return(
&cx,
local,
&plan.frame,
&frame_locals,
&counter_locals,
live_out,
resume.0,
key,
)?;
}
SegTerminator::End { loop_back: None } if plan.write_back_at_end => {
let all: Vec<u32> = (0..plan.frame.slots.len() as u32).collect();
write_live_out_and_return(
&cx,
local,
&plan.frame,
&frame_locals,
&counter_locals,
&all,
SEG_FINISHED as u32,
None,
)?;
}
SegTerminator::End { loop_back: None } => {
flush_cells(&cx);
let ret = cx
.ctx
.i32_type()
.const_int(SEG_FINISHED as u32 as u64, false);
cx.b.build_return(Some(&ret)).unwrap();
}
SegTerminator::End {
loop_back: Some(target),
} => {
cx.b.build_unconditional_branch(jump_blocks[target.0 as usize])
.unwrap();
}
SegTerminator::ForkJoin {
join_blk, live_out, ..
} => {
write_live_out_and_return(
&cx,
local,
&plan.frame,
&frame_locals,
&counter_locals,
live_out,
join_blk.0,
None,
)?;
}
}
}
Ok(())
}
pub fn lower_process_cfg<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
lower_process_cfg_with_repr(ctx, module, fns, plan, ValueRepr::FourState)
}
pub fn lower_process_cfg_with_repr<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
value_repr: ValueRepr,
) -> Result<FunctionValue<'ctx>, LoweringError> {
if plan.segments.is_empty() {
return Err(LoweringError::unsupported_at(
"a ProcessLoweringPlan with zero segments (malformed plan -- process::cfg::\
build_cfg always produces at least one)",
&plan.fn_sym,
));
}
let f = declare_process_fn(ctx, module, &plan.fn_name);
let _assertions = AssertionLowering::enter(plan);
let _inferred_disable =
crate::sva::emit::InferredDisableGuard::enter_plan(plan.inferred_disable.clone());
let nba_fn = module
.get_function(sv_sim_abi::process::NBA_ENQUEUE_SYMBOL)
.unwrap_or_else(|| declare_nba_enqueue(ctx, module));
let sys_fns = fetch_or_declare_sys_call_fns(ctx, module);
if plan.segments.len() == 1 {
lower_fast_path(ctx, module, fns, nba_fn, &sys_fns, plan, f, value_repr)?;
} else {
lower_switch_path(ctx, module, fns, nba_fn, &sys_fns, plan, f, value_repr)?;
}
if f.verify(true) {
Ok(f)
} else {
Err(LoweringError {
message: "process kernel function failed LLVM verify".into(),
at: plan.fn_sym.hierarchical_path(),
})
}
}
#[allow(clippy::too_many_arguments)]
fn write_live_out_and_return<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
local: PointerValue<'ctx>,
frame: &FrameLayout,
frame_locals: &HashMap<SymbolId, (PointerValue<'ctx>, u32, bool)>,
counter_locals: &HashMap<SyntheticSlot, (PointerValue<'ctx>, u32, bool)>,
live_out: &[u32],
ret_val: u32,
key: Option<IntValue<'ctx>>,
) -> Result<(), LoweringError> {
flush_cells(cx);
let all: Vec<u32>;
let live_out = if crate::vpi_dyn::enabled() {
all = (0..frame.slots.len() as u32)
.filter(|&i| {
live_out.contains(&i)
|| matches!(
frame.slots[i as usize].source,
FrameSlotSource::DeclaredVar(_)
)
})
.collect();
&all[..]
} else {
live_out
};
for idx in live_out {
let slot = &frame.slots[*idx as usize];
if slot.cell
&& let FrameSlotSource::DeclaredVar(id) = &slot.source
&& let Some(&(cell_ptr, _)) = cx.cells.get(id)
{
let p =
cx.b.build_load(cx.ctx.i64_type(), cell_ptr, "cell_ptr")
.unwrap();
let dst = frame_slot_ptr(cx.ctx, &cx.b, local, slot.offset);
cx.b.build_store(dst, p).unwrap();
continue;
}
let (ptr, width, _signed) = match &slot.source {
FrameSlotSource::DeclaredVar(id) => *frame_locals.get(id).ok_or_else(|| {
LoweringError::unsupported_at(
"live_out slot has no matching declared-local alloca (internal invariant)",
&cx.fn_sym,
)
})?,
FrameSlotSource::SyntheticCounter(counter) => *counter_locals
.get(&SyntheticSlot::Counter(*counter))
.ok_or_else(|| {
LoweringError::unsupported_at(
"live_out slot has no matching repeat-counter alloca (internal \
invariant)",
&cx.fn_sym,
)
})?,
FrameSlotSource::Temporary(temp) => *counter_locals
.get(&SyntheticSlot::Temp(*temp))
.ok_or_else(|| {
LoweringError::unsupported_at(
"live_out slot has no matching temporary alloca (internal invariant)",
&cx.fn_sym,
)
})?,
};
let _ = width;
let dst = frame_slot_ptr(cx.ctx, &cx.b, local, slot.offset);
copy_frame_slot(cx.ctx, &cx.b, slot, dst, ptr);
}
if crate::vpi_dyn::enabled() {
let mut vars: Vec<(SymbolId, PointerValue<'ctx>)> = frame
.slots
.iter()
.filter(|slot| !slot.cell)
.filter_map(|slot| match &slot.source {
FrameSlotSource::DeclaredVar(id) => Some((
id.clone(),
frame_slot_ptr(cx.ctx, &cx.b, local, slot.offset),
)),
_ => None,
})
.collect();
vars.sort_by(|a, b| a.0.cmp(&b.0));
crate::vpi_dyn::emit_frame_rebind(cx.ctx, cx.module, &cx.b, &vars);
}
let ret = key.unwrap_or_else(|| cx.ctx.i32_type().const_int(u64::from(ret_val), false));
cx.b.build_return(Some(&ret)).unwrap();
Ok(())
}
fn load_repeat_counter<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
ptr: PointerValue<'ctx>,
width: u32,
) -> IntValue<'ctx> {
let raw_ty = if cx.repr == ValueRepr::FourState {
op.i_n(width * 2)
} else {
op.i_n(width)
};
let raw =
cx.b.build_load(raw_ty, ptr, "repeat_counter_ld")
.unwrap()
.into_int_value();
let pv = PackedValue {
llvm: raw,
width,
repr: cx.repr,
signed: false,
};
op.lane_val(pv)
}
fn store_repeat_counter<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
ptr: PointerValue<'ctx>,
width: u32,
val: IntValue<'ctx>,
) {
let packed = op.pack(val, op.i_n(width).const_zero(), width, cx.repr, false);
cx.b.build_store(ptr, packed.llvm).unwrap();
}
fn lower_repeat_counter_init<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
count_expr: Expression<'d>,
ptr: PointerValue<'ctx>,
width: u32,
) -> Result<(), LoweringError> {
let count_pv = match lower_expr(cx, count_expr)? {
crate::expr::LoweredValue::Real(rv) => crate::real::real_to_count(cx, rv),
other => other.into_packed(
"repeat count must be an integral (non-real) value",
&cx.fn_sym,
)?,
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val = op.lane_val(count_pv);
let unk = op.lane_unk(count_pv);
let zero_c = op.i_n(count_pv.width).const_zero();
let unk_zero =
cx.b.build_int_compare(inkwell::IntPredicate::EQ, unk, zero_c, "repeat_unk_zero")
.unwrap();
let n0 =
cx.b.build_select(unk_zero, val, zero_c, "repeat_n0")
.unwrap()
.into_int_value();
let n = if count_pv.signed {
let is_neg =
cx.b.build_int_compare(inkwell::IntPredicate::SLT, n0, zero_c, "repeat_is_neg")
.unwrap();
cx.b.build_select(is_neg, zero_c, n0, "repeat_n")
.unwrap()
.into_int_value()
} else {
n0
};
store_repeat_counter(cx, &op, ptr, width, n);
Ok(())
}
struct ResumeGuard<'ctx, 'd> {
guard_fn: FunctionValue<'ctx>,
live_out: Vec<u32>,
vif_vars: Vec<Expression<'d>>,
}
fn build_resume_guards<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
value_repr: ValueRepr,
) -> Result<HashMap<u32, ResumeGuard<'ctx, 'd>>, LoweringError> {
let mut guards = HashMap::new();
for seg in &plan.segments {
let SegTerminator::Suspend {
point,
resume,
live_out,
} = &seg.terminator
else {
continue;
};
let signals = seg_signals(&plan.signals, seg)?;
if !local_event_terms(point, &signals).is_empty()
|| !event_elem_terms(point, &signals).is_empty()
|| !expr_event_terms(point, &signals).is_empty()
|| matches!(point, SuspendPoint::EdgeList(_, iffs) if iffs.iter().any(|g| !g.is_empty()))
{
let fn_name = format!("{}__guard_seg{}", plan.fn_name, resume.0);
let guard_fn = local_event_guard(
ctx,
module,
fns,
plan,
&signals,
point,
resume.0,
&fn_name,
value_repr,
seg.this_var,
)?;
guards.insert(
resume.0,
ResumeGuard {
guard_fn,
live_out: live_out.clone(),
vif_vars: vif_event_vars(point),
},
);
continue;
}
let guard_expr = match point {
SuspendPoint::Level(expr) => Some(*expr),
SuspendPoint::Edge {
iff: Some(iff_expr),
..
} => Some(*iff_expr),
_ => None,
};
let Some(guard_expr) = guard_expr else {
continue;
};
let fn_name = format!("{}__guard_seg{}", plan.fn_name, resume.0);
let guard_fn = compile_iff_guard_with_frame(
ctx,
module,
fns,
&signals,
&plan.fn_sym,
&fn_name,
guard_expr,
value_repr,
Some((&plan.frame, &plan.frame_signed)),
seg.this_var,
)?;
guards.insert(
resume.0,
ResumeGuard {
guard_fn,
live_out: live_out.clone(),
vif_vars: vif_event_vars(point),
},
);
}
Ok(guards)
}
#[allow(clippy::too_many_arguments)]
pub fn compile_iff_guard<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
name: &str,
expr: sv_lang::Expression<'d>,
value_repr: ValueRepr,
) -> Result<FunctionValue<'ctx>, LoweringError> {
compile_iff_guard_with_frame(
ctx, module, fns, signals, fn_sym, name, expr, value_repr, None, None,
)
}
#[allow(clippy::too_many_arguments)]
pub fn compile_iff_guard_with_frame<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
name: &str,
expr: sv_lang::Expression<'d>,
value_repr: ValueRepr,
frame: Option<(&FrameLayout, &[bool])>,
this: Option<Symbol<'d>>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
compile_guard(
ctx,
module,
fns,
signals,
fn_sym,
name,
expr,
value_repr,
frame,
this,
GuardBody::Condition { negated: false },
)
}
#[allow(clippy::too_many_arguments)]
fn compile_event_value<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
name: &str,
expr: sv_lang::Expression<'d>,
value_repr: ValueRepr,
frame: (&FrameLayout, &[bool]),
this: Option<Symbol<'d>>,
) -> Result<(FunctionValue<'ctx>, u32), LoweringError> {
let width = event_value_width(&expr, fn_sym)?;
let f = compile_guard(
ctx,
module,
fns,
signals,
fn_sym,
name,
expr,
value_repr,
Some(frame),
this,
GuardBody::Value,
)?;
Ok((f, width))
}
#[derive(Clone, Copy)]
enum GuardBody {
Condition { negated: bool },
Value,
}
fn event_value_width(e: &Expression<'_>, fn_sym: &Symbol<'_>) -> Result<u32, LoweringError> {
let ty = e.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("an event control expression with no type", fn_sym)
})?;
let width = ty.bit_width();
if ty.is_event()
|| !ty.is_integral()
|| width == 0
|| width > u64::from(crate::value::WIDE_THRESHOLD)
{
return Err(LoweringError::unsupported_at(
&format!(
"an event control on an expression of type {} (LRM 1800-2023 9.4.2): only an \
integral expression of at most {} bits is supported",
ty.to_sv_string(),
crate::value::WIDE_THRESHOLD
),
fn_sym,
));
}
Ok(width as u32)
}
fn event_value_words(width: u32) -> u32 {
(2 * width).div_ceil(64)
}
fn event_value_image<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<(IntValue<'ctx>, u32), LoweringError> {
let width = event_value_width(&e, &cx.fn_sym)?;
let p = lower_expr(cx, e)?.into_packed(
"an event control on an expression that is not a narrow packed value",
&cx.fn_sym,
)?;
let whole = int_type(cx.ctx, event_value_words(width) * 64);
let image =
cx.b.build_int_z_extend_or_bit_cast(p.llvm, whole, "ev_image")
.unwrap();
Ok((image, width))
}
#[allow(clippy::too_many_arguments)]
pub fn compile_negated_iff_guard<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
name: &str,
expr: sv_lang::Expression<'d>,
value_repr: ValueRepr,
) -> Result<FunctionValue<'ctx>, LoweringError> {
compile_guard(
ctx,
module,
fns,
signals,
fn_sym,
name,
expr,
value_repr,
None,
None,
GuardBody::Condition { negated: true },
)
}
#[allow(clippy::too_many_arguments)]
fn compile_guard<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
name: &str,
expr: sv_lang::Expression<'d>,
value_repr: ValueRepr,
frame: Option<(&FrameLayout, &[bool])>,
this: Option<Symbol<'d>>,
body: GuardBody,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i8_ty = ctx.i8_type();
let fn_ty = match body {
GuardBody::Value => ctx
.void_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into(), ptr_ty.into()], false),
_ if frame.is_some() => i8_ty.fn_type(&[ptr_ty.into(), ptr_ty.into()], false),
_ => i8_ty.fn_type(&[ptr_ty.into()], false),
};
let f = module.add_function(name, fn_ty, None);
f.get_nth_param(0).unwrap().set_name("handle");
let entry_bb = ctx.append_basic_block(f, "entry");
let b = ctx.create_builder();
b.position_at_end(entry_bb);
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let mut cx = LowerCtx {
ctx,
b,
repr: value_repr,
locals: HashMap::new(),
fn_sym: *fn_sym,
module,
mem_signals: 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: this,
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
};
let found = named_reads(&[expr.as_sem_node()]);
if let Some((layout, signed)) = frame {
let local = f.get_nth_param(1).unwrap().into_pointer_value();
let mut named: HashSet<SymbolId> = found.iter().map(|s| s.uid()).collect();
named.extend(this.map(|t| t.uid()));
for (idx, slot) in layout.slots.iter().enumerate() {
if let FrameSlotSource::DeclaredVar(id) = &slot.source
&& named.contains(id)
{
if slot.cell {
let (shadow, _, _) = open_cell(ctx, module, &cx.b, local, slot);
cx.locals
.insert(id.clone(), (shadow, slot.width_bits, signed[idx]));
continue;
}
let src = frame_slot_ptr(ctx, &cx.b, local, slot.offset);
let ptr = crate::types::entry_alloca_in(
&cx.b,
slot_storage_type(ctx, slot),
"guard_local",
);
copy_frame_slot(ctx, &cx.b, slot, ptr, src);
cx.locals
.insert(id.clone(), (ptr, slot.width_bits, signed[idx]));
}
}
}
let mut ids: Vec<SymbolId> = found.iter().map(|s| s.uid()).collect();
let owner = fn_sym.uid();
let mut stack = vec![expr.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& let Some(inst) = sv_sim_sim::sva::endpoint::watched_call(&e)
{
ids.push(sv_sim_sim::sva::endpoint::watcher_id(&owner, &inst));
}
if let Some(e) = n.as_expression()
&& let Ok(Some(call)) = sv_sim_sim::sva::sampled::decode(e)
{
let args =
sv_sim_sim::sva::sampled::strict_site_signals_in(call.arg).unwrap_or_default();
let views = sv_sim_sim::sva::sampled::use_views(&owner, &call, &args);
ids.extend(
views
.cur
.into_iter()
.chain(views.prev)
.map(|(_, cell)| cell),
);
}
stack.extend(n.children());
}
prime_signal_ids(&mut cx, fns, handle, signals, ids, fn_sym)?;
match body {
GuardBody::Condition { negated } => {
let bit = if negated {
lower_negated_condition(&cx, expr)?
} else {
lower_condition(&cx, expr)?
};
let ext =
cx.b.build_int_z_extend(bit, i8_ty, "guard_bool_zext")
.unwrap();
cx.b.build_return(Some(&ext)).unwrap();
}
GuardBody::Value => {
let (image, _) = event_value_image(&cx, expr)?;
let out = f.get_nth_param(2).unwrap().into_pointer_value();
cx.b.build_store(out, image).unwrap();
cx.b.build_return(None).unwrap();
}
}
if f.verify(true) {
Ok(f)
} else {
Err(LoweringError {
message: "compile_iff_guard: guard function failed LLVM verify".into(),
at: fn_sym.hierarchical_path(),
})
}
}
pub(crate) fn named_reads<'d>(nodes: &[SemNode<'d>]) -> Vec<Symbol<'d>> {
let mut found = Vec::new();
for &node in nodes {
collect_named_value_symbols(node, &mut found);
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
found.extend(vif_candidates(&e));
}
stack.extend(n.children());
}
}
found
}
pub(crate) fn prime_signal_reads<'ctx, 'd>(
cx: &mut LowerCtx<'ctx, 'd, '_>,
fns: &AccessorFns<'ctx>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
found: Vec<Symbol<'d>>,
fn_sym: &Symbol<'d>,
) -> Result<(), LoweringError> {
let ids = found.iter().map(|s| s.uid()).collect();
prime_signal_ids(cx, fns, handle, signals, ids, fn_sym)
}
pub(crate) fn prime_signal_ids<'ctx, 'd>(
cx: &mut LowerCtx<'ctx, 'd, '_>,
fns: &AccessorFns<'ctx>,
handle: PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
ids: Vec<SymbolId>,
_fn_sym: &Symbol<'d>,
) -> Result<(), LoweringError> {
let mut seen = HashSet::new();
for id in ids {
if !seen.insert(id.clone()) {
continue;
}
let Some(info) = signals.get(&id) else {
continue;
};
if matches!(info.class, SignalSlotClass::Memory { .. }) {
continue;
}
if crate::huge::is_huge_width(info.width) {
let ptr = crate::huge::rt_prime(cx, handle, info.slot, info.width, false);
cx.locals.insert(id, (ptr, info.width, info.signed));
continue;
}
let raw = match info.class {
SignalSlotClass::Inline => build_get_scalar(cx, fns, handle, info.slot, info.width),
SignalSlotClass::Wide => build_get_wide(cx, fns, handle, info.slot, info.width),
SignalSlotClass::Memory { .. } => unreachable!("handled above"),
};
let packed_ty = int_type(cx.ctx, info.width * 2);
let ptr = crate::types::entry_alloca_in(&cx.b, packed_ty, "guard_shadow");
cx.b.build_store(ptr, raw).unwrap();
cx.locals.insert(id, (ptr, info.width, info.signed));
}
Ok(())
}
fn resolve_suspend_point<'d>(
point: &SuspendPoint<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
tick: i32,
) -> Result<ResolvedSuspend, LoweringError> {
match point {
SuspendPoint::Delay(expr, _) if !crate::timescale::delay_is_static(expr) => {
Ok(ResolvedSuspend::DynamicDelay)
}
SuspendPoint::Delay(expr, scope) => Ok(ResolvedSuspend::Delay(eval_constant_delay_ticks(
expr,
scope.as_ref().unwrap_or(fn_sym),
tick,
)?)),
SuspendPoint::Edge { signal, edge, .. } => {
let sym = signal.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at("edge suspend signal has no resolved symbol", fn_sym)
})?;
let info = signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"edge suspend signal is not a known external signal (no SignalSlot -- the \
A2 slice only supports suspending on a module-level port/net's edge)",
&sym,
)
})?;
Ok(ResolvedSuspend::Edge {
cell: StateCellId(u64::from(info.slot)),
edge: to_state_edge_kind(*edge, fn_sym)?,
})
}
SuspendPoint::Level(expr) => {
let mut found = Vec::new();
collect_named_value_symbols(expr.as_sem_node(), &mut found);
let mut seen = HashSet::new();
let uniq: Vec<Symbol<'d>> =
found.into_iter().filter(|s| seen.insert(s.uid())).collect();
let sym = match uniq.as_slice() {
[sym] => *sym,
[] => {
return Err(LoweringError::unsupported_at(
"wait(expr) condition references no named-value symbol at all -- this \
crate cannot derive a subscription for it",
fn_sym,
));
}
_ => {
return Err(LoweringError::unsupported_at(
"wait(expr) condition's read-set spans more than one signal -- this \
task's scope only resolves a single-signal level-sensitive wait (see \
resolve_suspend_point's own SuspendPoint::Level doc comment); a \
multi-signal wait needs the ImplicitAny/subscription-set-table wiring \
this task does not build",
fn_sym,
));
}
};
let info = signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"wait(expr) condition's signal is not a known external signal (no \
SignalSlot -- only a module-level port/net can be waited on this wave)",
&sym,
)
})?;
if info.width != 1 {
return Err(LoweringError::unsupported_at(
"wait(expr) on a signal wider than 1 bit is out of this task's scope -- \
`sched::notify::infer_edge`'s edge classification (and therefore the \
BothEdges subscription this task reuses) is defined bit-0-only, which \
would silently miss a change confined to a higher bit of a wider signal",
&sym,
));
}
Ok(ResolvedSuspend::Edge {
cell: StateCellId(u64::from(info.slot)),
edge: StateEdgeKind::BothEdges,
})
}
SuspendPoint::CycleDelay {
count, clock, edge, ..
} => {
let sym = *clock;
let info = signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"CycleDelay (`##N`) clock signal is not a known external signal (no \
SignalSlot -- only a module-level port/net's edge can be counted this \
wave)",
&sym,
)
})?;
let edge = if *edge == sv_lang::EdgeKind::BothEdges {
StateEdgeKind::BothEdges
} else {
to_state_edge_kind(*edge, fn_sym)?
};
Ok(ResolvedSuspend::RepeatEdge {
count: u64::from(*count),
cell: StateCellId(u64::from(info.slot)),
edge,
})
}
SuspendPoint::OneStep => Ok(ResolvedSuspend::Delay(1)),
SuspendPoint::WaitFork => Ok(ResolvedSuspend::WaitFork),
SuspendPoint::ByRequest => Ok(ResolvedSuspend::ByRequest),
SuspendPoint::EdgeList(..) | SuspendPoint::ImplicitComb(_) => {
Err(LoweringError::unsupported_at(
"this SuspendPoint kind has no codegen-side resolution yet (a real multi-signal \
wait needs a per-process edge-term table this crate does not build -- CFG-shape \
only this wave, PR-006/Task 6)",
fn_sym,
))
}
}
}
fn constant_delay_integer(
cv: &sv_lang::ConstantValue,
fn_sym: &Symbol<'_>,
) -> Result<Option<f64>, LoweringError> {
let iv = cv
.as_integer()
.ok_or_else(|| LoweringError::unsupported_at("delay constant is not a number", fn_sym))?;
if iv.has_unknown() {
return Ok(Some(0.0));
}
let v = iv
.as_i64()
.ok_or_else(|| LoweringError::unsupported_at("delay constant is not a number", fn_sym))?;
Ok((v >= 0).then_some(v as f64))
}
const NEVER_TICKS: u64 = 1 << 63;
pub(crate) fn eval_constant_delay_ticks(
expr: &sv_lang::Expression<'_>,
fn_sym: &Symbol<'_>,
tick: i32,
) -> Result<u64, LoweringError> {
let cv = expr.constant_value().ok_or_else(|| {
LoweringError::unsupported_at(
"delay expression is not a compile-time constant (only a constant delay is \
supported)",
fn_sym,
)
})?;
let v = match &cv {
sv_lang::ConstantValue::Real(f) => *f,
other => match constant_delay_integer(other, fn_sym)? {
Some(v) => v,
None => return Ok(NEVER_TICKS),
},
};
crate::timescale::delay_ticks(v, fn_sym, tick)
}
pub(crate) fn eval_constant_delay_ticks_scaled(
expr: &sv_lang::Expression<'_>,
fn_sym: &Symbol<'_>,
scale: (i32, i32),
tick: i32,
) -> Result<u64, LoweringError> {
let cv = expr.constant_value().ok_or_else(|| {
LoweringError::unsupported_at("delay expression is not a compile-time constant", fn_sym)
})?;
let v = match &cv {
sv_lang::ConstantValue::Real(f) => *f,
other => match constant_delay_integer(other, fn_sym)? {
Some(v) => v,
None => return Ok(NEVER_TICKS),
},
};
crate::timescale::delay_ticks_scaled(v, fn_sym, scale, tick)
}
pub fn to_state_edge_kind(
e: sv_lang::EdgeKind,
fn_sym: &Symbol<'_>,
) -> Result<StateEdgeKind, LoweringError> {
match e {
sv_lang::EdgeKind::PosEdge => Ok(StateEdgeKind::Posedge),
sv_lang::EdgeKind::NegEdge => Ok(StateEdgeKind::Negedge),
sv_lang::EdgeKind::None | sv_lang::EdgeKind::BothEdges => {
Err(LoweringError::unsupported_at(
&format!(
"edge kind {e:?} is unsupported in the A2 slice (only posedge/negedge suspend \
points are ever constructed by process::cfg::build_cfg)"
),
fn_sym,
))
}
}
}
fn store_eval_result_bytes_and_return<'ctx>(
ctx: &'ctx Context,
b: &Builder<'ctx>,
sret: PointerValue<'ctx>,
bytes: &[u8; 36],
) {
let i8_ty = ctx.i8_type();
let elems: Vec<IntValue<'ctx>> = bytes
.iter()
.map(|byte| i8_ty.const_int(u64::from(*byte), false))
.collect();
let arr = i8_ty.const_array(&elems);
b.build_store(sret, arr).unwrap();
b.build_return(None).unwrap();
}
pub fn lower_kernel_fn<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
lower_kernel_fn_with_repr(ctx, module, fns, plan, ValueRepr::FourState)
}
pub fn lower_kernel_fn_with_repr<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
value_repr: ValueRepr,
) -> Result<FunctionValue<'ctx>, LoweringError> {
lower_kernel_fn_with_repr_impl(ctx, module, fns, plan, value_repr, None)
}
pub fn lower_kernel_fn_with_fork_support<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
value_repr: ValueRepr,
kernels: &mut Vec<FunctionValue<'ctx>>,
fork_table: &mut Vec<SpawnSpec>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let mut branch_frames = Vec::new();
let mut wait_table = Vec::new();
lower_kernel_fn_with_fork_branches(
ctx,
module,
fns,
plan,
value_repr,
kernels,
fork_table,
&mut branch_frames,
&mut wait_table,
)
}
#[allow(clippy::too_many_arguments)]
pub fn lower_kernel_fn_with_fork_branches<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
value_repr: ValueRepr,
kernels: &mut Vec<FunctionValue<'ctx>>,
fork_table: &mut Vec<SpawnSpec>,
branch_frames: &mut Vec<BranchFrame>,
wait_table: &mut Vec<WaitTableEntry>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let mut fork_ctx = ForkCompileCtx {
kernels,
fork_table,
branch_frames,
wait_table,
recursion: Vec::new(),
shared: HashMap::new(),
};
lower_kernel_fn_with_repr_impl(ctx, module, fns, plan, value_repr, Some(&mut fork_ctx))
}
#[derive(Debug, Clone)]
pub struct ExportTaskKernel {
pub kernel: u32,
pub entry_pc: u32,
pub frame_words: u32,
pub formals: Vec<Option<sv_sim_sim::process::liveness::FrameSlotDesc>>,
}
#[allow(clippy::too_many_arguments)]
pub fn lower_export_task_kernel<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
task: &Symbol<'d>,
signals: HashMap<SymbolId, SignalSlot>,
fn_name: String,
value_repr: ValueRepr,
kernels: &mut Vec<FunctionValue<'ctx>>,
fork_table: &mut Vec<SpawnSpec>,
branch_frames: &mut Vec<BranchFrame>,
wait_table: &mut Vec<WaitTableEntry>,
) -> Result<ExportTaskKernel, LoweringError> {
let cfg = sv_sim_sim::process::cfg::build_export_task_cfg(task, None)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), task))?;
let frame =
compute_liveness(&cfg).map_err(|e| LoweringError::unsupported_at(&e.to_string(), task))?;
let frame_words = frame.total_words();
let segments = lower_to_segments(&cfg, &frame);
let mut plan = build_lowering_plan(fn_name, *task, segments, frame, signals);
plan.fork_branch_names = cfg.fork_branch_names.clone();
plan.disable_broadcast = cfg.disable_broadcast.clone();
plan.write_back_at_end = true;
let formals = task
.subroutine_arguments()
.map(|f| {
let id = f.uid();
plan.frame
.slots
.iter()
.find(|s| matches!(&s.source, FrameSlotSource::DeclaredVar(d) if *d == id))
.cloned()
})
.collect();
let mut fc = ForkCompileCtx {
kernels,
fork_table,
branch_frames,
wait_table,
recursion: Vec::new(),
shared: HashMap::new(),
};
let fv = lower_kernel_fn_with_repr_impl(ctx, module, fns, &plan, value_repr, Some(&mut fc))?;
let kernel = fc.kernels.len() as u32;
fc.kernels.push(fv);
fc.branch_frames
.push((kernel, frame_words, Vec::new(), Vec::new()));
Ok(ExportTaskKernel {
kernel,
entry_pc: cfg.entry.0,
frame_words,
formals,
})
}
#[allow(clippy::too_many_arguments)]
pub fn lower_match_item_task_kernel<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
site: &crate::sva::actions::CallSite<'d>,
first_input: usize,
signals: HashMap<SymbolId, SignalSlot>,
fn_name: String,
value_repr: ValueRepr,
kernels: &mut Vec<FunctionValue<'ctx>>,
fork_table: &mut Vec<SpawnSpec>,
branch_frames: &mut Vec<BranchFrame>,
wait_table: &mut Vec<WaitTableEntry>,
) -> Result<ExportTaskKernel, LoweringError> {
let task = site.call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported(
"a match-item call of a task has no subroutine symbol",
"match-item task",
)
})?;
let cfg = sv_sim_sim::process::cfg::build_match_item_task_cfg(site.call)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &task))?;
let mut args = Vec::new();
let mut next = first_input;
for (arg, actual) in &site.args {
if !matches!(arg, sv_sim_sim::sva::ir::CallArg::Value(_)) {
continue;
}
let t = actual.expr_type().ok_or_else(|| {
LoweringError::unsupported_at(
"a by-value argument of a sequence match-item call has no type",
&task,
)
})?;
let width = t.value_width();
args.push((
crate::sva::proc::call_arg_key(actual),
(next, width, t.is_signed()),
));
next += if width > 64 {
2 * width.div_ceil(64) as usize
} else {
1
};
}
let _scope = crate::sva::proc::SpawnArgScope::enter(args);
let frame =
compute_liveness(&cfg).map_err(|e| LoweringError::unsupported_at(&e.to_string(), &task))?;
let frame_words = frame.total_words();
let segments = lower_to_segments(&cfg, &frame);
let mut plan = build_lowering_plan(fn_name, task, segments, frame, signals);
plan.fork_branch_names = cfg.fork_branch_names.clone();
plan.disable_broadcast = cfg.disable_broadcast.clone();
plan.write_back_at_end = true;
let mut fc = ForkCompileCtx {
kernels,
fork_table,
branch_frames,
wait_table,
recursion: Vec::new(),
shared: HashMap::new(),
};
let fv = lower_kernel_fn_with_repr_impl(ctx, module, fns, &plan, value_repr, Some(&mut fc))?;
let kernel = fc.kernels.len() as u32;
fc.kernels.push(fv);
fc.branch_frames
.push((kernel, frame_words, Vec::new(), Vec::new()));
Ok(ExportTaskKernel {
kernel,
entry_pc: cfg.entry.0,
frame_words,
formals: Vec::new(),
})
}
fn inherited_captures<'d>(
parent: &FrameLayout,
child: &FrameLayout,
inherited: &[sv_sim_sim::process::cfg::InheritedSlot],
fn_sym: &Symbol<'d>,
) -> Result<Vec<(u32, u32)>, LoweringError> {
let mut out = Vec::new();
for inh in inherited {
let (ps, cs) = match inh {
sv_sim_sim::process::cfg::InheritedSlot::Temp { parent, child } => (
FrameSlotSource::Temporary(*parent),
FrameSlotSource::Temporary(*child),
),
sv_sim_sim::process::cfg::InheritedSlot::Counter { parent, child } => (
FrameSlotSource::SyntheticCounter(*parent),
FrameSlotSource::SyntheticCounter(*child),
),
sv_sim_sim::process::cfg::InheritedSlot::Return { parent, child } => (
FrameSlotSource::Temporary(*parent),
FrameSlotSource::Temporary(*child),
),
};
let ret = matches!(inh, sv_sim_sim::process::cfg::InheritedSlot::Return { .. });
let find = |f: &FrameLayout, src: &FrameSlotSource| {
f.slots.iter().find(|x| x.source == *src).cloned()
};
let (Some(p), Some(c)) = (find(parent, &ps), find(child, &cs)) else {
return Err(LoweringError::unsupported_at(
"an inherited fork-branch slot has no frame slot (internal invariant)",
fn_sym,
));
};
let words = match p.repr {
sv_sim_sim::process::FrameSlot::Narrow4State { word_count } => u32::from(word_count),
sv_sim_sim::process::FrameSlot::Array { rows, row_words } => {
rows * u32::from(row_words)
}
sv_sim_sim::process::FrameSlot::Wide4State {
aval_word_count,
bval_word_count,
} => aval_word_count + bval_word_count,
};
let flag = if ret {
sv_sim_sim::sched::RETURN_COPY
} else {
0
};
out.extend((0..words).map(|k| ((p.offset + k) | flag, c.offset + k)));
}
Ok(out)
}
struct ForkCompileCtx<'a, 'ctx> {
kernels: &'a mut Vec<FunctionValue<'ctx>>,
fork_table: &'a mut Vec<SpawnSpec>,
branch_frames: &'a mut Vec<BranchFrame>,
wait_table: &'a mut Vec<WaitTableEntry>,
recursion: Vec<RecursionFrame>,
shared: HashMap<u64, (FunctionValue<'ctx>, u32, FrameLayout)>,
}
struct RecursionFrame {
entry_pc: u32,
frame_words: u32,
frame: FrameLayout,
pending: Vec<(usize, Vec<(u32, u32)>)>,
}
fn resolve_suspend_point_tabled<'d>(
point: &SuspendPoint<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
wait_table: &mut Vec<WaitTableEntry>,
tick: i32,
) -> Result<ResolvedSuspend, LoweringError> {
let any_change_terms = |exprs: &[Expression<'d>]| -> Vec<WaitTableEntry> {
let mut slots: Vec<u32> = Vec::new();
for e in exprs {
for slot in expr_dependency_slots(e, signals, fn_sym) {
if !slots.contains(&slot) {
slots.push(slot);
}
}
}
slots
.into_iter()
.map(|slot| WaitTableEntry::whole(slot, None))
.collect()
};
let plain_signal_term =
|expr: &Expression<'d>, edge: sv_lang::EdgeKind| -> Result<WaitTableEntry, LoweringError> {
let target = crate::expr::modport_expr_target(expr);
let expr = target.as_ref().unwrap_or(expr);
if expr.kind() == sv_lang::kinds::ExpressionKind::AssertionInstance {
let id = sv_sim_sim::sva::endpoint::watcher_id(&fn_sym.uid(), expr);
let info = signals.get(&id).ok_or_else(|| {
LoweringError::unsupported_at(
"a sequence event control `@(s)` whose instance is not watched here",
fn_sym,
)
})?;
return Ok(WaitTableEntry::whole(info.slot, None));
}
if let Some(info) = event_elem_signal(expr, signals) {
if edge != sv_lang::EdgeKind::None {
return Err(LoweringError::unsupported_at(
"an edge qualifier on a named event array element",
fn_sym,
));
}
return Ok(WaitTableEntry::whole(info.slot, None));
}
if let Some((info, lsb, width)) = constant_select_of_signal(expr, signals) {
let edge = match edge {
sv_lang::EdgeKind::PosEdge => Some(StateEdgeKind::Posedge),
sv_lang::EdgeKind::NegEdge => Some(StateEdgeKind::Negedge),
sv_lang::EdgeKind::BothEdges => Some(StateEdgeKind::BothEdges),
sv_lang::EdgeKind::None => None,
};
return Ok(WaitTableEntry {
slot: info.slot,
edge,
select: Some((lsb, width)),
});
}
let sym = expr
.referenced_symbol()
.filter(|s| {
sv_sim_sim::symid::is_value_name(expr.kind())
|| s.kind() == sv_lang::kinds::SymbolKind::ClockingBlock
})
.ok_or_else(|| {
LoweringError::unsupported_at(
"an event control term on a bit/part select or expression (only a \
plain signal name is supported in @(...) event lists)",
fn_sym,
)
})?;
let info = signals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"an event control term that is not a module-level signal",
&sym,
)
})?;
let edge = match edge {
sv_lang::EdgeKind::PosEdge => Some(StateEdgeKind::Posedge),
sv_lang::EdgeKind::NegEdge => Some(StateEdgeKind::Negedge),
sv_lang::EdgeKind::BothEdges => Some(StateEdgeKind::BothEdges),
sv_lang::EdgeKind::None => None,
};
Ok(WaitTableEntry::whole(info.slot, edge))
};
let locals = local_event_terms(point, signals);
let exprs = expr_event_terms(point, signals);
if !locals.is_empty() || !exprs.is_empty() {
let entries: Vec<(Expression<'d>, sv_lang::EdgeKind)> = match point {
SuspendPoint::Edge { signal, edge, .. } => vec![(*signal, *edge)],
SuspendPoint::EdgeList(entries, ..) => entries.clone(),
_ => Vec::new(),
};
let mut terms = Vec::new();
let mut extra = Vec::new();
for (k, (expr, edge)) in entries.iter().enumerate() {
if locals.iter().any(|t| t.index == k) {
terms.push(WaitTableEntry::whole(
sv_sim_sim::sched::SHARED_VARS_SLOT,
None,
));
} else if exprs.iter().any(|t| t.index == k) {
let mut deps = expr_dependency_slots(expr, signals, fn_sym).into_iter();
let first = deps.next().unwrap_or(sv_sim_sim::sched::SHARED_VARS_SLOT);
terms.push(WaitTableEntry::whole(first, None));
extra.extend(deps.map(|slot| WaitTableEntry::whole(slot, None)));
} else {
terms.push(plain_signal_term(expr, *edge)?);
}
}
terms.extend(extra);
let base = wait_table.len() as u32;
let count = terms.len() as u32;
wait_table.extend(terms);
return Ok(ResolvedSuspend::EventList { base, count });
}
let terms: Vec<WaitTableEntry> = match point {
SuspendPoint::ImplicitComb(exprs) => any_change_terms(exprs),
SuspendPoint::Level(expr) => {
let mut terms = any_change_terms(std::slice::from_ref(expr));
if crate::classes::reads_object_state(expr) {
terms.push(WaitTableEntry::whole(
sv_sim_sim::sched::OBJECT_STATE_SLOT,
None,
));
}
terms
}
SuspendPoint::EdgeList(entries, ..) => entries
.iter()
.map(|(expr, edge)| plain_signal_term(expr, *edge))
.collect::<Result<_, _>>()?,
SuspendPoint::Edge {
signal,
edge: edge @ (sv_lang::EdgeKind::None | sv_lang::EdgeKind::BothEdges),
..
} => vec![plain_signal_term(signal, *edge)?],
SuspendPoint::Edge { signal, edge, .. }
if constant_select_of_signal(signal, signals).is_some() =>
{
vec![plain_signal_term(signal, *edge)?]
}
SuspendPoint::Edge { .. }
| SuspendPoint::Delay(..)
| SuspendPoint::CycleDelay { .. }
| SuspendPoint::OneStep
| SuspendPoint::WaitFork
| SuspendPoint::ByRequest => return resolve_suspend_point(point, signals, fn_sym, tick),
};
let base = wait_table.len() as u32;
let count = terms.len() as u32;
wait_table.extend(terms);
Ok(ResolvedSuspend::EventList { base, count })
}
fn lower_kernel_fn_with_repr_impl<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
value_repr: ValueRepr,
mut fork_ctx: Option<&mut ForkCompileCtx<'_, 'ctx>>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let inner_plan = ProcessLoweringPlan {
fn_name: format!("{}__inner", plan.fn_name),
fn_sym: plan.fn_sym,
segments: plan.segments.clone(),
frame: plan.frame.clone(),
frame_signed: plan.frame_signed.clone(),
local_syms: plan.local_syms.clone(),
signals: plan.signals.clone(),
region_id: plan.region_id,
owning_instance: plan.owning_instance,
specify_dest_redirect: plan.specify_dest_redirect.clone(),
hosted_assertion: plan.hosted_assertion,
inferred_disable: plan.inferred_disable.clone(),
nested_assertions: plan.nested_assertions.clone(),
disable_keys: plan.disable_keys.clone(),
fork_branch_names: plan.fork_branch_names.clone(),
disable_broadcast: plan.disable_broadcast.clone(),
write_back_at_end: plan.write_back_at_end,
disable_scope_keys: plan.disable_scope_keys.clone(),
assertion_calls: plan.assertion_calls.clone(),
};
let inner_fn = lower_process_cfg_with_repr(ctx, module, fns, &inner_plan, value_repr)?;
let tick = crate::timescale::tick_exp(ctx, module, &plan.fn_sym);
let mut suspend_cases: Vec<(u32, [u8; 36])> = Vec::new();
for seg in &plan.segments {
let signals = seg_signals(&plan.signals, seg)?;
if let SegTerminator::Suspend { point, resume, .. } = &seg.terminator
&& let Some((signal, edge)) = vif_event(point)
{
let edge = &edge;
if let Some(terms) = vif_event_list(point) {
let Some(fc) = fork_ctx.as_deref_mut() else {
return Err(LoweringError::unsupported_at(
"an event control list through a virtual interface outside a process \
kernel",
&plan.fn_sym,
));
};
let is_vif = |t: &Expression<'d>| vif_term_member(t).is_some();
let cands: Vec<Vec<Symbol<'d>>> = terms
.iter()
.map(|(t, _)| {
if let Some(member) = vif_term_member(t) {
vif_candidates(&member)
} else {
Vec::new()
}
})
.collect();
let mut var_syms: Vec<String> = Vec::new();
let mut var_n: Vec<usize> = Vec::new();
let mut var_of: Vec<usize> = vec![0; terms.len()];
for (ti, (t, _)) in terms.iter().enumerate() {
let Some(base) = vif_term_member(t).and_then(|m| crate::vif::event_base(&m))
else {
continue;
};
let sym = vif_var_key(&base).ok_or_else(|| {
LoweringError::unsupported_at(
"a virtual interface expression with no source text",
&plan.fn_sym,
)
})?;
let j = match var_syms.iter().position(|s| *s == sym) {
Some(j) => j,
None => {
var_syms.push(sym);
var_n.push(cands[ti].len());
var_syms.len() - 1
}
};
if var_n[j] != cands[ti].len() {
return Err(LoweringError::unsupported_at(
"an event control list through a virtual interface whose terms differ \
in the instances they reach",
&plan.fn_sym,
));
}
var_of[ti] = j;
}
let total = var_n.iter().try_fold(1usize, |a, &n| a.checked_mul(n));
if total.is_none_or(|t| t >= 1 << (32 - crate::vif::KEY_SHIFT)) {
return Err(LoweringError::unsupported_at(
"an event control list through virtual interface variables whose \
candidate instances multiply past 4095 combinations",
&plan.fn_sym,
));
}
let total = total.unwrap_or(0);
let mut keyed: Vec<(u32, Vec<WaitTableEntry>)> = vec![(resume.0, Vec::new())];
for code in 0..total {
let mut ks = Vec::with_capacity(var_n.len());
let mut rest = code;
for &n in &var_n {
ks.push(rest % n);
rest /= n;
}
let mut entries = Vec::new();
let mut extra = Vec::new();
for (ti, (term, term_edge)) in terms.iter().enumerate() {
if !is_vif(term) {
let mut scratch = Vec::new();
let one =
SuspendPoint::EdgeList(vec![(*term, *term_edge)], vec![Vec::new()]);
resolve_suspend_point_tabled(
&one,
&signals,
&plan.fn_sym,
&mut scratch,
tick,
)?;
if scratch.is_empty() {
return Err(LoweringError::unsupported_at(
"an event term that waits on nothing in a list that also \
waits through a virtual interface",
&plan.fn_sym,
));
}
entries.push(scratch.remove(0));
extra.append(&mut scratch);
continue;
}
let cand = &cands[ti][ks[var_of[ti]]];
let info = signals.get(&cand.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"a virtual interface member this process cannot reach",
cand,
)
})?;
let e = match term_edge {
sv_lang::EdgeKind::None => None,
sv_lang::EdgeKind::BothEdges => Some(StateEdgeKind::BothEdges),
e @ (sv_lang::EdgeKind::PosEdge | sv_lang::EdgeKind::NegEdge) => {
Some(to_state_edge_kind(*e, &plan.fn_sym)?)
}
};
entries.push(WaitTableEntry::whole(info.slot, e));
}
entries.append(&mut extra);
keyed.push((
resume.0 + ((code as u32 + 1) << crate::vif::KEY_SHIFT),
entries,
));
}
for (key, entries) in keyed {
let base = fc.wait_table.len() as u32;
let count = entries.len() as u32;
fc.wait_table.extend(entries);
let er =
build_eval_result(ResolvedSuspend::EventList { base, count }, resume.0);
let bytes: [u8; 36] = unsafe { core::mem::transmute(er) };
suspend_cases.push((key, bytes));
}
continue;
}
if let SuspendPoint::CycleDelay { count, .. } = point {
let mut keyed = vec![(resume.0, None)];
for (k, cand) in vif_candidates(&signal).iter().enumerate() {
let cb = cand
.parent()
.filter(|p| p.kind() == sv_lang::kinds::SymbolKind::ClockingBlock)
.ok_or_else(|| {
LoweringError::unsupported_at(
"a cycle delay through a virtual interface member that is not a \
clock variable",
cand,
)
})?;
let info = signals.get(&cb.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"a virtual interface clocking block this process cannot reach",
&cb,
)
})?;
keyed.push((
resume.0 + ((k as u32 + 1) << crate::vif::KEY_SHIFT),
Some(info.slot),
));
}
for (key, slot) in keyed {
let resolved = match slot {
Some(slot) => ResolvedSuspend::RepeatEdge {
count: u64::from(*count),
cell: StateCellId(u64::from(slot)),
edge: StateEdgeKind::BothEdges,
},
None => ResolvedSuspend::Delay(NEVER_TICKS),
};
let er = build_eval_result(resolved, resume.0);
let bytes: [u8; 36] = unsafe { core::mem::transmute(er) };
suspend_cases.push((key, bytes));
}
continue;
}
let mut keyed = vec![(resume.0, None)];
for (k, cand) in vif_candidates(&signal).iter().enumerate() {
let info = signals.get(&cand.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"a virtual interface member this process cannot reach",
cand,
)
})?;
keyed.push((
resume.0 + ((k as u32 + 1) << crate::vif::KEY_SHIFT),
Some(info.slot),
));
}
for (key, slot) in keyed {
let resolved = match (slot, fork_ctx.as_deref_mut()) {
(Some(slot), _) if *edge != sv_lang::EdgeKind::None => ResolvedSuspend::Edge {
cell: StateCellId(u64::from(slot)),
edge: to_state_edge_kind(*edge, &plan.fn_sym)?,
},
(slot, Some(fc)) => {
let base = fc.wait_table.len() as u32;
fc.wait_table
.extend(slot.map(|slot| WaitTableEntry::whole(slot, None)));
ResolvedSuspend::EventList {
base,
count: u32::from(slot.is_some()),
}
}
(_, None) => {
return Err(LoweringError::unsupported_at(
"an any-change event control through a virtual interface outside \
a process kernel",
&plan.fn_sym,
));
}
};
let er = build_eval_result(resolved, resume.0);
let bytes: [u8; 36] = unsafe { core::mem::transmute(er) };
suspend_cases.push((key, bytes));
}
continue;
}
if let SegTerminator::Suspend { point, resume, .. } = &seg.terminator {
let resolved = match fork_ctx.as_deref_mut() {
Some(fc) => resolve_suspend_point_tabled(
point,
&signals,
&plan.fn_sym,
fc.wait_table,
tick,
)?,
None => resolve_suspend_point(point, &signals, &plan.fn_sym, tick)?,
};
let mut er = build_eval_result(resolved, resume.0);
if matches!(point, SuspendPoint::Level(_)) {
er = mark_rechecks_on_wake(er);
}
let bytes: [u8; 36] = unsafe { core::mem::transmute(er) };
suspend_cases.push((resume.0, bytes));
}
if let SegTerminator::ForkJoin {
kind,
branches,
join_blk,
..
} = &seg.terminator
{
let join_kind = match kind {
StatementBlockKind::JoinAll => JoinKind::All,
StatementBlockKind::JoinAny => JoinKind::Any,
StatementBlockKind::JoinNone => JoinKind::None,
StatementBlockKind::Sequential => {
return Err(LoweringError::unsupported_at(
"internal error: a SegTerminator::ForkJoin carries a Sequential block \
kind (process::cfg only builds ForkJoin for fork blocks)",
&plan.fn_sym,
));
}
};
let fc = fork_ctx.as_deref_mut().ok_or_else(|| {
LoweringError::unsupported_at(
"a fork/join site reached codegen but no fork-compilation context was \
installed -- a process whose CFG contains fork/join must be compiled via \
lower_kernel_fn_with_fork_support, never the plain lower_kernel_fn(_with_\
repr) (internal invariant, R-run Task 17)",
&plan.fn_sym,
)
})?;
let mut spawns: Vec<SpawnSpec> = Vec::with_capacity(branches.len());
for (bi, branch) in branches.iter().enumerate() {
let spawn_up = match branch.recursion {
sv_sim_sim::process::cfg::Recursion::SelfSpawn => Some(0usize),
sv_sim_sim::process::cfg::Recursion::AncestorSpawn(up) => Some(up as usize),
_ => None,
};
if let Some(up) = spawn_up {
let depth = fc.recursion.len();
let top = depth
.checked_sub(up + 1)
.and_then(|i| fc.recursion.get_mut(i))
.ok_or_else(|| {
LoweringError::unsupported_at(
"a recursive task call outside its activation (internal invariant)",
&plan.fn_sym,
)
})?;
let copies = inherited_captures(
&plan.frame,
&top.frame,
&branch.inherited,
&plan.fn_sym,
)?;
top.pending.push((fc.fork_table.len() + bi, copies));
spawns.push(SpawnSpec {
kernel: 0,
entry_pc: top.entry_pc,
parent: 0,
disable_name: Vec::new(),
});
continue;
}
let is_root = branch.recursion == sv_sim_sim::process::cfg::Recursion::Root;
let shared_id = branch
.share_id
.filter(|_| is_root || fc.recursion.is_empty());
let mut fresh = false;
let (branch_fn, branch_frame_words, branch_frame_layout) = if let Some(hit) =
shared_id.and_then(|id| fc.shared.get(&id).cloned())
{
hit
} else {
fresh = true;
let branch_frame = compute_liveness(branch)
.map_err(|e| LoweringError::unsupported_at(&e.to_string(), &plan.fn_sym))?;
let branch_frame_words = branch_frame.total_words();
let branch_segments = lower_to_segments(branch, &branch_frame);
let mut branch_plan = build_lowering_plan(
format!("{}__fork{}_{bi}", plan.fn_name, join_blk.0),
plan.fn_sym,
branch_segments,
branch_frame,
plan.signals.clone(),
);
branch_plan.nested_assertions = plan.nested_assertions.clone();
branch_plan.disable_keys = plan.disable_keys.clone();
branch_plan.disable_scope_keys = plan.disable_scope_keys.clone();
branch_plan.inferred_disable = plan.inferred_disable.clone();
branch_plan.fork_branch_names = branch.fork_branch_names.clone();
branch_plan.disable_broadcast = branch.disable_broadcast.clone();
branch_plan.write_back_at_end = branch.inherited.iter().any(|i| {
matches!(i, sv_sim_sim::process::cfg::InheritedSlot::Return { .. })
});
if is_root {
fc.recursion.push(RecursionFrame {
entry_pc: branch.entry.0,
frame_words: branch_frame_words,
frame: branch_plan.frame.clone(),
pending: Vec::new(),
});
}
let branch_fn = lower_kernel_fn_with_repr_impl(
ctx,
module,
fns,
&branch_plan,
value_repr,
Some(&mut *fc),
)?;
if let Some(id) = shared_id {
fc.shared.insert(
id,
(branch_fn, branch_frame_words, branch_plan.frame.clone()),
);
}
(branch_fn, branch_frame_words, branch_plan.frame.clone())
};
let kernel_idx = fc.kernels.len() as u32;
fc.kernels.push(branch_fn);
if is_root && fresh {
let frame = fc.recursion.pop().ok_or_else(|| {
LoweringError::unsupported_at(
"recursion frame lost (internal invariant)",
&plan.fn_sym,
)
})?;
let mut groups: Vec<(u32, Vec<(u32, u32)>)> = Vec::new();
for (row, copies) in frame.pending {
let again = match groups.iter().find(|(_, c)| *c == copies) {
Some((k, _)) => *k,
None => {
let again = fc.kernels.len() as u32;
fc.kernels.push(branch_fn);
crate::vpi_dyn::note_fork_branch(again, &branch.body);
fc.branch_frames.push((
again,
frame.frame_words,
copies.clone(),
Vec::new(),
));
groups.push((again, copies));
again
}
};
fc.fork_table[row].kernel = again;
}
}
crate::vpi_dyn::note_fork_branch(kernel_idx, &branch.body);
let captures: Vec<(u32, u32)> = branch_frame_layout
.slots
.iter()
.filter(|s| s.captured)
.filter_map(|s| {
let FrameSlotSource::DeclaredVar(id) = &s.source else {
return None;
};
plan.frame
.slots
.iter()
.find(|p| {
p.cell && matches!(&p.source, FrameSlotSource::DeclaredVar(d) if d == id)
})
.map(|p| (p.offset, s.offset))
})
.collect();
let mut captures = captures;
captures.extend(inherited_captures(
&plan.frame,
&branch_frame_layout,
&branch.inherited,
&plan.fn_sym,
)?);
let scopes = branch
.disable_scopes
.iter()
.flat_map(|(resume, label, exit)| match &plan.disable_scope_keys {
Some(keys) => keys.expand(resume.0, label, exit.0),
None => vec![(resume.0, label.clone(), exit.0)],
})
.collect();
fc.branch_frames
.push((kernel_idx, branch_frame_words, captures, scopes));
spawns.push(SpawnSpec {
kernel: kernel_idx,
entry_pc: branch.entry.0,
parent: 0,
disable_name: branch.disable_names.clone(),
});
}
let base = fc.fork_table.len() as u32;
let count = spawns.len() as u32;
fc.fork_table.extend(spawns);
let resolved = ResolvedSuspend::ForkJoin {
join_kind,
base,
count,
};
let er = build_eval_result(resolved, join_blk.0);
let bytes: [u8; 36] = unsafe { core::mem::transmute(er) };
suspend_cases.push((join_blk.0, bytes));
}
}
let done_er = build_eval_result(ResolvedSuspend::Done, 0);
let done_bytes: [u8; 36] = unsafe { core::mem::transmute(done_er) };
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let void_ty = ctx.void_type();
let i8_ty = ctx.i8_type();
let eval_result_ty = ctx.struct_type(&[i32_ty.into(), i8_ty.array_type(32).into()], false);
let fn_ty = void_ty.fn_type(
&[ptr_ty.into(), ptr_ty.into(), ptr_ty.into(), i32_ty.into()],
false,
);
let wrapper = module.add_function(&plan.fn_name, fn_ty, None);
let sret_kind_id = Attribute::get_named_enum_kind_id("sret");
wrapper.add_attribute(
AttributeLoc::Param(0),
ctx.create_type_attribute(sret_kind_id, eval_result_ty.as_any_type_enum()),
);
wrapper.get_nth_param(0).unwrap().set_name("sret");
wrapper.get_nth_param(1).unwrap().set_name("handle");
wrapper.get_nth_param(2).unwrap().set_name("local");
wrapper.get_nth_param(3).unwrap().set_name("resume_pc");
let entry_bb = ctx.append_basic_block(wrapper, "entry");
let b = ctx.create_builder();
b.position_at_end(entry_bb);
let sret = wrapper.get_nth_param(0).unwrap().into_pointer_value();
let handle = wrapper.get_nth_param(1).unwrap().into_pointer_value();
let local = wrapper.get_nth_param(2).unwrap().into_pointer_value();
let resume_pc = wrapper.get_nth_param(3).unwrap().into_int_value();
let raw = b
.build_call(
inner_fn,
&[handle.into(), local.into(), resume_pc.into()],
"inner_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let finished_bb = ctx.append_basic_block(wrapper, "finished");
let case_blocks: Vec<LlvmBasicBlock<'ctx>> = suspend_cases
.iter()
.enumerate()
.map(|(i, _)| ctx.append_basic_block(wrapper, &format!("susp_case_{i}")))
.collect();
let trap_bb = ctx.append_basic_block(wrapper, "kernel_abi_trap");
let mut switch_cases: Vec<(IntValue<'ctx>, LlvmBasicBlock<'ctx>)> = vec![(
i32_ty.const_int(SEG_FINISHED as u32 as u64, false),
finished_bb,
)];
for (i, (raw_val, _)) in suspend_cases.iter().enumerate() {
switch_cases.push((i32_ty.const_int(u64::from(*raw_val), false), case_blocks[i]));
}
b.build_switch(raw, trap_bb, &switch_cases).unwrap();
b.position_at_end(finished_bb);
store_eval_result_bytes_and_return(ctx, &b, sret, &done_bytes);
for (i, (_, bytes)) in suspend_cases.iter().enumerate() {
b.position_at_end(case_blocks[i]);
store_eval_result_bytes_and_return(ctx, &b, sret, bytes);
}
b.position_at_end(trap_bb);
b.build_unreachable().unwrap();
if wrapper.verify(true) {
Ok(wrapper)
} else {
Err(LoweringError {
message: "process ABI wrapper function failed LLVM verify".into(),
at: plan.fn_sym.hierarchical_path(),
})
}
}
fn external_refs_in_expr<'d>(
assign_expr: Expression<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> (Vec<Symbol<'d>>, HashSet<SymbolId>) {
let mut order = Vec::new();
let mut seen = HashSet::new();
let mut written = HashSet::new();
let is_memory_signal = |id: &SymbolId| {
matches!(
signals.get(id).map(|s| s.class),
Some(SignalSlotClass::Memory { .. })
)
};
let mut stack = vec![assign_expr.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
if sv_sim_sim::symid::is_value_name(e.kind())
&& let Some(sym) = e.referenced_symbol()
&& signals.contains_key(&sym.uid())
&& !is_memory_signal(&sym.uid())
&& seen.insert(sym.uid())
{
order.push(sym);
}
for sym in vif_candidates(&e) {
if signals.contains_key(&sym.uid())
&& !is_memory_signal(&sym.uid())
&& seen.insert(sym.uid())
{
order.push(sym);
}
}
if e.kind() == ExpressionKind::Assignment
&& e.is_nonblocking() != Some(true)
&& let Some(lhs) = e.left()
{
for sym in crate::select::lvalue_root_symbols(lhs) {
if signals.contains_key(&sym.uid()) && !is_memory_signal(&sym.uid()) {
written.insert(sym.uid());
}
}
}
for callee in crate::func::user_callees(&e) {
let abi = crate::func::dispatch_abi(&callee);
for sig in abi.signals {
let id = sig.uid();
if !signals.contains_key(&id) || is_memory_signal(&id) {
continue;
}
if abi.written.contains(&id) {
written.insert(id.clone());
}
if seen.insert(id) {
order.push(sig);
}
}
}
}
stack.extend(n.children());
}
(order, written)
}
fn names_any(e: Expression<'_>, set: &HashMap<SymbolId, SignalSlot>) -> bool {
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& sv_sim_sim::symid::is_value_name(x.kind())
&& x.referenced_symbol()
.is_some_and(|s| set.contains_key(&s.uid()))
{
return true;
}
stack.extend(n.children());
}
false
}
pub fn lower_cont_assign_kernel_fn<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
spec: &crate::pipeline::ContAssignLoweringSpec<'d>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let fn_name = spec.fn_name.clone();
let fn_sym = spec.fn_sym;
let assign_expr = spec.assign_expr;
let net_target = spec.net_target;
let signals = &spec.signals;
let two_phase = spec.force_site.is_none()
&& spec.port_source.is_none()
&& net_target.is_none()
&& !spec.read_signals.is_empty()
&& assign_expr.is_compound_assignment() != Some(true)
&& assign_expr.left().is_some_and(|l| {
matches!(
l.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
)
})
&& assign_expr
.right()
.is_some_and(|r| names_any(r, &spec.read_signals))
&& !spec
.read_signals
.values()
.any(|i| crate::huge::is_huge_width(i.width));
let whole_self_read = !two_phase
&& spec.force_site.is_none()
&& spec.port_source.is_none()
&& net_target.is_none()
&& !spec.read_signals.is_empty()
&& assign_expr.is_compound_assignment() != Some(true)
&& assign_expr
.left()
.is_some_and(|l| sv_sim_sim::symid::is_value_name(l.kind()))
&& assign_expr
.right()
.is_some_and(|r| names_any(r, &spec.read_signals))
&& !spec
.read_signals
.values()
.any(|i| crate::huge::is_huge_width(i.width));
let read_view: HashMap<SymbolId, SignalSlot> = if two_phase || whole_self_read {
let mut v = signals.clone();
v.extend(spec.read_signals.iter().map(|(k, i)| (k.clone(), *i)));
v
} else {
HashMap::new()
};
let view = if two_phase || whole_self_read {
&read_view
} else {
signals
};
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let void_ty = ctx.void_type();
let i8_ty = ctx.i8_type();
let eval_result_ty = ctx.struct_type(&[i32_ty.into(), i8_ty.array_type(32).into()], false);
let fn_ty = void_ty.fn_type(
&[ptr_ty.into(), ptr_ty.into(), ptr_ty.into(), i32_ty.into()],
false,
);
let f = module.add_function(&fn_name, fn_ty, None);
let sret_kind_id = Attribute::get_named_enum_kind_id("sret");
f.add_attribute(
AttributeLoc::Param(0),
ctx.create_type_attribute(sret_kind_id, eval_result_ty.as_any_type_enum()),
);
f.get_nth_param(0).unwrap().set_name("sret");
f.get_nth_param(1).unwrap().set_name("handle");
f.get_nth_param(2).unwrap().set_name("local");
f.get_nth_param(3).unwrap().set_name("resume_pc");
let entry_bb = ctx.append_basic_block(f, "entry");
let b = ctx.create_builder();
b.position_at_end(entry_bb);
let sret = f.get_nth_param(0).unwrap().into_pointer_value();
let handle = f.get_nth_param(1).unwrap().into_pointer_value();
let mut cx = LowerCtx {
ctx,
b,
repr: ValueRepr::FourState,
locals: HashMap::new(),
fn_sym,
module,
mem_signals: mem_signals_of(view),
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 force_rhs = spec.force_site.and(assign_expr.right());
let (mut refs, mut written) = external_refs_in_expr(force_rhs.unwrap_or(assign_expr), signals);
if force_rhs.is_some() {
written.clear();
}
let class_static_init = spec.var_init
&& net_target.is_some_and(|n| {
n.kind() == sv_lang::kinds::SymbolKind::ClassProperty
&& n.variable_lifetime() == sv_lang::VariableLifetime::Static
});
if class_static_init
&& let Some(class_sym) = net_target.and_then(|n| n.parent())
&& let Ok(members) = sv_sim_sim::netlist::classify_members(class_sym)
{
for init in members
.statics
.iter()
.filter_map(|s| s.initializer())
.filter(|e| e.constant_value().is_none())
{
let (r, w) = external_refs_in_expr(init, signals);
for sym in r {
if !refs.iter().any(|s| s.uid() == sym.uid()) {
refs.push(sym);
}
}
written.extend(w);
}
}
if let Some(net) = net_target
&& !class_static_init
{
if !signals.contains_key(&net.uid()) {
return Err(LoweringError::unsupported_at(
"net declaration assignment to a net with no signal slot",
&net,
));
}
if !matches!(signals[&net.uid()].class, SignalSlotClass::Memory { .. }) {
if !refs.iter().any(|s| s.uid() == net.uid()) {
refs.push(net);
}
written.insert(net.uid());
}
}
if let Some(src) = spec.port_source {
if !signals.contains_key(&src.uid()) {
return Err(LoweringError::unsupported_at(
"output port connection whose port has no signal slot in the parent",
&src,
));
}
if !refs.iter().any(|s| s.uid() == src.uid()) {
refs.push(src);
}
}
let mut inline_reads = crate::accessor::InlineReads::new(
refs.iter()
.filter(|sym| {
let info = view[&sym.uid()];
info.class == SignalSlotClass::Inline && !crate::huge::is_huge_width(info.width)
})
.count(),
);
for sym in refs {
let info = view[&sym.uid()];
if crate::huge::is_huge_width(info.width) {
let ptr = crate::huge::rt_prime(&cx, handle, info.slot, info.width, false);
cx.locals.insert(sym.uid(), (ptr, info.width, info.signed));
continue;
}
let raw = match info.class {
SignalSlotClass::Inline => inline_reads.get(&cx, fns, handle, info.slot, info.width),
SignalSlotClass::Wide => build_get_wide(&cx, fns, handle, info.slot, info.width),
SignalSlotClass::Memory { .. } => {
unreachable!("external_refs_in_expr excludes SignalSlotClass::Memory symbols")
}
};
let packed_ty = int_type(cx.ctx, info.width * 2);
let ptr = crate::types::entry_alloca_in(&cx.b, packed_ty, "ext_shadow");
cx.b.build_store(ptr, raw).unwrap();
cx.locals.insert(sym.uid(), (ptr, info.width, info.signed));
}
for v in sv_sim_sim::patterns::pattern_vars(assign_expr.as_sem_node()) {
let ty = v
.value_type()
.ok_or_else(|| LoweringError::unsupported_at("a pattern variable with no type", &v))?;
let width = ty.value_width();
let ptr = crate::types::entry_alloca_in(&cx.b, int_type(cx.ctx, width * 2), "pat_var");
cx.b.build_store(ptr, int_type(cx.ctx, width * 2).const_zero())
.unwrap();
cx.locals.insert(v.uid(), (ptr, width, ty.is_signed()));
}
match net_target {
None if let Some(site) = spec.force_site => {
let lhs = assign_expr
.left()
.ok_or_else(|| LoweringError::unsupported_at("a force with no target", &fn_sym))?;
let row_target = crate::force::row_target(&cx, lhs)?;
let plain = if row_target.is_none() {
Some(crate::force::target(&cx, lhs, signals)?)
} else {
None
};
let width = row_target
.as_ref()
.map_or_else(|| plain.as_ref().map_or(0, |p| p.2), |rt| rt.width);
let active_fn = module
.get_function(crate::force::SV_RT_FORCE_ACTIVE_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
crate::force::SV_RT_FORCE_ACTIVE_SYMBOL,
i32_ty.fn_type(&[ptr_ty.into(), ctx.i64_type().into()], false),
Some(Linkage::External),
)
});
let active =
cx.b.build_call(
active_fn,
&[handle.into(), ctx.i64_type().const_int(site, false).into()],
"force_active",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let on =
cx.b.build_int_compare(
inkwell::IntPredicate::NE,
active,
i32_ty.const_zero(),
"force_on",
)
.unwrap();
let then_bb = ctx.append_basic_block(f, "force_then");
let done_bb = ctx.append_basic_block(f, "force_done");
cx.b.build_conditional_branch(on, then_bb, done_bb).unwrap();
cx.b.position_at_end(then_bb);
let rhs = force_rhs.expect("a force driver has a right-hand side");
let op = OpBuilder { ctx, b: &cx.b };
let v = crate::real::to_storage(&cx, lower_expr(&cx, rhs)?, width, "a forced value")?;
let v = crate::select::fit_width(&op, v, width);
let flags = sv_sim_sim::sched::FORCE_REFRESH
| if site & crate::force::PROCEDURAL_SITE_BIT != 0 {
sv_sim_sim::sched::FORCE_PROCEDURAL_ASSIGN
} else {
0
};
if let Some(rt) = &row_target {
crate::force::emit_force_row(&cx, handle, rt, Some(v), flags, site);
} else if let Some((slot, lo, width, is_net, _)) = &plain {
crate::force::emit_force(
&cx,
handle,
slot.slot,
*lo,
*width,
v,
u32::from(*is_net) | flags,
site,
);
}
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
}
None if let Some(hidden) = spec.hidden_target => {
if crate::huge::is_huge_width(hidden.width)
|| matches!(hidden.class, SignalSlotClass::Memory { .. })
{
return Err(LoweringError::unsupported_at(
"a primitive input terminal expression wider than the inline value path",
&fn_sym,
));
}
let op = OpBuilder { ctx, b: &cx.b };
let rhs = lower_expr(&cx, assign_expr)?;
let v = crate::real::to_storage(
&cx,
rhs,
hidden.width,
"a primitive input terminal expression",
)?;
let v = crate::select::fit_width(&op, v, hidden.width).llvm;
match hidden.class {
SignalSlotClass::Inline => {
build_set_scalar(&cx, fns, handle, hidden.slot, v, hidden.width);
}
_ => build_set_wide(&cx, fns, handle, hidden.slot, v, hidden.width),
}
}
None if let Some(src) = spec.port_source => {
let lhs = assign_expr.left().ok_or_else(|| {
LoweringError::unsupported_at("an output port connection with no target", &fn_sym)
})?;
let (ptr, width, signed) = cx.locals[&src.uid()];
let value = if crate::huge::is_huge_width(width) {
crate::expr::LoweredValue::Huge(crate::huge::HugeValue { ptr, width, signed })
} else {
let v =
cx.b.build_load(int_type(ctx, width * 2), ptr, "port_ld")
.unwrap()
.into_int_value();
let pv = PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
};
if src.value_type().is_some_and(|t| t.is_floating()) {
crate::expr::LoweredValue::Real(crate::real::bits_to_real(&cx, pv))
} else {
let target = lhs.expr_type().map(|t| t.value_width()).unwrap_or(width);
crate::expr::LoweredValue::Packed(crate::select::fit_width(
&OpBuilder { ctx, b: &cx.b },
pv,
target,
))
}
};
crate::expr::store_to(&cx, lhs, value)?;
}
None if two_phase => {
let lhs = assign_expr.left().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing lvalue", &fn_sym)
})?;
let rhs_expr = assign_expr.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &fn_sym)
})?;
if crate::collections::coll_of_expr(&lhs).is_some() {
return Err(LoweringError::unsupported_at(
"a continuous assign driving a collection of a net whose right-hand \
side reads that net",
&fn_sym,
));
}
if let Some((_, shape)) = crate::arrays::array_target(&cx, lhs)? {
if !crate::collections::handle_offsets(&shape.elem).is_empty() {
return Err(LoweringError::unsupported_at(
"a continuous assign of an unpacked-array net slice whose \
elements hold class handles or collections and whose \
right-hand side reads that net",
&fn_sym,
));
}
let tmp = crate::expr::entry_alloca(
&cx,
crate::arrays::local_array_type(ctx, &shape, cx.repr),
"arr_cont_tmp",
);
let tmp_ref =
crate::arrays::ArrayRef::whole(&cx, crate::arrays::Backing::Local(tmp));
crate::arrays::assign_array_into_fresh(&cx, tmp_ref, &shape, rhs_expr)?;
cx.mem_signals = mem_signals_of(signals);
let Some((dst, dst_shape)) = crate::arrays::array_target(&cx, lhs)? else {
return Err(LoweringError::unsupported_at(
"an unpacked-array net slice target",
&fn_sym,
));
};
crate::arrays::copy_array(&cx, dst, &dst_shape, tmp_ref, &shape)?;
} else {
let rhs = crate::collections::owned(&cx, rhs_expr)?;
for id in spec.read_signals.keys() {
let Some(info) = signals.get(id) else {
continue;
};
if let Some(&(ptr, width, _)) = cx.locals.get(id) {
let raw = match info.class {
SignalSlotClass::Inline => {
build_get_scalar(&cx, fns, handle, info.slot, info.width)
}
SignalSlotClass::Wide => {
build_get_wide(&cx, fns, handle, info.slot, info.width)
}
SignalSlotClass::Memory { .. } => continue,
};
debug_assert_eq!(width, info.width);
cx.b.build_store(ptr, raw).unwrap();
}
}
cx.mem_signals = mem_signals_of(signals);
if let Some(r) = crate::arrays::resolve_element(&cx, lhs)? {
let op = OpBuilder { ctx, b: &cx.b };
let rhs_pv = crate::real::to_storage(
&cx,
rhs,
r.width,
"assignment of a >128-bit value to an array element",
)?;
let rhs_pv = crate::select::fit_width(&op, rhs_pv, r.width);
crate::arrays::write_element(&cx, &r, rhs_pv)?;
} else {
crate::collections::store_releasing(&cx, lhs, rhs)?;
}
}
}
None => {
lower_expr(&cx, assign_expr)?;
}
Some(var) if class_static_init => {
let class_sym = var.parent().ok_or_else(|| {
LoweringError::unsupported_at("class property has no enclosing class", &var)
})?;
crate::classes::run_dynamic_static_initializers(&cx, class_sym)?;
}
Some(var)
if spec.var_init
&& let Some(info) = cx.mem_signals.get(&var.uid()).copied()
&& let Some(shape) = var
.value_type()
.as_ref()
.and_then(crate::arrays::array_shape) =>
{
let dst = crate::arrays::ArrayRef::whole(&cx, crate::arrays::Backing::Memory(info));
crate::arrays::assign_array_into_fresh(&cx, dst, &shape, assign_expr)?;
}
Some(var) if spec.var_init => {
if crate::events::is_pool_event_var(&cx, &var) {
crate::events::init_event_var(&cx, &var)?;
} else {
let (ptr, width, signed) = cx.locals[&var.uid()];
let v = local_init_value(&cx, &var, width, signed)?;
cx.b.build_store(ptr, v.llvm).unwrap();
}
}
Some(net)
if let Some(info) = cx.mem_signals.get(&net.uid()).copied()
&& let Some(shape) = net
.value_type()
.as_ref()
.and_then(crate::arrays::array_shape) =>
{
let dst = crate::arrays::ArrayRef::whole(&cx, crate::arrays::Backing::Memory(info));
crate::arrays::assign_array_into_fresh(&cx, dst, &shape, assign_expr)?;
}
Some(net) => {
let width = net.value_type().map(|t| t.value_width()).unwrap_or(0);
let rhs = lower_expr(&cx, assign_expr)?;
if !crate::huge::try_store_symbol(&cx, &net, rhs)? {
let value = crate::real::to_storage(
&cx,
rhs,
width,
"a >128-bit net declaration assignment",
)?;
crate::select::store_whole(&cx, &net, value)?;
}
}
}
let mut written_sorted: Vec<&SymbolId> = written.iter().collect();
written_sorted.sort();
for id in written_sorted {
let (ptr, width, _signed) = cx.locals[id];
let info = signals[id];
if crate::huge::is_huge_width(width) {
let _ = ptr;
let i64t = cx.ctx.i64_type();
crate::huge::rt_mark(
&cx,
handle,
info.slot,
width,
i64t.const_zero(),
i64t.const_int(u64::from(width), false),
);
crate::huge::rt_commit(&cx, handle, info.slot, width);
continue;
}
let packed_ty = int_type(cx.ctx, width * 2);
let cur = cx.b.build_load(packed_ty, ptr, "ext_flush_ld").unwrap();
match info.class {
SignalSlotClass::Inline => build_set_scalar(
&cx,
fns,
handle,
info.slot,
cur.into_int_value(),
info.width,
),
SignalSlotClass::Wide => build_set_wide(
&cx,
fns,
handle,
info.slot,
cur.into_int_value(),
info.width,
),
SignalSlotClass::Memory { .. } => {
unreachable!("external_refs_in_expr excludes SignalSlotClass::Memory symbols")
}
}
}
let done_er = build_eval_result(ResolvedSuspend::Done, 0);
let done_bytes: [u8; 36] = unsafe { core::mem::transmute(done_er) };
store_eval_result_bytes_and_return(ctx, &cx.b, sret, &done_bytes);
if f.verify(true) {
Ok(f)
} else {
Err(LoweringError {
message: "continuous-assign kernel function failed LLVM verify".into(),
at: fn_sym.hierarchical_path(),
})
}
}
pub fn emit_process_table<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
kernels: &[FunctionValue<'ctx>],
) -> GlobalValue<'ctx> {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let arr_ty = ptr_ty.array_type(kernels.len() as u32);
let elems: Vec<PointerValue<'ctx>> = kernels
.iter()
.map(|f| f.as_global_value().as_pointer_value())
.collect();
let const_arr = ptr_ty.const_array(&elems);
let global = module.add_global(
arr_ty,
None,
sv_sim_abi::process::SV_SIM_PROCESS_TABLE_SYMBOL,
);
global.set_initializer(&const_arr);
global.set_linkage(Linkage::External);
global
}
#[derive(Clone, Debug, PartialEq)]
pub struct ProcessSideTable {
pub canonical_process_id: u64,
pub region_id: u32,
pub write_set: Vec<(u32, u32)>,
pub dpi_tag: DpiVpiTag,
}
pub fn build_process_side_table(
plan: &ProcessLoweringPlan<'_>,
canonical_process_id: sv_sim_sim::process::ProcessInstanceId,
) -> ProcessSideTable {
ProcessSideTable {
canonical_process_id: canonical_process_id.0,
region_id: plan.region_id,
write_set: write_set_of_segments(&plan.segments, &plan.signals),
dpi_tag: classify_dpi_tag(&plan.segments),
}
}
pub fn emit_process_side_table<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
tables: &[ProcessSideTable],
) -> (GlobalValue<'ctx>, GlobalValue<'ctx>) {
let i64_ty = ctx.i64_type();
let i32_ty = ctx.i32_type();
let write_set_entry_ty = ctx.struct_type(&[i32_ty.into(), i32_ty.into()], false);
let entry_ty = ctx.struct_type(
&[
i64_ty.into(),
i32_ty.into(),
i32_ty.into(),
i32_ty.into(),
i32_ty.into(),
],
false,
);
let mut flat_write_set: Vec<StructValue<'ctx>> = Vec::new();
let mut entries: Vec<StructValue<'ctx>> = Vec::with_capacity(tables.len());
for table in tables {
let offset = flat_write_set.len() as u32;
for &(slot, width) in &table.write_set {
flat_write_set.push(write_set_entry_ty.const_named_struct(&[
i32_ty.const_int(u64::from(slot), false).into(),
i32_ty.const_int(u64::from(width), false).into(),
]));
}
let len = table.write_set.len() as u32;
entries.push(entry_ty.const_named_struct(&[
i64_ty.const_int(table.canonical_process_id, false).into(),
i32_ty.const_int(u64::from(table.region_id), false).into(),
i32_ty.const_int(table.dpi_tag as u64, false).into(),
i32_ty.const_int(u64::from(offset), false).into(),
i32_ty.const_int(u64::from(len), false).into(),
]));
}
let write_set_arr_ty = write_set_entry_ty.array_type(flat_write_set.len() as u32);
let write_set_const = write_set_entry_ty.const_array(&flat_write_set);
let write_set_global = module.add_global(
write_set_arr_ty,
None,
sv_sim_abi::process::SV_SIM_PROCESS_WRITE_SET_SYMBOL,
);
write_set_global.set_initializer(&write_set_const);
write_set_global.set_linkage(Linkage::External);
write_set_global.set_constant(true);
let side_table_arr_ty = entry_ty.array_type(entries.len() as u32);
let side_table_const = entry_ty.const_array(&entries);
let side_table_global = module.add_global(
side_table_arr_ty,
None,
sv_sim_abi::process::SV_SIM_PROCESS_SIDE_TABLE_SYMBOL,
);
side_table_global.set_initializer(&side_table_const);
side_table_global.set_linkage(Linkage::External);
side_table_global.set_constant(true);
(side_table_global, write_set_global)
}
fn write_set_of_segments(
segments: &[Segment<'_>],
signals: &HashMap<SymbolId, SignalSlot>,
) -> Vec<(u32, u32)> {
let mut seen: HashSet<u32> = HashSet::new();
let mut out = Vec::new();
for seg in segments {
let Ok(signals) = seg_signals(signals, seg) else {
continue;
};
for stmt in &seg.body {
if effect_of_statement(stmt) != Effect::StateWrite {
continue;
}
let Some(e) = stmt.expr() else { continue };
let Some(lhs) = e.left() else { continue };
if !sv_sim_sim::symid::is_value_name(lhs.kind()) {
continue;
}
let Some(sym) = lhs.referenced_symbol() else {
continue;
};
let Some(slot) = signals.get(&sym.uid()) else {
continue;
};
if seen.insert(slot.slot) {
out.push((slot.slot, slot.width));
}
}
}
out
}
fn classify_dpi_tag(segments: &[Segment<'_>]) -> DpiVpiTag {
if segments.iter().any(segment_has_unprovable_call) {
DpiVpiTag::Barrier
} else {
DpiVpiTag::Pure
}
}
fn segment_has_unprovable_call(seg: &Segment<'_>) -> bool {
fn scan(node: SemNode<'_>) -> bool {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& e.kind() == ExpressionKind::Call
&& e.is_system_call() != Some(true)
{
return true;
}
stack.extend(n.children());
}
false
}
if seg.body.iter().any(|stmt| scan(stmt.as_sem_node())) {
return true;
}
if let Some((_, count)) = &seg.repeat_init
&& let Some(count_expr) = count.expr()
&& scan(count_expr.as_sem_node())
{
return true;
}
match &seg.terminator {
SegTerminator::SwitchGoto { on, .. } => on.reads().iter().any(|e| scan(e.as_sem_node())),
SegTerminator::CondGoto { cond, .. } => scan(cond.as_sem_node()),
SegTerminator::AssertGoto { stmt, .. } => {
sv_sim_sim::process::cfg::assertion_cond(stmt).is_some_and(|e| scan(e.as_sem_node()))
}
SegTerminator::CaseGoto { stmt, .. } => sv_sim_sim::process::liveness::case_reads(stmt)
.iter()
.any(|e| scan(e.as_sem_node())),
SegTerminator::Suspend {
point: SuspendPoint::Delay(e, _) | SuspendPoint::Level(e),
..
} => scan(e.as_sem_node()),
SegTerminator::Suspend {
point: SuspendPoint::EdgeList(entries, iffs),
..
} => {
entries.iter().any(|(e, _)| scan(e.as_sem_node()))
|| iffs.iter().flatten().any(|g| scan(g.as_sem_node()))
}
SegTerminator::Suspend {
point: SuspendPoint::Edge { iff: Some(g), .. },
..
} => scan(g.as_sem_node()),
SegTerminator::Suspend {
point: SuspendPoint::ImplicitComb(reads),
..
} => reads.iter().any(|e| scan(e.as_sem_node())),
SegTerminator::Suspend {
point:
SuspendPoint::Edge { .. }
| SuspendPoint::CycleDelay { .. }
| SuspendPoint::OneStep
| SuspendPoint::WaitFork
| SuspendPoint::ByRequest,
..
}
| SegTerminator::Goto(_)
| SegTerminator::CondGotoCounter { .. }
| SegTerminator::ForkJoin { .. }
| SegTerminator::End { .. } => false,
}
}
fn open_cell<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
b: &Builder<'ctx>,
local: PointerValue<'ctx>,
slot: &sv_sim_sim::process::liveness::FrameSlotDesc,
) -> (PointerValue<'ctx>, PointerValue<'ctx>, u32) {
let words = match slot.repr {
sv_sim_sim::process::FrameSlot::Narrow4State { word_count } => u32::from(word_count),
sv_sim_sim::process::FrameSlot::Array { rows, row_words } => rows * u32::from(row_words),
sv_sim_sim::process::FrameSlot::Wide4State {
aval_word_count,
bval_word_count,
} => aval_word_count + bval_word_count,
};
let bytes = words * 8;
let i64t = ctx.i64_type();
let shadow = crate::types::entry_alloca_in(b, i64t.array_type(words), "shared_local");
let cell_ptr = crate::types::entry_alloca_in(b, i64t, "cell_ptr");
let src = frame_slot_ptr(ctx, b, local, slot.offset);
let p = b.build_load(i64t, src, "cell_ptr_ld").unwrap();
b.build_store(cell_ptr, p).unwrap();
let from = cell_or_scratch(ctx, module, b, p.into_int_value(), bytes);
b.build_memcpy(shadow, 8, from, 8, i64t.const_int(u64::from(bytes), false))
.unwrap();
(shadow, cell_ptr, bytes)
}
fn cell_or_scratch<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
b: &Builder<'ctx>,
p: IntValue<'ctx>,
bytes: u32,
) -> PointerValue<'ctx> {
let name = format!("sv_sim_cell_scratch_{bytes}");
let scratch = module.get_global(&name).unwrap_or_else(|| {
let ty = ctx.i64_type().array_type(bytes.div_ceil(8));
let g = module.add_global(ty, None, &name);
g.set_linkage(Linkage::Internal);
g.set_initializer(&ty.const_zero());
g
});
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let as_ptr = b.build_int_to_ptr(p, ptr_ty, "cell").unwrap();
let null = b
.build_int_compare(
inkwell::IntPredicate::EQ,
p,
ctx.i64_type().const_zero(),
"cell_null",
)
.unwrap();
b.build_select(null, scratch.as_pointer_value(), as_ptr, "cell_or_scratch")
.unwrap()
.into_pointer_value()
}
fn flush_cells(cx: &LowerCtx<'_, '_, '_>) {
let mut ids: Vec<&SymbolId> = cx.cells.keys().collect();
ids.sort_by(|a, b| a.path().cmp(b.path()));
for id in ids {
let (cell_ptr, bytes) = cx.cells[id];
let Some(&(shadow, _, _)) = cx.locals.get(id) else {
continue;
};
let p =
cx.b.build_load(cx.ctx.i64_type(), cell_ptr, "cell_ptr")
.unwrap()
.into_int_value();
let to = cell_or_scratch(cx.ctx, cx.module, &cx.b, p, bytes);
let i64t = cx.ctx.i64_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let store = cx
.module
.get_function(SV_RT_CELL_STORE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_CELL_STORE_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into(), i64t.into()], false),
Some(Linkage::External),
)
});
cx.b.build_call(
store,
&[
to.into(),
shadow.into(),
i64t.const_int(u64::from(bytes), false).into(),
],
"cell_store",
)
.unwrap();
}
}
fn fresh_cell(cx: &LowerCtx<'_, '_, '_>, var: &Symbol<'_>) {
let Some(&(cell_ptr, bytes)) = cx.cells.get(&var.uid()) else {
return;
};
let i64t = cx.ctx.i64_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let alloc = cx
.module
.get_function(SV_RT_CELL_ALLOC_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_CELL_ALLOC_SYMBOL,
ptr_ty.fn_type(&[i64t.into()], false),
Some(Linkage::External),
)
});
let cell =
cx.b.build_call(
alloc,
&[i64t.const_int(u64::from(bytes), false).into()],
"cell",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_pointer_value();
let as_int = cx.b.build_ptr_to_int(cell, i64t, "cell_int").unwrap();
cx.b.build_store(cell_ptr, as_int).unwrap();
}
struct LocalEventTerm<'d> {
index: usize,
var: Symbol<'d>,
edge: sv_lang::EdgeKind,
}
fn edge_code_of(edge: sv_lang::EdgeKind) -> u64 {
match edge {
sv_lang::EdgeKind::None => 0,
sv_lang::EdgeKind::PosEdge => 1,
sv_lang::EdgeKind::NegEdge => 2,
sv_lang::EdgeKind::BothEdges => 3,
}
}
fn local_event_terms<'d>(
point: &SuspendPoint<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Vec<LocalEventTerm<'d>> {
let entries: Vec<(Expression<'d>, sv_lang::EdgeKind)> = match point {
SuspendPoint::Edge { signal, edge, .. } => vec![(*signal, *edge)],
SuspendPoint::EdgeList(entries, ..) => entries.clone(),
_ => return Vec::new(),
};
let mut out = Vec::new();
for (index, (e, edge)) in entries.into_iter().enumerate() {
if !sv_sim_sim::symid::is_value_name(e.kind()) || event_elem_signal(&e, signals).is_some() {
continue;
}
let Some(var) = e.referenced_symbol() else {
continue;
};
if signals.contains_key(&var.uid())
|| !matches!(
var.kind(),
sv_lang::kinds::SymbolKind::Variable | sv_lang::kinds::SymbolKind::FormalArgument
)
{
continue;
}
out.push(LocalEventTerm { index, var, edge });
}
out
}
struct EventElemTerm<'d> {
index: usize,
expr: Expression<'d>,
}
fn is_event_elem(e: &Expression<'_>) -> bool {
e.kind() == ExpressionKind::ElementSelect && e.expr_type().is_some_and(|t| t.is_event())
}
fn event_elem_array<'d>(e: &Expression<'d>) -> Option<Symbol<'d>> {
let mut cur = *e;
while cur.kind() == ExpressionKind::ElementSelect {
cur = cur.select_value()?;
}
if sv_sim_sim::symid::is_value_name(cur.kind()) {
cur.referenced_symbol()
} else {
None
}
}
fn event_elem_signal<'a, 'd>(
e: &Expression<'d>,
signals: &'a HashMap<SymbolId, SignalSlot>,
) -> Option<&'a SignalSlot> {
if e.expr_type().is_some_and(|t| t.is_event()) {
if let Some(var) = crate::events::hierarchical_event_var(e)
&& let Some(pool) = signals.get(&sv_sim_sim::state::event_pool::pool_of_key(&var.uid()))
{
return Some(pool);
}
if let Some(pool) = signals.get(&sv_sim_sim::state::event_pool::pool_key()) {
return Some(pool);
}
}
if !is_event_elem(e) {
return None;
}
signals
.get(&event_elem_array(e)?.uid())
.filter(|s| matches!(s.class, SignalSlotClass::Memory { .. }))
}
fn constant_select_of_signal<'a, 'd>(
e: &Expression<'d>,
signals: &'a HashMap<SymbolId, SignalSlot>,
) -> Option<(&'a SignalSlot, u32, u32)> {
if !matches!(
e.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
return None;
}
let mut root = *e;
while matches!(
root.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
root = root.select_value()?;
if root.expr_type().is_some_and(|t| t.is_unpacked_array()) {
return None;
}
}
let info = signals.get(&root.referenced_symbol()?.uid())?;
if matches!(info.class, SignalSlotClass::Memory { .. }) {
return None;
}
let (lsb, width) = crate::select::constant_window(*e, info.width).ok()?;
Some((info, lsb, width))
}
pub fn comb_read_windows<'d>(
body: &Statement<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> HashMap<SymbolId, Vec<(u32, u32)>> {
use sv_sim_sim::process::cfg::live_children;
let mut windows: HashMap<SymbolId, Vec<(u32, u32)>> = HashMap::new();
let mut whole: HashSet<SymbolId> = HashSet::new();
let mut stack = vec![body.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression() {
if e.kind() == ExpressionKind::Call && e.is_system_call() != Some(true) {
return HashMap::new();
}
if let Some((_, lsb, width)) = constant_select_of_signal(&e, signals) {
let mut root = e;
while matches!(
root.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
match root.select_value() {
Some(r) => root = r,
None => break,
}
}
if let Some(sym) = root.referenced_symbol() {
let w = windows.entry(sym.uid()).or_default();
if !w.contains(&(lsb, width)) {
w.push((lsb, width));
}
}
continue;
}
if sv_sim_sim::symid::is_value_name(e.kind()) {
if let Some(sym) = e.referenced_symbol() {
whole.insert(sym.uid());
}
continue;
}
if e.kind() == ExpressionKind::Assignment {
let lhs_is_read = e.is_compound_assignment() == Some(true);
if let Some(lhs) = e.left() {
let skip = !lhs_is_read
&& (sv_sim_sim::symid::is_value_name(lhs.kind())
|| constant_select_of_signal(&lhs, signals).is_some());
if !skip {
stack.push(lhs.as_sem_node());
}
}
if let Some(rhs) = e.right() {
stack.push(rhs.as_sem_node());
}
continue;
}
}
stack.extend(live_children(&n));
}
for id in whole {
windows.remove(&id);
}
windows
}
fn event_elem_terms<'d>(
point: &SuspendPoint<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Vec<EventElemTerm<'d>> {
let entries: Vec<Expression<'d>> = match point {
SuspendPoint::Edge { signal, .. } => vec![*signal],
SuspendPoint::EdgeList(entries, ..) => entries.iter().map(|(e, _)| *e).collect(),
_ => return Vec::new(),
};
entries
.into_iter()
.enumerate()
.filter(|(_, e)| {
event_elem_signal(e, signals).is_some() || crate::vif::event_elem_root(e).is_some()
})
.map(|(index, expr)| EventElemTerm { index, expr })
.collect()
}
struct ExprEventTerm<'d> {
index: usize,
expr: Expression<'d>,
edge: sv_lang::EdgeKind,
}
fn is_expr_event_term(e: &Expression<'_>, signals: &HashMap<SymbolId, SignalSlot>) -> bool {
if e.kind() == ExpressionKind::AssertionInstance
|| event_elem_signal(e, signals).is_some()
|| crate::vif::event_elem_root(e).is_some()
|| constant_select_of_signal(e, signals).is_some()
|| crate::vif::event_base(e).is_some()
{
return false;
}
let class_field = sv_sim_sim::symid::is_value_name(e.kind())
&& crate::classes::reads_object_state(e)
&& e.referenced_symbol().is_some_and(|s| {
!signals.contains_key(&s.uid())
&& s.value_type()
.as_ref()
.and_then(sv_sim_sim::state::event_pool::event_count)
.is_none()
});
if class_field {
return true;
}
let plain = e.referenced_symbol().is_some_and(|s| {
sv_sim_sim::symid::is_value_name(e.kind())
|| s.kind() == sv_lang::kinds::SymbolKind::ClockingBlock
});
!plain
}
fn expr_event_terms<'d>(
point: &SuspendPoint<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
) -> Vec<ExprEventTerm<'d>> {
let entries: Vec<(Expression<'d>, sv_lang::EdgeKind)> = match point {
SuspendPoint::Edge { signal, edge, .. } => vec![(*signal, *edge)],
SuspendPoint::EdgeList(entries, ..) => entries.clone(),
_ => return Vec::new(),
};
entries
.into_iter()
.enumerate()
.filter(|(_, (e, _))| is_expr_event_term(e, signals))
.map(|(index, (expr, edge))| ExprEventTerm { index, expr, edge })
.collect()
}
fn expr_dependency_slots<'d>(
e: &Expression<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
fn_sym: &Symbol<'d>,
) -> Vec<u32> {
let mut slots: Vec<u32> = Vec::new();
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& let Some(inst) = sv_sim_sim::sva::endpoint::watched_call(&x)
&& let Some(info) =
signals.get(&sv_sim_sim::sva::endpoint::watcher_id(&fn_sym.uid(), &inst))
&& !slots.contains(&info.slot)
{
slots.push(info.slot);
}
stack.extend(n.children());
}
let mut found = Vec::new();
collect_named_value_symbols(e.as_sem_node(), &mut found);
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression() {
found.extend(vif_candidates(&x));
}
stack.extend(n.children());
}
for sym in found {
let pooled_event = signals
.get(&sv_sim_sim::state::event_pool::pool_of_key(&sym.uid()))
.or_else(|| signals.get(&sv_sim_sim::state::event_pool::pool_key()))
.filter(|_| {
sym.value_type()
.as_ref()
.and_then(sv_sim_sim::state::event_pool::event_count)
.is_some()
});
if let Some(pool) = pooled_event {
if !slots.contains(&pool.slot) {
slots.push(pool.slot);
}
} else if let Some(info) = signals.get(&sym.uid()) {
if !slots.contains(&info.slot) {
slots.push(info.slot);
}
} else if matches!(
sym.kind(),
sv_lang::kinds::SymbolKind::Variable | sv_lang::kinds::SymbolKind::FormalArgument
) && !slots.contains(&sv_sim_sim::sched::SHARED_VARS_SLOT)
{
slots.push(sv_sim_sim::sched::SHARED_VARS_SLOT);
}
}
if crate::classes::reads_object_state(e)
&& !slots.contains(&sv_sim_sim::sched::OBJECT_STATE_SLOT)
{
slots.push(sv_sim_sim::sched::OBJECT_STATE_SLOT);
}
slots
}
const EVENT_ELEM_SNAPSHOT_BYTES: u32 = 24;
const EVENT_ELEM_STORE_BYTES: u32 = 40;
fn event_elem_site(resume: u32, index: usize) -> u64 {
local_event_site(resume, index) | sv_sim_sim::sched::ELEM_SITE_BIT
}
fn event_elem_snapshot<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
local: PointerValue<'ctx>,
site: u64,
expr: Expression<'d>,
) -> Result<(), LoweringError> {
let i64t = cx.ctx.i64_type();
let (el, row, valid, count, slot) =
if let Some(cands) = crate::vif::event_elem_candidates(cx, expr) {
let cands = cands?;
let mut acc: Option<(
IntValue<'ctx>,
IntValue<'ctx>,
IntValue<'ctx>,
IntValue<'ctx>,
)> = None;
for (el, code) in &cands {
let cell = crate::arrays::load_event_elem(cx, el)?;
let count = cx.b.build_int_truncate(cell, i64t, "ev_count").unwrap();
let code = i64t.const_int(*code, false);
let slot = i64t.const_int(u64::from(el.info.slot), false);
acc = Some(match acc {
None => (el.row, code, count, slot),
Some((r, v, c, s)) => {
let pick = |new: IntValue<'ctx>, old: IntValue<'ctx>| {
cx.b.build_select(el.valid, new, old, "ev_pick")
.unwrap()
.into_int_value()
};
(
pick(el.row, r),
pick(code, v),
pick(count, c),
pick(slot, s),
)
}
});
}
let Some((row, code, count, slot)) = acc else {
return Ok(());
};
let any = cands
.iter()
.map(|(el, _)| el.valid)
.try_fold(None::<IntValue<'ctx>>, |a, v| {
Ok::<_, LoweringError>(Some(match a {
Some(a) => cx.b.build_or(a, v, "ev_any_sel").unwrap(),
None => v,
}))
})?
.expect("a candidate");
let valid =
cx.b.build_select(any, code, i64t.const_zero(), "ev_code")
.unwrap()
.into_int_value();
(cands[0].0, row, valid, count, slot)
} else {
let Some(el) = crate::arrays::resolve_event_element(cx, expr)? else {
return Ok(());
};
let cell = crate::arrays::load_event_elem(cx, &el)?;
let count = cx.b.build_int_truncate(cell, i64t, "ev_count").unwrap();
let valid = cx.b.build_int_z_extend(el.valid, i64t, "ev_valid").unwrap();
let slot = i64t.const_int(u64::from(el.info.slot), false);
(el, el.row, valid, count, slot)
};
let acc = cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at("event element wait with no accessor context", &cx.fn_sym)
})?;
let handle =
cx.b.build_ptr_to_int(acc.handle, i64t, "ev_handle")
.unwrap();
let _ = el;
let buf = crate::types::entry_alloca_in(&cx.b, i64t.array_type(5), "ev_snap");
for (i, v) in [row, valid, count, slot, handle].into_iter().enumerate() {
let p = unsafe {
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(i as u64, false)],
"ev_snap_field",
)
.unwrap()
};
cx.b.build_store(p, v).unwrap();
}
edge_snapshot_call(cx, local, site, buf, EVENT_ELEM_STORE_BYTES);
Ok(())
}
fn local_event_site(resume: u32, index: usize) -> u64 {
(u64::from(resume) << 16) | index as u64
}
fn edge_snapshot_call<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
local: PointerValue<'ctx>,
site: u64,
value: PointerValue<'ctx>,
bytes: u32,
) {
let i64t = cx.ctx.i64_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let f = cx
.module
.get_function(SV_RT_EDGE_SNAPSHOT_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_EDGE_SNAPSHOT_SYMBOL,
cx.ctx.void_type().fn_type(
&[i64t.into(), i64t.into(), ptr_ty.into(), i64t.into()],
false,
),
Some(Linkage::External),
)
});
let frame = cx.b.build_ptr_to_int(local, i64t, "frame_id").unwrap();
cx.b.build_call(
f,
&[
frame.into(),
i64t.const_int(site, false).into(),
value.into(),
i64t.const_int(u64::from(bytes), false).into(),
],
"edge_snapshot",
)
.unwrap();
}
#[allow(clippy::too_many_arguments)]
fn local_event_guard<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plan: &ProcessLoweringPlan<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
point: &SuspendPoint<'d>,
resume: u32,
fn_name: &str,
value_repr: ValueRepr,
this: Option<Symbol<'d>>,
) -> Result<FunctionValue<'ctx>, LoweringError> {
let i64t = ctx.i64_type();
let i32t = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let f = module.add_function(
fn_name,
ctx.i8_type()
.fn_type(&[ptr_ty.into(), ptr_ty.into()], false),
None,
);
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let local = f.get_nth_param(1).unwrap().into_pointer_value();
let cx = LowerCtx {
ctx,
b: ctx.create_builder(),
repr: value_repr,
locals: HashMap::new(),
fn_sym: plan.fn_sym,
module,
mem_signals: 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 b = &cx.b;
b.position_at_end(ctx.append_basic_block(f, "entry"));
let terms = local_event_terms(point, signals);
let elem_terms = event_elem_terms(point, signals);
let expr_terms = expr_event_terms(point, signals);
let total = match point {
SuspendPoint::EdgeList(entries, ..) => entries.len(),
_ => 1,
};
let fired_fn = module
.get_function(SV_RT_FIRED_TERM_SYMBOL)
.unwrap_or_else(|| {
module.add_function(SV_RT_FIRED_TERM_SYMBOL, i64t.fn_type(&[], false), None)
});
let fired = b
.build_call(fired_fn, &[], "fired")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let mut by_signal = ctx.bool_type().const_zero();
for k in 0..total {
if terms.iter().any(|t| t.index == k)
|| elem_terms.iter().any(|t| t.index == k)
|| expr_terms.iter().any(|t| t.index == k)
{
continue;
}
let hit = b
.build_int_compare(
inkwell::IntPredicate::EQ,
fired,
i64t.const_int(k as u64, false),
"by_signal",
)
.unwrap();
by_signal = b.build_or(by_signal, hit, "by_signal_any").unwrap();
}
let check_fn = module
.get_function(SV_RT_EDGE_CHECK_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_EDGE_CHECK_SYMBOL,
i32t.fn_type(
&[
i64t.into(),
i64t.into(),
ptr_ty.into(),
i64t.into(),
i32t.into(),
i32t.into(),
],
false,
),
Some(Linkage::External),
)
});
let frame_id = b.build_ptr_to_int(local, i64t, "frame_id").unwrap();
let mut any = ctx.bool_type().const_zero();
for t in &terms {
let Some((idx, slot)) = plan.frame.slots.iter().enumerate().find(|(_, s)| {
s.cell && matches!(&s.source, FrameSlotSource::DeclaredVar(id) if *id == t.var.uid())
}) else {
continue;
};
let _ = idx;
let (shadow, _, bytes) = open_cell(ctx, module, b, local, slot);
let edge_code = edge_code_of(t.edge);
let r = b
.build_call(
check_fn,
&[
frame_id.into(),
i64t.const_int(local_event_site(resume, t.index), false)
.into(),
shadow.into(),
i64t.const_int(u64::from(bytes), false).into(),
i32t.const_int(u64::from(slot.width_bits), false).into(),
i32t.const_int(edge_code, false).into(),
],
"edge_check",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let ok = b
.build_int_compare(inkwell::IntPredicate::NE, r, i32t.const_zero(), "edge_hit")
.unwrap();
any = b.build_or(any, ok, "edge_any").unwrap();
}
let mut expr_hits = Vec::new();
for t in &expr_terms {
let (value_fn, width) = compile_event_value(
ctx,
module,
fns,
signals,
&plan.fn_sym,
&format!("{fn_name}_value{}", t.index),
t.expr,
value_repr,
(&plan.frame, &plan.frame_signed),
this,
)?;
let words = event_value_words(width);
let buf = crate::types::entry_alloca_in(b, i64t.array_type(words), "ev_value");
b.build_call(
value_fn,
&[handle.into(), local.into(), buf.into()],
"ev_eval",
)
.unwrap();
let r = b
.build_call(
check_fn,
&[
frame_id.into(),
i64t.const_int(local_event_site(resume, t.index), false)
.into(),
buf.into(),
i64t.const_int(u64::from(words) * 8, false).into(),
i32t.const_int(u64::from(width), false).into(),
i32t.const_int(edge_code_of(t.edge), false).into(),
],
"ev_value_check",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let hit = b
.build_int_compare(
inkwell::IntPredicate::NE,
r,
i32t.const_zero(),
"ev_value_hit",
)
.unwrap();
expr_hits.push((t.index, hit));
any = b.build_or(any, hit, "edge_any_expr").unwrap();
}
if !elem_terms.is_empty() || !expr_terms.is_empty() {
let load_fn = module
.get_function(SV_RT_EDGE_LOAD_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_EDGE_LOAD_SYMBOL,
ctx.void_type().fn_type(
&[i64t.into(), i64t.into(), ptr_ty.into(), i64t.into()],
false,
),
Some(Linkage::External),
)
});
let mut hits = Vec::new();
for t in &elem_terms {
let buf = crate::types::entry_alloca_in(b, i64t.array_type(3), "ev_snap");
b.build_call(
load_fn,
&[
frame_id.into(),
i64t.const_int(event_elem_site(resume, t.index), false)
.into(),
buf.into(),
i64t.const_int(u64::from(EVENT_ELEM_SNAPSHOT_BYTES), false)
.into(),
],
"ev_load",
)
.unwrap();
let field = |i: u64, name: &str| {
let p = unsafe {
b.build_in_bounds_gep(i64t, buf, &[i64t.const_int(i, false)], name)
.unwrap()
};
b.build_load(i64t, p, name).unwrap().into_int_value()
};
let row = field(0, "ev_row");
let was_valid = b
.build_int_compare(
inkwell::IntPredicate::NE,
field(1, "ev_valid"),
i64t.const_zero(),
"ev_was_valid",
)
.unwrap();
let before = field(2, "ev_before");
let elem_info = |array: &SignalSlot| MemorySignalInfo {
slot: array.slot,
elem_width: array.width,
signed: false,
num_rows: 0,
lower_index: 0,
};
let now = if let Some(root) = crate::vif::event_elem_root(&t.expr) {
let code = field(1, "ev_code");
let mut now = i64t.const_zero();
for (k, cand) in vif_candidates(&root).iter().enumerate() {
let array = signals.get(&cand.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"a virtual interface event array this process cannot reach",
cand,
)
})?;
let el = crate::arrays::EventElem {
info: elem_info(array),
row,
valid: ctx.bool_type().const_all_ones(),
};
let cell = crate::arrays::load_event_elem(&cx, &el)?;
let cnt = b.build_int_truncate(cell, i64t, "ev_now_k").unwrap();
let is_k = b
.build_int_compare(
inkwell::IntPredicate::EQ,
code,
i64t.const_int(k as u64 + 1, false),
"ev_is_cand",
)
.unwrap();
now = b
.build_select(is_k, cnt, now, "ev_now_pick")
.unwrap()
.into_int_value();
}
now
} else {
let array =
event_elem_signal(&t.expr, signals).expect("an element term has a slot");
let el = crate::arrays::EventElem {
info: elem_info(array),
row,
valid: ctx.bool_type().const_all_ones(),
};
let cell = crate::arrays::load_event_elem(&cx, &el)?;
b.build_int_truncate(cell, i64t, "ev_now").unwrap()
};
let moved = b
.build_int_compare(inkwell::IntPredicate::NE, now, before, "ev_moved")
.unwrap();
let hit = b.build_and(moved, was_valid, "ev_hit").unwrap();
hits.push((t.index, hit));
}
hits.extend(expr_hits.iter().copied());
hits.sort_by_key(|(index, _)| *index);
let mut term = fired;
let mut any_elem = ctx.bool_type().const_zero();
for (index, hit) in hits.iter().rev() {
term = b
.build_select(*hit, i64t.const_int(*index as u64, false), term, "ev_term")
.unwrap()
.into_int_value();
any_elem = b.build_or(any_elem, *hit, "ev_any").unwrap();
}
let term = b
.build_select(by_signal, fired, term, "ev_fired")
.unwrap()
.into_int_value();
let set_fn = module
.get_function(SV_RT_SET_FIRED_TERM_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_SET_FIRED_TERM_SYMBOL,
ctx.void_type().fn_type(&[i64t.into()], false),
Some(Linkage::External),
)
});
b.build_call(set_fn, &[term.into()], "ev_set_fired")
.unwrap();
any = b.build_or(any, any_elem, "edge_any_elem").unwrap();
}
let mut pass = b.build_or(by_signal, any, "pass").unwrap();
if let SuspendPoint::Edge { iff: Some(iff), .. } = point {
let g = compile_iff_guard_with_frame(
ctx,
module,
fns,
signals,
&plan.fn_sym,
&format!("{fn_name}_iff"),
*iff,
value_repr,
Some((&plan.frame, &plan.frame_signed)),
this,
)?;
let v = b
.build_call(g, &[handle.into(), local.into()], "iff")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let holds = b
.build_int_compare(
inkwell::IntPredicate::NE,
v,
ctx.i8_type().const_zero(),
"iff_holds",
)
.unwrap();
pass = b.build_and(pass, holds, "pass_and_iff").unwrap();
}
if let SuspendPoint::EdgeList(_, iffs) = point
&& iffs.iter().any(|g| !g.is_empty())
{
let mask = if iffs.len() == 1 {
i64t.const_int(1, false)
} else {
let mask_fn = module
.get_function(SV_RT_FIRED_MASK_SYMBOL)
.unwrap_or_else(|| {
module.add_function(SV_RT_FIRED_MASK_SYMBOL, i64t.fn_type(&[], false), None)
});
b.build_call(mask_fn, &[], "fired_mask")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value()
};
let mut bad = i64t.const_zero();
for (k, guards) in iffs.iter().enumerate() {
if guards.is_empty() {
continue;
}
if k >= 64
|| terms.iter().any(|t| t.index == k)
|| elem_terms.iter().any(|t| t.index == k)
|| expr_terms.iter().any(|t| t.index == k)
{
return Err(LoweringError::unsupported_at(
"an `iff` guard on an event-list member that is a process-local variable, an event-array element or an expression, or beyond member 63 (LRM 1800-2023 9.4.2.3)",
&plan.fn_sym,
));
}
let mut holds = ctx.bool_type().const_int(1, false);
for (j, iff) in guards.iter().enumerate() {
let g = compile_iff_guard_with_frame(
ctx,
module,
fns,
signals,
&plan.fn_sym,
&format!("{fn_name}_iff{k}_{j}"),
*iff,
value_repr,
Some((&plan.frame, &plan.frame_signed)),
this,
)?;
let v = b
.build_call(g, &[handle.into(), local.into()], "iff_k")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let h = b
.build_int_compare(
inkwell::IntPredicate::NE,
v,
ctx.i8_type().const_zero(),
"iff_k_holds",
)
.unwrap();
holds = b.build_and(holds, h, "iff_k_all").unwrap();
}
let bit = i64t.const_int(1u64 << k, false);
let this_bad = b
.build_select(holds, i64t.const_zero(), bit, "iff_k_bad")
.unwrap()
.into_int_value();
bad = b.build_or(bad, this_bad, "iff_bad").unwrap();
}
let not_bad = b.build_not(bad, "iff_not_bad").unwrap();
let good = b.build_and(mask, not_bad, "iff_good").unwrap();
let nz = |v: inkwell::values::IntValue<'ctx>, name: &str| {
b.build_int_compare(inkwell::IntPredicate::NE, v, i64t.const_zero(), name)
.unwrap()
};
let any_good = nz(good, "iff_any_good");
let has_mask = nz(mask, "iff_has_mask");
let no_mask = b.build_not(has_mask, "iff_no_mask").unwrap();
let ok0 = b.build_or(any_good, no_mask, "iff_ok0").unwrap();
let ok = b.build_or(ok0, any, "iff_ok").unwrap();
pass = b.build_and(pass, ok, "pass_and_iff_k").unwrap();
if iffs.len() > 1 {
let fired_cur = fired_now_value(b, fired_fn);
let mut term = fired_cur;
for k in (0..iffs.len().min(64)).rev() {
let bit = i64t.const_int(1u64 << k, false);
let masked = b.build_and(good, bit, "iff_bit").unwrap();
let hit = nz(masked, "iff_bit_set");
term = b
.build_select(hit, i64t.const_int(k as u64, false), term, "iff_term")
.unwrap()
.into_int_value();
}
let set_fn = module
.get_function(SV_RT_SET_FIRED_TERM_SYMBOL)
.unwrap_or_else(|| {
module.add_function(
SV_RT_SET_FIRED_TERM_SYMBOL,
ctx.void_type().fn_type(&[i64t.into()], false),
Some(Linkage::External),
)
});
b.build_call(set_fn, &[term.into()], "iff_set_fired")
.unwrap();
}
}
let r = b
.build_int_z_extend(pass, ctx.i8_type(), "pass_i8")
.unwrap();
b.build_return(Some(&r)).unwrap();
Ok(f)
}
#[cfg(test)]
mod tests {
use super::*;
use sv_lang::kinds::SymbolKind;
#[test]
fn lower_repeat_counter_init_clamps_a_negative_signed_count_to_zero() {
let design = crate::frontend::compile_source(
"module m; initial repeat (-1) $display(\"x\"); endmodule\n",
)
.unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let block = body
.members()
.find(|s| s.kind() == SymbolKind::ProceduralBlock)
.unwrap();
let root = block.body().unwrap();
assert_eq!(
root.kind(),
StatementKind::RepeatLoop,
"a bare `repeat (-1) stmt;` body with no begin/end must be a bare RepeatLoop \
statement"
);
let count_expr = root
.condition()
.expect("RepeatLoop's count expression lives on its own `condition()` slot");
let llvm_ctx = Context::create();
let module = llvm_ctx.create_module("test_repeat_counter_init");
let width = 32u32;
let fn_ty = llvm_ctx.i32_type().fn_type(&[], false);
let f = module.add_function("test_fn", fn_ty, None);
let entry = llvm_ctx.append_basic_block(f, "entry");
let b = llvm_ctx.create_builder();
b.position_at_end(entry);
let counter_ptr = b.build_alloca(llvm_ctx.i64_type(), "counter").unwrap();
let cx = LowerCtx {
ctx: &llvm_ctx,
b,
repr: ValueRepr::FourState,
locals: HashMap::new(),
fn_sym: body,
module: &module,
mem_signals: std::collections::HashMap::new(),
mem_accessor: None,
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(),
};
lower_repeat_counter_init(&cx, count_expr, counter_ptr, width)
.expect("a plain, integral repeat count must lower without error");
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let result = load_repeat_counter(&cx, &op, counter_ptr, width);
cx.b.build_return(Some(&result)).unwrap();
assert!(
f.verify(true),
"the emitted IR must pass LLVM's own verifier"
);
let jit = crate::jit::Jit::from_module(module).expect("JIT engine construction failed");
let raw = jit
.get_raw("test_fn")
.expect("test_fn must be a real symbol in the JIT'd module");
let callable: extern "C" fn() -> i32 =
unsafe { std::mem::transmute::<*const (), extern "C" fn() -> i32>(raw) };
let stored = callable();
assert_eq!(
stored, 0,
"repeat(-1) must clamp its negative signed count to a 0-iteration counter, never \
the huge unsigned-reinterpreted wraparound value a missing clamp would store (LRM \
1800-2023 SS12.7.1: a negative repeat count runs its body ZERO times)"
);
}
#[test]
fn wait_on_more_than_one_signal_is_a_named_error() {
let design = crate::frontend::compile_source(
"module m; logic a, b; initial wait (a && b) begin end endmodule\n",
)
.unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let block = body
.members()
.find(|s| s.kind() == SymbolKind::ProceduralBlock)
.unwrap();
let root = block.body().unwrap();
let cfg = sv_sim_sim::process::cfg::build_cfg(
&root,
sv_sim_sim::process::cfg::ProcessLoopKind::OneShot,
)
.unwrap();
let level_point = cfg
.blocks
.iter()
.find_map(|b| match &b.terminator {
sv_sim_sim::process::cfg::Terminator::Suspend {
point: point @ SuspendPoint::Level(_),
..
} => Some(point.clone()),
_ => None,
})
.expect("wait(a && b) must build a SuspendPoint::Level suspend terminator");
let signals: HashMap<SymbolId, SignalSlot> = HashMap::new();
let err = resolve_suspend_point(&level_point, &signals, &body, 0)
.expect_err("a multi-signal wait(expr) guard must be a named LoweringError");
assert!(
err.message.contains("more than one signal"),
"expected a named error citing the multi-signal wait(expr) scope restriction, got: \
{}",
err.message
);
}
#[test]
fn effect_of_statement_tags_assignment_as_state_write_and_declaration_as_pure() {
let design = crate::frontend::compile_source(
"module m; initial begin int x; x = 5; end endmodule\n",
)
.unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let block = body
.members()
.find(|s| s.kind() == SymbolKind::ProceduralBlock)
.unwrap();
let root = block.body().unwrap();
let cfg = sv_sim_sim::process::cfg::build_cfg(
&root,
sv_sim_sim::process::cfg::ProcessLoopKind::OneShot,
)
.unwrap();
let stmts = &cfg.blocks[0].stmts;
assert_eq!(stmts.len(), 2, "expected VariableDeclaration + Assignment");
assert_eq!(
effect_of_statement(&stmts[0]),
Effect::Pure,
"an unassigned VariableDeclaration must be Pure"
);
assert_eq!(
effect_of_statement(&stmts[1]),
Effect::StateWrite,
"x = 5 must be StateWrite"
);
}
}