10.6.2. Common part of the design problem definition¶
The part of the problem definition that is common to all design algorithms is defined in file definitions.py in folder demo/design/miller/
from pyopus.evaluator.distrib import *
# Problem definition
__all__ = [ 'heads', 'analyses', 'measures', 'variables', 'statParams', 'opParams', 'designParams' ]
heads = {
'opus': {
'simulator': 'SpiceOpus',
'settings': {
'debug': 0
},
'moddefs': {
'def': { 'file': 'miller.inc' },
'tb': { 'file': 'topdc.inc' },
'tbrr': { 'file': 'toprr.inc' },
'tm': { 'file': 'cmos180n.lib', 'section': 'tm' },
'wp': { 'file': 'cmos180n.lib', 'section': 'wp' },
'ws': { 'file': 'cmos180n.lib', 'section': 'ws' },
'wo': { 'file': 'cmos180n.lib', 'section': 'wo' },
'wz': { 'file': 'cmos180n.lib', 'section': 'wz' },
'mc': { 'file': 'cmos180n.lib', 'section': 'mc' },
},
'options': {
'method': 'trap'
},
'params': {
'ibias': 100e-6,
'lev1': -0.5,
'lev2': 0.5,
'tstart': 10000e-9,
'tr': 1e-9,
'tf': 1e-9,
'pw': 10000e-9,
'rload': 100e6,
'cload': 1e-12
}
}
}
variables={
'mosList': [ 'xmn1', 'xmn2', 'xmn3', 'xmn4', 'xmn5', 'xmp1', 'xmp2', 'xmp3' ],
'isNmos': [ 1, 1, 1, 1, 1, 0, 0, 0 ],
}
analyses = {
'op': {
'head': 'opus',
'modules': [ 'def', 'tb' ],
'options': {
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
# Save all standard results (voltages, currents)
# Save vgs, vth, vds, and vdsat for all MOS transistors in mosList
'saves': [
"all()",
"p(ipath(mosList, 'x1', 'm0'), ['vgs', 'vth', 'vds', 'vdsat'])"
],
'command': "op()"
},
'dc': {
'head': 'opus',
'modules': [ 'def', 'tb' ],
'options': {
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
'saves': [ ],
'command': "dc(-2.0, 2.0, 'lin', 100, 'vin1', 'dc')"
},
'ac': {
'head' : 'opus',
'modules': [ 'def', 'tb' ],
'options': {
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
'saves': [ "all()" ],
'command': "ac(1, 1e12, 'dec', 10)"
},
# Rfb and Cin should be large enough. Small values cause a plateau of incorrect
# rejection ratios at very low frequencies. This plateau ends at a frequency which
# is too high.
# The optimizer exploits this by lowering bandwidth and increasing gain.
# The obtained rejection ratios are then incorrect in some cases, i.e. too high.
# Therefore we require the bandwidth to be at least 100Hz and measure gain at 10Hz.
'accom': {
'head' : 'opus',
'modules': [ 'def', 'tbrr' ],
'options': {
},
'params': {
'rfb': 1e9,
'cin': 1,
'accom': 1.0,
'acvdd': 0.0,
'acvss': 0.0
},
'saves': [ "all()" ],
'command': "ac(1, 1e9, 'dec', 10)"
},
'acvdd': {
'head' : 'opus',
'modules': [ 'def', 'tbrr' ],
'options': {
},
'params': {
'rfb': 1e9,
'cin': 1,
'accom': 0.0,
'acvdd': 1.0,
'acvss': 0.0
},
'saves': [ "all()" ],
'command': "ac(1, 1e9, 'dec', 10)"
},
'acvss': {
'head' : 'opus',
'modules': [ 'def', 'tbrr' ],
'options': {
},
'params': {
'rfb': 1e9,
'cin': 1,
'accom': 0.0,
'acvdd': 0.0,
'acvss': 1.0
},
'saves': [ "all()" ],
'command': "ac(1, 1e9, 'dec', 10)"
},
'noise': {
'head' : 'opus',
'modules': [ 'def', 'tb' ],
'options': {
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
'saves': [ "all()" ],
'command': "noise(1, 1e12, 'dec', 10, 'vin1', 'out')"
},
'tran': {
'head' : 'opus',
'modules': [ 'def', 'tb' ],
'options': {
'reltol': 1e-4
},
'params': {
'rin': 1e6,
'rfb': 1e6
},
'saves': [ "all()" ],
'command': "tran(param['tr']*1, param['tstart']+param['pw']*2)"
},
'translew': {
'head' : 'opus',
'modules': [ 'def', 'tb' ],
'options': {
'reltol': 1e-4
},
'params': {
'rin': 1e6,
'rfb': 1e6,
'lev1': -0.8,
'lev2': 0.8
},
'saves': [ "all()" ],
'command': "tran(param['tr']*1, param['tstart']+param['pw']*2)"
},
'blank': {
'head': 'opus',
'params': {
'rin': 1e6,
'rfb': 1e6
},
'command': None
}
}
# Define performance measures, dependencies, and design requirements (lower and upper bounds)
measures = {
'isup': {
'analysis': 'op',
'expression': "isup=-i('vdd1')",
'upper': 1000e-6,
},
'out_op': {
'analysis': 'op',
'expression': "v('out')",
'lower': -12.5e-3,
'upper': 12.5e-3
},
'vgs_drv': {
'analysis': 'op',
'expression': """
res=[]
# Loop over all MOS instances in mosList
for inst in mosList:
# Generate full name for MOS instance
fullName=ipath(inst, 'x1', 'm0')
# Extract vgs and vth from results
vgs=p(fullName, 'vgs')
vth=p(fullName, 'vth')
# Compute difference and append to results list
diff=float(vgs-vth)
res.append(diff)
# Uncomment to print a debug message during evaluation
# m.debug("%s: vgs=%f vth=%f" % (fullName, vgs, vth))
# Return NumPy array
__result=np.array(res)
""",
'vector': True,
'lower': 0.0,
},
'vds_drv': {
'analysis': 'op',
'expression': """
res=[]
# Loop over all MOS instances in mosList
for inst in mosList:
# Generate full name for MOS instance
fullName=ipath(inst, 'x1', 'm0')
# Extract vds and vdsat from results
vds=p(fullName, 'vds')
vdsat=p(fullName, 'vdsat')
# Compute difference and append to results list
diff=float(vds-vdsat)
res.append(diff)
# Return NumPy array
__result=np.array(res)
""",
'vector': True,
'lower': 0.0,
},
'swing': {
'analysis': 'dc',
'expression': "m.DCswingAtGain(v('out'), v('inp', 'inn'), 0.5, 'out')",
'lower': 1.0,
},
'gain': {
'analysis': 'ac',
'expression': """
ndx=m.IatXval(np.abs(scale()), 10.0)[0]
__result=m.XatI(m.ACmag(m.ACtf(v('out'), v('inp', 'inn'))), ndx)
""",
'lower': 60.0,
},
#'bw': {
# 'analysis': 'ac',
# 'expression': "m.ACbandwidth(m.ACtf(v('out'), v('inp', 'inn')), scale())",
# 'lower': 4e3,
#},
'gain_com': {
'analysis': 'accom',
'expression': """
ndx=m.IatXval(np.abs(scale()), 10.0)[0]
__result=m.XatI(m.ACmag(m.ACtf(v('out'), 1.0)), ndx)
""",
},
'gain_vdd': {
'analysis': 'acvdd',
'expression': """
ndx=m.IatXval(np.abs(scale()), 10.0)[0]
__result=m.XatI(m.ACmag(m.ACtf(v('out'), 1.0)), ndx)
""",
},
'gain_vss': {
'analysis': 'acvss',
'expression': """
ndx=m.IatXval(np.abs(scale()), 10.0)[0]
__result=m.XatI(m.ACmag(m.ACtf(v('out'), 1.0)), ndx)
""",
},
'ugbw': {
'analysis': 'ac',
'expression': "m.ACugbw(m.ACtf(v('out'), v('inp', 'inn')), scale())",
'lower': 10e6,
},
'pm': {
'analysis': 'ac',
'expression': "m.ACphaseMargin(m.ACtf(v('out'), v('inp', 'inn')))",
'lower': 50.0,
},
'ph_slope': {
'analysis': 'ac',
'expression': """
# Transfer function
tf=m.ACtf(v('out'), v('inp', 'inn'))
# Phase slope in deg/dec
slope=m.dYdX(m.ACphase(tf), np.log10(scale()))
# Look only at points where gain>0dB
mask=m.ACmag(tf)>0
# Max slope in region defined by mask
__result=(slope*mask).max()
""",
'upper': 0,
},
'overshdn': {
'analysis': 'tran',
'expression': "m.Tundershoot(v('out'), scale(), t1=param['tstart'], t2=(param['pw']+param['tstart']+param['tr']))",
'upper': 0.15,
},
'overshup': {
'analysis': 'tran',
'expression': "m.Tovershoot(v('out'), scale(), t1=(param['pw']+param['tstart']+param['tr']))",
'upper': 0.15,
},
'tsetdn': {
'analysis': 'tran',
'expression': "m.TsettlingTime(v('out'), scale(), t1=param['tstart'], t2=(param['pw']+param['tstart']+param['tr']))",
'upper': 1000e-9,
},
'tsetup': {
'analysis': 'tran',
'expression': "m.TsettlingTime(v('out'), scale(), t1=(param['pw']+param['tstart'])+param['tr'])",
'upper': 1000e-9,
},
'slewdn': {
'analysis': 'translew',
'expression': "m.TslewRate('falling', v('out'), scale(), t1=param['tstart'], t2=(param['pw']+param['tstart']+param['tr']))",
'lower': 2e6,
},
'slewup': {
'analysis': 'translew',
'expression': "m.TslewRate('rising', v('out'), scale(), t1=(param['pw']+param['tstart']+param['tr']))",
'lower': 2e6,
},
'cmrr': {
'analysis': 'blank',
'expression': "result['gain'][cornerName]-result['gain_com'][cornerName]",
'lower': 60.0,
'depends': [ 'gain', 'gain_com' ]
},
'psrr_vdd': {
'analysis': 'blank',
'expression': "result['gain'][cornerName]-result['gain_vdd'][cornerName]",
'lower': 60.0,
'depends': [ 'gain', 'gain_vdd' ]
},
'psrr_vss': {
'analysis': 'blank',
'expression': "result['gain'][cornerName]-result['gain_vss'][cornerName]",
'lower': 60.0,
'depends': [ 'gain', 'gain_vss' ]
},
'onoise1k': {
'analysis': 'noise',
'expression': "m.XatI(ns('output'), m.IatXval(scale(), 1e3)[0])",
},
'inoise1k': {
'analysis': 'noise',
'expression': "m.XatI(ns('input'), m.IatXval(scale(), 1e3)[0])",
},
'in1kmn2id': {
'analysis': 'noise',
'expression': "m.XatI(ns('input', ipath('xmn2', 'x1', 'm0'), 'id'), m.IatXval(scale(), 1e3)[0])",
},
'in1kmn2rd': {
'analysis': 'noise',
'expression': "m.XatI(ns('input', ipath('xmn2', 'x1', 'm0'), 'rd'), m.IatXval(scale(), 1e3)[0])",
},
'in1kmn2': {
'analysis': 'noise',
'expression': "m.XatI(ns('input', ipath('xmn2', 'x1', 'm0')), m.IatXval(scale(), 1e3)[0])",
},
'area': {
'analysis': 'blank',
'expression': (
"(param['mirr_w']+param['mirr_wo'])*param['mirr_l']"
"+param['mirr_wd']*param['mirr_ld']"
"+param['out_w']*param['out_l']*(2)"
"+param['load_w']*param['load_l']*(1+1)"
"+param['diff_w']*param['diff_l']*(1+1)"
"+param['r_out']/1e3*12e-12"
"+param['c_out']/1e-12*100e-12"
),
'upper': 9000e-12
}
}
# Design parameters, lower bounds, upper bounds, and initial values
designParams={
'mirr_w': {
'lo': 1e-6,
'hi': 95e-6,
'init': 7.46e-005,
},
'mirr_wd': {
'lo': 1e-6,
'hi': 95e-6,
'init': 7.46e-005,
},
'mirr_wo': {
'lo': 1e-6,
'hi': 95e-6,
'init': 7.46e-005,
},
'mirr_l': {
'lo': 0.18e-6,
'hi': 4e-6,
'init': 5.63e-007,
},
'mirr_ld': {
'lo': 0.18e-6,
'hi': 4e-6,
'init': 5.63e-007,
},
'out_w': {
'lo': 1e-6,
'hi': 95e-6,
'init': 4.80e-5,
},
'out_l': {
'lo': 0.18e-6,
'hi': 4e-6,
'init': 3.75e-7,
},
'load_w': {
'lo': 1e-6,
'hi': 95e-6,
'init': 3.49e-5,
},
'load_l': {
'lo': 0.18e-6,
'hi': 4e-6,
'init': 2.57e-6,
},
'diff_w': {
'lo': 1e-6,
'hi': 95e-6,
'init': 7.73e-6,
},
'diff_l': {
'lo': 0.18e-6,
'hi': 4e-6,
'init': 1.08e-6,
},
'c_out': {
'lo': 1e-15,
'hi': 50e-12,
'init': 8.21e-12,
},
'r_out': {
'lo': 1,
'hi': 200e3,
'init': 1.97e+1,
}
}
# Statistical parameters, lower and upper bounds
statParams={ name: { 'dist': Normal(0,1) } for name in [
'mp1vt', 'mp1u0', 'mp2vt', 'mp2u0',
'mp3vt', 'mp3u0', 'mn1vt', 'mn1u0',
'mn2vt', 'mn2u0', 'mn3vt', 'mn3u0',
'mn4vt', 'mn4u0', 'mn5vt', 'mn5u0',
'gvtnmm', 'gu0nmm', 'gvtpmm', 'gu0pmm'
]}
# Operating parameters definitions, lower bounds, upper bounds, and nominal values
opParams={
'vdd': {
'lo': 1.7,
'hi': 2.0,
'init': 1.8
},
'temperature': {
'lo': 0.0,
'hi': 100.0,
'init': 25
}
}
Variable heads
contains the definitions of all simulators that are going
to be used. It is a dictionary. We are going to use Spice Opus only. Therefore
the dictionary has only one entry named opus
which is also a dictionary,
The first two members (simulator
and settings
) specify the simulator
and the arguments passed to the simulator wrapper at initialization (e.g.
debug level).
Dictionary moddefs
lists the netlist modules that will be
used for constructing the input netlist for the simulator. Modules def
,
tb
and tbrr
correspond to the amplifier definition (miller.inc
),
the first top level circuit (topdc.inc
), and the second top level circuit
(toprr.inc
). The remaining modules correspond to various MOS models in
the foundry’s library: tm
for the typical model, wp
for the worst power
model, ws
for worst speed model, wo
for worst one model, wz
for
worst zero model, and mc
for the Monte Carlo model used in Monte Carlo
analysis and mismatch analysis.
Dictionary options
lists the simulator options passed via the netlist
(in case of Spice Opus they are passed with the .options
directive).
Dictionary params
specifies the netlist parameters that will be defined
with .param
statements. We can see that the bias current, the input
pulse source shape, and the output load are specified here.
Variable variables
is a dictionary specifying the variables that will
be available during mesure evaluations and in the specifications of quantities
that need to be saved. In our case we define the list of MOS instances in
the circuit (mosList
) and corresponding flags that specify if a transistor
is a NMOS or a PMOS device (isNmos
). The latter is used only in the
Spectre demo because Spectre handles Vgs and Vds for PMOS transistors with
a negative sign in constrast to NMOS transistors where the sign is positive.
The analyses
variable lists the analyses pefromed by simulators.
For every analysis one has to specify the simulator to use (head
name):
the list of netlist modules that will be included in the simulator’s input
netlist (modules
), simulator options (options
), netlist parameters
(params
), the list of non-default quantities for the simulator to save
in its output files (saves
), and the actual analysis that the simulator
should invoke (analysis
). The simulator options and netlist parameters
specified here override those specified in the heads
variable.
The saves
entry does not have to be specified. If it is omitted the
default quantities are saved. An example of a custom save quantity list can
be seen in the definitions of the op
analysis. Here the default quantities
are saved (all()
) and certain properties of MOS transistors (vgs
,
vth
, vds
, and vdsat
) are saved for all MOS transistors defined in
variable mosList
. The ipath
function is used to add the outer path
(x1
) and the inner path (mo
) to form a fully qualified instance name
of the built-in MOS device corresponding to an individual transistor because
only built-in devices have special quantities that can be stored.
The measures
variable is a dictionary defining the performance measures
that will be extracted from simulator results. For every measure we must
specify the analysis name (analysis
), the expression or a script for
computing it (expression
), and a flag if the measure produces a vector
(vector
). The latter can be omitted if the measure produces a scalar.
Optionally one can also specify the lower and/or upper bound on acceptable
performance (lower
and upper
). If the measure produces a vector
this bound is applied to all components of a vector.
Some measures are not computed directly from simulation results. Instead
they are computed from other measures. For such measures blank
analysis
is defined. The command that executes the blank
analysis is set to
None
. If such a measure is computed from other measures a list
of measure names on which the measure depends must be given (depends
).
The designParams
variable lists the design parameters. For every parameter
the lower (lo
) and the upper (hi
) bound are specified, as well as
the initial value (init
).
The statParams
variable lists the statistical parameters. For every
statistical parameter the lower and the upper bound is specified (lo
and
hi
). All statistical parameters are for now assumed to be independent
normally distributed random variables with mean 0 and variance 1.
The opParams
variable lists the operating parameters of a circuit. In
our case these are the supply voltage (vdd
) and the temperature. For every
parameter one has to specify its lower and upper bound (lo
and hi
),
as well as, its nominal value (init
) are specified. One could also
simulate this circuit without taking into account that the operating
temperature can change over a range of values. In such cases the temperature
should be specified in the heads
variable under params
.