Documentation & Tutorials

Xyce/ADMS Users Guide

The Xyce/ADMS Verilog-A compiler tool

Xyce/ADMS is a set of XML templates that provide a code-generating "back-end" to the open source Verilog-A compiler ADMS. With the Xyce/ADMS templates and the ADMS compiler, the Xyce team has been able to import industry-standard device models written in Verilog-A into Xyce. To date, the capability is still an "alpha-quality" tool that has mostly been used internally by the Xyce team; but it has matured enough in recent releases to attract the interest of device model developers inside and outside of Sandia National Laboratories.

The purpose of this document is to describe the use of the Xyce/ADMS capability, its features, and its limitations.

What is ADMS?

ADMS ("Automatic Device Model Synthesizer") is an open-source Verilog-A translator. It reads a Verilog-A input file and produces a complex internal data structure representing the module. It then processes a set of XML templates written in an XSLT-based templating language called ADMST. These user-provided templates access the internal ADMS data structure, and can be written to emit code in any desired language targeting any desired simulator.

Though it is not completely consistent with the use of the term in computer science, since all code generation is provided through the use of XML templates after all Verilog-A parsing has been completed, we refer to these templates throughout this document as the "back-end" of an ADMS compiler. ADMS itself does not come with any code generation templates—these must all be provided by the developer of a simulator for which the code is targeted.

What is Xyce/ADMS?

The term "Xyce/ADMS" refers only to the set of XML templates provided by the Xyce team for use with ADMS. These templates allow ADMS to emit C++ code for a Xyce device model. In most cases, the code produced by this process is ready to compile into Xyce; in a few cases the C++ code emitted requires some manual editing to add features or Xyce-specific changes that cannot be expressed in Verilog-A.

The Xyce/ADMS templates are all found in the utils/ADMS subdirectory of a Xyce source distribution. The primary template files used in Xyce/ADMS are:

  • xyceVersion.xml — provides basic simulator variables to ADMS, mostly used to output comments.
  • xyceBasicTemplates.xml — a set of ADMST templates (subroutine-like code) used to process the ADMS data structure and emit fragments of code
  • xyceHeaderFile.xml — a template that generates the C++ ".h" header file defining the device class
  • xyceImplementationFile.xml — template that generates the C++ ".C" file implementing the device

In addition to templates that generate C++ model code, the Xyce team provides templates that can produce other artifacts of a model:

  • html_params.xml — generates an HTML file describing the device model, its variables, parameters, and other details. This is primarily of interest to Xyce/ADMS developers as a debugging tool.
  • xyceBootstrapFile.xml — generates a small C++ object declaration that can be used in creation of a shared-library plugin for the model
  • xyceMakefile.am.xml — generates a Makefile.am file that can be used to create a shared-library plugin as part of a Xyce build

A directory of all the source code for Verilog-A models that have been incorporated into Xyce (e.g. VBIC, PSP, MEXTRAM, etc.) is provided with the Xyce source code in the utils/ADMS/examples directory.  A "toys" subdirectory under this examples directory has a set of simple Verilog-A models is also included.  The toys directory also contains a DiodeClipper.cir netlist that consists entirely of devices provided as Verilog-A source in that directory.

Table of Contents

Obtaining ADMS

ADMS is no longer maintained as an open source product by its original author, Laurent Lamaitre.  Development of ADMS as open source software has been largely taken over by the Qucs project. 

ADMS source code may be downloaded from the Qucs/ADMS GitHub project.  Before using Xyce/ADMS to import device models into Xyce, you must install ADMS according to the directions on the ADMS project site.

Once installed, be sure that the directory into which ADMS was installed is in your PATH variable.  You should be able to run "admsXml --version" and have ADMS report its version number.  At the time of this writing, the most recent version of ADMS is version 2.3.5.  Most features of Xyce/ADMS will work with any version of ADMS as recent as 2.3.0.

Running Xyce/ADMS

In the remainder of this documentation, we will refer to several directories using the syntax of shell variables.  The following variables will be used:

  • XYCE_SRCDIR:  the top level of the Xyce source tree
  • XYCE_BUILDDIR:  the top level of the directory in which Xyce has been compiled
  • ${prefix}:  The prefix given to configure as "--prefix=".  This is where Xyce and its components will be installed by "make install".
  • XYCE_ADMSDIR:  A subdirectory of XYCE_SRCDIR that contains the Xyce/ADMS XML templates.  This will usually be $XYCE_SRCDIR/utils/ADMS.  When using an installed copy of Xyce, it will be ${prefix}/share/xml.

How it works

Xyce does not at this time have any capability for direct import of Verilog-A models through a netlist.  Before a model written in Verilog-A can be used, it must be converted to C++ using Xyce/ADMS, compiled, and linked into Xyce. 

There are two ways to link a model created in this way into Xyce: direct linking at build time, and building a shared-library plugin.  When the Xyce team imports a model into Xyce we use the former method, and we anticipate that most users of the capability will want to use the latter.

Directly linking a model into Xyce requires full access to the Xyce source code, all libraries that are required for building Xyce, and access to a Xyce build directory that has been used to compile Xyce.  It is done by modifying the Xyce build system to include compilation of the C++ version of the model, linking this into the binary, and registering the device.

The plugin method requires that Xyce be built specially to support shared library loads -- our standard binary distributions of Xyce do not support this feature yet.  A script is provided with Xyce that simplifies the generation and installation of such plugins, and a command-line option is available to load the plugin at run time.

Note: Due to limitations of the Windows linker and the Xyce build system, the plugin method does not work on Windows at this time, and can only be used on Unix-like operating systems.  Cygwin, though it provides facilities that emulate Unix under the Windows operating system, is unable to overcome the linker limitations.

Creating C++ code from Verilog-A

A Verilog-A input file, "mymodel.va," can be processed into C++ suitable for linking into Xyce using the following command line:

admsXml -e $XYCE_ADMSDIR/xyceVersion.xml -e $XYCE_ADMSDIR/xyceBasicTemplates.xml \
-e $XYCE_ADMSDIR/xyceHeaderFile.xml -e $XYCE_ADMSDIR/xyceImplementationFile.xml mymodel.va

