Universal Opamp SPICE Macromodels

I’ve been intending to do this for a while now, but haven’t really gotten the chance to figure it out until some recent toilet reading of this PDF: https://www.analog.com/media/en/technical-documentation/application-notes/AN-138.pdf

A while back, I posted a super simple opamp subcircuit which is purely linear and doesn’t take the voltage rails into account. I use this model frequently in my own work since it’s fast and gets you decent results as long as you are aware of the limitations. However, there are times where I need to consider saturation due to the voltage rails, mainly in comparator applications. I typically use LTspice and it has a handy built-in device called the “UniversalOpamp2” which has 4 different levels of models to pick from using a drop-down menu. The first two levels are the most practical, and I constantly use them. Level 1 is the same as the model I described above without voltage rail consideration. Level 2 adds quite a bit of functionality since it includes the voltage rails in the netlist.

Anyway, to get back on topic here, I wanted to create something similar to Level 2 which can be used in ngspice so people can use it in KiCad. Not only is this type of behavioral model faster and has less convergence issues than semiconductor-based models, it can also be used when no model is available from the opamp manufacturer. You just need to plug in a few datasheet values into the subcircuit parameters to get a rough approximation to the opamp you want to model.

I attached a library file of my own “Universal Opamp” models, which include both Level 1 and Level 2. The Level 2 is loosely based on the app note PDF I linked above. I removed all semiconductors and replaced all the diodes with voltage-controlled switches [EDIT (2019-12-11): v1.1 reverted back to semiconductors due to convergence issues]. I’ve tested both models in LTspice and KiCad 5.1.4 (for Windows). I structured the text such that it’s easier to read the comments and parameter descriptions when selecting the subcircuit model within Eeschema. The only parameter that’s missing between my version and the LTspice version is slew-rate, which I’m still not certain the best way to implement while retaining fast simulation speed. Maybe I’ll update it later along with a few other improvements, but I’m releasing this as is for now.

Below are a few screenshots of the models in action, particularly showing the GBW and Vrail parameters working. These models should work with any KiCad opamp symbol, provided the “Alternate node sequence” is properly mapped as defined in the library comments. Overriding the parameters from the defaults requires you to type them in after the model name. You can do this when selecting the model in the Spice Model Editor, or you can make the “Spice_Model” field visible on the schematic and edit the text like any other text. Although, if you use a lot of parameters with the text visible it could cause a bunch of clutter on the schematic.

EDIT (2019-12-11):
uopamp_v1.1.lib (3.6 KB)


Anyone interested in building op-amp macro-models should also see Mike Brinson’s tutorial on the subject which discusses the process in detail, it is QUCS tutorial but just as applicable to SPICE like simulator



1 Like

I agree. This is a very well-written paper.

I haven’t seen Mike Brinson’s paper before this but it looks quite comprehensive. (Of course, I haven’t paid much attention to opamp modeling for he last dozen years or so.)

I favored Mark Alexander’s Ap Note for Analog Devices (linked by @Ste), and once upon a time started a VB program that would create a macromodel listing using equations from that Ap Note and data sheet parameters. In a parallel effort Bonnie Baker did some good Ap Notes when she was at Burr-Brown in the early 1990’s. See HERE and HERE.

The annoying thing is the large number of manufacturer-supplied macromodels which continue to use only the basic Boyle/Cohn model, ignoring the improvements developed over decades.


1 Like

Any documentation using LaTeX usually gets an upvote from me. That platform is underutilized, especially for math heavy applications which would benefit greatly from it.

I didn’t use any concepts specifically from that paper because most of the structures he used reference GND within the models themselves. I tried to keep my entire Level 2 model floating, much like the Alexander/Bowers model, but while avoiding all semiconductors for faster Newton iterations. Eventually, I’ll add slew rate stuff, but I wanted to try some tricks first that might allow me to avoid adding an additional gain stage for that. I don’t have time to focus in on that at the moment. This model wasn’t slapped together quickly or directly copy/pasted from app notes. I spent a couple weeks trying to sort it all out. I imagine it would take another week or so of non-distracted time to make more improvements…but at least this is a starting point.

Dear Ste,

I use your model in one of my designs, and it works great. In an other design I get this error:

doAnalyses: TRAN: Timestep too small; time = 0.000337735, timestep = 1.25e-18: trouble with xu101:ideal-instance s.xu101.s4
run simulation(s) aborted

Shall I post the whole schematic?

Sure, I’d like to see what’s going on, and to make sure I didn’t goof something up. Can you also post the SPICE netlist?

Okay. Here is the netlist:

