Verilog-A compact model support

Verilog-A is a subset of Verilog-AMS. It is an industry standard modeling language for analog circuits. A Verilog-A device model is usually compiled into a shared library which can then be loaded by a simulator. The loaded model can then be used in circuit netlists.

OpenVAF is a Verilog-A compiler that builds shared library files which use the Open Source Device Interface (OSDI) to communicate with the simulator. SPICE OPUS supports OSDI version 0.3. At this point OP, DC, TF, AC, TRAN, and PZ analyses are supported. NOISE analysis support wll be implemented as soon as the OpenVAF compiler will support it (announced for 2023).

Building an OSDI file from Verilog-A sources

Suppose we want to build the following simple diode model stored in file spicediode.va

`include "constants.vams"
`include "disciplines.vams"
module simple_diode(A,C);
// Simplified SPICE diode
// Terminals and internal nodes
inout A, C;
electrical A,C,CI;
// Branches
branch (A,CI) br_a_ci;
branch (CI,C) br_ci_c;
// Model parameters
(*desc = "Saturation current", units = "A"*)
parameter real Is = 1e-14 from [0:inf];
(*desc = "Emission coefficient", units = ""*)
parameter real N = 1 from [0:inf];
(*desc = "Ohmic resistance", units = "Ohm" *)
parameter real Rs = 0.0 from [0:inf];
(*desc = "Reverse breakdown voltage", units = "V" *)
parameter real BV = 1e20 from [0:inf];
(*desc = "Reverse breakdown current", units = "A" *)
parameter real IBV = 1e-10 from (0:inf];
(*desc = "Saturation current temperature exponent", units = "" *)
parameter real XTI = 3.0 from (0:inf];
(*desc = "Activation energy", units = "eV" *)
parameter real EG = 1.12 from [0.1:inf];
(*desc = "Parameter extraction temperature", units = "C" *)
parameter real Tnom = 27 from [-300.15:inf];
(*desc = "Zero-bias junction capacitance", units = "F"*)
parameter real Cjo = 0.0 from [0:inf];
(*desc = "Junction potential", units = "V" *)
parameter real Vj = 1.0 from [0.01:inf];
(*desc = "Grading coefficient", units = "" *)
parameter real M = 0.5 from (0:0.9);
(*desc = "Forward bias junction fit parameter", units = "" *)
parameter real FC = 0.5 from [0:0.95);
(*desc = "Transit time", units ="s" *)
parameter real TT = 0.0 from [0:inf);
// Instance parameters
(*desc = "Device area factor", type = "instance", units = "" *)
parameter real area = 1.0 from (0:inf);
// Opvars
(*desc= "Diode voltage", units ="V" *) real Vd;
(*desc= "Diode ohmic current", units ="A" *) real Id;
(*desc= "Diode charge", units ="As" *) real Qd;
(*desc= "Differential conductance", units ="A/V" *) real gd;
(*desc= "Differential capacitance", units ="F" *) real cd;
// Adjustablje limited exponential
analog function real lexp;
input x, ylim;
real x, ylim;
real xlim;
begin
xlim = ln(ylim);
if (x < xlim) begin
lexp = exp(x);
end else begin
lexp = ylim*(x-xlim+1);
end
end
endfunction
// Variables
real Vf, Vres, VT, Q;
real expmaxf, expmaxr;
real Tdev, Tnomk, EGTnom, EGT;
real Vjeff, Cjoeff, F1, F2, F3;
real Iseff, IBVeff, If, Ib;
analog begin
// Device temperature and parameter measurement temperature (in K)
Tdev = $temperature;
Tnomk = Tnom + 273.15;
// Thermal voltage
VT = `P_K*Tdev/`P_Q;
// Branch voltages
Vf = V(br_a_ci);
Vres = V(br_ci_c);
// Temperature and area adjusted saturation current
Iseff = area * Is * pow(Tdev/Tnomk, XTI/N) * exp((Tdev/Tnomk-1)*EG/(N*VT));
// Forward exponential function limiting (at gd = 100/Rs)
if (Rs>1e-3)
expmaxf = 100/Rs*N*VT/Iseff;
else
expmaxf = 100/1e-3*N*VT/Iseff;
// Ohmic current of diode branch
If = Iseff * (lexp(Vf/(N*VT), expmaxf)-1);
// Area adjusted breakdown current
IBVeff = area * IBV;
// Reverese exponential function limiting (at gd = 100/Rs)
if (Rs>1e-3)
expmaxr = 100/Rs*N*VT/IBVeff;
else
expmaxr = 100/1e-3*N*VT/IBVeff;
// Breakdown current (at Vf=-BV the current Ib should be IBV)
if ($param_given(BV)) begin
Ib = IBVeff * lexp((-Vf-BV)/(N*VT), expmaxr);
end else
Ib = 0;
// Junction charge
EGTnom = 1.16-(7.02e-4*pow(Tnomk, 2))/(Tnomk+1108);
EGT = 1.16-(7.02e-4*pow(Tdev, 2))/(Tdev+1108);
Vjeff = Vj*(Tdev/Tnomk) - 3*VT*ln(Tdev/Tnomk) - (Tdev/Tnomk)*EGTnom + EGT;
Cjoeff = area * Cjo * (1+M*(400e-6*(Tdev-Tnomk)-(Vjeff-Vj)/Vj));
if (Vf<FC*Vjeff) begin
Q = If*TT + Cjoeff * Vjeff * (1-pow(1-Vf/Vjeff, 1-M))/(1-M);
end else begin
F1 = Vjeff * (1-pow(1-FC, 1-M))/(1-M);
F2 = pow(1-FC, 1+M);
F3 = 1 - FC*(1+M);
Q = If*TT + Cjoeff *
(F1 + (F3*(Vf-FC*Vjeff)+(M/(2*Vjeff)*(pow(Vf, 2)-pow(FC*Vjeff, 2))))/F2);
end
// Opvars (ddx(.,V(A))=ddx(.,V(br_a_ci))
Vd = Vf;
Id = If - Ib;
Qd = Q;
gd = ddx(Id, V(A));
cd = ddx(Q, V(A));
// Diode branch current, add a small conductance gmin (for convergence)
I(br_a_ci) <+ If - Ib + ddt(Q) + $simparam("gmin")*V(br_a_ci);
// Resistor branch
if (Rs <= 0)
// Collapse CI and C if Rs is 0
V(CI,C) <+ 0;
else
I(CI,C) <+ Vres / (Rs/area);
end
endmodule

