Introduction to device equations
The purpose of the Device Equations option is to change the built-in model equations for one or more of the semiconductor devices (GaAsFET, Diode, Junction FET, MOSFET, Bipolar transistor, and IGBT). This means you can extend PSpice to support user-defined or proprietary native device models.
This option is not an addition to PSpice: it is a different packaging of the program that includes the source code for the device model subroutines. You need a Device Equations license to modify and extend PSpice code, but you do not need a Device Equations license to use the modified code.
There are several kinds of changes that can be made using the Device Equations option. These include, in ascending order of complexity:
- Changing a parameter name
- Giving a parameter an alias
- Adding a parameter
- Changing the device equations
- Adding a new device
- Specifying new internal device structure
You need a supported C++ compiler to compile Device Equations extensions; for Windows 95/98 and NT, you need Microsoft Visual C++ 6.0 or later.
Device Equations extensions are implemented using a dynamic-link library, which means you can share your models with other users by distributing just a DLL.
If you want to run PSpice on Windows 95 or NT with a Device Equations DLL developed by someone else, then you do not need a compiler or a Device Equations license. Just copy the DLL into the directory with your PSpice program file. For more information, see Simulating with the device equations option<link>.
Making device model changes
To get started, look at the files M.H and MOS.CPP, which implement the MOSFET equations. The other devices have similar structures.
M.H contains two important class definitions:
During read-in, the simulator creates an instance of the transistor class for every MOSFET in the circuit and an instance of the model class for every .MODEL statement of type NMOS or PMOS. The transistor instance is set up using information particular to that transistor, such as the nodes to which it is connected, its length and width, and the locations of its entries in the circuit’s conductance matrix. All parameters of the model object are set up using the values from the .MODEL statement, if one exists; otherwise, the default values are used.
The transistor object corresponds to the LOC, LOCV, and LX tables in U.C. Berkeley SPICE2. The model object corresponds to the LOC and LOCM tables in SPICE.
The simulator needs to associate each entry in the model class with a model parameter name (and default value) in the .MODEL statement. You can accomplish this by using the ASSOCIATE macro. Just below the device class in M.H there is a list of all the parameters, each in an ASSOCIATE macro. The occurrence of ASSOCIATE binds together the class entry, the parameter name, and the default value. The read-in section of the simulator uses this information to parse the .MODEL statement.
For more details on how to change parameters, click the following:
Changing a parameter name
This is the easiest change. Find the parameter in the list of ASSOCIATE macros. Change the parameter’s name (last item on the line) and/or the default value (middle item). The names and defaults of the model parameters that are supplied can be changed, as well as those parameters that are added.
When the simulator runs, it prints the parameter values for each .MODEL statement unless the NOMOD option is used in the .OPTIONS statement. Normally only parameters which have not been defaulted are listed. A parameter can be forced to be listed, whether or not it has been defaulted, by preceding its name using an asterisk (*). For example, VTO is listed that way in M.H.
Giving a parameter an alias
Sometimes a parameter requires an alternate name (an alias). Several bipolar model parameters, such as ISE, already have alternate names. The alias for ISE is C2. Look in Q.H at the occurrences of the parameters ISE and C2 in the ASSOCIATE macros for an example of how this is accomplished. There is only one entry in the model class (Q_ise) for the parameter, but there are two ASSOCIATE entries. This means that either name (ISE or C2) on the .MODEL statement can put a number into the class entry Q_ise.
Insert the new name first if it is the name to be printed.
Adding a parameter
Adding a parameter is probably the most common case. The parameter must be added to both the model class (e.g., class M_Model) and the corresponding ASSOCIATE list. It is recommended to follow the PSpice naming convention (e.g., M_wd and M_vto), but it is not required.
Model parameters are set forth as pairs of elements instead of simple floating point values. This is to provide the use of expressions for model parameters. Because of this, when adding a parameter (for example, M_new), the following line is required:
The read-in mechanism can handle expressions for user-added parameters. By the time the model code is called, the expressions have been evaluated and their value placed in the appropriate fields. See the include file m.h for further examples and comments.
When the simulator is doing a read-in, model parameters are listed for each .MODEL statement (unless NOMOD has been specified on the .OPTIONS statement). Normally, only those parameters that have not been defaulted are listed. A parameter can be forced to be listed, even if it has been defaulted, by preceding its name using an asterisk (*) in the ASSOCIATE macro. For instance, VTO in M.H is listed in that manner.
The default value, OMITTED, is used by the simulator to force the calculation of a parameter’s value during read-in. For instance, VTO is calculated from other values if it is not given a value. These calculations are built into the read-in and are fixed. Cadence recommends that parameters that you add be given a normal default value and not be computed by using OMITTED.
Once the parameter has been added, the model class becomes one parameter longer, and the read-in section of PSpice places a value in its entry. The parameter can now be used in the device code (e.g., MOS.CPP).
Changing the device equations
The device equations are in the file that has the same name as the type of device (DIODE.CPP, BJT.CPP, JFET.CPP, MOS.CPP, GASFET.CPP). The files D.CPP, Q.CPP, J.CPP, M.CPP, and B.CPP contain auxiliary functions that implement the AC equations, matrix setup, temperature updating, etc.The code in these subroutines use the model parameters and the device’s terminal voltages to calculate the branch currents and conductances, and, during transient analysis, the terminal charges and branch capacitances. These equations are neither simple nor easy. A good understanding of U.C. Berkeley’s SPICE2G is recommended before making such a change. Two useful references are:
- Nagel, L.W., “SPICE2: A Computer Program to Simulate Semiconductor Circuits”, Memorandum No. M520, May 1975.
-
Cohen, Ellis, “Program Reference for SPICE2”, Memorandum No. M592, June 1976.
which are available from:
Software Distribution Office
EECS/ERL Industrial Liaison Program
205 Cory Hall #1770
University of California
Berkeley, CA 94720-1770
(510) 643-6687
For more details about device source files, see Functional subsections of the device source file.
Functional subsections of the device source file
The code in each of the device source files is arranged into separate functional subsections. Each subsection occurs at least once, but can occur several times for devices that have more than one level. The subsections required are outlined below.
SPICE2G is written in FORTRAN, whereas PSpice is in C/C++. For the device subroutines, as much correspondence as possible has been maintained between the two. Because of FORTRAN, SPICE kept integer and real numbers in different tables: NODPLC (indexed by LOC) and VALUE (indexed by LOCV or LOCM). In PSpice, these have been combined into one object (e.g., class M_Device).
The state vector information is constructed somewhat differently, though the overall pattern is similar. In SPICE the state vector information is kept in a set of vectors in VALUE. There is one vector for each time point “remembered” (from 4 to 7, depending on the order of the integration method). Each device’s LOC table contains an offset, LX, to its portion of the information in each state vector. In PSpice the number of state vectors is fixed, and each device’s state information is kept in its own device object (e.g., class M_Device).
For example, for MOSFETs the state vectors are an array, struct msv_def m_sv[MSTVCT] in class M_Device. MSTVCT is the number of state vectors and is defined in TRAN.H to be equal to 4. The definition of msv_def (also in M.H) lists the various currents, conductances, charges, and capacitances that are in the state vector. Finally, M.H contains a set of #defines, which allows accessing of the entries to the state vectors by name. It is these (uppercase) names which are then used in MOS.CPP. This may seem like a roundabout way of constructing the state vector information, but the actual usage (in MOS.CPP) is quite straightforward and is similar to that in SPICE.
Adding a new device
The Device Equations option does not allow the addition of an entirely new device. However, in many cases the same thing can be achieved by making use of an existing device.
Suppose, for example, that a lightning arrester device is to be added. The lightning arrester has two terminals, therefore it can be built into the diode equations, because the diode also has two terminals. This means that in the circuit (.CIR) file the lightning arresters would use the letter D to start and would refer to a .MODEL statement of the type D.
At first glance it appears that this would preclude using diodes in circuits, since they have been replaced by lightning arresters. This problem is avoided by keeping all the diode model parameters, adding the lightning arrester parameters, adding a LEVEL parameter, and giving the LEVEL parameter a default of 1. In the diode subroutine (in DIODE.CPP), a large if test would select all the old diode code if LEVEL=1 and all the new lightning arrester code otherwise. The new LEVEL parameter would switch between diode and lightning arrester.
This approach can be extended to as many devices as wanted. This could be:
And so on. The restriction is that all of the devices added to the diode must have two terminals. If the device to be added has three terminals, it must be built into a three terminal device, such as the JFET. The highest number of terminals that can be modeled is four, using the MOSFET. There is not a good way to add devices, such as pentodes, that have five or more terminals.
Specifying new internal device structure
You may want to change the topology of a device in order to accommodate a more elaborate set of parasitic resistances and/or capacitances. To do this requires that positions in the conductance matrix be assigned to include the terms that the additional equations generate. This requires five steps:
- Ensuring that all of the new internal nodes and matrix conductance terms are added to the device class in the device header file
- Allocating the new matrix elements
- Providing handles to access the new matrix elements and to bind the nodes to the branches
- Including logic, if needed, to support device model parameter checking and updating
- Adding the new device equations to the device code
Example
This process can be illustrated by looking at the PSpice JFET and GaAsFET devices, as shown in the procedure below. The topologies of these two devices are nearly identical, except that the GaAsFET has an additional internal capacitance, CDS, between the source and drain, and an additional internal resistance, RG, at the gate. This gives the GaAsFET topology one additional node where RG joins the rest of the structure and two additional internal branches.

