vj-codegen 0.1.0

LLVM code generator of the vj SystemVerilog simulator.
//! Time units (LRM 1800-2023 ยง3.14): every delay and `$time` is expressed
//! in its scope's time unit, rounded to that scope's precision, while the
//! scheduler counts one global tick -- the finest precision anywhere in the
//! design. Exponents are powers of ten of a second (`1ns` is -9, `10ps` is
//! -11).

use sv_lang::{Symbol, TimeScale, TimeScaleValue, TimeUnit};

use crate::error::LoweringError;

/// A scope with no `` `timescale `` in effect uses `1s / 1s` (iverilog's
/// choice; the LRM leaves it to the tool).
const DEFAULT_EXP: i32 = 0;

/// `v`'s power-of-ten exponent (`magnitude` is 1, 10 or 100).
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,
    }
}

/// The time scale in effect at `sym`: its own, or its nearest enclosing
/// scope's.
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
}

/// A time-scale value as written in a `` `timescale `` directive (`10ps`).
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)
}

/// The time scale in effect at `sym` as `$printtimescale` prints it (LRM
/// 1800-2023 ยง20.4.1): `1ns / 1ps`; a scope with none in effect reports the
/// default `1s / 1s` (see [`DEFAULT_EXP`]).
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(),
    }
}

/// The `(unit, precision)` exponents at `sym`.
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),
    }
}

/// The time unit's exponent at `sym` (an SDF `LABEL` value, written in the SDF file's
/// `TIMESCALE`, is converted to the unit of the module its specparam lives in, LRM 32.4.3).
pub fn unit_exp(sym: &Symbol<'_>) -> i32 {
    unit_and_precision(sym).0
}

/// The key a specparam is annotated and read under at run time
/// (`sv_sim_runtime::specparam`): the hierarchical path of its module instance, then its name.
/// Unique per instance (kernels are lowered per instance), and computable by the SDF host from
/// the instance path and the SDF `LABEL` name alone. `None` for a symbol outside any instance.
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()
    ))
}

/// Every specparam an expression reads (a constant expression is folded at compile time, so a
/// delay or a limit that reads a specparam must be told apart from a literal one: LRM 32.4.3
/// re-evaluates it when `$sdf_annotate` replaces the specparam).
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
}

/// Whether a delay folds to a compile-time constant: it is constant AND reads no specparam
/// (a specparam read is a run-time lookup of its annotated value, see [`lower_specparam_read`]).
pub(crate) fn delay_is_static(expr: &sv_lang::Expression<'_>) -> bool {
    expr.constant_value().is_some() && specparam_refs(*expr).is_empty()
}

/// The annotated value of the specparam `sym` as an `f64` in its module's time unit, read at
/// run time from `sv_sim_runtime::specparam`, or NaN when no `$sdf_annotate` LABEL annotated
/// it. The caller selects its own compile-time value on NaN.
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())
}

/// The time precision's exponent at `sym` (`$sdf_annotate` rounds an annotated delay to the
/// precision of the instance it lands in, LRM 3.14.2.2, as [`delay_ticks`] does a source one).
pub fn precision_exp(sym: &Symbol<'_>) -> i32 {
    unit_and_precision(sym).1
}

/// The scheduler tick's exponent: the finest time precision of any scope in
/// `sym`'s design.
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
    });
    // A design with no timescale at all ticks in the default unit.
    if any { finest } else { DEFAULT_EXP }
}

/// [`design_tick_exp`] for `sym`'s design, walked once per LLVM module and
/// remembered in a private constant global of it (unused by the code, so
/// LLVM drops it).
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
}

/// `10^n`, for a nonnegative `n` small enough for a `u64`.
pub(crate) fn pow10(n: i32) -> u64 {
    10u64.pow(n.max(0) as u32)
}

/// A delay `value` (in `sym`'s time unit) as scheduler ticks of exponent
/// `tick`: rounded to the scope's precision first (LRM 1800-2023
/// ยง3.14.2.2), then scaled to the global tick.
pub fn delay_ticks(value: f64, sym: &Symbol<'_>, tick: i32) -> Result<u64, LoweringError> {
    delay_ticks_scaled(value, sym, unit_and_precision(sym), tick)
}

/// [`delay_ticks`] for a value in the time unit `scale` (`(unit, precision)` exponents); `sym`
/// only locates an error.
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)
}

/// How many scheduler ticks (of exponent `tick`) make one time unit of
/// `sym`'s scope.
pub(crate) fn ticks_per_unit(sym: &Symbol<'_>, tick: i32) -> u64 {
    let (unit, _) = unit_and_precision(sym);
    pow10(unit - tick)
}

/// A delay expression (`#d`, `#(a + b)`, `q <= #d v`) as an `i64` count of
/// scheduler ticks: a constant folds at compile time, anything else is
/// evaluated at run time. An integral value is whole time units; a real one
/// is rounded to the scope's precision first (ยง3.14.2.2). An unknown (X/Z)
/// delay is zero, and a negative one its 64-bit two's complement (a delay
/// is an unsigned time value, ยง10.3.3).
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)
}

/// [`lower_delay_ticks`] for a delay declared in `scope` (an inlined subroutine) when that is not
/// the process's own scope: the delay is in the declaring scope's time unit (LRM 3.14.2.1).
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;
    // A delay that IS a specparam (`#dhigh`, LRM 32.4.3's clock example) is read as the real
    // number the SDF file annotated -- `dhigh = 60.5` waits 60.5 units, not the 60 an integral
    // specparam read would round to -- and the source value when nothing annotated it.
    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,
        )),
    }
}