Device models that contain terms that depend on exponentials of applied voltages can be especially problematic for the convergence of circuit simulators. The original SPICE3F5 models for diodes, BJTs, and MOSFETs as a result all make use of a special limiting function "pnjlim" to prevent runaway convergence issues. It does so by restricting the amount that particular junction voltages can change from one Newton iteration to the next during the search for a solution. When starting from a reasonable initial junction voltage, devices that have this limiting applied can usually converge quickly, even though without the limiting they might often diverge badly.
The best solution to this problem is to write your model so that it does not compute currents that depend on exponentials of applied voltages.
Some models can avoid these convergence problems simply by using the standard "limexp" function instead of the exponential function. In Xyce, as in other open-source circuit simulators, limexp is the exponential function up to a certain value of the argument, after which it is a linear function. This is often sufficient to eliminate some of the worst convergence problems in simple models when importing them into Xyce. It is not sufficient, however, for many of the published BJT and HBT models.
Most recent models of MOSFETs that have been written in Verilog-A avoid these convergence problems from the start. Some BJT and HBT models, however, continue to cause convergence problems and require limiting. In some cases (e.g. the VBIC 1.2 model) the need for this limiting is explictly mentioned in documentation about the model, even though the Verilog-A contains no limiting functions. In others (e.g. MEXTRAM), one can find implementations in open source codes where limiting has been applied in the C++ or C code for the model, even though such limiting is not present in the Verilog-A source.
Verilog-A provides a standard construct, "$limit", that is intended for use in device models and which could be used by model developers to make explicit the need for this kind of convergence assistence. To date, the Xyce team has not found any published Verilog-A models that use it. When the Xyce team has encountered Verilog-A models that require voltage limiting, we have always had to add it ourselves. Because the code required to do voltage limiting in Xyce is more involved than in SPICE3F5, we have implemented in Xyce/ADMS a way of using $limit to generate the required code, and have inserted use of this function into the Verilog-A source for the model. The models for which this was required are the VBIC (both 1.2 and 1.3 versions), MEXTRAM, and FBH HBT models.
Our implementation of $limit is somewhat non-standard, because we have found that the Verilog-A standard $limit function is not quite flexible enough to support the needs of those three models, and because ADMS parsing limitations prevent strict implementation of the Verilog-A standard $limit function.
The Verilog-A $limit function according to the LRM:
The Verilog-A Language Reference Manual states that the $limit function has the following syntaxes and meaning:
limited_variable = $limit(access_function_reference, string[, argument list]);
limited_variable = $limit(access_function_reference, analog_function_identifier[, argument list]);
An access function reference is a potential or flow accessor such as "V(a,c)" or "I(a,c)". The string in the first variant is used to specify a built in limiting function of the simulator,such as "pnjlim", and must be quoted. In the second variant, the analog_function_identifier is supposed to be specified without quotation marks. When the first form is used with an unrecognized function, it is up to the simulator to determine which function to apply.
The return value of the $limit function is supposed to be the value of the access function reference when the simulator is converged, but the output of the simulator's limiting function while unconverged.
The limiter function will be called in the following manner:
limter_function(value_of_access_function, stored_state_access_function [, argument list]);
where value_of_access_function is the value of the access function at the current iteration, and stored_state_access_function is that value from the previous iteration.
The Verilog-A $limit function as implemented by Xyce/ADMS
Because ADMS will not recognize the name of an analog function in a function argument list, only the first form of the $limit function can work in Xyce/ADMS:
limited_variable = $limit(access_function_reference, string, argument list);
The only internally recognized limiter function at this time are "pnjlim," "typedpnjlim", "pnjlim_new", and "typedpnjlim_new". If any other string is given, it is assumed to be the name of a user-defined analog function, and code will be emitted to call that analog function. pnjlim is the standard SPICE3F5 function. pnjlim_new is a newer implementation of pnjlim copied from ngspice, which performs correctly when called with large negative voltages, and identically to pnjlim when the voltage is positive. pnjlim does not limit negative voltages, only positive voltages. This variant is necessary when the negative of the voltage may be used in exponentials, as it is in a few places in the VBIC model.
Since the first argument of $limit must be an access function reference, this causes problems when the device model can have both a P-type and N-type variant, such as a BJT. The standard SPICE3F5 pnjlim function works on the value of the junction voltage after multiplication by the type variable (which is -1 for PNP transistors). It is not possible to apply pnjlim in this manner given the standard Verilog-A definition of $limit. As a result, Xyce/ADMS recognizes special strings "typedpnjlim" and "typedpnjlim_new" --- when these limiter "functions" are called, the voltage being limited is premultiplied by the last argument in the $limit call, which should be the variable that is +1 for N type and -1 for P type.
limited_variable = $limit(V(b,e),"typedpnjlim",$vt,tVcrit, TYPE);
Here, V(b,e) will be multiplied by TYPE, and the resulting product limited by pnjlim or pnjlim_new with its standard arguments. The value of the limited product will be stored in limited_variable.
For the case where pnjlim is not sufficient and a user-defined analog function is needed for limiting of a voltage that must be multiplied by a type variable (as in the MEXTRAM), a special form of $limit is recognized:
limited_variable = $limit(access_function,string,"typed",type_variable,argument_list);
In this variant, the literal string "typed" in the third argument indicates that the access function should be multiplied by the fourth argument before being passed to the limiter function.
In all cases, the limiter function is called as:
with value being premultiplied by the type variable if necessary.Examples of these limiter functions in use may be found in the utils/ADMS/examples subdirectories, in the VBIC 1.3, MEXTRAM, and FBH_HBT codes. A simple example of this applied to a diode may be found in the utils/ADMS/examples/toys/diode2.va file.
How is voltage limiting done in Xyce, and why is it different from SPICE?
The answer to this question is found in the Voltage Limiting section of the Xyce Mathematical Formulation document.
When a model has internal nodes connected with resistors that may be zero, this is commonly represented in Verilog-A using a pattern like this:
if ( Resistance != 0 )
I(a,b) <+ V(a,b)/R;
V(a,b) <+ 0;
This pattern is interpreted by Xyce/ADMS as a node collapse condition: if the resistance is zero, the a and b nodes are collapsed together, and one of the nodes is removed from the problem. If one of the nodes is internal and the other is external (e.g. present on the instance line and connected to other devices), the internal node is collapsed onto the external node. If both nodes are internal, the negative node is collapsed onto the positive. It is legal to collapse an internal node onto ground (completely removing the branch from the problem), but it is not legal to collapse an external node onto ground.
The collapse of nodes must be detectable at the time the device is instantiated --- and therefore the conditionals must depend only on bias-independent quantities. It is not possible in Xyce to modify the network topology of a circuit during a run, so parameter sweeps must not be performed that take a device's parameter through a value that triggers collapse (i.e. you can't use a .STEP loop to sweep a device's resistance over a range that includes zero). Further, due to the complexity of code generation for node collapse, Xyce/ADMS imposes a number of constraints on how this capability may be used:
- The variables used in the conditionals containing the node collapse contribution ( V(a,b) <+ 0 in the example above) must be dependent only on model or instance parameters.
- If the conditional involves variables that are neither model nor instance parameters, these variables ADMS must be able to determine that they depend only on model or instance parameters. That is, you should never use a temporary variable in this context that is also used elsewhere to hold bias-dependent values. ADMS does not keep track of variable dependence changes from one context to another --- if a variable ever holds bias-dependent quantities in a module, it is flagged by ADMS as a bias-dependent variable.
- If the conditional uses variables that depend on model or instance parameters, these variables MUST be initialized in an @(initial_model) or @(initial_instance) block.
- This is required because the node collapse will evaluated in a function that is separate from the function that evaluates model equations, and the variables must therefore be "global_instance" or "global_model" scoped for them to be visible to this function.
- It is best if one uses model or instance parameters directly in these conditionals instead of using variables computed from them. This is what most published models do anyway, and is the simplest case for Xyce/ADMS to handle.
Because Xyce/ADMS interprets all contributions of the form V(a,b) <+ 0 as indicating node collapse, and given the constraints placed by Xyce/ADMS on the conditionals that may contain such contributions, it is not possible to use this sort of contribution to implement "switch branches." Switch branches would have this kind of contribution inside a bias-dependent conditional.
Integration in Xyce of standard device types
By default, if a module is imported into Xyce without adding module attributes to the Verilog-A input, it is accessible from a netlist using the Xyce extension device type, "Y". The Y device type is unusual in that the real type of the device is determined by the characters immediately following the Y, up to the first space, and the name of the instance is the next word on the line. For example:
YRLC rlc1 1 2 MYMODEL R=1k L=1u C=1p
would invoke an "RLC" device named "rlc1", using a model card named MYMODEL, between nodes 1 and 2, using the given R,L, and C parameters. The model card used here would be of the form:
.model MYMODEL RLC ([parameters...])
This may be acceptable, but if you are writing a MOSFET or BJT model, it might be desirable to make your new device plug in to Xyce as an M or Q device instead. Xyce/ADMS provides a capability to do this using module attributes, as documented above. This is currently supported for MOSFET, BJT, capacitor, resistor, and diode devices.
The advantages of doing this include:
- Your netlist can easily be tested using several different models of the same device type for debugging or QA, simply by swapping model cards with a different level number
- BJTs will recognize both NPN and PNP model cards, automatically selecting the correct sign for the internal variable used to modify equations. Similarly, MOSFETS will recognize NMOS and PMOS model cards.
- Your netlist will use standard SPICE syntax for devices of that type (Q for BJT, M for MOSFET, etc.)
The essential attributes needed to accomplish this kind of usage are:
- xyceModelGroup --- may take the values "MOSFET", "BJT", "Diode", "Resistor" or "Capacitor". If given, the module will be instantiated using the standard SPICE syntax for that type of device.
- xyceLevelNumber --- you should always specify an unused model level for this attribute. Model levels currently in use may be listed by running Xyce with the "-param" option --- a list of all known devices will be emitted, and this can be searched for all the levels (e.g. "Xyce -param | grep -i '^q level'" to search for all currently implemented BJT models, or "Xyce -param | grep -i '^m level'" for MOSFETS).
- xyceTypeVariable --- set to the name of the internal variable in your module that is used to determine P or N type devices (applicable mainly to MOSFETs and BJTs). Xyce/ADMS will set this variable to -1 when a PMOS (for MOSFETs) or PNP (BJTs) model card is used.
- xycePTypeValue --- set this if your device uses a value other than "-1" for the P type device. So far, only the Xyce team has found only one device that requires this, the BSIM-CMG, which uses the value 0 for the DEVTYPE parameter to denote PMOS devices.
In addition, you may wish to set other module attributes such as "xyceDeviceName" --- these are generally used only in the automated creation of tables for inclusion in Xyce manuals, and also is emitted by the "-param" informational output function.