use sv_lang::{Symbol, TimeScale, TimeScaleValue, TimeUnit};
use crate::error::LoweringError;
const DEFAULT_EXP: i32 = 0;
pub(crate) fn exp_of(v: TimeScaleValue) -> i32 {
let unit = if matches!(v.unit, TimeUnit::Milliseconds) {
-3
} else if matches!(v.unit, TimeUnit::Microseconds) {
-6
} else if matches!(v.unit, TimeUnit::Nanoseconds) {
-9
} else if matches!(v.unit, TimeUnit::Picoseconds) {
-12
} else if matches!(v.unit, TimeUnit::Femtoseconds) {
-15
} else {
0
};
unit + match v.magnitude {
100 => 2,
10 => 1,
_ => 0,
}
}
pub(crate) fn scale_at(sym: &Symbol<'_>) -> Option<TimeScale> {
let mut cur = Some(*sym);
while let Some(s) = cur {
if let Some(ts) = s.time_scale() {
return Some(ts);
}
cur = s.parent();
}
None
}
pub(crate) fn value_text(v: TimeScaleValue) -> String {
let unit = match v.unit {
TimeUnit::Milliseconds => "ms",
TimeUnit::Microseconds => "us",
TimeUnit::Nanoseconds => "ns",
TimeUnit::Picoseconds => "ps",
TimeUnit::Femtoseconds => "fs",
_ => "s",
};
format!("{}{unit}", v.magnitude)
}
pub(crate) fn scale_text_at(sym: &Symbol<'_>) -> String {
match scale_at(sym) {
Some(ts) => format!("{} / {}", value_text(ts.base), value_text(ts.precision)),
None => "1s / 1s".to_string(),
}
}
pub fn unit_and_precision(sym: &Symbol<'_>) -> (i32, i32) {
match scale_at(sym) {
Some(ts) => (exp_of(ts.base), exp_of(ts.precision)),
None => (DEFAULT_EXP, DEFAULT_EXP),
}
}
pub fn unit_exp(sym: &Symbol<'_>) -> i32 {
unit_and_precision(sym).0
}
pub fn specparam_key(sym: &Symbol<'_>) -> Option<String> {
let mut scope = sym.parent()?;
while scope.kind() != sv_lang::kinds::SymbolKind::InstanceBody {
scope = scope.parent()?;
}
Some(format!(
"{}.{}",
sv_sim_sim::process::cfg::shown_path(&scope),
sym.name()
))
}
pub fn specparam_refs<'d>(e: sv_lang::Expression<'d>) -> Vec<Symbol<'d>> {
use sv_sim_sim::symid::SymbolUid;
let mut out: Vec<Symbol<'d>> = Vec::new();
let mut stack = vec![e.as_sem_node()];
while let Some(n) = stack.pop() {
if let Some(x) = n.as_expression()
&& sv_sim_sim::symid::is_value_name(x.kind())
&& let Some(sym) = x.referenced_symbol()
&& sym.kind() == sv_lang::kinds::SymbolKind::Specparam
&& !out.iter().any(|s| s.uid() == sym.uid())
{
out.push(sym);
}
stack.extend(n.children());
}
out
}
pub(crate) fn delay_is_static(expr: &sv_lang::Expression<'_>) -> bool {
expr.constant_value().is_some() && specparam_refs(*expr).is_empty()
}
pub(crate) fn lower_specparam_read<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
sym: &Symbol<'_>,
) -> Result<inkwell::values::FloatValue<'ctx>, LoweringError> {
use inkwell::AddressSpace;
use inkwell::module::Linkage;
let key = specparam_key(sym).ok_or_else(|| {
LoweringError::unsupported_at("a specparam outside any module instance", sym)
})?;
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let path = crate::systask::build_c_string_global(cx.ctx, cx.module, &key, "specparam_path");
let f64t = cx.ctx.f64_type();
let rt_fn = cx
.module
.get_function(sv_sim_runtime::specparam::SV_RT_SPECPARAM_VALUE_SYMBOL)
.unwrap_or_else(|| {
cx.module.add_function(
sv_sim_runtime::specparam::SV_RT_SPECPARAM_VALUE_SYMBOL,
f64t.fn_type(&[ptr_ty.into()], false),
Some(Linkage::External),
)
});
Ok(cx
.b
.build_call(rt_fn, &[path.into()], "rt_specparam_value")
.unwrap()
.try_as_basic_value()
.basic()
.expect("sv_rt_specparam_value returns a double")
.into_float_value())
}
pub fn precision_exp(sym: &Symbol<'_>) -> i32 {
unit_and_precision(sym).1
}
pub fn design_tick_exp(sym: &Symbol<'_>) -> i32 {
let mut root = *sym;
while let Some(p) = root.parent() {
root = p;
}
let mut finest = DEFAULT_EXP;
let mut any = false;
root.visit(|s| {
if let Some(ts) = s.time_scale() {
finest = finest.min(exp_of(ts.precision));
any = true;
}
sv_lang::Walk::Continue
});
if any { finest } else { DEFAULT_EXP }
}
pub(crate) fn tick_exp<'ctx>(
ctx: &'ctx inkwell::context::Context,
module: &inkwell::module::Module<'ctx>,
sym: &Symbol<'_>,
) -> i32 {
const NAME: &str = "sv_sim.design_tick_exp";
if let Some(g) = module.get_global(NAME)
&& let Some(v) = g
.get_initializer()
.and_then(|i| i.into_int_value().get_sign_extended_constant())
{
return v as i32;
}
let exp = design_tick_exp(sym);
let i32t = ctx.i32_type();
let g = module.add_global(i32t, None, NAME);
g.set_initializer(&i32t.const_int(exp as u64, true));
g.set_constant(true);
g.set_linkage(inkwell::module::Linkage::Private);
exp
}
pub(crate) fn pow10(n: i32) -> u64 {
10u64.pow(n.max(0) as u32)
}
pub fn delay_ticks(value: f64, sym: &Symbol<'_>, tick: i32) -> Result<u64, LoweringError> {
delay_ticks_scaled(value, sym, unit_and_precision(sym), tick)
}
pub fn delay_ticks_scaled(
value: f64,
sym: &Symbol<'_>,
scale: (i32, i32),
tick: i32,
) -> Result<u64, LoweringError> {
if value.is_nan() || value < 0.0 {
return Err(LoweringError::unsupported_at(
&format!("a negative or NaN delay ({value})"),
sym,
));
}
let (unit, precision) = scale;
let in_precision = (value * 10f64.powi(unit - precision)).round();
let ticks = in_precision * 10f64.powi(precision - tick);
if ticks > u64::MAX as f64 {
return Err(LoweringError::unsupported_at(
"a delay too long for the 64-bit scheduler time",
sym,
));
}
Ok(ticks as u64)
}
pub(crate) fn ticks_per_unit(sym: &Symbol<'_>, tick: i32) -> u64 {
let (unit, _) = unit_and_precision(sym);
pow10(unit - tick)
}
pub(crate) fn lower_delay_ticks<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
expr: sv_lang::Expression<'_>,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
lower_delay_ticks_in(cx, expr, None)
}
pub(crate) fn lower_delay_ticks_in<'ctx>(
cx: &crate::expr::LowerCtx<'ctx, '_, '_>,
expr: sv_lang::Expression<'_>,
scope: Option<&Symbol<'_>>,
) -> Result<inkwell::values::IntValue<'ctx>, LoweringError> {
use crate::expr::LoweredValue;
let tick = tick_exp(cx.ctx, cx.module, &cx.fn_sym);
let scale = scope
.map(unit_and_precision)
.or(cx.decl_scale.get())
.unwrap_or_else(|| unit_and_precision(&cx.fn_sym));
let i64t = cx.ctx.i64_type();
if delay_is_static(&expr) {
let ticks =
crate::process_lower::eval_constant_delay_ticks_scaled(&expr, &cx.fn_sym, scale, tick)?;
return Ok(i64t.const_int(ticks, false));
}
let (unit, precision) = scale;
let b = &cx.b;
let mut bare = expr;
while bare.kind() == sv_lang::kinds::ExpressionKind::Conversion
&& let Some(inner) = bare.conversion_operand()
{
bare = inner;
}
let specparam = bare
.referenced_symbol()
.filter(|s| s.kind() == sv_lang::kinds::SymbolKind::Specparam)
.filter(|_| sv_sim_sim::symid::is_value_name(bare.kind()));
let lowered = match specparam {
Some(sym) => {
let source = match bare.constant_value().or_else(|| sym.constant_value()) {
Some(sv_lang::ConstantValue::Real(f)) => f,
Some(other) => other.as_integer().and_then(|i| i.as_i64()).ok_or_else(|| {
LoweringError::unsupported_at("a specparam delay that is not a number", &sym)
})? as f64,
None => {
return Err(LoweringError::unsupported_at(
"a specparam delay with no constant value",
&sym,
));
}
};
let f64t = cx.ctx.f64_type();
let annotated = lower_specparam_read(cx, &sym)?;
let none = b
.build_float_compare(
inkwell::FloatPredicate::UNO,
annotated,
annotated,
"sp_none",
)
.unwrap();
LoweredValue::Real(crate::value::RealValue {
llvm: b
.build_select(none, f64t.const_float(source), annotated, "sp_value")
.unwrap()
.into_float_value(),
})
}
None => crate::expr::lower_expr(cx, expr)?,
};
match lowered {
LoweredValue::Real(r) => {
let f64t = cx.ctx.f64_type();
let f = crate::real::as_f64(cx, r.llvm);
let scaled = b
.build_float_mul(
f,
f64t.const_float(10f64.powi(unit - precision)),
"delay_prec",
)
.unwrap();
let round = inkwell::intrinsics::Intrinsic::find("llvm.round")
.and_then(|i| i.get_declaration(cx.module, &[f64t.into()]))
.expect("llvm.round");
let rounded = b
.build_call(round, &[scaled.into()], "delay_round")
.unwrap()
.try_as_basic_value()
.basic()
.expect("llvm.round returns a value")
.into_float_value();
let whole = b
.build_float_to_signed_int(rounded, i64t, "delay_int")
.unwrap();
let per = i64t.const_int(pow10(precision - tick), false);
Ok(b.build_int_mul(whole, per, "delay_ticks").unwrap())
}
LoweredValue::Packed(pv) => {
let op = crate::ops::OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let val = crate::expr::mem_row_index_i64(&op, cx, pv);
let unk = op.lane_unk(pv);
let known = b
.build_int_compare(
inkwell::IntPredicate::EQ,
unk,
unk.get_type().const_zero(),
"delay_known",
)
.unwrap();
let val = b
.build_select(known, val, i64t.const_zero(), "delay_xz0")
.unwrap()
.into_int_value();
let per = i64t.const_int(pow10(unit - tick), false);
Ok(b.build_int_mul(val, per, "delay_ticks").unwrap())
}
_ => Err(LoweringError::unsupported_at(
"a string-typed delay expression, or one wider than the packed-value limit",
&cx.fn_sym,
)),
}
}