use crate::types::TypeWidth;
use std::collections::HashMap;
use sv_sim_sim::symid::SymbolUid;
use inkwell::AddressSpace;
use inkwell::IntPredicate;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::values::{IntValue, PointerValue};
use crate::accessor::AccessorFns;
use sv_lang::BinaryOp;
use sv_lang::KnownSystemName;
use sv_lang::MethodFlags;
use sv_lang::UnaryOp;
use sv_lang::kinds::ExpressionKind;
use sv_sim_runtime::file_io::{
SV_RT_FEOF_SYMBOL, SV_RT_FOPEN_MCD_SYMBOL, SV_RT_FOPEN_SYMBOL, SV_RT_FSEEK_SYMBOL,
SV_RT_SYSTEM_SYMBOL,
};
use sv_sim_runtime::plusargs::{SV_RT_PLUSARGS_TEST_SYMBOL, SV_RT_PLUSARGS_VALUE_SYMBOL};
use crate::error::LoweringError;
use crate::ops::{OpBuilder, is_definitely_false, is_definitely_true, ternary_is_definitely_true};
use crate::types::{LoweredType, int_type, lower_type};
use crate::value::{AnyValue, PackedValue, RealValue, ValueRepr, WideValueRef};
#[derive(Clone, Copy)]
pub enum LoweredValue<'ctx> {
Packed(PackedValue<'ctx>),
Real(RealValue<'ctx>),
Wide(WideValueRef<'ctx>),
Str(crate::strings::StrValue<'ctx>),
Huge(crate::huge::HugeValue<'ctx>),
}
impl<'ctx> LoweredValue<'ctx> {
pub fn into_packed(
self,
what: &str,
sym: &sv_lang::Symbol,
) -> Result<PackedValue<'ctx>, LoweringError> {
match self {
LoweredValue::Packed(p) => Ok(p),
LoweredValue::Real(_) | LoweredValue::Wide(_) | LoweredValue::Str(_) => {
Err(LoweringError::unsupported_at(what, sym))
}
LoweredValue::Huge(h) => Err(LoweringError::unsupported_at(
&format!(
"{what} -- the operand is a {}-bit vector, wider than the wide-value \
threshold (LRM 1800-2023 6.9.1), which this construct does not support",
h.width
),
sym,
)),
}
}
pub fn into_real(
self,
what: &str,
sym: &sv_lang::Symbol,
) -> Result<RealValue<'ctx>, LoweringError> {
match self {
LoweredValue::Real(r) => Ok(r),
LoweredValue::Packed(_)
| LoweredValue::Wide(_)
| LoweredValue::Str(_)
| LoweredValue::Huge(_) => Err(LoweringError::unsupported_at(what, sym)),
}
}
}
pub const CONVERSION_KIND_COUNT: u32 = 5;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConversionAction {
Value,
Stream,
BitstreamCast,
}
pub fn classify_conversion_kind(kind: u32) -> Result<ConversionAction, String> {
match kind {
0 | 1 | 3 => Ok(ConversionAction::Value),
2 => Ok(ConversionAction::Stream),
4 => Ok(ConversionAction::BitstreamCast),
other => Err(format!(
"conversion_kind {other} is outside the pinned 0..{CONVERSION_KIND_COUNT} range \
(Implicit=0/Propagated=1/StreamingConcat=2/Explicit=3/BitstreamCast=4) -- vendored \
sv-lang may have added or renumbered a ConversionKind variant"
)),
}
}
pub const RANGE_SELECTION_KIND_COUNT: u32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RangeSelectionKindClass {
Simple,
IndexedUp,
IndexedDown,
}
pub fn classify_range_selection_kind(kind: u32) -> Result<RangeSelectionKindClass, String> {
match kind {
0 => Ok(RangeSelectionKindClass::Simple),
1 => Ok(RangeSelectionKindClass::IndexedUp),
2 => Ok(RangeSelectionKindClass::IndexedDown),
other => Err(format!(
"range_selection_kind {other} is outside the pinned 0..{RANGE_SELECTION_KIND_COUNT} \
range (Simple=0/IndexedUp=1/IndexedDown=2) -- vendored sv-lang may have added or \
renumbered a RangeSelectionKind variant"
)),
}
}
pub(crate) fn require_const_i64<'d>(
e: sv_lang::Expression<'d>,
what: &str,
sym: &sv_lang::Symbol<'d>,
) -> Result<i64, LoweringError> {
e.constant_value()
.as_ref()
.and_then(sv_lang::ConstantValue::as_integer)
.and_then(sv_lang::SVInt::as_i64)
.ok_or_else(|| LoweringError::unsupported_at(what, sym))
}
pub struct LowerCtx<'ctx, 'd, 'm> {
pub ctx: &'ctx Context,
pub b: Builder<'ctx>,
pub repr: ValueRepr,
pub locals: HashMap<sv_sim_sim::symid::SymbolId, (PointerValue<'ctx>, u32, bool)>,
pub fn_sym: sv_lang::Symbol<'d>,
pub module: &'m Module<'ctx>,
pub mem_signals: HashMap<sv_sim_sim::symid::SymbolId, MemorySignalInfo>,
pub mem_accessor: Option<MemAccessorCtx<'ctx, 'm>>,
pub lvalue_ref: std::cell::Cell<Option<LoweredValue<'ctx>>>,
pub deferred_start: std::cell::Cell<Option<PointerValue<'ctx>>>,
pub unbounded: std::cell::Cell<Option<IntValue<'ctx>>>,
pub coll_temp: std::cell::Cell<Option<inkwell::values::PointerValue<'ctx>>>,
#[allow(clippy::type_complexity)]
pub iter: std::cell::RefCell<
Vec<(
sv_sim_sim::symid::SymbolId,
LoweredValue<'ctx>,
LoweredValue<'ctx>,
)>,
>,
pub loop_exits: Vec<(
inkwell::basic_block::BasicBlock<'ctx>,
inkwell::basic_block::BasicBlock<'ctx>,
)>,
pub block_exits: Vec<(String, inkwell::basic_block::BasicBlock<'ctx>)>,
pub write_through: Option<WriteThrough<'ctx, 'm>>,
pub cells: HashMap<sv_sim_sim::symid::SymbolId, (PointerValue<'ctx>, u32)>,
pub copy_out: Vec<(PointerValue<'ctx>, PointerValue<'ctx>, IntValue<'ctx>)>,
pub elem_refs: HashMap<PointerValue<'ctx>, crate::collections::ElemRefSlot<'ctx>>,
pub ctor_this: std::cell::Cell<Option<IntValue<'ctx>>>,
pub decl_scale: std::cell::Cell<Option<(i32, i32)>>,
pub inline_this: Option<sv_lang::Symbol<'d>>,
pub end_flags: std::cell::RefCell<HashMap<u64, PointerValue<'ctx>>>,
pub specify_dest: HashMap<sv_sim_sim::symid::SymbolId, crate::process_lower::SignalSlot>,
}
#[derive(Clone)]
pub struct WriteThrough<'ctx, 'a> {
pub handle: PointerValue<'ctx>,
pub fns: &'a AccessorFns<'ctx>,
pub signals: HashMap<PointerValue<'ctx>, (u32, u32, bool)>,
pub reload: std::cell::RefCell<Option<crate::vpi_dyn::SharedReload<'ctx>>>,
}
pub(crate) fn write_through_store<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, ptr: PointerValue<'ctx>) {
crate::collections::elem_ref_flush(cx, ptr);
if let Some(wt) = &cx.write_through
&& let Some(&(slot, width, _)) = wt.signals.get(&ptr)
&& crate::huge::is_huge_width(width)
{
let i64t = cx.ctx.i64_type();
crate::huge::rt_mark(
cx,
wt.handle,
slot,
width,
i64t.const_zero(),
i64t.const_int(u64::from(width), false),
);
}
write_through_commit(cx, ptr);
}
pub(crate) fn write_through_commit<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, ptr: PointerValue<'ctx>) {
let Some(wt) = &cx.write_through else {
return;
};
commit_shadow(cx, wt, ptr);
}
pub(crate) fn commit_shadow<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
wt: &WriteThrough<'ctx, '_>,
ptr: PointerValue<'ctx>,
) {
let Some(&(slot, width, wide)) = wt.signals.get(&ptr) else {
return;
};
if crate::huge::is_huge_width(width) {
crate::huge::rt_commit(cx, wt.handle, slot, width);
} else {
let cur =
cx.b.build_load(int_type(cx.ctx, width * 2), ptr, "wt_ld")
.unwrap()
.into_int_value();
if wide {
crate::accessor::build_set_wide(cx, wt.fns, wt.handle, slot, cur, width);
} else {
crate::accessor::build_set_scalar(cx, wt.fns, wt.handle, slot, cur, width);
}
}
if crate::force::reload_after_write() {
reload_from_state(cx, wt, ptr);
}
}
pub(crate) fn reload_from_state<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
wt: &WriteThrough<'ctx, '_>,
ptr: PointerValue<'ctx>,
) {
let Some(&(slot, width, wide)) = wt.signals.get(&ptr) else {
return;
};
if crate::huge::is_huge_width(width) {
crate::huge::rt_prime(cx, wt.handle, slot, width, true);
return;
}
let v = if wide {
crate::accessor::build_get_wide(cx, wt.fns, wt.handle, slot, width)
} else {
crate::accessor::build_get_scalar(cx, wt.fns, wt.handle, slot, width)
};
cx.b.build_store(ptr, v).unwrap();
}
#[derive(Clone, Copy, Debug)]
pub struct MemorySignalInfo {
pub slot: u32,
pub elem_width: u32,
pub signed: bool,
pub num_rows: u32,
pub lower_index: i64,
}
#[derive(Clone, Copy)]
pub struct MemAccessorCtx<'ctx, 'a> {
pub handle: PointerValue<'ctx>,
pub fns: &'a AccessorFns<'ctx>,
}
fn is_cg042_type_query(name: KnownSystemName) -> bool {
matches!(
name,
KnownSystemName::Bits
| KnownSystemName::Size
| KnownSystemName::Low
| KnownSystemName::High
| KnownSystemName::Left
| KnownSystemName::Right
| KnownSystemName::Increment
| KnownSystemName::Dimensions
| KnownSystemName::UnpackedDimensions
| KnownSystemName::IsUnbounded
)
}
pub const SV_RT_TIME_SYMBOL: &str = "sv_rt_time";
pub(crate) fn fetch_or_declare_sv_rt_time<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module.get_function(SV_RT_TIME_SYMBOL).unwrap_or_else(|| {
let fn_ty = ctx.i64_type().fn_type(&[], false);
module.add_function(SV_RT_TIME_SYMBOL, fn_ty, Some(Linkage::External))
})
}
pub(crate) fn event_cell_ptr<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
target: sv_lang::Expression<'d>,
what: &str,
) -> Result<PointerValue<'ctx>, LoweringError> {
if !sv_sim_sim::symid::is_value_name(target.kind()) {
return Err(LoweringError::unsupported_at(
&format!(
"{what}: the event operand ({:?}) must be a plain named event (event \
handles held in variables / hierarchical events are not supported)",
target.kind()
),
&cx.fn_sym,
));
}
let sym = target.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at(&format!("{what}: unresolved event name"), &cx.fn_sym)
})?;
if let Some(hier) = sv_sim_sim::state::event_pool::hierarchical_pool_event(&target) {
return Err(LoweringError::unsupported_at(
&format!(
"{what}: the hierarchical event `{}` belongs to an instance that keeps its \
events in an event pool (it assigns, compares or passes an event, LRM \
1800-2023 15.5); another instance's access to its legacy cell would be \
silently lost",
hier.name()
),
&cx.fn_sym,
));
}
let (ptr, width, _) = *cx.locals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{what}: the event is not a module-level signal of this process's scope"),
&sym,
)
})?;
if width != sv_sim_sim::state::EVENT_CELL_WIDTH {
return Err(LoweringError::unsupported_at(
&format!(
"{what}: operand is a {width}-bit signal, not a named event's \
{}-bit cell",
sv_sim_sim::state::EVENT_CELL_WIDTH
),
&sym,
));
}
Ok(ptr)
}
pub(crate) fn lower_event_trigger<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
target: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
if crate::class_events::lower_trigger(cx, target)? {
return Ok(());
}
if sv_sim_sim::netlist::vif::is_member_access(&target) {
return crate::vif::event_trigger(cx, target);
}
if let Some(r) = crate::vif::event_elem_trigger(cx, target) {
return r;
}
if let Some(el) = crate::arrays::resolve_event_element(cx, target)? {
return crate::arrays::when_valid(cx, el.valid, || {
let cur = crate::arrays::load_event_elem(cx, &el)?;
let triggered = event_triggered_value(cx, cur);
crate::arrays::store_event_elem(cx, &el, triggered)
});
}
let ptr = event_cell_ptr(cx, target, "-> (event trigger)")?;
let w = sv_sim_sim::state::EVENT_CELL_WIDTH;
let packed_ty = int_type(cx.ctx, w * 2);
let cur =
cx.b.build_load(packed_ty, ptr, "event_cur")
.unwrap()
.into_int_value();
let packed = event_triggered_value(cx, cur);
cx.b.build_store(ptr, packed).unwrap();
write_through_store(cx, ptr);
Ok(())
}
pub(crate) fn event_triggered_value<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
cur: IntValue<'ctx>,
) -> IntValue<'ctx> {
let w = sv_sim_sim::state::EVENT_CELL_WIDTH;
let packed_ty = int_type(cx.ctx, w * 2);
let val_ty = int_type(cx.ctx, w);
let i64_ty = cx.ctx.i64_type();
let count = cx.b.build_int_truncate(cur, i64_ty, "event_count").unwrap();
let count =
cx.b.build_int_add(count, i64_ty.const_int(1, false), "event_count_inc")
.unwrap();
let rt_time = fetch_or_declare_sv_rt_time(cx.ctx, cx.module);
let now =
cx.b.build_call(rt_time, &[], "event_now")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let stamp =
cx.b.build_int_add(now, i64_ty.const_int(1, false), "event_stamp")
.unwrap();
let lo = cx.b.build_int_z_extend(count, val_ty, "event_lo").unwrap();
let hi = cx.b.build_int_z_extend(stamp, val_ty, "event_hi").unwrap();
let hi =
cx.b.build_left_shift(hi, val_ty.const_int(64, false), "event_hi_sh")
.unwrap();
let val = cx.b.build_or(lo, hi, "event_val").unwrap();
cx.b.build_int_z_extend(val, packed_ty, "event_packed")
.unwrap()
}
fn watcher_cell_through_handle<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
id: &sv_sim_sim::symid::SymbolId,
) -> Result<IntValue<'ctx>, LoweringError> {
let accessor = cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at(
"a sequence end-point method (`.triggered`, 16.9.11) read where no instance \
handle is available",
&cx.fn_sym,
)
})?;
let body = sv_sim_sim::netlist::enclosing_instance_body(&cx.fn_sym).ok_or_else(|| {
LoweringError::unsupported_at(
"a sequence end-point method (`.triggered`, 16.9.11) read outside any instance",
&cx.fn_sym,
)
})?;
let slots = crate::module::signal_slots_of(&body)?;
let slot = slots.get(id).ok_or_else(|| {
LoweringError::unsupported_at(
"a sequence end-point method (`.triggered`, 16.9.11) read in a subroutine whose \
instance is not watched (called from no procedural block of its instance)",
&cx.fn_sym,
)
})?;
Ok(crate::accessor::build_get_wide(
cx,
accessor.fns,
accessor.handle,
slot.slot,
slot.width,
))
}
fn lower_event_triggered<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
let target = *operands.first().ok_or_else(|| {
LoweringError::unsupported_at(".triggered with no event operand", &cx.fn_sym)
})?;
if let Some(v) = crate::class_events::lower_triggered(cx, target)? {
return Ok(v);
}
if sv_sim_sim::netlist::vif::is_member_access(&target) {
let hit = crate::vif::event_triggered(cx, target)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
hit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
if let Some(hit) = crate::vif::event_elem_triggered(cx, target) {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
hit?,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
if target.kind() == ExpressionKind::AssertionInstance
&& let Some(&slot) = cx
.end_flags
.borrow()
.get(&target.source_range().start.offset)
{
let bit =
cx.b.build_load(cx.ctx.bool_type(), slot, "end_flag")
.unwrap()
.into_int_value();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
bit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
let ptr = if target.kind() == ExpressionKind::AssertionInstance {
let id = sv_sim_sim::sva::endpoint::watcher_id(&cx.fn_sym.uid(), &target);
match cx.locals.get(&id) {
Some(&(p, _, _)) => p,
None => {
let cur = watcher_cell_through_handle(cx, &id)?;
let hit = event_stamped_now(cx, cur);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
hit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
}
} else if let Some(el) = crate::arrays::resolve_event_element(cx, target)? {
let cur = crate::arrays::load_event_elem(cx, &el)?;
let hit = event_stamped_now(cx, cur);
let hit =
cx.b.build_and(hit, el.valid, "event_elem_triggered")
.unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
hit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
} else {
event_cell_ptr(cx, target, ".triggered")?
};
let w = sv_sim_sim::state::EVENT_CELL_WIDTH;
let packed_ty = int_type(cx.ctx, w * 2);
let cur =
cx.b.build_load(packed_ty, ptr, "event_cur")
.unwrap()
.into_int_value();
let hit = event_stamped_now(cx, cur);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
hit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)))
}
pub(crate) fn event_stamped_now<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
cur: IntValue<'ctx>,
) -> IntValue<'ctx> {
let packed_ty = cur.get_type();
let i64_ty = cx.ctx.i64_type();
let hi =
cx.b.build_right_shift(cur, packed_ty.const_int(64, false), false, "event_hi")
.unwrap();
let hi = cx.b.build_int_truncate(hi, i64_ty, "event_stamp").unwrap();
let rt_time = fetch_or_declare_sv_rt_time(cx.ctx, cx.module);
let now =
cx.b.build_call(rt_time, &[], "event_now")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let now1 =
cx.b.build_int_add(now, i64_ty.const_int(1, false), "event_now1")
.unwrap();
cx.b.build_int_compare(IntPredicate::EQ, hi, now1, "event_triggered")
.unwrap()
}
pub const SV_RT_RANDOM_SYMBOL: &str = "sv_rt_random";
pub const SV_RT_URANDOM_SYMBOL: &str = "sv_rt_urandom";
pub const SV_RT_URANDOM_RANGE_SYMBOL: &str = "sv_rt_urandom_range";
pub const SV_RT_RANDOM_RESEED_SYMBOL: &str = "sv_rt_random_reseed";
pub const SV_RT_URANDOM_RESEED_SYMBOL: &str = "sv_rt_urandom_reseed";
fn fetch_or_declare_sv_rt_random<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module.get_function(SV_RT_RANDOM_SYMBOL).unwrap_or_else(|| {
let fn_ty = ctx.i32_type().fn_type(&[], false);
module.add_function(SV_RT_RANDOM_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn fetch_or_declare_sv_rt_urandom<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_URANDOM_SYMBOL)
.unwrap_or_else(|| {
let fn_ty = ctx.i32_type().fn_type(&[], false);
module.add_function(SV_RT_URANDOM_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn fetch_or_declare_sv_rt_urandom_range<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_URANDOM_RANGE_SYMBOL)
.unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let fn_ty = i32_ty.fn_type(&[i32_ty.into(), i32_ty.into()], false);
module.add_function(SV_RT_URANDOM_RANGE_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn fetch_or_declare_sv_rt_random_reseed<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_RANDOM_RESEED_SYMBOL)
.unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = i32_ty.fn_type(&[ptr_ty.into()], false);
module.add_function(SV_RT_RANDOM_RESEED_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn fetch_or_declare_sv_rt_urandom_reseed<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_URANDOM_RESEED_SYMBOL)
.unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = i32_ty.fn_type(&[ptr_ty.into()], false);
module.add_function(SV_RT_URANDOM_RESEED_SYMBOL, fn_ty, Some(Linkage::External))
})
}
pub(crate) fn packed_to_plain_i32<'ctx>(
op: &crate::ops::OpBuilder<'_, 'ctx>,
pv: PackedValue<'ctx>,
) -> inkwell::values::IntValue<'ctx> {
let at_width = match pv.width.cmp(&32) {
std::cmp::Ordering::Less => op.extend4(pv, 32, pv.signed),
std::cmp::Ordering::Greater => op.truncate4(pv, 32),
std::cmp::Ordering::Equal => pv,
};
op.lane_val(at_width)
}
fn resolve_seed_lvalue<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
seed_expr: sv_lang::Expression<'d>,
which_name: &str,
) -> Result<(PointerValue<'ctx>, u32, bool), LoweringError> {
let mut seed_expr = seed_expr;
if seed_expr.kind() == ExpressionKind::Assignment {
seed_expr = seed_expr.left().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{which_name}'s seed argument assignment has no lvalue"),
&cx.fn_sym,
)
})?;
}
while seed_expr.kind() == ExpressionKind::Conversion {
seed_expr = seed_expr.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{which_name}'s seed argument conversion missing operand"),
&cx.fn_sym,
)
})?;
}
if !sv_sim_sim::symid::is_value_name(seed_expr.kind()) {
return Err(LoweringError::unsupported_at(
&format!(
"{which_name}'s optional seed argument must be a plain variable (inout) lvalue, \
got {:?}",
seed_expr.kind()
),
&cx.fn_sym,
));
}
let sym = seed_expr.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{which_name}'s seed argument unresolved name"),
&cx.fn_sym,
)
})?;
cx.locals.get(&sym.uid()).copied().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{which_name}'s seed argument is not a local"),
&sym,
)
})
}
fn load_seed_as_plain_i32<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
slot: PointerValue<'ctx>,
width: u32,
signed: bool,
) -> inkwell::values::IntValue<'ctx> {
let ty = if cx.repr == ValueRepr::FourState {
op.i_n(width * 2)
} else {
op.i_n(width)
};
let cur_llvm =
cx.b.build_load(ty, slot, "seed_ld")
.unwrap()
.into_int_value();
let cur_pv = PackedValue {
llvm: cur_llvm,
width,
repr: cx.repr,
signed,
};
packed_to_plain_i32(op, cur_pv)
}
fn store_seed_from_plain_i32<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
slot: PointerValue<'ctx>,
width: u32,
updated_i32: inkwell::values::IntValue<'ctx>,
result_signed: bool,
) {
let src_w = updated_i32.get_type().get_bit_width();
let pv32 = op.pack(
updated_i32,
updated_i32.get_type().const_zero(),
src_w,
cx.repr,
result_signed,
);
let resized = match width.cmp(&src_w) {
std::cmp::Ordering::Less => op.truncate4(pv32, width),
std::cmp::Ordering::Greater => op.extend4(pv32, width, result_signed),
std::cmp::Ordering::Equal => pv32,
};
cx.b.build_store(slot, resized.llvm).unwrap();
write_through_store(cx, slot);
}
enum SeedPlace<'ctx, 'd> {
Slot(PointerValue<'ctx>, u32, bool),
Lvalue(sv_lang::Expression<'d>),
}
fn resolve_seed_place<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
seed_expr: sv_lang::Expression<'d>,
which_name: &str,
) -> Result<SeedPlace<'ctx, 'd>, LoweringError> {
let mut peeled = seed_expr;
if peeled.kind() == ExpressionKind::Assignment {
peeled = peeled.left().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{which_name}'s seed argument assignment has no lvalue"),
&cx.fn_sym,
)
})?;
}
while peeled.kind() == ExpressionKind::Conversion {
peeled = peeled.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{which_name}'s seed argument conversion missing operand"),
&cx.fn_sym,
)
})?;
}
let is_local = sv_sim_sim::symid::is_value_name(peeled.kind())
&& peeled
.referenced_symbol()
.is_some_and(|sym| cx.locals.contains_key(&sym.uid()));
if is_local {
let (slot, width, signed) = resolve_seed_lvalue(cx, seed_expr, which_name)?;
return Ok(SeedPlace::Slot(slot, width, signed));
}
if !peeled.expr_type().is_some_and(|t| t.is_integral()) {
return Err(LoweringError::unsupported_at(
&format!("{which_name}'s seed argument must be an integral variable"),
&cx.fn_sym,
));
}
Ok(SeedPlace::Lvalue(peeled))
}
fn load_seed_place<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
op: &OpBuilder<'_, 'ctx>,
place: &SeedPlace<'ctx, 'd>,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
match *place {
SeedPlace::Slot(slot, width, signed) => {
Ok(load_seed_as_plain_i32(cx, op, slot, width, signed))
}
SeedPlace::Lvalue(lv) => {
let pv = lower_expr(cx, lv)?
.into_packed("a PRNG seed argument must be an integral value", &cx.fn_sym)?;
Ok(packed_to_plain_i32(op, pv))
}
}
}
fn store_seed_place<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
op: &OpBuilder<'_, 'ctx>,
place: &SeedPlace<'ctx, 'd>,
updated_i32: inkwell::values::IntValue<'ctx>,
result_signed: bool,
) -> Result<(), LoweringError> {
match *place {
SeedPlace::Slot(slot, width, _) => {
store_seed_from_plain_i32(cx, op, slot, width, updated_i32, result_signed);
}
SeedPlace::Lvalue(lv) => {
let width = lv.expr_type().map_or(32, |t| t.value_width());
let pv32 = op.pack(
updated_i32,
updated_i32.get_type().const_zero(),
32,
cx.repr,
result_signed,
);
let resized = match width.cmp(&32) {
std::cmp::Ordering::Less => op.truncate4(pv32, width),
std::cmp::Ordering::Greater => op.extend4(pv32, width, result_signed),
std::cmp::Ordering::Equal => pv32,
};
store_to(cx, lv, LoweredValue::Packed(resized))?;
}
}
Ok(())
}
fn lower_random_system_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
which: KnownSystemName,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
match which {
KnownSystemName::Random => {
if let Some(seed_node) = e.children().first() {
let seed_expr = seed_node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
"$random's seed argument is not an expression",
&cx.fn_sym,
)
})?;
let place = resolve_seed_place(cx, seed_expr, "$random")?;
let seed_i32 = load_seed_place(cx, &op, &place)?;
let scratch =
crate::types::entry_alloca_in(&cx.b, cx.ctx.i32_type(), "random_seed_scratch");
cx.b.build_store(scratch, seed_i32).unwrap();
let rt_fn = fetch_or_declare_sv_rt_random_reseed(cx.ctx, cx.module);
let raw =
cx.b.build_call(rt_fn, &[scratch.into()], "rt_random_reseed_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let updated_i32 =
cx.b.build_load(cx.ctx.i32_type(), scratch, "random_seed_after")
.unwrap()
.into_int_value();
store_seed_place(cx, &op, &place, updated_i32, true)?;
return Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
true,
)));
}
let rt_fn = fetch_or_declare_sv_rt_random(cx.ctx, cx.module);
let raw =
cx.b.build_call(rt_fn, &[], "rt_random_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
true,
)))
}
KnownSystemName::URandom => {
if let Some(seed_node) = e.children().first() {
let seed_expr = seed_node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
"$urandom's seed argument is not an expression",
&cx.fn_sym,
)
})?;
let mut peeled = seed_expr;
while peeled.kind() == ExpressionKind::Conversion {
match peeled.conversion_operand() {
Some(o) => peeled = o,
None => break,
}
}
let seed_var = if sv_sim_sim::symid::is_value_name(peeled.kind())
&& peeled
.referenced_symbol()
.is_some_and(|sym| cx.locals.contains_key(&sym.uid()))
{
Some(resolve_seed_lvalue(cx, seed_expr, "$urandom")?)
} else {
None
};
let seed_i32 = match seed_var {
Some((slot, width, signed)) => {
load_seed_as_plain_i32(cx, &op, slot, width, signed)
}
None => match lower_expr(cx, seed_expr)? {
LoweredValue::Packed(pv) => packed_to_plain_i32(&op, pv),
_ => {
return Err(LoweringError::unsupported_at(
"$urandom's seed expression wider than a packed value",
&cx.fn_sym,
));
}
},
};
let scratch =
crate::types::entry_alloca_in(&cx.b, cx.ctx.i32_type(), "urandom_seed_scratch");
cx.b.build_store(scratch, seed_i32).unwrap();
let rt_fn = fetch_or_declare_sv_rt_urandom_reseed(cx.ctx, cx.module);
let raw =
cx.b.build_call(rt_fn, &[scratch.into()], "rt_urandom_reseed_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let updated_i32 =
cx.b.build_load(cx.ctx.i32_type(), scratch, "urandom_seed_after")
.unwrap()
.into_int_value();
if let Some((slot, width, _)) = seed_var {
store_seed_from_plain_i32(cx, &op, slot, width, updated_i32, false);
}
return Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)));
}
let rt_fn = fetch_or_declare_sv_rt_urandom(cx.ctx, cx.module);
let raw =
cx.b.build_call(rt_fn, &[], "rt_urandom_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)))
}
KnownSystemName::URandomRange => {
let children = e.children();
let (hi_node, rest) = children.split_first().ok_or_else(|| {
LoweringError::unsupported_at(
"$urandom_range call has no arguments (needs at least `hi`)",
&cx.fn_sym,
)
})?;
let hi_expr = hi_node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
"$urandom_range's `hi` argument is not an expression",
&cx.fn_sym,
)
})?;
let hi_pv = lower_expr(cx, hi_expr)?.into_packed(
"$urandom_range's `hi` argument must be an integral value",
&cx.fn_sym,
)?;
let hi_i32 = packed_to_plain_i32(&op, hi_pv);
let lo_i32 = if let Some(lo_node) = rest.first() {
let lo_expr = lo_node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
"$urandom_range's `lo` argument is not an expression",
&cx.fn_sym,
)
})?;
let lo_pv = lower_expr(cx, lo_expr)?.into_packed(
"$urandom_range's `lo` argument must be an integral value",
&cx.fn_sym,
)?;
packed_to_plain_i32(&op, lo_pv)
} else {
cx.ctx.i32_type().const_zero()
};
let rt_fn = fetch_or_declare_sv_rt_urandom_range(cx.ctx, cx.module);
let raw =
cx.b.build_call(
rt_fn,
&[hi_i32.into(), lo_i32.into()],
"rt_urandom_range_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)))
}
other => unreachable!(
"lower_random_system_call is only ever invoked (by lower_expr's Call arm, above) for \
KnownSystemName::{{Random,URandom,URandomRange}} -- got {other:?}"
),
}
}
pub const SV_RT_DIST_SYMBOL: &str = "sv_rt_dist";
fn dist_kind(name: &str) -> Option<(i32, usize)> {
use sv_sim_runtime::random as r;
Some(match name.trim_start_matches('$') {
"dist_uniform" => (r::DIST_UNIFORM, 2),
"dist_normal" => (r::DIST_NORMAL, 2),
"dist_exponential" => (r::DIST_EXPONENTIAL, 1),
"dist_poisson" => (r::DIST_POISSON, 1),
"dist_chi_square" => (r::DIST_CHI_SQUARE, 1),
"dist_t" => (r::DIST_T, 1),
"dist_erlang" => (r::DIST_ERLANG, 2),
_ => return None,
})
}
fn lower_dist_system_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
(kind, nparams): (i32, usize),
name: &str,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let children = e.children();
if children.len() != nparams + 1 {
return Err(LoweringError::unsupported_at(
&format!("{name} takes a seed and {nparams} parameter(s)"),
&cx.fn_sym,
));
}
let i32_ty = cx.ctx.i32_type();
let mut params = Vec::new();
for node in &children[1..] {
let ex = node.as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{name}'s argument is not an expression"),
&cx.fn_sym,
)
})?;
let pv = lower_expr(cx, ex)?.into_packed(
&format!("{name}'s arguments must be integral values"),
&cx.fn_sym,
)?;
params.push(packed_to_plain_i32(&op, pv));
}
params.resize(2, i32_ty.const_zero());
let seed_expr = children[0].as_expression().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{name}'s seed argument is not an expression"),
&cx.fn_sym,
)
})?;
let place = resolve_seed_place(cx, seed_expr, name)?;
let seed_i32 = load_seed_place(cx, &op, &place)?;
let scratch = crate::types::entry_alloca_in(&cx.b, i32_ty, "dist_seed_scratch");
cx.b.build_store(scratch, seed_i32).unwrap();
let rt_fn = fetch_or_declare_sv_rt_dist(cx.ctx, cx.module);
let raw =
cx.b.build_call(
rt_fn,
&[
i32_ty.const_int(kind as u64, false).into(),
scratch.into(),
params[0].into(),
params[1].into(),
],
"rt_dist_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let updated =
cx.b.build_load(i32_ty, scratch, "dist_seed_after")
.unwrap()
.into_int_value();
store_seed_place(cx, &op, &place, updated, true)?;
Ok(LoweredValue::Packed(op.pack(
raw,
i32_ty.const_zero(),
32,
cx.repr,
true,
)))
}
fn fetch_or_declare_sv_rt_dist<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module.get_function(SV_RT_DIST_SYMBOL).unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let fn_ty = i32_ty.fn_type(
&[i32_ty.into(), ptr_ty.into(), i32_ty.into(), i32_ty.into()],
false,
);
module.add_function(SV_RT_DIST_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn fetch_or_declare_sv_rt_fopen<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module.get_function(SV_RT_FOPEN_SYMBOL).unwrap_or_else(|| {
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()], false);
module.add_function(SV_RT_FOPEN_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn fetch_or_declare_sv_rt_fopen_mcd<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_FOPEN_MCD_SYMBOL)
.unwrap_or_else(|| {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i32_ty = ctx.i32_type();
let fn_ty = i32_ty.fn_type(&[ptr_ty.into()], false);
module.add_function(SV_RT_FOPEN_MCD_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn lower_fopen_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
let call = match operands.len() {
1 => {
let path_ptr = crate::systask::lower_format_expr(cx, operands[0])?;
let rt_fn = fetch_or_declare_sv_rt_fopen_mcd(cx.ctx, cx.module);
cx.b.build_call(rt_fn, &[path_ptr.into()], "rt_fopen_mcd_call")
.unwrap()
}
2 => {
let path_ptr = crate::systask::lower_format_expr(cx, operands[0])?;
let mode_ptr = crate::systask::lower_format_expr(cx, operands[1])?;
let rt_fn = fetch_or_declare_sv_rt_fopen(cx.ctx, cx.module);
cx.b.build_call(rt_fn, &[path_ptr.into(), mode_ptr.into()], "rt_fopen_call")
.unwrap()
}
n => {
return Err(LoweringError::unsupported_at(
&format!(
"$fopen takes (path) for a multichannel descriptor or (path, mode) for a file \
descriptor (LRM 21.3.1), got {n} arguments"
),
&cx.fn_sym,
));
}
};
let raw = call.try_as_basic_value().unwrap_basic().into_int_value();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
true,
)))
}
fn lower_system_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let cmd_ptr = match operands.len() {
0 => ptr_ty.const_null(),
1 => crate::systask::lower_format_expr(cx, operands[0])?,
n => {
return Err(LoweringError::unsupported_at(
&format!("$system takes at most one argument (LRM 20.17), got {n}"),
&cx.fn_sym,
));
}
};
let rt_fn = cx
.module
.get_function(SV_RT_SYSTEM_SYMBOL)
.unwrap_or_else(|| {
let fn_ty = cx.ctx.i32_type().fn_type(&[ptr_ty.into()], false);
cx.module
.add_function(SV_RT_SYSTEM_SYMBOL, fn_ty, Some(Linkage::External))
});
let call =
cx.b.build_call(rt_fn, &[cmd_ptr.into()], "rt_system_call")
.unwrap();
let raw = call.try_as_basic_value().unwrap_basic().into_int_value();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
true,
)))
}
fn fetch_or_declare_sv_rt_fseek<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module.get_function(SV_RT_FSEEK_SYMBOL).unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let i64_ty = ctx.i64_type();
let fn_ty = i32_ty.fn_type(&[i32_ty.into(), i64_ty.into(), i32_ty.into()], false);
module.add_function(SV_RT_FSEEK_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn lower_fseek_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
if operands.len() != 3 {
return Err(LoweringError::unsupported_at(
"$fseek requires exactly three arguments (fd, offset, operation)",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let fd_pv = lower_expr(cx, operands[0])?
.into_packed("$fseek's fd argument must be an integral value", &cx.fn_sym)?;
let fd_i32 = packed_to_plain_i32(&op, fd_pv);
let offset_pv = lower_expr(cx, operands[1])?.into_packed(
"$fseek's offset argument must be an integral value",
&cx.fn_sym,
)?;
let offset_i32 = packed_to_plain_i32(&op, offset_pv);
let offset_i64 =
cx.b.build_int_s_extend(offset_i32, cx.ctx.i64_type(), "fseek_offset_sext")
.unwrap();
let whence_pv = lower_expr(cx, operands[2])?.into_packed(
"$fseek's operation argument must be an integral value",
&cx.fn_sym,
)?;
let whence_i32 = packed_to_plain_i32(&op, whence_pv);
let rt_fn = fetch_or_declare_sv_rt_fseek(cx.ctx, cx.module);
let raw =
cx.b.build_call(
rt_fn,
&[fd_i32.into(), offset_i64.into(), whence_i32.into()],
"rt_fseek_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
Ok(LoweredValue::Packed(op.pack(
raw,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
true,
)))
}
fn fetch_or_declare_sv_rt_feof<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module.get_function(SV_RT_FEOF_SYMBOL).unwrap_or_else(|| {
let i32_ty = ctx.i32_type();
let i8_ty = ctx.i8_type();
let fn_ty = i8_ty.fn_type(&[i32_ty.into()], false);
module.add_function(SV_RT_FEOF_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn lower_feof_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
if operands.len() != 1 {
return Err(LoweringError::unsupported_at(
"$feof requires exactly one argument (fd)",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let fd_pv = lower_expr(cx, operands[0])?
.into_packed("$feof's fd argument must be an integral value", &cx.fn_sym)?;
let fd_i32 = packed_to_plain_i32(&op, fd_pv);
let rt_fn = fetch_or_declare_sv_rt_feof(cx.ctx, cx.module);
let raw8 =
cx.b.build_call(rt_fn, &[fd_i32.into()], "rt_feof_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let raw32 =
cx.b.build_int_z_extend(raw8, cx.ctx.i32_type(), "feof_zext")
.unwrap();
Ok(LoweredValue::Packed(op.pack(
raw32,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)))
}
fn fetch_or_declare_sv_rt_plusargs_test<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_PLUSARGS_TEST_SYMBOL)
.unwrap_or_else(|| {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i8_ty = ctx.i8_type();
let fn_ty = i8_ty.fn_type(&[ptr_ty.into()], false);
module.add_function(SV_RT_PLUSARGS_TEST_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn lower_test_plusargs_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
if operands.len() != 1 {
return Err(LoweringError::unsupported_at(
"$test$plusargs requires exactly one argument (name)",
&cx.fn_sym,
));
}
let name_ptr = crate::systask::lower_format_expr(cx, operands[0])?;
let rt_fn = fetch_or_declare_sv_rt_plusargs_test(cx.ctx, cx.module);
let raw8 =
cx.b.build_call(rt_fn, &[name_ptr.into()], "rt_plusargs_test_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let raw32 =
cx.b.build_int_z_extend(raw8, cx.ctx.i32_type(), "plusargs_test_zext")
.unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
raw32,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)))
}
fn fetch_or_declare_sv_rt_plusargs_value<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
) -> inkwell::values::FunctionValue<'ctx> {
module
.get_function(SV_RT_PLUSARGS_VALUE_SYMBOL)
.unwrap_or_else(|| {
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let i8_ty = ctx.i8_type();
let fn_ty = i8_ty.fn_type(&[ptr_ty.into(), ptr_ty.into()], false);
module.add_function(SV_RT_PLUSARGS_VALUE_SYMBOL, fn_ty, Some(Linkage::External))
})
}
fn value_plusargs_target<'d>(
cx: &LowerCtx<'_, 'd, '_>,
var_expr: sv_lang::Expression<'d>,
) -> Result<sv_lang::Expression<'d>, LoweringError> {
let mut var_expr = if var_expr.kind() == ExpressionKind::Assignment {
var_expr.left().ok_or_else(|| {
LoweringError::unsupported_at(
"$value$plusargs's variable argument's implicit assignment has no lvalue",
&cx.fn_sym,
)
})?
} else {
var_expr
};
while var_expr.kind() == ExpressionKind::Conversion {
var_expr = var_expr.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at(
"$value$plusargs's variable argument conversion missing operand",
&cx.fn_sym,
)
})?;
}
Ok(var_expr)
}
fn lower_stoch_queue_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
which: KnownSystemName,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_sim_runtime::stoch_queue::{ARG_WORDS, OUT_WORDS, SV_RT_STOCH_Q_SYMBOL, StochOp};
let name = match which {
KnownSystemName::QInitialize => "$q_initialize",
KnownSystemName::QAdd => "$q_add",
KnownSystemName::QRemove => "$q_remove",
KnownSystemName::QFull => "$q_full",
_ => "$q_exam",
};
type Outs = &'static [(usize, usize, Option<usize>)];
let (op, arity, inputs, outputs): (StochOp, usize, &[usize], Outs) = match which {
KnownSystemName::QInitialize => (StochOp::Initialize, 4, &[0, 1, 2], &[(3, 0, None)]),
KnownSystemName::QAdd => (StochOp::Add, 4, &[0, 1, 2], &[(3, 0, None)]),
KnownSystemName::QRemove => (
StochOp::Remove,
4,
&[0],
&[(1, 1, Some(2)), (2, 3, Some(4)), (3, 0, None)],
),
KnownSystemName::QFull => (StochOp::Full, 2, &[0], &[(1, 0, None)]),
_ => (StochOp::Exam, 4, &[0, 1], &[(2, 1, Some(2)), (3, 0, None)]),
};
let operands = e.operands();
if operands.len() != arity {
return Err(LoweringError::unsupported_at(
&format!("{name} with {} arguments (expects {arity})", operands.len()),
&cx.fn_sym,
));
}
let i64t = cx.ctx.i64_type();
let i32t = cx.ctx.i32_type();
let opb = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let to64 = |v: IntValue<'ctx>, width: u32| -> IntValue<'ctx> {
if width < 64 {
cx.b.build_int_z_extend(v, i64t, "stoch_zext").unwrap()
} else if width > 64 {
cx.b.build_int_truncate(v, i64t, "stoch_trunc").unwrap()
} else {
v
}
};
let args =
crate::types::entry_alloca_in(&cx.b, i64t.array_type(ARG_WORDS as u32), "stoch_args");
let outs = crate::types::entry_alloca_in(&cx.b, i64t.array_type(OUT_WORDS as u32), "stoch_out");
let slot = |base: PointerValue<'ctx>, i: usize| -> PointerValue<'ctx> {
unsafe {
cx.b.build_in_bounds_gep(i64t, base, &[i64t.const_int(i as u64, false)], "stoch_slot")
.unwrap()
}
};
let mut words = [i64t.const_zero(); ARG_WORDS];
for (pair, &idx) in inputs.iter().enumerate() {
let v = lower_expr(cx, operands[idx])?
.into_packed(&format!("{name}'s argument {}", idx + 1), &cx.fn_sym)?;
words[pair * 2] = to64(opb.lane_val(v), v.width);
words[pair * 2 + 1] = to64(opb.lane_unk(v), v.width);
}
let scope = e.system_call_scope().unwrap_or(cx.fn_sym);
let tick = crate::timescale::tick_exp(cx.ctx, cx.module, &scope);
words[6] = i64t.const_int(crate::timescale::ticks_per_unit(&scope, tick), false);
if op == StochOp::Exam {
let ty = value_plusargs_target(cx, operands[2])?
.expr_type()
.ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{name}'s value variable has no type"),
&cx.fn_sym,
)
})?;
words[7] = i64t.const_int(u64::from(ty.value_width()), false);
words[8] = i64t.const_int(u64::from(ty.is_signed()), false);
}
for (i, w) in words.iter().enumerate() {
cx.b.build_store(slot(args, i), *w).unwrap();
}
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let rt_fn = cx
.module
.get_function(SV_RT_STOCH_Q_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
SV_RT_STOCH_Q_SYMBOL,
cx.ctx
.void_type()
.fn_type(&[i32t.into(), ptr_ty.into(), ptr_ty.into()], false),
Some(inkwell::module::Linkage::External),
)
});
cx.b.build_call(
rt_fn,
&[
i32t.const_int(op as u32 as u64, false).into(),
args.into(),
outs.into(),
],
"rt_stoch_q_call",
)
.unwrap();
let load = |i: usize| -> IntValue<'ctx> {
cx.b.build_load(i64t, slot(outs, i), "stoch_out_word")
.unwrap()
.into_int_value()
};
let from64 = |w: IntValue<'ctx>, width: u32| -> IntValue<'ctx> {
if width < 64 {
cx.b.build_int_truncate(w, opb.i_n(width), "stoch_fit")
.unwrap()
} else {
w
}
};
for &(operand, vword, uword) in outputs {
let target = value_plusargs_target(cx, operands[operand])?;
let ty = target.expr_type().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("{name}'s output variable has no type"),
&cx.fn_sym,
)
})?;
if !ty.is_integral() || ty.value_width() > 64 {
return Err(LoweringError::unsupported_at(
&format!("{name}'s output variable is not an integral value of at most 64 bits"),
&cx.fn_sym,
));
}
let width = ty.value_width().max(1);
let unk = match uword {
Some(u) => from64(load(u), width),
None => opb.i_n(width).const_zero(),
};
let pv = opb.pack(
from64(load(vword), width),
unk,
width,
cx.repr,
ty.is_signed(),
);
crate::expr::store_to(cx, target, LoweredValue::Packed(pv))?;
}
if which == KnownSystemName::QFull {
return Ok(LoweredValue::Packed(opb.pack(
from64(load(1), 32),
from64(load(2), 32),
32,
cx.repr,
true,
)));
}
Ok(LoweredValue::Packed(opb.pack(
i32t.const_zero(),
i32t.const_zero(),
32,
cx.repr,
false,
)))
}
fn lower_countdrivers_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let bad =
|why: &str| LoweringError::unsupported_at(&format!("$countdrivers {why}"), &cx.fn_sym);
let operands = e.operands();
if operands.is_empty() || operands.len() > 6 {
return Err(bad("with no net argument or more than six arguments"));
}
let mut net = operands[0];
while net.kind() == ExpressionKind::Conversion {
match net.conversion_operand() {
Some(inner) => net = inner,
None => break,
}
}
let (key, off) = crate::systask::net_strength_key(net)
.ok_or_else(|| bad("of something other than a net or a constant bit-select of one"))?;
let mut base = net;
while matches!(
base.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
base = base
.select_value()
.ok_or_else(|| bad("of a malformed select"))?;
}
let sym = base
.referenced_symbol()
.ok_or_else(|| bad("of an unresolved name"))?;
let wt = cx
.write_through
.as_ref()
.ok_or_else(|| bad("outside a process (it reads the net's live state)"))?;
let slot = cx
.locals
.get(&sym.uid())
.and_then(|(ptr, _, _)| wt.signals.get(ptr))
.map(|e| e.0)
.ok_or_else(|| bad("of a net this process does not read"))?;
let i64t = cx.ctx.i64_type();
let i32t = cx.ctx.i32_type();
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let opb = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let outs = crate::types::entry_alloca_in(&cx.b, i32t.array_type(5), "countdrivers_out");
let rt_fn = cx
.module
.get_function("sv_rt_net_drivers")
.unwrap_or_else(|| {
cx.module.add_function(
"sv_rt_net_drivers",
cx.ctx.void_type().fn_type(
&[
ptr_ty.into(),
i32t.into(),
i64t.into(),
i32t.into(),
ptr_ty.into(),
],
false,
),
Some(inkwell::module::Linkage::External),
)
});
cx.b.build_call(
rt_fn,
&[
wt.handle.into(),
i32t.const_int(u64::from(slot), false).into(),
i64t.const_int(key, false).into(),
i32t.const_int(u64::from(off), false).into(),
outs.into(),
],
"rt_net_drivers_call",
)
.unwrap();
let load = |i: u64| -> IntValue<'ctx> {
let p = unsafe {
cx.b.build_in_bounds_gep(i32t, outs, &[i64t.const_int(i, false)], "countdrivers_slot")
.unwrap()
};
cx.b.build_load(i32t, p, "countdrivers_word")
.unwrap()
.into_int_value()
};
for (k, &operand) in operands.iter().enumerate().skip(1) {
let target = value_plusargs_target(cx, operand)?;
let ty = target
.expr_type()
.ok_or_else(|| bad("with an untyped output variable"))?;
if !ty.is_integral() || ty.value_width() > 64 {
return Err(bad(
"with an output variable that is not integral or exceeds 64 bits",
));
}
let width = ty.value_width().max(1);
let w = load(k as u64 - 1);
let v = match width.cmp(&32) {
std::cmp::Ordering::Less => {
cx.b.build_int_truncate(w, opb.i_n(width), "cd_fit")
.unwrap()
}
std::cmp::Ordering::Equal => w,
std::cmp::Ordering::Greater => {
cx.b.build_int_z_extend(w, opb.i_n(width), "cd_ext")
.unwrap()
}
};
let pv = opb.pack(
v,
opb.i_n(width).const_zero(),
width,
cx.repr,
ty.is_signed(),
);
crate::expr::store_to(cx, target, LoweredValue::Packed(pv))?;
}
let multi =
cx.b.build_int_compare(
IntPredicate::UGT,
load(1),
i32t.const_int(1, false),
"cd_multi",
)
.unwrap();
let multi = cx.b.build_int_z_extend(multi, i32t, "cd_multi32").unwrap();
Ok(LoweredValue::Packed(opb.pack(
multi,
i32t.const_zero(),
32,
cx.repr,
true,
)))
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum PlusArgsClass {
Int,
Real,
Str,
}
fn lower_value_plusargs_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
if operands.len() != 2 {
return Err(LoweringError::unsupported_at(
"$value$plusargs requires exactly two arguments (format, variable)",
&cx.fn_sym,
));
}
let target = value_plusargs_target(cx, operands[1])?;
let target_ty = target.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("$value$plusargs's variable has no type", &cx.fn_sym)
})?;
let class = if target_ty.is_string() {
PlusArgsClass::Str
} else if target_ty.is_floating() {
PlusArgsClass::Real
} else if target_ty.is_integral() {
PlusArgsClass::Int
} else {
return Err(LoweringError::unsupported_at(
"$value$plusargs into a variable that is not integral, real or string",
&cx.fn_sym,
));
};
let mut fmt_lit = operands[0];
while fmt_lit.kind() == ExpressionKind::Conversion {
fmt_lit = fmt_lit.conversion_operand().unwrap_or(fmt_lit);
}
if fmt_lit.kind() == ExpressionKind::StringLiteral {
let fmt_text = fmt_lit.string_literal_value().unwrap_or_default();
let (_, spec) = sv_sim_runtime::plusargs::split_value_format(&fmt_text).map_err(|err| {
LoweringError::unsupported_at(
&format!("$value$plusargs format string `{fmt_text}` is invalid: {err}"),
&cx.fn_sym,
)
})?;
if !matches!(spec, 'd' | 'h' | 'x' | 'o' | 'b' | 's' | 'e' | 'f' | 'g') {
return Err(LoweringError::unsupported_at(
&format!("$value$plusargs format specifier '%{spec}' (LRM 21.6)"),
&cx.fn_sym,
));
}
if class == PlusArgsClass::Str && spec != 's' {
return Err(LoweringError::unsupported_at(
&format!(
"$value$plusargs with '%{spec}' into a `string` variable (only %s is defined)"
),
&cx.fn_sym,
));
}
}
let fmt_ptr = crate::systask::lower_format_expr(cx, operands[0])?;
let target_width = target_ty.value_width();
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let out_alloca = crate::types::entry_alloca_in(&cx.b, rtarg_ty, "plusargs_value_out");
if let Some(instr) = out_alloca.as_instruction() {
instr
.set_alignment(16)
.expect("alloca instruction accepts an explicit alignment");
}
let kind_p =
cx.b.build_struct_gep(rtarg_ty, out_alloca, 0, "plusargs_value_kind_p")
.unwrap();
let kind_tag = match class {
PlusArgsClass::Int => 0,
PlusArgsClass::Str => 2,
PlusArgsClass::Real => 3,
};
cx.b.build_store(kind_p, cx.ctx.i32_type().const_int(kind_tag, false))
.unwrap();
let width_p =
cx.b.build_struct_gep(rtarg_ty, out_alloca, 1, "plusargs_value_width_p")
.unwrap();
cx.b.build_store(
width_p,
cx.ctx.i32_type().const_int(u64::from(target_width), false),
)
.unwrap();
let rt_fn = fetch_or_declare_sv_rt_plusargs_value(cx.ctx, cx.module);
let found8 =
cx.b.build_call(
rt_fn,
&[fmt_ptr.into(), out_alloca.into()],
"rt_plusargs_value_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let entry_block = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at("$value$plusargs lowered outside a basic block", &cx.fn_sym)
})?;
let f = entry_block.get_parent().ok_or_else(|| {
LoweringError::unsupported_at("$value$plusargs block has no parent function", &cx.fn_sym)
})?;
let found_block = cx.ctx.append_basic_block(f, "plusargs_value_found");
let merge_block = cx.ctx.append_basic_block(f, "plusargs_value_merge");
let found_bool =
cx.b.build_int_compare(
IntPredicate::NE,
found8,
cx.ctx.i8_type().const_zero(),
"plusargs_value_found_bool",
)
.unwrap();
cx.b.build_conditional_branch(found_bool, found_block, merge_block)
.unwrap();
cx.b.position_at_end(found_block);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if class == PlusArgsClass::Str {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let ptr_field =
cx.b.build_struct_gep(rtarg_ty, out_alloca, 8, "plusargs_value_str_ptr_p")
.unwrap();
let str_ptr =
cx.b.build_load(ptr_ty, ptr_field, "plusargs_value_str_ptr")
.unwrap()
.into_pointer_value();
let len_field =
cx.b.build_struct_gep(rtarg_ty, out_alloca, 9, "plusargs_value_str_len_p")
.unwrap();
let len32 =
cx.b.build_load(cx.ctx.i32_type(), len_field, "plusargs_value_str_len")
.unwrap()
.into_int_value();
let len64 =
cx.b.build_int_z_extend(len32, cx.ctx.i64_type(), "plusargs_value_str_len64")
.unwrap();
let s = crate::strings::intern_bytes(cx, str_ptr, len64);
crate::fileio::store_output(cx, target, LoweredValue::Str(s))?;
} else if class == PlusArgsClass::Real {
let real_ptr =
cx.b.build_struct_gep(rtarg_ty, out_alloca, 4, "plusargs_value_real_p")
.unwrap();
let real =
cx.b.build_load(cx.ctx.f64_type(), real_ptr, "plusargs_value_real")
.unwrap()
.into_float_value();
crate::expr::store_to(cx, target, LoweredValue::Real(RealValue { llvm: real }))?;
} else {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let width = target_width.max(1);
let words = u64::from(width).div_ceil(64);
let lane_ty = op.i_n((words * 64) as u32);
let mut lanes = Vec::new();
for (field, name) in [(5u32, "aval"), (6u32, "bval")] {
let slot =
cx.b.build_struct_gep(rtarg_ty, out_alloca, field, "plusargs_value_words_p")
.unwrap();
let words_ptr =
cx.b.build_load(ptr_ty, slot, "plusargs_value_words")
.unwrap()
.into_pointer_value();
let raw =
cx.b.build_load(lane_ty, words_ptr, &format!("plusargs_value_{name}"))
.unwrap();
inkwell::values::BasicValue::as_instruction_value(&raw)
.expect("a load is an instruction")
.set_alignment(8)
.expect("load accepts an explicit alignment");
let mut raw = raw.into_int_value();
if width != (words * 64) as u32 {
raw =
cx.b.build_int_truncate(raw, op.i_n(width), "plusargs_value_fit")
.unwrap();
}
lanes.push(raw);
}
let val =
cx.b.build_xor(lanes[0], lanes[1], "plusargs_value_val")
.unwrap();
let pv = op.pack(val, lanes[1], width, cx.repr, false);
crate::expr::store_to(cx, target, LoweredValue::Packed(pv))?;
}
cx.b.build_unconditional_branch(merge_block).unwrap();
cx.b.position_at_end(merge_block);
let found32 =
cx.b.build_int_z_extend(found8, cx.ctx.i32_type(), "plusargs_value_found_zext")
.unwrap();
Ok(LoweredValue::Packed(op.pack(
found32,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)))
}
pub(crate) fn lower_timescale_query<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
name: &str,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let target = match e.operands().first() {
Some(a) => a.arbitrary_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
"$timeunit/$timeprecision argument must name a module, instance or $unit",
&cx.fn_sym,
)
})?,
None => e.system_call_scope().unwrap_or(cx.fn_sym),
};
let (unit, prec) = crate::timescale::unit_and_precision(&target);
let v = if name == "$timeunit" { unit } else { prec };
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
cx.ctx.i32_type().const_int(i64::from(v) as u64, true),
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
true,
)))
}
pub(crate) fn lower_time_system_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
which: KnownSystemName,
_name: &str,
scope: &sv_lang::Symbol<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let tick = crate::timescale::tick_exp(cx.ctx, cx.module, scope);
let per_unit = crate::timescale::ticks_per_unit(scope, tick);
let rt_fn = fetch_or_declare_sv_rt_time(cx.ctx, cx.module);
let raw64 =
cx.b.build_call(rt_fn, &[], "rt_time_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let ticks = raw64;
let raw64 = if per_unit > 1 && which != KnownSystemName::RealTime {
let half =
cx.b.build_int_add(ticks, i64t.const_int(per_unit / 2, false), "time_round")
.unwrap();
cx.b.build_int_unsigned_div(half, i64t.const_int(per_unit, false), "time_units")
.unwrap()
} else {
ticks
};
match which {
KnownSystemName::Time => Ok(LoweredValue::Packed(op.pack(
raw64,
cx.ctx.i64_type().const_zero(),
64,
cx.repr,
false,
))),
KnownSystemName::STime => {
let raw32 =
cx.b.build_int_truncate(raw64, cx.ctx.i32_type(), "stime_trunc")
.unwrap();
Ok(LoweredValue::Packed(op.pack(
raw32,
cx.ctx.i32_type().const_zero(),
32,
cx.repr,
false,
)))
}
KnownSystemName::RealTime => {
let f =
cx.b.build_unsigned_int_to_float(ticks, cx.ctx.f64_type(), "realtime_uitofp")
.unwrap();
let f = if per_unit > 1 {
cx.b.build_float_div(
f,
cx.ctx.f64_type().const_float(per_unit as f64),
"realtime_units",
)
.unwrap()
} else {
f
};
Ok(LoweredValue::Real(RealValue { llvm: f }))
}
other => unreachable!(
"lower_time_system_call is only ever invoked (by lower_expr's Call arm, above) for \
KnownSystemName::{{Time,STime,RealTime}} -- got {other:?}"
),
}
}
pub(crate) fn mem_row_index_i64<'ctx>(
op: &OpBuilder<'_, 'ctx>,
cx: &LowerCtx<'ctx, '_, '_>,
idx: PackedValue<'ctx>,
) -> IntValue<'ctx> {
let val = op.lane_val(idx);
let src_bits = val.get_type().get_bit_width();
let i64_ty = cx.ctx.i64_type();
if src_bits == 64 {
val
} else if src_bits < 64 && idx.signed {
cx.b.build_int_s_extend(val, i64_ty, "mem_row_index_sext")
.unwrap()
} else if src_bits < 64 {
cx.b.build_int_z_extend(val, i64_ty, "mem_row_index_zext")
.unwrap()
} else {
cx.b.build_int_truncate(val, i64_ty, "mem_row_index_trunc")
.unwrap()
}
}
fn unsized_literal_leads_unknown(e: sv_lang::Expression<'_>) -> bool {
e.kind() == ExpressionKind::IntegerLiteral
&& e.is_declared_unsized() == Some(true)
&& e.integer_literal_value()
.and_then(|cv| {
let iv = cv.as_integer()?;
iv.bit(iv.bit_width() as usize - 1).map(|b| b.is_unknown())
})
.unwrap_or(false)
}
pub(crate) fn lower_constant<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
iv: &sv_lang::SVInt,
e: Option<sv_lang::Expression<'_>>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if let Some(ex) = e
&& let Some(h) = crate::huge::lower_constant(cx, ex)?
{
return Ok(h);
}
if e.is_none() && crate::huge::is_huge_width(iv.bit_width()) {
return Err(LoweringError::unsupported_at(
"a constant wider than 65535 bits with no expression to lower it from",
&cx.fn_sym,
));
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
match op.lower_const_any(iv, cx.repr) {
Some(AnyValue::Narrow(p)) => Ok(LoweredValue::Packed(p)),
Some(AnyValue::Wide(w)) => Ok(LoweredValue::Wide(w)),
None => Err(LoweringError::unsupported_at(
&format!(
"a {}-bit constant whose bits sv-lang does not expose",
iv.bit_width()
),
&cx.fn_sym,
)),
}
}
pub(crate) fn type_reference_equality(e: sv_lang::Expression<'_>, bop: BinaryOp) -> Option<bool> {
let negate = match bop {
BinaryOp::Equality | BinaryOp::CaseEquality => false,
BinaryOp::Inequality | BinaryOp::CaseInequality => true,
_ => return None,
};
let l = e.left()?.type_reference_target()?;
let r = e.right()?.type_reference_target()?;
Some(l.is_matching(&r) != negate)
}
pub(crate) fn read_modify_target<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if target.kind() == ExpressionKind::ElementSelect
&& let Some(r) = crate::collections::read_element(cx, target)
{
return r;
}
lower_expr(cx, target)
}
pub fn lower_expr<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let v = lower_expr_h(cx, e)?;
Ok(crate::huge::bridge(cx, v))
}
pub(crate) fn modport_expr_target<'d>(
e: &sv_lang::Expression<'d>,
) -> Option<sv_lang::Expression<'d>> {
if !matches!(
e.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
return None;
}
let sym = e.referenced_symbol()?;
if sym.kind() != sv_lang::kinds::SymbolKind::ModportPort {
return None;
}
sym.modport_port_explicit_connection()
}
pub fn lower_expr_h<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if let Some(inner) = modport_expr_target(&e) {
return lower_expr_h(cx, inner);
}
if let Some(v) = crate::vif::hoisted(&e) {
return Ok(v);
}
if let Some(v) = crate::huge::try_lower(cx, e)? {
return Ok(v);
}
lower_expr_regular(cx, e)
}
pub(crate) fn lower_expr_regular<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if crate::sva::proc::call_args_active()
&& let Some((slot, width, signed)) = {
let key = crate::sva::proc::call_arg_key(&e);
cx.locals.get(&key).copied().or_else(|| {
crate::sva::proc::spawn_arg_input(&key).map(|(k, width, signed)| {
(
crate::process_lower::assert_input_slot(cx, k, width),
width,
signed,
)
})
})
}
{
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = if cx.repr == ValueRepr::FourState {
op.i_n(width * 2)
} else {
op.i_n(width)
};
let v =
cx.b.build_load(ty, slot, "ld_call_arg")
.unwrap()
.into_int_value();
return Ok(LoweredValue::Packed(PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
}));
}
if e.expr_type().is_some_and(|t| t.is_string()) {
if let Some(s) = crate::strings::lower_string_expr(cx, e)? {
return Ok(LoweredValue::Str(s));
}
let v = lower_expr_node(cx, e)?;
return Ok(if let LoweredValue::Packed(pv) = v {
LoweredValue::Str(crate::strings::from_storage(cx, pv))
} else {
v
});
}
if let Some(r) = crate::collections::lower_constructor(cx, e) {
return r;
}
let v = lower_expr_node(cx, e)?;
if let LoweredValue::Packed(pv) = v
&& e.expr_type().is_some_and(|t| t.is_floating())
{
return Ok(LoweredValue::Real(crate::real::bits_to_real(cx, pv)));
}
if let LoweredValue::Packed(pv) = v
&& let Some(t) = e.expr_type()
&& t.is_integral()
&& t.value_width() == pv.width
{
return Ok(LoweredValue::Packed(PackedValue {
signed: t.is_signed(),
..pv
}));
}
Ok(v)
}
fn annotated_specparam_read<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
sym: &sv_lang::Symbol<'_>,
base: LoweredValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let annotated = crate::timescale::lower_specparam_read(cx, sym)?;
let f64t = cx.ctx.f64_type();
let none =
cx.b.build_float_compare(
inkwell::FloatPredicate::UNO,
annotated,
annotated,
"sp_none",
)
.unwrap();
match base {
LoweredValue::Real(r) => Ok(LoweredValue::Real(RealValue {
llvm: cx
.b
.build_select(none, r.llvm, annotated, "sp_real")
.unwrap()
.into_float_value(),
})),
LoweredValue::Packed(pv) => {
let safe =
cx.b.build_select(none, f64t.const_zero(), annotated, "sp_safe")
.unwrap()
.into_float_value();
let rounded = op.round_real_to_int(RealValue { llvm: safe }, pv.width, pv.signed);
let annotated_pv = op.pack(
rounded,
op.i_n(pv.width).const_zero(),
pv.width,
pv.repr,
pv.signed,
);
Ok(LoweredValue::Packed(PackedValue {
llvm: cx
.b
.build_select(none, pv.llvm, annotated_pv.llvm, "sp_int")
.unwrap()
.into_int_value(),
..pv
}))
}
wide @ (LoweredValue::Wide(_) | LoweredValue::Str(_) | LoweredValue::Huge(_)) => Ok(wide),
}
}
fn lower_expr_node<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
match e.kind() {
ExpressionKind::IntegerLiteral
| ExpressionKind::StringLiteral
| ExpressionKind::UnbasedUnsizedIntegerLiteral => {
let cv = match e.kind() {
ExpressionKind::IntegerLiteral => e.integer_literal_value(),
ExpressionKind::StringLiteral => e.string_literal_int_value(),
ExpressionKind::UnbasedUnsizedIntegerLiteral => e.unbased_unsized_literal_value(),
_ => unreachable!(
"filtered by the outer match arm above, which already restricts \
`e.kind()` to exactly these three -- `kind()` is a pure read with no \
side effects between the two calls"
),
}
.ok_or_else(|| LoweringError::unsupported_at("non-const literal", &cx.fn_sym))?;
let iv = cv
.as_integer()
.ok_or_else(|| LoweringError::unsupported_at("non-integer literal", &cx.fn_sym))?;
lower_constant(cx, iv, Some(e))
}
ExpressionKind::RealLiteral => {
let val = e.real_literal_value().ok_or_else(|| {
LoweringError::unsupported_at("real literal missing value", &cx.fn_sym)
})?;
Ok(LoweredValue::Real(RealValue {
llvm: cx.ctx.f64_type().const_float(val),
}))
}
ExpressionKind::TimeLiteral => {
let val = e.time_literal_value().ok_or_else(|| {
LoweringError::unsupported_at("time literal missing value", &cx.fn_sym)
})?;
let val = match e.time_literal_scale() {
Some(ts) => {
let steps = 10f64.powi(
crate::timescale::exp_of(ts.base) - crate::timescale::exp_of(ts.precision),
);
(val * steps).round() / steps
}
None => val,
};
Ok(LoweredValue::Real(RealValue {
llvm: cx.ctx.f64_type().const_float(val),
}))
}
ExpressionKind::NamedValue
| ExpressionKind::HierarchicalValue
| ExpressionKind::ArbitrarySymbol => {
if e.kind() == ExpressionKind::ArbitrarySymbol
&& !e.referenced_symbol().is_some_and(|s| {
matches!(
s.kind(),
sv_lang::kinds::SymbolKind::Variable | sv_lang::kinds::SymbolKind::Net
)
})
{
return e
.referenced_symbol()
.and_then(|s| crate::vif::instance_value(cx, &s))
.ok_or_else(|| {
LoweringError::unsupported_at(
"a symbol used as a value that is not an interface instance",
&cx.fn_sym,
)
});
}
let sym = e
.referenced_symbol()
.ok_or_else(|| LoweringError::unsupported_at("unresolved name", &cx.fn_sym))?;
if let Some(v) = crate::array_methods::iterator_value(cx, &sym) {
return Ok(v);
}
if let Some(v) = crate::classes::try_read_bare_field(cx, &sym)? {
return Ok(v);
}
if let Some((slot, width, signed)) = cx.locals.get(&sym.uid()).copied() {
crate::collections::elem_ref_refresh(cx, slot);
let ty = if cx.repr == ValueRepr::FourState {
op.i_n(width * 2)
} else {
op.i_n(width)
};
let v = cx.b.build_load(ty, slot, "ld").unwrap().into_int_value();
let pv = PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
};
if sym.value_type().is_some_and(|t| t.is_floating()) {
return Ok(LoweredValue::Real(crate::real::bits_to_real(cx, pv)));
}
return Ok(LoweredValue::Packed(pv));
}
let cv = e
.constant_value()
.or_else(|| {
matches!(
sym.kind(),
sv_lang::kinds::SymbolKind::Specparam
| sv_lang::kinds::SymbolKind::Parameter
| sv_lang::kinds::SymbolKind::EnumValue
)
.then(|| sym.constant_value())
.flatten()
})
.ok_or_else(|| LoweringError::unsupported_at("read of non-local", &sym))?;
let base = match &cv {
sv_lang::ConstantValue::Real(f) => LoweredValue::Real(RealValue {
llvm: cx.ctx.f64_type().const_float(*f),
}),
sv_lang::ConstantValue::String(text) => {
LoweredValue::Str(crate::strings::literal(cx, text.as_bytes()))
}
sv_lang::ConstantValue::Null => {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(op.pack(
op.i_n(64).const_zero(),
op.i_n(64).const_zero(),
64,
cx.repr,
false,
))
}
sv_lang::ConstantValue::Array(_)
if e.expr_type().is_some_and(|t| t.is_unpacked_struct()) =>
{
let ty = e.expr_type().expect("checked above");
let mut strs = Vec::new();
let bits = const_stored_bits_strs(&ty, &cv, 0, &mut strs)
.filter(|b| !b.is_empty() && b.len() as u32 <= crate::value::WIDE_THRESHOLD)
.ok_or_else(|| {
LoweringError::unsupported_at(
"an unpacked-struct named constant with a field that is not \
integral, real or string (or wider than the wide-value threshold)",
&sym,
)
})?;
let mut pv = crate::value::const_bits(cx.ctx, &bits, false, cx.repr);
for (off, text) in &strs {
let id = crate::strings::literal(cx, text).llvm;
let wide = pv.llvm.get_type();
let ext = cx.b.build_int_z_extend(id, wide, "sid").unwrap();
let sh = cx
.b
.build_left_shift(ext, wide.const_int(*off as u64, false), "sid_sh")
.unwrap();
pv.llvm = cx.b.build_or(pv.llvm, sh, "sid_or").unwrap();
}
LoweredValue::Packed(pv)
}
_ => {
let iv = cv.as_integer().ok_or_else(|| {
LoweringError::unsupported_at(
"named constant is neither an integer, a real nor a string \
(an unpacked-array, struct or class-handle named constant \
read is out of scope)",
&sym,
)
})?;
lower_constant(cx, iv, Some(e))?
}
};
if sym.kind() == sv_lang::kinds::SymbolKind::Specparam {
return annotated_specparam_read(cx, &op, &sym, base);
}
Ok(base)
}
ExpressionKind::UnaryOp => {
let operand_expr = e.operand().ok_or_else(|| {
LoweringError::unsupported_at("unary op missing operand", &cx.fn_sym)
})?;
let uop = e.unary_op().ok_or_else(|| {
LoweringError::unsupported_at("unary op missing operator", &cx.fn_sym)
})?;
if matches!(
uop,
UnaryOp::Preincrement
| UnaryOp::Predecrement
| UnaryOp::Postincrement
| UnaryOp::Postdecrement
) {
let old = read_modify_target(cx, operand_expr)?;
if let LoweredValue::Real(rv) = old {
let one = cx.ctx.f64_type().const_float(1.0);
let f = crate::real::as_f64(cx, rv.llvm);
let new = match uop {
UnaryOp::Preincrement | UnaryOp::Postincrement => {
cx.b.build_float_add(f, one, "real_inc").unwrap()
}
_ => cx.b.build_float_sub(f, one, "real_dec").unwrap(),
};
let new = LoweredValue::Real(RealValue { llvm: new });
store_to(cx, operand_expr, new)?;
return Ok(match uop {
UnaryOp::Preincrement | UnaryOp::Predecrement => new,
_ => old,
});
}
let old_pv =
old.into_packed("increment/decrement of a >128-bit target", &cx.fn_sym)?;
let width = old_pv.width;
let one_pv = op.pack(
op.i_n(width).const_int(1, false),
op.i_n(width).const_zero(),
width,
cx.repr,
old_pv.signed,
);
let new_pv = match uop {
UnaryOp::Preincrement | UnaryOp::Postincrement => op.add4(old_pv, one_pv),
UnaryOp::Predecrement | UnaryOp::Postdecrement => op.sub4(old_pv, one_pv),
_ => unreachable!("filtered by the outer matches! above"),
};
store_to(cx, operand_expr, LoweredValue::Packed(new_pv))?;
let result_pv = match uop {
UnaryOp::Preincrement | UnaryOp::Predecrement => new_pv,
UnaryOp::Postincrement | UnaryOp::Postdecrement => old_pv,
_ => unreachable!("filtered by the outer matches! above"),
};
return Ok(LoweredValue::Packed(result_pv));
}
if uop == UnaryOp::LogicalNot
&& let Some(non_null) = crate::events::lower_event_condition(cx, operand_expr)?
{
let is_null = cx.b.build_not(non_null, "ev_isnull").unwrap();
return Ok(LoweredValue::Packed(op.pack(
is_null,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
let operand_lv = lower_expr(cx, operand_expr)?;
match operand_lv {
LoweredValue::Str(_) => Err(LoweringError::unsupported_at(
&format!("unary op {uop:?} on a string operand"),
&cx.fn_sym,
)),
LoweredValue::Real(rv) => match uop {
UnaryOp::Minus => Ok(LoweredValue::Real(RealValue {
llvm: cx.b.build_float_neg(rv.llvm, "fneg").unwrap(),
})),
UnaryOp::Plus => Ok(LoweredValue::Real(rv)),
UnaryOp::LogicalNot => {
let t = crate::real::real_truth(cx, rv);
let not = cx.b.build_not(t, "real_lnot").unwrap();
Ok(LoweredValue::Packed(op.pack(
not,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)))
}
other => Err(LoweringError::unsupported_at(
&format!("unary op {other:?} on a real operand"),
&cx.fn_sym,
)),
},
LoweredValue::Packed(a) => Ok(LoweredValue::Packed(match uop {
UnaryOp::BitwiseNot => op.not4(a),
UnaryOp::BitwiseAnd => op.redand4(a),
UnaryOp::BitwiseOr => op.redor4(a),
UnaryOp::BitwiseXor => op.redxor4(a),
UnaryOp::BitwiseNand => op.not4(op.redand4(a)),
UnaryOp::BitwiseNor => op.not4(op.redor4(a)),
UnaryOp::BitwiseXnor => op.not4(op.redxor4(a)),
UnaryOp::Minus => op.neg4(a),
UnaryOp::Plus => a,
UnaryOp::LogicalNot => op.lnot4(a),
other => {
return Err(LoweringError::unsupported_at(
&format!("unary op {other:?}"),
&cx.fn_sym,
));
}
})),
v @ (LoweredValue::Wide(_) | LoweredValue::Huge(_)) if uop == UnaryOp::Plus => {
Ok(v)
}
LoweredValue::Wide(_) | LoweredValue::Huge(_) => {
Err(LoweringError::unsupported_at(
&format!("unary op {uop:?} on a wide (>WIDE_THRESHOLD-bit) operand"),
&cx.fn_sym,
))
}
}
}
ExpressionKind::BinaryOp => {
let bop = e.binary_op().ok_or_else(|| {
LoweringError::unsupported_at("binary op missing operator", &cx.fn_sym)
})?;
if let Some(eq) = type_reference_equality(e, bop) {
let bit = if eq {
sv_lang::Bit::One
} else {
sv_lang::Bit::Zero
};
return Ok(LoweredValue::Packed(crate::value::const_bits(
cx.ctx,
&[bit],
false,
cx.repr,
)));
}
if matches!(
bop,
BinaryOp::Equality
| BinaryOp::Inequality
| BinaryOp::CaseEquality
| BinaryOp::CaseInequality
) && let Some(v) = crate::events::lower_event_equality(
cx,
e,
matches!(bop, BinaryOp::Inequality | BinaryOp::CaseInequality),
)? {
return Ok(v);
}
if matches!(
bop,
BinaryOp::Equality
| BinaryOp::Inequality
| BinaryOp::CaseEquality
| BinaryOp::CaseInequality
) && let Some(r) = crate::collections::lower_equality(
cx,
e,
matches!(bop, BinaryOp::Inequality | BinaryOp::CaseInequality),
) {
return r;
}
let is_real_operand = |x: Option<sv_lang::Expression<'d>>| {
x.and_then(|x| x.expr_type())
.is_some_and(|t| t.is_floating())
};
if (is_real_operand(e.left()) || is_real_operand(e.right()))
&& !matches!(
bop,
BinaryOp::LogicalImplication | BinaryOp::LogicalEquivalence
)
{
return lower_real_binary_expr(cx, e, bop);
}
let is_string_operand = |x: Option<sv_lang::Expression<'d>>| {
x.and_then(|x| x.expr_type()).is_some_and(|t| t.is_string())
};
if (is_string_operand(e.left()) || is_string_operand(e.right()))
&& let Some(v) = crate::strings::lower_string_binary(cx, e, bop)?
{
return Ok(v);
}
if e.left()
.and_then(|x| x.expr_type())
.is_some_and(|t| t.is_unpacked_array())
{
return crate::arrays::compare_arrays(cx, e, bop).map(LoweredValue::Packed);
}
if matches!(bop, BinaryOp::LogicalAnd | BinaryOp::LogicalOr) {
if let Some(v) = fold_constant_logical(cx, e)? {
return Ok(v);
}
return lower_short_circuit_logical_value(
cx,
e,
bop == BinaryOp::LogicalAnd,
false,
);
}
if bop == BinaryOp::LogicalImplication {
return lower_short_circuit_logical_value(cx, e, false, true);
}
if bop == BinaryOp::LogicalEquivalence {
return lower_logical_equivalence(cx, e);
}
if bop == BinaryOp::Power {
let base = lower_expr(
cx,
e.left().ok_or_else(|| {
LoweringError::unsupported_at("power missing base operand", &cx.fn_sym)
})?,
)?
.into_packed("power on a non-integral (real) base operand", &cx.fn_sym)?;
let exp_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("power missing exponent operand", &cx.fn_sym)
})?;
let const_exp: Option<i64> = exp_expr
.constant_value()
.as_ref()
.and_then(sv_lang::ConstantValue::as_integer)
.and_then(sv_lang::SVInt::as_i64);
return Ok(LoweredValue::Packed(match const_exp {
Some(exp) => op.pow4_const(base, exp),
None => {
let exp_pv = lower_expr(cx, exp_expr)?.into_packed(
"power on a non-integral (real) exponent operand",
&cx.fn_sym,
)?;
if exp_pv.width > 64
&& u64::from(exp_pv.width.min(base.width)) * u64::from(base.width)
> 4_000_000
{
return Err(LoweringError::unsupported_at(
"'**' with a runtime exponent wider than 64 bits \
on a base this wide (the unrolled square-and-\
multiply scan would exceed the size limit)",
&cx.fn_sym,
));
}
op.pow4_call(cx.module, base, exp_pv, base.signed)
}
}));
}
let l = lower_expr(
cx,
e.left().ok_or_else(|| {
LoweringError::unsupported_at("binary op missing left operand", &cx.fn_sym)
})?,
)?
.into_packed(
"binary op on a non-integral (real) left operand",
&cx.fn_sym,
)?;
let r = lower_expr(
cx,
e.right().ok_or_else(|| {
LoweringError::unsupported_at("binary op missing right operand", &cx.fn_sym)
})?,
)?
.into_packed(
"binary op on a non-integral (real) right operand",
&cx.fn_sym,
)?;
Ok(LoweredValue::Packed(match bop {
BinaryOp::BinaryAnd => op.and4(l, r),
BinaryOp::BinaryOr => op.or4(l, r),
BinaryOp::BinaryXor => op.xor4(l, r),
BinaryOp::BinaryXnor => op.not4(op.xor4(l, r)),
BinaryOp::Add => op.add4(l, r),
BinaryOp::Subtract => op.sub4(l, r),
BinaryOp::Multiply => op.mul4(l, r),
BinaryOp::Divide => op.div4(l, r),
BinaryOp::Mod => op.mod4(l, r),
BinaryOp::LogicalShiftLeft | BinaryOp::ArithmeticShiftLeft => op.shl4(l, r),
BinaryOp::LogicalShiftRight => op.lshr4(l, r),
BinaryOp::ArithmeticShiftRight => op.ashr4(l, r),
BinaryOp::CaseEquality => op.caseeq4(l, r),
BinaryOp::CaseInequality => op.not4(op.caseeq4(l, r)),
BinaryOp::WildcardEquality => op.wildeq4(l, r),
BinaryOp::WildcardInequality => op.not4(op.wildeq4(l, r)),
BinaryOp::Equality => op.eq4(l, r),
BinaryOp::Inequality => op.neq4(l, r),
BinaryOp::LessThan => {
if l.signed {
op.slt4(l, r)
} else {
op.ult4(l, r)
}
}
BinaryOp::LessThanEqual => {
if l.signed {
op.sle4(l, r)
} else {
op.ule4(l, r)
}
}
BinaryOp::GreaterThan => {
if l.signed {
op.sgt4(l, r)
} else {
op.ugt4(l, r)
}
}
BinaryOp::GreaterThanEqual => {
if l.signed {
op.sge4(l, r)
} else {
op.uge4(l, r)
}
}
other => {
return Err(LoweringError::unsupported_at(
&format!("binary op {other:?}"),
&cx.fn_sym,
));
}
}))
}
ExpressionKind::ConditionalOp => lower_ternary(cx, e),
ExpressionKind::Inside | ExpressionKind::Dist => {
let is_dist = e.kind() == ExpressionKind::Dist;
let left_expr = if is_dist {
e.dist_left()
} else {
e.inside_left()
};
let left_expr = left_expr.ok_or_else(|| {
LoweringError::unsupported_at("inside missing left operand", &cx.fn_sym)
})?;
let candidates = if is_dist {
e.dist_items().iter().map(|item| item.value()).collect()
} else {
e.inside_range_list()
};
if left_expr.expr_type().is_some_and(|t| t.is_string()) {
return crate::strings::lower_string_inside(cx, left_expr, candidates);
}
let left = match lower_expr(cx, left_expr)? {
LoweredValue::Real(r) => {
return crate::real::lower_real_inside(cx, r, candidates);
}
other => other.into_packed("inside on a non-integral left operand", &cx.fn_sym)?,
};
if candidates.is_empty() {
return Err(LoweringError::unsupported_at(
"inside with an empty candidate list",
&cx.fn_sym,
));
}
let mut acc: Option<PackedValue<'ctx>> = None;
for cand in candidates {
let contrib = inside_candidate(cx, left, cand)?;
acc = Some(match acc {
None => contrib,
Some(prev) => op.or4(prev, contrib),
});
}
Ok(LoweredValue::Packed(
acc.expect("candidates non-empty, checked above"),
))
}
ExpressionKind::ElementSelect => {
if let Some(r) = crate::collections::read_element_warning(cx, e) {
return r;
}
if let Some(r) = crate::collections::read_fixed_in_coll(cx, e) {
return r;
}
let base_expr = e.select_value().ok_or_else(|| {
LoweringError::unsupported_at("bit-select missing base value", &cx.fn_sym)
})?;
if base_expr.expr_type().is_some_and(|t| t.is_string()) {
let idx = e.selector().ok_or_else(|| {
LoweringError::unsupported_at("string index missing", &cx.fn_sym)
})?;
return crate::strings::lower_getc(cx, base_expr, idx);
}
if let Some(v) = crate::vif::read_element(cx, e) {
return v;
}
if let Some(v) = crate::arrays::read_constant_unpacked_element(cx, e)? {
return Ok(v);
}
if let Some(r) = crate::arrays::resolve_element(cx, e)? {
return Ok(LoweredValue::Packed(crate::arrays::read_element(cx, &r)?));
}
let base = lower_expr(cx, base_expr)?
.into_packed("bit-select on a non-integral (real) base value", &cx.fn_sym)?;
let geom = crate::select::geom_of(base_expr.expr_type(), base.width);
let window = crate::select::select_window(cx, e, base.width, geom)?;
let mut out = crate::select::read_window(&op, base, window);
out.signed = e.expr_type().is_some_and(|t| t.is_signed());
Ok(LoweredValue::Packed(crate::select::two_state_result(
&op, e, out,
)))
}
ExpressionKind::RangeSelect => {
let base_expr = e.select_value().ok_or_else(|| {
LoweringError::unsupported_at("part-select missing base value", &cx.fn_sym)
})?;
let base = lower_expr(cx, base_expr)?.into_packed(
"part-select on a non-integral (real) base value",
&cx.fn_sym,
)?;
let geom = crate::select::geom_of(base_expr.expr_type(), base.width);
let window = crate::select::select_window(cx, e, base.width, geom)?;
let out = crate::select::read_window(&op, base, window);
Ok(LoweredValue::Packed(crate::select::two_state_result(
&op, e, out,
)))
}
ExpressionKind::Concatenation => {
let operand_exprs = e.operands();
if operand_exprs.is_empty() {
return Err(LoweringError::unsupported_at(
"concatenation with no operands (e.g. an empty queue `{}`) is out of scope",
&cx.fn_sym,
));
}
let mut kept = Vec::with_capacity(operand_exprs.len());
for o in operand_exprs {
if is_zero_replication(&o) {
if let Some(inner) = o.replication_concat() {
let _ = lower_expr(cx, inner)?;
}
} else {
kept.push(o);
}
}
let operand_exprs = kept;
let mut operands = Vec::with_capacity(operand_exprs.len());
for oe in operand_exprs {
operands.push(lower_expr(cx, oe)?.into_packed(
"concatenation operand must itself lower to an integral value",
&cx.fn_sym,
)?);
}
Ok(LoweredValue::Packed(op.concat4(&operands)))
}
ExpressionKind::Replication => {
let count_expr = e.replication_count().ok_or_else(|| {
LoweringError::unsupported_at("replication missing count", &cx.fn_sym)
})?;
let count_i64 = require_const_i64(
count_expr,
"replication count must be a compile-time constant",
&cx.fn_sym,
)?;
if count_i64 <= 0 {
return Err(LoweringError::unsupported_at(
"replication with a non-positive count (a nested zero-count \
replication contributing zero width to an enclosing \
concatenation is out of scope)",
&cx.fn_sym,
));
}
let unit_expr = e.replication_concat().ok_or_else(|| {
LoweringError::unsupported_at("replication missing concat operand", &cx.fn_sym)
})?;
let unit = lower_expr(cx, unit_expr)?.into_packed(
"replication operand must itself lower to an integral value",
&cx.fn_sym,
)?;
Ok(LoweredValue::Packed(op.replicate4(unit, count_i64 as u32)))
}
ExpressionKind::Conversion => {
if e.conversion_is_const_cast() == Some(true)
&& let Some((slot, width, signed)) = cx
.locals
.get(&crate::sva::proc::const_cast_key(&e))
.copied()
{
let ty = if cx.repr == ValueRepr::FourState {
op.i_n(width * 2)
} else {
op.i_n(width)
};
let v =
cx.b.build_load(ty, slot, "ld_const")
.unwrap()
.into_int_value();
return Ok(LoweredValue::Packed(PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
}));
}
let operand_expr = e.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at("conversion missing operand", &cx.fn_sym)
})?;
if let Some(v) = crate::events::lower_event_truth(cx, e)? {
return Ok(v);
}
if operand_expr.expr_type().is_some_and(|t| t.is_string())
&& let Some(to) = e.expr_type()
&& to.is_integral()
{
let s = crate::strings::to_string(cx, lower_expr_h(cx, operand_expr)?)?;
return Ok(LoweredValue::Packed(crate::strings::to_bits(
cx,
s,
to.value_width(),
to.is_signed(),
)?));
}
let kind = e.conversion_kind().ok_or_else(|| {
LoweringError::unsupported_at("conversion missing conversion_kind", &cx.fn_sym)
})?;
match classify_conversion_kind(kind) {
Ok(ConversionAction::Value) => {}
Ok(ConversionAction::Stream) => {
return crate::stream::lower_stream_conversion(cx, e);
}
Ok(ConversionAction::BitstreamCast) => {
return crate::stream::lower_bitstream_cast(cx, e);
}
Err(msg) => return Err(LoweringError::unsupported_at(&msg, &cx.fn_sym)),
}
let to_ty = e.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("conversion missing result type", &cx.fn_sym)
})?;
let from_ty = operand_expr.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("conversion operand missing type", &cx.fn_sym)
})?;
match (from_ty.is_floating(), to_ty.is_floating()) {
(false, true) => {
if !from_ty.is_integral() {
return Err(LoweringError::unsupported_at(
&format!(
"conversion from non-integral, non-floating type {} to real",
from_ty.to_sv_string()
),
&cx.fn_sym,
));
}
let from_pv = lower_expr(cx, operand_expr)?.into_packed(
"int-to-real conversion operand must itself lower to an integral value",
&cx.fn_sym,
)?;
let float_ty = match lower_type(cx.ctx, &to_ty, cx.repr)? {
LoweredType::Float(t) => t,
LoweredType::Int(_) => {
unreachable!("to_ty.is_floating() was just checked true")
}
};
let flat = op.to_2state4(from_pv);
let raw = op.lane_val(flat);
let float_v = if from_pv.signed {
cx.b.build_signed_int_to_float(raw, float_ty, "sitofp")
.unwrap()
} else {
cx.b.build_unsigned_int_to_float(raw, float_ty, "uitofp")
.unwrap()
};
Ok(LoweredValue::Real(RealValue {
llvm: crate::real::as_f64(cx, float_v),
}))
}
(true, false) => {
if !to_ty.is_integral() {
return Err(LoweringError::unsupported_at(
&format!(
"conversion from real to non-integral, non-floating type {}",
to_ty.to_sv_string()
),
&cx.fn_sym,
));
}
let from_rv = lower_expr(cx, operand_expr)?.into_real(
"real-to-int conversion operand must itself lower to a real value",
&cx.fn_sym,
)?;
let to_width = to_ty.value_width();
let fty = from_rv.llvm.get_type();
let diff =
cx.b.build_float_sub(from_rv.llvm, from_rv.llvm, "rti_vmv")
.unwrap();
let bad = cx
.b
.build_float_compare(inkwell::FloatPredicate::UNO, diff, diff, "rti_bad")
.unwrap();
let safe =
cx.b.build_select(bad, fty.const_zero(), from_rv.llvm, "rti_safe")
.unwrap()
.into_float_value();
let rounded =
op.round_real_to_int(RealValue { llvm: safe }, to_width, to_ty.is_signed());
let wty = op.i_n(to_width);
let unk = if to_ty.is_four_state() {
cx.b.build_select(bad, wty.const_all_ones(), wty.const_zero(), "rti_unk")
.unwrap()
.into_int_value()
} else {
wty.const_zero()
};
Ok(LoweredValue::Packed(op.pack(
rounded,
unk,
to_width,
cx.repr,
to_ty.is_signed(),
)))
}
(true, true) => {
let rv = lower_expr(cx, operand_expr)?
.into_real("real conversion of a non-real value", &cx.fn_sym)?;
let f = crate::real::as_f64(cx, rv.llvm);
Ok(LoweredValue::Real(RealValue {
llvm: if to_ty.bit_width() == 32 {
crate::real::round_to_short(cx, f)
} else {
f
},
}))
}
(false, false) => {
let from = lower_expr(cx, operand_expr)?.into_packed(
"int<->int conversion operand must itself lower to an integral value",
&cx.fn_sym,
)?;
let flattened = if from_ty.is_four_state() && !to_ty.is_four_state() {
op.to_2state4(from)
} else {
from
};
let to_width = to_ty.value_width();
let from_width = from_ty.value_width();
let width_adjusted = match to_width.cmp(&from_width) {
std::cmp::Ordering::Greater => {
let fill = if kind == 1 {
to_ty.is_signed()
} else {
flattened.signed
} || unsized_literal_leads_unknown(operand_expr);
op.extend4(flattened, to_width, fill)
}
std::cmp::Ordering::Less => op.truncate4(flattened, to_width),
std::cmp::Ordering::Equal => flattened,
};
Ok(LoweredValue::Packed(PackedValue {
signed: to_ty.is_signed(),
..width_adjusted
}))
}
}
}
ExpressionKind::Call => {
if e.is_system_call() != Some(true) {
return lower_user_subroutine_call(cx, e);
}
let sub = e.system_subroutine().ok_or_else(|| {
LoweringError::unsupported_at(
"system call missing its SystemMethod handle",
&cx.fn_sym,
)
})?;
let name = sub.name();
if sub.known_name_id() == KnownSystemName::Cast {
return crate::sysfunc::lower_cast(cx, e);
}
if let Some(v) = crate::vpi_systf::lower_call(cx, e, &name)? {
return Ok(v);
}
if matches!(
sub.known_name_id(),
KnownSystemName::CoverageControl
| KnownSystemName::CoverageGetMax
| KnownSystemName::CoverageGet
| KnownSystemName::CoverageMerge
| KnownSystemName::CoverageSave
| KnownSystemName::GetCoverage
| KnownSystemName::SetCoverageDbName
| KnownSystemName::LoadCoverageDb
) {
let args: Vec<sv_lang::Expression<'d>> = e
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.collect();
if let Some(v) = crate::covergroup::lower_system_call(cx, &name, &args)? {
return Ok(v);
}
}
if sub.known_name_id() == KnownSystemName::Randomize {
return crate::classes_randomize::lower_randomize_call(cx, e);
}
if let Some(r) = crate::array_methods::lower(cx, sub.known_name_id(), e) {
return r;
}
if let Some(r) = crate::collections::lower_method(cx, sub.known_name_id(), e) {
return r;
}
if sub.known_name_id() == KnownSystemName::CountDrivers {
return lower_countdrivers_call(cx, e);
}
if matches!(
sub.known_name_id(),
KnownSystemName::QInitialize
| KnownSystemName::QAdd
| KnownSystemName::QRemove
| KnownSystemName::QFull
| KnownSystemName::QExam
) {
return lower_stoch_queue_call(cx, e, sub.known_name_id());
}
if matches!(
sub.known_name_id(),
KnownSystemName::Time | KnownSystemName::STime | KnownSystemName::RealTime
) {
let scope = e.system_call_scope().unwrap_or(cx.fn_sym);
return lower_time_system_call(cx, sub.known_name_id(), &name, &scope);
}
if name == "$timeunit" || name == "$timeprecision" {
return lower_timescale_query(cx, e, &name);
}
if let Some(kind) = dist_kind(&name) {
return lower_dist_system_call(cx, kind, &name, e);
}
if matches!(
sub.known_name_id(),
KnownSystemName::Random | KnownSystemName::URandom | KnownSystemName::URandomRange
) {
return lower_random_system_call(cx, sub.known_name_id(), e);
}
if sub.known_name_id() == KnownSystemName::FOpen {
return lower_fopen_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::System {
return lower_system_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::FSeek {
return lower_fseek_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::FEof {
return lower_feof_call(cx, e);
}
if let Some(r) = crate::sva::emit::lower_inferred_disable(cx, sub.known_name_id()) {
return r;
}
if let Some(r) = crate::sampled::lower_sampled_call(cx, sub.known_name_id(), e) {
return r;
}
if sub.known_name_id() == KnownSystemName::Triggered {
return lower_event_triggered(cx, e);
}
if let Some(r) = crate::fileio::lower_file_read_sysfunc(cx, sub.known_name_id(), e) {
return r;
}
if let Some(r) = crate::real::lower_real_sysfunc(cx, sub.known_name_id(), e) {
return r;
}
if let Some(r) = crate::strings::lower_string_sysfunc(cx, sub.known_name_id(), e) {
return r;
}
if let Some(r) = crate::sysfunc::lower_bit_sysfunc(cx, sub.known_name_id(), e) {
return r;
}
if let Some(r) = crate::sysfunc::lower_enum_method(cx, sub.known_name_id(), e) {
return r;
}
if sub.known_name_id() == KnownSystemName::TestPlusArgs {
return lower_test_plusargs_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::ValuePlusArgs {
return lower_value_plusargs_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::Randomize {
return crate::classes_randomize::lower_randomize_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::RandMode {
return crate::rand_mode::lower_rand_mode_call(cx, e);
}
if sub.known_name_id() == KnownSystemName::ConstraintMode {
return crate::rand_mode::lower_constraint_mode_call(cx, e);
}
if !is_cg042_type_query(sub.known_name_id()) {
return Err(LoweringError::unsupported_at(
&format!(
"system call {name} is not one of CG-042's constant- \
foldable type-query functions ($bits/$size/$low/ \
$high/$left/$right/$increment/$dimensions/ \
$unpacked_dimensions) -- a runtime-behavior system \
call (e.g. $display/$random) is CG-043's scope, and \
a different constant-sometimes-foldable family (e.g. \
$rtoi/$itor/$clog2) is a separate, not-yet- \
implemented path, not Task 13's"
),
&cx.fn_sym,
));
}
if e.constant_value().is_none()
&& sub.known_name_id() == KnownSystemName::Bits
&& let Some(arg) = e.children().into_iter().find_map(|n| n.as_expression())
&& let Some(t) = arg.expr_type()
&& t.is_fixed_size()
&& t.bitstream_width() > 0
&& t.bitstream_width() <= u32::MAX as u64
{
let w = t.bitstream_width() as u32;
let bits: Vec<sv_lang::Bit> = (0..32)
.map(|i| {
if (w >> i) & 1 == 1 {
sv_lang::Bit::One
} else {
sv_lang::Bit::Zero
}
})
.collect();
return Ok(LoweredValue::Packed(crate::value::const_bits(
cx.ctx, &bits, false, cx.repr,
)));
}
let cv = e.constant_value().ok_or_else(|| {
LoweringError::unsupported_at(
&format!(
"type-query system call {name} is not a compile-time \
constant here (e.g. $size on a genuinely dynamic \
array) -- CG-042 only constant-folds statically- \
foldable calls"
),
&cx.fn_sym,
)
})?;
let iv = cv.as_integer().ok_or_else(|| {
LoweringError::unsupported_at(
&format!("system call {name}'s folded constant is not an integer"),
&cx.fn_sym,
)
})?;
lower_constant(cx, iv, Some(e))
}
ExpressionKind::Assignment => {
if e.is_compound_assignment() == Some(true) {
let lhs_expr = e.left().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing lvalue", &cx.fn_sym)
})?;
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
if lhs_expr.kind() == ExpressionKind::ElementSelect
&& lhs_expr.expr_type().is_some_and(|t| t.is_integral())
&& let Some(r) = crate::arrays::resolve_element(cx, lhs_expr)?
{
let current = LoweredValue::Packed(crate::arrays::read_element(cx, &r)?);
let saved = cx.lvalue_ref.replace(Some(current));
let rhs = lower_expr(cx, rhs_expr);
cx.lvalue_ref.set(saved);
let rhs_pv = rhs?.into_packed(
"compound assignment of a non-integral value to an array element",
&cx.fn_sym,
)?;
let rhs_pv = crate::select::fit_width(&op, rhs_pv, r.width);
crate::arrays::write_element(cx, &r, rhs_pv)?;
return Ok(LoweredValue::Packed(rhs_pv));
}
if crate::classes::has_side_effects(lhs_expr)
&& lhs_expr.expr_type().is_some_and(|t| t.is_integral())
&& let Some(v) = crate::select::lower_modify_target(cx, lhs_expr, &mut |old| {
let saved = cx.lvalue_ref.replace(Some(LoweredValue::Packed(old)));
let rhs = lower_expr(cx, rhs_expr);
cx.lvalue_ref.set(saved);
rhs?.into_packed("compound assignment of a non-integral value", &cx.fn_sym)
})?
{
return Ok(LoweredValue::Packed(v));
}
let current = read_modify_target(cx, lhs_expr)?;
let saved = cx.lvalue_ref.replace(Some(current));
let rhs = lower_expr(cx, rhs_expr);
cx.lvalue_ref.set(saved);
return store_to(cx, lhs_expr, rhs?);
}
if e.is_nonblocking() == Some(true) {
return crate::func::lower_subroutine_nba(cx, e);
}
let lhs_expr = e.left().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing lvalue", &cx.fn_sym)
})?;
let lhs_expr = modport_expr_target(&lhs_expr).unwrap_or(lhs_expr);
if lhs_expr.expr_type().is_some_and(|t| t.is_event()) {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
return crate::events::lower_event_assignment(cx, lhs_expr, rhs_expr);
}
if lhs_expr.kind() == ExpressionKind::Streaming
&& let Some(rhs_expr) = e.right()
&& (crate::stream::source_needs_runtime(rhs_expr)
|| crate::stream::target_needs_runtime(lhs_expr))
{
crate::stream::unpack_runtime_source(
cx,
lhs_expr,
rhs_expr,
&mut |operand, with, bits| {
if let Some(w) = with {
return crate::stream_with::unpack(cx, operand, w, bits);
}
store_to(cx, operand, LoweredValue::Packed(bits)).map(|_| ())
},
None,
None,
)?;
return Ok(LoweredValue::Packed(op.pack(
cx.ctx.bool_type().const_zero(),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
if crate::classes::is_class_field_lvalue(lhs_expr) {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::owned(cx, rhs_expr)?;
let v = crate::classes::try_assign_field(cx, lhs_expr, rhs)?.ok_or_else(|| {
LoweringError::unsupported_at(
"internal: is_class_field_lvalue/try_write_field disagreed",
&cx.fn_sym,
)
})?;
return Ok(v);
}
if let Some(rhs_expr) = e.right()
&& let Some(done) = crate::arrays::assign_coll_slice(cx, lhs_expr, rhs_expr)
{
done?;
return Ok(LoweredValue::Packed(op.pack(
cx.ctx.bool_type().const_zero(),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
if crate::collections::coll_of_expr(&lhs_expr).is_some() {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
return crate::collections::assign_whole(cx, lhs_expr, rhs_expr);
}
if let Some(rhs_expr) = e.right()
&& let Some(done) = crate::vif::assign_array(cx, lhs_expr, rhs_expr)
{
done?;
return Ok(LoweredValue::Packed(op.pack(
cx.ctx.bool_type().const_zero(),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
if let Some((dst, shape)) = crate::arrays::array_target(cx, lhs_expr)? {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
crate::arrays::assign_array(cx, dst, &shape, rhs_expr)?;
return Ok(LoweredValue::Packed(op.pack(
cx.ctx.bool_type().const_zero(),
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
if let Some(r) = crate::arrays::resolve_element(cx, lhs_expr)? {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::owned(cx, rhs_expr)?;
if let Some(t) = lhs_expr.expr_type() {
crate::collections::release_element(cx, &t, &r)?;
}
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)?;
return Ok(LoweredValue::Packed(rhs_pv));
}
if !sv_sim_sim::symid::is_value_name(lhs_expr.kind()) {
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::owned(cx, rhs_expr)?;
return crate::collections::store_releasing(cx, lhs_expr, rhs);
}
let sym = lhs_expr.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at("assignment lvalue unresolved name", &cx.fn_sym)
})?;
let (slot, width, signed) = *cx
.locals
.get(&sym.uid())
.ok_or_else(|| LoweringError::unsupported_at("assignment to a non-local", &sym))?;
let rhs_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("assignment missing rvalue", &cx.fn_sym)
})?;
let rhs = crate::collections::owned(cx, rhs_expr)?;
crate::collections::release_old(cx, lhs_expr)?;
let rhs_pv = crate::real::to_storage(
cx,
rhs,
width,
"assignment of a >128-bit value to a local",
)?;
cx.b.build_store(slot, rhs_pv.llvm).unwrap();
write_through_store(cx, slot);
Ok(LoweredValue::Packed(PackedValue {
llvm: rhs_pv.llvm,
width,
repr: cx.repr,
signed,
}))
}
ExpressionKind::MemberAccess => {
if let Some(v) = crate::vif::read(cx, e) {
return v;
}
if let Some(v) = crate::covergroup::lower_option_read(cx, e)? {
return Ok(v);
}
let base_expr = e.operand().ok_or_else(|| {
LoweringError::unsupported_at("member access missing base value", &cx.fn_sym)
})?;
if base_expr.expr_type().is_some_and(|t| t.is_class()) {
let member = e.member_symbol().ok_or_else(|| {
LoweringError::unsupported_at(
"member access on a class value missing its member symbol",
&cx.fn_sym,
)
})?;
if crate::classes::is_static_field(&member) {
return crate::classes::read_static(cx, &member);
}
if matches!(member.kind(), sv_lang::kinds::SymbolKind::Parameter)
&& let Some(cv) = member.constant_value()
&& let Some(iv) = cv.as_integer()
{
return lower_constant(cx, iv, None);
}
if !crate::classes::is_instance_field(&member) {
return Err(LoweringError::unsupported_at(
"a class member access that is neither a static nor a non-static \
property read",
&cx.fn_sym,
));
}
let h = crate::classes::receiver_handle(cx, base_expr)?;
return crate::classes::read_field_checked(cx, h, &member, e.source_range().start);
}
if let Some(v) = crate::tagged::read_member(cx, e, base_expr)? {
return Ok(v);
}
let base = lower_expr(cx, base_expr)?.into_packed(
"member access on a non-integral (real) base value is out of scope",
&cx.fn_sym,
)?;
let member = e.member_symbol().ok_or_else(|| {
LoweringError::unsupported_at("member access missing member symbol", &cx.fn_sym)
})?;
let field_ty = member.value_type().ok_or_else(|| {
LoweringError::unsupported_at("member access field missing a type", &cx.fn_sym)
})?;
if let Some(base_ty) = base_expr.expr_type()
&& sv_sim_sim::state::storage::is_unpacked_aggregate(&base_ty)
{
let (off, fw) = crate::select::field_window(Some(base_ty), &member)
.filter(|(off, fw)| *fw > 0 && off + fw <= u64::from(base.width))
.ok_or_else(|| {
LoweringError::unsupported_at(
"unpacked struct/union field outside its base's stored bits",
&cx.fn_sym,
)
})?;
let lo = op.i_n(base.width).const_int(off, false);
let bits = PackedValue {
signed: field_ty.is_signed(),
..op.part_select4(base, lo, cx.ctx.bool_type().const_zero(), fw as u32)
};
return Ok(if field_ty.is_floating() {
LoweredValue::Real(crate::real::bits_to_real(cx, bits))
} else if field_ty.is_string() {
LoweredValue::Str(crate::strings::from_storage(cx, bits))
} else {
LoweredValue::Packed(bits)
});
}
if !field_ty.is_integral() {
return Err(LoweringError::unsupported_at(
"member access on a non-integral struct/union field \
(real/string/class-handle/...) is out of scope",
&cx.fn_sym,
));
}
let field_width_u64 = field_ty.bit_width();
let offset_u64 = member.field_bit_offset();
if field_width_u64 == 0
|| field_width_u64 > base.width as u64
|| offset_u64 > (base.width as u64) - field_width_u64
{
return Err(LoweringError::unsupported_at(
"member access field offset/width does not fit within its \
base struct/union's own already-lowered width",
&cx.fn_sym,
));
}
let field_width = field_width_u64 as u32;
let offset = offset_u64 as u32;
let lo = op.i_n(base.width).const_int(offset as u64, false);
let lo_unknown = cx.ctx.bool_type().const_zero();
Ok(LoweredValue::Packed(PackedValue {
signed: field_ty.is_signed(),
..op.part_select4(base, lo, lo_unknown, field_width)
}))
}
ExpressionKind::NewClass => crate::classes::lower_new_class(cx, e),
ExpressionKind::NewCovergroup => crate::covergroup::lower_new(cx, e),
ExpressionKind::CopyClass => crate::classes::lower_copy_class(cx, e),
ExpressionKind::UnboundedLiteral => crate::collections::lower_unbounded(cx)
.ok_or_else(|| LoweringError::unsupported_at("`$` outside a queue index", &cx.fn_sym)),
ExpressionKind::NullLiteral => {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
op.i_n(64).const_zero(),
op.i_n(64).const_zero(),
64,
cx.repr,
false,
)))
}
ExpressionKind::LValueReference | ExpressionKind::EmptyArgument => {
cx.lvalue_ref.get().ok_or_else(|| {
LoweringError::unsupported_at(
"lvalue reference outside a compound assignment",
&cx.fn_sym,
)
})
}
ExpressionKind::SimpleAssignmentPattern
| ExpressionKind::StructuredAssignmentPattern
| ExpressionKind::ReplicatedAssignmentPattern => lower_packed_pattern(cx, e),
ExpressionKind::TaggedUnion => crate::tagged::lower_tagged_expr(cx, e),
ExpressionKind::MinTypMax => {
let typ = e
.children()
.into_iter()
.filter_map(|n| n.as_expression())
.nth(1)
.ok_or_else(|| {
LoweringError::unsupported_at(
"a min:typ:max expression with no typical operand",
&cx.fn_sym,
)
})?;
lower_expr(cx, typ)
}
kind if kind == ExpressionKind::Streaming && !crate::stream::source_needs_runtime(e) => {
Ok(LoweredValue::Packed(crate::stream::lower_streaming(cx, e)?))
}
other => Err(LoweringError::unsupported_at(
&format!("expression kind {other:?}"),
&cx.fn_sym,
)),
}
}
pub(crate) fn const_stored_bits(
ty: &sv_lang::Type<'_>,
cv: &sv_lang::ConstantValue,
) -> Option<Vec<sv_lang::Bit>> {
let mut strs = Vec::new();
let bits = const_stored_bits_strs(ty, cv, 0, &mut strs)?;
strs.is_empty().then_some(bits)
}
pub(crate) fn const_stored_bits_strs(
ty: &sv_lang::Type<'_>,
cv: &sv_lang::ConstantValue,
base: usize,
strs: &mut Vec<(usize, Vec<u8>)>,
) -> Option<Vec<sv_lang::Bit>> {
use sv_lang::{Bit, ConstantValue};
use sv_sim_sim::state::storage;
if ty.is_string() {
let ConstantValue::String(text) = cv else {
return None;
};
strs.push((base, text.as_bytes().to_vec()));
return Some(vec![Bit::Zero; storage::storage_width(ty)? as usize]);
}
if matches!(cv, ConstantValue::Null) {
return Some(vec![Bit::Zero; storage::storage_width(ty)? as usize]);
}
if ty.is_floating() {
let ConstantValue::Real(f) = cv else {
return None;
};
let w = ty.bit_width() as usize;
let raw = if w == 32 {
u64::from((*f as f32).to_bits())
} else {
f.to_bits()
};
return Some(
(0..w)
.map(|i| {
if raw >> i & 1 == 1 {
Bit::One
} else {
Bit::Zero
}
})
.collect(),
);
}
if ty.is_integral() {
let v = cv.as_integer()?;
let mut bits = sv_sim_sim::state::svint_bits(v)?.into_owned();
let pad = if v.is_signed() {
bits.last().copied().unwrap_or(Bit::Zero)
} else {
Bit::Zero
};
bits.resize(ty.bit_width() as usize, pad);
return Some(bits);
}
let ConstantValue::Array(elems) = cv else {
return None;
};
let total = storage::storage_width(ty)? as usize;
let mut out = vec![Bit::Zero; total];
if ty.is_unpacked_struct() {
for (i, f) in ty.fields().enumerate() {
let (lo, w) = storage::field_slice(ty, i as u32)?;
let sub =
const_stored_bits_strs(&f.value_type()?, elems.get(i)?, base + lo as usize, strs)?;
if sub.len() as u64 != w {
return None;
}
out[lo as usize..(lo + w) as usize].copy_from_slice(&sub);
}
return Some(out);
}
if ty.is_unpacked_array() && !ty.is_dynamically_sized_array() {
let elem = ty.element_type()?;
let ew = storage::storage_width(&elem)? as usize;
let n = elems.len();
let ascending =
ty.fixed_unpacked_array_range().left < ty.fixed_unpacked_array_range().right;
for (p, el) in elems.iter().enumerate() {
let slot = if ascending { p } else { n - 1 - p };
let sub = const_stored_bits_strs(&elem, el, base + slot * ew, strs)?;
if sub.len() != ew || (slot + 1) * ew > total {
return None;
}
out[slot * ew..(slot + 1) * ew].copy_from_slice(&sub);
}
return Some(out);
}
None
}
pub(crate) fn refuse_tolerance_range(
cand: sv_lang::Expression<'_>,
sym: &sv_lang::Symbol,
) -> Result<(), LoweringError> {
use sv_lang::AstNode;
use sv_lang::nodes::ValueRangeExpressionSyntax;
let op = crate::classes::with_design_trees(|trees| {
trees.iter().find_map(|tree| {
let node = cand.syntax(tree)?;
let op = ValueRangeExpressionSyntax::cast(node)?.op()?;
Some(op.text().to_string())
})
});
match op.as_deref() {
None | Some(":") => Ok(()),
Some(op) => Err(LoweringError::unsupported_at(
&format!("an `inside` tolerance range (`[a {op} b]`, LRM 11.4.13)"),
sym,
)),
}
}
pub(crate) fn tolerance_op(cand: sv_lang::Expression<'_>) -> Option<bool> {
use sv_lang::AstNode;
use sv_lang::nodes::ValueRangeExpressionSyntax;
crate::classes::with_design_trees(|trees| {
trees.iter().find_map(|tree| {
let node = cand.syntax(tree)?;
let op = ValueRangeExpressionSyntax::cast(node)?.op()?;
match op.text() {
"+/-" => Some(false),
"+%-" => Some(true),
_ => None,
}
})
})
}
pub(crate) fn is_open_bound(x: Option<sv_lang::Expression<'_>>) -> bool {
let mut x = x;
while let Some(c) = x.filter(|c| c.kind() == ExpressionKind::Conversion) {
x = c.conversion_operand();
}
x.is_some_and(|x| {
x.kind() == ExpressionKind::UnboundedLiteral
|| matches!(x.constant_value(), Some(sv_lang::ConstantValue::Unbounded))
})
}
pub(crate) fn inside_candidate<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
left: PackedValue<'ctx>,
cand: sv_lang::Expression<'d>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let bound = |x: Option<sv_lang::Expression<'d>>, which: &str| {
let x = x.ok_or_else(|| {
LoweringError::unsupported_at(
&format!("inside range missing {which} bound"),
&cx.fn_sym,
)
})?;
lower_expr(cx, x)?.into_packed(
&format!("inside range with a non-integral (real) {which} bound"),
&cx.fn_sym,
)
};
if cand.kind() == ExpressionKind::ValueRange {
if let Some(relative) = tolerance_op(cand) {
let a = bound(cand.left(), "centre")?;
let b = bound(cand.right(), "tolerance")?;
return Ok(op.inside_tolerance_candidate4(left, a, b, relative));
}
let open = is_open_bound;
let lo = if open(cand.left()) {
left
} else {
bound(cand.left(), "low")?
};
let hi = if open(cand.right()) {
left
} else {
bound(cand.right(), "high")?
};
return Ok(op.inside_range_candidate4(left, lo, hi));
}
if let Some(c) = crate::collections::coll_of_expr(&cand) {
if c.assoc || !c.elem.is_integral() {
return Err(LoweringError::unsupported_at(
"inside over an associative array or a collection of non-integral elements",
&cx.fn_sym,
));
}
return inside_collection_scan(cx, left, cand, &c);
}
if cand
.expr_type()
.is_some_and(|t| t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&t))
{
let target = match crate::arrays::array_target(cx, cand)? {
Some(t) => Some(t),
None => match crate::arrays::spill_member_array(cx, cand)? {
Some(t) => Some(t),
None => crate::arrays::array_call_value(cx, cand)?,
},
};
let Some((r, shape)) = target else {
return Err(LoweringError::unsupported_at(
"inside over an unpacked array that is not a variable, memory, member or call",
&cx.fn_sym,
));
};
let i64t = cx.ctx.i64_type();
let mut acc: Option<PackedValue<'ctx>> = None;
for k in 0..shape.rows() {
let el = crate::arrays::ElemRef {
backing: r.backing,
row: cx
.b
.build_int_add(r.base, i64t.const_int(k, false), "inside_row")
.unwrap(),
valid: r.valid,
width: shape.elem.value_width(),
signed: shape.elem.is_signed(),
default: crate::arrays::default_value(cx, &shape.elem),
};
let item = crate::arrays::read_element(cx, &el)?;
let c = op.inside_plain_candidate4(left, item);
acc = Some(match acc {
None => c,
Some(prev) => op.or4(prev, c),
});
}
return acc.ok_or_else(|| {
LoweringError::unsupported_at("inside over a zero-element array", &cx.fn_sym)
});
}
let item = lower_expr(cx, cand)?
.into_packed("inside with a non-integral (real) candidate", &cx.fn_sym)?;
Ok(op.inside_plain_candidate4(left, item))
}
fn inside_collection_scan<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
left: PackedValue<'ctx>,
cand: sv_lang::Expression<'d>,
c: &crate::collections::Coll<'d>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let h = crate::collections::handle_of(cx, cand)?;
let n = crate::collections::size_of_handle(cx, c, h);
let f =
cx.b.get_insert_block()
.and_then(|bb| bb.get_parent())
.expect("lowering happens inside a function");
let shape = PackedValue {
llvm: op.packed_type(1, cx.repr).const_zero(),
width: 1,
repr: cx.repr,
signed: false,
};
let acc_ty = shape.llvm.get_type();
let acc_slot = entry_alloca(cx, acc_ty, "inside_scan_acc");
cx.b.build_store(acc_slot, shape.llvm).unwrap();
let i_slot = entry_alloca(cx, i64t, "inside_scan_i");
cx.b.build_store(i_slot, i64t.const_zero()).unwrap();
let head = cx.ctx.append_basic_block(f, "inside_scan_head");
let body = cx.ctx.append_basic_block(f, "inside_scan_body");
let done = cx.ctx.append_basic_block(f, "inside_scan_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let i = cx.b.build_load(i64t, i_slot, "i").unwrap().into_int_value();
let more =
cx.b.build_int_compare(inkwell::IntPredicate::SLT, i, n, "more")
.unwrap();
cx.b.build_conditional_branch(more, body, done).unwrap();
cx.b.position_at_end(body);
let LoweredValue::Packed(item) = crate::collections::read_at_coll(cx, c, h, i) else {
return Err(LoweringError::unsupported_at(
"an `inside` collection element that is not a packed value",
&cx.fn_sym,
));
};
let m = op.inside_plain_candidate4(left, item);
let load = |name: &str| PackedValue {
llvm: cx
.b
.build_load(acc_ty, acc_slot, name)
.unwrap()
.into_int_value(),
..shape
};
let next = op.or4(load("acc"), m);
cx.b.build_store(acc_slot, next.llvm).unwrap();
let inc =
cx.b.build_int_add(i, i64t.const_int(1, false), "i_next")
.unwrap();
cx.b.build_store(i_slot, inc).unwrap();
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(done);
Ok(load("inside_scan_res"))
}
fn lower_packed_pattern<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = e.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("assignment pattern has no type", &cx.fn_sym)
})?;
let bits_array = ty.is_unpacked_array() && !ty.is_dynamically_sized_array();
if !ty.is_integral() && !ty.is_unpacked_struct() && !bits_array {
return Err(LoweringError::unsupported_at(
"an unpacked assignment pattern (an array value) outside an array assignment, \
initializer or comparison",
&cx.fn_sym,
));
}
let mut elems = e.pattern_elements();
if e.kind() == ExpressionKind::ReplicatedAssignmentPattern {
let n = e
.replicated_pattern_count()
.and_then(|c| c.constant_value())
.and_then(|c| c.as_i64())
.ok_or_else(|| {
LoweringError::unsupported_at(
"a replicated assignment pattern with a non-constant count",
&cx.fn_sym,
)
})?;
elems = (0..n).flat_map(|_| elems.iter().copied()).collect();
}
if bits_array {
let range = ty.fixed_unpacked_array_range();
if range.left < range.right {
elems.reverse();
}
}
let mut parts = Vec::with_capacity(elems.len());
for el in elems {
let v = crate::real::to_storage(
cx,
crate::collections::owned(cx, el)?,
el.expr_type().map(|t| t.value_width()).unwrap_or(0),
"an assignment pattern element wider than 128 bits",
)?;
let w = el
.expr_type()
.map(|t| t.value_width())
.filter(|w| *w > 0)
.unwrap_or(v.width);
parts.push(crate::select::fit_width(&op, v, w));
}
if parts.is_empty() {
return Err(LoweringError::unsupported_at(
"an empty assignment pattern",
&cx.fn_sym,
));
}
let v = op.concat4(&parts);
let width = ty.value_width();
if v.width != width {
return Err(LoweringError::unsupported_at(
&format!(
"a packed assignment pattern's elements total {} bits for a {width}-bit type",
v.width
),
&cx.fn_sym,
));
}
Ok(LoweredValue::Packed(PackedValue {
signed: ty.is_signed(),
..v
}))
}
fn lower_user_subroutine_call<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
call: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if let Some(v) = crate::covergroup::lower_call(cx, call)? {
return Ok(v);
}
match crate::classes::lower_std_process_call(cx, call)? {
crate::classes::ProcessCall::Lowered(Some(v)) => return Ok(v),
crate::classes::ProcessCall::Lowered(None) => {
return Err(LoweringError::unsupported_at(
"a void method call cannot be used as an expression value",
&cx.fn_sym,
));
}
crate::classes::ProcessCall::NotProcess => {}
}
let not_vif_call = sv_sim_sim::netlist::vif::call_targets(&call).is_none();
if let Some(callee) = call.call_subroutine()
&& callee.subroutine_kind() == Some(sv_lang::SubroutineKind::Function)
&& let Some(rv) = callee.subroutine_return_val_var()
&& let Some((slot, width, signed)) = cx
.locals
.get(&sv_sim_sim::process::cfg::call_result_key(&call))
.or_else(|| cx.locals.get(&rv.uid()))
.copied()
&& sv_sim_sim::process::cfg::subroutine_can_suspend(&callee)
{
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = if cx.repr == ValueRepr::FourState {
op.i_n(width * 2)
} else {
op.i_n(width)
};
let v =
cx.b.build_load(ty, slot, "ld_ret")
.unwrap()
.into_int_value();
let pv = PackedValue {
llvm: v,
width,
repr: cx.repr,
signed,
};
if rv.value_type().is_some_and(|t| t.is_floating()) {
return Ok(LoweredValue::Real(crate::real::bits_to_real(cx, pv)));
}
return Ok(LoweredValue::Packed(pv));
}
if not_vif_call && let Some(recv) = call.call_this_class() {
return crate::classes::lower_method_call(cx, Some(recv), call);
}
if not_vif_call
&& let Some(callee) = call.call_subroutine()
&& crate::classes::resolve_implicit_this_instance_method(callee)?.is_some()
{
return crate::classes::lower_method_call(cx, None, call);
}
let callee = call.call_subroutine().ok_or_else(|| {
LoweringError::unsupported_at(
"non-system call missing its callee subroutine symbol",
&cx.fn_sym,
)
})?;
if crate::vif::call_arms_apply(&call) {
let ret = callee
.subroutine_return_type()
.filter(|t| !t.is_void())
.ok_or_else(|| {
LoweringError::unsupported_at(
"a void function/task call cannot be used as an expression value",
&cx.fn_sym,
)
})?;
let (slot, is_real) = if ret.is_floating() {
let slot = entry_alloca(cx, cx.ctx.f64_type(), "vif_ret");
cx.b.build_store(slot, cx.ctx.f64_type().const_zero())
.unwrap();
(slot, true)
} else {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = op.packed_type(ret.value_width(), cx.repr);
let slot = entry_alloca(cx, ty, "vif_ret");
cx.b.build_store(slot, crate::arrays::default_value(cx, &ret))
.unwrap();
(slot, false)
};
let (arms, merge) = crate::vif::call_arms(cx, call).expect("checked")?;
for (callee, bb) in arms {
cx.b.position_at_end(bb);
match lower_user_subroutine_call_of(cx, callee, call)? {
LoweredValue::Real(r) => {
cx.b.build_store(slot, r.llvm).unwrap();
}
LoweredValue::Packed(p) => {
cx.b.build_store(slot, p.llvm).unwrap();
}
_ => {
return Err(LoweringError::unsupported_at(
"a string or wide function result through a virtual interface",
&cx.fn_sym,
));
}
}
cx.b.build_unconditional_branch(merge).unwrap();
}
cx.b.position_at_end(merge);
return Ok(if is_real {
let f =
cx.b.build_load(cx.ctx.f64_type(), slot, "vif_ret_ld")
.unwrap()
.into_float_value();
LoweredValue::Real(RealValue { llvm: f })
} else {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let ty = op.packed_type(ret.value_width(), cx.repr);
let v =
cx.b.build_load(ty, slot, "vif_ret_ld")
.unwrap()
.into_int_value();
LoweredValue::Packed(PackedValue {
llvm: v,
width: ret.value_width(),
repr: cx.repr,
signed: ret.is_signed(),
})
});
}
let callee = sv_sim_sim::netlist::itf::call_target(&call).unwrap_or(callee);
lower_user_subroutine_call_of(cx, callee, call)
}
fn lower_user_subroutine_call_of<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
callee: sv_lang::Symbol<'d>,
call: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if callee.subroutine_flags().contains(MethodFlags::DPI_IMPORT) {
let v = crate::dpi::lower_dpi_import_call(cx, &callee, call)?.ok_or_else(|| {
LoweringError::unsupported_at(
"a void DPI import cannot be used as an expression value",
&cx.fn_sym,
)
})?;
crate::stmt_shared::leave_if_aborted(cx)?;
return Ok(v);
}
let ret_sv = callee.subroutine_return_type();
if ret_sv.as_ref().is_none_or(sv_lang::Type::is_void) {
return Err(LoweringError::unsupported_at(
"a void function/task call cannot be used as an expression value",
&cx.fn_sym,
));
}
let ret_sv = ret_sv.unwrap();
let width = ret_sv.value_width();
let signed = ret_sv.is_signed();
let returns_real = ret_sv.is_floating();
let callee_fn = crate::func::resolve_callee_fn(cx, callee)?;
let (mut llvm_args, copy_backs) = crate::func::lower_call_args(cx, &callee, call)?;
let image = if crate::huge::is_image_width(width) && ret_sv.is_integral() {
let img = crate::huge::result_image(cx, width)?;
llvm_args.push(img.into());
Some(img)
} else {
None
};
if llvm_args.len() != callee_fn.count_params() as usize {
return Err(LoweringError::unsupported_at(
"user function call argument count does not match the callee's \
declared parameter count",
&cx.fn_sym,
));
}
let call_site =
cx.b.build_call(callee_fn, &llvm_args, "user_fn_call")
.unwrap();
if sv_sim_sim::process::cfg::subroutine_can_abort(&callee) {
crate::stmt_shared::leave_if_aborted(cx)?;
}
crate::func::commit_callee_writes(cx, &callee);
if crate::dpi::reaches_context_import(&callee) {
crate::dpi::reload_shadows(cx);
}
crate::func::copy_back_outputs(cx, copy_backs)?;
if let Some(ptr) = image {
return Ok(LoweredValue::Huge(crate::huge::HugeValue {
ptr,
width,
signed,
}));
}
let ret_v = call_site.try_as_basic_value().basic().ok_or_else(|| {
LoweringError::unsupported_at(
"user function call produced no return value at the LLVM \
level despite a non-void SV return type",
&cx.fn_sym,
)
})?;
if returns_real {
let f = crate::real::as_f64(cx, ret_v.into_float_value());
return Ok(LoweredValue::Real(RealValue { llvm: f }));
}
Ok(LoweredValue::Packed(PackedValue {
llvm: ret_v.into_int_value(),
width,
repr: cx.repr,
signed,
}))
}
fn fold_constant_logical<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
match e.constant_value().and_then(|cv| cv.as_integer().cloned()) {
Some(iv) => lower_constant(cx, &iv, Some(e)).map(Some),
None => Ok(None),
}
}
pub fn lower_condition<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
expr: sv_lang::Expression<'d>,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
if let Some(v) = crate::events::lower_event_condition(cx, expr)? {
return Ok(v);
}
if expr.kind() == ExpressionKind::BinaryOp
&& let Some(bop) = expr.binary_op()
&& matches!(bop, BinaryOp::LogicalAnd | BinaryOp::LogicalOr)
{
let lv = match fold_constant_logical(cx, expr)? {
Some(v) => v,
None => lower_short_circuit_logical_condition(cx, expr, bop == BinaryOp::LogicalAnd)?,
};
let pv = lv.into_packed("condition must be an integral (non-real) value", &cx.fn_sym)?;
return Ok(is_definitely_true(cx.ctx, &cx.b, pv));
}
let pv = match lower_expr_h(cx, expr)? {
LoweredValue::Real(rv) => return Ok(crate::real::real_truth(cx, rv)),
other => crate::huge::truth_packed(
cx,
other,
crate::huge::TruthMode::Strict,
"condition of a >128-bit value",
)?,
};
Ok(is_definitely_true(cx.ctx, &cx.b, pv))
}
pub fn lower_negated_condition<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
expr: sv_lang::Expression<'d>,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
let pv = match lower_expr_h(cx, expr)? {
LoweredValue::Real(rv) => {
let truth = crate::real::real_truth(cx, rv);
return Ok(cx.b.build_not(truth, "real_not").unwrap());
}
other => crate::huge::truth_packed(
cx,
other,
crate::huge::TruthMode::Strict,
"condition of a >128-bit value",
)?,
};
Ok(is_definitely_false(cx.ctx, &cx.b, pv))
}
pub(crate) fn store_to<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
store_to_releasing(cx, lhs, value, None)
}
pub(crate) fn store_to_releasing<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
release: Option<&mut dyn FnMut(PackedValue<'ctx>)>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
if matches!(value, LoweredValue::Huge(_))
&& crate::huge::class_prop_root(lhs)
&& crate::huge::try_store(cx, lhs, value)?.is_some()
{
return Ok(value);
}
if let Some(v) = crate::classes::try_write_field(cx, lhs, value)? {
return Ok(v);
}
if let Some(v) = crate::covergroup::lower_option_write(cx, lhs, value)? {
return Ok(v);
}
if crate::huge::try_store(cx, lhs, value)?.is_some() {
return Ok(value);
}
if let Some(r) = crate::collections::write_fixed_in_coll(cx, lhs, value) {
r?;
return Ok(value);
}
if let Some(r) = crate::collections::write_element(cx, lhs, value) {
r?;
return Ok(value);
}
if let Some(elem) = crate::collections::partial_element_target(lhs) {
return store_partial_element(cx, lhs, elem, value, release);
}
let stream_width = match value {
LoweredValue::Packed(p) if lhs.kind() == ExpressionKind::Streaming => Some(p.width),
_ => None,
};
let width = stream_width
.or_else(|| lhs.expr_type().map(|t| t.value_width()).filter(|w| *w > 0))
.ok_or_else(|| {
LoweringError::unsupported_at("assignment target has no width", &cx.fn_sym)
})?;
let bits = crate::real::to_storage(cx, value, width, "assignment of a >128-bit value")?;
match release {
Some(release) => crate::select::lower_assign_releasing(cx, lhs, bits, release)?,
None => crate::select::lower_assign_to(cx, lhs, bits)?,
}
Ok(value)
}
fn lower_real_binary_expr<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
bop: BinaryOp,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let left = e.left().ok_or_else(|| {
LoweringError::unsupported_at("binary op missing left operand", &cx.fn_sym)
})?;
let right = e.right().ok_or_else(|| {
LoweringError::unsupported_at("binary op missing right operand", &cx.fn_sym)
})?;
let l = lower_expr_h(cx, left)?;
let r = lower_expr_h(cx, right)?;
if matches!(bop, BinaryOp::LogicalAnd | BinaryOp::LogicalOr) {
let truth = |v: LoweredValue<'ctx>| -> Result<IntValue<'ctx>, LoweringError> {
Ok(match v {
LoweredValue::Real(rv) => crate::real::real_truth(cx, rv),
other => is_definitely_true(
cx.ctx,
&cx.b,
crate::huge::truth_packed(
cx,
other,
crate::huge::TruthMode::Strict,
"logical operand of a >128-bit value",
)?,
),
})
};
let (a, b) = (truth(l)?, truth(r)?);
let bit = if bop == BinaryOp::LogicalAnd {
cx.b.build_and(a, b, "real_land").unwrap()
} else {
cx.b.build_or(a, b, "real_lor").unwrap()
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
return Ok(LoweredValue::Packed(op.pack(
bit,
cx.ctx.bool_type().const_zero(),
1,
cx.repr,
false,
)));
}
let (LoweredValue::Real(lr), LoweredValue::Real(rr)) = (l, r) else {
return Err(LoweringError::unsupported_at(
&format!("binary op {bop:?} between a real and a non-real operand"),
&cx.fn_sym,
));
};
crate::real::real_binary(cx, bop, lr, rr).ok_or_else(|| {
LoweringError::unsupported_at(
&format!("operator {bop:?} is not defined on real operands"),
&cx.fn_sym,
)
})
}
fn lower_short_circuit_logical_condition<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
is_and: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let left_expr = e.left().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| missing left operand", &cx.fn_sym)
})?;
let right_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| missing right operand", &cx.fn_sym)
})?;
let left_pv = crate::huge::truth_packed(
cx,
lower_expr_h(cx, left_expr)?,
crate::huge::TruthMode::Strict,
"logical &&/|| on a non-integral (real) left operand",
)?;
let left_true = is_definitely_true(cx.ctx, &cx.b, left_pv);
let entry_block = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| lowered outside a basic block", &cx.fn_sym)
})?;
let f = entry_block.get_parent().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| block has no parent function", &cx.fn_sym)
})?;
let (eval_name, merge_name) = if is_and {
("land_rhs", "land_merge")
} else {
("lor_rhs", "lor_merge")
};
let rhs_block = cx.ctx.append_basic_block(f, eval_name);
let merge_block = cx.ctx.append_basic_block(f, merge_name);
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let left_reduced = op.redor4(left_pv);
let left_reduced_val = op.lane_val(left_reduced);
let left_reduced_unk = op.lane_unk(left_reduced);
let land_not_val =
cx.b.build_not(left_reduced_val, "land_cond_not_val")
.unwrap();
let land_not_unk =
cx.b.build_not(left_reduced_unk, "land_cond_not_unk")
.unwrap();
let left_definitely_false =
cx.b.build_and(land_not_val, land_not_unk, "land_cond_known_false")
.unwrap();
if is_and {
cx.b.build_conditional_branch(left_definitely_false, merge_block, rhs_block)
.unwrap();
} else {
cx.b.build_conditional_branch(left_true, merge_block, rhs_block)
.unwrap();
}
let short_circuit_pred = entry_block;
cx.b.position_at_end(rhs_block);
let right_pv = crate::huge::truth_packed(
cx,
lower_expr_h(cx, right_expr)?,
crate::huge::TruthMode::Strict,
"logical &&/|| on a non-integral (real) right operand",
)?;
let right_true = is_definitely_true(cx.ctx, &cx.b, right_pv);
let and_merge_true = if is_and {
let right_reduced = op.redor4(right_pv);
let and_combined = op.and4(left_reduced, right_reduced);
is_definitely_true(cx.ctx, &cx.b, and_combined)
} else {
right_true
};
let rhs_pred = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at(
"logical &&/|| right operand left no open basic block",
&cx.fn_sym,
)
})?;
cx.b.build_unconditional_branch(merge_block).unwrap();
cx.b.position_at_end(merge_block);
let bool_ty = cx.ctx.bool_type();
let phi = cx.b.build_phi(bool_ty, "logical_result").unwrap();
let short_circuit_val = if is_and {
bool_ty.const_zero()
} else {
bool_ty.const_int(1, false)
};
phi.add_incoming(&[
(&short_circuit_val, short_circuit_pred),
(&and_merge_true, rhs_pred),
]);
let result_i1 = phi.as_basic_value().into_int_value();
Ok(LoweredValue::Packed(op.pack(
result_i1,
bool_ty.const_zero(),
1,
cx.repr,
false,
)))
}
fn lower_short_circuit_logical_value<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
is_and: bool,
negate_left: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let left_expr = e.left().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| missing left operand", &cx.fn_sym)
})?;
let right_expr = e.right().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| missing right operand", &cx.fn_sym)
})?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let left_pv = crate::huge::truth_packed(
cx,
lower_expr_h(cx, left_expr)?,
crate::huge::TruthMode::Ternary,
"logical &&/|| on a non-integral (real) left operand",
)?;
let mut left_reduced = op.redor4(left_pv);
if negate_left {
left_reduced = op.not4(left_reduced);
}
let left_val = op.lane_val(left_reduced);
let left_unk = op.lane_unk(left_reduced);
let entry_block = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| lowered outside a basic block", &cx.fn_sym)
})?;
let f = entry_block.get_parent().ok_or_else(|| {
LoweringError::unsupported_at("logical &&/|| block has no parent function", &cx.fn_sym)
})?;
let skip_rhs = if is_and {
let not_val = cx.b.build_not(left_val, "land_not_val").unwrap();
let not_unk = cx.b.build_not(left_unk, "land_not_unk").unwrap();
cx.b.build_and(not_val, not_unk, "land_known_false")
.unwrap()
} else {
left_val
};
let (eval_name, merge_name) = if is_and {
("land_rhs_v", "land_merge_v")
} else {
("lor_rhs_v", "lor_merge_v")
};
let rhs_block = cx.ctx.append_basic_block(f, eval_name);
let merge_block = cx.ctx.append_basic_block(f, merge_name);
cx.b.build_conditional_branch(skip_rhs, merge_block, rhs_block)
.unwrap();
let short_circuit_pred = entry_block;
cx.b.position_at_end(rhs_block);
let right_pv = crate::huge::truth_packed(
cx,
lower_expr_h(cx, right_expr)?,
crate::huge::TruthMode::Ternary,
"logical &&/|| on a non-integral (real) right operand",
)?;
let right_reduced = op.redor4(right_pv);
let combined = if is_and {
op.and4(left_reduced, right_reduced)
} else {
op.or4(left_reduced, right_reduced)
};
let combined_val = op.lane_val(combined);
let combined_unk = op.lane_unk(combined);
let rhs_pred = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at(
"logical &&/|| right operand left no open basic block",
&cx.fn_sym,
)
})?;
cx.b.build_unconditional_branch(merge_block).unwrap();
cx.b.position_at_end(merge_block);
let bool_ty = cx.ctx.bool_type();
let val_phi = cx.b.build_phi(bool_ty, "logical_val").unwrap();
val_phi.add_incoming(&[(&left_val, short_circuit_pred), (&combined_val, rhs_pred)]);
let unk_phi = cx.b.build_phi(bool_ty, "logical_unk").unwrap();
unk_phi.add_incoming(&[(&left_unk, short_circuit_pred), (&combined_unk, rhs_pred)]);
Ok(LoweredValue::Packed(op.pack(
val_phi.as_basic_value().into_int_value(),
unk_phi.as_basic_value().into_int_value(),
1,
cx.repr,
false,
)))
}
fn lower_logical_equivalence<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let (Some(le), Some(re)) = (e.left(), e.right()) else {
return Err(LoweringError::unsupported_at(
"logical <-> missing operand",
&cx.fn_sym,
));
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let side = |x: sv_lang::Expression<'d>| -> Result<PackedValue<'ctx>, LoweringError> {
let pv = crate::huge::truth_packed(
cx,
lower_expr_h(cx, x)?,
crate::huge::TruthMode::Ternary,
"logical <-> on a non-integral (real) operand",
)?;
Ok(op.redor4(pv))
};
let l = side(le)?;
let r = side(re)?;
Ok(LoweredValue::Packed(op.not4(op.xor4(l, r))))
}
pub fn is_side_effect_free(
e: &sv_lang::Expression,
fn_sym: &sv_lang::Symbol,
sink: &dyn crate::coverage::CoverageSink,
) -> Result<bool, LoweringError> {
let self_pure = match e.kind() {
ExpressionKind::IntegerLiteral
| ExpressionKind::RealLiteral
| ExpressionKind::TimeLiteral
| ExpressionKind::UnbasedUnsizedIntegerLiteral
| ExpressionKind::NullLiteral
| ExpressionKind::UnboundedLiteral
| ExpressionKind::StringLiteral
| ExpressionKind::NamedValue
| ExpressionKind::HierarchicalValue
| ExpressionKind::BinaryOp
| ExpressionKind::Inside
| ExpressionKind::Concatenation
| ExpressionKind::Replication
| ExpressionKind::RangeSelect
| ExpressionKind::Conversion
| ExpressionKind::DataType
| ExpressionKind::TypeReference
| ExpressionKind::LValueReference
| ExpressionKind::SimpleAssignmentPattern
| ExpressionKind::StructuredAssignmentPattern
| ExpressionKind::ReplicatedAssignmentPattern
| ExpressionKind::EmptyArgument
| ExpressionKind::ValueRange
| ExpressionKind::MinTypMax
| ExpressionKind::ClockingEvent
| ExpressionKind::TaggedUnion => true,
ExpressionKind::MemberAccess => !e
.operand()
.and_then(|b| b.expr_type())
.is_some_and(|t| t.is_tagged_union() || t.is_class()),
ExpressionKind::ElementSelect => !e
.select_value()
.and_then(|b| b.expr_type())
.is_some_and(|t| t.is_associative_array()),
ExpressionKind::ConditionalOp => !e.conditions().iter().any(|c| c.pattern().is_some()),
ExpressionKind::UnaryOp => !matches!(
e.unary_op(),
Some(
UnaryOp::Preincrement
| UnaryOp::Predecrement
| UnaryOp::Postincrement
| UnaryOp::Postdecrement
)
),
ExpressionKind::Call
| ExpressionKind::Assignment
| ExpressionKind::NewArray
| ExpressionKind::NewClass
| ExpressionKind::NewCovergroup
| ExpressionKind::CopyClass => false,
ExpressionKind::Invalid
| ExpressionKind::Streaming
| ExpressionKind::ArbitrarySymbol
| ExpressionKind::Dist
| ExpressionKind::AssertionInstance => {
sink.record_fallback(
crate::coverage::construct_id_for_expression_kind(e.kind()),
crate::coverage::loc_for_expr(e),
);
false
}
_ => {
return Err(LoweringError::unsupported_at(
&format!(
"is_side_effect_free: unrecognized ExpressionKind {:?} \
(sv-lang's ExpressionKind::COUNT likely grew past this \
match -- ARCHITECTURE.md SS7 item 9: no silent \
no-op default)",
e.kind()
),
fn_sym,
));
}
};
if !self_pure {
return Ok(false);
}
for c in e.children().iter() {
if let Some(ce) = c.as_expression()
&& !is_side_effect_free(&ce, fn_sym, sink)?
{
return Ok(false);
}
}
Ok(true)
}
fn lower_nonintegral_ternary<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
cond_true: IntValue<'ctx>,
cond_false: IntValue<'ctx>,
then_expr: sv_lang::Expression<'d>,
else_expr: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let arm = |e: sv_lang::Expression<'d>| -> Result<inkwell::values::BasicValueEnum<'ctx>, LoweringError> {
match lower_expr(cx, e)? {
LoweredValue::Real(r) => Ok(crate::real::as_f64(cx, r.llvm).into()),
LoweredValue::Str(st) => Ok(st.llvm.into()),
_ => Err(LoweringError::unsupported_at(
"a conditional operator arm of a real or string result is neither",
&cx.fn_sym,
)),
}
};
let f = cx
.b
.get_insert_block()
.and_then(|bb| bb.get_parent())
.ok_or_else(|| LoweringError::unsupported_at("ternary outside a function", &cx.fn_sym))?;
let then_block = cx.ctx.append_basic_block(f, "tern_then");
let else_block = cx.ctx.append_basic_block(f, "tern_else");
let merge_block = cx.ctx.append_basic_block(f, "tern_merge");
let entry_pred = cx.b.get_insert_block().expect("positioned");
cx.b.build_conditional_branch(cond_false, else_block, then_block)
.unwrap();
cx.b.position_at_end(then_block);
let then_v = arm(then_expr)?;
let then_pred = cx.b.get_insert_block().expect("positioned");
cx.b.build_conditional_branch(cond_true, merge_block, else_block)
.unwrap();
cx.b.position_at_end(else_block);
let then_seen = cx.b.build_phi(then_v.get_type(), "tern_then_seen").unwrap();
let zero: inkwell::values::BasicValueEnum<'ctx> = if then_v.is_float_value() {
cx.ctx.f64_type().const_zero().into()
} else {
then_v.get_type().into_int_type().const_zero().into()
};
then_seen.add_incoming(&[(&zero, entry_pred), (&then_v, then_pred)]);
let a = then_seen.as_basic_value();
let b = arm(else_expr)?;
let is_real = a.is_float_value();
if is_real != b.is_float_value() {
return Err(LoweringError::unsupported_at(
"conditional operator arms of different types",
&cx.fn_sym,
));
}
let merged: inkwell::values::BasicValueEnum<'ctx> = if is_real {
let (a, b) = (a.into_float_value(), b.into_float_value());
let same =
cx.b.build_float_compare(inkwell::FloatPredicate::OEQ, a, b, "tern_same")
.unwrap();
cx.b.build_select(same, a, cx.ctx.f64_type().const_zero(), "tern_real")
.unwrap()
} else {
let (a, b) = (a.into_int_value(), b.into_int_value());
let same =
cx.b.build_int_compare(inkwell::IntPredicate::EQ, a, b, "tern_same")
.unwrap();
let empty = crate::strings::literal(cx, b"").llvm;
cx.b.build_select(same, a, empty, "tern_str").unwrap()
};
let else_val =
cx.b.build_select(cond_false, b, merged, "tern_else_val")
.unwrap();
let else_pred = cx.b.get_insert_block().expect("positioned");
cx.b.build_unconditional_branch(merge_block).unwrap();
cx.b.position_at_end(merge_block);
let phi = cx.b.build_phi(then_v.get_type(), "tern_result").unwrap();
phi.add_incoming(&[(&then_v, then_pred), (&else_val, else_pred)]);
let v = phi.as_basic_value();
Ok(if is_real {
LoweredValue::Real(RealValue {
llvm: v.into_float_value(),
})
} else {
LoweredValue::Str(crate::strings::StrValue {
llvm: v.into_int_value(),
})
})
}
fn lower_ternary<'ctx, 'd, 'm>(
cx: &LowerCtx<'ctx, 'd, 'm>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let conds = e.conditions();
if conds.len() != 1 || conds[0].pattern().is_some() {
return crate::tagged::lower_pattern_ternary(cx, e);
}
let cond_expr = conds[0].expr();
let then_expr = e.true_value().ok_or_else(|| {
LoweringError::unsupported_at("ternary missing true-value arm", &cx.fn_sym)
})?;
let else_expr = e.false_value().ok_or_else(|| {
LoweringError::unsupported_at("ternary missing false-value arm", &cx.fn_sym)
})?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let cond_pv = crate::huge::truth_packed(
cx,
lower_expr_h(cx, cond_expr)?,
crate::huge::TruthMode::Ternary,
"ternary condition must be an integral (non-real) value",
)?;
let cond_true = ternary_is_definitely_true(cx.ctx, &cx.b, cond_pv);
let cond_false = is_definitely_false(cx.ctx, &cx.b, cond_pv);
if e.expr_type()
.is_some_and(|t| t.is_floating() || t.is_string())
{
return lower_nonintegral_ternary(cx, cond_true, cond_false, then_expr, else_expr);
}
if is_side_effect_free(&then_expr, &cx.fn_sym, &crate::coverage::NoopCoverageSink)?
&& is_side_effect_free(&else_expr, &cx.fn_sym, &crate::coverage::NoopCoverageSink)?
{
let then_pv = lower_expr(cx, then_expr)?.into_packed(
"ternary then-arm must be an integral (non-real) value",
&cx.fn_sym,
)?;
let else_pv = lower_expr(cx, else_expr)?.into_packed(
"ternary else-arm must be an integral (non-real) value",
&cx.fn_sym,
)?;
if then_pv.width != else_pv.width {
return Err(LoweringError::unsupported_at(
"ternary then/else arms have mismatched widths (expected \
slang's own inserted Conversion nodes to have already \
equalized them)",
&cx.fn_sym,
));
}
let merged = op.ternary_merge4(then_pv, else_pv);
let inner = op.select(cond_false, else_pv, merged);
let result = op.select(cond_true, then_pv, inner);
return Ok(LoweredValue::Packed(result));
}
let entry_block = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at("ternary lowered outside a basic block", &cx.fn_sym)
})?;
let f = entry_block.get_parent().ok_or_else(|| {
LoweringError::unsupported_at("ternary block has no parent function", &cx.fn_sym)
})?;
let then_block = cx.ctx.append_basic_block(f, "tern_then");
let else_block = cx.ctx.append_basic_block(f, "tern_else");
let merge_block = cx.ctx.append_basic_block(f, "tern_merge");
cx.b.build_conditional_branch(cond_false, else_block, then_block)
.unwrap();
cx.b.position_at_end(then_block);
let then_pv = lower_expr(cx, then_expr)?.into_packed(
"ternary then-arm must be an integral (non-real) value",
&cx.fn_sym,
)?;
let then_pred = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at("ternary then-arm left no open basic block", &cx.fn_sym)
})?;
cx.b.build_conditional_branch(cond_true, merge_block, else_block)
.unwrap();
cx.b.position_at_end(else_block);
let seen =
cx.b.build_phi(then_pv.llvm.get_type(), "tern_then_seen")
.unwrap();
let zero = then_pv.llvm.get_type().const_zero();
seen.add_incoming(&[(&zero, entry_block), (&then_pv.llvm, then_pred)]);
let seen_pv = PackedValue {
llvm: seen.as_basic_value().into_int_value(),
..then_pv
};
let else_pv = lower_expr(cx, else_expr)?.into_packed(
"ternary else-arm must be an integral (non-real) value",
&cx.fn_sym,
)?;
if then_pv.width != else_pv.width {
return Err(LoweringError::unsupported_at(
"ternary then/else arms have mismatched widths (expected \
slang's own inserted Conversion nodes to have already \
equalized them)",
&cx.fn_sym,
));
}
let merged_pv = op.ternary_merge4(seen_pv, else_pv);
let else_val = op.select(cond_false, else_pv, merged_pv);
let else_pred = cx.b.get_insert_block().ok_or_else(|| {
LoweringError::unsupported_at("ternary else-arm left no open basic block", &cx.fn_sym)
})?;
cx.b.build_unconditional_branch(merge_block).unwrap();
cx.b.position_at_end(merge_block);
let phi =
cx.b.build_phi(then_pv.llvm.get_type(), "tern_result")
.unwrap();
phi.add_incoming(&[(&then_pv.llvm, then_pred), (&else_val.llvm, else_pred)]);
Ok(LoweredValue::Packed(PackedValue {
llvm: phi.as_basic_value().into_int_value(),
width: then_pv.width,
repr: cx.repr,
signed: then_pv.signed,
}))
}
pub(crate) fn entry_alloca<'ctx, T: inkwell::types::BasicType<'ctx>>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
ty: T,
name: &str,
) -> PointerValue<'ctx> {
crate::types::entry_alloca_in(&cx.b, ty, name)
}
pub(crate) fn is_zero_replication(e: &sv_lang::Expression<'_>) -> bool {
e.kind() == ExpressionKind::Replication
&& e.replication_count()
.and_then(|c| c.constant_value())
.is_some_and(|v| v.as_integer().and_then(|i| i.as_u64()) == Some(0))
}
fn store_partial_element<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
elem: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
_release: Option<&mut dyn FnMut(PackedValue<'ctx>)>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let cur = crate::collections::read_element(cx, elem)
.ok_or_else(|| LoweringError::unsupported_at("a non-collection element", &cx.fn_sym))??
.into_packed("a collection element written in part", &cx.fn_sym)?;
let ptr = entry_alloca(cx, op.packed_type(cur.width, cx.repr), "coll_part");
cx.b.build_store(ptr, cur.llvm).unwrap();
let width = lhs
.expr_type()
.map(|t| t.value_width())
.filter(|w| *w > 0)
.ok_or_else(|| {
LoweringError::unsupported_at("assignment target has no width", &cx.fn_sym)
})?;
let bits = crate::real::to_storage(cx, value, width, "assignment of a >128-bit value")?;
let saved = cx.coll_temp.replace(Some(ptr));
let r = crate::select::lower_assign_to(cx, lhs, bits);
cx.coll_temp.set(saved);
r?;
let new = PackedValue {
llvm: cx
.b
.build_load(op.packed_type(cur.width, cx.repr), ptr, "coll_part_new")
.unwrap()
.into_int_value(),
..cur
};
let store = || {
crate::collections::write_element(cx, elem, LoweredValue::Packed(new)).unwrap_or_else(
|| {
Err(LoweringError::unsupported_at(
"a non-collection element",
&cx.fn_sym,
))
},
)
};
let assoc = elem
.select_value()
.and_then(|b| crate::collections::coll_of_expr(&b))
.is_some_and(|c| c.assoc);
if assoc {
let ok = crate::collections::chain_in_range(cx, lhs, elem)?;
crate::collections::guarded_by(cx, ok, store)?;
} else {
store()?;
}
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mem_row_index_i64_extends_a_narrow_index_by_its_own_signedness() {
let design = crate::frontend::compile_source("module m; endmodule\n").unwrap();
let body = design
.top_instances()
.next()
.unwrap()
.instance_body()
.unwrap();
let llvm_ctx = Context::create();
let module = llvm_ctx.create_module("test_mem_row_index_i64");
let i8_ty = llvm_ctx.i8_type();
let i64_ty = llvm_ctx.i64_type();
let fn_ty = i64_ty.fn_type(&[i8_ty.into()], 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 param = f.get_nth_param(0).unwrap().into_int_value();
let cx = LowerCtx {
ctx: &llvm_ctx,
b,
repr: ValueRepr::FourState,
locals: HashMap::new(),
fn_sym: body,
module: &module,
mem_signals: HashMap::new(),
mem_accessor: None,
lvalue_ref: Default::default(),
unbounded: Default::default(),
coll_temp: Default::default(),
iter: Default::default(),
loop_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(),
block_exits: Default::default(),
deferred_start: Default::default(),
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let idx = PackedValue {
llvm: param,
width: 8,
repr: ValueRepr::TwoState,
signed: false,
};
let row_index = mem_row_index_i64(&op, &cx, idx);
cx.b.build_return(Some(&row_index)).unwrap();
let fs = module.add_function("test_fn_signed", fn_ty, None);
let entry_s = llvm_ctx.append_basic_block(fs, "entry");
cx.b.position_at_end(entry_s);
let idx_s = PackedValue {
llvm: fs.get_nth_param(0).unwrap().into_int_value(),
width: 8,
repr: ValueRepr::TwoState,
signed: true,
};
let row_index_s = mem_row_index_i64(&op, &cx, idx_s);
cx.b.build_return(Some(&row_index_s)).unwrap();
assert!(
f.verify(true) && fs.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(i8) -> i64 =
unsafe { std::mem::transmute::<*const (), extern "C" fn(i8) -> i64>(raw) };
let result = callable(-56i8);
assert_eq!(
result, 200,
"an UNSIGNED 8-bit index 0xC8 must zero-extend to 200"
);
let raw_s = jit
.get_raw("test_fn_signed")
.expect("test_fn_signed must be a real symbol in the JIT'd module");
let callable_s: extern "C" fn(i8) -> i64 =
unsafe { std::mem::transmute::<*const (), extern "C" fn(i8) -> i64>(raw_s) };
assert_eq!(
callable_s(-56i8),
-56,
"a SIGNED 8-bit index 0xC8 is -56 and must sign-extend"
);
}
}