bosdi — Batched OSDI
Experimental — bosdi is under active development. The OSDI binary evaluation path is stable and well-tested, but the Verilog-A to JAX lowering compiler (
bosdi.va) is in alpha and its API may change without notice. The VA lowering depends on a custom fork of OpenVAF that exposes the compiler's intermediate representation; this fork is not yet merged upstream.
Evaluate OSDI device models (Verilog-A compiled to .osdi binaries) in batched
parallel via JAX.
Two evaluation paths
bosdi provides two ways to evaluate Verilog-A compact models inside JAX:
OSDI binary path (stable)
Loads a pre-compiled .osdi binary and evaluates N device instances in parallel via Rayon inside a JAX XLA custom call.
The OSDI ABI provides analytical Jacobians with respect to node voltages only (conductances dI/dV, capacitances
dQ/dV). A @custom_jvp rule makes jax.grad() work through node voltages — but not through model parameters or
state.
from osdi_loader import load_osdi_model
from osdi_jax import osdi_eval
model = load_osdi_model("path/to/device.osdi")
N = 1024
voltages = jnp.zeros((N, model.num_nodes), dtype=jnp.float64)
params = jnp.full((N, model.num_params), jnp.nan, dtype=jnp.float64)
old_state = jnp.zeros((N, model.num_states), dtype=jnp.float64)
cur, cond, chg, cap, new_state = osdi_eval(model.id, voltages, params, old_state)
# jax.grad works through node voltages
grad_fn = jax.grad(lambda v: osdi_eval(model.id, v, params, old_state)[0].sum())
VA to JAX lowering (alpha)
Compiles Verilog-A source directly into pure JAX/Python, producing a function that is fully differentiable through all inputs — voltages, parameters, and temperature. This enables parameter optimization, sensitivity analysis, and end-to-end gradient-based design flows that the OSDI path cannot support.
Requires openvaf-r (a custom OpenVAF fork).
python -m bosdi.va device.va
When to use which
| OSDI binary | VA to JAX | |
|---|---|---|
| Use case | Circuit simulation (Newton solve) | Parameter fitting, sensitivity analysis, inverse design |
| Differentiable w.r.t. | Node voltages only | Voltages, parameters, and temperature |
| Performance | Fast — Rayon-parallel C/Rust, batched XLA FFI | Pure Python/JAX — slower per-eval, but composable with jax.jit/jax.vmap |
| Maturity | Stable | Alpha |
| Dependencies | None beyond bosdi | openvaf-r fork |
The OSDI path treats the compiled model as a black box and extracts only what the ABI exposes: currents, charges, and their Jacobians w.r.t. node voltages. This is exactly what a Newton solver needs, but the parameter axis is opaque to JAX — you cannot backpropagate through it.
The VA to JAX path exists to remove that limitation. By lowering the Verilog-A source into native JAX operations, every computation becomes visible to JAX's autodiff, making the model fully differentiable. This is what enables gradient-based parameter extraction, design-space exploration, and end-to-end optimization of circuits where device parameters are the degrees of freedom.
Architecture
OSDI path:
Python: osdi_eval() → JAX XLA custom call
→ C++ (nanobind/XLA FFI): unpack buffers
→ Rust (Rayon): evaluate N devices in parallel
→ OSDI binary: currents, conductances, charges, capacitances
VA path:
Verilog-A source → openvaf-r (MIR dump)
→ bosdi.va lowering + SCCP optimization
→ Pure JAX/Python function (fully differentiable)
Installation
Using Pixi (recommended)
git clone https://github.com/gdsfactory/bosdi && cd bosdi
pixi run build
Using pip
pip install bosdi
Build & test
pixi run build # compile Rust static lib + C++ extension
pixi run test # standalone pytest suite; Circulax is not required
git submodule update --init tests/pdks/ihp # pinned IHP device libraries
pixi run --locked -e integration test-integration # build and test with Circulax + IHP
# single test
pixi run pytest tests/test_osdi.py::test_resistor_dc_evaluation -v
The integration environment has its own solve group and includes Circulax only for testing. It builds this checkout's
native extension before running public DC/AC/transient, simulator-settings and generated-component checks. Both Linux
and Windows CI run it alongside the standalone suite. Circulax is temporarily pinned to the immutable integration commit
for PR #64; replace that pin with an upstream release once the required public native APIs are released. No Circulax
Verilog-A extra is requested, so tests use this checkout's bosdi rather than installing a second copy. The netlists
extra provides its model-card parser. OpenVAF must be on PATH; JSON lowering tests additionally need the custom
compiler's dump support.
The IHP integration suite uses the pinned gdsfactory/IHP submodule in tests/pdks/ihp, rather than copied model
fixtures. It enumerates all 34 SG13G2 VACASK subcircuits and checks compiled, JIT-executed DC and small-signal responses
at 1 MHz and 1 GHz against VACASK. Updating the submodule adds a failing catalogue check if new devices need test cases.
VACASK is pinned to an OSDI 0.4-compatible build and is a test-only dependency. Its SPICE primitives are compiled from
the existing test sources on every platform because the Windows wheel does not bundle OSDI modules.
These are typical-corner device checks at explicit sizes and biases, not complete process qualification. The test
harness hoists repeated common includes, grounds implicit BJT substrate terminals, and folds varactor voltage terms only
after verifying their coefficients are zero. Isolation diodes use a nonzero well spacing to avoid an upstream ln(0)
expression; the wrapper forwards public geometry parameters while child cards recompute private derived values. The
upstream checkout stays unchanged, and these parser adaptations do not imply support for arbitrary voltage-dependent
model-card expressions. The test-only PDK is excluded from source packages.
OSDI outputs
The OSDI path returns per-device arrays shaped by model.num_nodes (terminals + internal nodes + branch-current
auxiliaries):
| Output | Shape | Description |
|---|---|---|
cur |
[N, num_nodes] |
Resistive current residual at each unknown |
cond |
[N, num_nodes²] |
G = ∂cur/∂V Jacobian (flattened row-major) |
chg |
[N, num_nodes] |
Charge residual at each unknown |
cap |
[N, num_nodes²] |
C = ∂chg/∂V Jacobian (flattened row-major) |
Pass jnp.nan for any parameter to use its Verilog-A default. Parameters can be addressed by name via
model.param_names. See tests/test_bsim4_model_card.py for a full example.
Further reading
- OSDI technical reference — parameter handling, model introspection, output layout, host-simulator integration (companion method vs MNA/DAE), and debug utilities
Limitations
- Platform: Linux, macOS, and Windows; Python 3.11+; OSDI 0.4 ABI only.
.osdibinaries are platform-specific — compile from.vasources via openvaf-r on each target - OSDI differentiability:
jax.grad()works through node voltages only, not model parameters — use the VA path for parameter gradients - ABI states (
num_states > 0): the descriptor rejects these by default. Audited OpenVAF voltage-limiting slots may use the explicit policy described below; generic history-dependent state is unsupported. - VA lowering (alpha): user-defined
analog functioncalls and noise contributions are not yet supported
Native OSDI node collapse
OsdiModel.num_nodes includes every raw OSDI node, including internal nodes that instance setup may collapse. Allocate
voltage/state buffers using the model metadata rather than the external terminal count. The evaluator applies only
setup_instance's selected collapse flags, and represents unused raw node slots with voltage-equality equations. This
preserves a fixed shape for batches whose instances have different parasitic resistances. Both cached and uncached
native paths use the same mapping. Physical currents and charges are stamped into the surviving node; equality rows
carry no charge.
Integral operators and analysis modes
load_osdi_model(..., analysis="dc" | "ac" | "tran") and osdi_component(..., analysis=...) select an immutable
evaluation mode for a model registration. The default "ac" preserves the full current/charge stamp API. DC disables
reactive evaluation so idt uses its explicit initial-value equation. AC and transient enable integration consistently
in both full and residual-only evaluation. OpenVAF uses CALC_REACT_JACOBIAN to select these integral equations, so
clearing it only for a residual-only call is incorrect.
To reproduce VACASK AC, first solve DC and retain its conductance matrix. Then evaluate the reactive Jacobian in AC mode
at that operating point and solve (G_dc + j*omega*C_ac) x = rhs. A new registration/handle is required to change mode.
Transient callers must provide a DC-consistent initial point. This does not add general state-history or $abstime
support.
OpenVAF voltage-limiting slots
OpenVAF derives OSDI num_states from its $limit slots. These Newton limiting buffers are distinct from physical
ddt charges and idt unknowns, which are represented by the circuit DAE. For an audited binary whose slots serve only
voltage limiting, use osdi_component(..., state_policy="limiting_only"). Bosdi leaves ENABLE_LIM disabled, evaluates
the unmodified device equations, and does not propagate ABI state outputs. This can reduce convergence robustness
compared with a simulator that enables limiting. It does not add fictitious delayed unknowns.
The default state_policy="reject" remains appropriate for an unaudited binary. The ABI does not describe what its
state slots mean; limiting_only is an explicit assertion by the caller, not automatic compiler detection. Generic
history-dependent models, $abstime, and enabled voltage limiting still need a simulator lifecycle implementation.
descriptor.with_analysis("dc" | "ac" | "tran") returns cached immutable registrations preserving the binary path,
ports, defaults, temperature, and state policy. Circulax uses these registrations to orchestrate native analyses.
Registration cache lifetime
load_osdi_model reuses native registration IDs across newly created descriptors and circuits using a process-local
@lru_cache(maxsize=128). The key includes canonical binary path, filesystem identity/size/timestamps, ABI, temperature
and analysis mode. Concurrent misses are serialized. Callers receive independent metadata copies; port names and
model-card defaults remain descriptor-local. Failed loads are not cached.
This bounds Python cache entries, not the native registry: native IDs remain alive for existing circuits and JIT executables until the process exits. Rebuild binaries under new/content-addressed paths (as Circulax's compilation cache does); replacing a live shared-library file in place is not a supported reload strategy. Native IDs and batch handles cannot be persisted across processes; compiled binary artifacts can.
Batch setup handles remain circuit-owned because they carry instance parameters and expose free(). A global LRU of
those mutable handles would require shared ownership and invalidation before it could safely be introduced. The
per-descriptor/per-circuit analysis caches have only three valid modes and retain their local lifetime.
Releases
The Git tag is the Python and Pixi package version. No manual version bump is needed. setuptools-scm writes
bosdi/_version.py during the build and preserves the version in source archives. The Cargo package is a private static
library with its own internal version; it is not published to crates.io.
After merging the changes to main, tag the release commit and push the tag:
git switch main
git pull --ff-only
git tag -a v0.1.8 -m "Release 0.1.8"
git push origin v0.1.8
Use the next unused version. The release workflow builds Python 3.12/3.13 wheels for Linux, macOS, and Windows, runs the
release tests, and checks every artifact's filename and embedded version against the tag before publishing. Stable tags
(vX.Y.Z) publish to PyPI; prereleases (vX.Y.Za1, vX.Y.Zb1, vX.Y.Zrc1, or vX.Y.Z.dev1) publish to TestPyPI and
are marked as prereleases on GitHub.
An existing GitHub release is updated and receives the build artifacts. Failed workflow jobs can be rerun; already published PyPI filenames are skipped. Package contents for a published version are immutable, so code changes require a new tag. Tagging alone does not update the committed changelog; review its Unreleased section before releasing.
The old v0.1.7 release did not publish a 0.1.7 package. Publish the repaired setup under a new unused tag rather
than moving the existing release tag.
Metadata
Release files for bosdi 0.1.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| bosdi-0.1.8.tar.gz | 1.0 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| bosdi-0.1.8-cp313-cp313-win_amd64.whl | CPython 3.13 | CPython 3.13 | Windows x86-64 | Details |
| bosdi-0.1.8-cp313-cp313-manylinux_2_28_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.28+ x86-64 | Details |
| bosdi-0.1.8-cp313-cp313-macosx_11_0_arm64.whl | CPython 3.13 | CPython 3.13 | macOS 11.0+ ARM64 | Details |
| bosdi-0.1.8-cp312-cp312-win_amd64.whl | CPython 3.12 | CPython 3.12 | Windows x86-64 | Details |
| bosdi-0.1.8-cp312-cp312-manylinux_2_28_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.28+ x86-64 | Details |
| bosdi-0.1.8-cp312-cp312-macosx_11_0_arm64.whl | CPython 3.12 | CPython 3.12 | macOS 11.0+ ARM64 | Details |
Total release size: 9.7 MB
Release files / bosdi-0.1.8.tar.gz
| Download URL | bosdi-0.1.8.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
fa6a1b97ee07d31929df5e29057f7c1e28409f761014bf5dfcce3bc1c8d1aeb1
|
|
BLAKE2b-256 checksum How to use checksums |
e91c6550e7c78a5a28f39a01b246a9f7a35c84e315f46ef552fc9541f2755560
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / bosdi-0.1.8-cp313-cp313-win_amd64.whl
| Download URL | bosdi-0.1.8-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 368.1 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
9400d3c43603fe27a46f6e720852cb194945fd1d3c3d1d208c3d7ffd2370dcfb
|
|
BLAKE2b-256 checksum How to use checksums |
5c28dd483ea427b6cff89e5be7340220d0d818466694dc2acfe181a2a71dfc46
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / bosdi-0.1.8-cp313-cp313-manylinux_2_28_x86_64.whl
| Download URL | bosdi-0.1.8-cp313-cp313-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.13 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
11fc94d01e4fed40e03fc589a39cf2091f170f1abef0a80a13278bc018447e33
|
|
BLAKE2b-256 checksum How to use checksums |
a4506c7752dca9148b54534cc817aed13acbf42c5058150de82287867971c410
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / bosdi-0.1.8-cp313-cp313-macosx_11_0_arm64.whl
| Download URL | bosdi-0.1.8-cp313-cp313-macosx_11_0_arm64.whl |
|---|---|
| Size | 865.4 kB |
| Tags | CPython 3.13 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d0fb3464d927b9cbffb1cbd335b2e570b9a19442e125d9ba9c4913ce89dd3325
|
|
BLAKE2b-256 checksum How to use checksums |
772f29c8ed4c7c4b97242c7d5946519a4e18339c94dea8633db7ecffee83ed3c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / bosdi-0.1.8-cp312-cp312-win_amd64.whl
| Download URL | bosdi-0.1.8-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 368.1 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
de136da29f2deb3eb5d9fbebe1a822e5721e9643e7701ff74f53a0a823fbe4dc
|
|
BLAKE2b-256 checksum How to use checksums |
f7dc31e8947f7cbc5ffd4b720becf808a28f9143599c4c625cab3b85155353f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / bosdi-0.1.8-cp312-cp312-manylinux_2_28_x86_64.whl
| Download URL | bosdi-0.1.8-cp312-cp312-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.12 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
4b0e43bfaf31a7e98b1789d92c800fcdeb17c710661295beaf3d672d7a523155
|
|
BLAKE2b-256 checksum How to use checksums |
281ea4941ed1a4833dff114459ea24f20df6be61b06e908a135c6a33c1f0f137
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency logRelease files / bosdi-0.1.8-cp312-cp312-macosx_11_0_arm64.whl
| Download URL | bosdi-0.1.8-cp312-cp312-macosx_11_0_arm64.whl |
|---|---|
| Size | 865.3 kB |
| Tags | CPython 3.12 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
8e5bc3bf326273ecfd68b2bb9e2d4d1edd865cf489405afdeff0d1647ea5e10e
|
|
BLAKE2b-256 checksum How to use checksums |
f8ce7099ab15b2eee1b66731977c3f659034034fe5023be996a193e599252a0f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.
Transparency log