Tutorial: Adding a device to Xyce

Introduction

This tutorial is intended as a brief introduction to adding a device model to the Xyce™ Parallel Electronic Simulator.

NOTE: This document has largely been superseded by the Xyce/ADMS Users’ Guide.  It is being retained in the documentation section because it is not strictly limited to adding device models through a Verilog-A process, but is not regularly updated as Xyce development proceeds.

Target Audience

The target audience is compact model developers. 

Prerequisites

You should be familiar with the Xyce Mathematical Formulation before attempting to add a device model to Xyce. It is assumed that you already know how to build Xyce, and that you are comfortable using your operating system’s editors. It is assumed here that you are already a compact model developer and that you are only reading this to learn the specifics of how to get your model coded in Xyce. 

This document is a more in-depth discussion of the process of adding a compact model in Verilog-A to Xyce, explaining how to wire a device into Xyce permanently and also how to create a shared-library plugin.  A separate document, the Xyce/ADMS Users’ Guide, provides a simpler process that was introduced with Xyce 6.5, and documents the capabilities of the Xyce/ADMS Verilog compiler.  It should be read first.

Overview

This tutorial will show an example of how to add a very simple device model to Xyce. The demonstration model will implement a two-terminal device that simulates a simple series RLC combination. The tutorial will show one approach to adding this device: implementation of the compact model in Verilog-A, converting this model to C++ code that can be used in Xyce by using ADMS with the Xyce/ADMS back-end, and adding the new model to Xyce.

Direct implementation of the compact model in C++ and adding the new model to Xyce is more difficult, and requires a more thorough understanding of the Xyce code structure. This tutorial will not describe that technique. If you are interested in developing C++ compact models in Xyce, you should still review the Verilog-A method and look at the C++ source code it generates. Then consult the Doxygen documentation that can be generated in the doc/doxygen directory of the Xyce source tree.   The "Circuit Device How To…" page in the Doxygen documentation describes the basic functions you must provide, and points you at the resistor and capacitor devices as sample implementations. The RLC device created through the Verilog-A translation process is another implementation example.

Note: The doxygen documentation is no longer provided in source tarballs, as it was determined that it inflated the download size and was of limited interest to most users, but may be generated if you have Doxygen and graphviz installed on your system.  Simply type "doxygen Doxyfile" in the doc/doxygen directory, and it will produce files in the "html" subdirectory that may be browsed with a web browser (open html/index.html first).

In either case, you must be building Xyce from source code in order to add models to it. Xyce does have a limited beta capability using shared-library "plug-ins" to add models at run time, but building these plug-ins still requires access to the full Xyce source code tree because the plugin capability is not enabled by default and is not present in any binary release of Xyce.

Adding a device to Xyce through Verilog-A

This section assumes you are already familiar with the Verilog-A language and are comfortable developing compact models in this language. This page does not include any Verilog-A instruction.

In this tutorial, we will add a simple two-terminal device to Xyce that internally simulates a resistor, capacitor, and inductor in series. When we’re done, this device can be accessed in a netlist using the Xyce "Y device" extension:

YRLC <name> <node1> <node2> R=<resistance> L=<inductance> C=<capacitance>

Prerequisites

Before you can use Verilog-A, you must download and build the ADMS code generator. Download the ADMS source code from https://github.com/Qucs/ADMS.  This github site is the current repository for the actively maintained version of ADMS, which has already had patches proposed by the Xyce team applied to it.  In prior releases we had recommended obtaining ADMS from an unmaintained sourceforge repository and applying patches to it, but that is no longer recommended.

Build ADMS according to the instructions provided with it.

The Xyce/ADMS capability is a work in progress

The Xyce team has been using the ADMS procedure outlined in this tutorial for several years, and has used it to add several devices to Xyce, including the following models: VBIC 3T_et_cf, FBH HBT_X 2.1, PSP, EKV 3.0, EKV 2.6, and BSIM-CMG 107. Nevertheless, the back-end described here is very much a work in progress and all of the models mentioned have required a lot more work than the simple RLC model does. The RLC model described below can be imported into Xyce just by running ADMS, adding the generated code to the Xyce build process, and recompiling.

Several issues add complexity to the task of getting a sophisticated Verilog-A model into Xyce:

  • Node collapse, where an internal node may be mapped away based on the value of a model parameter
  • Voltage limiting via "pnjlim" or other means
  • Setting initial junction drops (as is usually required for BJT and HBT models)
  • Complex use of analog functions

So far, the team has been able to improve the back-end to the point that it is not especially difficult to deal with these issues, but each one of them presents challenges to the back-end that almost always force us to do post-processing of the generated code before it will compile. If you are developing Verilog-A models that make use of these features, you may have to do some extra work (e.g., hand-editing or making patches) after ADMS is finished generating code.  As an example, if you look in the utils/ADMS/examples/fbh_hbt-2.1, you will find three ".patch" files that post-process that device’s analog function template definitions so that they will compile, and add junction voltage initialization.

The RLC device in Verilog-A

The simplest implementation of the series RLC device is given below.

`include "disciplines.vams"
`include "constants.vams"

`define attr(txt) (*txt*)

module rlc (p,n) `attr(xyceSpiceDeviceName="RLC" xyceLevelNumber="1");
  electrical p,n;
  inout p,n;
  electrical internal1, internal2;

  parameter real L=1e-3 from (0:inf) `attr(info="Inductance" type="instance");
  parameter real R=1e3 from (0:inf) `attr(info="Resistance" type="instance");
  parameter real C=1e-12 from (0:inf) `attr(info="Capacitance" type="instance");
  real InductorCurrent;
  real CapacitorCharge;

  analog
  begin

    I(p,internal1)<+ V(p,internal1)/R;

    CapacitorCharge = V(internal1,internal2)*C;
    I(internal1,internal2) <+ ddt(CapacitorCharge);
   
    InductorCurrent=I(internal2,n);
    V(internal2,n) <+ L*ddt(InductorCurrent);

  end
endmodule

This simple example is not the optimum implementation of the RLC, but demonstrates some basic requirements. The above Verilog-A code may be found in the "utils/ADMS/examples/rlc.va" file included in the Xyce source code distribution.

In this example, the resistor, capacitor, and inductor are implemented exactly as they would be if they were stand-alone devices. A better example of how to implement this device, resulting in one fewer solution variable being required, is included in the Xyce source code as file "utils/ADMS/examples/rlc2.va".

Things to note about the example
  • The first two lines are required for ADMS to include some definitions we need.
  • The third line defines an "attr" macro that we use in the Xyce/ADMS back-end to tell Xyce how to use the device. This information cannot be encoded in Verilog-A directly and cannot be guessed. Some other simulators that use ADMS have information like this hard coded in their back-ends instead of using the technique here; but by doing it this way Xyce is able to import new Verilog-A models without having to edit the back-end code (unless the new model uses features not supported in the existing back-end).
  • The Verilog-A module declaration "module rlc(p,n);" has been augmented with a call to the "attr" macro. The xyceSpiceDeviceName="RLC" attribute tells Xyce that when the model is compiled, it will be accessed as a device of type "YRLC". The xyceLevelNumber="1″ is in this case not used, but it can be used to create multiple devices of the same type, selectable by having a model card with a "level" parameter to distinguish them. If these attributes are left out then the Xyce/ADMS back-end emits placeholder code with the comment "FIXME" in several places, and these can be hand-edited to add the appropriate information as a post-processing step. Using the attributes, however, is more convenient.
  • Each parameter declaration has been augmented with attributes using the "attr" macro. "info" is used for documentation of the parameter (Xyce can output a LaTeX table containing device parameters including this information). "type" can be "instance" or "model", to declare that a parameter will occur either on an instance line or in a .model card. There are numerous other Xyce-specific attributes that can also be set. See the examples in the "utils/ADMS/examples" directory to see how they may be used in real devices such as the VBIC 3T_et_cf model or the FBH HBT_X model.

Processing the Verilog-A with ADMS

Once your Verilog-A file is created, it is necessary to use the Xyce/ADMS back-end with ADMS to generate C++ code that can be used in Xyce. Assuming your RLC code is saved in a file called "rlc.va", and that your Xyce source code tree is in $HOME/src/Xyce, this is accomplished by running "admsXml" (installed by ADMS) with command line arguments to point at the files in the Xyce/ADMS back-end.

admsXml -x -e $HOME/src/Xyce/utils/ADMS/adms.implicit.xml \
-e $HOME/src/Xyce/utils/ADMS/xyceVersion_nosac.xml \
-e $HOME/src/Xyce/utils/ADMS/xyceBasicTemplates_nosac.xml \
-e $HOME/src/Xyce/utils/ADMS/xyceAnalogFunction_nosac.xml \
-e $HOME/src/Xyce/utils/ADMS/xyceHeaderFile_nosac.xml \
-e $HOME/src/Xyce/utils/ADMS/xyceImplementationFile_nosac.xml \
rlc.va

As when we built Xyce, we have used the shell "continuation character" to break this single command line into two lines. It is important that "\" be the last character on the line, that it be preceded by a space, and that no spaces follow it.

The admsXml command line above will create a .C and a .h file ready to compile in to Xyce. The name of the files is based on the name of the device in the Verilog-A "module" line, and NOT the file name of the Verilog-A code itself. In the current implementation, the file name is constructed by appending "N_DEV_ADMS" to the module name. In the case of the RLC device, our output files are N_DEV_ADMSrlc.C and N_DEV_ADMSrlc.h.

To use this code in Xyce, it will be necessary to add it to the build system of Xyce, add a call to the new device’s "registerDevice" method in an appropriate place, and recompile Xyce.  These steps are discussed below.

"Wiring in" the ADMS-generated code

The ADMS command line given in the previous section created two C++ files, a header and an implementation file named "N_DEV_ADMSrlc.h" and "N_DEV_ADMSrlc.C", respectively. Because the RLC device is so simple, the code produced by ADMS is ready to insert into Xyce with no further modification. All that is necessary is to add the two files to the Xyce build process and to add code to the device manager to get Xyce to recognize the new device.

Add the RLC device to the build system

There is already an "ADMS" subdirectory of the src/DeviceModelPKG directory that is used for adding new devices produced by ADMS. Copy N_DEV_ADMSrlc.h to the src/DeviceModelPKG/ADMS/include directory, and copy N_DEV_ADMSrlc.C to the src/DeviceModelPKG/ADMS/src directory.

Edit the file "Makefile.am" in src/DeviceModelPKG/ADMS, and add the names of the two files to the "libADMS_la_SOURCES" variable, which should look something like this when you are done (the actual appearance will vary from release to release of Xyce, as this file is updated often — the point here is that you’ve added two lines to what already exists in the file):

libADMS_la_SOURCES = \
  $(srcdir)/src/N_DEV_RegisterADMSDevices.C \
  $(srcdir)/src/N_DEV_ADMSHBT_X.C \
  $(srcdir)/src/N_DEV_ADMSPSP103VA.C \
  $(srcdir)/src/N_DEV_ADMSbsimcmg.C \
  $(srcdir)/src/N_DEV_ADMSvbic.C \
  $(srcdir)/src/N_DEV_ADMSrlc.C \
  $(srcdir)/include/N_DEV_RegisterADMSDevices.h \
  $(srcdir)/include/N_DEV_ADMSHBT_X.h \
  $(srcdir)/include/N_DEV_ADMSPSP103VA.h \
  $(srcdir)/include/N_DEV_ADMSbsimcmg.h \
  $(srcdir)/include/N_DEV_ADMSrlc.h \
  $(srcdir)/include/N_DEV_ADMSvbic.h

Remember to have no spaces after any "\" character above.

Register the new device

Edit the file "N_DEV_RegisterADMSDevices.C" in the src/DeviceModelPKG/ADMS/src directory. You will see a block of "#include" directives. Add your new device’s include file here so it will now read:

#include <N_DEV_ADMSHBT_X.h>
#include <N_DEV_ADMSPSP103VA.h>
#include <N_DEV_ADMSvbic.h>
#include <N_DEV_ADMSbsimcmg.h>
#include <N_DEV_ADMSrlc.h>

Next, look a few lines down in the file and you’ll see a series of calls to "registerDevice();" Add your new device’s registerDevice call to this, so it reads:

  ADMSvbic::registerDevice();
  ADMSHBT_X::registerDevice();
  ADMSPSP103VA::registerDevice();
  ADMSbsimcmg::registerDevice();
  ADMSrlc::registerDevice();

Your device is now set up to be used. All that’s left is to update the build system so that the new device is compiled, and rebuild Xyce.

Updating the build system

To rebuild the Xyce build system, you need the following tools:

If you don’t have these tools installed on your system, you must install them before proceeding. Most operating systems provide these tools in their package management systems, or you can build them from source using the links above.

Once you have the autotools installed, you can execute the "bootstrap" script in the top level of the Xyce source tree:

  cd $HOME/src/Xyce
  ./bootstrap

Xyce is now ready to be recompiled. You will need to change directories to your Xyce build directory and re-run the "configure" invocation you used when building Xyce originally. Once configure is done, a "make" in the build directory will compile your new device and wire it in to Xyce.

You can check that the new device is there by running "src/Xyce -param | less" in the build directory. Xyce will respond by emitting the instance and model parameters recognized by all devices, and you can see your new device in there as "RLC level 1".

Testing the Verilog-A imported device

The RLC device you just created and wired in to Xyce can now be run in a netlist.

Because the SPICE netlist format recognizes device types by the first character on the line, there is a limited number of device types that can be supported using the normal netlist syntax. Xyce has opted to use the "Y" device as an extension mechanism. In this mechanism, rather than having a device’s type and name specified as the first word on the line, the device type is taken from the characters following "Y" up to the first space, and the device name is the second word on the line.

By using the particular set of attributes on module declaration of the Verilog-A source, we have indicated that our new device will be known as a "YRLC" device. It will be used in a netlist using the following format:

YRLC <name> <node1> <node2> R=<resistance> L=<inductance> C=<capacitance>

Compare this to how a resistor would be specified:

R<name> <node1> <node2> <resistance>

A complete netlist that uses the new device is:

Test of series RLC circuit

V1 1 0 SIN (5v 5v 20MEG)
Yrlc rlc1 1 0 R=1kohm L=1mH C=1pf

.tran 1n 4u
.print tran v(1) I(v1)
.end

This netlist will produce the same signals as the one given below, where the R, L, and C are discrete devices:

Test of series RLC circuit

V1 1 0 SIN (5v 5v 20MEG)
R1 1 2 1Kohm
C1 2 3 1pF
L1 3 0 1mH

.tran 1n 4u
.print tran v(1) I(v1)
.end

Alternate approach: run-time dynamic library plug-in

In Xyce the most established method of adding a Verilog-A model is the one we have described up to now. But starting with the 6.1 release we have begun to implement a way of loading new devices using dynamically loaded shared libraries, rather than wiring them in at compile time. This feature is experimental and currently works only on real Unix-like systems like Linux, FreeBSD, and Mac OS X. It does not yet work on Windows under Cygwin.

If you want to try this method out, be sure to undo any changes you have made to Xyce to hard-wire the RLC device according to the directions above!

To use this feature you must build the shared-library version of Xyce. Once the shared-library build of Xyce is complete, you can build your new device as a shared library.

Rebuild Xyce to use shared libraries

The first step to using dynamic plug-ins is to rebuild Xyce to support the feature. Add the following two lines to the "configure" command line you used to build Xyce originally:

   --enable-shared \
   --enable-xyce-shareable \

Once configure is re-run with these additional arguments, recompile Xyce with "make clean && make". The result will be that Xyce will be linked against a shared-library "libxyce.so" (or libxyce.dylib on OS X), and the user plugin option will be available.

Create the demo plug-in.

In your Xyce source directory you will find that there is a "user_plugin" directory that just happens to contain exactly the "rlc.va" Verilog-A model we have used above, and in your build directory you will find user_plugin directory that contains a Makefile that is all ready to use. From the top level of the build directory, after building Xyce, simply type "make plugin".

The build system will run ADMS on the Verilog input, compile the code, and produce a "libADMSrlc.so" file in the user_plugin/.libs directory.

You can then run "src/Xyce -plugin `pwd`/user_plugin/.libs/libADMSrlc.so -param" and see that the RLC device is present. If you omit the "-plugin" option and the shared library name, you will see the RLC device is not there.  You can use Xyce with the "-plugin" option and shared library name to run the example netlist above, producing the same results.

If you install the shared build of Xyce with "make install", it will put the shared libraries into the lib directory associated with your install prefix. If you do so and want the plugin accessible, you should also do a "make install-plugin", which will put the shared library of the plugin in the same place.  Xyce will not automatically detect the presence of plugin libraries in its lib directory — you will always need to use the "-plugin" flag to tell Xyce which plugin to load.

Using a different device as a plugin

As shipped, the user_plugin directory is intended merely as a demonstration of how to create a shared library plugin. There are three essential components to this plugin directory:

  • A Makefile.am file that declares the name of the library to produce and the files needed to produce it.
    • The relevant Makefile.am is the one in the "user_plugin" subdirectory of the Xyce source tree.
    • This Makefile.am also contains the ADMS command line to produce C++ from the verilog input.
    • If you ever edit this file, you will need to re-run the "bootstrap" script in the top-level of the Xyce source tree, as you had to do when wiring in the device by hand.
    • After running "bootstrap" you will also need to re-run "configure" in your build directory.
  • A "N_ADMSrlc_bootstrap.C" file that declares a structure called "Bootstrap", whose constructor calls the "registerDevice()" method of the new device
    • When Xyce is started with the "-plugin" option, it will load the shared library and create this Bootstrap object. The constructor of the object will automatically register the device, just as we did by hand when we added the "registerDevice()" call to the N_DEV_RegisterADMSDevices.C file, above.
  • The Verilog-A input file, rlc.va
    • This file will be processed into C++ by ADMS.

If you wanted to generate a plugin for a different device, you’d have to modify the Makefile.am file to compile your device instead. It is well commented and the files are named intuitively, so it should be reasonably easy to understand how to modify it. You would also need to produce a "bootstrap" file similar to the one provided for the RLC device. 

Some things to note about the user_plugin directory if you are planning to modify its contents for your own device:

  • The RLC device is very simple and is entirely contained in one file. 
    • If you are trying to build a more complex Verilog-A model, you might have to add additional options to the ADMS command line in user_plugin/Makefile.am
    • The most likely thing you would have to add to the ADMS command line is an option to let it find any files that your main ".va" file includes.  For example, if your top-level Verilog file includes several files in the same directory,  add "-I$(srcdir)" to the admsXml command line.
  • You will have to create a bootstrap C++ file similar to the one in N_DEV_ADMSrlc_bootstrap.C, customized to reference your own model’s registerDevice method. 
    • The Xyce/ADMS back-end will create a class whose name is the concatenation of your "module" name and the letters "ADMS."  Our RLC model had a line "module rlc" at the beginning, and so its name is "ADMSrlc"
    • Your bootstrap file needs to include the .h file generated by ADMS, and must define a bootstrap struct whose constructor calls your registerDevice.
      • The N_DEV_ADMSrlc_bootstrap.C file could be copied, replacing all the instances of "rlc" with the name that appears on your device’s module line.
  • You will need to rewrite the user_plugin/Makefile.am file in the Xyce source tree to refer to your own files instead of our demonstration files.  Copying the existing Makefile.am, but changing "rlc" everywhere in that file to the module name of your device should be enough, unless you need to add a "-I" flag as described above.

While the user_plugin directory is set up to use a Verilog-A model and ADMS to convert the Verilog-A to C++, you can also use this technique if you are writing a device from scratch in C++. You will have to modify the Makefile.am accordingly.

Final notes

In this tutorial we have demonstrated how to create a simple model in Verilog-A, translate that model into C++ using ADMS, and build that C++ model into Xyce.

If you intend to develop your own models, it is a worthwhile exercise to read through the C++ code that ADMS has produced and compare it to the code for other devices in Xyce, especially if you intend to develop models in C++ from scratch. The Doxygen documentation is no longer distributed in Xyce releases, but may be built (assuming you have Doxygen installed) by "cd"ing into the doc/doxygen directory of the Xyce source tree (say, $HOME/src/Xyce) and running  the command "doxygen Doxyfile".  You may then view the file $HOME/src/Xyce/doc/doxygen/html/index.html in a web browser, and you should read the "Circuit Device How To" pages while studying the device C++ code.