This will create a .h and .C file with base names "N_DEV_ADMS" followed by the name of the model on the Verilog-A "module" line.  In the command lines above, the shell continuation-line character "\" has been used to show that the last line is a continuation of the line before it.  You may type the entire admsXml invocation on a single line, or use the "\" character with no following spaces to continue typing the command on a new line.  This convention is used throughout this document in order to produce command lines that are neatly formatted for the web.

For most models, this C++ code can be added to Xyce directly.  In some special cases it might need to be modified by hand.

Creating a shared-library plug-in: Quickstart

Once generated, the fastest approach to testing out a new Verilog-A conversion in Xyce is to create a shared-library plugin for it.  In order to do so, Xyce must be built with some non-default options.  Since this feature is still somewhat embryonic, no binary of Xyce provided by the Xyce team has been built with these options, so this means you must build Xyce from source yourself.  

Prerequisites:

You must configure Xyce using the "--enable-shared" and "--enable-xyce-shareable" and rebuild it.  The "Quick Start" instructions below are also intended to be used with an installed build of Xyce, so you should set "--prefix=" to a directory into which you have permission to write.  Follow the instructions in the Building Guide as for any other compilation, adding these three options to your configure invocation.  Once you've compiled Xyce and tested it using the test suite, execute "make install" in your build directory to install Xyce, its shared libraries, the Xyce/ADMS templates, and the headers necessary for compiling a plugin into your chosen prefix.  

The Xyce team recommends that you choose a prefix other than the default /usr/local or the standard Linux /usr prefixes, both because you might want both serial and parallel builds of Xyce installed, and because the install process places a copy of the "libtool" script created at configure time into ${prefix}/libexec.

NOTE:  The shared library plugin system works properly in parallel runs only since Xyce Release 6.7.  A shared library built according to the directions below using the "buildxyceplugin" script for a serial build should work in a parallel build as long as the  OpenMPI used for the parallel build uses the same base compiler as the serial build, and that parallel Xyce and Trilinos have both been built with the same compiler options as those for the serial build.

Building a plugin:

Once Xyce is installed, a script called "buildxyceplugin" will be installed in the "bin" directory under the prefix you selected.  This script is created at configure time and captures all of the options that were used to build Xyce.  There will also be a script called "libtool" installed into the "libexec" directory under that prefix.  This "libtool" contains configuration necessary to build shared libraries on your system, and is also created at the time you configure your Xyce build.

"buildxyceplugin" takes the following command-line syntax:

   buildxyceplugin [-o <name>] [-d] <verilog-a input file>* <destination directory>

It will perform the following actions:

  • For each Verilog-A source file given, run ADMS with the Xyce/ADMS templates to create C++ code.
  • Compile each C++ file created into an object file.
  • Create a "bootstrap" file used by Xyce to add each of the new modules as a device type accessible from a netlist, and compile it into an object file.
  • Link all of the modules given and the bootstrap file into a shared library file, and install it into the destination directory.  By default, the shared library will be named "Xyce_Plugin_<modulename>.so", with the module name taken from the last Verilog-A file on the command line.  If the optional "-o <name>" argument is given, the plugin will be called "<name>.so".  The plugin will have the ".so" suffix even on Mac OS X, where the normal dynamic library suffix is ".dylib". 

Unless module attributes are added to the Verilog-A input, devices added in this manner will all be "Y" devices, and therefore must be accessed from a netlist using the Y syntax:

   Y<module name> <unique instance name>  <node>* <model name> <instance parameter list>

Module attributes may be added to the Verilog-A input to plug the device into Xyce as new MOSFET (M), BJT (Q), Resistor (R), Capacitor (C), or Diode (D) model levels instead of as Y devices.

If the model has model parameters, then a model name is required, and the model card will be of the same type as the module name:

   .model <unique model name> <module name>  <model parameter list>

By default, all parameters defined in a Verilog-A module are model parameters, and not instance parameters.  Using parameter attributes, the developer can designate some parameters as instance parameters instead, or as both instance and model.  See the section below on parameter attributes for details.

Using your plugin:

Once the buildxyceplugin script completes without error, the plugin may be accessed by adding the "-plugin" option to Xyce, followed by the complete path to the plugin shared library.  This command line option is printed at the end of the buildxyceplugin run, so you can just cut and paste it into a Xyce command line.  Add this command-line option to the Xyce command line anytime you need to use the devices in your plugin, as in:

   Xyce -plugin /my/plugin/directory/myplugin.so  netlist.cir

Building the "toys" plugin

Now that we have presented the basic "quick start" process, you might want to try Xyce/ADMS plugin creation using the "toys" subdirectory.  Here is a simple step-by-step process (making use of the pseudo-shell variables mentioned earlier, which you should replace with the actual paths to the Xyce source and your installation directory):

# navigate to the directory containing the Verilog source
cd ${XYCE_SRCDIR}/utils/ADMS/examples/toys
#Build a plugin
${prefix}/bin/buildxyceplugin -o toys *.va ${prefix}/lib
# Test the plugin
${prefix}/bin/Xyce -plugin ${prefix}/lib/toys.so DiodeClipper.cir

The result should be a file DiodeClipper.cir.prn containing the output signals for a diode clipper circuit as described in the Xyce Users' Guide.  You may plot these signals using the graphics tool of your choice.  The DiodeClipper.cir netlist exercises most of the models in the directory, including a Verilog-A Vsrc, diode, resistor, and capacitor. 

The toys plugin created here also has two versions of a series RLC device, rlc.va and rlc2.va.  The netlist ${XYCE_SRCDIR}/utils/ADMS/examples/toys/rlc_series.cir can be used to test the second of these:

cd ${XYCE_SRCDIR}/utils/ADMS/examples/toys
${prefix}/bin/Xyce -plugin ${prefix}/lib/toys.so rlc_series.cir

