# OSDI Devices

`OsdiDevice` (`osdidevice.h`/`osdidevice.cpp`) is the [`Device`](devbase.md) implementation for one device descriptor within a loaded [`OsdiFile`](osdifile.md) - the OSDI counterpart to [`BuiltinDevice<...>`](devbuiltin.md), but a single concrete class (not a template) since an OSDI device's parameter set isn't known until the shared library is loaded at runtime, so it can't be captured as C++ template parameters the way a builtin device's `ModelParams`/`InstanceParams`/`InstanceData` structs are.

## Mostly a scoped forwarding layer

Most of `OsdiDevice`'s public API - `instanceParameterIndex()`, `modelParameterIndex()`, `outvarIndex()`, the `*Count()`/`*Name()`/`*OsdiParameterId()` accessors, node and noise-source lookups, `nonzero*()` - is a thin wrapper that forwards to the matching [`OsdiFile`](osdifile.md) method with this device's own `index_` already filled in, so [`OsdiModel`](osdimodel.md)/[`OsdiInstance`](osdiinstance.md) can query "what's my parameter count/name/type" without repeating the device index at every call site.

`getDataPtr<T>(objPtr, offset)` is a small free function (`reinterpret_cast<T>((char*)objPtr + offset)`) used throughout this device family to reach into an opaque per-instance/per-model blob at a byte offset the OSDI descriptor supplies - since the OSDI ABI describes its data layout by offset into an untyped block rather than a shared C++ struct, this is how the simulator and the compiled Verilog-A code agree on where a given piece of data lives without sharing a header.

## Parameter access through the descriptor's `access()` callback

`readParameter()`/`writeParameter()`/`parameterPtr<T>()` are the actual parameter-value bridge: rather than the offset-based [`Introspection`](introspection.md) trick builtin devices use, OSDI parameter access goes through `descriptor_->access(instance, model, osdiId, flags)` - a function pointer the compiled shared library provides, which returns a raw pointer into the opaque instance/model blob for a given parameter id and access flags (read vs. write, instance vs. model). `parameterPtr<T>()` additionally validates the requested pointer type (`int`/`double`) against the parameter's declared OSDI type before handing out the pointer, refusing anything else; `readParameter()`/`writeParameter()` build on this to convert between that raw pointer and a [`Value`](value.md). `freeValues()` releases any heap-allocated (string/vector) parameter storage inside an instance/model blob - since only OSDI (not the simulator) knows how that storage was allocated, this too goes through the descriptor.

## Crossing into the OSDI ABI

`populateSimParas()`/`updateSimInfo()` translate the simulator's own `SimulatorOptions` and [`EvalSetup`](elsetup.md) into OSDI's own C structs (`OsdiSimParas`, `OsdiSimInfo`) that get passed by pointer into the compiled module's `eval()` entry point - this pair of functions is the literal boundary where the simulator's C++ data crosses into OSDI's C ABI. `processInitInfo()` interprets an `OsdiInitInfo` the descriptor's `setup`-equivalent callback returns (parameter/topology errors, `$abort`/`$finish`/`$stop` requests) back into a [`Status`](status.md)/[`DeviceRequests`](elsetup.md).

## Structural descriptor accessors

`collapsedNodesPatternSize()`, `jacobianEntriesCount()`/`jacobianEntry()`, `internalStateCount()`, and `tolerances()` expose the fixed, per-device-type structural information (collapsible node patterns, the Jacobian sparsity pattern, state count, per-node tolerance natures) the descriptor declares once for the device type - as opposed to the per-instance data reached via `getDataPtr()`/`access()` above.
