use std::collections::{BTreeSet, HashMap};
use inkwell::AddressSpace;
use inkwell::context::Context;
use inkwell::module::Module;
use inkwell::values::IntValue;
use sv_lang::kinds::SymbolKind;
use sv_lang::{Design, EdgeKind, Expression, KnownSystemName, Symbol, Walk};
use sv_sim_sim::sva::global_clock::global_clocking_of;
use sv_sim_sim::sva::sampled::{
ClockEvent, SampledCall, SampledOp, clock_event_of, decode, guard_name, op_of, p_id, site_loc,
sites_of_block, strict_site_signals_in, use_views,
};
use sv_sim_sim::symid::{SymbolId, SymbolUid};
use crate::accessor::{AccessorFns, build_get_scalar, build_get_wide};
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, MemAccessorCtx, lower_condition, lower_expr};
use crate::module::{member_path, resolve_member_path};
use crate::ops::OpBuilder;
use crate::process_lower::{ProcessLoweringPlan, SignalSlot, SignalSlotClass, mem_signals_of};
use crate::value::{PackedValue, ValueRepr};
pub(crate) fn lower_sampled_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
op_of(name)?;
Some(lower(cx, e))
}
fn unsupported(cx: &LowerCtx<'_, '_, '_>, what: &str) -> LoweringError {
LoweringError::unsupported_at(what, &cx.fn_sym)
}
fn lower<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let call = decode(e)
.map_err(|m| unsupported(cx, &m.0))?
.ok_or_else(|| unsupported(cx, "not a sampled value function"))?;
let no_future = || {
unsupported(
cx,
"$future_gclk/$rising_gclk/$falling_gclk/$steady_gclk/$changing_gclk need a value at a \
FUTURE global clock tick (LRM 16.9.4: they are legal only in a property or sequence \
expression), so they have no procedural form",
)
};
let in_subroutine = cx.fn_sym.kind() == SymbolKind::Subroutine;
if cx.fn_sym.kind() != SymbolKind::ProceduralBlock && !in_subroutine {
return Err(unsupported(
cx,
"a sampled value function outside a procedural block or a module-level subroutine \
(a continuous assignment body) is not supported: its value history is kept per \
procedural block or subroutine",
));
}
let args = strict_site_signals_in(call.arg).map_err(|m| unsupported(cx, &m.0))?;
let views = use_views(&cx.fn_sym.uid(), &call, &args);
let cells = if in_subroutine {
subroutine_cells(cx, &views)?
} else {
HashMap::new()
};
if views
.next
.iter()
.any(|(_, cell)| !cx.locals.contains_key(cell) && !cells.contains_key(cell))
{
return Err(no_future());
}
let cur = with_views(cx, &cells, &views.cur, || lower_expr(cx, call.arg))?;
if call.op.is_future() {
let next = with_views(cx, &cells, &views.next, || lower_expr(cx, call.arg))?;
if call.op == SampledOp::Future {
return Ok(next);
}
let what = "a future value change function of a real, string or >128-bit value";
let cur = cur.into_packed(what, &cx.fn_sym)?;
let next = next.into_packed(what, &cx.fn_sym)?;
let op = match call.op {
SampledOp::Rising => SampledOp::Rose,
SampledOp::Falling => SampledOp::Fell,
SampledOp::Steady => SampledOp::Stable,
_ => SampledOp::Changed,
};
return Ok(LoweredValue::Packed(value_change(cx, op, next, cur)));
}
match call.op {
SampledOp::Sampled | SampledOp::Past => Ok(cur),
SampledOp::Rose | SampledOp::Fell | SampledOp::Stable | SampledOp::Changed => {
let what = "a value change function of a real, string or >128-bit value";
let cur = cur.into_packed(what, &cx.fn_sym)?;
let prev = with_views(cx, &cells, &views.prev, || lower_expr(cx, call.arg))?
.into_packed(what, &cx.fn_sym)?;
Ok(LoweredValue::Packed(value_change(cx, call.op, cur, prev)))
}
SampledOp::Future
| SampledOp::Rising
| SampledOp::Falling
| SampledOp::Steady
| SampledOp::Changing => unreachable!("lowered above"),
}
}
fn value_change<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: SampledOp,
cur: PackedValue<'ctx>,
prev: PackedValue<'ctx>,
) -> PackedValue<'ctx> {
let ob = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let bit = match op {
SampledOp::Rose => {
let not_prev1 = cx.b.build_not(lsb_is(cx, prev, true), "prev_not1").unwrap();
cx.b.build_and(lsb_is(cx, cur, true), not_prev1, "rose")
.unwrap()
}
SampledOp::Fell => {
let not_prev0 =
cx.b.build_not(lsb_is(cx, prev, false), "prev_not0")
.unwrap();
cx.b.build_and(lsb_is(cx, cur, false), not_prev0, "fell")
.unwrap()
}
SampledOp::Stable => same_value(cx, cur, prev),
SampledOp::Changed => {
let same = same_value(cx, cur, prev);
cx.b.build_not(same, "changed").unwrap()
}
_ => unreachable!("only the four value change functions reach here"),
};
ob.pack(bit, ob.i_n(1).const_zero(), 1, cx.repr, false)
}
pub(crate) fn lsb_is<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: PackedValue<'ctx>,
one: bool,
) -> IntValue<'ctx> {
let ob = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i1 = ob.i_n(1);
let val =
cx.b.build_int_truncate(ob.lane_val(v), i1, "lsb_val")
.unwrap();
let unk =
cx.b.build_int_truncate(ob.lane_unk(v), i1, "lsb_unk")
.unwrap();
let known = cx.b.build_not(unk, "lsb_known").unwrap();
let want = if one {
val
} else {
cx.b.build_not(val, "lsb_val_n").unwrap()
};
cx.b.build_and(want, known, "lsb_is").unwrap()
}
pub(crate) fn same_value<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
a: PackedValue<'ctx>,
b: PackedValue<'ctx>,
) -> IntValue<'ctx> {
let ob = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let eq = ob.caseeq4(a, b);
cx.b.build_int_truncate(ob.lane_val(eq), ob.i_n(1), "same")
.unwrap()
}
type Cell<'ctx> = (inkwell::values::PointerValue<'ctx>, u32, bool);
fn subroutine_cells<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
views: &sv_sim_sim::sva::sampled::UseViews,
) -> Result<HashMap<SymbolId, Cell<'ctx>>, LoweringError> {
let Some(accessor) = cx.mem_accessor else {
return Err(unsupported(
cx,
"a sampled value function in a subroutine called where no instance handle is \
available",
));
};
let body = sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym).ok_or_else(|| {
unsupported(
cx,
"a sampled value function in a subroutine outside any instance",
)
})?;
if !sv_sim_sim::netlist::module_items(&body)
.iter()
.any(|m| m.uid() == cx.fn_sym.uid())
{
return Err(unsupported(
cx,
"a sampled value function in a class method or package subroutine (its value \
history is kept per module-level subroutine, LRM 16.9.3)",
));
}
let slots = crate::module::signal_slots_of(&body)?;
let mut out = HashMap::new();
for (_, id) in views.cur.iter().chain(&views.prev).chain(&views.next) {
if out.contains_key(id) {
continue;
}
let Some(info) = slots.get(id) else {
return Err(unsupported(
cx,
"a sampled value function's history cell has no state slot",
));
};
let raw = match info.class {
SignalSlotClass::Inline => {
build_get_scalar(cx, accessor.fns, accessor.handle, info.slot, info.width)
}
SignalSlotClass::Wide => {
build_get_wide(cx, accessor.fns, accessor.handle, info.slot, info.width)
}
SignalSlotClass::Memory { .. } => {
return Err(unsupported(cx, "a sampled value function over a memory"));
}
};
let ty = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.i_n(info.width * 2);
let ptr = crate::types::entry_alloca_in(&cx.b, ty, "sva_cell");
cx.b.build_store(ptr, raw).unwrap();
out.insert(id.clone(), (ptr, info.width, info.signed));
}
Ok(out)
}
fn with_views<'ctx, T>(
cx: &LowerCtx<'ctx, '_, '_>,
cells: &HashMap<SymbolId, Cell<'ctx>>,
views: &[(SymbolId, SymbolId)],
f: impl FnOnce() -> Result<T, LoweringError>,
) -> Result<T, LoweringError> {
let mut saved = Vec::with_capacity(views.len());
for (sig, cell) in views {
let (Some(&(sig_ptr, w, _)), Some(&(cell_ptr, cell_w, _))) = (
cx.locals.get(sig),
cells.get(cell).or_else(|| cx.locals.get(cell)),
) else {
return Err(unsupported(
cx,
"a sampled value function reads a signal whose value history is not available \
here (an automatic or block-local variable, a hierarchical reference, or a call \
outside a procedural block)",
));
};
debug_assert_eq!(w, cell_w, "a hidden cell is a clone of its source");
let ty = match sig_ptr
.as_instruction()
.and_then(|i| i.get_allocated_type().ok())
{
Some(t) if t.is_array_type() => t,
_ => OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.i_n(w * 2)
.into(),
};
let old = cx.b.build_load(ty, sig_ptr, "view_old").unwrap();
let new = cx.b.build_load(ty, cell_ptr, "view_cell").unwrap();
cx.b.build_store(sig_ptr, new).unwrap();
saved.push((sig_ptr, old));
}
let r = f();
for (ptr, old) in saved {
cx.b.build_store(ptr, old).unwrap();
}
r
}
pub(crate) fn edge_occurred<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
edge: EdgeKind,
prev: PackedValue<'ctx>,
cur: PackedValue<'ctx>,
) -> IntValue<'ctx> {
let b = &cx.b;
let pos = |b: &inkwell::builder::Builder<'ctx>| {
let not_cur0 = b.build_not(lsb_is(cx, cur, false), "n_cur0").unwrap();
let rise_a = b
.build_and(lsb_is(cx, prev, false), not_cur0, "rise_a")
.unwrap();
let not_prev1 = b.build_not(lsb_is(cx, prev, true), "n_prev1").unwrap();
let rise_b = b
.build_and(not_prev1, lsb_is(cx, cur, true), "rise_b")
.unwrap();
b.build_or(rise_a, rise_b, "posedge").unwrap()
};
let neg = |b: &inkwell::builder::Builder<'ctx>| {
let not_cur1 = b.build_not(lsb_is(cx, cur, true), "n_cur1").unwrap();
let fall_a = b
.build_and(lsb_is(cx, prev, true), not_cur1, "fall_a")
.unwrap();
let not_prev0 = b.build_not(lsb_is(cx, prev, false), "n_prev0").unwrap();
let fall_b = b
.build_and(not_prev0, lsb_is(cx, cur, false), "fall_b")
.unwrap();
b.build_or(fall_a, fall_b, "negedge").unwrap()
};
match edge {
EdgeKind::PosEdge => pos(b),
EdgeKind::NegEdge => neg(b),
EdgeKind::BothEdges => {
let p = pos(b);
let n = neg(b);
b.build_or(p, n, "anyedge").unwrap()
}
EdgeKind::None => {
let same = same_value(cx, prev, cur);
b.build_not(same, "changed").unwrap()
}
}
}
pub(crate) struct ClockResolver<'d> {
design: &'d Design,
analysis: Option<sv_lang::Analysis<'d>>,
trees: Option<Vec<sv_lang::SyntaxTree>>,
}
impl<'d> ClockResolver<'d> {
pub(crate) fn new(design: &'d Design) -> Self {
Self {
design,
analysis: None,
trees: None,
}
}
fn global_event(&mut self, block: &Symbol<'d>) -> Result<ClockEvent<'d>, LoweringError> {
let unsup = |what: &str| LoweringError::unsupported_at(what, block);
let body = sv_sim_sim::netlist::enclosing_instance_body(block)
.ok_or_else(|| unsup("a procedural block outside any instance body"))?;
let design = self.design;
let trees = self.trees.get_or_insert_with(|| design.syntax_trees());
let found = global_clocking_of(&body, trees).map_err(|e| unsup(&e.0))?;
let Some(global) = found else {
return Err(unsup(
"a global clocking sampled value function with no global clocking declaration in \
scope (LRM 14.14: the lookup climbs the instance hierarchy to the top and an \
absent declaration is an error)",
));
};
let tc = global
.block
.clocking_block_event()
.and_then(|n| n.as_timing_control())
.ok_or_else(|| unsup("the global clocking declaration has no event"))?;
clock_event_of(tc).map_err(|e| unsup(&format!("the global clocking's event: {}", e.0)))
}
fn subroutine_default_event(
&mut self,
sub: &Symbol<'d>,
) -> Result<ClockEvent<'d>, LoweringError> {
let unsup = |what: &str| LoweringError::unsupported_at(what, sub);
let design = self.design;
let trees = self.trees.get_or_insert_with(|| design.syntax_trees());
let tree = trees
.iter()
.find(|t| sub.syntax(t).is_some())
.ok_or_else(|| {
unsup("no syntax tree for a subroutine holding a sampled value function")
})?;
let found = sv_sim_sim::state::default_clocking_of(sub, tree)
.map_err(|e| unsup(&format!("default clocking (LRM 14.12): {e:?}")))?;
let Some(cb) = found else {
return Err(unsup(
"a sampled value function in a subroutine has no clocking event (LRM 16.9.3: a \
subroutine is no procedure, so no clock is inferred from it (16.14.6), and an \
explicit clocking event is a timing control a function body cannot hold; \
declare a default clocking (14.12) in the module, or use $sampled)",
));
};
let tc = cb
.clocking_block_event()
.and_then(|n| n.as_timing_control())
.ok_or_else(|| unsup("the default clocking declaration has no event"))?;
clock_event_of(tc).map_err(|e| unsup(&format!("the default clocking's event: {}", e.0)))
}
fn resolve<'a>(
&'a mut self,
block: &Symbol<'d>,
call: &SampledCall<'d>,
) -> Result<ClockEvent<'a>, LoweringError> {
let unsup = |what: &str| LoweringError::unsupported_at(what, block);
if let Some(ev) = call.clock {
return Ok(ev);
}
if block.kind() == SymbolKind::Subroutine {
if call.global {
return Err(unsup(
"a global clocking sampled value function ($rising_gclk and the like) in a \
subroutine (LRM 16.9.4: legal only in a property or sequence expression)",
));
}
return self.subroutine_default_event(block);
}
if call.global {
return self.global_event(block);
}
let (tc, twin) = self.procedure_clock(block)?;
let tc = tc.or_else(|| static_assertion_clock(block));
let tc = tc.ok_or_else(|| {
unsup(
"no clock can be inferred for a sampled value function with no clocking event \
(LRM 16.14.6: the procedure shall have no blocking timing control and exactly \
one event control, and no default clocking applies)",
)
})?;
let mut ev = sv_sim_sim::sva::sampled::clock_event_of(tc).map_err(|e| unsup(&e.0))?;
if ev.iff.is_some_and(|g| holds_sampled_call(g.as_sem_node())) {
ev.iff = None;
}
if twin {
return sv_sim_sim::sva::sampled::relocate_clock(block, &ev).map_err(|e| unsup(&e.0));
}
Ok(ev)
}
fn procedure_clock<'a>(
&'a mut self,
block: &Symbol<'d>,
) -> Result<(Option<sv_lang::TimingControl<'a>>, bool), LoweringError> {
let unsup = |what: &str| LoweringError::unsupported_at(what, block);
if self.analysis.is_none() {
self.analysis = Some(
self.design
.analyze(sv_lang::AnalysisFlags::NONE, 1)
.map_err(|e| {
unsup(&format!(
"clock inference failed to analyze the design: {e}"
))
})?,
);
}
let analysis = self.analysis.as_ref().expect("built above");
let owner = block.uid();
let infer = |proc: &sv_lang::AnalyzedProcedure<'a>| {
sv_sim_sim::analysis::infer_procedural_clock(proc).map_err(|e| unsup(&e.to_string()))
};
let body = sv_sim_sim::netlist::enclosing_instance_body(block)
.ok_or_else(|| unsup("a procedural block outside any instance body"))?;
if let Some(proc) = find_procedure(analysis, &body, owner) {
let tc = match infer(&proc)? {
Some(tc) => Some(tc),
None => sv_sim_sim::analysis::infer_structural_clock(block)
.map_err(|e| unsup(&e.to_string()))?,
};
return Ok((tc, false));
}
let mut scope = block.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::CheckerInstanceBody {
let tc = sv_sim_sim::analysis::infer_structural_clock(block)
.map_err(|e| unsup(&e.to_string()))?;
return Ok((tc, false));
}
scope = s.parent();
}
let path = member_path(block, &body)
.ok_or_else(|| unsup("a procedural block that is not under its instance body"))?;
let mut twins = Vec::new();
self.design.root().visit(|sym| {
if sym.kind() == SymbolKind::Instance
&& let Some(b) = sym.instance_body()
&& b != body
&& b.instance_body_has_same_type(&body)
{
twins.push(b);
}
Walk::Continue
});
for twin in twins {
let Some(twin_block) = resolve_member_path(&twin, &path) else {
continue;
};
let twin_uid = twin_block.uid();
if let Some(proc) = find_procedure(analysis, &twin, twin_uid) {
return Ok((infer(&proc)?, true));
}
}
Err(unsup(
"the clock of a sampled value function with no clocking event could not be \
inferred: the analysis has no record of this procedure, in its instance body \
or any of that body's generate blocks, or of an identical instance's; write the clocking event explicitly, e.g. $past(x, , , @(posedge clk))",
))
}
pub(crate) fn inferred_node(
&mut self,
block: &Symbol<'d>,
) -> Result<Option<sv_lang::SemNode<'d>>, LoweringError> {
let (tc, _) = self.procedure_clock(block)?;
Ok(tc.and_then(|tc| sv_sim_sim::sva::sampled::relocate_clock_node(block, &tc)))
}
}
fn find_procedure<'a>(
analysis: &'a sv_lang::Analysis<'_>,
body: &Symbol<'a>,
owner: SymbolId,
) -> Option<sv_lang::AnalyzedProcedure<'a>> {
let scopes: Vec<Symbol<'a>> = std::iter::once(*body)
.chain(sv_sim_sim::netlist::generate_scopes(body))
.collect();
let checkers = scopes
.iter()
.flat_map(sv_sim_sim::netlist::checker_instances)
.filter_map(|c| sv_sim_sim::netlist::checker_instance_body(&c));
scopes
.iter()
.copied()
.chain(checkers)
.find_map(|s| analysis.procedures(s).find(|p| p.symbol().uid() == owner))
}
fn holds_sampled_call(node: sv_lang::SemNode<'_>) -> bool {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(e) = n.as_expression()
&& matches!(sv_sim_sim::sva::sampled::decode(e), Ok(Some(_)))
{
return true;
}
stack.extend(n.children());
}
false
}
fn static_assertion_clock<'d>(block: &Symbol<'d>) -> Option<sv_lang::TimingControl<'d>> {
let stmt =
crate::module::static_assertion_statement(sv_sim_sim::process::cfg::proc_body(block)?)?;
stmt.as_sem_node()
.children()
.into_iter()
.find_map(|n| n.as_assertion_expr())
.filter(|r| r.kind() == sv_lang::kinds::AssertionExprKind::Clocking)
.and_then(|r| r.children().first().copied())
.and_then(|n| n.as_timing_control())
}
pub(crate) fn emit_guards<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
plans: &[ProcessLoweringPlan<'d>],
design: &'d Design,
) -> Result<(), LoweringError> {
let mut resolver = ClockResolver::new(design);
let mut bodies = BTreeSet::new();
for plan in plans {
if let Some(body) = sv_sim_sim::netlist::enclosing_instance_body(&plan.fn_sym)
&& bodies.insert(body.uid())
{
emit_subroutine_guards(ctx, module, fns, &mut resolver, &body)?;
}
if plan.fn_sym.kind() != SymbolKind::ProceduralBlock {
continue;
}
let sites = sites_of_block(&plan.fn_sym)
.map_err(|e| LoweringError::unsupported_at(&e.0, &plan.fn_sym))?;
let owner = plan.fn_sym.uid();
for site in sites {
let call = site.call;
if call.op.depth(call.ticks) == 0 || call.op.is_future() {
continue;
}
let name = guard_name(&owner, &site_loc(&call.call));
if module.get_function(&name).is_some() {
continue;
}
let ev = resolver.resolve(&plan.fn_sym, &call)?;
compile_site_guard(
ctx,
module,
fns,
plan.fn_sym,
&plan.signals,
&name,
&call,
&ev,
)?;
}
}
Ok(())
}
fn emit_subroutine_guards<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
resolver: &mut ClockResolver<'d>,
body: &Symbol<'d>,
) -> Result<(), LoweringError> {
let mut slots = None;
for sub in subroutines_of(body) {
let sites = sites_of_block(&sub).map_err(|e| LoweringError::unsupported_at(&e.0, &sub))?;
let owner = sub.uid();
for site in sites {
let call = site.call;
if call.op.depth(call.ticks) == 0 || call.op.is_future() {
continue;
}
let name = guard_name(&owner, &site_loc(&call.call));
if module.get_function(&name).is_some() {
continue;
}
let ev = resolver.resolve(&sub, &call)?;
if slots.is_none() {
slots = Some(crate::module::signal_slots_of(body)?);
}
let table = slots.as_ref().expect("built above");
compile_site_guard(ctx, module, fns, sub, table, &name, &call, &ev)?;
}
}
Ok(())
}
fn subroutines_of<'d>(body: &Symbol<'d>) -> Vec<Symbol<'d>> {
sv_sim_sim::netlist::module_items(body)
.into_iter()
.filter(|m| m.kind() == SymbolKind::Subroutine)
.collect()
}
pub(crate) fn plans_use_sampled_values(plans: &[ProcessLoweringPlan<'_>]) -> bool {
plans.iter().any(|p| {
(p.fn_sym.kind() == SymbolKind::ProceduralBlock
&& sites_of_block(&p.fn_sym).map_or(true, |s| !s.is_empty()))
|| sv_sim_sim::netlist::enclosing_instance_body(&p.fn_sym).is_some_and(|b| {
subroutines_of(&b)
.iter()
.any(|s| sites_of_block(s).map_or(true, |x| !x.is_empty()))
})
})
}
#[allow(clippy::too_many_arguments)]
fn compile_site_guard<'ctx, 'd>(
ctx: &'ctx Context,
module: &Module<'ctx>,
fns: &AccessorFns<'ctx>,
block: Symbol<'d>,
signals: &HashMap<SymbolId, SignalSlot>,
name: &str,
call: &SampledCall<'d>,
ev: &ClockEvent<'d>,
) -> Result<(), LoweringError> {
let fn_sym: Symbol<'d> = block;
let fn_sym = &fn_sym;
let i8_ty = ctx.i8_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let f = module.add_function(name, i8_ty.fn_type(&[ptr_ty.into()], false), None);
f.get_nth_param(0).unwrap().set_name("handle");
let entry = ctx.append_basic_block(f, "entry");
let handle = f.get_nth_param(0).unwrap().into_pointer_value();
let b = ctx.create_builder();
b.position_at_end(entry);
let mut cx = LowerCtx {
ctx,
b,
repr: ValueRepr::FourState,
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: Default::default(),
end_flags: Default::default(),
specify_dest: Default::default(),
elem_refs: Default::default(),
};
let bad = |m: String| LoweringError::unsupported_at(&m, fn_sym);
let mut sigs: BTreeSet<SymbolId> = strict_site_signals_in(ev.signal).map_err(|e| bad(e.0))?;
for extra in ev.iff.into_iter().chain(call.gate) {
sigs.extend(strict_site_signals_in(extra).map_err(|e| bad(e.0))?);
}
let mut views = Vec::new();
for sig in &sigs {
for id in [sig.clone(), p_id(sig)] {
prime(&mut cx, fns, handle, signals, &id)?;
}
views.push((sig.clone(), p_id(sig)));
}
let what = "a clocking event on a real, string or >128-bit value";
let cur = lower_expr(&cx, ev.signal)?.into_packed(what, fn_sym)?;
let no_cells = HashMap::new();
let prev = with_views(&cx, &no_cells, &views, || lower_expr(&cx, ev.signal))?
.into_packed(what, fn_sym)?;
let mut tick = edge_occurred(&cx, ev.edge, prev, cur);
for cond in ev.iff.into_iter().chain(call.gate) {
let c = with_views(&cx, &no_cells, &views, || lower_condition(&cx, cond))?;
tick = cx.b.build_and(tick, c, "tick_and").unwrap();
}
let ext = cx.b.build_int_z_extend(tick, i8_ty, "tick_i8").unwrap();
cx.b.build_return(Some(&ext)).unwrap();
if f.verify(true) {
Ok(())
} else {
Err(LoweringError {
message: "sampled value tick guard failed LLVM verify".into(),
at: fn_sym.hierarchical_path(),
})
}
}
fn prime<'ctx>(
cx: &mut LowerCtx<'ctx, '_, '_>,
fns: &AccessorFns<'ctx>,
handle: inkwell::values::PointerValue<'ctx>,
signals: &HashMap<SymbolId, SignalSlot>,
id: &SymbolId,
) -> Result<(), LoweringError> {
let Some(info) = signals.get(id) else {
return Err(unsupported(
cx,
"a sampled value function's clock or gate reads a signal with no state slot here",
));
};
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 { .. } => {
return Err(unsupported(
cx,
"a sampled value function's clock or gate reads a memory",
));
}
};
let ty = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.i_n(info.width * 2);
let ptr = crate::types::entry_alloca_in(&cx.b, ty, "sva_shadow");
cx.b.build_store(ptr, raw).unwrap();
cx.locals.insert(id.clone(), (ptr, info.width, info.signed));
Ok(())
}