diff --git a/integration_tests/VAMS2023_MATH/vams2023_math.va b/integration_tests/VAMS2023_MATH/vams2023_math.va new file mode 100644 index 00000000..fe14801a --- /dev/null +++ b/integration_tests/VAMS2023_MATH/vams2023_math.va @@ -0,0 +1,38 @@ +`include "constants.vams" +`include "disciplines.vams" + +// Exercises the VAMS-2023 expm1()/ln1p() builtins through the whole pipeline +// (HIR -> MIR -> autodiff -> LLVM/OSDI), including their derivatives. +// +// expm1 is the natural spelling for a diode current: the usual +// is*(exp(vd/(n*vt)) - 1) cancels to zero once |vd| << n*vt, which is exactly +// what expm1 avoids. +module vams2023_math(A, C); + inout A, C; + electrical A, C; + + branch (A, C) br_ac; + + (*desc = "Saturation current", units = "A"*) parameter real is = 1e-14 from [0:inf]; + (*desc = "Emission coefficient"*) parameter real n = 1.0 from (0:inf); + (*desc = "Leakage conductance", units = "S"*) parameter real gleak = 1e-12 from [0:inf]; + + real vt, vd, id, vleak; + + (*desc = "diode admittance", units = "S"*) real gd; + + analog begin + vt = `P_K * $temperature / `P_Q; + vd = V(br_ac); + + id = is * expm1(vd / (n * vt)); + + // Independent argument so this is not simplified into the expm1 above, + // which keeps ln1p and its derivative in the generated code. + vleak = vt * ln1p(vd / vt); + + I(br_ac) <+ id + gleak * vleak; + + gd = ddx(id, V(A)); + end +endmodule diff --git a/openvaf/hir_def/src/builtin.rs b/openvaf/hir_def/src/builtin.rs index 10424a05..0f62f9bc 100644 --- a/openvaf/hir_def/src/builtin.rs +++ b/openvaf/hir_def/src/builtin.rs @@ -24,105 +24,107 @@ pub enum BuiltIn { cos = 9u8, cosh = 10u8, exp = 11u8, - floor = 12u8, - flow = 13u8, - potential = 14u8, - hypot = 15u8, - ln = 16u8, - log = 17u8, - max = 18u8, - min = 19u8, - pow = 20u8, - sin = 21u8, - sinh = 22u8, - sqrt = 23u8, - tan = 24u8, - tanh = 25u8, - display = 26u8, - strobe = 27u8, - write = 28u8, - monitor = 29u8, - debug = 30u8, - fclose = 31u8, - fopen = 32u8, - fdisplay = 33u8, - fwrite = 34u8, - fstrobe = 35u8, - fmonitor = 36u8, - fgets = 37u8, - fscanf = 38u8, - swrite = 39u8, - sformat = 40u8, - sscanf = 41u8, - rewind = 42u8, - fseek = 43u8, - ftell = 44u8, - fflush = 45u8, - ferror = 46u8, - feof = 47u8, - fdebug = 48u8, - finish = 49u8, - stop = 50u8, - fatal = 51u8, - warning = 52u8, - error = 53u8, - info = 54u8, - abstime = 55u8, - dist_chi_square = 56u8, - dist_exponential = 57u8, - dist_poisson = 58u8, - dist_uniform = 59u8, - dist_erlang = 60u8, - dist_normal = 61u8, - dist_t = 62u8, - random = 63u8, - arandom = 64u8, - rdist_chi_square = 65u8, - rdist_exponential = 66u8, - rdist_poisson = 67u8, - rdist_uniform = 68u8, - rdist_erlang = 69u8, - rdist_normal = 70u8, - rdist_t = 71u8, - clog2 = 72u8, - log10 = 73u8, - temperature = 74u8, - vt = 75u8, - simparam = 76u8, - simparam_str = 77u8, - simprobe = 78u8, - discontinuity = 79u8, - param_given = 80u8, - port_connected = 81u8, - analog_node_alias = 82u8, - analog_port_alias = 83u8, - test_plusargs = 84u8, - value_plusargs = 85u8, - bound_step = 86u8, - analysis = 87u8, - ac_stim = 88u8, - noise_table = 89u8, - noise_table_log = 90u8, - white_noise = 91u8, - flicker_noise = 92u8, - limit = 93u8, - absdelay = 94u8, - ddt = 95u8, - idt = 96u8, - idtmod = 97u8, - ddx = 98u8, - zi_nd = 99u8, - zi_np = 100u8, - zi_zd = 101u8, - zi_zp = 102u8, - laplace_nd = 103u8, - laplace_np = 104u8, - laplace_zd = 105u8, - laplace_zp = 106u8, - limexp = 107u8, - last_crossing = 108u8, - slew = 109u8, - transition = 110u8, + expm1 = 12u8, + floor = 13u8, + flow = 14u8, + potential = 15u8, + hypot = 16u8, + ln = 17u8, + ln1p = 18u8, + log = 19u8, + max = 20u8, + min = 21u8, + pow = 22u8, + sin = 23u8, + sinh = 24u8, + sqrt = 25u8, + tan = 26u8, + tanh = 27u8, + display = 28u8, + strobe = 29u8, + write = 30u8, + monitor = 31u8, + debug = 32u8, + fclose = 33u8, + fopen = 34u8, + fdisplay = 35u8, + fwrite = 36u8, + fstrobe = 37u8, + fmonitor = 38u8, + fgets = 39u8, + fscanf = 40u8, + swrite = 41u8, + sformat = 42u8, + sscanf = 43u8, + rewind = 44u8, + fseek = 45u8, + ftell = 46u8, + fflush = 47u8, + ferror = 48u8, + feof = 49u8, + fdebug = 50u8, + finish = 51u8, + stop = 52u8, + fatal = 53u8, + warning = 54u8, + error = 55u8, + info = 56u8, + abstime = 57u8, + dist_chi_square = 58u8, + dist_exponential = 59u8, + dist_poisson = 60u8, + dist_uniform = 61u8, + dist_erlang = 62u8, + dist_normal = 63u8, + dist_t = 64u8, + random = 65u8, + arandom = 66u8, + rdist_chi_square = 67u8, + rdist_exponential = 68u8, + rdist_poisson = 69u8, + rdist_uniform = 70u8, + rdist_erlang = 71u8, + rdist_normal = 72u8, + 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, } #[derive(Eq, PartialEq, Copy, Clone, Hash, Debug)] #[allow(nonstandard_style, unreachable_pub)] @@ -251,11 +253,13 @@ pub fn insert_builtin_scope(dst: &mut IndexMap { let arg0 = self.lower_expr(args[0]); self.ctx.ins().exp(arg0) } + BuiltIn::expm1 => { + let arg0 = self.lower_expr(args[0]); + self.ctx.ins().expm1(arg0) + } BuiltIn::limexp => { let arg0 = self.lower_expr(args[0]); @@ -393,6 +397,10 @@ impl BodyLoweringCtx<'_, '_, '_> { let arg0 = self.lower_expr(args[0]); self.ctx.ins().ln(arg0) } + BuiltIn::ln1p => { + let arg0 = self.lower_expr(args[0]); + self.ctx.ins().ln1p(arg0) + } BuiltIn::sin => { let arg0 = self.lower_expr(args[0]); self.ctx.ins().sin(arg0) diff --git a/openvaf/hir_ty/src/builtin.rs b/openvaf/hir_ty/src/builtin.rs index 51f5abef..9a351a36 100644 --- a/openvaf/hir_ty/src/builtin.rs +++ b/openvaf/hir_ty/src/builtin.rs @@ -428,8 +428,10 @@ copied_builtins! { COS = REAL_MATH_1 COSH = REAL_MATH_1 EXP = REAL_MATH_1 + EXPM1 = REAL_MATH_1 FLOOR = REAL_MATH_1 LN = REAL_MATH_1 + LN1P = REAL_MATH_1 LOG = REAL_MATH_1 CLOG2 = INT_MATH_2 LOG10 = REAL_MATH_1 diff --git a/openvaf/hir_ty/src/builtin/generated.rs b/openvaf/hir_ty/src/builtin/generated.rs index a3dcbeca..6547b474 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; 111usize] = [ +const BUILTIN_INFO: [BuiltinInfo; 113usize] = [ ABS, ACOS, ACOSH, @@ -17,11 +17,13 @@ const BUILTIN_INFO: [BuiltinInfo; 111usize] = [ COS, COSH, EXP, + EXPM1, FLOOR, FLOW, POTENTIAL, HYPOT, LN, + LN1P, LOG, MAX, MIN, diff --git a/openvaf/mir/src/builder/generated.rs b/openvaf/mir/src/builder/generated.rs index 529a83ae..48e9346c 100644 --- a/openvaf/mir/src/builder/generated.rs +++ b/openvaf/mir/src/builder/generated.rs @@ -142,10 +142,18 @@ pub trait InstBuilder<'f>: InstBuilderBase<'f> { let (inst, dfg) = self.unary(Opcode::Exp, arg0); dfg.first_result(inst) } + fn expm1(self, arg0: Value) -> Value { + let (inst, dfg) = self.unary(Opcode::Expm1, arg0); + dfg.first_result(inst) + } fn ln(self, arg0: Value) -> Value { let (inst, dfg) = self.unary(Opcode::Ln, arg0); dfg.first_result(inst) } + fn ln1p(self, arg0: Value) -> Value { + let (inst, dfg) = self.unary(Opcode::Ln1p, arg0); + dfg.first_result(inst) + } fn log(self, arg0: Value) -> Value { let (inst, dfg) = self.unary(Opcode::Log, arg0); dfg.first_result(inst) diff --git a/openvaf/mir/src/instructions/generated.rs b/openvaf/mir/src/instructions/generated.rs index 849ad495..7b9edffc 100644 --- a/openvaf/mir/src/instructions/generated.rs +++ b/openvaf/mir/src/instructions/generated.rs @@ -27,64 +27,66 @@ pub enum Opcode { OptBarrier = 11u8, Sqrt = 12u8, Exp = 13u8, - Ln = 14u8, - Log = 15u8, - Clog2 = 16u8, - Floor = 17u8, - Ceil = 18u8, - Sin = 19u8, - Cos = 20u8, - Tan = 21u8, - Asin = 22u8, - Acos = 23u8, - Atan = 24u8, - Sinh = 25u8, - Cosh = 26u8, - Tanh = 27u8, - Asinh = 28u8, - Acosh = 29u8, - Atanh = 30u8, - Iadd = 31u8, - Isub = 32u8, - Imul = 33u8, - Idiv = 34u8, - Irem = 35u8, - Ishl = 36u8, - Ishr = 37u8, - Ixor = 38u8, - Iand = 39u8, - Ior = 40u8, - Fadd = 41u8, - Fsub = 42u8, - Fmul = 43u8, - Fdiv = 44u8, - Frem = 45u8, - Ilt = 46u8, - Igt = 47u8, - Ige = 48u8, - Ile = 49u8, - Flt = 50u8, - Fgt = 51u8, - Fge = 52u8, - Fle = 53u8, - Ieq = 54u8, - Feq = 55u8, - Seq = 56u8, - Beq = 57u8, - Ine = 58u8, - Fne = 59u8, - Sne = 60u8, - Bne = 61u8, - Hypot = 62u8, - Atan2 = 63u8, - Pow = 64u8, - Br = 65u8, - Jmp = 66u8, - Exit = 67u8, - Call = 68u8, - Phi = 69u8, + 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, } -pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 69usize + 1] = [ +pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 71usize + 1] = [ OpcodeConstraints::new(0, 0), OpcodeConstraints::new(1u8, 1u8), OpcodeConstraints::new(1u8, 1u8), @@ -116,6 +118,8 @@ pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 69usize + 1] = [ OpcodeConstraints::new(1u8, 1u8), 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), @@ -156,7 +160,7 @@ pub(super) const OPCODE_CONSTRAINTS: [OpcodeConstraints; 69usize + 1] = [ OpcodeConstraints::new(0u8, 0u8), OpcodeConstraints::new(0u8, 1u8), ]; -pub(super) const OPCODE_NAMES: [&str; 69usize + 1] = [ +pub(super) const OPCODE_NAMES: [&str; 71usize + 1] = [ "", "inot", "bnot", @@ -171,7 +175,9 @@ pub(super) const OPCODE_NAMES: [&str; 69usize + 1] = [ "optbarrier", "sqrt", "exp", + "expm1", "ln", + "ln1p", "log", "clog2", "floor", @@ -228,7 +234,7 @@ pub(super) const OPCODE_NAMES: [&str; 69usize + 1] = [ "call", "phi", ]; -pub(super) const OPCODE_FORMAT: [InstructionFormat; 69usize + 1] = [ +pub(super) const OPCODE_FORMAT: [InstructionFormat; 71usize + 1] = [ InstructionFormat::Binary, InstructionFormat::Unary, InstructionFormat::Unary, @@ -260,6 +266,8 @@ pub(super) const OPCODE_FORMAT: [InstructionFormat; 69usize + 1] = [ InstructionFormat::Unary, InstructionFormat::Unary, InstructionFormat::Unary, + InstructionFormat::Unary, + InstructionFormat::Unary, InstructionFormat::Binary, InstructionFormat::Binary, InstructionFormat::Binary, @@ -317,7 +325,9 @@ impl std::str::FromStr for Opcode { "optbarrier" => Ok(Opcode::OptBarrier), "sqrt" => Ok(Opcode::Sqrt), "exp" => Ok(Opcode::Exp), + "expm1" => Ok(Opcode::Expm1), "ln" => Ok(Opcode::Ln), + "ln1p" => Ok(Opcode::Ln1p), "log" => Ok(Opcode::Log), "clog2" => Ok(Opcode::Clog2), "floor" => Ok(Opcode::Floor), diff --git a/openvaf/mir_autodiff/src/builder.rs b/openvaf/mir_autodiff/src/builder.rs index 2d363246..dcba8069 100644 --- a/openvaf/mir_autodiff/src/builder.rs +++ b/openvaf/mir_autodiff/src/builder.rs @@ -557,12 +557,18 @@ impl<'a, 'u> DerivativeBuilder<'a, 'u> { // exp(x) -> exp(x) // Opcode::Exp => res, Opcode::Exp => self.ins().exp(arg0), + // expm1(x) -> exp(x) + // NOT res + 1: for x << 0 expm1(x) rounds to -1 and the sum cancels to zero, + // whereas exp(x) stays accurate. + Opcode::Expm1 => self.ins().exp(arg0), // hypot(x,y) -> (x' + y')/2hypot(x,y) // sqrt(x) -> 1/2sqrt(x) Opcode::Hypot | Opcode::Sqrt => self.ins().fmul(F_TWO, res), // ln(x) -> 1/x Opcode::Ln => arg0, + // ln1p(x) -> 1/(1+x) + Opcode::Ln1p => self.ins().fadd(arg0, F_ONE), // log(x) -> log(e)/x Opcode::Log => self.ins().fdiv(F_LOG10_E, arg0), // sin(x) -> cos(x) @@ -843,6 +849,7 @@ impl<'a, 'u> DerivativeBuilder<'a, 'u> { Opcode::Fdiv => gen_div_derivative(self, arg0, arg1, false), Opcode::Exp + | Opcode::Expm1 | Opcode::Log | Opcode::Sin | Opcode::Cos @@ -859,6 +866,7 @@ impl<'a, 'u> DerivativeBuilder<'a, 'u> { } Opcode::Ln + | Opcode::Ln1p |Opcode::Sqrt | Opcode::Asin | Opcode::Acos diff --git a/openvaf/mir_autodiff/src/builder/tests.rs b/openvaf/mir_autodiff/src/builder/tests.rs index b795406a..63fa840e 100644 --- a/openvaf/mir_autodiff/src/builder/tests.rs +++ b/openvaf/mir_autodiff/src/builder/tests.rs @@ -907,6 +907,161 @@ fn third_order_log10() { check_num(src, expect, &[v11], res); } +#[test] +fn third_order_expm1() { + let src = r##" + function %bar(v10) { + fn0 = const fn %ddx_v10(1) -> 1 + + block0: + v12 = expm1 v10 + v14 = call fn0 (v12) + v15 = call fn0 (v14) + v18 = call fn0 (v15) + v100 = optbarrier v18 + }"##; + let expect = expect![[r#" + function %bar(v10) { + inst0 = const fn %ddx_v10(1) -> 1 + + block0: + v12 = expm1 v10 + v101 = exp v10 + v102 = exp v10 + v103 = exp v10 + v100 = optbarrier v103 + } + "#]]; + + let v10 = 0.5f64; + // expm1(x) = e^x - 1, so every derivative is e^x + let res = v10.exp(); + check_num(src, expect, &[v10], res); +} + +#[test] +fn second_order_expm1_chain() { + let src = r##" + function %bar(v10, v11) { + fn0 = const fn %ddx_v10(1) -> 1 + + block0: + v12 = fmul v10, v11 + v13 = expm1 v12 + v14 = call fn0 (v13) + v15 = call fn0 (v14) + v100 = optbarrier v15 + }"##; + let expect = expect![[r#" + function %bar(v10, v11) { + inst0 = const fn %ddx_v10(1) -> 1 + + block0: + v12 = fmul v10, v11 + v13 = expm1 v12 + v101 = exp v12 + v102 = fmul v11, v101 + v103 = exp v12 + v104 = fmul v11, v103 + v105 = fmul v104, v11 + v100 = optbarrier v105 + } + "#]]; + + let v10 = 0.5f64; + let v11 = 3f64; + let res = v11 * v11 * (v10 * v11).exp(); + check_num(src, expect, &[v10, v11], res); +} + +#[test] +fn third_order_ln1p() { + let src = r##" + function %bar(v10) { + fn0 = const fn %ddx_v10(1) -> 1 + + block0: + v12 = ln1p v10 + v14 = call fn0 (v12) + v15 = call fn0 (v14) + v18 = call fn0 (v15) + v100 = optbarrier v18 + }"##; + let expect = expect![[r#" + function %bar(v10) { + inst0 = const fn %ddx_v10(1) -> 1 + v3 = fconst 0.0 + v6 = fconst 0x1.0000000000000p0 + + block0: + v12 = ln1p v10 + v101 = fadd v10, v6 + v102 = fdiv v6, v101 + v103 = fmul v101, v101 + v104 = fadd v6, v3 + v105 = fmul v104, v6 + v106 = fdiv v105, v103 + v107 = fsub v3, v106 + v108 = fmul v103, v103 + v109 = fmul v104, v101 + v110 = fmul v104, v101 + v111 = fadd v109, v110 + v112 = fadd v3, v3 + v113 = fmul v112, v6 + v114 = fdiv v113, v103 + v115 = fmul v111, v105 + v116 = fdiv v115, v108 + v117 = fsub v114, v116 + v118 = fsub v3, v117 + v100 = optbarrier v118 + } + "#]]; + + let v10 = 0.5f64; + // ln1p(x) = ln(1+x), so the third derivative is 2/(1+x)^3 + let res = 2.0 / (1.0 + v10).powi(3); + check_num(src, expect, &[v10], res); +} + +#[test] +fn second_order_ln1p_chain() { + let src = r##" + function %bar(v10, v11) { + fn0 = const fn %ddx_v10(1) -> 1 + + block0: + v12 = fmul v10, v11 + v13 = ln1p v12 + v14 = call fn0 (v13) + v15 = call fn0 (v14) + v100 = optbarrier v15 + }"##; + let expect = expect![[r#" + function %bar(v10, v11) { + inst0 = const fn %ddx_v10(1) -> 1 + v3 = fconst 0.0 + v6 = fconst 0x1.0000000000000p0 + + block0: + v12 = fmul v10, v11 + v13 = ln1p v12 + v101 = fadd v12, v6 + v102 = fdiv v11, v101 + v103 = fmul v101, v101 + v104 = fadd v11, v3 + v105 = fmul v104, v11 + v106 = fdiv v105, v103 + v107 = fsub v3, v106 + v100 = optbarrier v107 + } + "#]]; + + let v10 = 0.5f64; + let v11 = 3f64; + let res = -(v11 * v11) / (1.0 + v10 * v11).powi(2); + check_num(src, expect, &[v10, v11], res); +} + #[test] fn subgraph() { let src = r##" diff --git a/openvaf/mir_interpret/src/lib.rs b/openvaf/mir_interpret/src/lib.rs index ddb9f47f..1831108f 100644 --- a/openvaf/mir_interpret/src/lib.rs +++ b/openvaf/mir_interpret/src/lib.rs @@ -152,7 +152,9 @@ impl<'a> Interpreter<'a> { mir::Opcode::OptBarrier => args(0), mir::Opcode::Sqrt => f64::sqrt(args(0).f64()).into(), mir::Opcode::Exp => f64::exp(args(0).f64()).into(), + mir::Opcode::Expm1 => f64::exp_m1(args(0).f64()).into(), mir::Opcode::Ln => f64::ln(args(0).f64()).into(), + mir::Opcode::Ln1p => f64::ln_1p(args(0).f64()).into(), mir::Opcode::Log => f64::log10(args(0).f64()).into(), mir::Opcode::Clog2 => { let val = args(0).i32(); diff --git a/openvaf/mir_llvm/src/builder.rs b/openvaf/mir_llvm/src/builder.rs index 6d8d3de2..330f50ad 100644 --- a/openvaf/mir_llvm/src/builder.rs +++ b/openvaf/mir_llvm/src/builder.rs @@ -904,7 +904,10 @@ impl<'ll> Builder<'_, '_, 'll> { Opcode::Sne => NonNull::from(self.strcmp(args, true)).as_ptr(), Opcode::Sqrt => NonNull::from(self.intrinsic(args, "llvm.sqrt.f64")).as_ptr(), Opcode::Exp => NonNull::from(self.intrinsic(args, "llvm.exp.f64")).as_ptr(), + // no LLVM intrinsics exist for these two; call libm directly + Opcode::Expm1 => NonNull::from(self.intrinsic(args, "expm1")).as_ptr(), Opcode::Ln => NonNull::from(self.intrinsic(args, "llvm.log.f64")).as_ptr(), + Opcode::Ln1p => NonNull::from(self.intrinsic(args, "log1p")).as_ptr(), Opcode::Log => NonNull::from(self.intrinsic(args, "llvm.log10.f64")).as_ptr(), Opcode::Clog2 => { let leading_zeros = @@ -954,7 +957,9 @@ impl<'ll> Builder<'_, '_, 'll> { | Opcode::Fge | Opcode::Sqrt | Opcode::Exp + | Opcode::Expm1 | Opcode::Ln + | Opcode::Ln1p | Opcode::Log | Opcode::Clog2 | Opcode::Floor diff --git a/openvaf/mir_llvm/src/intrinsics.rs b/openvaf/mir_llvm/src/intrinsics.rs index f04fd44a..c3badf0e 100644 --- a/openvaf/mir_llvm/src/intrinsics.rs +++ b/openvaf/mir_llvm/src/intrinsics.rs @@ -53,6 +53,8 @@ impl<'a, 'll> CodegenCx<'a, 'll> { ifn!("acosh", fn(t_f64) -> t_f64); ifn!("asinh", fn(t_f64) -> t_f64); ifn!("atanh", fn(t_f64) -> t_f64); + ifn!("expm1", fn(t_f64) -> t_f64); + ifn!("log1p", fn(t_f64) -> t_f64); if name == "hypot" { let name = if self.target.options.is_like_windows { "_hypot" } else { "hypot" }; diff --git a/openvaf/mir_opt/src/const_eval.rs b/openvaf/mir_opt/src/const_eval.rs index 1741a396..e26654d0 100644 --- a/openvaf/mir_opt/src/const_eval.rs +++ b/openvaf/mir_opt/src/const_eval.rs @@ -68,7 +68,9 @@ pub fn eval_unary(func: &mut Function, op: Opcode, val: Const) -> Option match op { Opcode::Sqrt => func.dfg.f64const(val.sqrt()), Opcode::Exp => func.dfg.f64const(val.exp()), + Opcode::Expm1 => func.dfg.f64const(val.exp_m1()), Opcode::Ln => func.dfg.f64const(val.ln()), + Opcode::Ln1p => func.dfg.f64const(val.ln_1p()), Opcode::Log => func.dfg.f64const(val.log10()), Opcode::Floor => func.dfg.f64const(val.floor()), Opcode::Ceil => func.dfg.f64const(val.ceil()), diff --git a/openvaf/mir_opt/src/const_prop/tests.rs b/openvaf/mir_opt/src/const_prop/tests.rs index f91ab441..5eefd53a 100644 --- a/openvaf/mir_opt/src/const_prop/tests.rs +++ b/openvaf/mir_opt/src/const_prop/tests.rs @@ -53,6 +53,61 @@ pub fn const_phi() { check(raw, expect) } +/// `expm1`/`ln1p` must be const folded rather than hitting the `unreachable!` arm +/// in `eval_unary`. +#[test] +pub fn const_expm1_ln1p() { + let raw = r##" + function %bar(v20) { + v30 = fconst 0x1.0000000000000p0 + block0: + v21 = expm1 v30 + v22 = ln1p v30 + v23 = fadd v21, v22 + v24 = fmul v23, v20 + } + "##; + + let expect = expect![[r#" + function %bar(v20) { + v32 = fconst 0x1.34a9b4ad2e5e5p1 + block0: + v24 = fmul v32, v20 + } + "#]]; + + check(raw, expect) +} + +/// `ln1p` exists precisely because `ln(1+x)` loses all precision for small `x`. +/// Const folding must keep that property: with x = 1e-16 the `ln1p` result stays +/// 1e-16 while the naive `ln(1+x)` in the same function folds to 0. +#[test] +pub fn const_ln1p_precision() { + let raw = r##" + function %bar(v20) { + v30 = fconst 0x1.cd2b297d889bcp-54 + v31 = fconst 0x1.0000000000000p0 + block0: + v21 = ln1p v30 + v32 = fadd v31, v30 + v33 = ln v32 + v22 = fmul v21, v20 + v23 = fmul v33, v20 + } + "##; + + let expect = expect![[r#" + function %bar(v20) { + v30 = fconst 0x1.cd2b297d889bcp-54 + block0: + v22 = fmul v30, 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 fad43524..704c4151 100644 --- a/openvaf/mir_opt/src/simplify.rs +++ b/openvaf/mir_opt/src/simplify.rs @@ -120,6 +120,8 @@ impl<'a, FP: Arithmetic, M: Fn(Value, &Function) -> Value> SimplifyCtx<'a, FP, M } Opcode::Exp => Opcode::Ln, Opcode::Ln => Opcode::Exp, + Opcode::Expm1 => Opcode::Ln1p, + Opcode::Ln1p => Opcode::Expm1, Opcode::Log => { if let Some([x, y]) = self.as_binary(arg, Opcode::Pow) { if x == F_TEN { diff --git a/openvaf/syntax/src/name.rs b/openvaf/syntax/src/name.rs index 290a7d73..703351f8 100644 --- a/openvaf/syntax/src/name.rs +++ b/openvaf/syntax/src/name.rs @@ -166,6 +166,14 @@ pub mod kw { pub const use_: super::Name = super::Name::new_inline("use"); #[allow(bad_style, dead_code)] pub const root: super::Name = super::Name::new_inline("$root"); + // VAMS-2023 reserves expm1/ln1p, but compact models written against older + // revisions define their own functions with these names (e.g. HiSIMSOTB). + // Declared outside `keywords!` so they are not reported as reserved and a + // user-defined function keeps shadowing the builtin. + #[allow(bad_style, dead_code)] + pub const expm1: super::Name = super::Name::new_inline("expm1"); + #[allow(bad_style, dead_code)] + pub const ln1p: super::Name = super::Name::new_inline("ln1p"); keywords! { above, abs, @@ -504,8 +512,10 @@ pub mod sysfun { abs, clog2, ln, + ln1p, log10, exp, + expm1, sqrt, pow, floor, diff --git a/openvaf/test_data/mir/expm1_shadowing.mir b/openvaf/test_data/mir/expm1_shadowing.mir new file mode 100644 index 00000000..cb5e2801 --- /dev/null +++ b/openvaf/test_data/mir/expm1_shadowing.mir @@ -0,0 +1,13 @@ +function %(v16, v19, v22) { + v6 = fconst 0x1.0000000000000p0 + block0: +@0003 v17 = exp v16 +@0005 v18 = fsub v17, v6 +@0004 v20 = fadd v6, v16 +@0005 v21 = ln v20 + v23 = optbarrier v18 + v24 = optbarrier v21 + jmp block1 + + block1: +} diff --git a/openvaf/test_data/mir/expm1_shadowing.va b/openvaf/test_data/mir/expm1_shadowing.va new file mode 100644 index 00000000..2d7758fc --- /dev/null +++ b/openvaf/test_data/mir/expm1_shadowing.va @@ -0,0 +1,26 @@ +// A user-defined analog function must keep shadowing the expm1/ln1p builtins +// added for VAMS-2023. Several in-tree compact models (for example HiSIMSOTB) +// declare their own `expm1`, so this must not become an error or silently +// resolve to the builtin. +module test; + parameter real p = 0.5; + real a; + real b; + + analog function real expm1; + input x; + real x; + expm1 = exp(x) - 1.0; + endfunction + + analog function real ln1p; + input x; + real x; + ln1p = ln(1.0 + x); + endfunction + + analog begin + a = expm1(p); + b = ln1p(p); + end +endmodule diff --git a/openvaf/test_data/mir/vams2023_math.mir b/openvaf/test_data/mir/vams2023_math.mir new file mode 100644 index 00000000..d888d95a --- /dev/null +++ b/openvaf/test_data/mir/vams2023_math.mir @@ -0,0 +1,14 @@ +function %(v16, v18, v20, v22, v24) { + block0: +@0003 v17 = expm1 v16 +@0006 v19 = ln1p v16 +@0009 v21 = expm1 v16 +@000c v23 = ln1p v16 + v25 = optbarrier v17 + v26 = optbarrier v19 + v27 = optbarrier v21 + v28 = optbarrier v23 + jmp block1 + + block1: +} diff --git a/openvaf/test_data/mir/vams2023_math.va b/openvaf/test_data/mir/vams2023_math.va new file mode 100644 index 00000000..7c2437cd --- /dev/null +++ b/openvaf/test_data/mir/vams2023_math.va @@ -0,0 +1,15 @@ +// expm1/ln1p from Verilog-AMS VAMS-2023 (Mantis 7780), in both the +// Verilog-AMS spelling and the Verilog `$` spelling. +module test; + parameter real x = 0.5; + real a; + real b; + real c; + real d; + analog begin + a = expm1(x); + b = ln1p(x); + c = $expm1(x); + d = $ln1p(x); + end +endmodule diff --git a/openvaf/test_data/mir/vams2023_sysfun_math.mir b/openvaf/test_data/mir/vams2023_sysfun_math.mir new file mode 100644 index 00000000..696dcbdf --- /dev/null +++ b/openvaf/test_data/mir/vams2023_sysfun_math.mir @@ -0,0 +1,82 @@ +function %(v16, v17, v20, v23, v27, v28, v29, v32, v35, v39) { + v3 = fconst 0.0 + v4 = iconst 0 + block0: +@0004 v18 = flt v16, v17 +@0004 br v18, block2, block3 + + block2: +@0004 jmp block4 + + block3: +@0004 jmp block4 + + block4: +@0004 v19 = phi [v16, block2], [v17, block3] +@0008 v21 = fgt v16, v17 +@0008 br v21, block5, block6 + + block5: +@0008 jmp block7 + + block6: +@0008 jmp block7 + + block7: +@0008 v22 = phi [v16, block5], [v17, block6] +@000b v24 = flt v16, v3 +@000b br v24, block8, block9 + + block8: +@000b v25 = fneg v16 +@000b jmp block10 + + block9: +@000b jmp block10 + + block10: +@000b v26 = phi [v25, block8], [v16, block9] +@000f v30 = ilt v28, v29 +@000f br v30, block11, block12 + + block11: +@000f jmp block13 + + block12: +@000f jmp block13 + + block13: +@000f v31 = phi [v28, block11], [v29, block12] +@0013 v33 = igt v28, v29 +@0013 br v33, block14, block15 + + block14: +@0013 jmp block16 + + block15: +@0013 jmp block16 + + block16: +@0013 v34 = phi [v28, block14], [v29, block15] +@0016 v36 = ilt v28, v4 +@0016 br v36, block17, block18 + + block17: +@0016 v37 = ineg v28 +@0016 jmp block19 + + block18: +@0016 jmp block19 + + block19: +@0016 v38 = phi [v37, block17], [v28, block18] + v55 = optbarrier v19 + v68 = optbarrier v22 + v78 = optbarrier v26 + v85 = optbarrier v31 + v89 = optbarrier v34 + v90 = optbarrier v38 + jmp block1 + + block1: +} diff --git a/openvaf/test_data/mir/vams2023_sysfun_math.va b/openvaf/test_data/mir/vams2023_sysfun_math.va new file mode 100644 index 00000000..3fe9a15a --- /dev/null +++ b/openvaf/test_data/mir/vams2023_sysfun_math.va @@ -0,0 +1,20 @@ +// VAMS-2023 (Mantis 7795) added the alternative Verilog `$` spelling for +// min/max/abs. These resolve to the same builtins as the bare names, so both +// the real and the integer overload have to keep working. +module test; + parameter real rx = 0.5; + parameter real ry = 1.5; + parameter integer ix = 2; + parameter integer iy = 3; + real a, b, c; + integer d, e, f; + analog begin + a = $min(rx, ry); + b = $max(rx, ry); + c = $abs(rx); + + d = $min(ix, iy); + e = $max(ix, iy); + f = $abs(ix); + end +endmodule diff --git a/openvaf/test_data/osdi/vams2023_math.snap b/openvaf/test_data/osdi/vams2023_math.snap new file mode 100644 index 00000000..3d01b300 --- /dev/null +++ b/openvaf/test_data/osdi/vams2023_math.snap @@ -0,0 +1,18 @@ +param "$mfactor" +units = "", desc = "Multiplier (Verilog-A $mfactor)", flags = ParameterFlags(PARA_KIND_INST) +param "is" +units = "A", desc = "Saturation current", flags = ParameterFlags(0x0) +param "n" +units = "", desc = "Emission coefficient", flags = ParameterFlags(0x0) +param "gleak" +units = "S", desc = "Leakage conductance", flags = ParameterFlags(0x0) + +2 terminals +node "A" units = "V", runits = "A" +node "C" units = "V", runits = "A" +jacobian (A, A) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (A, C) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (C, A) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +jacobian (C, C) JacobianFlags(JACOBIAN_ENTRY_RESIST | JACOBIAN_ENTRY_REACT_CONST) +0 states +has bound_step false diff --git a/sourcegen/src/hir_builtins.rs b/sourcegen/src/hir_builtins.rs index a7ab25db..b7687c05 100644 --- a/sourcegen/src/hir_builtins.rs +++ b/sourcegen/src/hir_builtins.rs @@ -81,7 +81,7 @@ const ANALOG_OPERATORS_SYSFUN: [&str; 1] = ["$limit"]; const ANALYSIS_FUNS: [&str; 6] = ["analysis", "ac_stim", "noise_table", "noise_table_log", "white_noise", "flicker_noise"]; -const BUILTINS: [&str; 26] = [ +const BUILTINS: [&str; 28] = [ "abs", "acos", "acosh", @@ -94,11 +94,13 @@ const BUILTINS: [&str; 26] = [ "cos", "cosh", "exp", + "expm1", "floor", "flow", "potential", "hypot", "ln", + "ln1p", "log", "max", "min", @@ -112,7 +114,7 @@ const BUILTINS: [&str; 26] = [ const PARAM_SYSFUNS: [&str; 6] = ["mfactor", "xposition", "yposition", "angle", "hflip", "vflip"]; -const SYSFUNS: [&str; 81] = [ +const SYSFUNS: [&str; 86] = [ "$display", "$strobe", "$write", @@ -161,8 +163,10 @@ const SYSFUNS: [&str; 81] = [ "$rdist_t", "$clog2", "$ln", + "$ln1p", "$log10", "$exp", + "$expm1", "$sqrt", "$pow", "$floor", @@ -181,6 +185,9 @@ const SYSFUNS: [&str; 81] = [ "$asinh", "$acosh", "$atanh", + "$min", + "$max", + "$abs", "$temperature", "$vt", "$simparam", diff --git a/sourcegen/src/mir_instructions.rs b/sourcegen/src/mir_instructions.rs index 443e2ffc..6d0a76e4 100644 --- a/sourcegen/src/mir_instructions.rs +++ b/sourcegen/src/mir_instructions.rs @@ -77,7 +77,9 @@ opcodes! { Sqrt Exp + Expm1 Ln + Ln1p Log Clog2 Floor