ECE 3410, Utah State University
Complex circuits use hierarchy to organize the netlist into modules or subcircuits. This is useful both for design clarity and for repeating multiple copies of the same circuit.
To declare a subcircuit, SPICE uses this syntax:
.subckt <subckt type name> <list of I/O ports>
* devices, nodes, and connections with local scope
.ends
All declared devices and connections have local scope except for the
ground node 0. In NGSpice, the node gnd
also has global
scope and can be referenced anywhere.
After declaring a subcircuit, it can be placed in a netlist by using
the X
device type:
This simple example implements a resistive voltage divider:
Notice that within the subcircuit the ports a
and
b
are treated like node names. This circuit should work as
a voltage divider with v(b)=v(a)*R1/(R1+R2) = v(a)/3
.
Save these lines in a file called
netlists/voltageDivider.sp
Now make a new file called netlists/circuit1.sp
with
these lines:
* circuit1: instantiate and simulate a voltage divider
.include netlists/voltageDivider.sp
V1 n1 0 DC 1V
X1 n1 n2 voltageDivider
.control
op
print all
.endc
.end
Explanation: this circuit connects node n1
to the
voltage divider’s port a
, and node n2
is
connected to the voltage divider’s port b
. At
n1
the voltage is 1V, so at n2
we expect to
see (1/3)V.
Run the simulation using this command:
ngspice netlists/circuit1.sp | tee -a log.txt
Your output should include these lines:
No. of Data Rows : 1
n1 = 1.000000e+00
n2 = 6.666667e-01
v1#branch = -3.33333e-04
The lines report the voltages at nodes n1
and
n2
, and lastly the current sourced through
v1
.
As with other hardware description languages, SPICE allows parametric subcircuits. For example, suppose we want to define a voltage divider with adjustable resistor values. This would be much more useful than a fixed voltage divider.
Parameters and their default settings are defined as follows:
The parameters can be individually defined for each instance like this:
If no parameters are defined on the instance line, then the default values are used.
r1
and r2
to the
voltageDivider.sp
subcircuit.circuit1.sp
to verify that you get the same
result.circuit1.sp
to file circuit2.sp
and
change the parameters so that R1=2k and R2=8k.circuit2.sp
and verify that you see the
expected results:No. of Data Rows : 1
n1 = 1.000000e+00
n2 = 8.000000e-01
v1#branch = -1.00000e-04
Electronic device manufacturers usually provide SPICE models for their products. For complex devices like an op amp, the models are usually given as subcircuits.
An example model for the ua741 op amp is provided in
models/ua741.md
(here the file type md
means
“model” and not “markdown”). Open the file and look it
over. Pay special attention to the header:
*-----------------------------------------------------------------------------
*
* To use a subcircuit, the name must begin with 'X'. For example:
* X1 1 2 3 4 5 uA741
*
* connections: non-inverting input
* | inverting input
* | | positive power supply
* | | | negative power supply
* | | | | output
* | | | | |
.subckt uA741 1 2 3 4 5
*
An inverting weighted summer schematic is shown below, followed by its SPICE netlist. (You may need to scroll down to see the whole netlist). In the next few slides, we will explain each part of the netlist.
* Inverting Weighted Summer netlist
* Use parameters to set resistor values:
* (change these to match your experiment)
.param r1=1k
.param r2=1k
.param rf=1k
* Import the op amp subcircuit:
.include models/ua741.md
* Op amps need power supplies:
VDD ndd 0 DC 15V
VSS nss 0 DC -15V
* Next, the input voltages:
V1 n1 0 DC 1V
V2 n2 0 DC 5V
* Then the resistor network:
R1 n1 nn {r1}
R2 n2 nn {r2}
RF nout nn {rf}
* Finally, place the op amp:
* non-inverting input
* | inverting input
* | | positive power supply
* | | | negative power supply
* | | | | output
* | | | | | model type
* | | | | | |
X1 0 nn ndd nss nout ua741
.param
StatementIn addition to subcircuit parameters, SPICE allows top-level netlist
parameters using the .param
statement:
When we import the op amp subcircuit model, we also need to set power supplies, since they are not built-into the model.
When the resistors are placed, their values are defined by the
.param
statements. We reference those parameters as
follows:
The op amp itself is placed using the subcircuit syntax:
Make a file named netlists/summer.sp
and enter the
netlist explained in the preceding slides.
Make a file named tests/summer_op.sp
and perform an
operating point test:
* Weighted Summer operating point test
.include netlists/summer.sp
.control
op
print n1 n2 nout
.endc
Ve3rify that the results match these lines:
No. of Data Rows : 1
n1 = 1.000000e+00
n2 = 5.000000e+00
nout = -5.99976e+00
In summer.sp
, change the .param
statements
to match the resistor values you calculated in Pre-lab Exercise 5. Then
create a testbench named tests/exercise5.sp
to perform
these actions in your .control
script, in this order:
n1
,
n1
, and nout
.V2
to zero volts using the alter
command, like this:V1
from 0 to 5 volts in steps of 1V.print n1 n2 nout
deriv
function. Since we are
sweeping V1
, the function deriv(v(nout))
will
calculate the derivative of Vout
with respect to
V1
:This derivative is designed to be -1V/V, so the calculations should be close to that.
v(nout)
and the error, {v(nout)
- (-v(n1)-2*v(n2))}
. Save the plots as
plots/dc_output.svg
and
plots/dc_error.svg
.Note that these are the same procedures you will be performing in the physical experiment, Procedures 1(A), 1(B), and 1(C).
When you conduct the physical experiment, the simulation should give you an idea of whether your experiment is setup properly. The physical results should roughly correspond to what you saw in the simulation.
In your lab report, you should compare simulation and measurement results. For example, you should plot measured and simulated results for Vout together on the same plot, and see if they differ in any specific way. You can also compare your simulated and measured derivatives of Vout vs V1 and analyze any significant differences.
Copy the file netlists/summer.sp
to a new file named
netlists/highpass_summer.sp
.
In highpass_summer.sp
change the definition of
V2
to a sinusoid, i.e. the line should look like
this:
Declare parameters VO=0V
, VA=1V
, and
freq=1k
Create an AC testbench named
tests/highpass_summer_ac.sp
Create a transient simulation testbench named
tests/highpass_summer_tran.sp
to perform the experiment
described in Procedure 2(A).
The testbench details are discussed in the following slides.
Since our netlist uses parameters, it would be convenient to use them
in the testbench. NGSpice control
scripts have a different
parameter scope, so they can’t “see” the netlist parameters unless we
explicitly pass them through like this:
.include netlists/highpass_summer.sp
* pass some parameters to control script:
.csparam freq=freq
.csparam VA=VA
Then in the control script, the parameters can be accessed in
expressions. In this example we use the freq
parameter to
set the timescale for transient simulation:
Procedure 2(A) requires testing several different values for V1, and
several different DC offsets for the sinusoid V2. To efficiently
simulate all of those cases, we can place the experimental settings in
vectors and use a foreach
loop to test each case:
In the above code, two vectors are declared: gain
and
outofs
, which will store measurement results for the gain
and output offset, respectively. The unitvec(6)
function
initializes a vector of length 6 containing all 1s. We multiply this
vector by zero to make them all 0s.
Next we use the compose
function to specify the values
for V1 and V2:
foreach
loop for multiple cases * Use a FOREACH LOOP to run each case
foreach idx 0 1 2 3 4 5
* Grab parameters from their vectors:
set VI=v1dc[$idx]
set VO=v2ofs[$idx]
* Change the settings of sinusoidal source V2,
* and DC source V1, and repeat the simulation:
alter V1 DC $VI
alter @V2[sin] = [ $VO $&VA $&freq ]
* Run transient simulation
* (here the "$&" expansion is used so the
* settings are given as strings)
tran $&tstep $&tstop
To measure the gain, we use the PP
(i.e. peak-to-peak)
meas
function to obtain the amplitudes at the output and
input. The gain is the ratio of amplitudes. To measure
the output offset voltage, we use the AVG
(i.e. average)
meas
function. The results are stored in the output vectors
at the current index:
Since we are producing several cases, we will want to title the plots using the voltage settings for each case, and we will want to save each hardcopy to a unique filename to avoid overwriting results.
The foreach
loop is concluded by an end
statement.
The output vectors can be printed in a table using the
print
function as shown below.
The control
script ends with .endc
and the
testbench ends with .end
The table of output data should look something like this:
--------------------------------------------------------------------------------
Index v1dc v2ofs outofs gain
--------------------------------------------------------------------------------
0 0.000000e+00 0.000000e+00 1.463937e-02 1.993725e+00
1 0.000000e+00 1.000000e+00 1.463937e-02 1.993725e+00
2 0.000000e+00 2.000000e+00 1.463937e-02 1.993725e+00
3 1.000000e+00 0.000000e+00 -9.84697e-01 2.000365e+00
4 -1.00000e+00 0.000000e+00 1.013977e+00 1.996517e+00
5 -2.00000e+00 0.000000e+00 2.014064e+00 1.991924e+00
You should be able to use these results to make direct comparisons with your physical experiments.
highpass_summer.sp
change the definition of
V2
to add an AC magnitude of 1, i.e. the line should look
like this:tests/highpass_summer_ac.sp
Perform an AC
simulation with a logarithmic
(i.e. dec
) frequency sweep. Use the mag
function to obtain the gain, and the vdb
function to obtain
the gain in decibels. Then plot the results versus the frequency and
save plots.
Use the meas
command with MAX
and
WHEN
functions to measure the peak gain (in dB), and the
two -3dB cutoff frequencies. Then report the results to the console.
These measurements serve as a prediction for your physical experiment.
* Measure peak gain at mid-band
meas ac max_dB MAX gain_dB
let drop3dB=max_dB-3
* Find lower and upper cutoff frequencies
meas ac f_low WHEN gain_dB=drop3dB CROSS=1
meas ac f_high WHEN gain_dB=drop3dB CROSS=2
let bw=f_high-f_low
echo "Frequency range from $&f_low to $&f_high with total bandwidth $&bw"
netlists/highpass_summer.sp
netlist to a new
file named netlists/two_stage_summer.sp
, then modify the
new file to implement the schematic shown in Fig. 2.3 of the
pre-lab.tests/highpass_summer_ac.sp
file to a new
testbench named tests/two_stage_summer_ac.sp
.Important Note: The low end of the AC sweep should be 1Hz, not 500Hz. The experimental procedure should be revised both for the simulation and the physical experiment.
The preferred way to turn in your work is to use git
.
From the Linux terminal:
git add *
git commit -a -m "Submitting SPICE 2 assignment"
git push origin master
Alternatively you can upload a ZIP file to Canvas containing all your assignment files.
Comment about
set
andlet
There are two kinds of variables in NGSpice:
set
declares and defines a unitless numerical variable$
prefix.idx
loop variable is of this variety.let
declares and defines a vector which may have physical units.$&
prefix to convert value to string.$&
can convert a whole vector or array into a strings.$&
cannot always be used with array indices like[idx]
, so we need to sometimes do multiple steps of conversion.Examples: