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.