10.4.4. Using the performance evaluator
This demo shows how to use the pyopus.evaluator.performance.PerformanceEvaluator
class for
generating the simulator jobs and evaluating the collected simulation results.
Read chapter SPICE OPUS simulator interface to learn about the sample
circuit and the device model files. The example uses the SPICE OPUS circuit
simulator.
The circuit description is split in two files. The demo/evaluation/02-evaluator/opamp.inc
file contains the definition of the Miller opamp subcircuit.
* Simple opamp, 0.18u, 1.8V, SpiceOpus
* Any include or lib that is set from pyopus (specified in problem definition)
* must not be specified here.
* Include files - fixed
.include 'mosmm.inc'
* Any parameter that is set from PyOPUS (specified in problem definition)
* must not be specified here.
* Operating conditions - fixed
.param ibias=1.000000000000e-004
* Design parameters - fixed
.param out_w=4.800592541419e-005
.param out_l=3.750131780858e-007
.param load_w=3.486243671853e-005
.param load_l=2.572996921261e-006
.param dif_w=7.728734451428e-006
.param dif_l=1.082371380389e-006
.param c_out=8.211596855053e-012
.param r_out=1.968986740568e+001
* inp inn out vdd vss bias slp slpx
.subckt amp 3 4 5 1 2 7 11 12
xmp1 9 10 1 1 submodp w={load_w} l={load_l} m=2
xmp2 10 10 1 1 submodp w={load_w} l={load_l} m=2
xmp1s 9 12 1 1 submodp w=1u l=0.5u
xmp3 5 9 1 1 submodp w={out_w} l={out_l} m=16
xmn2 9 3 8 2 submodn w={dif_w} l={dif_l}
xmn3 10 4 8 2 submodn w={dif_w} l={dif_l}
xmn1s 7 11 2 2 submodn w=1u l=0.5u
xmn1b 7 7 2 2 submodn w={mirr_w} l={mirr_l} m=2
xmn1 8 7 2 2 submodn w={mirr_w} l={mirr_l} m=2
xmn4 5 7 2 2 submodn w={mirr_w} l={mirr_l} m=16
cout 5a 9 c={c_out}
rout 5 5a r={r_out}
.ends
File demo/evaluation/02-evaluator/topdc.inc
contains the testbench circuit
surrounding the opamp. This circuit is used for evaluating the opamp’s
performance.
* Test topology
x1 (inp inn out vdd 0 bias 0 vdd) amp
vdd (vdd 0) dc={vdd}
ibias (vdd bias) dc={ibias}
rfb (out inn) r={rfb}
rin (in inn) r={rin}
vcom (inp 0) dc={vdd/2}
vin (in inp) dc=0 acmag=1 pulse=({lev1} {lev2} {tstart} {tr} {tf} {pw})
rload (out 0) r=100k
cload (out 0) c=0.5p
File: demo/evaluation/02-evaluator/runme.py
# Test performance evaluator
from pyopus.evaluator.performance import PerformanceEvaluator
from pyopus.evaluator.aggregate import formatParameters
if __name__=='__main__':
# Here we define the simulators that are used (in our case just SpiceOpus),
# circuit dscription fragments (or so-called modules), simulator settings
# passed to the simulator interface class at construction, simulator
# options valid for all analyses, and parameters that will be passed to
# the circuit description.
heads = {
'opus': {
'simulator': 'SpiceOpus',
'settings': {
'debug': 0
},
'moddefs': {
'def': { 'file': 'opamp.inc' },
'tb': { 'file': 'topdc.inc' },
'mos_tm': { 'file': 'cmos180n.lib', 'section': 'tm' },
'mos_wp': { 'file': 'cmos180n.lib', 'section': 'wp' },
'mos_ws': { 'file': 'cmos180n.lib', 'section': 'ws' },
'mos_wo': { 'file': 'cmos180n.lib', 'section': 'wo' },
'mos_wz': { 'file': 'cmos180n.lib', 'section': 'wz' }
},
'options': {
'method': 'trap'
},
'params': {
'lev1': 0.0,
'lev2': 0.5,
'tstart': 1e-9,
'tr': 1e-9,
'tf': 1e-9,
'pw': 500e-9
}
}
}
# This is a dictionary of Python variables that can be accessed in the
# expressions as var['name']
variables={
'saveInst': [ 'xmn2', 'xmn3', 'xmn1' ],
'saveProp': [ 'vgs', 'vth', 'vds', 'vdsat' ]
}
# List of circuit analyses
# For every analysis we specify the head (simulator to use),
# modules that constitute the circuit description,
# simulator options (that can override the options in the
# heads structure), parameters passed to the circuit description
# (override those specified in the heads structure), the list
# of extra quantities to save during the simulation, and the
# command that invokes the simulation.
analyses = {
'op': {
'head': 'opus',
'modules': [ 'def', 'tb' ],
'options': {
'method': 'gear'
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
# Save current through vdd. Save voltages at inp, inn, and out.
# Save vgs, vth, vds, and vdsat for mn2, mn3, and mn1.
'saves': [
"i(['vdd'])",
"v(['inp', 'inn', 'out'])",
"p(ipath(saveInst, 'x1', 'm0'), saveProp)"
],
'command': "op()"
},
'dc': {
'head': 'opus',
'modules': [ 'def', 'tb' ],
'options': {
'method': 'gear'
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
'saves': [ ],
'command': "dc(-2.0, 2.0, 'lin', 100, 'vin', 'dc')"
},
'blank': {
'head': 'opus',
'params': {
'rin': 1e6,
'rfb': 1e6
},
'command': None
}
}
# Here we define the corner points where the performances will be evaluated.
# Every corner point can specify its own modules that are added to the modules
# listed for an analysis. Parameters Specified here override those specified in
# the heads structure, but get overridden by the parameters specified in the
# analysis structure.
corners = {
# Valid for all heads
'nominal': {
'modules': [ 'mos_tm' ],
'params': {
'temperature': 25,
'vdd': 1.8,
}
},
# Valid for head 'opus'
('worst_power', 'opus'): {
'modules': [ 'mos_wp' ],
'params': {
'temperature': 100,
'vdd': 2.0,
}
},
'worst_speed': {
'modules': [ 'mos_ws' ],
'params': {
'temperature': 100,
'vdd': 1.8,
}
},
'worst_zero': {
'modules': [ 'mos_wz' ],
'params': {
'temperature': 100,
'vdd': 1.8,
}
},
'worst_one': {
'modules': [ 'mos_wo' ],
'params': {
'temperature': 100,
'vdd': 1.8,
}
}
}
# Finally, we define what we want to measure. For every performance
# we specify the analysis that generates the simulation results
# from which the performance is extracted. We can specify the formula
# for extracting the result as either a Python expression or a Python
# script that stores the result in the __result variable.
# For every performance we also specify the list of corners for which
# the performance will be evaluated. If no corners are apecified the
# performance is evaluated across all listed corners.
# Performances are scalars by default. For vector performances this
# must be specified explicitly.
measures = {
# Supply current
'isup': {
'analysis': 'op',
'corners': [ 'nominal', 'worst_power', 'worst_speed' ],
'expression': "__result=-i('vdd')"
},
# Output voltage at zero input voltage
'out_op': {
'analysis': 'op',
'corners': [ 'nominal', 'worst_power', 'worst_speed' ],
'expression': "v('out')"
},
# Vgs overdrive (Vgs-Vth) for mn2, mn3, and mn1.
'vgs_drv': {
'analysis': 'op',
'corners': [ 'nominal', 'worst_power', 'worst_speed', 'worst_one', 'worst_zero' ],
'expression': "array(list(map(m.Poverdrive(p, 'vgs', p, 'vth'), ipath(saveInst, 'x1', 'm0'))))",
'vector': True
},
# Vds overdrive (Vds-Vdsat) for mn2, mn3, and mn1.
'vds_drv': {
'analysis': 'op',
'corners': [ 'nominal', 'worst_power', 'worst_speed', 'worst_one', 'worst_zero' ],
'expression': "array(list(map(m.Poverdrive(p, 'vds', p, 'vdsat'), ipath(saveInst, 'x1', 'm0'))))",
'vector': True
},
# DC swing where differential gain is above 50% of maximal gain
'swing': {
'analysis': 'dc',
'corners': [ 'nominal', 'worst_power', 'worst_speed', 'worst_one', 'worst_zero' ],
'expression': "swing=m.DCswingAtGain(v('out'), v('inp', 'inn'), 0.5, 'out')"
},
# Surface area occupied by the current mirrors
'mirr_area': {
'analysis': 'blank',
'corners': [ 'nominal' ],
'expression': (
"param['mirr_w']*param['mirr_l']*(2+2+16)"
)
}
}
# Order in which the performance mesures are printed.
outOrder = [
'mirr_area', 'isup', 'out_op',
'vgs_drv', 'vds_drv',
'swing',
]
# Input parameters
inParams={
'mirr_w': 7.46e-005,
'mirr_l': 5.63e-007
}
# Order in which input parameters are printed.
inOrder=[ 'mirr_w', 'mirr_l' ]
# Construct an evaluator. Turn on debugging so we get a printout
# on what is happening.
pe=PerformanceEvaluator(heads, analyses, measures, corners, variables=variables, debug=2)
# Evaluate the circuit for parameter values specified in inParams.
# These values have the lowest priority and are overridden by the
# parameters specified in the heads, corners, and analyses structures.
(results, anCount)=pe(inParams)
# Print the parameters and the results.
print("")
print(formatParameters(inParams, inOrder))
print(pe.formatResults(outOrder, nMeasureName=10, nCornerName=15))
# Print analysis count
print("Analysis count: "+str(anCount))
# Note that you can also pick out the results manually from
# the results structure. To get the isup value in the nominal
# corner, one would write
print("Isup in nominal corner: %e" % results['isup']['nominal'])
pe.finalize()
This is the output we get (excluding debug output)
mirr_w: 7.460000e-05
mirr_l: 5.630000e-07
mirr_area | nominal: 8.400e-10
isup | nominal: 1.078e-03
| worst_power: 1.111e-03
| worst_speed: 1.068e-03
out_op | nominal: 9.035e-01
| worst_power: 1.004e+00
| worst_speed: 9.041e-01
vgs_drv | nominal: [ 0.20979231 0.21155576 -0.01015429]
| worst_power: [ 0.25183658 0.25389873 -0.00648101]
| worst_speed: [ 0.26954062 0.2716014 0.00189362]
| worst_one: [ 0.25043159 0.25257277 -0.00648411]
| worst_zero: [ 0.26837357 0.27032976 0.00189365]
vds_drv | nominal: [ 0.81923904 0.70281508 0.12600147]
| worst_power: [ 0.93198506 0.80043261 0.25860023]
| worst_speed: [ 0.7543191 0.61157677 0.10224135]
| worst_one: [ 0.68953021 0.5430789 0.1819348 ]
| worst_zero: [ 0.87452691 0.74942676 0.10313849]
swing | nominal: 1.486e+00
| worst_power: 1.661e+00
| worst_speed: 1.450e+00
| worst_one: 1.454e+00
| worst_zero: 1.463e+00
Analysis count: {'dc': 5, 'op': 5}
Isup in nominal corner: 1.078055e-03
First the parameter values are printed followed by the performance measure values. The values are printed for all corners where a performance measure was evaluated. Finally the analyssi count is printed along with the manually picked performance (Isup in nominal corner).
Note that debug output can be turned off by setting the debug parameter to 0.