Procedure
Step one: editing the device header file
These differences are reflected in the device class definitions in J.H and B.H. Each of the device nodes is given a name and declared to be of type CKT_IDX.
The JFET device class, J_Device, lists the two internal nodes j_d and j_s, while the GaAsFET device class, B_Device, has three internal nodes b_d, b_s, and a new one, b_g. The two additional branches in the GaAsFET require three new matrix conductance terms.
The conductance terms are declared type MTX_IDX and are listed immediately following the internal nodes.
The JFET has a term j_GG, which appears on the matrix diagonal for the external gate node.
The GaAsFET has an additional gate node which requires one additional matrix diagonal conductance term, b_gg, along with two off-diagonal conductance terms, b_Gg and b_gG. These are used by the source code in GASFET.CPP to designate where the conductance terms associated with RG go when the matrix is loaded. CDS doesn’t need any additional nodes or matrix terms because the items required are already in place to accommodate the parallel current source, id.
With the nodes and conductance terms taken care of in the device header file, the first step is completed.
Step two: setting up memory allocation for the new matrix elements
You can set up memory allocation to properly incorporate the new equations into the conductance matrix by modifying J.CPP. In this file is the function J_Device::MatPtr(), while B.CPP contains B_Device::MatPtr(). These functions call the function Reserve() once for each conductance matrix term that was declared in the header file. For instance, when b_gg, b_Gg, and b_gG are added for the GaAsFET, these require corresponding code in B_Device::MatPtr() as follows:
The arguments ng and nG are local variables that serve as aliases for the respective device nodes, b_g and b_G, and are assigned at the beginning of B_Device::MatPtr() as follows:
Step three: binding the nodes and branches
The mechanics of step three, binding the nodes and branches, are very similar to the mechanics of step two. The functions of interest are J_Device::MatLoc() and B_Device::MatLoc(), and they now call Indxcl() instead of Reserve(). The GaAsFET again has three more lines of code:
flag &= Indxcl (&(bloc->b_gg),ng,ng);
flag &= Indxcl (&(bloc->b_Gg),nG,ng);
flag &= Indxcl (&(bloc->b_gG),ng,nG);
Step four: handling model parameters
Step four, handling model parameters, is basically the same as it would be for a case not involving topology changes, with one significant exception: this requires handling the case where the parasitics associated with an internal node can be zero. In this case the node must be generated conditionally. An instance of this is the GaAsFET internal resistance RG. If RG is zero, the parasitic resistance between the internal node b_g and the external node b_G can be removed from the circuit. This is accomplished in the function B_Device::AddInternalNodes() in B.CPP, using the following line of code:
INTERNAL_NODE(P->B_rg,b_g,b_G);
INTERNAL_NODE() is a macro that performs the required logic, depending on whether the model parameter B_rg is zero or not. The other two calls to this macro in B_Device::AddInternalNodes() correspond to the RD and RS resistances that also exist for the JFET.
Step five: implementing the new device equations
The final step does not involve any further topological considerations and is carried out just as it would be if the device internal topology weren’t being changed.
Recompiling and linking the device equations option
The source files needed to create the Device Equations DLL can be copied from the CD to any directory you choose, though it is recommended that you create a new empty directory. The MSVC++ project files, DEVEQ.DSP and DEVEQ.MAK, are included to compile and link the DLL.
For information on obtaining the Microsoft compiler, contact Microsoft Corporation directly.
To create a new deveq.dll:
- Load DEVEQ.DSP into the Visual C++ development environment.
- From the Build menu, select Build Deveq.dll.
- The project supports debug and release versions of the build target.
- After DEVEQ.DLL is built, copy it to the directory that contains PSpice.EXE.
For details on how to personalize your DEVEQ.DLL file, click Personalizing your DLL.
Personalizing your DLL
The function DLLMain() in DEVEQDLL.CPP contains the following line of code:
DEVEQVERSIONINFO(“Device Equations“,VERSIONNUM);
To personalize your DLL, change the first argument to a string which identifies you as the author of the DLL, as in:
DEVEQVERSIONINFO(“(c)Copyright 1998\nMyCorp\n123 MyAddress\nMyCity, ST 12345” ,”9.0.1”);
You can leave the VERSIONNUM argument alone, in which case it will match the version number of your PSpice release, or you can substitute your own version numbers. It is useful to be able to relate the DLL to the PSpice release it was built from, so you should use VERSIONNUM unless there is a compelling need to change it.
Simulating with the device equations option
After you obtain a working Device Equations DLL, place it in the directory that contains PSpice.EXE.
PSpice will locate and load DEVEQ.DLL when you start the program, provided the .INI file entry is specified correctly. For instructions on modifying the .INI file, click Selecting which models to use from a Device Equations DLL.
The code in the DLL will be substituted for the device model code that ships with the plain version of PSpice. The presence of the DLL is also noted in the Devices tab of the PSpice Simulation Status Window and in the .OUT file.
If PSpice doesn’t find the DLL, it runs as the normally configured PSpice.
Selecting which models to use from a Device Equations DLL
You can tell PSpice which device models to use from a custom DLL by adding an entry to the pspice.ini configuration file; for any device type you do not specify, PSpice uses the normally configured PSpice models.
To specify which models to use from a custom DLL:
- In a standard text editor (such as Notepad), open pspice.ini, located in your Windows directory.
-
Find the [PSpice ] section and add this line to the section:
USE_DEVEQ_MODELS="<device letters>"
where <device letters> is any or all of the following:
For example, to use all of the possible device models from your custom DLL, type the following:
USE_DEVEQ_MODELS="BDJMQ"
- Save pspice.ini.
- Start PSpice and run a simulation.
Return to top