This netlist can be modified to replace "YRLC2" with "YRLC" to test the second variant of the RLC device.

The rlc_parallel.cir netlist exercises the rlc3.va parallel RLC-network model, which is described later in this document.

For more detail...

This document has documented a simple process for creating a shared-library plugin for Xyce using the "buildxyceplugin" script.  More detail, including documentation of how to add a device to Xyce without the shared-library approach, may be found in the "Adding a device to Xyce" tutorial on this site.  Users of the Windows operating system who wish to develop models in Verilog-A must follow the more involved method described there in order to link devices into Xyce statically.

Verilog-A features implemented in Xyce/ADMS

Xyce/ADMS implements only the analog features of Verilog-A.  The list below references the Verilog-A Language Reference manual version 2.3.1.

  • Data Types (LRM chapter 3)
    • Integer, real and string data types
    • parameter declarations
      • defaults may depend on other parameters
      • parameter ranges are supported, including use of "include" and "exclude" values
      • parameter aliases via the aliasparam statement are supported
      • parameter attributes are supported
    • electrical and thermal disciplines are currently supported
    • Scalar branch declarations are supported
  • Expressions (LRM chapter 4)
    • Arithmetic, relational, logical, bitwise, and conditional operators of LRM table 4-2 are implemented, with the exception of the exponentiation operator "**".
      • only operators that translate directly to valid operators of C++ are currently recognized
      • In versions of Xyce/ADMS prior to Xyce 6.4, the conditional operator would not work if any arguments depend on probes. 
    • Bitwise shift operators are supported.
    • The functions in table 4-14 are supported, but only in the "Traditional Verilog-AMS" style (without the leading $).
    • Functions in table 4-15 are supported if the C++ compiler in use supports them.  The code emitted is exactly the "Equivalent C function" in table 4-15.
    • Signal access functions listed in table 4-16 are supported with the exception of the current into the module (e.g. I(<port>)).
    • The ddt function is supported, but only in contribution statements.
    • The limexp function is supported.
    • The white_noise and flicker_noise functions are supported, but only in contribution statements
    • Analog functions are supported, but with limitations.
  • Analog behavior (LRM chapter 5)
    • Electrical and thermal discipline potential (V and Temp) and flow (I and Pwr) accessors are supported.
    • Flow and potential probes (LRM 5.4.2.1) are both supported.
    • Both flow and potential sources (LRM 5.4.2.2) are supported, with some limitations.
    • Direct branch contributions are supported with limitations.
    • @(initial_step) is supported, but only without any analysis-specific argument.  See the Limitations section below for more detail.
  • System tasks and functions (LRM chapter 9)
    • Of the functions defined in LRM tables 9-1 through 9-19, only the following functions are supported:
      • $strobe --- not fully implemented.  In this partial implemenation, the format and all variable arguments are simply printed in order.  The format specifiers are not filled in printf- style
      • $finish --- causes Xyce to abort execution and exit with the given message.  As such it functions more like $fatal should.
      • $abstime -- returns the current simulation time (requires a recent version of ADMS --- older versions of ADMS did not recognize $abstime)
      • $realtime --- in older versions of ADMS this was the only recognized way to obtain simulation time, and Xyce/ADMS treats it as a synonym for $abstime.  Later versions of the Verilog-A language reference state that this function is not valid in analog context, so it should be considered deprecated.
      • $temperature --- returns the current simulation temperature in Kelvin
      • $vt  -- the thermal voltage.  If an argument is given, return thermal voltage at given temperature, otherwise return thermal voltage at current simulation temperature
      • $simparam -- partially implemented.  Only "gmin" currently recognized.
      • $limit --- partially implemented, with limitations and Xyce-specific features.
      • $param_given -- returns true if the named parameter has been specified by the user

Limitations of the Xyce/ADMS implementation

Even when a Verilog-A feature is supported by Xyce/ADMS, it may be subject to some restrictions.

  • The ddt function may only be used in contribution statements.
  • Static, dynamic, and noise contributions must be separately specified.
    • it is not legal to mix terms with ddt() on the same contribution line as any terms without ddt()
    • Due to the way Xyce implements ddt() contributions, it is best if only one term with ddt() appears in each contribution statement. 
    • It is not legal to mix  noise functions with non-noise terms on a contribution line.
    • The right-hand side of a noise contribution must contain nothing but a single noise function.
  • Only contributions to current branches may contain noise functions.  Noise functions are not valid in contributions into voltage branches.
  • Since noise functions may only appear in contribution statements, correlated noise must be handled by adding additional nodes to the model, and referencing the node voltage as the correlated noise source.  It is not legal to set a variable to a noise function and use the value of the variable.
  • Branch declarations are supported, but branches so defined are treated simply as aliases for the pair of nodes they represent.  Two named branches between the same pair of nodes are treated as identical.  This has consequences when flow accessors are used on the right-hand side of contributions or expressions --- it is not possible in Xyce/ADMS to declare multiple branches and then access the currents on those branches as if they were parallel.  This limitation is a consequence of how ADMS constructs its data tree; ADMS will substitute the branch's nodes into the data tree wherever the accessor is used, and therefore information about the alias is obscured from the code generation back-end ("Xyce/ADMS").  The only workaround for this limitation is to make the parallel branches distinct by other means.  Examples are given in the "Guidance" and "Special Topics" sections below.
  • Analog functions are translated into a templated C++ function in which all arguments and the return vaue are expected to be of the same level of probe dependence.  Complex use of analog functions that are used with many different combinations of argument dependence may be incompatible with this implementation, and may require post-code-generation manual patching.  The FBH HBT model is the only one we have encountered so far that has problems of this sort.  When this happens, the code generated by Xyce/ADMS will not compile.
  • While both flow and potential sources are supported, assignment of a constant zero (0 or 0.0) to a potential in a contribution statement is special.  Such a contribution represents a collapse of the branch, and should always be enclosed in a conditional whose condition depends only on model or instance parameters (never on solution variables).  See the "Special Topics" section below, under the "node collapse" subsection.
    • Xyce uses modified nodal analysis to solve the circuit problem.  As a result, non-zero or solution-dependent contributions into a potential results in Xyce/ADMS generating an extra solution variable for the "branch current."  This branch current is available to the Verilog-A model as a flow probe.
  • Since the idt function is not supported, capacitors cannot be modeled as suggested in LRM section 5.6.4.  They must be handled by differentiating the charge and contributing into a current source rather than integrating I/C with respect to time and contributing into a voltage source.  The examples rlc.va and rlc2.va in the utils/ADMS/examples directory of the Xyce source tree demonstrate this alternate approach.
    • The example in LRM section 5.6.4 is also invalid in Xyce/ADMS because it combines the static (non-differentiated) contribution of the resistor on the same contribution statement as dynamic (time-differentiated) contributions from the capacitor and inductor.
  • The @(initial_step) analog event is true for all Newton iterations of the DC operating point associated with any analysis.  It is incorrect to use this event to perform one-time initialization of bias-independent quantities, as they will be recomputed for every Newton iteration of the DC OP.  It is also incorrect to use this block to attempt to apply initial conditions or set initial junction voltages. 
    • Bias-independent quantites should be computed in the ADMS-specific pseudo-analog-event blocks @(initial_instance) or @(initial_model)  (see the Xyce/ADMS extensions section below).
  • The $limit function is implemented, but in a very Xyce-specific manner. 
    • ADMS does not properly recognize (unquoted) analog function names as the second argument of a $limit call.  To deal with this, Xyce/ADMS will treat any string in this position that it does not recognize as an analog function name.
    • Xyce/ADMS recognizes only a few pre-defined limiter function names in the second argument.  These are documented separately in the "Special Topics" section of this document. 
    • Given the way Xyce sets up the nonlinear problem, voltage limiting in Xyce is more difficult to do than in SPICE, and the implementation of $limit does more than simply apply a function to its arguments. 