.title KiCad schematic
.include "/home/lev/git/library/electronic/model/BJT/BCP53.LIB"
.include "/home/lev/git/library/electronic/model/BJT/BCP56.LIB"
.include "/home/lev/git/library/electronic/model/OPA/uopamp.lib"
.include "/home/lev/git/library/electronic/model/diode/SBR10U40CT.LIB"
.include "/home/lev/git/library/electronic/model/mosfet/irfr1205.LIB"
R109 /GATE Net-_Q102-Pad3_ 10
XQ102 Net-_D101-Pad2_ /VDRV Net-_Q102-Pad3_ BCP56
XQ103 0 /VDRV Net-_Q102-Pad3_ BCP53
XU101 /OPA_P Net-_C101-Pad1_ Net-_D101-Pad2_ Net-_U101-Pad4_ /OPA_OUT uopamp_lvl2
R104 /VDRV 0 15k
R103 /VDRV /OPA_OUT 1.5k
V101 Net-_R101-Pad2_ 0 dc 5
V102 Net-_D101-Pad2_ 0 DC 12
R117 /OPA_OUT Net-_C101-Pad1_ 10meg
C104 /OUT 0 1000u IC=12
XQ101 /DRAIN /GATE 0 irfru1205
L101 Net-_D101-Pad1_ /OUT 22u
D101 Net-_D101-Pad1_ Net-_D101-Pad2_ DI_SBR10U40CT
R102 Net-_D101-Pad2_ /OUT 12
R105 Net-_D101-Pad1_ /DRAIN 0
R101 /OPA_P Net-_R101-Pad2_ 10
XU102 Net-_R106-Pad2_ Net-_C101-Pad1_ Net-_D101-Pad2_ 0 Net-_R108-Pad1_ uopamp_lvl2
R108 Net-_R108-Pad1_ Net-_R106-Pad2_ 47k
R107 Net-_R106-Pad2_ 0 100k
R106 Net-_D101-Pad2_ Net-_R106-Pad2_ 100k
R110 Net-_R108-Pad1_ Net-_C101-Pad1_ 1k
C101 Net-_C101-Pad1_ 0 10n IC=0
V103 Net-_U101-Pad4_ 0 dc -5
R111 /GATE 0 47
.save @r109[i]
.save @r104[i]
.save @r103[i]
.save @v101[i]
.save @v102[i]
.save @r117[i]
.save @c104[i]
.save @l101[i]
.save @d101[id]
.save @r102[i]
.save @r105[i]
.save @r101[i]
.save @r108[i]
.save @r107[i]
.save @r106[i]
.save @r110[i]
.save @c101[i]
.save @v103[i]
.save @r111[i]
.save V(/DRAIN)
.save V(/GATE)
.save V(/OPA_OUT)
.save V(/OPA_P)
.save V(/OUT)
.save V(/VDRV)
.save V(Net-_C101-Pad1_)
.save V(Net-_D101-Pad1_)
.save V(Net-_D101-Pad2_)
.save V(Net-_Q102-Pad3_)
.save V(Net-_R101-Pad2_)
.save V(Net-_R106-Pad2_)
.save V(Net-_R108-Pad1_)
.save V(Net-_U101-Pad4_)
.tran 1u 10m
.options TEMP=25
.options TNOM=25
.options GMIN=1p

And you can find the schematic and other models here:


Thanks for your help!

I have replaced the SW switch model from uopamp_v1.0.lib

.model ideal SW(Ron=1u Roff=1g Vt=0 Vh=1m)

by a VSWITCH model with a very small hysteresis

.MODEL ideal VSWITCH(VON=-1u VOFF=1u RON=1u ROFF=1g)

Then I get as simulation result a constant /opa_out = 3.5 V.

Switches are often not a good choice for transient simulation because they produce steps, and the derivative of their response is not continuous. The VSWITCH model (you need PSPICE compatibility enabled by placing
set ngbehavior=ps
into a .spiceinit file in your home directory) has smoothed out corners.

Btw: I could not run your simulation.sch. All models had user specific paths and would need editing. The symbols for the opamps were missing. I don’t have the l_opa lib file.

Thanks. I uploaded the OPA library to the same folder. I believe the switch is used to replace diodes.

Ah, I knew in the back of my head that might cause an issue like that. Especially since Mike E. used to always talk about how the old SPICE versions had diode breakdown voltage discontinuities and/or non-differentiable corners. Whups.

I typically use a similar function in LTspice, where if you set Vh to a negative value it fits the discontinuity to a polynomial. But I didn’t see such a function in the ngspice manual so I just took Vh out completely. There’s also a “level 2” of which is slightly more complicated:

The switch has three distinct modes of voltage control, depending on the value of the hysteresis voltage, Vh. If Vh is zero, the switch is always completely on or off depending upon whether the input voltage is above the threshold. If Vh is positive, the switch shows hysteresis, as if it was controlled by a Schmitt trigger with trip points at Vt - Vh and Vt + Vh. Note that Vh is half the voltage between trip points which is different than the common laboratory nomenclature. If Vh is negative, the switch will smoothly transition between the on and off impedances. The transition occurs between the control voltages of Vt - Vh and Vt + Vh. The smooth transition follows a low order polynomial fit to the logarithm of the switch's conduction. There is also a level 2 voltage-controlled switch which is an advanced version of the level 1 switch with negative hysteresis. The level 2 switch is never completely on or off. The conduction as a function of control voltage Vc is

g(Vc) = exp(A * atan((Vc - Vt)/abs(Vh)) + B)

And here is a snip from the PSPICE manual showing the math they use:

Anyway, I can’t get the VSWITCH to work properly on my end. I think the VON and VOFF in your VSWITCH model card might have the negative sign swapped???. I was only able to get a correct simulation within LTspice after making the swap, but it gets wonky if I use a circuit that saturates. I then ran it in KiCad and the output of a simple non-inverting amplifier circuit will latch up into saturation when over-driven and not come out.

If you revert the sign back to how it was, then the circuit just doesn’t operate at all.

Running it on @lev’s circuit makes my Eeschema completely crash. If I make one of VON or VOFF zero then it doesn’t crash but also doesn’t converge. His U102 is an oscillator circuit, so it makes sense why convergence issues are cropping up here. Also @lev, is U101 inverting input supposed to be connected to U102 inverting input? Seems like a mistake to me and that U101 inverting input should be connected to U102’s output. Or am I misunderstanding the circuit? I’m reattaching the entire project folder so we’re all on the same page here. I also included an LM7321.lib to show that the circuit simulates correctly when using that model from TI.
ngspice_lev.zip (19.6 KB)

But going forward, @holger is it your opinion that it might be better to just replace the fake diodes with default-valued diode (D) models, even if convergence speed far away from the switch point would suffer? Or perhaps a custom behavioral source to do the smooth switching which doesn’t rely on PSPICE compatibility (assuming we get the VSWITCH to actually work)? Using default diode models makes it slightly harder to tune the rail saturation voltages, but I can still try to get something close.

The best solution may be to use the simple diode (manual chapt. 12.2.29 SimpleDiodeModel ). This model consists of three regions (on, off, breakdown). Each region is modelled by a resistor. The region boundaries are set by Vfwd and Vrev and are smoothed out by a quadratic smoothing function with range ± ?epsilon. In addition you may set a (smooth) current limit. This model is also available in LTSPICE.

I’ve got it. Thanks!

The connection between the inverting input of U102 and the inverting input of U101 is intentional. The oscillator is a triangle waveform generator. This is a PWM circuit realized by OPAs. The input is the V101 generator.

Ahhhh…I know about that model in LTspice but I didn’t realize it was also in ngspice. I didn’t think to look in chapter 12. Thanks, man!

EDIT: Ah, dang. I can’t get the syntax for this diode model to be compatible between LTspice and ngspice. Do you have any ideas to make this possible without having to add the set ngbehavior=lta into the spice.rc?

Hey, @lev. Last night, I whipped up a test opamp subcircuit using semiconductor diodes. Can you give this LIB file a shot and let me know if it works for you? Thanks!
uopamp_lvl2_test.lib (1.7 KB)

What is the syntax difference (if you have enabled LTSPICE compatibility)?

This is coded in ngspice (to not introduce compatibility problems with standard diodes). The model is then selected by looking at some of the parameters that are different in the 2 diode invocations. If this works well, the simple diode could be made available in the next release without need for a compatibility switch.

The syntax works fine as long as the compatibility switch is set properly. However, I did notice a couple things. First, that according to ngspice manual Chapter 12 the default values for Roff and Rrev don’t match LTspice’s defaults:
Second is that the simulation results using this model for the lvl2 opamp are slightly different than when I run it in LTspice, even when specifying all parameters explicitly. If I saturate the opamp, the voltage difference to the rails is an extra 50mV when running in KiCad vs LTspice. Not a big difference, but I thought I’d mention it. Lastly, I’m curious to know if ngbehavior=lta implies ngbehavior=ps. Or is there a way to set both?

That’s how LTspice makes the distinction: This idealized model is used if any of Ron, Roff, Vfwd, Vrev or Rrev is specified in the model.

I don’t want you adding in features like this for no reason. I can do some speed testing between semiconductor diodes and fake diodes to see if it’s worth it, first. It’s possible that the time difference to convergence is negligible.

You may use ltps or ltpsa

@Ste, thanks for the update! It does work! However, I get strange voltage drop in the oscillator.

But I can live with it. This simulation was done to show that the follower MOSFET driver is alive.

Thanks again!