use std::sync::atomic::{AtomicBool, Ordering};
use inkwell::AddressSpace;
use inkwell::module::Linkage;
use inkwell::values::{FunctionValue, IntValue, PointerValue};
use sv_lang::kinds::{ExpressionKind, SymbolKind};
use vj_sim::symid::SymbolUid;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, lower_expr};
use crate::ops::OpBuilder;
use crate::process_lower::{SignalSlot, SignalSlotClass};
use crate::value::PackedValue;
pub const SV_RT_FORCE_SYMBOL: &str = "sv_rt_force";
pub const SV_RT_RELEASE_SYMBOL: &str = "sv_rt_release";
pub const SV_RT_FORCE_ACTIVE_SYMBOL: &str = "sv_rt_force_active";
pub const SV_RT_FORCE_ROW_SYMBOL: &str = "sv_rt_force_row";
static FORCES_LOWERED: AtomicBool = AtomicBool::new(false);
pub fn note_design_forces() {
FORCES_LOWERED.store(true, Ordering::SeqCst);
}
pub(crate) fn reload_after_write() -> bool {
FORCES_LOWERED.load(Ordering::SeqCst)
}
pub fn contains_force(node: sv_lang::SemNode<'_>) -> bool {
use sv_lang::kinds::StatementKind;
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if n.as_statement().is_some_and(|s| {
matches!(
s.kind(),
StatementKind::ProceduralAssign | StatementKind::ProceduralDeassign
)
}) {
return true;
}
stack.extend(vj_sim::process::cfg::live_children(&n));
}
false
}
pub fn force_drivers<'d>(node: sv_lang::SemNode<'d>) -> Vec<(u64, sv_lang::Expression<'d>)> {
use sv_lang::kinds::StatementKind;
let mut out = Vec::new();
let mut stack = vec![node];
while let Some(n) = stack.pop() {
if let Some(s) = n.as_statement()
&& s.kind() == StatementKind::ProceduralAssign
&& let Some(assign) = s.expressions().into_iter().next()
&& assign.right().is_some_and(|r| r.constant_value().is_none())
{
out.push((site_of(&s), assign));
}
stack.extend(vj_sim::process::cfg::live_children(&n));
}
out
}
fn site_of(stmt: &sv_lang::Statement<'_>) -> u64 {
let offset = stmt.source_range().start.offset;
if stmt.kind() == sv_lang::kinds::StatementKind::ProceduralAssign
&& !stmt.procedural_assign_is_force()
{
offset | PROCEDURAL_SITE_BIT
} else {
offset
}
}
pub const PROCEDURAL_SITE_BIT: u64 = 1 << 62;
pub(crate) fn target<'d>(
cx: &LowerCtx<'_, 'd, '_>,
lhs: sv_lang::Expression<'d>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, SignalSlot>,
) -> Result<(SignalSlot, u32, u32, bool, sv_lang::Symbol<'d>), LoweringError> {
let bad =
|why: &str| LoweringError::unsupported_at(&format!("force/release of {why}"), &cx.fn_sym);
if lhs.kind() == ExpressionKind::MemberAccess {
let (mut cur, mut lo, mut width) = (lhs, 0u64, 0u64);
while cur.kind() == ExpressionKind::MemberAccess {
let inner = cur.operand().ok_or_else(|| bad("a malformed member"))?;
let member = cur
.member_symbol()
.ok_or_else(|| bad("a malformed member"))?;
if !inner.expr_type().is_some_and(|t| t.is_integral()) {
return Err(bad("a member of an unpacked struct or a class"));
}
let (off, w) = crate::select::field_window(inner.expr_type(), &member)
.ok_or_else(|| bad("a member that is not a slice of its struct"))?;
if width == 0 {
width = w;
}
lo += off;
cur = inner;
}
if !vj_sim::symid::is_value_name(cur.kind()) {
return Err(bad("a member of something other than a signal"));
}
let sym = cur
.referenced_symbol()
.ok_or_else(|| bad("an unresolved name"))?;
let slot = *signals
.get(&sym.uid())
.ok_or_else(|| bad("a non-module signal"))?;
if matches!(slot.class, SignalSlotClass::Memory { .. }) {
return Err(bad("a member of an unpacked array"));
}
return Ok((
slot,
lo as u32,
width as u32,
continuously_driven(&sym),
sym,
));
}
let (base, window) = if vj_sim::symid::is_value_name(lhs.kind()) {
(lhs, None)
} else if matches!(
lhs.kind(),
ExpressionKind::ElementSelect | ExpressionKind::RangeSelect
) {
let base = lhs
.select_value()
.ok_or_else(|| bad("a malformed select"))?;
(base, Some(lhs))
} else {
return Err(bad("a target that is not a signal or a select of one"));
};
if !vj_sim::symid::is_value_name(base.kind()) {
return Err(bad("a nested select"));
}
let sym = base
.referenced_symbol()
.ok_or_else(|| bad("an unresolved name"))?;
let slot = *signals
.get(&sym.uid())
.ok_or_else(|| bad("a non-module signal"))?;
if matches!(slot.class, SignalSlotClass::Memory { .. }) {
return Err(bad(
"a whole unpacked array or a select inside one of its elements",
));
}
let (lo, width) = match window {
None => (0, slot.width),
Some(sel) => {
let geom = crate::select::geom_of(base.expr_type(), slot.width);
let w = crate::select::select_window(cx, sel, slot.width, geom)?;
let lo =
cx.b.build_int_cast_sign_flag(w.lo, cx.ctx.i64_type(), false, "force_lo")
.unwrap()
.get_zero_extended_constant()
.ok_or_else(|| bad("a select with a variable index"))?;
let whole = w.edge.is_none_or(|e| {
let full = if w.width >= 64 {
u64::MAX
} else {
(1u64 << w.width) - 1
};
e.delta.get_zero_extended_constant() == Some(0)
&& w.width <= 64
&& e.valid.get_zero_extended_constant() == Some(full)
});
if w.bad.get_zero_extended_constant() != Some(0) || !whole {
return Err(bad("an out-of-range select"));
}
(lo as u32, w.width)
}
};
Ok((slot, lo, width, continuously_driven(&sym), sym))
}
pub(crate) struct RowTarget {
pub slot: u32,
pub row: u64,
pub width: u32,
pub is_net: bool,
}
pub(crate) fn row_target<'d>(
cx: &LowerCtx<'_, 'd, '_>,
lhs: sv_lang::Expression<'d>,
) -> Result<Option<RowTarget>, LoweringError> {
if lhs.kind() != ExpressionKind::ElementSelect {
return Ok(None);
}
let Some(elem) = crate::arrays::resolve_element(cx, lhs)? else {
return Ok(None);
};
let crate::arrays::Backing::Memory(info) = elem.backing else {
return Ok(None);
};
let bad =
|why: &str| LoweringError::unsupported_at(&format!("force/release of {why}"), &cx.fn_sym);
let row = elem
.row
.get_zero_extended_constant()
.zip(elem.valid.get_zero_extended_constant())
.filter(|&(_, valid)| valid == 1)
.map(|(row, _)| row)
.ok_or_else(|| bad("an unpacked-array element with a variable or out-of-range index"))?;
let mut root = lhs;
while !vj_sim::symid::is_value_name(root.kind()) {
root = root
.select_value()
.ok_or_else(|| bad("a malformed element select"))?;
}
let is_net = root
.referenced_symbol()
.is_some_and(|sym| continuously_driven(&sym));
Ok(Some(RowTarget {
slot: info.slot,
row,
width: elem.width,
is_net,
}))
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_force_row<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
t: &RowTarget,
v: Option<PackedValue<'ctx>>,
flags: u32,
site: u64,
) {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let c = cx.ctx;
let (i32t, i64t, ptr) = (
c.i32_type(),
c.i64_type(),
c.ptr_type(AddressSpace::default()),
);
let f = declare(
cx,
SV_RT_FORCE_ROW_SYMBOL,
c.void_type().fn_type(
&[
ptr.into(),
i32t.into(),
i64t.into(),
i32t.into(),
i32t.into(),
ptr.into(),
ptr.into(),
i32t.into(),
i64t.into(),
],
false,
),
);
let (val, unk) = match v {
Some(v) => (
lane_words(cx, op.lane_val(v), t.width),
lane_words(cx, op.lane_unk(v), t.width),
),
None => (ptr.const_null(), ptr.const_null()),
};
cx.b.build_call(
f,
&[
handle.into(),
i32t.const_int(u64::from(t.slot), false).into(),
i64t.const_int(t.row, false).into(),
i32t.const_zero().into(),
i32t.const_int(u64::from(t.width), false).into(),
val.into(),
unk.into(),
i32t.const_int(u64::from(flags | u32::from(t.is_net)), false)
.into(),
i64t.const_int(site, false).into(),
],
"rt_force_row_call",
)
.unwrap();
}
pub fn continuously_driven(sym: &sv_lang::Symbol<'_>) -> bool {
if sym.kind() == SymbolKind::Net {
return true;
}
let mut scope = sym.parent();
while let Some(s) = scope {
if s.kind() == SymbolKind::InstanceBody {
break;
}
scope = s.parent();
}
let Some(body) = scope else {
return false;
};
let drives = |e: Option<sv_lang::Expression<'_>>| {
e.and_then(|e| e.left())
.is_some_and(|lhs| crate::select::lvalue_root_symbols(lhs).contains(sym))
};
vj_sim::netlist::module_items(&body).into_iter().any(|m| {
let kind = m.kind();
if kind == SymbolKind::ContinuousAssign {
drives(m.continuous_assign_assignment())
} else if kind == SymbolKind::Port {
m.port_internal_symbol().as_ref() == Some(sym)
&& m.port_direction() != sv_lang::ArgumentDirection::Out
} else if kind == SymbolKind::Instance {
m.instance_port_connections().into_iter().any(|c| {
drives(
c.expression()
.filter(|e| e.kind() == ExpressionKind::Assignment),
)
})
} else {
false
}
})
}
fn declare<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
name: &str,
ty: inkwell::types::FunctionType<'ctx>,
) -> FunctionValue<'ctx> {
cx.module
.get_function(name)
.unwrap_or_else(|| cx.module.add_function(name, ty, Some(Linkage::External)))
}
fn lane_words<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
lane: IntValue<'ctx>,
width: u32,
) -> PointerValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let n = width.div_ceil(64).max(1);
let t = op.i_n(64 * n);
let slot = crate::expr::entry_alloca(cx, t, "force_lane");
cx.b.build_store(slot, op.resize_unsigned(lane, width, 64 * n))
.unwrap();
slot
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_force<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
handle: PointerValue<'ctx>,
slot: u32,
lo: u32,
width: u32,
v: PackedValue<'ctx>,
flags: u32,
site: u64,
) {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let c = cx.ctx;
let (i32t, i64t, ptr) = (
c.i32_type(),
c.i64_type(),
c.ptr_type(AddressSpace::default()),
);
let f = declare(
cx,
SV_RT_FORCE_SYMBOL,
c.void_type().fn_type(
&[
ptr.into(),
i32t.into(),
i32t.into(),
i32t.into(),
ptr.into(),
ptr.into(),
i32t.into(),
i64t.into(),
],
false,
),
);
let val = lane_words(cx, op.lane_val(v), width);
let unk = lane_words(cx, op.lane_unk(v), width);
let k = |x: u64| i32t.const_int(x, false);
cx.b.build_call(
f,
&[
handle.into(),
k(u64::from(slot)).into(),
k(u64::from(lo)).into(),
k(u64::from(width)).into(),
val.into(),
unk.into(),
k(u64::from(flags)).into(),
i64t.const_int(site, false).into(),
],
"rt_force_call",
)
.unwrap();
}
fn row_flags(stmt: &sv_lang::Statement<'_>) -> u32 {
if stmt.procedural_assign_is_force() {
0
} else {
vj_sim::sched::FORCE_PROCEDURAL_ASSIGN
}
}
pub(crate) fn lower_procedural_assign<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, SignalSlot>,
stmt: sv_lang::Statement<'d>,
) -> Result<(), LoweringError> {
let assign = stmt.expressions().into_iter().next().ok_or_else(|| {
LoweringError::unsupported_at("a force/assign with no assignment", &cx.fn_sym)
})?;
let (lhs, rhs) = assign.left().zip(assign.right()).ok_or_else(|| {
LoweringError::unsupported_at("a force/assign that is not an assignment", &cx.fn_sym)
})?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if lhs.kind() == ExpressionKind::Concatenation {
let parts = concat_targets(lhs)?;
let widths = parts
.iter()
.map(|p| crate::select::lvalue_width(cx, *p))
.collect::<Result<Vec<_>, _>>()?;
let total: u32 = widths.iter().sum();
let v = crate::real::to_storage(cx, lower_expr(cx, rhs)?, total, "a forced value")?;
let v = crate::select::fit_width(&op, v, total);
let mut offset = 0u32;
for (part, w) in parts.iter().zip(&widths).rev() {
let slice = op.part_select4(
v,
op.i_n(total).const_int(u64::from(offset), false),
cx.ctx.bool_type().const_zero(),
*w,
);
assign_one(cx, handle, signals, &stmt, *part, Some(slice), rhs)?;
offset += w;
}
return Ok(());
}
assign_one(cx, handle, signals, &stmt, lhs, None, rhs)
}
fn concat_targets<'d>(
lhs: sv_lang::Expression<'d>,
) -> Result<Vec<sv_lang::Expression<'d>>, LoweringError> {
let mut out = Vec::new();
for p in lhs.operands() {
if p.kind() == ExpressionKind::Concatenation {
out.extend(concat_targets(p)?);
} else {
out.push(p);
}
}
Ok(out)
}
fn assign_one<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, SignalSlot>,
stmt: &sv_lang::Statement<'d>,
lhs: sv_lang::Expression<'d>,
piece: Option<PackedValue<'ctx>>,
rhs: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let value = |width: u32| -> Result<PackedValue<'ctx>, LoweringError> {
match piece {
Some(p) => Ok(p),
None => crate::real::to_storage(cx, lower_expr(cx, rhs)?, width, "a forced value"),
}
};
if let Some(rt) = row_target(cx, lhs)? {
let v = crate::select::fit_width(&op, value(rt.width)?, rt.width);
emit_force_row(cx, handle, &rt, Some(v), row_flags(stmt), site_of(stmt));
return Ok(());
}
let (slot, lo, width, is_net, sym) = target(cx, lhs, signals)?;
let v = crate::select::fit_width(&op, value(width)?, width);
let biased = biased_slot(cx, &sym, slot.slot);
let procedural = if stmt.procedural_assign_is_force() {
0
} else {
vj_sim::sched::FORCE_PROCEDURAL_ASSIGN
};
emit_force(
cx,
handle,
biased,
lo,
width,
v,
u32::from(is_net) | procedural,
site_of(stmt),
);
reload_shadow(cx, &sym);
Ok(())
}
pub(crate) fn lower_procedural_deassign<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, SignalSlot>,
stmt: sv_lang::Statement<'d>,
) -> Result<(), LoweringError> {
let lhs = stmt.expressions().into_iter().next().ok_or_else(|| {
LoweringError::unsupported_at("a release/deassign with no target", &cx.fn_sym)
})?;
if lhs.kind() == ExpressionKind::Concatenation {
for part in concat_targets(lhs)? {
deassign_one(cx, handle, signals, &stmt, part)?;
}
return Ok(());
}
deassign_one(cx, handle, signals, &stmt, lhs)
}
fn deassign_one<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
handle: PointerValue<'ctx>,
signals: &std::collections::HashMap<vj_sim::symid::SymbolId, SignalSlot>,
stmt: &sv_lang::Statement<'d>,
lhs: sv_lang::Expression<'d>,
) -> Result<(), LoweringError> {
if let Some(rt) = row_target(cx, lhs)? {
let flags = vj_sim::sched::FORCE_ROW_RELEASE
| if stmt.procedural_deassign_is_release() {
0
} else {
vj_sim::sched::FORCE_PROCEDURAL_ASSIGN
};
emit_force_row(cx, handle, &rt, None, flags, 0);
return Ok(());
}
let (slot, lo, width, _, sym) = target(cx, lhs, signals)?;
let c = cx.ctx;
let (i32t, ptr) = (c.i32_type(), c.ptr_type(AddressSpace::default()));
let f = declare(
cx,
SV_RT_RELEASE_SYMBOL,
c.void_type().fn_type(
&[
ptr.into(),
i32t.into(),
i32t.into(),
i32t.into(),
i32t.into(),
],
false,
),
);
let k = |x: u32| i32t.const_int(u64::from(x), false);
let biased = biased_slot(cx, &sym, slot.slot);
let flags = if stmt.procedural_deassign_is_release() {
0
} else {
vj_sim::sched::FORCE_PROCEDURAL_ASSIGN
};
cx.b.build_call(
f,
&[
handle.into(),
k(biased).into(),
k(lo).into(),
k(width).into(),
k(flags).into(),
],
"rt_release_call",
)
.unwrap();
reload_shadow(cx, &sym);
Ok(())
}
pub(crate) fn biased_slot(cx: &LowerCtx<'_, '_, '_>, sym: &sv_lang::Symbol<'_>, slot: u32) -> u32 {
cx.write_through
.as_ref()
.zip(cx.locals.get(&sym.uid()))
.and_then(|(wt, (ptr, _, _))| wt.signals.get(ptr).map(|e| e.0))
.unwrap_or(slot)
}
pub(crate) fn reload_shadow(cx: &LowerCtx<'_, '_, '_>, sym: &sv_lang::Symbol<'_>) {
let Some(wt) = &cx.write_through else {
return;
};
let Some(&(ptr, _, _)) = cx.locals.get(&sym.uid()) else {
return;
};
crate::expr::reload_from_state(cx, wt, ptr);
}