To build the OSDI module you will need the OpenVAF compiler. To save you the inconvenience both tested versions of the compiler (Windows and Linux) are available at the Download page.

The Windows version requires the MSVC linker from Visual Studio 2019 to be installed.
It needs to be run in the **x64 Tools Command Prompt for VS2019**.

Once you have the compiler, type

openvaf simple_diode.va

A file named simple_diode.osdi will be created in the same directory. Now start SPICE OPUS and
switch to the directory, where the .osdi file resides with the **cd** command. Then
in the SPICE OPUS command prompt type

osdiload ./simple_diode.osdi

The model will be loaded. You can check that with the following command

siminfo devices

The loaded model can be found at the end of the output generated by SPICE OPUS.
The **osdiload** command searches for the model in the default dynamic
library search path followed by the SPICE lib/osdi directory. If you are loading
an OSDI file from teh current directory, prefix its name with "./". A set of
precompiled Verilog-A models is installed in the SPICE lib/osdi directory when you
install SPICE OPUS. Once an OSDI model is loaded it cannot be unloaded. Therefore
you need to restart the simulator and reload the model every time you change the
Verilog-A source file and recompile it.

The **osdiload** command has the following
syntax

osdiload [quiet|normal|verbose] <osdi_file>

The **quiet** option supresses all messages, while the **verbose**
option prints out detailed information. If nothing is specified as the first option
**normal** is assumed.

Detailed information about a device can be obtained with the **devinfo** command.
Type

devinfo simple_diode

The output lists all terminals, internal nodes, model, and instance parameters of a device.

simple_diode: A simulator independent device with OSDI interface v0.3
Terminals: 2
1: A
2: C
Internal nodes: 1
3: CI
Collapsible node pairs: 1
(CI,C)
Model parameters:
$mfactor :RW--: real :
is :RW--: real : Saturation current
n :RW--: real : Emission coefficient
rs :RW--: real : Ohmic resistance
bv :RW--: real : Reverse breakdown voltage
ibv :RW--: real : Reverse breakdown current
xti :RW--: real : Saturation current temperature exponent
eg :RW--: real : Activation energy
tnom :RW--: real : Parameter extraction temperature
cjo :RW--: real : Zero-bias junction capacitance
vj :RW--: real : Junction potential
m :RW--: real : Grading coefficient
fc :RW--: real : Forward bias junction fit parameter
tt :RW--: real : Transit time
Instance parameters:
$mfactor :RW--: real :
vd :R---: real : Diode voltage
id :R---: real : Diode ohmic current
qd :R---: real : Diode charge
gd :R---: real : Differential conductance
cd :R---: real : Differential capacitance
dt :RW--: real : Instance delta temperature
temp :RW--: real : Instance temperature