Verilog-A features not implemented in Xyce/ADMS

Xyce/ADMS does not implement any of the features of Verilog-A (or rather Verilog-AMS) intended for digital, discrete-event simulation.  

Xyce/ADMS is unable to optimize initializations of bias-independent quantities, automatically detecting them and computing them only once at the beginning of a run.  If it is desired that a block of computations be carried out only at the beginning of a run, it is necessary to designate these blocks using the "@(initial_instance)" or "@(initial_model)" analog event extensions of ADMS.

Xyce/ADMS does not implement all analog features specified in the Verilog-AMS language reference manual.

  • Data types (LRM chapter 3)
    • Arrays are not implemented
    • genvars are not implemented
    • Only electrical and thermal net disciplines are currently recognized. 
    • Nodeset initializers are not supported.
    • Ground declarations are not supported.
    • implicit nets cannot be used.  All nodes in the model must be declared.
    • Digital nets are not supported, so ports of type "wreal" are not recognized.  Ports must be declared as either electrical or thermal.
    • Only scalar branches are supported.  Vector branches are not implemented.
  • Expressions (LRM chapter 4)
    • The exponentiation operator (**) does  not work.  Attempting to use this operator will result in invalid C++ code.  Use the "pow" function instead.
    • Arithmetic shift operators (<<< and >>>) are not valid in analog blocks, and therefore are not supported by Xyce/ADMS.
    • Concatenation is not supported.
    • The functions in table 4-14 are supported, but the "Verilog" style (with the leading $) is not.
    • Functions in table 4-15 are only supported if the C++ compiler in use supports them.  The code emitted is exactly the "Equivalent C function" in table 4-15.
    • Signal access functions that return the current flow into the module (e.g. I(<port>)) are not supported.
    • The ddt function is not supported except in contribution statements.
    • The ddx, idt, absdelay, transition, idtmod, slew, and last_crossing functions are not supported.
    • Laplace transform and Z transform filters are not supported
    • Analysis-dependent functions other than white_noise and flicker_noise are not supported.
    • The noise_table function is not supported.
  • Analog behavior (LRM chapter 5)
    • "analog initial" blocks are not supported.
    • Flow and Potential accessors for nodes of disciplines other than electrical and thermal are not fully supported.
    • Flow accessors for port branches (LRM 5.4.3) are not supported.
    • Vector branches are not supported.
    • Access of net or branch attributes (e.g. abstol) is not supported.
    • Switch branches (LRM 5.6.5) are not supported in Xyce/ADMS.  The example in LRM 5.6.5 is also invalid in Xyce/ADMS because a contribution of zero into a voltage source always indicates node collapse, and must be in a conditional that is independent of probes (dependent only on model or instance parameters).
    • Indirect branch contributions are not supported.
    • Analog events other than @(initial_step) are not supported.
    • Analysis-specific initial_step events are not supported by ADMS.
    • The event "OR" operator (LRM section 5.10.1) is not supported.
    • Monitored events (LRM 5.10.3) and named events (LRM 5.10.4) are not supported.
  • Hierarchical structures (LRM chapter 6)
    • Xyce/ADMS does not support hierarchical instantiation of Verilog-A modules
    • Modules to be imported into Xyce must be stand-alone modules, and cannot instantiate other modules from Verilog-A.
    • paramset is not supported
  • Mixed-signal (LRM chapter 7)
    • Xyce/ADMS does not support mixed-signal use of Verilog-AMS.  Only the analog compact modeling features are supported.
  • System tasks and functions (LRM chapter 9)
    • Only the functions listed in the prior section are implemented.  Most are not.

Xyce-specific Verilog-A extensions in Xyce/ADMS

In order to allow more seamless integration of new Verilog-A models into the existing structure of the Xyce device model package, several extensions of basic Verilog-A are supported by Xyce/ADMS.  Most of these are implemented by adding non-standard "attributes" to parameters and modules.  This technique has the advantage that models decorated with these attributes will have those attributes ignored by simulators other than Xyce.  One additional feature is provided by ADMS itself: the "@(initial_instance)" and "@(initial_model)" blocks.

