From 9dd365ffe3020447d5cb53d1c980e82ebca94781 Mon Sep 17 00:00:00 2001 From: Sai Date: Wed, 29 Jul 2026 21:03:32 -0700 Subject: [PATCH] Add $rtoi/$itor in analog context with truncating semantics VAMS-2023 (Mantis 7920) permits IEEE 1364 $rtoi/$itor in analog. $rtoi truncates toward zero, so it uses a new FItrunc MIR opcode and LLVM FPToSI instead of the rounding language cast FIcast/lround. Co-authored-by: Cursor --- .../VAMS2023_RTOI/vams2023_rtoi.va | 31 ++++ openvaf/hir_def/src/builtin.rs | 78 +++++----- openvaf/hir_lower/src/expr.rs | 12 ++ openvaf/hir_ty/src/builtin.rs | 4 + openvaf/hir_ty/src/builtin/generated.rs | 4 +- openvaf/mir/src/builder/generated.rs | 4 + openvaf/mir/src/instructions/generated.rs | 143 +++++++++--------- openvaf/mir_autodiff/src/builder.rs | 1 + openvaf/mir_autodiff/src/lib.rs | 1 + openvaf/mir_interpret/src/lib.rs | 3 + openvaf/mir_llvm/src/builder.rs | 11 ++ openvaf/mir_opt/src/const_eval.rs | 2 + openvaf/mir_opt/src/const_prop/tests.rs | 55 +++++++ openvaf/mir_opt/src/simplify.rs | 2 + openvaf/syntax/src/name.rs | 2 + openvaf/test_data/mir/vams2023_rtoi_itor.mir | 18 +++ openvaf/test_data/mir/vams2023_rtoi_itor.va | 21 +++ openvaf/test_data/osdi/vams2023_rtoi.snap | 10 ++ sourcegen/src/hir_builtins.rs | 6 +- sourcegen/src/mir_instructions.rs | 3 + 20 files changed, 303 insertions(+), 108 deletions(-) create mode 100644 integration_tests/VAMS2023_RTOI/vams2023_rtoi.va create mode 100644 openvaf/test_data/mir/vams2023_rtoi_itor.mir create mode 100644 openvaf/test_data/mir/vams2023_rtoi_itor.va create mode 100644 openvaf/test_data/osdi/vams2023_rtoi.snap diff --git a/integration_tests/VAMS2023_RTOI/vams2023_rtoi.va b/integration_tests/VAMS2023_RTOI/vams2023_rtoi.va new file mode 100644 index 00000000..cb0e5616 --- /dev/null +++ b/integration_tests/VAMS2023_RTOI/vams2023_rtoi.va @@ -0,0 +1,31 @@ +`include "constants.vams" +`include "disciplines.vams" + +// Exercises `$rtoi` / `$itor` (VAMS-2023 Mantis 7920) through HIR → MIR → +// LLVM/OSDI. Arguments come from a branch voltage so const-fold cannot delete +// the truncating cast before codegen. +// +// The contribution uses `$itor($rtoi(...))` so both conversions stay live, and +// the scale is chosen so a rounding `$rtoi` (→ ±2 for |x|≈1.9) would change +// the DC residual relative to truncation (→ ±1). +module vams2023_rtoi(A, C); + inout A, C; + electrical A, C; + + branch (A, C) br; + + (*desc = "Output scale", units = "A"*) parameter real scale = 1.0 from (0:inf); + + real x, xr; + integer xi; + + analog begin + x = V(br); + + // Keep |frac| in (0.5, 1) so truncation and rounding disagree. + xr = 1.0 + 0.9 * (x / (1.0 + abs(x))); + xi = $rtoi(xr); + // Round-trip through `$itor` so integer→real also reaches LLVM. + I(br) <+ scale * $itor(xi); + end +endmodule diff --git a/openvaf/hir_def/src/builtin.rs b/openvaf/hir_def/src/builtin.rs index 0f62f9bc..a15a3e62 100644 --- a/openvaf/hir_def/src/builtin.rs +++ b/openvaf/hir_def/src/builtin.rs @@ -88,43 +88,45 @@ pub enum BuiltIn { rdist_t = 73u8, clog2 = 74u8, log10 = 75u8, - temperature = 76u8, - vt = 77u8, - simparam = 78u8, - simparam_str = 79u8, - simprobe = 80u8, - discontinuity = 81u8, - param_given = 82u8, - port_connected = 83u8, - analog_node_alias = 84u8, - analog_port_alias = 85u8, - test_plusargs = 86u8, - value_plusargs = 87u8, - bound_step = 88u8, - analysis = 89u8, - ac_stim = 90u8, - noise_table = 91u8, - noise_table_log = 92u8, - white_noise = 93u8, - flicker_noise = 94u8, - limit = 95u8, - absdelay = 96u8, - ddt = 97u8, - idt = 98u8, - idtmod = 99u8, - ddx = 100u8, - zi_nd = 101u8, - zi_np = 102u8, - zi_zd = 103u8, - zi_zp = 104u8, - laplace_nd = 105u8, - laplace_np = 106u8, - laplace_zd = 107u8, - laplace_zp = 108u8, - limexp = 109u8, - last_crossing = 110u8, - slew = 111u8, - transition = 112u8, + rtoi = 76u8, + itor = 77u8, + temperature = 78u8, + vt = 79u8, + simparam = 80u8, + simparam_str = 81u8, + simprobe = 82u8, + discontinuity = 83u8, + param_given = 84u8, + port_connected = 85u8, + analog_node_alias = 86u8, + analog_port_alias = 87u8, + test_plusargs = 88u8, + value_plusargs = 89u8, + bound_step = 90u8, + analysis = 91u8, + ac_stim = 92u8, + noise_table = 93u8, + noise_table_log = 94u8, + white_noise = 95u8, + flicker_noise = 96u8, + limit = 97u8, + absdelay = 98u8, + ddt = 99u8, + idt = 100u8, + idtmod = 101u8, + ddx = 102u8, + zi_nd = 103u8, + zi_np = 104u8, + zi_zd = 105u8, + zi_zp = 106u8, + laplace_nd = 107u8, + laplace_np = 108u8, + laplace_zd = 109u8, + laplace_zp = 110u8, + limexp = 111u8, + last_crossing = 112u8, + slew = 113u8, + transition = 114u8, } #[derive(Eq, PartialEq, Copy, Clone, Hash, Debug)] #[allow(nonstandard_style, unreachable_pub)] @@ -342,6 +344,8 @@ pub fn insert_builtin_scope(dst: &mut IndexMap { self.ctx.ins().ceil(arg0) } + // `$rtoi`: IEEE 1364 truncates toward zero. Must not reuse `FIcast`, + // which rounds (language real→integer conversion / `llvm.lround`). + BuiltIn::rtoi => { + let arg0 = self.lower_expr(args[0]); + self.ctx.ins().fitrunc(arg0) + } + // `$itor`: integer→real; same semantics as the language `IFcast`. + BuiltIn::itor => { + let arg0 = self.lower_expr(args[0]); + self.ctx.ins().ifcast(arg0) + } + BuiltIn::max => { let comparison = match_signature!(signature: MAX_REAL => InstBuilder::fgt, MAX_INT => InstBuilder::igt); let arg0 = self.lower_expr(args[0]); diff --git a/openvaf/hir_ty/src/builtin.rs b/openvaf/hir_ty/src/builtin.rs index 6eeee682..0d668d31 100644 --- a/openvaf/hir_ty/src/builtin.rs +++ b/openvaf/hir_ty/src/builtin.rs @@ -188,6 +188,10 @@ bultins! { const fn REAL_MATH_1(Val(Real)) -> Real; const fn REAL_MATH_2(Val(Real),Val(Real)) -> Real; const fn INT_MATH_1(Val(Integer)) -> Integer; + // IEEE 1364 / VAMS-2023 §9.11 conversion system functions. + // `$rtoi` truncates toward zero; `$itor` is the integer→real inverse. + const fn RTOI(Val(Real)) -> Integer; + const fn ITOR(Val(Integer)) -> Real; VT = const { diff --git a/openvaf/hir_ty/src/builtin/generated.rs b/openvaf/hir_ty/src/builtin/generated.rs index 6547b474..b04ea871 100644 --- a/openvaf/hir_ty/src/builtin/generated.rs +++ b/openvaf/hir_ty/src/builtin/generated.rs @@ -4,7 +4,7 @@ use hir_def::BuiltIn; use crate::builtin::*; -const BUILTIN_INFO: [BuiltinInfo; 113usize] = [ +const BUILTIN_INFO: [BuiltinInfo; 115usize] = [ ABS, ACOS, ACOSH, @@ -81,6 +81,8 @@ const BUILTIN_INFO: [BuiltinInfo; 113usize] = [ RDIST_T, CLOG2, LOG10, + RTOI, + ITOR, TEMPERATURE, VT, SIMPARAM, diff --git a/openvaf/mir/src/builder/generated.rs b/openvaf/mir/src/builder/generated.rs index 48e9346c..2eb65e40 100644 --- a/openvaf/mir/src/builder/generated.rs +++ b/openvaf/mir/src/builder/generated.rs @@ -110,6 +110,10 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { let (inst, dfg) = self.unary(Opcode::FIcast, arg0); dfg.first_result(inst) } + fn fitrunc(self, arg0: Value) -> Value { + let (inst, dfg) = self.unary(Opcode::FItrunc, arg0); + dfg.first_result(inst) + } fn ifcast(self, arg0: Value) -> Value { let (inst, dfg) = self.unary(Opcode::IFcast, arg0); dfg.first_result(inst) diff --git a/openvaf/mir/src/instructions/generated.rs b/openvaf/mir/src/instructions/generated.rs index 7b9edffc..291f01a3 100644 --- a/openvaf/mir/src/instructions/generated.rs +++ b/openvaf/mir/src/instructions/generated.rs @@ -19,74 +19,75 @@ pub enum Opcode { Fneg = 3u8, Ineg = 4u8, FIcast = 5u8, - IFcast = 6u8, - BIcast = 7u8, - IBcast = 8u8, - FBcast = 9u8, - BFcast = 10u8, - OptBarrier = 11u8, - Sqrt = 12u8, - Exp = 13u8, - Expm1 = 14u8, - Ln = 15u8, - Ln1p = 16u8, - Log = 17u8, - Clog2 = 18u8, - Floor = 19u8, - Ceil = 20u8, - Sin = 21u8, - Cos = 22u8, - Tan = 23u8, - Asin = 24u8, - Acos = 25u8, - Atan = 26u8, - Sinh = 27u8, - Cosh = 28u8, - Tanh = 29u8, - Asinh = 30u8, - Acosh = 31u8, - Atanh = 32u8, - Iadd = 33u8, - Isub = 34u8, - Imul = 35u8, - Idiv = 36u8, - Irem = 37u8, - Ishl = 38u8, - Ishr = 39u8, - Ixor = 40u8, - Iand = 41u8, - Ior = 42u8, - Fadd = 43u8, - Fsub = 44u8, - Fmul = 45u8, - Fdiv = 46u8, - Frem = 47u8, - Ilt = 48u8, - Igt = 49u8, - Ige = 50u8, - Ile = 51u8, - Flt = 52u8, - Fgt = 53u8, - Fge = 54u8, - Fle = 55u8, - Ieq = 56u8, - Feq = 57u8, - Seq = 58u8, - Beq = 59u8, - Ine = 60u8, - Fne = 61u8, - Sne = 62u8, - Bne = 63u8, - Hypot = 64u8, - Atan2 = 65u8, - Pow = 66u8, - Br = 67u8, - Jmp = 68u8, - Exit = 69u8, - Call = 70u8, - Phi = 71u8, + FItrunc = 6u8, + IFcast = 7u8, + BIcast = 8u8, + IBcast = 9u8, + FBcast = 10u8, + BFcast = 11u8, + OptBarrier = 12u8, + Sqrt = 13u8, + Exp = 14u8, + Expm1 = 15u8, + Ln = 16u8, + Ln1p = 17u8, + Log = 18u8, + Clog2 = 19u8, + Floor = 20u8, + Ceil = 21u8, + Sin = 22u8, + Cos = 23u8, + Tan = 24u8, + Asin = 25u8, + Acos = 26u8, + Atan = 27u8, + Sinh = 28u8, + Cosh = 29u8, + Tanh = 30u8, + Asinh = 31u8, + Acosh = 32u8, + Atanh = 33u8, + Iadd = 34u8, + Isub = 35u8, + Imul = 36u8, + Idiv = 37u8, + Irem = 38u8, + Ishl = 39u8, + Ishr = 40u8, + Ixor = 41u8, + Iand = 42u8, + Ior = 43u8, + Fadd = 44u8, + Fsub = 45u8, + Fmul = 46u8, + Fdiv = 47u8, + Frem = 48u8, + Ilt = 49u8, + Igt = 50u8, + Ige = 51u8, + Ile = 52u8, + Flt = 53u8, + Fgt = 54u8, + Fge = 55u8, + Fle = 56u8, + Ieq = 57u8, + Feq = 58u8, + Seq = 59u8, + Beq = 60u8, + Ine = 61u8, + Fne = 62u8, + Sne = 63u8, + Bne = 64u8, + Hypot = 65u8, + Atan2 = 66u8, + Pow = 67u8, + Br = 68u8, + Jmp = 69u8, + Exit = 70u8, + Call = 71u8, + Phi = 72u8, } -pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 71usize + 1] = [ +pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 72usize + 1] = [ OpcodeConstraints::new(0, 0), OpcodeConstraints::new(1u8, 1u8), OpcodeConstraints::new(1u8, 1u8), @@ -120,6 +121,7 @@ pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 71usize + 1] = [ OpcodeConstraints::new(1u8, 1u8), OpcodeConstraints::new(1u8, 1u8), OpcodeConstraints::new(1u8, 1u8), + OpcodeConstraints::new(1u8, 1u8), OpcodeConstraints::new(2u8, 1u8), OpcodeConstraints::new(2u8, 1u8), OpcodeConstraints::new(2u8, 1u8), @@ -160,13 +162,14 @@ pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 71usize + 1] = [ OpcodeConstraints::new(0u8, 0u8), OpcodeConstraints::new(0u8, 1u8), ]; -pub(super) const OPCODE_NAMES: [&str; 71usize + 1] = [ +pub(super) const OPCODE_NAMES: [&str; 72usize + 1] = [ "", "inot", "bnot", "fneg", "ineg", "ficast", + "fitrunc", "ifcast", "bicast", "ibcast", @@ -234,7 +237,7 @@ pub(super) const OPCODE_NAMES: [&str; 71usize + 1] = [ "call", "phi", ]; -pub(super) const OPCODE_FORMAT: [InstructionFormat; 71usize + 1] = [ +pub(super) const OPCODE_FORMAT: [InstructionFormat; 72usize + 1] = [ InstructionFormat::Binary, InstructionFormat::Unary, InstructionFormat::Unary, @@ -268,6 +271,7 @@ pub(super) const OPCODE_FORMAT: [InstructionFormat; 71usize + 1] = [ InstructionFormat::Unary, InstructionFormat::Unary, InstructionFormat::Unary, + InstructionFormat::Unary, InstructionFormat::Binary, InstructionFormat::Binary, InstructionFormat::Binary, @@ -317,6 +321,7 @@ impl std::str::FromStr for Opcode { "fneg" => Ok(Opcode::Fneg), "ineg" => Ok(Opcode::Ineg), "ficast" => Ok(Opcode::FIcast), + "fitrunc" => Ok(Opcode::FItrunc), "ifcast" => Ok(Opcode::IFcast), "bicast" => Ok(Opcode::BIcast), "ibcast" => Ok(Opcode::IBcast), diff --git a/openvaf/mir_autodiff/src/builder.rs b/openvaf/mir_autodiff/src/builder.rs index dcba8069..0c5ab5a7 100644 --- a/openvaf/mir_autodiff/src/builder.rs +++ b/openvaf/mir_autodiff/src/builder.rs @@ -796,6 +796,7 @@ impl<'a, 'u> DerivativeBuilder<'a, 'u> { | Opcode::FBcast | Opcode::BFcast | Opcode::FIcast + | Opcode::FItrunc | Opcode::Irem | Opcode::Inot | Opcode::Ixor diff --git a/openvaf/mir_autodiff/src/lib.rs b/openvaf/mir_autodiff/src/lib.rs index ccd29d98..84c11f9b 100644 --- a/openvaf/mir_autodiff/src/lib.rs +++ b/openvaf/mir_autodiff/src/lib.rs @@ -52,6 +52,7 @@ fn zero_derivative(dfg: &DataFlowGraph, inst: Inst) -> bool { | Opcode::FBcast | Opcode::BFcast | Opcode::FIcast + | Opcode::FItrunc | Opcode::Irem | Opcode::Inot | Opcode::Ixor diff --git a/openvaf/mir_interpret/src/lib.rs b/openvaf/mir_interpret/src/lib.rs index 1831108f..e0c9506f 100644 --- a/openvaf/mir_interpret/src/lib.rs +++ b/openvaf/mir_interpret/src/lib.rs @@ -144,6 +144,9 @@ impl<'a> Interpreter<'a> { mir::Opcode::Fneg => (-args(0).f64()).into(), mir::Opcode::Ineg => (-args(0).i32()).into(), mir::Opcode::FIcast => (args(0).f64() as i32).into(), + // `$rtoi`: truncate toward zero. Distinct MIR opcode from the rounding + // language cast so the interpreter cannot accidentally share paths later. + mir::Opcode::FItrunc => (args(0).f64() as i32).into(), mir::Opcode::IFcast => (args(0).i32() as f64).into(), mir::Opcode::BIcast => (args(0).bool() as i32).into(), mir::Opcode::IBcast => (args(0).i32() != 0).into(), diff --git a/openvaf/mir_llvm/src/builder.rs b/openvaf/mir_llvm/src/builder.rs index b9dfcb54..946a355d 100644 --- a/openvaf/mir_llvm/src/builder.rs +++ b/openvaf/mir_llvm/src/builder.rs @@ -741,6 +741,17 @@ impl<'ll> Builder<'_, '_, 'll> { UNNAMED, ) } + // `$rtoi`: IEEE 1364 truncates toward zero via FPToSI. + // Do not call `llvm.lround` (that is `FIcast` / language real→int). + Opcode::FItrunc => { + let arg = NonNull::from(self.values[args[0]].get(self)).as_ptr(); + llvm_sys::core::LLVMBuildFPToSI( + self.llbuilder, + arg, + NonNull::from(self.cx.ty_int()).as_ptr(), + UNNAMED, + ) + } Opcode::BFcast => { let arg = NonNull::from(self.values[args[0]].get(self)).as_ptr(); llvm_sys::core::LLVMBuildUIToFP( diff --git a/openvaf/mir_opt/src/const_eval.rs b/openvaf/mir_opt/src/const_eval.rs index e26654d0..bab3b6d7 100644 --- a/openvaf/mir_opt/src/const_eval.rs +++ b/openvaf/mir_opt/src/const_eval.rs @@ -87,6 +87,8 @@ pub fn eval_unary(func: &mut Function, op: Opcode, val: Const) -> Option Opcode::Acosh => func.dfg.f64const(val.acosh()), Opcode::Atanh => func.dfg.f64const(val.atanh()), Opcode::FIcast => func.dfg.iconst(val.round() as i32), + // `$rtoi` / `FItrunc`: truncate toward zero (Rust `as i32`). + Opcode::FItrunc => func.dfg.iconst(val as i32), Opcode::FBcast => (val.abs() != 0.0).into(), Opcode::Fneg => func.dfg.f64const(-val), _ => unreachable!("invalid real operation {}", op), diff --git a/openvaf/mir_opt/src/const_prop/tests.rs b/openvaf/mir_opt/src/const_prop/tests.rs index 5eefd53a..95ee7878 100644 --- a/openvaf/mir_opt/src/const_prop/tests.rs +++ b/openvaf/mir_opt/src/const_prop/tests.rs @@ -108,6 +108,61 @@ pub fn const_ln1p_precision() { check(raw, expect) } +/// `$rtoi` / `fitrunc` truncates toward zero. 1.9 must fold to 1 (not 2). +#[test] +pub fn const_fitrunc_truncates_toward_zero() { + let raw = r##" + function %bar(v20) { + v30 = fconst 0x1.e666666666666p0 + block0: + v21 = fitrunc v30 + v22 = fneg v30 + v23 = fitrunc v22 + v24 = iadd v21, v23 + v25 = iadd v24, v21 + v26 = iadd v25, v20 + } + "##; + + let expect = expect![[r#" + function %bar(v20) { + v5 = iconst 1 + block0: + v26 = iadd v5, v20 + } + "#]]; + + check(raw, expect) +} + +/// Language cast `ficast` rounds: 1.9 → 2. Guard that `$rtoi` did not +/// accidentally start sharing this opcode (would fold to 2, not 1). +#[test] +pub fn const_ficast_rounds() { + let raw = r##" + function %bar(v20) { + v30 = fconst 0x1.e666666666666p0 + block0: + v21 = ficast v30 + v22 = fneg v30 + v23 = ficast v22 + v24 = iadd v21, v23 + v25 = iadd v24, v21 + v26 = iadd v25, v20 + } + "##; + + let expect = expect![[r#" + function %bar(v20) { + v31 = iconst 2 + block0: + v26 = iadd v31, v20 + } + "#]]; + + check(raw, expect) +} + #[test] pub fn no_const_backward_edge() { let raw = r##" diff --git a/openvaf/mir_opt/src/simplify.rs b/openvaf/mir_opt/src/simplify.rs index 704c4151..5a0c190c 100644 --- a/openvaf/mir_opt/src/simplify.rs +++ b/openvaf/mir_opt/src/simplify.rs @@ -95,6 +95,8 @@ impl<'a, FP: Arithmetic, M: Fn(Value, &Function) -> Value> SimplifyCtx<'a, FP, M Opcode::Fneg => return self.simplify_sub_inst::(F_ZERO, arg), Opcode::Ineg => return self.simplify_sub_inst::(ZERO, arg), Opcode::FIcast => Opcode::IFcast, + // Truncation of an integer→real cast is the identity (in-range). + Opcode::FItrunc => Opcode::IFcast, // When the inverse is lossy not transofmration is possible Opcode::IFcast | Opcode::BIcast diff --git a/openvaf/syntax/src/name.rs b/openvaf/syntax/src/name.rs index 4ee57422..0a2ad458 100644 --- a/openvaf/syntax/src/name.rs +++ b/openvaf/syntax/src/name.rs @@ -491,6 +491,8 @@ pub mod sysfun { bitstoreal, realtobits, + rtoi, + itor, dist_chi_square, dist_exponential, diff --git a/openvaf/test_data/mir/vams2023_rtoi_itor.mir b/openvaf/test_data/mir/vams2023_rtoi_itor.mir new file mode 100644 index 00000000..bca40fff --- /dev/null +++ b/openvaf/test_data/mir/vams2023_rtoi_itor.mir @@ -0,0 +1,18 @@ +function %(v16, v18, v19, v21, v22, v24, v30) { + v25 = fconst 0x1.e666666666666p0 + block0: +@0003 v17 = fitrunc v16 +@0006 v20 = fitrunc v19 +@0009 v23 = ifcast v22 +@000c v26 = fitrunc v25 +@000e v27 = fneg v25 +@000f v28 = fitrunc v27 +@0010 v29 = iadd v26, v28 + v31 = optbarrier v17 + v32 = optbarrier v20 + v33 = optbarrier v23 + v34 = optbarrier v29 + jmp block1 + + block1: +} diff --git a/openvaf/test_data/mir/vams2023_rtoi_itor.va b/openvaf/test_data/mir/vams2023_rtoi_itor.va new file mode 100644 index 00000000..8d0b7307 --- /dev/null +++ b/openvaf/test_data/mir/vams2023_rtoi_itor.va @@ -0,0 +1,21 @@ +// VAMS-2023 (Mantis 7920): `$rtoi` / `$itor` in the analog context. +// +// IEEE 1364 `$rtoi` truncates toward zero. OpenVAF's language real→integer +// cast (`FIcast`) rounds, so these literals must lower to `fitrunc` and fold +// to truncated integers — never to the rounded values 2 / -2. +module test; + parameter real rx = 1.9; + parameter real ry = -1.9; + parameter integer ix = 7; + real a; + integer b, c, d; + analog begin + // Non-const params keep `fitrunc` / `ifcast` visible in MIR. + b = $rtoi(rx); + c = $rtoi(ry); + a = $itor(ix); + + // Literals exercise const-fold truncation (1.9 → 1, -1.9 → -1). + d = $rtoi(1.9) + $rtoi(-1.9); + end +endmodule diff --git a/openvaf/test_data/osdi/vams2023_rtoi.snap b/openvaf/test_data/osdi/vams2023_rtoi.snap new file mode 100644 index 00000000..c7f78fb1 --- /dev/null +++ b/openvaf/test_data/osdi/vams2023_rtoi.snap @@ -0,0 +1,10 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) +param "scale" +units = "A", desc = "Output scale", flags = ParameterFlags(0x0) + +2 terminals +node "A" units = "V", runits = "A" +node "C" units = "V", runits = "A" +0 states +has bound_step false diff --git a/sourcegen/src/hir_builtins.rs b/sourcegen/src/hir_builtins.rs index b7687c05..8dcfa5b5 100644 --- a/sourcegen/src/hir_builtins.rs +++ b/sourcegen/src/hir_builtins.rs @@ -114,7 +114,7 @@ const BUILTINS: [&str; 28] = [ const PARAM_SYSFUNS: [&str; 6] = ["mfactor", "xposition", "yposition", "angle", "hflip", "vflip"]; -const SYSFUNS: [&str; 86] = [ +const SYSFUNS: [&str; 88] = [ "$display", "$strobe", "$write", @@ -188,6 +188,10 @@ const SYSFUNS: [&str; 86] = [ "$min", "$max", "$abs", + // VAMS-2023 (Mantis 7920): conversion system functions in the analog context. + // IEEE 1364 `$rtoi` truncates toward zero; do not alias the rounding `FIcast`. + "$rtoi", + "$itor", "$temperature", "$vt", "$simparam", diff --git a/sourcegen/src/mir_instructions.rs b/sourcegen/src/mir_instructions.rs index 6d0a76e4..3dfb48c3 100644 --- a/sourcegen/src/mir_instructions.rs +++ b/sourcegen/src/mir_instructions.rs @@ -66,6 +66,9 @@ opcodes! { Ineg FIcast + // Truncating real→integer cast used by `$rtoi` (IEEE 1364). Distinct from + // `FIcast`, which rounds via `llvm.lround` / `.round()` for language casts. + FItrunc IFcast BIcast