About Model Development in VerilogA
This topic provides a brief introduction to the VerilogA language by means of examples. A more complete description of the language is available in the VerilogA and VerilogAMS Reference Manual. A simple resistor is first defined, then enhanced with noise. Models for capacitors and inductors are then developed. These models use the ddt()
operator to automatically generate the timedependent functionality of the devices. Finally, a nonlinear diode is created demonstrating modeling of more complex behavior.
Creating a Linear Resistor in VerilogA
The linear resistor in the example code below was introduced in the previous section. This provides a simple example of the anatomy of a VerilogA model. Line numbers are used here to help explain the code, but should not be included in the actual source code.
`include "disciplines.vams"
module R(p,n);

electrical p,n;

parameter real R=50.0;

analog V(p,n) <+ R * I(p,n);
endmodule
Line 1 instructs the compiler to insert the contents of the file disciplines.vams into the text. This file contains the definitions that make the VerilogA specific for electrical modeling.
Line 2 and line 6 declares the module block, within which the model behavior will be defined. The model is named R and has two ports, named "p" and "n". Ports provide connections to other modules.
Line 3 declares that the ports p and n have the nature of those declared in the electrical discipline, as defined in the disciplines.vams header file. Natures and disciplines provide a way to map the general flows and potentials to particular domains, like electrical, thermal, or mechanical.
Line 4 declares one parameter, called R, and assigns it a default value of 50.0. The default value is set if the simulator is not passed an assignment in the netlist. In this case, the parameter is explicitly declared as real. However, if this attribute (which could also be integer ) is not provided, the language infers the type from the default value. In this case, 50.0 would indicate a real type, whereas 50 would indicate an integer. The parameter declaration also includes a simple method to restrict the range values. This is described in #Using Parameter Ranges to Restrict VerilogA Parameter Values Parameter values cannot be modified by the VerilogA code. If the value needs to be modified, it should be assigned to an intermediate variable.
The keyword analog
in line 5 declares the analog block. In this case, it is a single statement. However, statements can be grouped together using begin/end keywords to denote blocks which, in turn, can be named to allow local declarations. The simple, single statement includes several key aspects of the language. On the right hand side, the access function I(p,n)
returns the current flowing from node p to n. This is multiplied by the value of the parameter R . The "<+" in line 5 is called the contribution operator and in this example contributes the value of the evaluated right hand side expression as the voltage from p to n.
Adding Noise to the VerilogA Resistor
VerilogA provides several ways to add noise, including frequencyindependent, frequencydependent, and piecewise linear frequencydependent noise. In the case of a resistor, the thermal noise is 4*K*T/R
. The value of Boltzmann's constant is available in another header file, constants.vams , as a macro definition. VerilogA supports preprocessor commands similar to other languages like C. The details of macros are discussed in the VerilogA and VerilogAMS Reference Manual, but in general macros can be thought of as simple text substitutions, typically used to help make the code more readable and to gather all the constant definitions into one place. In the header file, the definition is
`define P_K 1.3806226e23
whereas in the VerilogA code, the value is used as `P_K . The temperature of the circuit is a value that can be changed outside of the model and so must be dynamically accessed. VerilogA models use system functions to retrieve information that the simulator can change. The temperature environment parameter function is $temperature
and returns the circuit's ambient temperature in Kelvin.
The actual contribution of the noise is made with the white_noise()
operator, which takes the noise contribution as an argument. Noise functions also allow for an optional string to label the noise contribution. Some simulators can sort the noise according to the labels.
`include "disciplines.vams"
`include "constants.vams"
module R(p,n);

electrical p,n;

parameter real R=50.0;

analog V(p,n) <+ R * I(p,n) + white_noise(4 * `P_K * $temperature / R, "thermal");
endmodule
Note that line 6 of the example code above shows the added noise.
Creating a Linear Capacitor and Inductor in VerilogA
Capacitors and inductors are implemented in a similar way to resistors. However, these devices have dependencies on time. In the case of a capacitor, the relationship is,
I = C * dV / dt
In this case, the contribution is a current through the branch. The right hand side includes a derivative with respect to time. This is implemented with the ddt()
operator. The model then becomes,
`include "disciplines.vams" module C(p,n); inout p,n; electrical p,n; parameter real C=0 from [0:inf); analog I(p,n) <+ C * ddt(V(p,n)); endmodule
This example also illustrates one use of the range functions in the parameter declaration. The " from [0:inf)
" addition restricts the value of C from 0 up to, but not including, infinity.
Similarly, the inductor relationship is,
V = L * dI/dt
and the source code is:
`include "disciplines.vams" module L(p,n); inout p,n; electrical p,n; parameter real L=0 from [0:inf); analog V(p,n) <+ L * ddt(I(p,n)); endmodule
Creating a Nonlinear Diode in VerilogA
VerilogA is wellsuited for describing nonlinear behavior. The basic PN junction diode behavior will be used as an example. The IV relation is,
I = Is * (exp(V/Vth  Rs * I)  1)
The implementation is shown below.
`include "disciplines.vams" `include "constants.vams" module diode(anode,cathode); electrical anode, cathode; parameter real Area = 1.0 from (0:inf]; //Area scaling factor parameter real Is = 1e14 from [0:inf]; //Saturation current [A] parameter real Rs = 0.0 from [0:inf); // Series resistance [Ohm] parameter real N = 1.0 from (0:inf); //Ideality parameter real Tt = 0.0 from [0:inf]; //Transit time [s] parameter real Cjo = 0.0 from [0:inf]; //Junction capacitance [F] parameter real Vj = 1.0 exclude 0; //Junction potential [v] parameter real M = 0.5 from [0:inf]; //Grading coef parameter real Fc = 0.5 from [0:1]; //Forward bias junct parm parameter real Kf = 0.0; //Flicker noise coef parameter real Af = 1.0 from (0:inf); //Flicker noise exponent real Vd, Id, Qd; real f1, f2, f3, Fcp; analog begin f1 = (Vj/(1  M))*(1  pow((1  Fc), 1  M)); f2 = pow((1  Fc), (1 + M)); f3 = 1  Fc * (1 + M); Fcp = Fc * Vj; Vd = V(anode, cathode); // Intrinsic diode Id = Area * Is * (exp(Vd / (N * $vt  Rs * I(anode, cathode)) / $vt))) 1); // Capacitance (junction and diffusion) if (Vd <= Fcp) Qd = Tt * Id + Area * Cjo * Vj * (1  pow((1  Vd / Vj), (1  M)))/(1  M); else Qd = Tt * Id + Area * Cjo * (f1 + (1 / f2) * (f3 * (Vd  Fcp) + (0.5* M / Vj) * (Vd * Vd  Fcp * Fcp))); I(anode, cathode) <+ Id + ddt(Qd); end endmodule
The more complicated behavior requires more complicated code. Comments are added to help clarify the source. VerilogA supports two types of comment characters. Text to the right of //
and text between /*
and */
blocks will be ignored.
The analog block is extended from a single line to multiple lines using the begin and end keywords to indicate a compound expression. Intermediate variables are declared to make the code more readable. These variables are declared in the module but outside the analog block.
A new system function, $vt , is used. This function returns the thermal voltage calculated at an optional temperature. If no arguments are passed, the ambient circuit temperature is used. The mathematical operators exp()
and pow()
are also used. VerilogA includes a wide range of mathematical functions.
Adding an Internal Node to the Diode
Note that the transcendental diode relationship includes the drop in the junction voltage due to the series resistance. An alternate method of implementing the series resistance would be to add an internal node. An internal node (also called a net) is added by simply declaring the node as electrical, without adding the node to the port list on the module declaration line. The diode code changes as shown:
`include "constants.vams" `include "disciplines.vams" module diode_va(anode,cathode); electrical anode, cathode, internal; parameter real Area = 1.0 from (0:inf]; //Area scaling factor parameter real Is = 1e14 from [0:inf]; //Saturation current [A] parameter real Rs = 0.0 from [0:inf]; //Ohmic res [Ohm] parameter real N = 1.0 from [0:inf]; //Emission coef parameter real Tt = 0.0 from [0:inf]; //Transit time [s] parameter real Cjo = 0.0 from [0:inf]; //Junction capacitance [F] parameter real Vj = 1.0 exclude 0; //Junction potential [v] parameter real M = 0.5 from [0:inf]; //Grading coef parameter real Kf = 0.0; //Flicker noise coef parameter real Af = 1.0 from (0:inf); //Flicker noise exponent parameter real Fc = 0.5 from [0:1]; //Forward bias junct parm real Vd, Id, Qd; real f1, f2, f3, Fcp; analog begin f1 = (Vj/(1  M))*(1  pow((1  Fc), 1  M)); f2 = pow((1  Fc), (1 + M)); f3 = 1  Fc * (1 + M); Fcp = Fc * Vj; Vd = V(anode, internal); // Intrinsic diode Id = Area * Is * ((Vd / (N * $vt))  1); // Capacitance (junction and diffusion) if (Vd <= Fcp) Qd = Tt * Id + Area * Cjo * Vj * (1  pow((1  Vd / Vj), (1  M)))/(1  M); else Qd = Tt * Id + Area * Cjo * (f1 + (1 / f2) * (f3 * (Vd  Fcp) + (0.5 * M / Vj) * (Vd * Vd  Fcp * Fcp))); I(anode, internal) <+ Id + ddt(Qd);; V(internal, cathode) <+ I(internal, cathode) * (Rs / Area); end endmodule
Adding Noise to the Diode
Noise is contributed in the same way as it was for the basic resistor. In this case, the shot noise equation shows the dependence on the diode current. The 1/f
noise is added using the flicker_noise()
operator, which takes as arguments the value of the noise contribution as well as the exponent to apply to the 1/f term.
// Noise I(anode, cathode) <+ white_noise(2 * `P_Q * Id, "shot"); I(anode, cathode) <+ flicker_noise(Kf * pow(Id, Af), 1.0, "flicker");
The thermal noise from the series resistor is added in the same fashion as was done for the resistor. Note that the label is modified to indicate which resistor the noise is generated from. This is useful when the analysis supports Sort Noise by Name.
// Series resistor V(internal, cathode) <+ white_noise(4 * `P_K * T * (Rs / Area), "Rs");
Adding Limiting to the Diode for Better Convergence
The exponential function used in the diode code can result in large swings in currents for small changes in voltage during the simulator's attempts to solve the circuit equations. A special operator, limexp()
can be used instead of exp()
to allow the simulator algorithms to limit the exponential in simulatorspecific ways. The specific algorithm used is simulator dependent.
Using Parameter Ranges to Restrict VerilogA Parameter Values
The parameter declaration allows the range of the parameter to be conveniently restricted. At run time, the parameter value is checked to be sure it is acceptable. If it is not, the simulator issues an error and stops.
By default, parameters can range from infinity to infinity. To restrict a range either the exclusive from ( : ) can be used, or the inclusive from [ : ] or a combination of the two. For example,
from (0 : 10]
will restrict the parameter from 0 to 10, excluding the value of 0 but including the value of 10.
Exceptions to ranges are indicated by the except attribute. For example,
except 5
will not allow the value of 5 to be passed.
Ranges and exceptions can be repeated and combined. For example,
parameter real X = 20.0 from (inf: 10] from [10:inf);
can also be written as,
parameter real X = 20.0 exclude (10:10);
If a simulator supports sweeping of parameters, the model developer will have to be aware of issues related to sweeping through ranges.
Creating Sources in VerilogA
Analog sources can also be described with VerilogA using the same concepts. Sources typically have some relation to the specific time during the simulation. The time is available from the $abstime
function, which simply returns the real time (in seconds) of the simulation.
A simple sine wave source would have the form:
`include "disciplines.vams" `include "constants.vams" module sine_wave(n1,n2); electrical n1,n2; parameter real gain = 1.0, freq = 1.0; analog V(n1,n2) <+ gain * sin(2 * `M_PI * freq* $abstime); $bound_step(0.05/freq); endmodule
The mathematical constant for PI is available as M_PI from the constants.vams header file. Note that the multiple parameter declarations were combined on one line as an alternative to declaring each on its own line.
The system function $bound_step()
restricts the simulator's transient steps to the size 0.05/freq
. This allows the model to define the resolution of the signal to be controlled.
An additional use of defining sources in VerilogA is to create test bench circuits as part of the model source file. This test module would provide sources with appropriate values and sweep ranges to allow the validation of the model to be contained within the code definition. This is a useful method of providing portable tests when distributing models among different simulators.
Creating Behavioral Models in VerilogA
VerilogA enables the user to trade off between various levels of abstraction. Certain circuit blocks lend themselves to simple analog descriptions, resulting in improvements in simulator execution time compared to transistor level descriptions. Since VerilogA supports all of the analysis functionality, the user is typically only trading off simulation accuracy when using a behavioral description of a circuit.
The PhaseLocked Loop (PLL) is a good example of a circuit that can be represented in behavioral blocks.
The VerilogA source code below demonstrates a PLL circuit. The PLL consists of a phase detector, an amplifier, and a voltage controlled oscillator. In this example, a swept sine source is used to test the circuit.
The VCO and phase detector are defined as:
// Voltage Controlled Oscillator module vco(in, out); inout in, out; electrical in, out; parameter real gain = 1, fc = 1; analog V(out) <+ sin(2*`M_PI*(fc*$abstime() + idt(gain*V(in)))); endmodule // Phase Detector module phaseDetector(lo, rf, if_); inout lo, rf, if_; electrical lo, rf, if_; parameter real gain=1; analog function real chopper; input sw, in; real sw, in; chopper = sw > 0 ? in : in; endfunction // chopper analog V(if_) <+ gain*chopper(V(lo),V(rf)); endmodule
The modules use the keyword inout
to declare that the ports are both input and output. Some simulators will check consistency of connection of ports (useful when ports are declared input or output only). ADS will not.
The phaseDetector
makes use of an analog function definition of chopper to simplify the code. Analog functions can be thought of a subroutines that can take many values but return one value. This is in contrast to macros, which should be thought of as inline text substitutions.
The PLL module uses hierarchy to instantiate these components:
// Phase Locked Loop module pll(rf, out, ref, if_); inout rf, out, ref, if_; electrical rf, out, ref, if_; parameter real tau = 1m from (0:inf); parameter real loopGain = 1 from (0:inf); parameter real fc = 2.0k from (0:inf); real cap, res ; electrical lo; phaseDetector #(.gain(2)) pd1(lo, rf, if_); vco #(.gain(loopGain/2), .fc(fc) ) vco1(out, lo); analog begin cap = 150e9; res = tau / cap; V(out, if_) <+ I(out, if_)*res; I(out, ref) <+ ddt(cap*V(out,ref)); end endmodule
For testing, a swept frequency source is defined:
module sinRampSrc(p,n); inout p,n; electrical p,n; parameter real fStart = 1, hzPerSec = 1; analog V(p,n) <+ sin(2*`M_PI*(fStart+hzPerSec/2*$abstime)*$abstime); endmodule
The modules are connected in a circuit (using appropriate AEL and symbol definitions) and a transient simulation is run.
PhasedLocked Loop Test Circuit
Plotting the Out node shows the VCO control voltage response to the swept frequency input, indicating the locking range of the circuit.
VCO Locking Range
Using Hierarchy to Manage Model Complexity
VerilogA supports module hierarchy, which allows modules to be embedded in other modules, enabling you to create complex circuit topologies.
To use a hierarchy, the model developer creates textual definitions of individual modules in the usual fashion. Each module definition is independent, that is, the definitions can not be nested. Special statements within the module definitions instantiate copies of the other modules. Parameter values can be passed or modified by the hierarchical modules, providing a way to customize the behavior of instantiated modules.
The instantiation format is:
module_name #(parameter list and values) instance name(port connections);
For example, the previous definitions of R, L, and C can be used with a new module called RLC to create a simple filter.
module RLC(in, out); electrical in, out, n1, n2; parameter real RR = 50.0 from [0:inf); parameter real LL = 0.0 from [0:inf); parameter real CC = 0.0 from [0:inf); R #(.R(RR)) R1(in, n1); L #(.L(LL)) L1(n1, n2); C #(.C(CC)) C1(n2, out); endmodule
The RLC module creates a series RLC from the input port in to the output port out , using two internal nodes, n1 and n2. The RLC module's parameter values of RR, LL, and CC are passed to the modules R, L, and C's parameters R, L, and C via #(.R(RR)), #(.L(LL)), and #(.C(CC)).
A unique advantage of the Compiled Model Library file is that the VerilogA source is effectively hidden from end users. This, coupled with VerilogA's hierarchical structure, gives model developers a simple way to distribute intellectual property without exposing proprietary information.