Attributes are a standard part of the Verilog-A language, documented in section 2.9 of the Verilog-A Language Reference Manual.  In the 2.3.1 version of the LRM, only two standard attributes are defined:  "desc" and "units".  Xyce/ADMS supports these two standard attributes, but adds a number of others.

Attributes are specified by enclosing attribute specifications inside the delimiter pair "(*" and "*)".

Parameter attributes

Parameter attributes are enclosed in "(*"/"*)" delimiters on parameter declaration lines, as in

  parameter real  R=1 from (0:inf) (* desc="Resistance" units="Ohm" *);

A parameter declared with no attributes will be a model parameter, have unknown units, and have no name assigned in the "Description" column of LaTeX tables that are generated using the "-doc" or "-doc_cat" options of Xyce.

The parameter attributes recognized by Xyce/ADMS are:

  • desc --- a description of the parameter.  Note:  The text of this parameter is used directly in LaTeX output, and should not contain the "^" or "_" characters, which are treated specially by LaTeX.
  • info   --- a synonym for "desc"
  • units --- Units of the parameter.  This is used only in automated generation of tables for the reference manual.  Xyce/ADMS recognizes specific units and if given, will add decorations to the internal parameter definition so that LaTeX tables that include the parameter will have appropriate markup to render the unit well.  Where recognized, the text unit is converted into the appropriate internal unit identifier as if it had been specified with "xyceUnit" (below).  The complete list of recognized text units was chosen by attempting to incorporate all of the text units that have appeared in Verilog-A source code for industry standard models.  Unfortunately, there is no standard for this, and some models use arbitrary combinations that are not supported by Xyce/ADMS.
    • m --- meters
    • m^2 -- square meters
    • m^-3 --- 1/meters^3
    • F  --- Farad
    • Ohm --- Ohms (rendered as upper case Greek letter Omega)
    • V --- volt
    • /V  or V^-1 --- 1/Volt
    • V^-0.5 --- 1/(sqrt(Volt))
    • A   --- Ampere
    • S   --- Siemens, rendered as 1/(upper case Omega).
    • K   --- Kelvin
    • K^-1 or 1/K -- 1/Kelvin
    • degC --- degrees Celsius (rendered with degree symbol and upper case C)
    • /C   or 1/C or degC^-1  --- 1/degC
    • C      --- Coulomb.  Note: this choice of C to mean "Coulomb" is based on the use of that unit string in the VBIC model.  We have seen other models that use "C" for "degrees C". 
    • V/K --- Volt/Kelvin
    • m^2/V/s --- meters^2/Volt/second
    • m/V --- meters/Volt
    • Vm  --- meters*Volt
    • Vm^-1 --- Volt/meters
    • A/V^3 or AV^-3 -- Amperes/Volts^3
    • Am/V^3 or AV^-3m --- Amperes*meters/Volts^3
    • Am^-1 --- Amperes/meters
    • Am^-2 --- Amperes/meters^2
    • Am^-3 --- Amperes/meters^3
    • Ohm m^2 --- Ohms * meters^2
    • Ohm/sq --- Ohms per square
    • Fm^-1  --- Farad/meters
    • Fm^-2  --- Farad/meters^2
    • any unit not listed above will be considered an "unknown" unit for the purpose of Xyce documentation
  • type  --- indicates whether the parameter is an instance or model parameter. 
    • Though some simulators appear to accept "both" as a parameter type, Xyce/ADMS (and ADMS itself) does not.  Only "instance" and "model" are accepted.
    • Though some best-practices articles state that models should never have to specify whether a parameter is model or instance, there is an overhead to allowing a parameter to be both model and instance in Xyce.  Therefore in Xyce/ADMS all parameters are assumed to be model parameters unless designated as instance, and if designated as instance they are not also model parameters unless a second attribute, xyceAlsoModel (below) is given.
  • xyceUnit --- Similar to "units" but takes a unit designator as listed in the "ParameterUnit" enum in the "src/DeviceModelPKG/Core/include/N_DEV_Units.h" file.  A far wider range of parameter units may be selected in this manner than by the "units" attribute.  If neither units nor xyceUnit is given, the parameter is listed as having unknown units.
  • xyceCategory --- Allows parameters to be grouped in categories.  These categories are used only in generation of LaTeX tables for Xyce documentation with the "-doc_cat" option of Xyce.  Valid values are those listed in the ParameterCategory enum in "src/DeviceModelPKG/Core/include/N_DEV_Units.h.  If not given, the parameter will appear in categorized tables in the "unknown category" section.
  • xyceAlsoModel --- Since ADMS cannot recognize "both" as an option for the type attribute, this attribute is provided in order to allow a parameter to be specified as being both an instance and model parameter.  To use it, set "type" to "instance" and set xyceAlsoModel to "yes."  
    • Parameters designated as both instance and model in this manner will be processed as follows:
      • If the instance parameter is specified by the user, the value given is used.
      • if the instance parameter is not specified by the user, the value of the model parameter is used.
      • If the model parameter is not given by the user, the default value of the model parameter is used.
  • dependence --- Allows the model developer to designate that the parameter may take a time-dependent expression.  Only two values are permitted:
    • NO_DEP --- the default, requires that any expression used to define this parameter's value in a netlist be independent of time.
    • TIME_DEP --- designates that it is legal for the user to use a time-dependent expression for this parameter's value in a netlist.

Module attributes

Module attributes are specified inside "(*"/"*)" delimiters on a module line, as in

  module mymodel(p,n) (* xyceModelGroup="MOSFET" xyceLevelNumber="2468" *);

The module attributes are used mainly to allow models to be used with standard SPICE device type specifiers like "M" (MOSFET) and "Q" (BJT) instead of the default "device extension" device "Y".  A module with no attributes will be imported into Xyce in such a manner that it will need to be instantiated in a netlist with "Y<modulename> <uniquename> <nodelist> <model name>", and have an associated model card of ".model <modelname> <module name>".