SPICE OPUS changes the device name to lowercase so that Verilog-A modules with uppercase in their name are accessible in the simulator (the netlist is case-insensitive).

All Verilog-A parameter names are convered to lowercase. If the Verilog-A
device does not have a parameter named **temp** an instance parameter
with that name is addded by SPICE OPUS. When given this parameter overrides the circuit
temperature for a particular instance. Similarly, if there is no Verilog-A
parameter named **dt** an instance parameter with that name is added
by SPICE OPUS. When given it specifies by how much the instance temperature is
greater than the circuit temperature (or its override specified by the
**temp** parameter).
Both parameters are specified in degrees Celsius.

All Verilog-A instance parameters (with exception of parameters **temp**
and **dt** added by SPICE OPUS)
can be specified as model parameters. In this case they define the default value of the
corresponding instance parameter.

The **R** flag means that the parameter can be read, while
the **W** flag means that the parameter can be set. The **P**
flag denotes principal parameters (e.g. the resistance of a resistor). Principal
parameters can be found in builtin SPICE devices. The **A** flag
denotes aliases (different name for a parameter that is listed before a sequence
of aliases in the parameter list. The flags are followed by parameter type and
parameter description.

All OSDI devices have an instance parameter named **$mfactor**. This
is the multiplicity parameter (equivalent of the M parameter found in builtin SPICE
devices).

Internal nodes of OSDI devices

OSDI devices can have internal nodes. In the diode example CI is an internal node. Internal node names are case sensitive. They are not converted to lowercase by SPICE OPUS. Because the NUTMEG interpreter is case sensitive they can easily be accessed as vectors named

**<instance_name>#<node_name>**

OSDI devices can collapse internal nodes if the resistance between two nodes is small
enough. In this way the CI node of the above example collapses into terminal C when
the Rs parameter is 0. Collapsible node pairs are listed by the **devinfo**
command.

Simulator parameters accessible in Verilog-A

Selected simulator parameters are accessible in Verilog-A models via the **$simparam**
Verilog-A function. The OSDI interface in SPICE OPUS provides the following simulator options
to Verilog-A models:

**gdev**is the value of the**gmin**simulator parameter**gmin**is the greater of values**gmin**and**gmindiag**, where the latter one is the value added to the diagonal elements of the equation matrix during gmin stepping.**tnom**is the value of the**tnom**simulator parameter**scale**is the value of the**scale**simulator parameter**simulatorVersion**is the version of the simulator**sourceScaleFactor**is the value of source scaling factor during source stepping (between 0 and 1). Normally this parameter is 1.

Opvars are instance parameters that cannot be set. They can only be read. SPICE OPUS computes opvars during operating point analysis and transient analysis. Typically opvars are used for accessing differential conductances, differential capacitances, device charges and currents, etc. In the above example there are 5 opvars: vd (internal diode voltage), id (diode ohmic current), qd (diode charge), gd (differential conductance of the internal diode), and cd (differential capacitance of the internal diode).

Opvars can be stored by issuing save directives before the analysis is started. See the examples below for more information.

Change limiting functions limit the change of a quantity between consecutive iterations of the
Newton-Raphson algorithm. These functions are built into SPICE and are used by its builtin models
to improve convergence. They return the limited value of the given quantity and prevent the
simulator from stopping the Newton-Raphson iteration before the change becomes small enough.
They are available in Verilog-A via the **$limit**
function with the following syntax.

$limit(quantity, function_name, ...)

Quantity can be any branch voltage or current. The function name must be quoted. The following functions are available

**pnjlim**limits the voltage change across pn junctions. Two extra arguments need to be specified.**vt**is the thermal voltage.**vcrit**specifies the voltage above which limiting takes place. Changes above vcrit are limited when they exceed 2vt.**typedpnjlim**same as pnjlim, except that the quantity is multiplied by the third extra argument**type**before pnjlim limiting takes place.**limvds**limits the drain-source voltage change for FET transistors. No additional parameters are required.**fetlim**limits the gate-source voltage change for FET transistors. One additional parameter is required specifying the threshold voltage**vto**.**limitlog**logarithmically limits the change of a quantity if it exceeds the value given by additional parameter**LIM_TOL**.

Specifying OSDI devices in netlists

An OSDI device name must start with **N**. The rest is the same as with builtin
SPICE devices. The model type is the module name from the Verilog-A file converted to lowercase.
To create a diode based on the above example one should write

n1 (1 0) dmod area=1.0
.model dmod simple_diode is=1e-12 n=2 rs=1u
+ bv=8 ibv=1e-8
+ cjo=100p vj=1.0 m=0.5

Not all terminals of a device must be connected. If the last n terminals are not specified
in the netlist they remain unconnected. Whether a terminal is connected can
be detected in Verilog-A code with the **$port_connected** function.

A netlist containing an OSDI instance is successfully parsed if the corresponding OSDI
file is loaded before the netlist is loaded into the simulator. This means that you have
to either load the osdi file manually before invoking the **source** command
or load it at simulator startup by specifying the corresponding **osdiload**
command in the **spinit** file.

There is, however, a third option. You can
load an osdi file with the **.osdiload** netlist directive. If the osdi
file is already loaded, nothing happens. If, however, the file has not been loaded yet,
it will be loaded before the netlist is parsed. The syntax of the **.osdiload**
netlist directive is the same as the syntax of the **osdiload** NUTMEG command.
The only difference is the way the simulator searches for the osdi file. The following
search order is used

- the directory where the file invoking the
**.osdiload**netlist directive is located - the default dll search path
- SPICE lib directory (lib/osdi)

The following is a complete example demonstrating OP, DC, AC, and TRAN analysis
of an OSDI device. It also demonstrates accessing opvars via the **show**
command in OP analysis and **save** directives in DC and TRAN analysis

Verilog-A diode test - OP, DC, AC, and TRAN
.osdiload spicediode.osdi
n1 (1 0) dmod area=1.0
v1 (1 0) dc=0.5 ac=1 pulse=(-10 2 0 20)
.model dmod simple_diode is=1e-12 n=2 rs=0.1
+ bv=80 ibv=1e-8
+ cjo=100p vj=1.0 m=0.5
.control
destroy all
delete all
echo Operating point analysis
op
print all
show n1 : all
save all @n1[gd] @n1[cd]
dc v1 -82 2.0 lin 1000
plot -i(v1) yl -1 1 ylabel "Id [A]" xlabel "V(1) [V]"
+ title "I/V characteristic of a diode"
plot @n1[gd] ylog xl 0.5 2 ylabel "gd [A/V]" xlabel "V(1) [V]"
+ title "Differential conductance"
plot @n1[cd] ylog ylabel "cd [F]" xlabel "V(1) [V]"
+ title "Differential capacitance"
* Do not save opvars in AC analysis (they do not change)
delete all
* Internal resistance and diode form an mpedance voltage divider.
* Compute the frequency dependence of its ratio.
ac dec 10 1 100g
plot db(v(1)-n1#CI) xlabel "f [Hz]" ylabel "V(A,Ci)/V(1) [dB]"
+ title "Internal vs external diode AC voltage"
* Ramp diode voltage from -10V to 2V slowly (in 20s).
* Plot the differential capacitance.
save all @n1[gd] @n1[cd]
* First point of transient analysis is incorrect
tran 1m 20
plot @n1[cd] vs v(1) ylog ylabel "cd [F]" xlabel "V(1) [V]"
+ title "cd from transient analysis, bad first point"
* Skipping the first point of transient analysis
tran 1m 20 0.1u
plot @n1[cd] vs v(1) ylog ylabel "cd [F]" xlabel "V(1) [V]"
+ title "cd from transient analysis, skipped bad point"
.endc
.end

The first point of transient analysis is not valid (opvars are computed incirrectly). To get the correct results we skip the first point by setting start time in transient analysis.

The second example demonstrates the reverse recovery effect of the diode.

Verilog-A diode test - reverse recovery
.osdiload spicediode.osdi
n1 (1 0) dmod area=1.0
r1 (10 1) r=50
v1 (10 0) dc=0 pulse=(50 -50 1u 10n)
.model dmod simple_diode is=1e-12 n=2 rs=0.1
+ bv=80 ibv=1e-8
+ cjo=100p vj=1.0 m=0.5
+ tt=1e-6
.options method=gear
.control
destroy all
delete all
tran 10n 2u
plot -i(v1) xlabel "t[s]" ylabel "i[A]"
+ title "Diode reverse recovery"
.endc
.end