---
title: Open Source Device Interface (OSDI) Specification - Working Draft
subtitle: Version 0.1
author:
name: SemiMod UG (haftungsbeschränkt)
...
\pagebreak
# Introduction
All circuit simulators have their own unique interface for incorporating compact semiconductor device models.
This slows down model integration, development, distribution and standardization.
To remedy this situation, the Verilog-A language has been introduced as a means of having a unified description of compact models.
Most simulators have used the transpiler ADMS for incorporating Verilog-A models, yet ADMS's XML file based
transpilation approach has significant disadvantages w.r.t. execution time, compilation time and language standard support.
OSDI targets to overcome these disadvantages by defining a simulator-independent interface for compiled models.
Models will be compiled to shared object files that adhere to the OSDI interface.
Then, the simulation engine just needs to implement code that makes this interface suitable for the respective engine.
The open-source Verilog-A compiler OpenVAF will serve as a back-end for OSDI.
Foremost, this will position OpenVAF and OSDI as the new open-source standard compiler for Verilog-A models,
which in turn would be excellent for Verilog-A standardization.
Furthermore, having a ready-to-use compiler will help circuit simulators obtain Verilog-A language support quicker, at reduced implementation and time effort.
# General Overview
Each compiled object file exports the following four symbols:
* `uint32_t` **`OSDI_VERSION_MAJOR`**
* `uint32_t` **`OSDI_VERSION_MINOR`**
* `OsdiDescriptor[]` **`OSDI_DESCRIPTORS`**
* `uint32_t` **`OSDI_NUM_DESCRIPTORS`**
The symbols `OSDI_VERSION_MAJOR` and `OSDI_VERSION_MINOR` indicate the OSDI version that the object file was compiled with.
Simulators that implement OSDI version `X.Y` must be able to load all shared libraries with `OSDI_VERSION_MAYOR = X` and `OSDI_VERSION_MINOR <=Y`.
While OSDI is under developoment (Version `X=0`) simulator implementations only have to support `OSDI_VERSION_MINOR =Y`.
`OSDI_DESCRIPTORS` is a list of device descriptors of length `OSDI_NUM_DESCRIPTORS`.
A device descriptor is an instance of the `OsdiDescriptor` struct that contains all compiled information about a device model
(Verilog-A module).
It encodes everything required to simulate the model and is the main API surfe of OSDI.
Compared to more traditional hardcoded models OSDI model do not call simulator specific functions (with a couple exceptions).
Instead `OsdiDescriptor` contains a lot of static metadata.
This allows more flexability for the simulator implementation.
Compiler implementations are also simplified because static data is less complex than executable code.
To facilitate this metadata OSDI contains a larger number of datastructures.
The static metata is closely intertwined with the behaviour of the compiled functions.
Therefore describing the metadata and functions seperatly would be inadequate.
Instead the overall function of the interface will be roughly explained for each simulation stages.
Afterwards all datastructures and routines are formally documented.
## Terminology
To adequatly describe the various stages a consistent set of terminology is required.
There a three basic entities that are at the centre of the interface.
An *instance* refers to a **single physical device** used within a netlist.
A *model* is a set of parameters that is **shared between multiple physical devices** (instances).
A *device descriptor* is a single model implementation (eg `BSIMCMG` or `HICUM/L2`) and does not change during runtime. These three entities form a strict hierachy.
## Parameter Input
During the first stage the simulator reads the user configuration, creates the (+^model)/(+^instance) and populates these with parameters.
After the correct (+device_descriptor) is identified by the simulator the size of the (+model) and (+instance) data can be obtained.
The simulator must allocate the required memory.
A list of all parameters is provided in `OsdiDescriptor.param_opvar`.
This list contains all static information about the parameter such as the name, type etc. It does **not contain** the default value and bounds as these can depend on the value of other parameters, simulator parameters and even temperature. Populating default values and bounds checking are handled by OSDI during the initalization stage.
The value of parameters are stored within the (+model) and (+instance) data.
However the simulator can not access these values directly because later stages must know which values were explictly set.
Instead the `OsdiDescriptor.access` function is called to obtain access to obtain access to parameter storage.
A set of flags is provided to this function which tell OSDI how the pointer will be used by the simulator so it can update addtional internal data correctly.
## Circuit Setup
After the (+model)/(+instance) data is created and populated, the simulator must create the required (+^node) and (+^matrix_entry).
Furthermore default values for all parameters that were not explicitly set during the parameter input stage must be calculated.
Finally any evalutations that only depends on parameter values and temperature are executed.
The `setup_model` routine handles model parameters (and dependent calculations).
The `setup_instance` and routines does the same for instance parameters.
Furthermore this function handles node collapsing and is therfore closely intertwined with the metadata describing (+^node) and (+^matrix_entry).
A list of all (+^node) is provided in `OsdiDescriptor.nodes`.
It indicates whether a (+node) is a (+terminal) and its name for use within user configuration.
The index within this list is used to refer to (+^node) elsewhere.
All **non-zero** (+^matrix_entry) are listed in `OsdiDescriptor.jacobian_entries`.
A matrix entry is characterised by its row and column within the matrix.
The index within this list is used to refer to (+^matrix_entry) elsewhere.
Multiple nodes may be collapsed into a single node when some aspect of a (+compact_model) is not used.
`OsdiDescriptor.collapsible` contains all pairs of nodes that **might** be collapsed.
Which of these collapsible pairs actually takes effect for an (+instace) is determined during the `setup_instance` routine and saved within the instance data.
The simulator must used all of this information to create a mapping from OSDI ids to global data.
This mapping is the most important result during the setup phase and will be used extensively during the evaluate phase
## Evaluation
```C
struct OsdiDescriptor {
// metadata
char *name;
uint32_t num_nodes; /* nodes */
uint32_t num_terminals;
OsdiNode *nodes;
uint32_t num_jacobian_entries; /* jacobian */
OsdiNodePair *jacobian_entries;
bool *const_jacobian_entries;
size_t instance_size; /* memory */
size_t model_size;
size_t ptrs_offset;
uint32_t num_collapsible; /* node collapsing */
OsdiNodePair *collapsible;
size_t is_collapsible_offset;
OsdiNoiseSource *noise_sources; /* noise */
uint32_t num_sources;
uint32_t num_params; /* parameters and op variables */
uint32_t num_instance_params;
uint32_t num_opvars;
OsdiParamOpVar *inout;
// routines
void *(*access)(void *inst, void *model, uint32_t id, uint32_t flags);
OsdiModelInfo (*setup_model)(void *handle, void *model);
OsdiInstanceInfo (*setup_instance)(void *handle, void *inst, void *model,
double temperature, uint32_t num_terminals);
uint32_t (*eval)(void *handle, void *inst, void *model, OsdiSimInfo *info);
void (*eval_noise)(void *inst, void *model, double freq,
double *noise_dens, double *ln_noise_dens);
void (*to_spice)(void *inst, uint32_t flags);
}
```
\pagebreak
# Metadata
## `char *name`
The name of the device model/Verilog-A module.
<!-- MM: Sollte hier nicht Verilog-A module name stehen oder so? Ja -->
## `uint32_t num_nodes`
The **total** number of nodes (including terminals and other unkowns) used in the device model.
The number of nodes during simulation may be lower due to node collapsing.
## `uint32_t num_terminals`
The number of device terminals.
Each terminal corresponds to a Verilog-A port (marked `inout`, `input` or `output` in the Verilog-A source).
## `OsdiNode *nodes`
```C
struct OsdiNode {
char *name;
char *units;
}
```
A list of size `num_nodes` that contains metadata for each node.
The node's index in this list is used to represent a node in all parts of the interface.
The first `num_terminals` entries in the `nodes` list are filled with the model's terminals in the same order as defined in Verilog-A.
The remaining entries do not follow a specific defined order.
Each element of the list is an instances of the `OsdiNode` structure, defined above.
It contains the `name` of the node (for node potentials the name of the Verilog-A nets)
and the nodes `units`. The `units` field here corresponds to the contents of the `units` attributes of the Verilog-A nature associated with the unknown (`potential_nature` for node potential and `flow_nature` for branch flows).
\pagebreak
## `uint32_t num_jacobian_entries`
The number of jacobian entries with **non-zero** values in the resistive or reactive jacobian.
## `OsdiNodePair *jacobian_entries`
A list with length `num_jacobian_entries` that defines the **non-zero** entries in the resistive jacobian.
Each entry contains two indices.
The first node corresponds to the row, the second entry corresponds to the column in the jacobian.
Other parts of the interface refer to jacobian entries via the indices in this list.
## `bool *const_jacobian_entries`
A list of booleans with length `num_jacobian_entries`. The boolean value with index `i` indicates
if jacobian entry `i` is constant, i.e. **independent of the operating point**.
## `size_t instance_size`
The size of the instance data in bytes.
## `size_t model_size`
The size of the model data in bytes.
## `size_t ptrs_offset`
The offset of the `instance_ptrs` list within the instance data.
This list has a length fo `2*num_nodes + 2*num_jacobian_entries` and entries of type `double*`.
The `instance_ptrs` list is accessed as follows:
```C
doubles **instance_ptrs = (double **) ((char *)inst) + ptrs_offset;
```
The `instance_ptrs` list contains pointers to the jacobian
entries and rhs that are used in the `eval` and `to_spice` functions for writing jacobian and rhs entries into the simulator's global matrix.
This `instance_ptrs` list must be populated by the simulator before these functions are called.
The first `num_nodes` elements correspond to the resistive rhs.
The next `num_nodes` elements correspond to the reactive rhs.
The next `num_resist_jacobian_entries` elements correspond to the resistive jacobian elements,
The next `num_react_jacobian_entries` elements correspond to the reactive jacobian elements.
\pagebreak
## `uint32_t num_collapsible`
The number of node pairs that can be collapsed, typically indicated by `V(x,y) <+ 0` in Verilog-A.
## `OsdiNodePair *collapsible`
A list with length `num_collapsible` that contains the pairs of nodes that are collapsible, typically indicated by `V(x,y) <+ 0` in Verilog-A.
The `setup_instance` routine tells the simulator which of these pairs to collapse for a specific instance.
## `size_t is_collapsible_offset`
Provides the offset of the `is_collapsible` lists' beginning from the beginning of the instance data in byes.
The `is_collapsible` list has length`num_collapsible` and contains `bool` elements. A pointer to the list is obtained as follows:
```C
bool *is_collapsible = (bool *) ((char *)inst) + is_collapsible_offset;
```
`is_collapsible[i]` is set true if the `collapsible` node pair at `collapsible[i]` is collapsed for the given instance.
This entries of `is_collapsible` are set in the `setup_instance` routine.
## `uint32_t num_noise_src`
The number of uncorrelated noise sources used in the model (`white_noise`, `flicker_noise`, `table_noise` and `table_noise_log` in Verilog-A).
## `OsdiNoiseSource *noise_sources`
```C
struct OsdiNoiseSource {
char *name;
OsdiNodePair nodes;
}
```
A list of all noise sources used within the device model with length `num_noise_src`.
Each element is an instance of the `OsdiNoiseSource` struct defined above.
The field `name` corresponds to the name specified in Verilog-A or an empty string otherwise.
The `nodes` field corresponds to the nodes that the noise source is connected to.
The first node corresponds to the positive node (first node in Verilog-A contribution).
Correlated sources are not directly supported at the moment.
For that purpose a correlation network can be used instead.
\pagebreak
## `uint32_t num_params`
The number of Verilog-A model parameters.
## `uint32_t num_instance_params`
The number of Verilog-A parameters marked with the attribute `type="instace"`.
## `uint32_t num_opvar`
The number of operating point variables (marked with `description` and `units` attribute).
## `OsdiParamOpvar* inout_data`
```C
struct OsdiParamOpvar {
char **name;
uint32_t num_alias;
char *description;
char *units;
uint32_t base_type;
uint32_t len;
}
```
A list of metadata for each Verilog-A parameter with length `num_params + num_opvar`.
Each element is an instance of the `OsdiParamOpvar` struct defined above.
The first `num_opvar` elements correspond to the models operating point variables.
The following `num_instance_params` elements correspond to the models instance parameters.
The remaining elements correspond to the model parameters.
The `name` field contains a list of identifiers with `1 + num_alias` entries.
Its first entry corresponds to the canonical identifier while the remaining `num_alias` entries are aliases.
The `description` and `units` field correspond the values of the Verilog-A attributes with the same name.
Type information is encoded in the `base_type` and `len` fields.
The 2 least significant bits within the `base_type` fields indicate which Verilog-A base type (`real`, `integer` or `string`).
Additionally the most significant bit (MSB) is set for instance parameter and the second MSB for operating point variables.
For arrays the `len` field contains the length of the array while for scalar parameters the `len` field is set to 0.
```C
#define INOUT_TY_REAL 0; // double
#define INOUT_TY_INT 1; // int32_t
#define INOUT_TY_STR 2; // char*
#define INOUT_OPVAR (1 << 30);
#define INOUT_INSTANCE (1 << 31);
```
\pagebreak
<!-- void *(*access_param)(void* inst, void *model, char *name, -->
<!-- uint32_t flags); -->
<!-- // teardown -->
<!-- void (*teardown_model)(void *model); -->
<!-- void (*teardown_instance)(void* inst); -->
<!-- // evaluation -->
<!-- uint32_t (*eval)(void *handle, void* inst, void *model, -->
<!-- double **resist_jacobian, double **react_jacobian, -->
<!-- double **resist_residual, double **reactive_residual, -->
<!-- SimInfo *sim_info, void **state_vec); -->
<!-- void (*eval_noise)(void* inst, void *model, double freq, -->
<!-- double *noise_dens, double *ln_noise_dens); -->
\pagebreak
# Routines
Routines provide access to information that can not be exposed as static metadata.
This includes mainly compiled behavioral Verilog-A code and access to heterogeneous data inside the model and instance data.
All routines get a pointer to the model and/or instance data
as arguments from the simulator.
These pointers must be allocated by the simulator with correct size, alignment and are initialized with zeroed bytes.
This can be achieved as follows:
```C
void* inst = calloc(1, descriptor.instance_size);
void* model = calloc(1, descriptor.model_size);
```
All functions that execute Verilog-A behavioural code have the `void *handle` argument.
This argument is then given to `osdi_init_log_message` and `osdi_finish_log_message` whenever Verilog-A emits messages.
The contents of this pointer are entirely up to the simulator.
See the documentation for `osdi_init_log_message` and `osdi_finish_log_message` for more details.
## `access`
```C
void *access(void *inst, void *model, uint32_t id, uint32_t flags);
```
This function allows the simulator to read and write parameters as well as operating point variables.
The data corresponding to `inout_data[id]` is accessed.
The `flags` argument indicates what kind of operation the returned pointer shall be used for.
The following bit-flags are available:
```C
#define ACCESS_FLAG_SET 1
#define ACCESS_FLAG_INIT 2
#define ACCESS_FLAG_INSTANCE 4
```
Reading the pointer is always allowed.
When `ACCESS_FLAG_SET` is set the pointer can be written as well.
During initial reading of the netlist `ACCESS_FLAG_INIT` must also be set.
This allows the simulator to determine which parameters are explicitly set, and which retain their default value.
If `ACCESS_FLAG_INIT` is not set only parameters that were explicitly provided during initialization can be updated.
If such an access is attempted regardless a null pointer will be returned.
By default the `access` function will always access model parameters.
To access the corresponding instance parameter instead `ACCESS_FLAG_INSTANCE` must be provided.
If an instance parameter is not set for an instance, the parameter is copied from the model during `setup_instance`.
Note that operating point variables can only be read and therefore the method returns a null pointer when `flags != 0`.
\pagebreak
## `setup_model`
```C
OsdiModelInfo setup_model(void *handle, void *model);
struct OsdiInitInfo {
uint32_t flags;
uint32_t num_errors;
InitError *errors;
}
```
This function initializes all parameters that were not explicitly set and also performs bounds check on all model parameters (and instance parameters that were set for the model).
Additionally it executes all Verilog-A code that does not depend on:
* operating point
* analysis mode
* time step
* absolute time
* values of instance parameter
* `$param_given` with instance parameters as argument
* `$port_connected`
* `$temperature`
* `@final_step` event
The function returns an instance of the `OsdiModelInfo` struct.
It contains a list of errors that occurred while checking the model parameters.
Additionally it contains any execution flags emitted by the behavioural Verilog-A code.
The documentation for these flags can be found in the documentation of the `eval` function.
This function must be called whenever the model parameters change.
Whenever this function is called for a model the `setup_instance` routine must be called for all its instances.
\pagebreak
## `setup_instance`
```C
OsdiInstanceInfo setup_instance(void *handle, void *inst, void *model,
double temperature, uint32_t num_terminals);
struct OsdiInitInfo {
uint32_t flags;
uint32_t num_errors;
InitError *errors;
}
```
This function initializes all instance parameters that were not explicitly set and also performs bounds check on all instance parameters.
Instance parameters that were not set for the instance but are set for the model are copied into the instance.
The routine executes all Verilog-A code that was not executed in `setup_model` and does not depend on:
* operating point
* analysis mode
* time step
* absolute time
* `@final_step` event
This function returns an instance of the `OsdiInstanceInfo` struct.
It contains a list of errors that occurred while checking the instance parameters.
Furthermore any execution flags emitted by the behavioural Verilog-A code are also provided.
The documentation for these flags can be found in the documentation of the `eval` function.
This function also populates the `is_collapsible` list within the instance data.
When this function is called repeatedly (during a parameter sweep) the simulator must ensure that node collapsing is updated whenever `is_collapsible`
changes. Alternatively the simulator can disallow such sweeps producing an error whenever `is_collapsible` changes.
This function must be called whenever the model or instance parameters are changed.
\pagebreak
## `eval`
```C
uint32_t eval(void *handle, void *inst, void *model, OsdiSimInfo *info);
struct OsdiSimInfo {
uint32_t flags;
double timestep;
double abstime;
}
```
This function evaluates the remaining behavioral code in the Verilog-A analog block
and writes the jacobian and rhs entries. This function contains the bulk of the Verilog-A code.
Due to its complexity, the documentation for this function is split in multiple parts as follows.
### Arguments
Apart from the standard pointers, `eval` requires a pointer to an instance of the `OsdiSimInfo` struct.
This struct contains generic information about the current simulation.
The `flags` field is a bitset where each bit indicates a different property of the current simulation.
```C
#define SIMINFO_CALC_NOISE 1
#define SIMINFO_CALC_REACT_RHS 2
#define SIMINFO_CALC_RESIST_RHS 4
#define SIMINFO_CALC_REACT_JACOBIAN 8
#define SIMINFO_CALC_RESIST_JACOBIAN 16
#define SIMINFO_CALC_OP 32
#define SIMINFO_ANALYSIS_NOISE 256
#define SIMINFO_ANALYSIS_STATIC 512
#define SIMINFO_ANALYSIS_DC 1024
#define SIMINFO_ANALYSIS_AC 2048
#define SIMINFO_ANALYSIS_IC 4096
#define SIMINFO_ANALYSIS_NODESET 8192
```
The `SIMINFO_CALC_<X>` flags indicate which results are required.
Setting only the required flags allows faster execution time.
For example, not calculating the reactive rhs and jacobian can significantly speed up DC simulations.
The `SIMINFO_ANALYSIS_<X>` flags indicate which analysis is being run for use with analysis dependent function in Verilog-A.
The flags match those outlined in *Table 4-21* of the Verilog-AMS standard.
It is up to the simulator when to emit these when there is no 1 to 1 correspondence with behaviour outlined in the language standard.
The `timestep` and `abstime` fields contain information about the time in large signal (transient) analysis.
The `timestep` field indicates the time difference between the previous and current time step in seconds.
The `abstime` field indicates the time of the current operating point in seconds.
<!-- The `state_vec` argument is an array where each entry contain an allocation of size `state_vec_size`. -->
<!-- The entry `state_vec[0]` corresponds to the current time step while increasing indices correspond to previous time steps (for large signal analysis). -->
<!-- The total number of state vec entries are definied in the `num_states` argument. -->
Apart from the explicit function arguments the `eval` function also reads the `instance_ptrs` list.
This list contains pointers to the rhs and jacobian entries.
Is is contained within the instance data and must be populated by the simulator.
The simulator must correctly take node collapsing into account when populating this list.
### Returned Flags
Verilog-A allows behavioural code to control the simulation flow.
This functionality is accommodated by setting bit-flags for the return value.
```C
#define EVAL_RET_FLAG_LIM 1
#define EVAL_RET_FLAG_FATAL 2
#define EVAL_RET_FLAG_FINISH 4
#define EVAL_RET_FLAG_STOP 8
```
The `EVAL_RET_FLAG_LIM` flag indicates that a `$limit` function (like `pnjlim`) has reduced the change of a potential.
If the `EVAL_RET_FLAG_FATAL` flag is set at a fatal error occurred.
The simulator must **abort** the current simulation with an error.
The `EVAL_RET_FLAG_FINISH` flag indicates that `$finish` was called.
If the current iteration has converged the simulator must **exit gracefully**.
Otherwise this flag should be ignored.
The `EVAL_RET_FLAG_STOP` flag indicates that `$stop` was called.
If the current iteration has converged the simulator must **pause** the current simulation.
Otherwise this flag should be ignored.
## `eval_noise`
```C
void eval_noise(void *inst, void *model, double freq, double *noise_dens,
double *ln_noise_dens);
```
This function is the primary function called during noise analysis.
It generates the (potentially) frequency dependent noise densities based upon the (operating point dependent) results of the `eval` function.
The results are written into `noise_dens` and `ln_noise_dens`.
These two pointer must each point to a list of doubles with length `num_noise_src`.
The element `noise_dens[i]` is set to the noise density that corresponds to the noise source described in `noise_sources[i]`.
`ln_noise_dens[i]` is set to `log(noise_dens[i])`.
\pagebreak
## `to_spice`
```C
void to_spice(void *inst, double timestep);
```
OSDI uses a seperate reactive and resistive RHS and jacobian.
This approach allows OSDI to support a large array of analysis modes (like HB, tran, AC and DC simulations) without providing a separate function for each analysis type. This approach is well suited for many circuit simulators which use a similar formulation internally.
However, SPICE based simulators only have a single jacobian and rhs.
Therefore these matrices must be merged to support transient simulations in SPICE-like simulators.
Additionally, SPICE based simulators use a different formulation of the NEWTON method:
\begin{gather}
J(x_{k+1} - x_k) = - F(x_k) \label{eq:newton}\\
J x_{k+1} = J x_{k} - F(x_k) \label{eq:newton_spice}
\end{gather}
While the newton method is more commonly formulated as shown in \eqref{eq:newton}, SPICE uses the formulation shown in \eqref{eq:newton_spice}.
Therefore, the rhs is also converted for use within SPICE based simulators.
This function assumes that the `eval` function was run previously.
For transient simulations the time difference between the last operating point and this operating point must be provided as the `timestep` argument.
For other analysis types the `timestep` argument must be set to `+0.0`.
In case of transient analysis this function adds `react_jacobian[i]/time_step` to each entry in the resistive jacobian.
Furthermore the reactive rhs is added to the resistive rhs.
Therefore the simulator must perform a numeric time derivative on the reactive RHS before calling this function.
This functionality can not be provided within the `to_spice` function as the exact time derivative algorithm is simulator dependent.
In any analysis mode the final rhs entry $x_\mathrm{i}^\prime$ is then calculated with the formula below.
Here $J_\mathrm{jl}$ is the (resistive) jacobian entry with row $j$ and column $l$ generated by the eval function and $x_\mathrm{i}$ is the (resistive) rhs entry for node `` by the eval function.
$$
x_\mathrm{i}^\prime = - x_\mathrm{i} + \sum_{j} \sum_{l} J_\mathrm{lj} x_\mathrm{j}
$$
The functionality provided by this function could be implemented by the simulator.
However the implementation would become involved as the mapping between pointers and jacobian entries and node collapsing would have to be taken into account. As a result the implementation would involve a lot of branching and require memory allocations.
This can have a non trivial performance overhead.
In contrast a compiler that can target OSDI already has access to all information required to generate such a function without branches or allocations.
Therefore OSDI provides this function to make integration with SPICE based simulator easier and performant.
\pagebreak
# Callbacks
```C
extern FILE *osdi_init_log_message(void *handle, uint32_t lvl);
extern void osdi_finish_log_message(void *handle, FILE *stream, uint32_t lvl);
```
OSDI generally tries to avoid callbacks to simulator specific functions.
However such callbacks can not be entirely avoided.
Any implementation must provide symbols for all callbacks defined herein.
Every callback accepts a `handle` pointer.
This pointer is passed by the simulator to any function that executes Verilog-A behavioral code.
It is intended to allow the callbacks to access to simulator specific data.
The `osdi_init_log_message`/`osdi_finish_log_message` callbacks are required to allow behavioral Verilog-A code to emit log messages.
Simulators may wish to redirect these log messages or attach additional metadata.
They are are called before/after a message is emitted.
The returned `FILE` pointer will be used as the destination for the message.
Both functions receive the level of the log messages as an integer.
Appropriate constants are defined in the OSDI header file and shown below.
``` C
#define LOG_LVL_DEBUG 0
#define LOG_LVL_INFO 1
#define LOG_LVL_WARN 2
#define LOG_LVL_ERR 3
#define LOG_LVL_FATAL 4
```
A basic implementation that writes each message to a separate line in stdout prefixed with the level is shown below.
```C
extern FILE *osdi_init_log_message(void *handle, uint32_t lvl){
switch(lvl){
case LOG_LVL_DEBUG: fwrite(stdout, "VA debug: "); break;
case LOG_LVL_INFO: fwrite(stdout, "VA info: "); break;
case LOG_LVL_WARN: fwrite(stdout, "VA warn: "); break;
case LOG_LVL_ERR: fwrite(stdout, "VA error: "); break;
case LOG_LVL_FATAL: fwrite(stdout, "VA fatal: "); break;
default: fwrite(stdout, "VA unkown message: "); break;
}
return stdout;
}
```
```C
extern void osdi_finish_log_message(void *handle, FILE *stream, uint32_t lvl){
fwrite(stream, "\n");
return;
}
```
\pagebreak
# Verilog-A Standard Compliance
OSDI is predominantly aimed at compact modelling.
In the compact modeling community some parts of the Verilog-A language are de-facto not used.
For allowing fast and consistent results some limitations of the Verilog-A language subset are necessary as defined below.
## `limexp`
The `limexp` function is commonly used in Verilog-A because exponential overflow is a major cause of convergence issues.
The language standard defines that `limexp` should limit the change of its argument between iterations.
This requires access to values from previous iterations and also a limiting algorithm that is applicable in the general case.
As a result all implementations known to the author have opted to use a simple linearised exponential instead, for example:
```C
if (x < EXP_LIM){
return exp(x)
}else{
return exp(EXP_LIM) *(x + 1 - EXP_LIM)
}
```
OSDI uses this linearised function as well with `EXP_LIM = 80`.
This may change in future versions if tangible improvements can be demonstrated with a different algorithm.
## Hidden State
OSDI compliant compilers must assume that a compiled model does not have hidden states.
This often allows more code to be moved into the `model_setup` and `instance_setup` functions, significantly improving performance.
If a variables has the `hidden_state` attribute in Verilog-A, the compiler can not make that assumption.
The same attribute can be placed on a Verilog-A module to allow hidden state for all variables within the module.
\pagebreak
## Small Signal Operators
The arguments of the following analog operators are linearised during small signal and noise simulations:
* `ddt`
* `idt`
* `idt_mod`
* `white_noise`
* `flicker_noise`
* `table_noise`
* `table_noise_log`
As a result they can only appear in linear expressions on the rhs of a contribute statement.
This is demonstrated with a couple examples:
``` verilog
I(c,a) <+ Id + c*ddt(Qd); // possible
y = Id + ddt(Qd); // not possible
I(c,a) <+ y;
I(c,a) <+ Id + exp(ddt(Qd)); // not possible
```
\pagebreak