Recognized attributes for modules are:

  • xyceModelGroup --- allows a module to be registered as one of the standard SPICE types.  Accepted values are:
    • MOSFET: registers device as an "M" device, recognizing both NMOS and PMOS model cards
    • BJT: registers device as a "Q" device, recognizing both NPN and PNP model cards
    • Diode: registers device as a "D" device, recognizing D model cards
    • Resistor:  registers as an "R" device, with optional "R" model cards if the device has model parameters
    • Capacitor: Registers as a "C" device
    • If no xyceModelGroup attribute is given, the device will be a Y device, and will recognize only model cards with the type name equal to the module name.
  • xyceLevelNumber --- for devices with xyceModelGroup specified, gives the level number that must be specified on the ".model" line to select this model
    • If not given, defaults to 1.  ALWAYS specify xyceLevelNumber if you specify xyceModelGroup.
  • xyceDeviceName --- the name given to the device in documentation and "-param" output
    • If not given, defaults to "ADMS <module name>"
  • xyceTypeVariable --- for devices with N and P types (MOSFETS and BJTs), the variable to set to signify which type is selected by the model card
    • This is only needed if the device has been added as one of the standard SPICE devices, e.g. with xyceModelGroup="MOSFET" or xyceModelGroup="BJT".
    • If given, Xyce/ADMS will generate code to set this variable if the model card is of type PMOS (for MOSFETS) or PNP (for BJTs).
    • By default, the variable will be set to -1 if PMOS or PNP model cards are used.
    • If the value the model expects for PMOS/PNP is not -1, you must specify it with the xycePTypeValue attribute.
    • If no xyceTypeVariable is set, then users must identify P type devices in a non-standard way, such as through the use of a type variable on the model card.
  • xycePTypeValue --- specify a value of xyceTypeVariable that signifies P type instead of N type
    • If not given, -1 will be used.
  • xyceSpiceDeviceName  -- used to specify what SPICE-type instance line should invoke this model
    • This is overridden by any xyceModelGroup attribute.
    • Use this only to wire in the device as something other than a MOSFET, BJT, Diode, Resistor, or Capacitor
    • If neither xyceModelGroup nor xyceSpiceDeviceName is given, the device will be a Y device with model type defined by the module name.
  • xyceModelCardType  --- used to specify what .model type is associated with this device
    • This is overridden by any xyceModelGroup attribute
    • Used along with xyceSpiceDeviceName to wire in devices that aren't MOSFET, BJT, Diode, Resistor or Capacitor

@(initial_model) and @(initial_instance)

ADMS and the Xyce/ADMS back-end support constructs "@(initial_model)" and "@(initial_instance)" that are used to designate code blocks that should be executed only once, on instantiation of a device or instantiation of an associated model.  These two constructs are not part of the Verilog-A language, but are an extension provided by ADMS.

Code in the @(initial_model) block is inserted into the module's "Model::processParams" method, which is called once at the time the model card is processed, and each time any .STEP directive causes device model parameters to change.

Code in @(initial_instance) is inserted into the module's "Instance::processParams" method, which is called once for each instance of the device when the netlist is first processed, and again any time a .STEP directive changes instance or model parameters.

Variables assigned to in the @(initial_instance) and @(initial_model) may be referenced from outside these blocks.  Xyce/ADMS will emit declarations for such variables to assure their availability in all contexts where they are used.

Since these pseudo-analog-event constructs are not recognized as part of the Verilog-A language, they should be used wrapped in an "ifdef":

`ifdef insideADMS
      @(initial_model)
`endif
      begin
      <bias-independent assignments that depend only on model parameters>
      end
`ifdef insideADMS
      @(initial_instance)
`endif
      begin
      <bias-independent assignments that depend only on instance and model parameters>
      end

It is best practice when writing a Verilog-A model for use in Xyce that these bias-independent calculations be inside such blocks. If this is not done, the bias-independent calculations will be done on every Newton iteration of every step of the simulation.

To summarize:
  • Variables that are used only inside an @(initial_instance) or @(initial_model) block will be declared as local variables in the processParams() function into which the code will be generated.
  • Variables assigned to inside @(initlal_instance) and used elsewhere in the analog code block will be declared as member variables of the Instance class.
  • Variables assigned to inside @(initial_model) and used elswhere (either in the main analog code block or in the @(initial_instance) block) will be declared as member variables of the Model class.
  • Assignments in @(initial_instance) and @(initial_model) must have bias-independent right hand sides.
  • Assignments in @(initial_model) may only reference model parameters on the right-hand side.
  • Assignments in @(initial_instance) may reference model or instance parameters on the right hand side, and may also reference variables set in @(initial_model).  A common use is to compute temperature-corrected versions of model parameters in @(initial_model) so they are shared by all instances, and then compute size-dependent quantities (which may differ from instance to instance) from the temperature-corrected quantities in @(initial_instance).
  • Assignments in @(initial_model) are executed once per model card present in the netlist, upon initialization or whenever model parameters or the temperature change (as by a .step directive).  The variables set here are shared by all instances of the device that reference the same model card.
  • Assignments in @(initial_instance) are executed once per instance of the device in the circuit, upon initialization or whenver instance parameters, model parameters, or the temperature changes (as by a .step directive).  Each instance of the device has its own copy of the variable.
  • The processParams functions are also called whenever a model or instance parameter is written in the netlist as an expression dependent on global_param variables, and a .step causes the global_param to change value.

Guidance for writing models for use with Xyce

Xyce/ADMS is still a work in progress, and does not support every Verilog-A construct defined in the Language Reference Manual.  The supported and unsupported features were detailed earlier in this document.  Even where Xyce/ADMS accepts a construct, there may be issues of compatibility with the Xyce simulator that can prevent a Verilog-A model from working properly in Xyce.  In this section, we present a list of DOs and DO NOTs for building a Verilog-A model for Xyce with ADMS and Xyce/ADMS.

