async def _search_helper( self, w: int, seg_min: int, seg_max: Optional[int], data_min: Optional[Any], data_max: Optional[Any], ) -> Tuple[Any, int, int, int]: # first, binary search on segments without changing width bin_iter = BinaryIterator(seg_min, seg_max, search_step=self._search_step) bval_min = bval_max = None if data_max is not None: bval_max = self.get_bin_search_info(data_max)[0] if data_min is not None: bval_min = self.get_bin_search_info(data_min)[0] bin_iter.set_current(seg_min) bin_iter.up(val=bval_min) elif seg_max is not None and data_max is not None: bin_iter.set_current(seg_max) bin_iter.down(val=bval_max) bounds = [[seg_min, bval_min, data_min], [seg_max, bval_max, data_max]] while bin_iter.has_next(): cur_seg = bin_iter.get_next() cur_data = await self.get_data(cur_seg, w) cur_bval, up = self.get_bin_search_info(cur_data) if up: bounds[0][0] = cur_seg bounds[0][1] = cur_bval bounds[0][2] = cur_data bin_iter.up(val=cur_bval) else: bounds[1][0] = cur_seg bounds[1][1] = cur_bval bounds[1][2] = cur_data bin_iter.down(val=cur_bval) if bounds[1][1] is None: idx = 0 seg_min = seg_max = bounds[0][0] elif bounds[0][1] is None: idx = 1 seg_min = seg_max = bounds[1][0] else: idx = int(abs(bounds[1][1]) < abs(bounds[0][1])) seg_min = bounds[0][0] seg_max = bounds[1][0] opt_bnd = bounds[idx] opt_seg = opt_bnd[0] opt_data = opt_bnd[2] a_min = seg_min * w a_max = seg_max * w return opt_data, opt_seg, a_min, a_max
def design_amp(amp_specs, nch_db, pch_db): sim_env = amp_specs['sim_env'] vdd = amp_specs['vdd'] vtail = amp_specs['vtail'] vgs_res = amp_specs['vgs_res'] gain_min = amp_specs['gain_min'] bw_min = amp_specs['bw_min'] cload = amp_specs['cload'] fun_ibiasn = nch_db.get_function('ibias', env=sim_env) fun_gmn = nch_db.get_function('gm', env=sim_env) fun_gdsn = nch_db.get_function('gds', env=sim_env) fun_cdn = nch_db.get_function('cdb', env=sim_env) + nch_db.get_function( 'cds', env=sim_env) fun_cgsn = nch_db.get_function('cgs', env=sim_env) fun_ibiasp = pch_db.get_function('ibias', env=sim_env) fun_gdsp = pch_db.get_function('gds', env=sim_env) fun_cdp = pch_db.get_function('cdd', env=sim_env) vgsn_idx = nch_db.get_fun_arg_index('vgs') vgsn_min, vgsn_max = fun_ibiasn.get_input_range(vgsn_idx) num_pts = int(math.ceil((vgsn_max - vgsn_min) / vgs_res)) vgs_list = np.linspace(vgsn_min, vgsn_max, num_pts + 1).tolist() vgsp_idx = pch_db.get_fun_arg_index('vgs') vgsp_min, vgsp_max = fun_ibiasp.get_input_range(vgsp_idx) # sweep vgs, find best point performance = None for vgsn_cur in vgs_list: vout = vgsn_cur + vtail narg = nch_db.get_fun_arg(vgs=vgsn_cur, vds=vgsn_cur, vbs=vtail) ibiasn_unit = fun_ibiasn(narg) gmn_unit = fun_gmn(narg) gdsn_unit = fun_gdsn(narg) cdn_unit = fun_cdn(narg) cgsn_unit = fun_cgsn(narg) # find max gain def gain_fun1(vgsp_test): parg_test = pch_db.get_fun_arg(vgs=vgsp_test, vds=vout - vdd, vbs=0) ibiasp_unit_test = fun_ibiasp(parg_test) gdsp_unit_test = fun_gdsp(parg_test) return gmn_unit / ibiasn_unit / (gdsn_unit / ibiasn_unit + gdsp_unit_test / ibiasp_unit_test) result = minimize_cost_golden_float(gain_fun1, gain_min, vgsp_min, vgsp_max, tol=vgs_res / 10) opt_vgsp = result.x if opt_vgsp is None: print('vgsn = %.4g, max gain: %.4g' % (vgsn_cur, result.vmax)) break # get number of input fingers needed to achieve gain_max with minimum number of load fingers seg_in_init = fun_ibiasp( pch_db.get_fun_arg(vgs=opt_vgsp, vds=vout - vdd, vbs=0)) * 2 / ibiasn_unit seg_in_init = int(round(seg_in_init / 2)) * 2 # sweep gm size seg_in_iter = BinaryIterator(2, None, step=2) seg_in_iter.set_current(seg_in_init) while seg_in_iter.has_next(): seg_in = seg_in_iter.get_next() ibiasn = seg_in * ibiasn_unit gmn = seg_in * gmn_unit gdsn = seg_in * gdsn_unit # sweep load size seg_load_iter = BinaryIterator(2, None, step=2) while seg_load_iter.has_next(): seg_load = seg_load_iter.get_next() vbp, sgn = find_load_bias(pch_db, vdd, vout, vgsp_min, vgsp_max, ibiasn, seg_load, fun_ibiasp) if vbp is None: if sgn > 0: seg_load_iter.up() else: seg_load_iter.down() else: parg = pch_db.get_fun_arg(vgs=vbp - vdd, vds=vout - vdd, vbs=0) gdsp = seg_load * fun_gdsp(parg) if gmn / (gdsp + gdsn) >= gain_min: seg_load_iter.save_info((vbp, parg)) seg_load_iter.down() else: seg_load_iter.up() seg_load = seg_load_iter.get_last_save() if seg_load is None: # no load solution -> cannot meet gain spec. break vbp, parg = seg_load_iter.get_last_save_info() gdsp = seg_load * fun_gdsp(parg) cdp = seg_load * fun_cdp(parg) cdn = seg_in * cdn_unit cgsn = seg_in * cgsn_unit ro_cur = 1 / (gdsp + gdsn) gain_cur = gmn * ro_cur cpar_cur = cdn + cdp + (1 + 1 / gain_cur) * cgsn # check intrinsic bandwidth good if 1 / (ro_cur * cpar_cur * 2 * np.pi) < bw_min: break cload_cur = cload + cpar_cur bw_cur = 1 / (ro_cur * cload_cur * 2 * np.pi) if bw_cur < bw_min: seg_in_iter.up() else: seg_in_iter.save_info( (seg_load, vbp, ibiasn, gain_cur, bw_cur)) seg_in_iter.down() if seg_in_iter.get_last_save() is None: continue seg_in = seg_in_iter.get_last_save() seg_load, vbp, ibiasn, gain_cur, bw_cur = seg_in_iter.get_last_save_info( ) if performance is None or performance[0] > ibiasn: performance = (ibiasn, gain_cur, bw_cur, seg_in, seg_load, vgsn_cur, vbp) if performance is None: return None ibias_opt, gain_opt, bw_opt, seg_in, seg_load, vgs_in, vload = performance vio = vtail + vgs_in seg_tail, vbias = find_tail_bias(fun_ibiasn, nch_db, vtail, vgsn_min, vgsn_max, seg_in, ibias_opt) return dict( ibias=2 * ibias_opt, gain=gain_opt, bw=bw_opt, seg_in=seg_in, seg_load=seg_load, seg_tail=seg_tail, vtail=vbias, vindc=vio, voutdc=vio, vload=vload, vgs_in=vgs_in, )