Add expm1/ln1p math builtins (VAMS-2023) - #20
Merged
Conversation
Verilog-AMS VAMS-2023 (Mantis 7780) adds `expm1` and `ln1p` alongside the
existing math builtins. Both are wired up the same way as `exp`/`ln`: as new
MIR unary opcodes, lowered to the libm `expm1`/`log1p` symbols since LLVM has
no intrinsics for them. Both the plain and `$`-prefixed spellings are accepted.
Derivatives:
d/dx expm1(x) = exp(x)
d/dx ln1p(x) = 1/(1+x)
The derivative of expm1 emits `exp(x)` rather than reusing the already
computed `expm1(x) + 1`, which would cancel to zero for large negative x.
The two names are deliberately *not* added to the reserved keyword set even
though VAMS-2023 reserves them: HiSIMSOTB and other compact models written
against older revisions declare their own `expm1` function, and those
declarations must keep shadowing the builtin.
Tests:
- numeric autodiff checks up to third order plus chain rule cases
- MIR snapshots for both spellings
- MIR snapshot proving a user-defined `expm1` still shadows the builtin
- const folding, including a precision check that ln1p(1e-16) stays 1e-16
where the naive ln(1+x) folds to 0
`Builder::intrinsic` panics with "intrinsic not found" unless the symbol is declared in `CodegenCx::intrinsic`, so codegen would have aborted on the first model that actually called expm1()/ln1p(). No in-tree model used the new builtins (HiSIMSOTB declares its own expm1), so the OSDI/LLVM harness could not have caught this. Added an integration model that uses both, which the osdi and openvaf integration harnesses compile through LLVM.
Co-authored-by: Cursor <cursoragent@cursor.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements
expm1()andln1p()from Verilog-AMS VAMS-2023 (Mantis 7780, table in 4.3.1). This is the first slice of the VAMS-2023 alignment from #19 — smallest self-contained piece first so the approach can be checked before I do the larger items.To answer your question from #19 directly: no, I don't have a VAMS-2023 implementation to diff against. So instead of trusting a reference, I pinned every behaviour to something checkable — the closed-form derivatives,
f64::exp_m1/f64::ln_1pfor the constant-folding path, and the LRM's own accuracy statements. Details below.Why these aren't just wrappers
The LRM adds them for accuracy, and the difference is total rather than marginal:
ln(1 + 1e-16)0.0ln1p(1e-16)1e-16The in-tree
DIODEmodel already computesis*(limexp(vd/(n*vt)) - 1), which is exactly the subtractionexpm1exists to avoid.What's wired up
New MIR unary opcodes
Expm1/Ln1p, lowered to the libmexpm1/log1psymbols since LLVM has no intrinsics for either. Both the plain and the$-prefixed spelling are accepted, matching the LRM table.Derivatives:
The
expm1rule emits a freshexp(x)rather than reusing the already computedexpm1(x) + 1. That shortcut looks cheaper but is wrong: forx = -40,expm1(x)rounds to exactly-1.0, soexpm1(x) + 1cancels to0, while the trueexp(-40)is4.25e-18.Two things worth your call
1. I did not add these to the reserved keyword set, even though VAMS-2023 reserves them.
HiSIMSOTB(in-tree,hisimsotb.va:1124) declares its ownanalog function real expm1, and other models written against older revisions do the same. Reserving the names turns those into hard errors.I first tried routing them through
kw_compso they'd produce the existing warn-by-defaultvams_keyword_compatlint. That works and keeps the models compiling, but the lint's note reads "will likely never be used in the implemented language subset", which is now false — sokw_compis the wrong bucket. I left the names unreserved instead, which keeps this PR purely additive: a user-defined function still shadows the builtin, and no existing model changes behaviour. Happy to add a properly worded lint in a follow-up if you'd rather flag it.2.
Builder::intrinsicpanics for unregistered symbols.CodegenCx::intrinsichas to declare the libm name or codegen aborts withintrinsic not found. I hit this, and no in-tree model would have caught it, becauseHiSIMSOTBshadowsexpm1and nothing else used the new builtins. That's why this PR also adds an integration model — otherwise the LLVM path stays uncovered in CI.Testing
Verified locally against LLVM 18.1.8:
cargo test -p osdi— 27/27, including the newVAMS2023_MATHmodel compiled through LLVM/OSDIcargo test -p openvaf --test integration(RUN_DEV_TESTS=1 RUN_SLOW_TESTS=1) — 71/71, which compiles the new model to a shared library,dlopens it and processes its parametersRUN_DEV_TESTS=1— all 26 pre-existing compact models unchangedcargo fmt -- --checkcleanNew tests:
expm1still shadows the builtin (it lowers to the inlinedexp(x) - 1.0body, not the opcode)ln1p(1e-16)folds back to1e-16while the naiveln(1+x)in the same function folds to0One pre-existing failure is unrelated and also fails on a clean
mobcheckout:sourcegen osdi::gen_osdi_structspanics atsourcegen/src/osdi.rs:200.