DO:

  • Make your model well-behaved in all ranges of possible solution variable
    Xyce uses a Newton-Raphson method to converge to a solution.  In the course of finding a solution, it may explore parts of the solution space that are well outside what you might consider "normal" ranges of values.  Make sure your model behaves well even in these regimes, and you will avoid many of the problems that force other models to use techniques like voltage limiting (à la "pnjlim").  It is not enough to argue that the device should never run in that regime --- unconverged solutions might go there.
  • Use @(initial_instance) and @(initial_model) pseudo-analog events for blocks that initialize bias-independent variables that depend only on instance or model parameters, respectively. 
    While some authors have stated that model developers should not be forced to be concerned with such things, it is phenomenally difficult in ADMS to make the necessary determinations at code-generation time that allow a back-end to segregate bias-independent code so that it can be evaluated only at an initialization step.  Using @(initial_instance) and @(initial_model) makes it trivial.

    Since this is an ADMS-specific extension, if you wish your model to be portable between simulators that do not use ADMS for Verilog-A processing, you should use an ifdef.  A simple example would be:

    `ifdef insideADMS
      @(initial_model)
    `endif
      begin : initializeModel
        [...]
      end
    

    A slightly cleaner look can be obtained by defining macros in an include file:
    `ifdef insideADMS
      `define MODEL @(initial_model) 
      `define INSTANCE @(initial_instance)
    `else
      `define MODEL
      `define INSTANCE
    `endif
    

    And in the main code:
    `MODEL
      begin : initializeModel
       [...]
      end
    
  • Always declare variables that are used inside @(initial_instance) or @(initial_model) blocks outside the "analog begin" block, at module scope.
    This is a limitation of how ADMS parses the Verilog-A and flags variable scopes to the back-end:  ADMS marks variables used inside one of these special blocks and also used inside the main analog block as having "global_instance" or "global_model" scope.  These variables must be declared in Xyce's C++ code as member variables in the Instance or Model classes so that they can be accessed by the several functions that are called in the process of running a simulation; variables used only locally in one or the other place can simply be local variables to the function that is generated.  When a variable is declared in the Verilog-A at module scope and used both inside and outside of the @(initial_*) blocks, ADMS flags them in a way that can be used to trigger this decision making.

    Declaring variables inside any begin/end block makes ADMS always scope these variables as block local variables, and they are never flagged as "global_instance" or "global_model" scoped variables.  This makes it impossible for Xyce/ADMS to determine that it must move the declaration of those variables into the Instance or Model classes.

    RIGHT:
    module mymodel(p,n);
      electrical p,n;
      inout p,n;
      real myvar1, myvar2;
      parameter real someparam=1;
      analog begin
        @(initial_model)
        begin : initial_model
          myvar1 = someparam*5.0;
        end
        myvar2 = myvar1*V(p,n)
        [....]
      end
    endmodule
    

    WRONG:
    module mymodel(p,n);
      electrical p,n;
      inout p,n;
      parameter real someparam=1;
      analog begin
        real myvar1, myvar2;
        @(initial_model)
        begin : initial_model
          myvar1 = someparam*5.0;
        end
        myvar2 = myvar1*V(p,n)
        [....]
      end
    endmodule
    

    The first of these will generate correct code in Xyce, with myvar1 being a member of the Model class associated with the module.  The second will generate incorrect code, with myvar1 being declared local in both the Model::processParams function (where the initial_model code is placed) and the Instance::updateIntermediateVars function (where the rest of the analog block is placed).

    When myvar1 is used in a node-collapse conditional (see below), Xyce/ADMS will throw a fatal error in the second case, because only variables of global_instance or global_model scope may be used in such conditionals.

    The Xyce team has found that most published Verilog-A models have variables declared at module scope.  We have found only two that put the declarations inside the analog block, and fail to process correctly in Xyce/ADMS (usually with a fatal error as described in the last paragraph).  Moving the declarations outside of the analog block completely fixes the problem.
  • Localise "OP" computations and enclose them in an ifdef
    As of Xyce Release 6.5, the SPICE-like ".OP" function does not output device-specific operating point variables.  Many Verilog-A models have large numbers of computations of such variables that end up being inaccessible in Xyce.  While we do intend to extend Xyce's .OP output capability in the future, doing this extra computation is wasteful in any current version of Xyce.  Further, many models that evaluate operating point variables make use of the "ddx" verilog operator, which is currently unsupported by Xyce/ADMS.
    Placing all of these operations inside an ifdef allows them to be turned on with a simple "`define" addition to the source code.
    RIGHT:
     `ifdef __DO_OP_CALC__
      CGSI = - ddx(QGI,V(Branch1));
      [...]
     `endif
     
    When processing this model for Xyce, simply do not define the "__DO_OP_CALC__" symbol.

    IF YOU DO NOT FOLLOW THIS GUIDANCE: Xyce/ADMS will output C++ code that will either be inefficient or not compile.  Operating point variables that do not involve the ddx operator will be calculated on every evaluation, but Xyce will not emit the code necessary to access these variables; computing them is therefore just a waste of processor cycles and memory.  Xyce/ADMS will also emit function calls with the text "FIXME" in their names whenever the Verilog-A ddx operator is called, and this code will not compile because these functions are not defined.

