from pyopus.evaluator.distrib import *

# Problem definition

__all__ = [ 'heads', 'analyses', 'measures', 'variables', 'statParams', 'opParams', 'designParams' ]


heads = {
	'opus': {
		'simulator': 'Xyce', 
		'settings': {
			'debug': 10
		},
		'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()", 
			"i('vdd1')",
			"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')"
	}, 
	'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()", "ns(ipath('xmn2', 'x1', 'm0'))", "ns(ipath('xmn2', 'x1', 'm0'), ['id', 'rd'])" ], 
		'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(f"{fullName}: vgs={vgs:f} vth={vth:f}")
# 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
	}
}
