use crate::types::TypeWidth;
use inkwell::IntPredicate;
use inkwell::values::{IntValue, PointerValue};
use sv_lang::kinds::ExpressionKind;
use sv_sim_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{
LowerCtx, MemorySignalInfo, RangeSelectionKindClass, classify_range_selection_kind, lower_expr,
require_const_i64,
};
use crate::ops::OpBuilder;
use crate::value::{PackedValue, ValueRepr};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct SelectGeom {
pub left: i64,
pub right: i64,
pub elem_width: u32,
}
impl SelectGeom {
pub(crate) fn elems(self) -> i64 {
(self.left - self.right).abs() + 1
}
pub(crate) fn descending(self) -> bool {
self.left >= self.right
}
pub(crate) fn pos(self, i: i64) -> i64 {
if self.descending() {
i - self.right
} else {
self.right - i
}
}
pub(crate) fn canonical(width: u32) -> Self {
SelectGeom {
left: i64::from(width) - 1,
right: 0,
elem_width: 1,
}
}
}
pub(crate) fn geom_of(ty: Option<sv_lang::Type<'_>>, width: u32) -> SelectGeom {
let Some(ty) = ty else {
return SelectGeom::canonical(width);
};
if ty.is_packed_array() {
let r = ty.packed_array_range();
let ew = ty
.element_type()
.map(|e| e.value_width())
.unwrap_or(1)
.max(1);
if u64::from(r.width()) * u64::from(ew) == u64::from(width) {
return SelectGeom {
left: i64::from(r.left),
right: i64::from(r.right),
elem_width: ew,
};
}
}
if ty.is_unpacked_array()
&& !ty.is_dynamically_sized_array()
&& let Some(elem) = ty.element_type()
&& let Some(ew) = sv_sim_sim::state::storage::storage_width(&elem)
{
let r = ty.fixed_unpacked_array_range();
if u64::from(r.width()) * ew == u64::from(width) {
return SelectGeom {
left: i64::from(r.upper()),
right: i64::from(r.lower()),
elem_width: ew as u32,
};
}
}
if ty.is_integral() && !ty.is_struct() && !ty.is_union() {
let r = ty.bit_vector_range();
if r.width() == width {
return SelectGeom {
left: i64::from(r.left),
right: i64::from(r.right),
elem_width: 1,
};
}
}
SelectGeom::canonical(width)
}
#[derive(Clone, Copy)]
pub(crate) struct Window<'ctx> {
pub lo: IntValue<'ctx>,
pub width: u32,
pub bad: IntValue<'ctx>,
pub edge: Option<Edge<'ctx>>,
pub pad: Option<Pad<'ctx>>,
}
#[derive(Clone, Copy)]
pub(crate) struct Pad<'ctx> {
pub total: u32,
pub shift: u32,
pub dynamic: Option<IntValue<'ctx>>,
}
#[derive(Clone, Copy)]
pub(crate) struct Edge<'ctx> {
pub delta: IntValue<'ctx>,
pub valid: IntValue<'ctx>,
}
fn shift_by<'ctx>(
op: &OpBuilder<'_, 'ctx>,
x: IntValue<'ctx>,
amt: IntValue<'ctx>,
left: bool,
) -> IntValue<'ctx> {
let ty = x.get_type();
let i64t = op.ctx.i64_type();
let over =
op.b.build_int_compare(
IntPredicate::UGE,
amt,
i64t.const_int(u64::from(ty.get_bit_width()), false),
"edge_over",
)
.unwrap();
let safe =
op.b.build_select(over, i64t.const_zero(), amt, "edge_amt")
.unwrap()
.into_int_value();
let amt_n =
op.b.build_int_cast_sign_flag(safe, ty, false, "edge_amt_n")
.unwrap();
let shifted = if left {
op.b.build_left_shift(x, amt_n, "edge_shl").unwrap()
} else {
op.b.build_right_shift(x, amt_n, false, "edge_shr").unwrap()
};
op.b.build_select(over, ty.const_zero(), shifted, "edge_sh")
.unwrap()
.into_int_value()
}
fn shift_signed<'ctx>(
op: &OpBuilder<'_, 'ctx>,
x: IntValue<'ctx>,
d: IntValue<'ctx>,
left_when_positive: bool,
) -> IntValue<'ctx> {
let i64t = op.ctx.i64_type();
let neg =
op.b.build_int_compare(IntPredicate::SLT, d, i64t.const_zero(), "edge_neg")
.unwrap();
let flipped = op.b.build_int_neg(d, "edge_mag").unwrap();
let mag =
op.b.build_select(neg, flipped, d, "edge_abs")
.unwrap()
.into_int_value();
let pos_way = shift_by(op, x, mag, left_when_positive);
let neg_way = shift_by(op, x, mag, !left_when_positive);
op.b.build_select(neg, neg_way, pos_way, "edge_dir")
.unwrap()
.into_int_value()
}
fn range_mask<'ctx>(
op: &OpBuilder<'_, 'ctx>,
start: IntValue<'ctx>,
n: u32,
base_width: u32,
) -> IntValue<'ctx> {
let i64t = op.ctx.i64_type();
let zero = i64t.const_zero();
let missing_lo = {
let neg = op.b.build_int_neg(start, "edge_miss_lo").unwrap();
let below =
op.b.build_int_compare(IntPredicate::SLT, start, zero, "edge_below")
.unwrap();
op.b.build_select(below, neg, zero, "edge_lo_n")
.unwrap()
.into_int_value()
};
let missing_hi = {
let end =
op.b.build_int_add(start, i64t.const_int(u64::from(n), false), "edge_end")
.unwrap();
let over =
op.b.build_int_sub(
end,
i64t.const_int(u64::from(base_width), false),
"edge_miss_hi",
)
.unwrap();
let above =
op.b.build_int_compare(
IntPredicate::SGT,
end,
i64t.const_int(u64::from(base_width), false),
"edge_above",
)
.unwrap();
op.b.build_select(above, over, zero, "edge_hi_n")
.unwrap()
.into_int_value()
};
let ones = op.i_n(n).const_all_ones();
op.and(
shift_by(op, ones, missing_lo, true),
shift_by(op, ones, missing_hi, false),
)
}
fn make_edge<'ctx>(
op: &OpBuilder<'_, 'ctx>,
lo: IntValue<'ctx>,
start: IntValue<'ctx>,
n: u32,
base_width: u32,
) -> Edge<'ctx> {
Edge {
delta: op.b.build_int_sub(start, lo, "edge_delta").unwrap(),
valid: range_mask(op, start, n, base_width),
}
}
pub(crate) fn select_window<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
base_width: u32,
geom: SelectGeom,
) -> Result<Window<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
match e.kind() {
ExpressionKind::ElementSelect => {
let idx_expr = e.selector().ok_or_else(|| {
LoweringError::unsupported_at("bit-select missing index", &cx.fn_sym)
})?;
let idx = lower_expr(cx, idx_expr)?.into_packed(
"bit-select index must itself lower to an integral value",
&cx.fn_sym,
)?;
Ok(dynamic_window(&op, idx, 0, 1, geom, base_width))
}
ExpressionKind::RangeSelect => {
let kind_ord = e.range_selection_kind().ok_or_else(|| {
LoweringError::unsupported_at(
"part-select missing range_selection_kind",
&cx.fn_sym,
)
})?;
let kind = classify_range_selection_kind(kind_ord)
.map_err(|msg| LoweringError::unsupported_at(&msg, &cx.fn_sym))?;
let left_expr = e.range_left().ok_or_else(|| {
LoweringError::unsupported_at("part-select missing left bound", &cx.fn_sym)
})?;
let right_expr = e.range_right().ok_or_else(|| {
LoweringError::unsupported_at("part-select missing right bound", &cx.fn_sym)
})?;
match kind {
RangeSelectionKindClass::Simple => {
let l = require_const_i64(
left_expr,
"simple part-select left bound must be a compile-time constant",
&cx.fn_sym,
)?;
let r = require_const_i64(
right_expr,
"simple part-select right bound must be a compile-time constant",
&cx.fn_sym,
)?;
let (pl, pr) = if geom.elems() == 1 && l < r {
(geom.right - l, geom.right - r)
} else {
(geom.pos(l), geom.pos(r))
};
let (lo_e, hi_e) = (pl.min(pr), pl.max(pr));
let count = hi_e - lo_e + 1;
let ew = i64::from(geom.elem_width);
if count > 0 && count.saturating_mul(ew) > i64::from(base_width) {
return oversize_window(cx, &op, lo_e, hi_e, geom, base_width);
}
let width = checked_window_width(cx, count, geom, base_width)?;
let in_range = lo_e >= 0 && hi_e < geom.elems();
let fully_out = hi_e < 0 || lo_e >= geom.elems();
if !in_range && !fully_out {
let i64t = cx.ctx.i64_type();
let clamped = lo_e.clamp(0, geom.elems() - count);
let lo = i64t.const_int((clamped * ew) as u64, true);
let start = i64t.const_int((lo_e * ew) as u64, true);
return Ok(Window {
lo: op.i_n(base_width).const_int((clamped * ew) as u64, false),
width,
bad: cx.ctx.bool_type().const_zero(),
edge: Some(make_edge(&op, lo, start, width, base_width)),
pad: None,
});
}
let lo_bits = if in_range { (lo_e * ew) as u64 } else { 0 };
Ok(Window {
lo: op.i_n(base_width).const_int(lo_bits, false),
width,
bad: cx.ctx.bool_type().const_int(u64::from(!in_range), false),
edge: None,
pad: None,
})
}
RangeSelectionKindClass::IndexedUp | RangeSelectionKindClass::IndexedDown => {
let count = require_const_i64(
right_expr,
"indexed part-select width must be a compile-time constant",
&cx.fn_sym,
)?;
if count <= 0 {
return Err(LoweringError::unsupported_at(
"indexed part-select with a non-positive width",
&cx.fn_sym,
));
}
let oversize =
count.saturating_mul(i64::from(geom.elem_width)) > i64::from(base_width);
if !oversize {
checked_window_width(cx, count, geom, base_width)?;
}
let idx = lower_expr(cx, left_expr)?.into_packed(
"indexed part-select base must itself lower to an integral value",
&cx.fn_sym,
)?;
let up = kind == RangeSelectionKindClass::IndexedUp;
let low_delta = match (up, geom.descending()) {
(true, true) | (false, false) => 0,
(true, false) => count - 1,
(false, true) => -(count - 1),
};
if oversize {
return dynamic_oversize_window(
cx, &op, idx, low_delta, count, geom, base_width,
);
}
Ok(dynamic_window(&op, idx, low_delta, count, geom, base_width))
}
}
}
other => Err(LoweringError::unsupported_at(
&format!("select_window called on a {other:?} expression"),
&cx.fn_sym,
)),
}
}
pub fn constant_window(e: sv_lang::Expression<'_>, base_width: u32) -> Result<(u32, u32), String> {
constant_window_skip(e, base_width, 0)
}
pub fn constant_window_skip(
e: sv_lang::Expression<'_>,
base_width: u32,
skip: usize,
) -> Result<(u32, u32), String> {
fn depth(e: sv_lang::Expression<'_>) -> usize {
match (e.kind(), e.select_value()) {
(ExpressionKind::ElementSelect | ExpressionKind::RangeSelect, Some(b)) => 1 + depth(b),
_ => 0,
}
}
fn go(e: sv_lang::Expression<'_>, base_width: u32, skip: usize) -> Result<(u32, u32), String> {
let kind = e.kind();
if kind != ExpressionKind::ElementSelect && kind != ExpressionKind::RangeSelect {
return Ok((0, base_width));
}
let base = e.select_value().ok_or("a select with no base value")?;
if depth(e) <= skip {
return Ok((0, base_width));
}
let (base_lo, base_bits) = go(base, base_width, skip)?;
step(e, base, base_lo, base_bits)
}
go(e, base_width, skip)
}
fn step(
e: sv_lang::Expression<'_>,
base: sv_lang::Expression<'_>,
base_lo: u32,
base_bits: u32,
) -> Result<(u32, u32), String> {
let kind = e.kind();
let geom = geom_of(base.expr_type(), base_bits);
let constant = |x: Option<sv_lang::Expression<'_>>, what: &str| -> Result<i64, String> {
x.and_then(|x| x.constant_value())
.and_then(|c| c.as_i64())
.ok_or_else(|| format!("{what} is not a compile-time constant"))
};
let (pos, count) = if kind == ExpressionKind::ElementSelect {
(geom.pos(constant(e.selector(), "the select index")?), 1)
} else {
let sel = classify_range_selection_kind(
e.range_selection_kind()
.ok_or("a part-select with no kind")?,
)?;
let left = constant(e.range_left(), "the part-select's left bound")?;
let right = constant(e.range_right(), "the part-select's right bound")?;
match sel {
RangeSelectionKindClass::Simple => {
let (pl, pr) = (geom.pos(left), geom.pos(right));
(pl.min(pr), (pl - pr).abs() + 1)
}
RangeSelectionKindClass::IndexedUp | RangeSelectionKindClass::IndexedDown => {
if right <= 0 {
return Err("an indexed part-select with a non-positive width".into());
}
let (a, b) = if sel == RangeSelectionKindClass::IndexedUp {
(left, left + right - 1)
} else {
(left - right + 1, left)
};
(geom.pos(a).min(geom.pos(b)), right)
}
}
};
if pos < 0 || pos + count > geom.elems() {
return Err("the select is outside the value's declared range".into());
}
let ew = u64::from(geom.elem_width);
let lo = u64::from(base_lo) + pos as u64 * ew;
Ok((lo as u32, (count as u64 * ew) as u32))
}
fn oversize_window<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
lo_e: i64,
hi_e: i64,
geom: SelectGeom,
base_width: u32,
) -> Result<Window<'ctx>, LoweringError> {
let ew = i64::from(geom.elem_width);
let total = (hi_e - lo_e + 1).saturating_mul(ew);
let total = u32::try_from(total).map_err(|_| {
LoweringError::unsupported_at("part-select addressing more than 2^32 bits", &cx.fn_sym)
})?;
let (in_lo, in_hi) = (lo_e.max(0), hi_e.min(geom.elems() - 1));
let fully_out = in_lo > in_hi;
let (lo_bits, width, shift) = if fully_out {
(0, 1, 0)
} else {
(
in_lo * ew,
((in_hi - in_lo + 1) * ew) as u32,
((in_lo - lo_e) * ew) as u32,
)
};
Ok(Window {
lo: op.i_n(base_width).const_int(lo_bits as u64, false),
width,
bad: cx.ctx.bool_type().const_int(u64::from(fully_out), false),
edge: None,
pad: Some(Pad {
total,
shift,
dynamic: None,
}),
})
}
fn dynamic_oversize_window<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: &OpBuilder<'_, 'ctx>,
idx: PackedValue<'ctx>,
low_delta: i64,
count: i64,
geom: SelectGeom,
base_width: u32,
) -> Result<Window<'ctx>, LoweringError> {
let total = u32::try_from(count.saturating_mul(i64::from(geom.elem_width))).map_err(|_| {
LoweringError::unsupported_at("part-select addressing more than 2^32 bits", &cx.fn_sym)
})?;
let cw = idx.width.max(64) + 2;
let cty = op.i_n(cw);
let val = op.lane_val(idx);
let ext = if idx.width == cw {
val
} else if idx.signed {
op.b.build_int_s_extend(val, cty, "sel_idx_sext").unwrap()
} else {
op.b.build_int_z_extend(val, cty, "sel_idx_zext").unwrap()
};
let c = |v: i64| cty.const_int(v as u64, true);
let low =
op.b.build_int_add(ext, c(low_delta), "sel_low_index")
.unwrap();
let pos = if geom.descending() {
op.b.build_int_sub(low, c(geom.right), "sel_pos").unwrap()
} else {
op.b.build_int_sub(c(geom.right), low, "sel_pos").unwrap()
};
let before =
op.b.build_int_compare(IntPredicate::SLE, pos, c(-count), "sel_before")
.unwrap();
let past =
op.b.build_int_compare(IntPredicate::SGE, pos, c(geom.elems()), "sel_past")
.unwrap();
let unk = op.lane_unk(idx);
let unknown =
op.b.build_int_compare(
IntPredicate::NE,
unk,
unk.get_type().const_zero(),
"sel_idx_unknown",
)
.unwrap();
let missed = op.b.build_or(before, past, "sel_fully_out").unwrap();
let bad = op.b.build_or(missed, unknown, "sel_bad").unwrap();
let ew = c(i64::from(geom.elem_width));
let shift = op.b.build_int_mul(pos, ew, "sel_start_bits").unwrap();
let shift = op.b.build_int_neg(shift, "sel_pad_shift").unwrap();
let shift =
op.b.build_select(bad, c(0), shift, "sel_shift_safe")
.unwrap()
.into_int_value();
let shift =
op.b.build_int_cast_sign_flag(shift, cx.ctx.i64_type(), true, "sel_shift64")
.unwrap();
Ok(Window {
lo: op.i_n(base_width).const_zero(),
width: base_width,
bad,
edge: None,
pad: Some(Pad {
total,
shift: 0,
dynamic: Some(shift),
}),
})
}
pub(crate) fn checked_window_width(
cx: &LowerCtx<'_, '_, '_>,
count: i64,
geom: SelectGeom,
base_width: u32,
) -> Result<u32, LoweringError> {
let bits = count as u64 * u64::from(geom.elem_width);
if count <= 0 || bits > u64::from(base_width) {
return Err(LoweringError::unsupported_at(
"part-select wider than the value it selects from",
&cx.fn_sym,
));
}
Ok(bits as u32)
}
fn dynamic_window<'ctx>(
op: &OpBuilder<'_, 'ctx>,
idx: PackedValue<'ctx>,
low_delta: i64,
count: i64,
geom: SelectGeom,
base_width: u32,
) -> Window<'ctx> {
let cw = idx.width.max(64) + 2;
let cty = op.i_n(cw);
let val = op.lane_val(idx);
let ext = if idx.width == cw {
val
} else if idx.signed {
op.b.build_int_s_extend(val, cty, "sel_idx_sext").unwrap()
} else {
op.b.build_int_z_extend(val, cty, "sel_idx_zext").unwrap()
};
let c = |v: i64| cty.const_int(v as u64, true);
let low =
op.b.build_int_add(ext, c(low_delta), "sel_low_index")
.unwrap();
let pos = if geom.descending() {
op.b.build_int_sub(low, c(geom.right), "sel_pos").unwrap()
} else {
op.b.build_int_sub(c(geom.right), low, "sel_pos").unwrap()
};
let below =
op.b.build_int_compare(IntPredicate::SLT, pos, c(0), "sel_below")
.unwrap();
let above =
op.b.build_int_compare(IntPredicate::SGT, pos, c(geom.elems() - count), "sel_above")
.unwrap();
let unk = op.lane_unk(idx);
let unknown =
op.b.build_int_compare(
IntPredicate::NE,
unk,
unk.get_type().const_zero(),
"sel_idx_unknown",
)
.unwrap();
let width = (count as u32) * geom.elem_width;
let ew = c(i64::from(geom.elem_width));
let edge_possible = count > 1;
let oob = op.b.build_or(below, above, "sel_oob").unwrap();
let fully_out = if edge_possible {
let before =
op.b.build_int_compare(IntPredicate::SLT, pos, c(1 - count), "sel_before")
.unwrap();
let past =
op.b.build_int_compare(IntPredicate::SGE, pos, c(geom.elems()), "sel_past")
.unwrap();
op.b.build_or(before, past, "sel_fully_out").unwrap()
} else {
oob
};
let bad = op.b.build_or(fully_out, unknown, "sel_bad").unwrap();
let clamped = if edge_possible {
let capped =
op.b.build_select(above, c(geom.elems() - count), pos, "sel_cap")
.unwrap()
.into_int_value();
op.b.build_select(below, c(0), capped, "sel_clamp")
.unwrap()
.into_int_value()
} else {
pos
};
let lo_c = op.b.build_int_mul(clamped, ew, "sel_lo_bits").unwrap();
let lo_c =
op.b.build_select(bad, c(0), lo_c, "sel_lo_safe")
.unwrap()
.into_int_value();
let lo =
op.b.build_int_cast_sign_flag(lo_c, op.i_n(base_width), false, "sel_lo")
.unwrap();
let edge = edge_possible.then(|| {
let i64t = op.ctx.i64_type();
let start = op.b.build_int_mul(pos, ew, "sel_start_bits").unwrap();
let start =
op.b.build_select(bad, c(0), start, "sel_start_safe")
.unwrap()
.into_int_value();
let start64 =
op.b.build_int_cast_sign_flag(start, i64t, true, "sel_start64")
.unwrap();
let lo64 =
op.b.build_int_cast_sign_flag(lo_c, i64t, true, "sel_lo64")
.unwrap();
make_edge(op, lo64, start64, width, base_width)
});
Window {
lo,
width,
bad,
edge,
pad: None,
}
}
pub(crate) fn two_state_result<'ctx>(
op: &OpBuilder<'_, 'ctx>,
e: sv_lang::Expression<'_>,
v: PackedValue<'ctx>,
) -> PackedValue<'ctx> {
if v.repr == ValueRepr::TwoState || e.expr_type().is_none_or(|t| t.is_four_state()) {
return v;
}
op.pack(
op.lane_val(v),
op.i_n(v.width).const_zero(),
v.width,
v.repr,
v.signed,
)
}
pub(crate) fn read_window<'ctx>(
op: &OpBuilder<'_, 'ctx>,
base: PackedValue<'ctx>,
window: Window<'ctx>,
) -> PackedValue<'ctx> {
let core = op.part_select4(base, window.lo, window.bad, window.width);
if let Some(pad) = window.pad {
return pad_read(op, core, window.bad, pad, base.repr);
}
let Some(edge) = window.edge else {
return core;
};
let v = shift_signed(op, op.lane_val(core), edge.delta, false);
let u = shift_signed(op, op.lane_unk(core), edge.delta, false);
let outside = op.not(edge.valid);
let v = op.and(v, edge.valid);
let u = op.or(op.and(u, edge.valid), outside);
let shifted = op.pack(v, u, window.width, base.repr, core.signed);
op.select(
window.bad,
op.pack(
op.i_n(window.width).const_zero(),
op.i_n(window.width).const_all_ones(),
window.width,
base.repr,
core.signed,
),
shifted,
)
}
fn pad_read<'ctx>(
op: &OpBuilder<'_, 'ctx>,
core: PackedValue<'ctx>,
bad: IntValue<'ctx>,
pad: Pad<'ctx>,
repr: ValueRepr,
) -> PackedValue<'ctx> {
let ty = op.i_n(pad.total);
let at = ty.const_int(u64::from(pad.shift), false);
let place = |lane: IntValue<'ctx>| {
let wide = op.b.build_int_z_extend(lane, ty, "pad_ext").unwrap();
match pad.dynamic {
Some(d) => shift_signed(op, wide, d, true),
None => op.b.build_left_shift(wide, at, "pad_shl").unwrap(),
}
};
let slice = op.i_n(core.width).const_all_ones();
let inside = place(slice);
let v = place(op.lane_val(core));
let u = op.or(place(op.lane_unk(core)), op.not(inside));
let shifted = op.pack(v, u, pad.total, repr, core.signed);
op.select(
bad,
op.pack(
ty.const_zero(),
ty.const_all_ones(),
pad.total,
repr,
core.signed,
),
shifted,
)
}
fn merge_edge<'ctx>(
op: &OpBuilder<'_, 'ctx>,
current: PackedValue<'ctx>,
edge: Option<Edge<'ctx>>,
value: PackedValue<'ctx>,
) -> PackedValue<'ctx> {
let Some(edge) = edge else {
return value;
};
let (placed, mask) = edge_place(op, value, edge);
let keep = op.not(mask);
let v = op.or(op.and(op.lane_val(current), keep), op.lane_val(placed));
let u = op.or(op.and(op.lane_unk(current), keep), op.lane_unk(placed));
op.pack(v, u, value.width, value.repr, value.signed)
}
fn edge_place<'ctx>(
op: &OpBuilder<'_, 'ctx>,
value: PackedValue<'ctx>,
edge: Edge<'ctx>,
) -> (PackedValue<'ctx>, IntValue<'ctx>) {
let mask = shift_signed(op, edge.valid, edge.delta, true);
let v = op.and(shift_signed(op, op.lane_val(value), edge.delta, true), mask);
let u = op.and(shift_signed(op, op.lane_unk(value), edge.delta, true), mask);
(op.pack(v, u, value.width, value.repr, value.signed), mask)
}
#[derive(Clone, Copy)]
pub(crate) enum LvalueRoot<'ctx> {
Shadow { ptr: PointerValue<'ctx> },
MemRow {
info: MemorySignalInfo,
row: IntValue<'ctx>,
},
Signal { slot: u32 },
Field {
h: IntValue<'ctx>,
slot: sv_sim_sim::objects::FieldSlot,
},
}
#[derive(Clone, Copy)]
pub(crate) enum RootMode<'a> {
Shadow,
Signal(
&'a std::collections::HashMap<
sv_sim_sim::symid::SymbolId,
crate::process_lower::SignalSlot,
>,
),
}
#[derive(Clone, Copy)]
pub(crate) struct Lvalue<'ctx> {
pub root: LvalueRoot<'ctx>,
pub root_width: u32,
pub root_signed: bool,
pub lo: IntValue<'ctx>,
pub width: u32,
pub bad: IntValue<'ctx>,
pub edge: Option<Edge<'ctx>>,
pub pad: Option<Pad<'ctx>>,
}
pub(crate) fn resolve_lvalue<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
mode: RootMode<'_>,
) -> Result<Lvalue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let no = cx.ctx.bool_type().const_zero();
if let Some(inner) = crate::expr::modport_expr_target(&e) {
return resolve_lvalue(cx, inner, mode);
}
if let Some((h, slot, ty)) = crate::classes::class_field_root(cx, e)? {
let width = ty.value_width();
return Ok(Lvalue {
root: LvalueRoot::Field { h, slot },
root_width: width,
root_signed: ty.is_signed(),
lo: op.i_n(width).const_zero(),
width,
bad: no,
edge: None,
pad: None,
});
}
if let Some(r) = crate::vif::picked_lvalue(cx, e, mode) {
return r;
}
match e.kind() {
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => {
let sym = e.referenced_symbol().ok_or_else(|| {
LoweringError::unsupported_at("assignment lvalue unresolved name", &cx.fn_sym)
})?;
let (root, width, signed) = match mode {
RootMode::Shadow => {
let (ptr, width, signed) = *cx.locals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at(
"assignment to a name that is neither a local nor a signal of this \
scope",
&sym,
)
})?;
(LvalueRoot::Shadow { ptr }, width, signed)
}
RootMode::Signal(signals) => {
let sym = if sym.kind() == sv_lang::kinds::SymbolKind::ClockVar {
sym.initializer()
.and_then(|i| i.referenced_symbol())
.unwrap_or(sym)
} else {
sym
};
let info = signals.get(&sym.uid()).copied().ok_or_else(|| {
LoweringError::unsupported_at(
"nonblocking assignment to a process-local variable (only a \
module-level signal has an NBA staging target)",
&sym,
)
})?;
if matches!(
info.class,
crate::process_lower::SignalSlotClass::Memory { .. }
) {
return Err(LoweringError::unsupported_at(
"nonblocking assignment to a whole memory (only element writes \
`mem[i] <= v` are supported)",
&sym,
));
}
let slot = cx
.specify_dest
.get(&sym.uid())
.map_or(info.slot, |r| r.slot);
(LvalueRoot::Signal { slot }, info.width, info.signed)
}
};
Ok(Lvalue {
root,
root_width: width,
root_signed: signed,
lo: op.i_n(width).const_zero(),
width,
bad: no,
edge: None,
pad: None,
})
}
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect => {
let base_expr = e.select_value().ok_or_else(|| {
LoweringError::unsupported_at("select lvalue missing base value", &cx.fn_sym)
})?;
if e.kind() == ExpressionKind::ElementSelect
&& let Some(c) = crate::collections::coll_of_expr(&base_expr)
{
let Some(ptr) = cx.coll_temp.get() else {
return Err(LoweringError::unsupported_at(
"a nonblocking or reference write into part of a queue or dynamic-array \
element (use a blocking assignment)",
&cx.fn_sym,
));
};
return Ok(Lvalue {
root: LvalueRoot::Shadow { ptr },
root_width: c.width,
root_signed: c.elem.is_signed(),
lo: op.i_n(c.width).const_zero(),
width: c.width,
bad: no,
edge: None,
pad: None,
});
}
if e.kind() == ExpressionKind::RangeSelect
&& crate::collections::coll_of_expr(&base_expr).is_some()
{
return Err(LoweringError::unsupported_at(
"a slice of a queue or dynamic array as an assignment target (assign the \
elements individually)",
&cx.fn_sym,
));
}
if e.kind() == ExpressionKind::ElementSelect
&& let Some(r) = crate::arrays::resolve_element(cx, e)?
{
let bad = cx.b.build_not(r.valid, "arr_lv_bad").unwrap();
let root = match r.backing {
crate::arrays::Backing::Memory(info) => LvalueRoot::MemRow { info, row: r.row },
crate::arrays::Backing::Local(base) => LvalueRoot::Shadow {
ptr: crate::arrays::local_row_ptr(cx, base, r.row, r.width),
},
};
return Ok(Lvalue {
root,
root_width: r.width,
root_signed: r.signed,
lo: op.i_n(r.width).const_zero(),
width: r.width,
bad,
edge: None,
pad: None,
});
}
let inner = resolve_lvalue(cx, base_expr, mode)?;
no_pad_base(cx, &inner)?;
let geom = geom_of(base_expr.expr_type(), inner.width);
let w = select_window(cx, e, inner.width, geom)?;
Ok(nest(cx, inner, w))
}
ExpressionKind::MemberAccess => {
let base_expr = e.operand().ok_or_else(|| {
LoweringError::unsupported_at("member-access lvalue missing base", &cx.fn_sym)
})?;
let member = e.member_symbol().ok_or_else(|| {
LoweringError::unsupported_at("member-access lvalue missing member", &cx.fn_sym)
})?;
let inner = resolve_lvalue(cx, base_expr, mode)?;
no_pad_base(cx, &inner)?;
let Some((off, fw)) = field_window(base_expr.expr_type(), &member)
.filter(|(off, fw)| *fw > 0 && fw + off <= u64::from(inner.width))
else {
return Err(LoweringError::unsupported_at(
"assignment to a struct/union field that is not a slice of its base's \
stored bits",
&cx.fn_sym,
));
};
let mut bad = no;
if let Some(base_ty) = base_expr.expr_type().filter(sv_lang::Type::is_tagged_union) {
let union_value = if matches!(mode, RootMode::Signal(_)) {
lower_expr(cx, base_expr)?.into_packed(
"a tagged union nonblocking target must read as an integral value",
&cx.fn_sym,
)?
} else {
let cur = load_root(cx, &inner)?;
read_window(
&op,
cur,
Window {
lo: inner.lo,
width: inner.width,
bad: inner.bad,
edge: inner.edge,
pad: None,
},
)
};
if let Some(miss) = crate::tagged::checked_member_window(
cx,
&base_ty,
union_value,
&member,
inner.bad,
e.source_range().start,
)? {
bad = miss;
}
}
let w = Window {
lo: op.i_n(inner.width).const_int(off, false),
width: fw as u32,
bad,
edge: None,
pad: None,
};
Ok(nest(cx, inner, w))
}
other => Err(LoweringError::unsupported_at(
&format!("assignment to a {other:?} lvalue is not supported"),
&cx.fn_sym,
)),
}
}
pub(crate) fn field_window(
base_ty: Option<sv_lang::Type<'_>>,
member: &sv_lang::Symbol<'_>,
) -> Option<(u64, u64)> {
use sv_sim_sim::state::storage;
let base_ty = base_ty?;
if base_ty.is_class() {
return None;
}
if storage::is_unpacked_aggregate(&base_ty) {
return storage::field_slice(&base_ty, member.field_index());
}
let field_ty = member.value_type()?;
field_ty
.is_integral()
.then(|| (member.field_bit_offset(), field_ty.bit_width()))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct RefWindow {
pub row: Option<u64>,
pub lo: u64,
pub width: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RefActualError {
NonConstant,
Packed,
Unsupported(String),
}
pub fn ref_actual_window<'d>(
e: sv_lang::Expression<'d>,
) -> Result<(sv_lang::Expression<'d>, Option<RefWindow>), RefActualError> {
use crate::types::TypeWidth;
match e.kind() {
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue => Ok((e, None)),
ExpressionKind::MemberAccess => {
let base = e.operand().ok_or_else(|| {
RefActualError::Unsupported("a member access with no base".to_owned())
})?;
let member = e.member_symbol().ok_or_else(|| {
RefActualError::Unsupported("a member access with no member".to_owned())
})?;
let base_ty = base.expr_type();
if !base_ty
.as_ref()
.is_some_and(sv_sim_sim::state::storage::is_unpacked_aggregate)
{
return Err(RefActualError::Packed);
}
if member.value_type().is_some_and(|t| t.is_unpacked_array()) {
return Err(RefActualError::Unsupported(
"a struct member that is itself an unpacked array".to_owned(),
));
}
let (root, inner) = ref_actual_window(base)?;
let Some((off, width)) = field_window(base_ty, &member).filter(|(_, w)| *w > 0) else {
return Err(RefActualError::Unsupported(
"a struct member that is not a slice of its struct's stored bits".to_owned(),
));
};
let window = match inner {
Some(w) => RefWindow {
row: w.row,
lo: w.lo + off,
width,
},
None => RefWindow {
row: None,
lo: off,
width,
},
};
Ok((root, Some(window)))
}
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect => {
let mut chain = Vec::new();
let mut cur = e;
while matches!(
cur.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
let base = cur.select_value().ok_or_else(|| {
RefActualError::Unsupported("a select with no base".to_owned())
})?;
if !base.expr_type().is_some_and(|t| t.is_unpacked_array()) {
return Err(RefActualError::Packed);
}
chain.push(cur);
cur = base;
}
chain.reverse();
if !matches!(
cur.kind(),
ExpressionKind::NamedValue | ExpressionKind::HierarchicalValue
) {
return Err(RefActualError::Unsupported(
"an element of an array that is itself a member or another select".to_owned(),
));
}
let shape = cur
.expr_type()
.as_ref()
.and_then(crate::arrays::array_shape)
.ok_or_else(|| {
RefActualError::Unsupported("a dynamically sized array element".to_owned())
})?;
if chain.len() != shape.dims.len() {
return Err(RefActualError::Unsupported(
"a sub-array of a multi-dimensional unpacked array (name one element)"
.to_owned(),
));
}
let mut row = 0u64;
for (k, sel) in chain.iter().enumerate() {
if sel.kind() != ExpressionKind::ElementSelect {
return Err(RefActualError::Unsupported(
"a slice of an unpacked array".to_owned(),
));
}
let idx = sel
.selector()
.and_then(|x| x.constant_value())
.and_then(|c| c.as_i64())
.ok_or(RefActualError::NonConstant)?;
let (a, b) = shape.dims[k];
let low = a.min(b);
let size = (a - b).unsigned_abs() + 1;
let off = idx - low;
if off < 0 || off as u64 >= size {
return Err(RefActualError::Unsupported(format!(
"index {idx} is outside the array's bounds [{a}:{b}]"
)));
}
row += off as u64 * shape.stride(k);
}
let elem = shape.elem;
if !(elem.is_integral()
|| elem.is_floating()
|| sv_sim_sim::state::storage::is_unpacked_aggregate(&elem))
{
return Err(RefActualError::Unsupported(
"an array element that is a string, class, chandle or collection".to_owned(),
));
}
Ok((
cur,
Some(RefWindow {
row: Some(row),
lo: 0,
width: u64::from(elem.value_width()),
}),
))
}
other => Err(RefActualError::Unsupported(format!(
"a {other:?} expression is not a variable"
))),
}
}
fn no_pad_base(cx: &LowerCtx<'_, '_, '_>, inner: &Lvalue<'_>) -> Result<(), LoweringError> {
if inner.pad.is_some() {
return Err(LoweringError::unsupported_at(
"a select or member of a part-select that addresses bits outside its vector",
&cx.fn_sym,
));
}
Ok(())
}
fn nest<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, inner: Lvalue<'ctx>, w: Window<'ctx>) -> Lvalue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let rel = if inner.width == inner.root_width {
w.lo
} else {
cx.b.build_int_z_extend(w.lo, op.i_n(inner.root_width), "lv_rel_lo")
.unwrap()
};
let lo = cx.b.build_int_add(inner.lo, rel, "lv_lo").unwrap();
let bad = cx.b.build_or(inner.bad, w.bad, "lv_bad").unwrap();
let edge = match (inner.edge, w.edge) {
(None, None) => None,
(inner_edge, outer_edge) => {
let i64t = cx.ctx.i64_type();
let delta = match (inner_edge, outer_edge) {
(Some(i), Some(o)) => cx.b.build_int_add(i.delta, o.delta, "lv_delta").unwrap(),
(Some(e), None) | (None, Some(e)) => e.delta,
(None, None) => i64t.const_zero(),
};
let valid = match (inner_edge, outer_edge) {
(None, Some(o)) => o.valid,
(Some(_), outer) => {
let lo64 =
cx.b.build_int_cast_sign_flag(lo, i64t, false, "lv_lo64")
.unwrap();
let start = cx.b.build_int_add(lo64, delta, "lv_start").unwrap();
let root = range_mask(&op, start, w.width, inner.root_width);
outer.map_or(root, |o| op.and(o.valid, root))
}
(None, None) => op.i_n(w.width).const_all_ones(),
};
Some(Edge { delta, valid })
}
};
Lvalue {
lo,
width: w.width,
bad,
edge,
pad: w.pad,
..inner
}
}
pub(crate) fn load_root<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
lv: &Lvalue<'ctx>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let llvm = match lv.root {
LvalueRoot::Shadow { ptr } => {
crate::collections::elem_ref_refresh(cx, ptr);
cx.b.build_load(op.packed_type(lv.root_width, cx.repr), ptr, "lv_root")
.unwrap()
.into_int_value()
}
LvalueRoot::Signal { .. } => {
return Err(LoweringError::unsupported_at(
"internal: a nonblocking target root has no loadable shadow",
&cx.fn_sym,
));
}
LvalueRoot::Field { h, slot } => {
return Ok(
crate::classes::field_get_call(cx, h, slot, lv.root_width, lv.root_signed).0,
);
}
LvalueRoot::MemRow { info, row } => {
let accessor = mem_accessor(cx)?;
let safe_row =
cx.b.build_select(lv.bad, cx.ctx.i64_type().const_zero(), row, "lv_safe_row")
.unwrap()
.into_int_value();
crate::accessor::build_get_mem_word(
cx,
accessor.fns,
accessor.handle,
info.slot,
safe_row,
info.elem_width,
)
}
};
Ok(PackedValue {
llvm,
width: lv.root_width,
repr: cx.repr,
signed: lv.root_signed,
})
}
fn mem_accessor<'ctx, 'm>(
cx: &LowerCtx<'ctx, '_, 'm>,
) -> Result<crate::expr::MemAccessorCtx<'ctx, 'm>, LoweringError> {
cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at(
"memory write reached with no accessor context wired (LowerCtx::mem_accessor)",
&cx.fn_sym,
)
})
}
fn edge_value<'ctx>(
op: &OpBuilder<'_, 'ctx>,
root: PackedValue<'ctx>,
lv: &Lvalue<'ctx>,
value: PackedValue<'ctx>,
) -> PackedValue<'ctx> {
if lv.edge.is_none() {
return value;
}
let current = op.part_select4(root, lv.lo, lv.bad, lv.width);
merge_edge(op, current, lv.edge, value)
}
pub(crate) fn store_lvalue<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
lv: &Lvalue<'ctx>,
value: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
match lv.root {
LvalueRoot::Shadow { ptr } => {
let cur = load_root(cx, lv)?;
let value = edge_value(&op, cur, lv, value);
let merged = op.insert4(cur, lv.lo, lv.bad, value);
let chosen = op.select(lv.bad, cur, merged);
cx.b.build_store(ptr, chosen.llvm).unwrap();
crate::expr::write_through_store(cx, ptr);
}
LvalueRoot::Signal { .. } => {
return Err(LoweringError::unsupported_at(
"internal: a blocking write resolved to a nonblocking target root",
&cx.fn_sym,
));
}
LvalueRoot::Field { h, slot } => {
let new = if lv.width == lv.root_width {
value
} else {
let cur = load_root(cx, lv)?;
let merged = op.insert4(cur, lv.lo, lv.bad, value);
op.select(lv.bad, cur, merged)
};
crate::classes::field_set_call(cx, h, slot, new);
}
LvalueRoot::MemRow { info, row } => {
let accessor = mem_accessor(cx)?;
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let store_bb = cx.ctx.append_basic_block(func, "lv_mem_store");
let done_bb = cx.ctx.append_basic_block(func, "lv_mem_done");
cx.b.build_conditional_branch(lv.bad, done_bb, store_bb)
.unwrap();
cx.b.position_at_end(store_bb);
let new_row = if lv.width == lv.root_width {
value
} else {
let cur = load_root(cx, lv)?;
let value = edge_value(&op, cur, lv, value);
op.insert4(cur, lv.lo, lv.bad, value)
};
crate::accessor::build_set_mem_word(
cx,
accessor.fns,
accessor.handle,
info.slot,
row,
new_row.llvm,
info.elem_width,
);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
}
}
Ok(())
}
pub(crate) fn lvalue_width<'d>(
cx: &LowerCtx<'_, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<u32, LoweringError> {
e.expr_type()
.map(|t| t.value_width())
.filter(|w| *w > 0)
.ok_or_else(|| {
LoweringError::unsupported_at(
"concatenation lvalue operand has no integral width",
&cx.fn_sym,
)
})
}
pub(crate) fn lower_assign_to<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
if crate::strings::assign_char(cx, lhs, value)? {
return Ok(());
}
for_each_target(cx, lhs, value, RootMode::Shadow, &mut |lv, v| {
store_lvalue(cx, &lv, v)
})
}
pub(crate) fn lower_assign_releasing<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: PackedValue<'ctx>,
release: &mut dyn FnMut(PackedValue<'ctx>),
) -> Result<(), LoweringError> {
for_each_target(cx, lhs, value, RootMode::Shadow, &mut |lv, v| {
let old = read_target(cx, &lv)?;
release(old);
store_lvalue(cx, &lv, v)
})
}
pub(crate) fn lower_modify_target<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
f: &mut dyn FnMut(PackedValue<'ctx>) -> Result<PackedValue<'ctx>, LoweringError>,
) -> Result<Option<PackedValue<'ctx>>, LoweringError> {
if !matches!(
lhs.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect | ExpressionKind::MemberAccess
) {
return Ok(None);
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if let Some(lvs) = crate::vif::nested_lvalues(cx, lhs, RootMode::Shadow) {
let lvs = lvs?;
if lvs.iter().any(|lv| lv.pad.is_some()) {
return Ok(None);
}
let mut old: Option<PackedValue<'ctx>> = None;
for lv in &lvs {
let v = read_target(cx, lv)?;
old = Some(match old {
Some(prev) => op.select(lv.bad, prev, v),
None => v,
});
}
let old = old.ok_or_else(|| {
LoweringError::unsupported_at("a virtual interface member target", &cx.fn_sym)
})?;
let new = fit_width(&op, f(old)?, lvs[0].width);
for lv in &lvs {
store_lvalue(cx, lv, new)?;
}
return Ok(Some(new));
}
let lv = resolve_lvalue(cx, lhs, RootMode::Shadow)?;
if lv.pad.is_some() {
return Ok(None);
}
let old = read_target(cx, &lv)?;
let new = fit_width(&op, f(old)?, lv.width);
store_lvalue(cx, &lv, new)?;
Ok(Some(new))
}
fn read_target<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
lv: &Lvalue<'ctx>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let root = load_root(cx, lv)?;
let window = Window {
lo: lv.lo,
width: lv.width,
bad: lv.bad,
edge: lv.edge,
pad: lv.pad,
};
let read = read_window(&op, root, window);
let zero_lane = op.i_n(read.width).const_zero();
let zero = op.pack(zero_lane, zero_lane, read.width, cx.repr, read.signed);
Ok(op.select(lv.bad, zero, read))
}
pub(crate) fn lower_nba_to<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<
sv_sim_sim::symid::SymbolId,
crate::process_lower::SignalSlot,
>,
lhs: sv_lang::Expression<'d>,
value: PackedValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
for_each_target(cx, lhs, value, RootMode::Signal(signals), &mut |lv, v| {
stage_nba_masked(cx, handle, &lv, v, delay)
})
}
pub(crate) fn mem_row_lvalue<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
info: crate::expr::MemorySignalInfo,
row: IntValue<'ctx>,
width: u32,
signed: bool,
) -> Lvalue<'ctx> {
Lvalue {
root: LvalueRoot::MemRow { info, row },
root_width: width,
root_signed: signed,
lo: cx.ctx.i32_type().const_zero(),
width,
bad: cx.ctx.bool_type().const_zero(),
edge: None,
pad: None,
}
}
pub(crate) fn stage_nba_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
info: crate::expr::MemorySignalInfo,
row: IntValue<'ctx>,
value: PackedValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let lv = mem_row_lvalue(cx, info, row, value.width, value.signed);
stage_nba_masked(cx, handle, &lv, value, delay)
}
fn stage_nba_masked<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
lv: &Lvalue<'ctx>,
value: PackedValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let (slot, row, has_row) = match lv.root {
LvalueRoot::Signal { slot } => (slot, cx.ctx.i64_type().const_zero(), 0u64),
LvalueRoot::MemRow { info, row } => (info.slot, row, 1u64),
LvalueRoot::Field { h, slot } => {
return stage_nba_field(cx, h, slot, lv, value, delay);
}
LvalueRoot::Shadow { .. } => {
return Err(LoweringError::unsupported_at(
"internal: a nonblocking target resolved to a shadow root",
&cx.fn_sym,
));
}
};
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let stage_bb = cx.ctx.append_basic_block(func, "nba_stage");
let done_bb = cx.ctx.append_basic_block(func, "nba_done");
cx.b.build_conditional_branch(lv.bad, done_bb, stage_bb)
.unwrap();
cx.b.position_at_end(stage_bb);
let w = lv.root_width;
let nwords = w.div_ceil(64);
let store_ty = op.i_n(nwords * 64);
let place = |lane: IntValue<'ctx>, name: &str| -> PointerValue<'ctx> {
let wide =
cx.b.build_int_z_extend_or_bit_cast(lane, store_ty, name)
.unwrap();
let lo =
cx.b.build_int_z_extend_or_bit_cast(lv.lo, store_ty, "nba_lo")
.unwrap();
let shifted = cx.b.build_left_shift(wide, lo, name).unwrap();
let ptr = crate::expr::entry_alloca(cx, store_ty, name);
cx.b.build_store(ptr, shifted).unwrap();
ptr
};
let (value, mask) = match lv.edge {
Some(edge) => edge_place(&op, value, edge),
None => (value, op.i_n(value.width).const_all_ones()),
};
let val_ptr = place(op.lane_val(value), "nba_val");
let unk_ptr = place(op.lane_unk(value), "nba_unk");
let mask_ptr = place(mask, "nba_mask");
let i32_ty = cx.ctx.i32_type();
let f = rt_nba_masked_fn(cx);
cx.b.build_call(
f,
&[
handle.into(),
i32_ty.const_int(u64::from(slot), false).into(),
row.into(),
i32_ty.const_int(has_row, false).into(),
val_ptr.into(),
unk_ptr.into(),
mask_ptr.into(),
i32_ty.const_int(u64::from(nwords), false).into(),
i32_ty.const_int(u64::from(w), false).into(),
delay.into(),
],
"rt_nba_masked_call",
)
.unwrap();
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
}
fn stage_nba_field<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
h: IntValue<'ctx>,
slot: sv_sim_sim::objects::FieldSlot,
lv: &Lvalue<'ctx>,
value: PackedValue<'ctx>,
delay: IntValue<'ctx>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let func = cx.b.get_insert_block().unwrap().get_parent().unwrap();
let stage_bb = cx.ctx.append_basic_block(func, "nba_fld_stage");
let done_bb = cx.ctx.append_basic_block(func, "nba_fld_done");
cx.b.build_conditional_branch(lv.bad, done_bb, stage_bb)
.unwrap();
cx.b.position_at_end(stage_bb);
let (value, mask) = match lv.edge {
Some(edge) => edge_place(&op, value, edge),
None => (value, op.i_n(value.width).const_all_ones()),
};
let zero = PackedValue {
llvm: op.packed_type(lv.root_width, cx.repr).const_zero(),
width: lv.root_width,
repr: cx.repr,
signed: lv.root_signed,
};
let no = cx.ctx.bool_type().const_zero();
let val_root = op.insert4(zero, lv.lo, no, value);
let mask_pv = op.pack(mask, mask, value.width, value.repr, false);
let mask_root = op.insert4(zero, lv.lo, no, mask_pv);
let i64t = cx.ctx.i64_type();
let words = slot.words as u32;
let buf = crate::expr::entry_alloca(cx, i64t.array_type(2 * words + 1), "nba_fld_buf");
crate::classes::store_packed_words(cx, buf, val_root, words);
let mask_ptr = unsafe {
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(u64::from(words), false)],
"nba_fld_mask",
)
.unwrap()
};
crate::classes::store_packed_words(cx, mask_ptr, mask_root, words);
let delay_ptr = unsafe {
cx.b.build_in_bounds_gep(
i64t,
buf,
&[i64t.const_int(u64::from(2 * words), false)],
"nba_fld_delay",
)
.unwrap()
};
cx.b.build_store(delay_ptr, delay).unwrap();
crate::classes::obj_call(
cx,
sv_sim_runtime::objects_rt::ObjOp::FieldNba,
h,
i64t.const_int(slot.offset as u64, false),
i64t.const_int(slot.words as u64, false),
buf,
);
cx.b.build_unconditional_branch(done_bb).unwrap();
cx.b.position_at_end(done_bb);
Ok(())
}
pub(crate) fn rt_nba_masked_fn<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
) -> inkwell::values::FunctionValue<'ctx> {
let name = crate::process_lower::SV_RT_NBA_MASKED_SYMBOL;
cx.module.get_function(name).unwrap_or_else(|| {
let ptr = cx.ctx.ptr_type(inkwell::AddressSpace::default());
let i32_ty = cx.ctx.i32_type();
let i64_ty = cx.ctx.i64_type();
let fn_ty = cx.ctx.void_type().fn_type(
&[
ptr.into(),
i32_ty.into(),
i64_ty.into(),
i32_ty.into(),
ptr.into(),
ptr.into(),
ptr.into(),
i32_ty.into(),
i32_ty.into(),
i64_ty.into(),
],
false,
);
cx.module
.add_function(name, fn_ty, Some(inkwell::module::Linkage::External))
})
}
fn for_each_target<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: sv_lang::Expression<'d>,
value: PackedValue<'ctx>,
mode: RootMode<'_>,
write: &mut dyn FnMut(Lvalue<'ctx>, PackedValue<'ctx>) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if lhs.kind() == ExpressionKind::Streaming {
return crate::stream::unpack_stream(
cx,
lhs,
value,
&mut |operand, with, bits| {
if let Some(w) = with {
if matches!(mode, RootMode::Signal(_)) {
return crate::stream_with::unpack_with(
cx,
operand,
w,
bits,
&mut |row, v| {
let crate::arrays::Backing::Memory(info) = row.backing else {
return Err(LoweringError::unsupported_at(
"a nonblocking assignment to a local array",
&cx.fn_sym,
));
};
crate::arrays::guarded(cx, row.valid, || {
write(mem_row_lvalue(cx, info, row.base, v.width, v.signed), v)
})
},
);
}
return crate::stream_with::unpack(cx, operand, w, bits);
}
if operand.expr_type().is_some_and(|t| {
t.is_unpacked_array() && !sv_sim_sim::state::storage::is_collection(&t)
}) {
if matches!(mode, RootMode::Signal(_)) {
return crate::arrays::write_array_rows_from_packed(
cx, operand, bits, write,
);
}
return crate::arrays::store_array_from_packed(cx, operand, bits);
}
for_each_target(cx, operand, bits, mode, write)
},
None,
);
}
if lhs.kind() == ExpressionKind::Concatenation {
let parts = lhs.operands();
let widths = parts
.iter()
.map(|p| lvalue_width(cx, *p))
.collect::<Result<Vec<_>, _>>()?;
let total: u32 = widths.iter().sum();
if total != value.width {
return Err(LoweringError::unsupported_at(
&format!(
"concatenation lvalue is {total} bits but the assigned value is {} bits",
value.width
),
&cx.fn_sym,
));
}
let mut offset = 0u32;
for (part, w) in parts.iter().zip(&widths).rev() {
let slice = op.part_select4(
value,
op.i_n(value.width).const_int(u64::from(offset), false),
cx.ctx.bool_type().const_zero(),
*w,
);
for_each_target(cx, *part, slice, mode, write)?;
offset += w;
}
return Ok(());
}
if let Some(lvs) = crate::vif::lvalues(cx, lhs, mode) {
for lv in lvs? {
write(lv, fit_width(&op, value, lv.width))?;
}
return Ok(());
}
if let Some(lvs) = crate::vif::nested_lvalues(cx, lhs, mode) {
for lv in lvs? {
write_one(cx, lv, value, write)?;
}
return Ok(());
}
let lv = resolve_lvalue(cx, lhs, mode)?;
write_one(cx, lv, value, write)
}
fn write_one<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lv: Lvalue<'ctx>,
value: PackedValue<'ctx>,
write: &mut dyn FnMut(Lvalue<'ctx>, PackedValue<'ctx>) -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let addressed = lv.pad.map_or(lv.width, |p| p.total);
if value.width != addressed {
return Err(LoweringError::unsupported_at(
&format!(
"assigned value is {} bits but its target addresses {} bits (expected \
sv-lang's assignment-context Conversion to have equalized them)",
value.width, addressed
),
&cx.fn_sym,
));
}
let mut lv = lv;
let value = match lv.pad {
Some(Pad {
total,
dynamic: Some(d),
..
}) => {
let ty = op.i_n(lv.width);
let slice = |lane: IntValue<'ctx>| {
let moved = shift_signed(&op, lane, d, false);
cx.b.build_int_truncate(moved, ty, "pad_slice").unwrap()
};
let valid = slice(op.i_n(total).const_all_ones());
lv.edge = Some(Edge {
delta: cx.ctx.i64_type().const_zero(),
valid,
});
op.pack(
slice(op.lane_val(value)),
slice(op.lane_unk(value)),
lv.width,
value.repr,
value.signed,
)
}
Some(pad) => op.part_select4(
value,
op.i_n(value.width).const_int(u64::from(pad.shift), false),
cx.ctx.bool_type().const_zero(),
lv.width,
),
None => value,
};
write(lv, value)
}
pub(crate) fn fit_width<'ctx>(
op: &OpBuilder<'_, 'ctx>,
v: PackedValue<'ctx>,
width: u32,
) -> PackedValue<'ctx> {
if v.width == width {
return v;
}
let ty = op.i_n(width);
let lane = |x: IntValue<'ctx>, name: &str| -> IntValue<'ctx> {
if width < v.width {
op.b.build_int_truncate(x, ty, name).unwrap()
} else if v.signed {
op.b.build_int_s_extend(x, ty, name).unwrap()
} else {
op.b.build_int_z_extend(x, ty, name).unwrap()
}
};
let val = lane(op.lane_val(v), "fit_v");
let unk = lane(op.lane_unk(v), "fit_u");
op.pack(val, unk, width, v.repr, v.signed)
}
pub(crate) fn store_whole<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
sym: &sv_lang::Symbol<'d>,
value: PackedValue<'ctx>,
) -> Result<(), LoweringError> {
let (ptr, width, _) = *cx.locals.get(&sym.uid()).ok_or_else(|| {
LoweringError::unsupported_at("assignment target has no shadow copy", sym)
})?;
if value.width != width {
return Err(LoweringError::unsupported_at(
&format!(
"assigned value is {} bits for a {width}-bit target",
value.width
),
sym,
));
}
cx.b.build_store(ptr, value.llvm).unwrap();
crate::collections::elem_ref_flush(cx, ptr);
Ok(())
}
pub fn lvalue_root_symbols<'d>(e: sv_lang::Expression<'d>) -> Vec<sv_lang::Symbol<'d>> {
let mut out = Vec::new();
collect_roots(e, &mut out);
out
}
fn collect_roots<'d>(e: sv_lang::Expression<'d>, out: &mut Vec<sv_lang::Symbol<'d>>) {
let kind = e.kind();
if let Some(inner) = crate::expr::modport_expr_target(&e) {
collect_roots(inner, out);
} else if sv_sim_sim::symid::is_value_name(kind) {
out.extend(e.referenced_symbol());
} else if matches!(
kind,
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
if let Some(b) = e.select_value() {
collect_roots(b, out);
}
} else if kind == ExpressionKind::MemberAccess {
if let Some(b) = e.operand() {
collect_roots(b, out);
}
} else if kind == ExpressionKind::Concatenation {
for p in e.operands() {
collect_roots(p, out);
}
}
}
#[cfg(test)]
mod tests {
use super::SelectGeom;
#[test]
fn positions_follow_the_declared_direction() {
let desc = SelectGeom {
left: 8,
right: 1,
elem_width: 1,
};
assert_eq!(desc.pos(1), 0);
assert_eq!(desc.pos(8), 7);
let asc = SelectGeom {
left: 0,
right: 7,
elem_width: 1,
};
assert_eq!(asc.pos(7), 0);
assert_eq!(asc.pos(0), 7);
assert_eq!(asc.elems(), 8);
}
}