DO NOT:

  • Do not use @(initial_step) to denote computations to be done only once, at initialization.
    The @(initial_step) analog event is defined in the Verilog-A Language Reference Manual as being true at the operating point step of every analysis.  Xyce uses a Newton-Raphson solver (among others) to obtain the operating point, and this means that any code inside an @(initial_step) block is run at every iteration in the course of solving the operating point. 

    Since other simulators may implement @(initial_step) in a different way, if your model is designed for portability then you should use ifdefs to turn off this usage when using ADMS.  An example:
    `ifdef insideADMS
      @(initial_model)
    `else
      @(initial_step)
    `endif
      begin : initializeModel
        [...]
      end
    

    In this example, the correct @(initial_model) ADMS extension will be used when compiling with ADMS, and the @(initial_step) analog event used for other simulators you know work with that approach.

  • Do not use analysis-specific @(initial_step("type")) analog events
    These are not recognized by ADMS, and will cause ADMS to exit with an error.  Use an ifdef to prevent these from being seen when compiling with ADMS.
  • Do not attempt to apply initial biases in @(initial_step)
    We have seen models that require initialization of junction voltages (e.g. VBIC) in order to converge well.  The VBIC authors point out the need for such initialization and leave this initialization to simulator implementations, but we have seen other models that attempt to set initial conditions from the Verilog using user-provided "IC"  parameters and the @(initial_step) analog event, e.g.:
    WRONG:
    @(initial_step)
    begin
      CapacitorCharge = C*IC;
    end
    This will not work in Xyce, due to the way the nonlinear problem is solved.  Any attempt by a device to compute bias-dependent quantities using anything other than the biases currently applied from the solution vector will lead to convergence failures. There is currently no way to do junction voltage initialization directly from Verilog-A in Xyce.  Applying initial biases in devices requires manual post-ADMS patching of the device using the "voltage limiting" machinery described below.  The post-code-generation patches that are applied to the VBIC models in the utils/ADMS/examples/vbic_r1.3_prerelease directory of the source tree give examples of how this junction initialization should be performed.
  • Do not use a single temporary variable to store multiple bias-dependent temporary computations
    Due to the way ADMS records variable dependencies on probes, a variable that is used in multiple temporary bias-dependent computations will be marked as depending on all of the probes that appear in all expressions that set that variable.   Any other variable that depends on the first will inherit all of those dependencies.  This can lead to unnecessary Jacobian elements being defined only to be filled with zeros when the actual derivatives of expressions are computed.

    Instead, you should either use unique temporaries for different contexts, or wrap expressions containing temporaries in a begin/end block:
    INEFFICIENT:
    real temp,foo,bar;
    temp=5.0*v(a,b);
    foo=10.0*temp;
    temp=10.0*v(c,d);
    bar=5.0*temp;

    EFFICIENT:
    real foo,bar;
    begin
      real temp;
      temp=5.0*v(a,b);
      foo=10.0*temp;
    end
    begin
      real temp;
      temp=10*v(c,d);
      bar=5.0*temp;
    end

    In the first case, ADMS will mark that "temp" depends on both V(a,b) and V(c,d), even though in context it only depends on one of these at a time.  Therefore both foo and bar will be marked as depending on both probes, and this could lead to extra (numerically zero) Jacobian elements in the linear system that Xyce uses.  In the second case, because each "temp" is block-local, ADMS knows to mark it as dependent only on one of the probes.

    This kind of thing shows up commonly when model developers create macros that reference a single, global temporary variable.  Making the macro create a local block with a local temporary is just as straightforward, and avoids this problem.  Further, using macros that reference a global temporary can lead to scoping problems if the same macro is used both inside and outside of an @(initial_instance) or @(initial_model) block.
  • Do not assume that multiple named branches between the same pair of nodes can be used to access distinct currents
    In Verilog-A, one may declare named branches between nodes, and use the names instead of the node pair when constructing probes and contributions.  Due to a limitation in the way ADMS constructs its data tree, the information about the use of these branch names is obscured by the time the code generation templates get access to it, and it is not currently possible for Xyce/ADMS to treat these named branches as anything other than an alias for a node pair.  This unfortunately means that the following Verilog-A snippet, which attempts to implement a parallel RLC network, will produce incorrect code:
    WRONG:
    module rlc3(p,n);
    electrical p,n;
    inout p,n;
    parameter L=1e-3 from (0,inf);
    parameter R=1e3 from (0,inf);
    parameter C=1e-12 from (0,inf);
    branch (p,n) inductorBranch;
    real inductorCurrent;
    real capacitorCharge;
    analog
      begin
         I(p,n) <+ V(p,n)/R;
         capacitorCharge = C*V(p,n);
         I(p,n) <+ ddt(CapacitorCharge);
         inductorCurrent = I(inductorBranch);
         V(inductorBranch) <+ L*ddt(inductorCurrent);
    end
    endmodule

    This module, when run through Xyce/ADMS, will emit no error messages, and will produce code that will compile and link into Xyce.  But the code generated will be incorrect, and will attempt to create a set of equations such that I(inductorBranch)=I(p,n) is the total current flow between p and n, and not the current only through the inductor.  The most likely result is that any simulation using this model will fail to converge at DC.

    Workaround

    In order to implement the RLC network intended above, it is necessary to work around this bug by "faking out" Xyce/ADMS so it recognizes that the current through the inductor is different from the total current flowing from p to n.  This is done by inserting an additional resistor in series with the inductance, and then letting that resistance take zero value.

    module rlc3(p,n);
    electrical p,n;
    inout p,n;
    electrical internal1;
    parameter L=1e-3 from (0,inf);
    parameter R=1e3 from (0,inf);
    parameter C=1e-12 from (0,inf);
    parameter R_dummy=0 from [0,inf);
    branch (internal1,n) inductorBranch;
    real inductorCurrent;
    real capacitorCharge;
    analog
      begin
         I(p,n) <+ V(p,n)/R;
         capacitorCharge = C*V(p,n);
         I(p,n) <+ ddt(CapacitorCharge);
         inductorCurrent = I(inductorBranch);
         V(inductorBranch) <+ L*ddt(inductorCurrent);
         if (R_dummy>0)
           I(p,internal1) <+ V(p,internal1)/R_dummy;
         else
           V(p,internal1) <+ 0;
    end
    endmodule
    In this workaround, the inductor branch is clearly distinct from (p,n) at the time Xyce/ADMS generates code, but the zero-ohm resistor will cause the internal1 node to map to p, thereby making "inductorBranch" really be (p,n) at run time.  This module will produce correct code that does what the model developer intended.

    A working parallel RLC model demonstrating this workaround is provided as "rlc3.va" in the "toys" directory described earlier.  It can be compiled into a shared library as described above, and tested using the "rlc_parallel.cir" netlist that accompanies it.

Special topics

There are a number of issues that require special attention when writing Verilog-A models for inclusion into Xyce, or for porting existing Verilog-A models into Xyce.

Voltage limiting

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:

    limiter_function(value, last_value[,argument_list]);

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.

Node collapse

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;
else
  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.