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, )
def draw_layout(self): # type: () -> None w = self.params['w'] h_unit = self.params['h_unit'] sub_type = self.params['sub_type'] threshold = self.params['threshold'] top_layer = self.params['top_layer'] nser = self.params['nser'] ndum = self.params['ndum'] in_tr_info = self.params['in_tr_info'] out_tr_info = self.params['out_tr_info'] bias_idx = self.params['bias_idx'] vdd_tr_info = self.params['vdd_tr_info'] res_type = self.params['res_type'] res_options = self.params['res_options'] cap_spx = self.params['cap_spx'] cap_spy = self.params['cap_spy'] cap_margin = self.params['cap_margin'] half_blk_x = self.params['half_blk_x'] show_pins = self.params['show_pins'] res = self.grid.resolution lay_unit = self.grid.layout_unit w_unit = int(round(w / lay_unit / res)) if res_options is None: my_options = dict(well_end_mode=2) else: my_options = res_options.copy() my_options['well_end_mode'] = 2 # find resistor length info = ResArrayBaseInfo(self.grid, sub_type, threshold, top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True) lmin, lmax = info.get_res_length_bounds() bin_iter = BinaryIterator(lmin, lmax, step=2) while bin_iter.has_next(): lcur = bin_iter.get_next() htot = info.get_place_info(lcur, w_unit, 1, 1)[3] if htot < h_unit: bin_iter.save() bin_iter.up() else: bin_iter.down() # draw resistor l_unit = bin_iter.get_last_save() nx = 2 * (nser + ndum) self.draw_array(l_unit * lay_unit * res, w, sub_type, threshold, nx=nx, ny=1, top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True, min_height=h_unit) # connect resistors vdd, biasp, biasn, outp_h, outn_h, xl, xr = self.connect_resistors( ndum, nser, bias_idx) # draw MOM cap caplp, capln, caprp, caprn = self.draw_mom_cap(nser, xl, xr, cap_spx, cap_spy, cap_margin) # connect resistors to MOM cap, and draw metal resistors vm_layer = self.bot_layer_id + 1 self.connect_to_tracks(outp_h, capln.track_id) self.connect_to_tracks(outn_h, caprn.track_id) # connect outputs to horizontal tracks hm_layer = vm_layer + 1 pidx, nidx, tr_w = in_tr_info res_in_w = self.grid.get_track_width(hm_layer, tr_w, unit_mode=True) inp, inn = self.connect_differential_tracks(caplp, caprp, hm_layer, pidx, nidx, width=tr_w) tr_lower, tr_upper = inp.lower_unit, inp.upper_unit self.add_res_metal_warr(hm_layer, pidx, tr_lower - res_in_w, tr_lower, width=tr_w, unit_mode=True) self.add_res_metal_warr(hm_layer, nidx, tr_lower - res_in_w, tr_lower, width=tr_w, unit_mode=True) inp = self.add_wires(hm_layer, pidx, tr_lower - 2 * res_in_w, tr_lower - res_in_w, width=tr_w, unit_mode=True) inn = self.add_wires(hm_layer, nidx, tr_lower - 2 * res_in_w, tr_lower - res_in_w, width=tr_w, unit_mode=True) pidx, nidx, tr_w = out_tr_info res_out_w = self.grid.get_track_width(hm_layer, tr_w, unit_mode=True) self.connect_differential_tracks(capln, caprn, hm_layer, pidx, nidx, track_lower=tr_lower, track_upper=tr_upper, width=tr_w, unit_mode=True) self.add_res_metal_warr(hm_layer, pidx, tr_upper, tr_upper + res_out_w, width=tr_w, unit_mode=True) self.add_res_metal_warr(hm_layer, nidx, tr_upper, tr_upper + res_out_w, width=tr_w, unit_mode=True) outp = self.add_wires(hm_layer, pidx, tr_upper + res_out_w, tr_upper + 2 * res_out_w, width=tr_w, unit_mode=True) outn = self.add_wires(hm_layer, nidx, tr_upper + res_out_w, tr_upper + 2 * res_out_w, width=tr_w, unit_mode=True) # connect/export vdd if vdd_tr_info is None: self.add_pin('VDD_vm', vdd, label='VDD:', show=show_pins) else: self.add_pin('VDD_vm', vdd, label='VDD', show=show_pins) for tr_info in vdd_tr_info: tid = TrackID(hm_layer, tr_info[0], width=tr_info[1]) self.add_pin('VDD', self.connect_to_tracks(vdd, tid), show=show_pins) # add pins self.add_pin('biasp', biasp, show=show_pins) self.add_pin('biasn', biasn, show=show_pins) self.add_pin('outp', outp, show=show_pins) self.add_pin('outn', outn, show=show_pins) self.add_pin('inp', inp, show=show_pins) self.add_pin('inn', inn, show=show_pins) self._sch_params = dict( l=l_unit * lay_unit * res, w=w, intent=res_type, nser=nser, ndum=ndum, res_in_info=(hm_layer, res_in_w * res * lay_unit, res_in_w * res * lay_unit), res_out_info=(hm_layer, res_out_w * res * lay_unit, res_out_w * res * lay_unit), )
def fill_symmetric_min_density_info(area, targ_area, n_min, n_max, sp_min, sp_max=None, fill_on_edge=True, cyclic=False): # type: (int, int, int, int, int, Optional[int], bool, bool) -> Tuple[Tuple[Any, ...], bool] """Fill the given 1-D area as little as possible. Compute fill location such that the given area is filled with the following properties: 1. the area is as uniform as possible. 2. the area is symmetric with respect to the center 3. all fill blocks have lengths between n_min and n_max. 4. all fill blocks are at least sp_min apart. Parameters ---------- area : int total number of space we need to fill. targ_area : int target minimum fill area. If not achievable, will do the best that we can. n_min : int minimum length of the fill block. Must be less than or equal to n_max. n_max : int maximum length of the fill block. sp_min : int minimum space between each fill block. sp_max : Optional[int] if given, make sure space between blocks does not exceed this value. Must be greater than sp_min fill_on_edge : bool If True, we put fill blocks on area boundary. Otherwise, we put space block on area boundary. cyclic : bool If True, we assume we're filling in a cyclic area (it wraps around). Returns ------- info : Tuple[Any, ...] the fill information tuple. invert : bool True if space/fill is inverted. """ # first, fill as much as possible max_result = fill_symmetric_max_density_info(area, targ_area, n_min, n_max, sp_min, sp_max=sp_max, fill_on_edge=fill_on_edge, cyclic=cyclic) fill_area, nfill_opt = max_result[0][:2] if fill_area <= targ_area: # we cannot/barely meet area spec; return max result return max_result # now, reduce fill by doing binary search on n_max n_max_iter = BinaryIterator(n_min, n_max) while n_max_iter.has_next(): n_max_cur = n_max_iter.get_next() try: info, invert = fill_symmetric_max_num_info( area, nfill_opt, n_min, n_max_cur, sp_min, fill_on_edge=fill_on_edge, cyclic=cyclic) fill_area_cur = area - info[0] if invert else info[0] if invert: _, sp_cur = _get_min_max_blk_len(info) else: sp_cur = sp_min if info[1][2] == 0 else sp_min + 1 if fill_area_cur >= targ_area and (sp_max is None or sp_cur <= sp_max): # both specs passed n_max_iter.save_info((info, invert)) n_max_iter.down() else: # reduce n_max too much n_max_iter.up() except ValueError: # get here if n_min == n_max and there's no solution. n_max_iter.up() last_save = n_max_iter.get_last_save_info() if last_save is None: # no solution, return max result return max_result # return new minimum solution info, invert = last_save fill_area = area - info[0] if invert else info[0] return (fill_area, nfill_opt, info[1]), invert
def draw_layout(self): # type: () -> None top_layer = self.params['top_layer'] lch = self.params['lch'] w = self.params['w'] sub_type = self.params['sub_type'] threshold = self.params['threshold'] port_width = self.params['port_width'] well_width = self.params['well_width'] end_mode = self.params['end_mode'] is_passive = self.params['is_passive'] max_nxblk = self.params['max_nxblk'] port_tid = self.params['port_tid'] show_pins = self.params['show_pins'] res = self.grid.resolution well_width = int(round(well_width / res)) right_end = (end_mode & 8) != 0 left_end = (end_mode & 4) != 0 top_end = (end_mode & 2) != 0 bot_end = (end_mode & 1) != 0 # get layout info, also set RoutingGrid to substrate grid. layout_info = AnalogBaseInfo(self.grid, lch, 0, top_layer=top_layer, end_mode=end_mode) # compute template width in number of sd pitches # find maximum number of fingers we can draw bin_iter = BinaryIterator(1, None) while bin_iter.has_next(): cur_fg = bin_iter.get_next() cur_pinfo = layout_info.get_placement_info(cur_fg) cur_core_width = cur_pinfo.core_width if cur_core_width == well_width: bin_iter.save_info(cur_pinfo) break elif cur_core_width < well_width: bin_iter.save_info(cur_pinfo) bin_iter.up() else: bin_iter.down() sub_fg_tot = bin_iter.get_last_save() if sub_fg_tot is None: raise ValueError('Cannot draw substrate that fit in width: %d' % well_width) # check width parity requirement if max_nxblk > 0: blkw = self.grid.get_block_size(top_layer, unit_mode=True)[0] place_info = bin_iter.get_last_save_info() cur_nxblk = place_info.tot_width // blkw while sub_fg_tot > 0 and (cur_nxblk > max_nxblk or (max_nxblk - cur_nxblk) % 2 != 0): sub_fg_tot -= 1 place_info = layout_info.get_placement_info(sub_fg_tot) cur_nxblk = place_info.tot_width // blkw if sub_fg_tot <= 0: raise ValueError('Cannot draw substrate with width = %d, ' 'max_nxblk = %d' % (well_width, max_nxblk)) layout_info.set_fg_tot(sub_fg_tot) self.grid = layout_info.grid place_info = layout_info.get_placement_info(sub_fg_tot) edgel_x0 = place_info.edge_margins[0] tot_width = place_info.tot_width # create masters master_list = [ self.new_template(params=dict( lch=lch, fg=sub_fg_tot, sub_type=sub_type, threshold=threshold, is_end=bot_end, top_layer=top_layer, ), temp_cls=AnalogEndRow), self.new_template(params=dict( lch=lch, w=w, sub_type=sub_type, threshold=threshold, fg=sub_fg_tot, top_layer=top_layer, options=dict(is_passive=is_passive), ), temp_cls=AnalogSubstrate), self.new_template(params=dict( lch=lch, fg=sub_fg_tot, sub_type=sub_type, threshold=threshold, is_end=top_end, top_layer=top_layer, ), temp_cls=AnalogEndRow), ] ycur = 0 array_box = BBox.get_invalid_bbox() sub_conn, inst = None, None for master, orient in zip(master_list, ['R0', 'R0', 'MX']): if orient == 'MX': ycur += master.array_box.top_unit name_id = master.get_layout_basename() edge_layout_info = master.get_edge_layout_info() xcur = edgel_x0 if left_end: edge_info = master.get_left_edge_info() edge_params = dict( is_end=True, guard_ring_nf=0, name_id=name_id, layout_info=edge_layout_info, adj_blk_info=edge_info, ) edge_master = self.new_template(params=edge_params, temp_cls=AnalogEdge) if not edge_master.is_empty: edge_inst = self.add_instance(edge_master, loc=(edgel_x0, ycur), orient=orient, unit_mode=True) array_box = array_box.merge(edge_inst.array_box) xcur = edge_inst.array_box.right_unit inst = self.add_instance(master, loc=(xcur, ycur), orient=orient, unit_mode=True) array_box = array_box.merge(inst.array_box) if isinstance(master, AnalogSubstrate): conn_params = dict( layout_info=edge_layout_info, layout_name=name_id + '_subconn', is_laygo=False, ) conn_master = self.new_template(params=conn_params, temp_cls=AnalogSubstrateConn) sub_conn = self.add_instance(conn_master, loc=(xcur, ycur), orient=orient, unit_mode=True) xcur = inst.array_box.right_unit if right_end: edge_info = master.get_right_edge_info() edge_params = dict( is_end=True, guard_ring_nf=0, name_id=name_id, layout_info=edge_layout_info, adj_blk_info=edge_info, ) edge_master = self.new_template(params=edge_params, temp_cls=AnalogEdge) if not edge_master.is_empty: xcur += edge_master.array_box.right_unit eor = 'MY' if orient == 'R0' else 'R180' edge_inst = self.add_instance(edge_master, loc=(xcur, ycur), orient=eor, unit_mode=True) array_box = array_box.merge(edge_inst.array_box) if orient == 'R0': ycur += master.array_box.top_unit # calculate substrate Y coordinates imp_yb, thres_yb = master_list[0].sub_ysep imp_yt, thres_yt = master_list[2].sub_ysep self._sub_bndy = (imp_yb, ycur - imp_yt), (thres_yb, ycur - thres_yt) # get left/right substrate coordinates tot_imp_box = BBox.get_invalid_bbox() for lay in self.grid.tech_info.get_implant_layers('ptap'): tot_imp_box = tot_imp_box.merge(self.get_rect_bbox(lay)) for lay in self.grid.tech_info.get_implant_layers('ntap'): tot_imp_box = tot_imp_box.merge(self.get_rect_bbox(lay)) if not tot_imp_box.is_physical(): self._sub_bndx = None, None else: self._sub_bndx = tot_imp_box.left_unit, tot_imp_box.right_unit # set array box and size self.array_box = array_box bound_box = BBox(0, 0, tot_width, inst.bound_box.top_unit, res, unit_mode=True) if self.grid.size_defined(top_layer): self.set_size_from_bound_box(top_layer, bound_box) else: self.prim_bound_box = bound_box self.prim_top_layer = top_layer self.add_cell_boundary(bound_box) hm_layer = layout_info.mconn_port_layer + 1 if port_tid is None: # find center track index hm_mid = self.grid.coord_to_nearest_track(hm_layer, self.array_box.yc_unit, mode=0, half_track=True, unit_mode=True) # connect to horizontal metal layer. hm_pitch = self.grid.get_track_pitch(hm_layer, unit_mode=True) ntr = self.array_box.height_unit // hm_pitch # type: int if port_width is None: port_width = self.grid.get_max_track_width( hm_layer, 1, ntr, half_end_space=False) port_tid = TrackID(hm_layer, hm_mid, width=port_width) else: port_tid = TrackID(hm_layer, port_tid[0], width=port_tid[1]) port_name = 'VDD' if sub_type == 'ntap' else 'VSS' sub_wires = self.connect_to_tracks( sub_conn.get_port(port_name).get_pins(hm_layer - 1), port_tid) self.add_pin(port_name, sub_wires, show=show_pins) self._fg_tot = sub_fg_tot
def draw_layout(self): # type: () -> None w = self.params['w'] h_unit = self.params['h_unit'] narr = self.params['narr'] sub_type = self.params['sub_type'] threshold = self.params['threshold'] top_layer = self.params['top_layer'] nser = self.params['nser'] ndum = self.params['ndum'] port_tr_w = self.params['port_tr_w'] res_type = self.params['res_type'] res_options = self.params['res_options'] cap_spx = self.params['cap_spx'] cap_spy = self.params['cap_spy'] cap_h_list = self.params['cap_h_list'] half_blk_x = self.params['half_blk_x'] show_pins = self.params['show_pins'] if nser % 2 != 0: raise ValueError('This generator only supports even nser.') res = self.grid.resolution lay_unit = self.grid.layout_unit w_unit = int(round(w / lay_unit / res)) if res_options is None: my_options = dict(well_end_mode=2) else: my_options = res_options.copy() my_options['well_end_mode'] = 2 # find resistor length info = ResArrayBaseInfo(self.grid, sub_type, threshold, top_layer=top_layer, res_type=res_type, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True) lmin, lmax = info.get_res_length_bounds() bin_iter = BinaryIterator(lmin, lmax, step=2) while bin_iter.has_next(): lcur = bin_iter.get_next() htot = info.get_place_info(lcur, w_unit, 1, 1)[3] if htot < h_unit: bin_iter.save() bin_iter.up() else: bin_iter.down() # draw resistor l_unit = bin_iter.get_last_save() nx = 2 * ndum + narr * nser self.draw_array(l_unit * lay_unit * res, w, sub_type, threshold, nx=nx, ny=1, top_layer=top_layer, res_type=res_type, grid_type=None, ext_dir='y', options=my_options, connect_up=True, half_blk_x=half_blk_x, half_blk_y=True, min_height=h_unit) # get cap settings bot_layer = self.bot_layer_id + 1 for lay in range(bot_layer, top_layer + 1): if self.grid.get_direction(lay) == 'x': cap_spx = max( cap_spx, self.grid.get_line_end_space(lay, 1, unit_mode=True)) # connect resistors and draw MOM caps tmp = self._connect_resistors(narr, nser, ndum, cap_spx, port_tr_w, show_pins) rout_list, cap_x_list = tmp tmp = self._draw_mom_cap(cap_x_list, bot_layer, top_layer, cap_spy, cap_h_list, port_tr_w, show_pins) cout_list, ores_info, cres_info = tmp # connect bias resistor to cap for rout, cout in zip(rout_list, cout_list): self.connect_to_track_wires(rout, cout) # set schematic parameters self._sch_params = dict( narr=narr, ndum=ndum * 2, hp_params=dict( l=l_unit * lay_unit * res, w=w, intent=res_type, nser=nser, ndum=0, res_in_info=cres_info, res_out_info=ores_info, sub_name='VSS', ), )
def find_core_size( self, # type: ResTech grid, # type: RoutingGrid params, # type: Dict[str, Any] wres, # type: int hres, # type: int wblk, # type: int hblk, # type: int ext_dir, # type: str max_blk_ext, # type: int ): # type: (...) -> Tuple[int, int, Dict[str, Any]] """Compute resistor core size that meets DRC rules. Given current resistor block size and the block pitch, increase the resistor block size if necessary to meet DRC rules. Parameters ---------- grid : RoutingGrid the RoutingGrid object. params : Dict[str, Any] the resistor parameters dictionary. wres : int resistor core width, in resolution units. hres : int resistor core height, in resolution units. wblk : int the horizontal block pitch, in resolution units. hblk : int the vertical block pitch, in resolution units. ext_dir : Optional[str] if equal to 'x', then we will only stretch the resistor core horizontally. If equal to 'y', we will only stretch the resistor core vertically. Otherwise, we will find the resistor core with the minimum area that meets the density spec. max_blk_ext : int number of block pitches we can extend the resistor core size by. If we cannot find a valid core size by extending this many block pitches, we declare failure. Returns ------- nxblk : int width of the resistor core, in units of wblk. nyblk : int height of the resistor core, in units of hblk. layout_info : Dict[str, Any] the core layout information dictionary. """ nxblk = wres // wblk nyblk = hres // hblk ans = None x_only = (ext_dir == 'x') if x_only or (ext_dir == 'y'): # only extend X or Y direction if x_only: bin_iter = BinaryIterator(nxblk, nxblk + max_blk_ext + 1) else: bin_iter = BinaryIterator(nyblk, nyblk + max_blk_ext + 1) while bin_iter.has_next(): ncur = bin_iter.get_next() if x_only: wcur, hcur = ncur * wblk, hres else: wcur, hcur = wres, ncur * hblk tmp = self.get_core_info(grid, wcur, hcur, **params) if tmp is None: bin_iter.up() else: ans = tmp bin_iter.save() bin_iter.down() if ans is None: raise ValueError( 'failed to find DRC clean core with maximum %d ' 'additional block pitches.' % max_blk_ext) if x_only: nxblk = bin_iter.get_last_save() else: nyblk = bin_iter.get_last_save() return nxblk, nyblk, ans else: # extend in both direction opt_area = (nxblk + max_blk_ext + 1) * (nyblk + max_blk_ext + 1) # linear search in height, binary search in width # in this way, for same area, use height as tie breaker nxopt, nyopt = nxblk, nyblk for nycur in range(nyblk, nyblk + max_blk_ext + 1): # check if we should terminate linear search if nycur * nxblk >= opt_area: break bin_iter = BinaryIterator(nxblk, nxblk + max_blk_ext + 1) hcur = nycur * hblk while bin_iter.has_next(): nxcur = bin_iter.get_next() if nxcur * nycur >= opt_area: # this point can't beat current optimum bin_iter.down() else: tmp = self.get_core_info(grid, nxcur * wblk, hcur, **params) if tmp is None: bin_iter.up() else: # found new optimum ans, nxopt, nyopt = tmp, nxcur, nycur opt_area = nxcur * nycur bin_iter.down() if ans is None: raise ValueError( 'failed to find DRC clean core with maximum %d ' 'additional block pitches.' % max_blk_ext) return nxopt, nyopt, ans
def find_edge_size( self, # type: ResTech grid, # type: RoutingGrid core_info, # type: Dict[str, Any] is_lr_edge, # type: bool params, # type: Dict[str, Any] blk1, # type: int max_blk_ext, # type: int ): # type: (...) -> Tuple[int, Dict[str, Any]] """Compute resistor edge size that meets DRC rules. Calculate edge dimension (width for LR edge, height for TB edge) that meets DRC rules Parameters ---------- grid : RoutingGrid the RoutingGrid object. core_info : Dict[str, Any] core layout information dictionary. is_lr_edge : bool True if this is left/right edge, False if this is top/bottom edge. params : Dict[str, Any] the resistor parameters dictionary. blk1 : int dimension1 block size in resolution units. max_blk_ext : int maximum number of blocks we can extend by. Returns ------- n1 : int edge length in dimension 1 as number of blocks. layout_info : Dict[str, Any] the edge layout information dictionary. """ bin_iter = BinaryIterator(1, max_blk_ext + 2) ans = None while bin_iter.has_next(): n1 = bin_iter.get_next() if is_lr_edge: tmp = self.get_lr_edge_info(grid, core_info, n1 * blk1, **params) else: tmp = self.get_tb_edge_info(grid, core_info, n1 * blk1, **params) if tmp is None: bin_iter.up() else: ans = tmp bin_iter.save() bin_iter.down() if ans is None: raise ValueError('failed to find DRC clean core with maximum %d ' 'additional block pitches.' % max_blk_ext) return bin_iter.get_last_save(), ans
def size_unit_block( self, conn_layer: int, top_layer: int, nx: int, ny: int, tr_manager: TrackManager, wire_specs: Mapping[int, Any], mode: ExtendMode, max_ext: int = 1000, **kwargs: Any ) -> Tuple[int, int, Dict[int, WireLookup], ArrayLayInfo]: wire_specs = WireSpecs.make_wire_specs( conn_layer, top_layer, tr_manager, wire_specs, min_size=self.min_size, blk_pitch=self.blk_pitch, align_default=Alignment.CENTER_COMPACT) blk_info: Optional[ArrayLayInfo] = None w_min, h_min = wire_specs.min_size blk_w, blk_h = wire_specs.blk_size w = w_min h = h_min opt_area = COORD_MAX**2 def helper_fun( w_test: int, h_test: int, binfo: Optional[ArrayLayInfo], wc: int, hc: int, opt_a: int, iterator: BinaryIterator ) -> Tuple[Optional[ArrayLayInfo], int, int, int]: cur_area = w_test * h_test if cur_area >= opt_a: # this point can't beat current optimum iterator.down() return binfo, wc, hc, opt_a else: cur_info = self.get_blk_info(conn_layer, w_test, h_test, nx, ny, **kwargs) if cur_info is None: iterator.up() return binfo, wc, hc, opt_a else: # found new optimum iterator.down() return cur_info, w_test, h_test, cur_area if mode is ExtendMode.AREA: # extend in both direction # linear search in height, binary search in width # in this way, for same area, use height as tie breaker for h_cur in range(h_min, h_min + max_ext * blk_h, blk_h): if w_min * h_cur >= opt_area: # terminate linear search break bin_iter = BinaryIterator(w_min, w_min + max_ext * blk_w, step=blk_w) while bin_iter.has_next(): w_cur = bin_iter.get_next() blk_info, w, h, opt_area = helper_fun( w_cur, h_cur, blk_info, w, h, opt_area, bin_iter) elif mode is ExtendMode.X: h_cur = h_min bin_iter = BinaryIterator(w_min, w_min + max_ext * blk_w, step=blk_w) while bin_iter.has_next(): w_cur = bin_iter.get_next() blk_info, w, h, opt_area = helper_fun(w_cur, h_cur, blk_info, w, h, opt_area, bin_iter) else: w_cur = w_min bin_iter = BinaryIterator(h_min, h_min + max_ext * blk_h, step=blk_h) while bin_iter.has_next(): h_cur = bin_iter.get_next() blk_info, w, h, opt_area = helper_fun(w_cur, h_cur, blk_info, w, h, opt_area, bin_iter) if blk_info is None: raise ValueError(f'Failed to find legal resistor unit block size ' f'with max_ext={max_ext}') return w, h, wire_specs.place_wires(tr_manager, w, h), blk_info
async def _get_stack( self, dut_params: Dict[str, Any], mm_specs: Dict[str, Any], r_targ: float, stack_max: Optional[int] = None) -> Tuple[np.ndarray, np.ndarray]: """Binary searches the stack size to hit target r_targ output resistance. If stack_max is None, we skip sizing. This is set when sizing the main driver. NOTE: this function modifies dut_params and tbm_specs. Parameters ---------- dut_params: Dict[str, Any] Driver generator parameters mm_specs: Dict[str, Any] Specs for DriverPullUpDownMM r_targ: Target output resistance stack_max: Maximum allowed transistor stack size Returns ------- r_pu, r_pd: Tuple[np.ndarray, np.ndarray] Measured pull-up / pull-down output resistance across given corners """ if not stack_max: dut_params['stack'] = 1 sim_id = f'stack_1' r_pu, r_pd = await self._get_resistance(sim_id, dut_params, mm_specs) return r_pu, r_pd r_best = 0.0 bin_iter = BinaryIterator(1, None) while bin_iter.has_next(): cur_stack = bin_iter.get_next() while bin_iter.has_next() and cur_stack > stack_max: bin_iter.down(float('inf')) cur_stack = bin_iter.get_next() if cur_stack > stack_max: break dut_params['stack'] = cur_stack sim_id = f'stack_{cur_stack}' r_pu, r_pd = await self._get_resistance(sim_id, dut_params, mm_specs) r_test = min(np.min(r_pu), np.min(r_pd)) r_best = max(r_test, r_best) if r_targ > min(np.min(r_pu), np.min(r_pd)): bin_iter.up(r_targ - min(np.min(r_pu), np.min(r_pd))) else: bin_iter.save_info((cur_stack, r_pu, r_pd)) bin_iter.down(r_targ - min(np.min(r_pu), np.min(r_pd))) save_info = bin_iter.get_last_save_info() if save_info is None: self.error(f'Cannot meet spec with stack_max = {stack_max}, ' f'r_best = {r_best:.4g}') stack, r_pu, r_pd = bin_iter.get_last_save_info() dut_params['stack'] = stack return r_pu, r_pd
def draw_layout(self): # type: () -> None lch = self.params['lch'] ptap_w = self.params['ptap_w'] ntap_w = self.params['ntap_w'] wp = self.params['wp'] wn = self.params['wn'] thp = self.params['thp'] thn = self.params['thn'] nx = self.params['nx'] ny = self.params['ny'] fill_config = self.params['fill_config'] top_layer = self.params['top_layer'] sup_width = self.params['sup_width'] options = self.params['options'] show_pins = self.params['show_pins'] if options is None: options = {} # get power fill size w_tot, h_tot = self.grid.get_fill_size(top_layer, fill_config, unit_mode=True) w_tot *= nx h_tot *= ny # get number of fingers info = AnalogBaseInfo(self.grid, lch, 0, top_layer=top_layer) bin_iter = BinaryIterator(2, None) while bin_iter.has_next(): fg_cur = bin_iter.get_next() w_cur = info.get_placement_info(fg_cur).tot_width if w_cur < w_tot: bin_iter.save() bin_iter.up() elif w_cur > w_tot: bin_iter.down() else: bin_iter.save() break fg_tot = bin_iter.get_last_save() if fg_tot is None: raise ValueError('Decaep cell width exceed fill width.') self.draw_base(lch, fg_tot, ptap_w, ntap_w, [wn], [thn], [wp], [thp], ng_tracks=[1], pg_tracks=[1], n_orientations=['MX'], p_orientations=['R0'], top_layer=top_layer, min_height=h_tot, **options) if self.bound_box.height_unit > h_tot: raise ValueError('Decap cell height exceed fill height.') nmos = self.draw_mos_conn('nch', 0, 0, fg_tot, 0, 0) pmos = self.draw_mos_conn('pch', 0, 0, fg_tot, 2, 2, gate_pref_loc='s') vss_tid = self.make_track_id('pch', 0, 'g', 0) vdd_tid = self.make_track_id('nch', 0, 'g', 0) self.connect_to_substrate('ptap', nmos['d']) self.connect_to_substrate('ntap', pmos['s']) vss_g = self.connect_to_tracks([nmos['s'], pmos['g']], vss_tid) vdd_g = self.connect_to_tracks([pmos['d'], nmos['g']], vdd_tid) vss, vdd = self.fill_dummy(vdd_width=sup_width, vss_width=sup_width) vss.append(vss_g) vdd.append(vdd_g) self.add_pin('VSS', vss, label='VSS:', show=show_pins) self.add_pin('VDD', vdd, label='VDD:', show=show_pins)
def design(self, itarg_list, vds2_list, ft_min, stack_list=None): # type: (List[float], List[float], float, Optional[List[int]]) -> None """Design the diode load. Parameters ---------- itarg_list : List[float] target single-ended bias current across simulation environments. vds2_list : List[float] list of op-amp stage 2 vds voltage across simulation environments. ft_min : float minimum transit frequency of the composit transistor. stack_list : Optional[List[int]] list of valid stack numbers. """ if stack_list is None: stack_list = self._stack_list vgs_idx = self._db.get_fun_arg_index('vgs') num_stack = len(stack_list) self._best_op = None best_score = None for intent in self._intent_list: for w in self._valid_widths: for idx1 in range(num_stack): stack1 = stack_list[idx1] self._db.set_dsn_params(w=w, intent=intent, stack=stack1) ib1 = self._db.get_function_list('ibias') gm1 = self._db.get_function_list('gm') gds1 = self._db.get_function_list('gds') cd1 = self._db.get_function_list('cdd') vgs1_min, vgs1_max = ib1[0].get_input_range(vgs_idx) for idx2 in range(idx1, num_stack): stack2 = stack_list[idx2] self._db.set_dsn_params(stack=stack2) ib2 = self._db.get_function_list('ibias') gm2 = self._db.get_function_list('gm') gds2 = self._db.get_function_list('gds') cd2 = self._db.get_function_list('cdd') vgs2_min, vgs2_max = ib2[0].get_input_range(vgs_idx) vgs_min = max(vgs1_min, vgs2_min) vgs_max = min(vgs1_max, vgs2_max) seg1_iter = BinaryIterator(2, None, step=2) while seg1_iter.has_next(): seg1 = seg1_iter.get_next() all_neg = True one_pass = False seg2_iter = BinaryIterator(0, None, step=2) while seg2_iter.has_next(): seg2 = seg2_iter.get_next() vgs_list, err_code = self._solve_vgs( itarg_list, seg1, seg2, ib1, ib2, vgs_min, vgs_max) if err_code < 0: # too few fingers seg2_iter.up() elif err_code > 0: # too many fingers seg2_iter.down() else: one_pass = True cur_score = self._compute_score( ft_min, seg1, seg2, gm1, gm2, gds1, gds2, cd1, cd2, vgs_list) if cur_score != -1: all_neg = False if cur_score < 0: seg2_iter.down() else: seg2_iter.save() seg2_iter.up() if best_score is None or cur_score > best_score: best_score = cur_score self._best_op = (intent, stack1, stack2, w, seg1, seg2, vgs_list, vds2_list) if seg2_iter.get_last_save() is None: # no solution for seg2 if all_neg and one_pass: # all solutions encountered have negative resistance, # this means we have insufficent number of diode fingers. seg1_iter.up() elif not one_pass: # exit immediately with no solutions at all; too many fingers seg1_iter.down() else: # all positive resistance solution break V*_min specs. # this means we have too many number of fingers. seg1_iter.down() else: seg1_iter.save() seg1_iter.up()
async def _design_output_inverter(self, inv_in_pseg: int, inv_in_nseg: int, pseg: int, nseg: int, inv_nseg: int, inv_pseg: int, out_inv_m: int, fanout: float, pinfo: Any, tbm_specs: Dict[str, Any], has_rst, vin, vout) -> int: """ Given all other sizes and total output inverter segments, this function will optimize the output inverter to minimize rise/fall mismatch. """ tb_params = self._get_full_tb_params() # Use a binary iterator to find the PMOS size iterator = BinaryIterator(-out_inv_m + 1, out_inv_m - 1) err_best = float('inf') all_corners = get_tech_global_info( 'bag3_digital')['signoff_envs']['all_corners'] while iterator.has_next(): pseg_off = iterator.get_next() dut_params = self._get_lvl_shift_params_dict(pinfo, pseg, nseg, inv_pseg, inv_nseg, inv_in_nseg, inv_in_pseg, out_inv_m, has_rst, dual_output=False, skew_out=True, out_pseg_off=pseg_off) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) err_worst = -1 * float('Inf') worst_env = '' sim_worst = None for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd_in'] = all_corners[vin][env] tbm_specs['sim_params']['vdd'] = all_corners[vout][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_output_inv_pseg_{pseg_off}_{env}', dut, tbm, tb_params) tdr_cur, tdf_cur = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') if math.isinf(np.max(tdr_cur)) or math.isinf(np.max(tdf_cur)): raise ValueError("Got infinite delay!") if tdr_cur[0] < 0 or tdf_cur[0] < 0: raise ValueError("Got negative delay.") err_cur = np.abs(tdr_cur[0] - tdf_cur[0]) if err_cur > err_worst: err_worst = err_cur worst_env = env tdr = tdr_cur[0] tdf = tdf_cur[0] sim_worst = sim_results ''' print(f'iter: {pseg_off}') print(f'env: {worst_env}, tdr: {tdr}, tdf: {tdf}') breakpoint() ''' if tdr < tdf: iterator.down(tdr - tdf) else: iterator.up(tdr - tdf) err_abs = np.abs(tdr - tdf) if err_abs < err_best: err_best = err_abs iterator.save_info(pseg_off) pseg_off = iterator.get_last_save_info() if pseg_off is None: raise ValueError("Could not find PMOS size to match target delay") self.log(f'Calculated output inverter to skew PMOS by {pseg_off}.') return pseg_off
async def _design_lvl_shift_inv_pun(self, pseg: int, nseg: int, inv_nseg: int, out_inv_m: int, fanout: float, pinfo: Any, tbm_specs: Dict[str, Any], has_rst, dual_output, vin, vout) -> Tuple[int, int]: """ Given the NMOS pull down size, this function will design the PMOS pull up so that the delay mismatch is minimized. # TODO: Need to double check on how this handles corners """ inv_beta = get_tech_global_info('bag3_digital')['inv_beta'] tb_params = self._get_full_tb_params() # Use a binary iterator to find the PMOS size load_seg = nseg + (pseg if has_rst else 0) inv_pseg_nom = int( np.round(inv_beta * load_seg / ((1 + inv_beta) * fanout))) inv_pseg_nom = 1 if inv_pseg_nom == 0 else inv_pseg_nom iterator = BinaryIterator(-inv_pseg_nom + 1, 0) err_best = float('inf') inv_in_nseg, inv_in_pseg = self._size_input_inv_for_fanout( inv_pseg_nom, inv_nseg, pseg, nseg, fanout, has_rst) all_corners = get_tech_global_info( 'bag3_digital')['signoff_envs']['all_corners'] while iterator.has_next(): pseg_off = iterator.get_next() inv_pseg = inv_pseg_nom + pseg_off dut_params = self._get_lvl_shift_params_dict( pinfo, pseg, nseg, inv_pseg, inv_nseg, inv_in_nseg, inv_in_pseg, out_inv_m, has_rst, dual_output) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) err_worst = -1 * float('Inf') for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd_in'] = all_corners[vin][env] tbm_specs['sim_params']['vdd'] = all_corners[vout][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_inv_pseg_{inv_pseg}_{env}', dut, tbm, tb_params) tdr_cur, tdf_cur = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') ''' plt.figure() plt.plot(sim_results.data['time'].flatten(), sim_results.data['in'].flatten(), 'b') plt.plot(sim_results.data['time'].flatten(), sim_results.data['inb_buf'].flatten(), 'g') plt.plot(sim_results.data['time'].flatten(), sim_results.data['in_buf'].flatten(), 'r') plt.plot(sim_results.data['time'].flatten(), sim_results.data['midn'].flatten(), 'k') plt.plot(sim_results.data['time'].flatten(), sim_results.data['midp'].flatten(), 'c') plt.plot(sim_results.data['time'].flatten(), sim_results.data['out'].flatten(), 'm') plt.legend(['in', 'inb_buf', 'in_buf', 'midn', 'midp', 'out']) plt.title(f'pseg_off: {pseg_off}, pseg: {inv_pseg}, nseg: {inv_nseg-pseg_off}, fanout: {fanout}') plt.show(block=False) ''' # Error checking if math.isinf(np.max(tdr_cur)) or math.isinf(np.max(tdf_cur)): raise ValueError("Got infinite delay!") if np.min(tdr_cur) < 0 or np.min(tdf_cur) < 0: raise ValueError("Got negative delay.") err_cur = np.abs(tdr_cur[0] - tdf_cur[0]) if err_cur > err_worst: err_worst = err_cur worst_env = env tdr = tdr_cur[0] tdf = tdf_cur[0] ''' print(f'iter: {inv_pseg}') print(f'env: {worst_env}, tdr: {tdr}, tdf: {tdf}') breakpoint() ''' if tdr < tdf: iterator.down(tdr - tdf) else: iterator.up(tdr - tdf) err_abs = np.abs(tdr - tdf) if err_abs < err_best: err_best = err_abs iterator.save_info(pseg_off) pseg_off = iterator.get_last_save_info() pseg_off = 0 if pseg_off is None else pseg_off # Should only hit this case if inv_pseg_nom = 1 inv_pseg = inv_pseg_nom + pseg_off return inv_pseg, inv_nseg - 0 * pseg_off
async def _design_lvl_shift_inv_pdn(self, pseg: int, nseg: int, out_inv_m: int, fanout: float, pinfo: Any, tbm_specs: Dict[str, Any], has_rst, dual_output, vin, vout) -> int: """ This function figures out the NMOS nseg for the inverter given the target delay TODO: Make this use digitaldB instead """ min_fanout: float = get_tech_global_info('bag3_digital')['min_fanout'] inv_beta: float = get_tech_global_info('bag3_digital')['inv_beta'] tb_params = self._get_full_tb_params() # Use a binary iterator to find the NMOS size max_nseg = int(np.round(nseg / min_fanout)) iterator = BinaryIterator(1, max_nseg) load_seg = nseg + (pseg if has_rst else 0) inv_pseg = int( np.round(inv_beta * load_seg / ((1 + inv_beta) * fanout))) inv_pseg = 1 if inv_pseg == 0 else inv_pseg all_corners = get_tech_global_info( 'bag3_digital')['signoff_envs']['all_corners'] while iterator.has_next(): inv_nseg = iterator.get_next() inv_in_nseg, inv_in_pseg = self._size_input_inv_for_fanout( inv_pseg, inv_nseg, pseg, nseg, fanout, has_rst) dut_params = self._get_lvl_shift_params_dict( pinfo, pseg, nseg, inv_pseg, inv_nseg, inv_in_pseg, inv_in_nseg, out_inv_m, has_rst, dual_output) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) err_worst = -1 * float('Inf') for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd_in'] = all_corners[vin][env] tbm_specs['sim_params']['vdd'] = all_corners[vout][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_inv_nseg_{inv_nseg}_{env}', dut, tbm, tb_params) tdr_cur, tdf_cur = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'inb_buf', 'in_buf', True, in_pwr='vdd_in', out_pwr='vdd_in') target_cur, _ = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'inb_buf', 'midp', True, in_pwr='vdd_in', out_pwr='vdd') # Check for error conditions if math.isinf(np.max(tdr_cur)) or math.isinf( np.max(tdf_cur)) or math.isinf(np.max(target_cur)): raise ValueError( "Got infinite delay in level shifter design script (sizing inverter NMOS)." ) if np.min(tdr_cur) < 0 or np.min(target_cur) < 0: raise ValueError( "Got negative delay in level shifter design script (sizing inverter NMOS). " ) err_cur = tdr_cur[0] - target_cur[0] if err_cur > err_worst: err_worst = err_cur worst_env = env tdr = tdr_cur[0] target = target_cur[0] ''' print(f'iter: {inv_nseg}') print(f'env: {worst_env}, tdr: {tdr}, target: {target}') ''' if tdr < target: iterator.down(target - tdr) iterator.save_info(inv_nseg) else: iterator.up(target - tdr) tmp_inv_nseg = iterator.get_last_save_info() if tmp_inv_nseg is None: tmp_inv_nseg = max_nseg self.warn( "Could not size pull down of inverter to meet required delay, picked the " "max inv_nseg based on min_fanout.") return tmp_inv_nseg
def draw_layout(self): lch = self.params['lch'] ntap_w = self.params['ntap_w'] w = self.params['w'] fg_ref = self.params['fg_ref'] threshold = self.params['threshold'] out_xc_list = self.params['out_xc_list'] supply_tracks = self.params['supply_tracks'] em_specs = self.params['em_specs'] tr_widths = self.params['tr_widths'] tr_spaces = self.params['tr_spaces'] tot_width = self.params['tot_width'] guard_ring_nf = self.params['guard_ring_nf'] show_pins = self.params['show_pins'] # get AnalogBaseInfo hm_layer = self.mos_conn_layer + 1 ym_layer = hm_layer + 1 layout_info = AnalogBaseInfo(self.grid, lch, guard_ring_nf, top_layer=ym_layer, half_blk_y=False) # compute total number of fingers to achieve target width. bin_iter = BinaryIterator(2, None, step=2) while bin_iter.has_next(): fg_cur = bin_iter.get_next() w_cur = layout_info.get_placement_info(fg_cur).tot_width if w_cur < tot_width: bin_iter.save() bin_iter.up() elif w_cur > tot_width: bin_iter.down() else: bin_iter.save() break fg_tot = bin_iter.get_last_save() # find number of tracks needed for output tracks from EM specs hm_tr_w_out = self.grid.get_min_track_width(hm_layer, **em_specs) hm_tr_sp_out = self.grid.get_num_space_tracks(hm_layer, hm_tr_w_out, half_space=True) hm_w = self.grid.get_track_width(hm_layer, hm_tr_w_out, unit_mode=True) ym_tr_w = self.grid.get_min_track_width(ym_layer, bot_w=hm_w, **em_specs, unit_mode=True) # construct track width/space dictionary from EM specs tr_manager = TrackManager(self.grid, tr_widths, tr_spaces, half_space=True) tr_w_dict = { 'in': { hm_layer: tr_manager.get_width(hm_layer, 'in') }, 'out': { hm_layer: hm_tr_w_out, ym_tr_w: ym_tr_w }, } tr_sp_dict = { ('in', 'out'): { hm_layer: max(hm_tr_sp_out, tr_manager.get_space(hm_layer, ('in', 'out'))) }, } tr_manager = TrackManager(self.grid, tr_w_dict, tr_sp_dict, half_space=True) pw_list = [w, w, w] pth_list = [threshold, threshold, threshold] wire_names = dict( nch=[], pch=[ dict( ds=['out'], g=['in'], ), dict( g=['out'], ds=['out'], ds2=['out'], ), dict(g=['in'], ds=['out']), ], ) # draw transistor rows self.draw_base(lch, fg_tot, ntap_w, ntap_w, [], [], pw_list, pth_list, tr_manager=tr_manager, wire_names=wire_names, p_orientations=['MX', 'R0', 'R0'], guard_ring_nf=guard_ring_nf, pgr_w=ntap_w, ngr_w=ntap_w, top_layer=ym_layer, half_blk_y=False) outn_tid = self.get_wire_id('pch', 0, 'ds', wire_name='out') inp_tid = self.get_wire_id('pch', 0, 'g', wire_name='in') bias_tid = self.get_wire_id('pch', 1, 'g', wire_name='out') vdd_tid = self.get_wire_id('pch', 1, 'ds', wire_name='out') tail_tid = self.get_wire_id('pch', 1, 'ds2', wire_name='out') inn_tid = self.get_wire_id('pch', 2, 'g', wire_name='in') outp_tid = self.get_wire_id('pch', 2, 'ds', wire_name='out') out_pitch = out_xc_list[1] - out_xc_list[0] sd_pitch = layout_info.sd_pitch_unit if out_pitch % sd_pitch != 0: raise ValueError('Oops') fg = out_pitch // sd_pitch - fg_ref # draw transistors and connect inp_list = [] inn_list = [] tail_list = [] bias_list = [] vdd_m_list = [] outp_list = [] outn_list = [] layout_info = self.layout_info num_out = len(out_xc_list) for idx, xc in enumerate(out_xc_list): ym_idx = self.grid.coord_to_track(ym_layer, xc, unit_mode=True) vtid = TrackID(ym_layer, ym_idx, width=ym_tr_w) # find column index that centers on given track index x_coord = self.grid.track_to_coord(ym_layer, ym_idx, unit_mode=True) col_center = layout_info.coord_to_col(x_coord, unit_mode=True) col_idx = col_center - (fg // 2) # draw transistors if idx == 0: mref = self.draw_mos_conn('pch', 1, col_idx - fg_ref, fg_ref, 2, 0, d_net='ibias', diode_conn=True, gate_pref_loc='d') bias_list.append(mref['g']) bias_list.append(mref['d']) vdd_m_list.append(mref['s']) mtop = self.draw_mos_conn('pch', 2, col_idx, fg, 2, 0, s_net='ioutp', d_net='tail') mbot = self.draw_mos_conn('pch', 0, col_idx, fg, 0, 2, s_net='ioutn', d_net='tail') mtail = self.draw_mos_conn('pch', 1, col_idx, fg, 2, 0, gate_pref_loc='s', s_net='', d_net='tail') mref = self.draw_mos_conn('pch', 1, col_idx + fg, fg_ref, 2, 0, gate_pref_loc='d', diode_conn=True, d_net='ibias') # connect inp_list.append(mbot['g']) inn_list.append(mtop['g']) bias_list.append(mref['g']) bias_list.append(mref['d']) bias_list.append(mtail['g']) tail_list.append(mtop['d']) tail_list.append(mbot['d']) tail_list.append(mtail['d']) vdd_m_list.append(mtail['s']) vdd_m_list.append(mref['s']) outp_h = self.connect_to_tracks(mtop['s'], outp_tid) outp_list.append(outp_h) self.add_pin('ioutp', self.connect_to_tracks(outp_h, vtid), show=show_pins) outn_h = self.connect_to_tracks(mbot['s'], outn_tid) outn_list.append(outn_h) self.add_pin('ioutn', self.connect_to_tracks(outn_h, vtid), show=show_pins) self.connect_wires(outp_list) self.connect_wires(outn_list) self.add_pin('inp', self.connect_to_tracks(inp_list, inp_tid, track_lower=0, unit_mode=True), show=show_pins) self.add_pin('inn', self.connect_to_tracks(inn_list, inn_tid, track_lower=0, unit_mode=True), show=show_pins) ibias = self.connect_to_tracks(bias_list, bias_tid, track_lower=0, unit_mode=True) self.add_pin('ibias', ibias, show=show_pins) self.connect_to_tracks(tail_list, tail_tid, track_lower=ibias.lower_unit, track_upper=ibias.upper_unit, unit_mode=True) vdd_m = self.connect_to_tracks(vdd_m_list, vdd_tid) _, vdd_warrs = self.fill_dummy() vdd_warrs.append(vdd_m) right_tidx = 0 for tidx in supply_tracks: vtid = TrackID(ym_layer, tidx, width=ym_tr_w) right_tidx = max(right_tidx, tidx) self.add_pin('VDD', self.connect_to_tracks(vdd_warrs, vtid), show=show_pins) for xc in out_xc_list: tidx = self.grid.coord_to_track(ym_layer, xc, unit_mode=True) vtid = TrackID(ym_layer, tidx, width=ym_tr_w) self.add_pin('VDD', self.connect_to_tracks(vdd_m, vtid), show=show_pins) self.fill_box = bnd_box = self.bound_box for lay in range(1, self.top_layer): self.do_max_space_fill(lay, bnd_box, fill_pitch=3) self._sch_params = dict( lch=lch, w_dict={ 'in': w, 'tail': w }, th_dict={ 'in': threshold, 'tail': threshold }, seg_dict={ 'in': fg * num_out, 'tail': fg * num_out, 'ref': fg_ref * (num_out + 1) }, dum_info=self.get_sch_dummy_info(), ) ratio = fg * num_out / (fg_ref * (num_out + 1)) scale = num_out / ratio self._ibias_em_specs = em_specs.copy() for key in ['idc', 'iac_rms', 'iac_peak']: if key in em_specs: self._ibias_em_specs[key] *= scale
async def _design_lvl_shift_inv_pun(self, pseg: int, nseg: int, inv_nseg: int, out_inv_m: int, fanout: float, pinfo: Any, tbm_specs: Dict[str, Any], has_rst, dual_output, vin, vout) -> Tuple[int, int]: """ Given the NMOS pull down size, this function will design the PMOS pull up so that the delay mismatch is minimized. """ inv_beta = get_tech_global_info('bag3_digital')['inv_beta'] tb_params = self._get_full_tb_params() # Use a binary iterator to find the PMOS size load_seg = nseg + (pseg if has_rst else 0) inv_pseg_nom = int( np.round(inv_beta * load_seg / ((1 + inv_beta) * fanout))) inv_pseg_nom = 1 if inv_pseg_nom == 0 else inv_pseg_nom inv_nseg_nom = inv_nseg # save the value of nseg coming into this function as nominal range_to_vary = min(inv_pseg_nom, inv_nseg) iterator = BinaryIterator( -range_to_vary + 1, 1) # upper limit is exclusive hence using 1 instead of 0 # variation will be done such that the total P+N segments remains the same # This allows the input inverter sizing to be done once at the start # iterator = BinaryIterator(-inv_pseg_nom+1, 0) # iterator = BinaryIteratorInterval(get_tech_global_info('bag3_digital')['width_interval_list_p'], # -inv_pseg_nom+1, 0) err_best = float('inf') inv_in_nseg, inv_in_pseg = self._size_input_inv_for_fanout( inv_pseg_nom, inv_nseg, pseg, nseg, fanout, has_rst) all_corners = get_tech_global_info( 'bag3_digital')['signoff_envs']['all_corners'] while iterator.has_next(): pseg_off = iterator.get_next() inv_pseg = inv_pseg_nom + pseg_off inv_nseg = inv_nseg_nom - pseg_off dut_params = self._get_lvl_shift_params_dict( pinfo, pseg, nseg, inv_pseg, inv_nseg, inv_in_pseg, inv_in_nseg, out_inv_m, has_rst, dual_output) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) err_worst = -1 * float('Inf') for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd_in'] = all_corners[vin][env] tbm_specs['sim_params']['vdd'] = all_corners[vout][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_inv_pseg_{inv_pseg}_{env}', dut, tbm, tb_params) tdr_cur, tdf_cur = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') print("Balance rise/fall delays inv_pup Info: ", env, tdr_cur, tdf_cur) # Error checking if math.isinf(np.max(tdr_cur)) or math.isinf(np.max(tdf_cur)): raise ValueError("Got infinite delay!") if np.min(tdr_cur) < 0 or np.min(tdf_cur) < 0: raise ValueError("Got negative delay.") err_cur = np.abs(tdr_cur[0] - tdf_cur[0]) if err_cur > err_worst: err_worst = err_cur worst_env = env tdr = tdr_cur[0] tdf = tdf_cur[0] if tdr < tdf: iterator.down(tdr - tdf) else: iterator.up(tdr - tdf) err_abs = np.abs(tdr - tdf) if err_abs < err_best: err_best = err_abs iterator.save_info(pseg_off) pseg_off = iterator.get_last_save_info() pseg_off = 0 if pseg_off is None else pseg_off # Should only hit this case if inv_pseg_nom = 1 inv_pseg = inv_pseg_nom + pseg_off inv_nseg = inv_nseg_nom - pseg_off return inv_pseg, inv_nseg - 0 * pseg_off