def find_tail_bias(fun_ibiasn, nch_db, vtail, vgs_min, vgs_max, seg_tail_min, itarg): seg_tail_iter = BinaryIterator(seg_tail_min, None, step=2) while seg_tail_iter.has_next(): seg_tail = seg_tail_iter.get_next() def fun_zero(vgs): narg = nch_db.get_fun_arg(vgs=vgs, vds=vtail, vbs=0) return fun_ibiasn(narg) * seg_tail - itarg if fun_zero(vgs_min) > 0: # smallest possible current > itarg seg_tail_iter.down() if fun_zero(vgs_max) < 0: # largest possible current < itarg seg_tail_iter.up() else: vbias = sciopt.brentq(fun_zero, vgs_min, vgs_max) # type: float seg_tail_iter.save_info(vbias) seg_tail_iter.down() seg_tail = seg_tail_iter.get_last_save() vbias = seg_tail_iter.get_last_save_info() return seg_tail, vbias
async def _upsize_gate_for_del_spec( self, dut_params: Dict[str, Any], tspec: float, seg_cur: int, is_nand: bool, tbm: CombLogicTimingTB, seg_even: bool, spec_type: str, seg_max: Optional[int] = None, ) -> Tuple[int, np.ndarray, np.ndarray]: if spec_type != 'delay' and spec_type != 'slope': raise ValueError("spec_type must be either 'delay' or 'slope'.") bin_iter = BinaryIterator(seg_cur, seg_max, step=1 << seg_even) while bin_iter.has_next(): new_seg = bin_iter.get_next() dut_params['params'][ 'seg_nand' if is_nand else 'seg_nor'] = new_seg dut = await self.async_new_dut('nand_nor_upsize', STDCellWrapper, dut_params) sim_results = await self.async_simulate_tbm_obj( 'nand_nor_upsize_sim', dut, tbm, self._tb_params) if spec_type == 'slope': ans = CombLogicTimingTB.get_output_trf( sim_results.data, tbm.specs, 'nand_pu' if is_nand else 'nor_pd') gate_tr, gate_tf = ans else: ans = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'nand_pu' if is_nand else 'nor_pd', out_invert=True) gate_tf, gate_tr = ans trf_metric = gate_tf if is_nand else gate_tr if np.max(trf_metric) > tspec: bin_iter.up(np.max(trf_metric) - tspec) else: bin_iter.down(np.max(trf_metric) - tspec) bin_iter.save_info((new_seg, gate_tr, gate_tf)) info = bin_iter.get_last_save_info() if info is None: gate_str = "nand" if is_nand else "nor" err_str = f'Could not find a size for {gate_str} to meet the target spec of {tspec}.' self.error(err_str) seg, tr, tf = info return seg, tr, tf
def design_tail(specs, itarg, seg_min): """Find smallest tail transistor that biases the differential amplifier.""" db = specs['in_db'] sim_env = specs['sim_env'] vds = specs['vdst_min'] in_type = specs['in_type'] if in_type == 'pch': vds *= -1 ib_fun = db.get_function('ibias', env=sim_env) vgs_idx = db.get_fun_arg_index('vgs') vgs_min, vgs_max = ib_fun.get_input_range(vgs_idx) # binary search on number of fingers. seg_tail_iter = BinaryIterator(seg_min, None, step=2) while seg_tail_iter.has_next(): seg_tail = seg_tail_iter.get_next() def fun_zero(vgs): farg = db.get_fun_arg(vgs=vgs, vds=vds, vbs=0) return ib_fun(farg) * seg_tail - itarg val_min = fun_zero(vgs_min) val_max = fun_zero(vgs_max) if val_min > 0 and val_max > 0: # smallest possible current > itarg seg_tail_iter.down() elif val_min < 0 and val_max < 0: # largest possbile current < itarg seg_tail_iter.up() else: vbias = sciopt.brentq(fun_zero, vgs_min, vgs_max) # type: float seg_tail_iter.save_info(vbias) seg_tail_iter.down() seg_tail = seg_tail_iter.get_last_save() if seg_tail is None: raise ValueError('No solution for tail.') vgs_opt = seg_tail_iter.get_last_save_info() tail_op = db.query(vbs=0, vds=vds, vgs=vgs_opt) return seg_tail, tail_op
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 fill_symmetric_min_density_info(area: int, n_min: int, n_max: int, sp_min: int, area_specs: List[Tuple[int, int, int]], sp_max: Optional[int] = None, fill_on_edge: bool = True, cyclic: bool = False) -> FillInfo: """Fill the given 1-D area to satisfy minimum density constraint 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. 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. area_specs : List[Tuple[int, int, int]] list of area specifications, in (target, scale, extension) format. 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 : FillInfo the fill information object. """ # first, fill as much as possible using scale/extension of the first area spec. max_result = fill_symmetric_max_density_info(area, n_min, n_max, sp_min, area_specs, sp_max=sp_max, fill_on_edge=fill_on_edge, cyclic=cyclic) if not max_result.meet_area_specs(area_specs): # we cannot meet area spec; return max result return max_result # now, reduce fill by doing binary search on n_max nfill_opt = max_result.num_fill n_max_iter = BinaryIterator(n_min, n_max) while n_max_iter.has_next(): n_max_cur = n_max_iter.get_next() try: info = fill_symmetric_max_num_info(area, nfill_opt, n_min, n_max_cur, sp_min, fill_on_edge=fill_on_edge, cyclic=cyclic) if info.meet_area_specs(area_specs) and (sp_max is None or info.sp_max <= sp_max): # both specs passed n_max_iter.save_info(info) 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 else: max_result = last_save # see if we can further reduce fill by doing binary search on nfill_opt nfill_iter = BinaryIterator(1, nfill_opt) n_max = n_max_iter.get_last_save() while nfill_iter.has_next(): nfill_cur = nfill_iter.get_next() try: info = fill_symmetric_max_num_info(area, nfill_cur, n_min, n_max, sp_min, fill_on_edge=fill_on_edge, cyclic=cyclic) if info.meet_area_specs(area_specs) and (sp_max is None or info.sp_max <= sp_max): # both specs passed nfill_iter.save_info(info) nfill_iter.down() else: # reduce nfill too much nfill_iter.up() except ValueError: nfill_iter.up() last_save = nfill_iter.get_last_save_info() if last_save is None: return max_result # return new minimum solution return last_save
async def async_design(self, pinfo: Mapping[str, Any], nbits: int, rtol: float, atol: float, tbit: float, trf: float, cload: float, mc_params: Param, num_cores: int, target: Mapping[str, Any], delay_cell_params: Mapping[str, Any], **kwargs: Mapping[str, Any]) -> Mapping[str, Any]: td_min = target['td_min'] td_max = target['td_max'] t_max = target['t_max'] td_sigma = target['td_sigma'] tristate_seg = kwargs.get('tristate_seg', self.global_info['seg_min']) tristate_stack = kwargs.get('tristate_stack', 1) seg_buf_abs_max = kwargs.get('seg_buf_abs_max', 50) seg_buf_max_override = kwargs.get('seg_buf_max_override', None) seg_buf_min_override = kwargs.get('seg_buf_min_override', None) design_using_signoff = kwargs.get('design_using_signoff', False) mc_corner = kwargs.get('mc_corner', 'tt_25') mc_env_override = kwargs.get("mc_env_override", None) mc_worst_corner = kwargs.get("mc_worst_corner", True) plot_result: bool = kwargs.get('plot_result', False) dsn_monte_carlo: bool = kwargs.get('dsn_monte_carlo', True) gen_specs: Optional[Mapping[str, Any]] = kwargs.get('gen_cell_specs', None) gen_cell_args: Optional[Mapping[str, Any]] = kwargs.get( 'gen_cell_args', None) # 0. Setup design environments and the testbench manager if design_using_signoff: dsn_envs = self.global_info['signoff_envs'] dsn_env_names = dsn_envs['all_corners']['envs'] dsn_env_vdds = dsn_envs['all_corners']['vdd'] else: dsn_envs = self.global_info['dsn_envs'] dsn_env_names = [ env for dct in dsn_envs.values() for env in dct['env'] ] dsn_env_vdds = { e: dsn_envs[c]['vdd'] for c in dsn_envs.keys() for e in dsn_envs[c]['env'] } if not mc_worst_corner and not mc_env_override: raise ValueError( "If not performing mc on the worst corner, specify mc_env_override!" ) dut_pins = [ 'a_in', 'b_in', 'intout', 'VDD', 'VSS', f'sp<{nbits - 1}:0>', f'sn<{nbits - 1}:0>', 'a_in_buf' ] tbm_dict = {} for dsn_env in dsn_env_names: tbm_specs = self._get_tbm_specs( [dsn_env], dict(vdd={dsn_env: dsn_env_vdds[dsn_env]}), dut_pins, tbit, trf, cload, nbits, rtol, atol) tbm = cast(DigitalTranTB, self.make_tbm(DigitalTranTB, tbm_specs)) tbm_dict[dsn_env] = tbm tbm_params = dict() # 1. Setup the base phase interpolator and extract the input cap pwr_domains = dict(b_in=('VSS', 'VDD'), a_in=('VSS', 'VDD'), out=('VSS', 'VDD')) cin_dut_conns = dict(a_in=1) for i in range(nbits): for name, value in zip(['a_en', 'b_en', 'a_enb', 'b_enb'], [1, 0, 0, 1]): cin_dut_conns[f'{name}<{i}>'] = value pwr_domains[f'{name}<{i}>'] = ('VSS', 'VDD') gen_params = dict(cls_name=PhaseInterpolator.get_qualified_name(), params=dict( pinfo=pinfo, unit_params={ 'seg': tristate_seg, 'stack_p': tristate_stack, 'stack_n': tristate_stack }, inv_params={ 'seg': 2, 'stack_p': 1, 'stack_n': 1 }, nbits=nbits, draw_sub=True, export_outb=True, abut_tristates=True, )) pi_in_cap = await self._get_input_cap( gen_params, 'b_in', cload, cin_dut_conns, pwr_domains, [dict(pin='out', type='cap', value='c_out')], vdd=dsn_env_vdds['tt_25'], sim_envs=['tt_25']) # 2. Setup the base delay cell and extract it's input cap pin_names = ['bk1', 'ci_p', 'co_p', 'in_p', 'out_p'] pwr_domains = {pin_name: ('VSS', 'VDD') for pin_name in pin_names} cin_dut_conns = dict(bk1=1, ci_p=0) load_list = [ dict(pin='out_p', type='cap', value='c_out'), dict(pin='co_p', type='cap', value='c_out') ] gen_params = dict(cls_name=DelayCellCore.get_qualified_name(), params=dict(pinfo=pinfo, stand_alone=True, **delay_cell_params)) dc_in_cap = await self._get_input_cap(gen_params, 'ci_p', cload, cin_dut_conns, pwr_domains, load_list, vdd=dsn_env_vdds['tt_25'], sim_envs=['tt_25']) # 3. Size the delay cell to be able to drive the phase interpolator dc_up_scale_factor = int(round(pi_in_cap / dc_in_cap)) delay_cell_params['seg_dict']['in'] *= dc_up_scale_factor delay_cell_params['seg_dict']['out'] *= dc_up_scale_factor delay_cell_params['seg_dict']['sr'] *= dc_up_scale_factor nand_seg = delay_cell_params['seg_dict']['out'] * 2 inv_in_cap = self.global_info['cin_inv']['cin_per_seg'] inv_seg = int(round(np.sqrt(dc_in_cap * pi_in_cap) / inv_in_cap)) # 4. Upsize the buffer inverter on the output self.log('-' * 80) if seg_buf_min_override: self.log(f"Minimum Segments Overridden to {seg_buf_min_override}") min_seg_buf = seg_buf_min_override else: self.log('Find the min size for all the codes to be positive') seg_buf_min_iter = BinaryIterator(2, None, 2) while seg_buf_min_iter.has_next(): _seg_buf = seg_buf_min_iter.get_next() dut_params = self._update_dut_params( pinfo, nbits, tristate_seg, seg_buf=_seg_buf, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, name=f'sim_min_{_seg_buf}') # find min and max delay step tstep_min = results['min_step'] tstep_max = results['max_step'] self.log( f"Got min delay {tstep_min}, max delay {tstep_max}, with " f"{_seg_buf} segments") if tstep_min < 0: seg_buf_min_iter.up() else: seg_buf_min_iter.save() seg_buf_min_iter.down() min_seg_buf = seg_buf_min_iter.get_last_save() self.log('-' * 80) if seg_buf_max_override: self.log(f'Maximum Segments Overridden to {seg_buf_max_override}') max_seg_buf = seg_buf_max_override else: self.log( 'Now find the maximum size for all the codes to be positive') seg_buf_max_iter = BinaryIterator(10, None, 2) max_reached = False while seg_buf_max_iter.has_next(): _seg_buf = seg_buf_max_iter.get_next() dut_params = self._update_dut_params( pinfo, nbits, tristate_seg, seg_buf=_seg_buf, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, name=f'sim_max_{_seg_buf}') # find min and max delay step tstep_min = results['min_step'] tstep_max = results['max_step'] self.log( f"Got min delay {tstep_min}, max delay {tstep_max}, with " f"{_seg_buf} segments") if tstep_min < 0: seg_buf_max_iter.down() elif _seg_buf > seg_buf_abs_max: max_reached = True break else: seg_buf_max_iter.save() seg_buf_max_iter.up() max_seg_buf = seg_buf_max_iter.get_last_save( ) if not max_reached else seg_buf_abs_max self.log('-' * 80) self.log( f'Minimum Buffer segments to keep positive delays: {min_seg_buf}') self.log( f'Maximum Buffer segments to keep positive delays: {max_seg_buf}') seg_buf_bin_iter = BinaryIterator(min_seg_buf, max_seg_buf, 2) while seg_buf_bin_iter.has_next(): _seg_buf = seg_buf_bin_iter.get_next() dut_params = self._update_dut_params(pinfo, nbits, tristate_seg, seg_buf=_seg_buf, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, 'sim_size') tdelay_max = results['max_dly'] tstep_min = results['min_step'] tstep_max = results['max_step'] if tdelay_max > t_max and tstep_min > td_min and tstep_max < td_max: # delay constraint violated, linearity constraint met seg_buf_bin_iter.down() elif tdelay_max < t_max and (tstep_min < td_min or tstep_max > td_max): # delay constraint met, linearity constraint violated seg_buf_bin_iter.up() elif tdelay_max < t_max and tstep_min > td_min and tstep_max < td_max: # both constraints met seg_buf_bin_iter.save_info((dut_params, results)) seg_buf_bin_iter.down() else: self.error( 'Both delay and linearity constraints violated, please relax specs.' ) seg_buf_final = seg_buf_bin_iter.get_last_save() if not seg_buf_final: self.error( "Design failed!, unable to meet linearity specs within range of inv sizes" ) self.log( f'Final output buffer size is {seg_buf_final}, before Monte Carlo sim.' ) dut_params, results = seg_buf_bin_iter.get_last_save_info() if dsn_monte_carlo: # 5. Monte Carlo simulations mc_tbm_dict = {} if mc_worst_corner: mc_envs = [mc_corner] mc_vdd = dict(vdd={mc_corner: dsn_env_vdds[mc_corner]}) mc_tbm_specs = self._get_tbm_specs([mc_corner], mc_vdd, dut_pins, tbit, trf, cload, nbits, rtol, atol) mc_tbm_specs['monte_carlo_params'] = mc_params mc_tbm = cast(DigitalTranTB, self.make_tbm(DigitalTranTB, mc_tbm_specs)) mc_tbm_dict[mc_corner] = mc_tbm else: # TODO mc_envs = ... ... dut_params = self._update_dut_params(pinfo, nbits, tristate_seg, seg_buf=seg_buf_final, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params) mc_results = await self._measure_times(mc_envs, mc_tbm_dict, dut_params, tbm_params, tbit, nbits, name='sim_mc_pre') mc_factor, sigma_max = self._get_mc_factor(mc_results, td_sigma) self.log(f'Max std. dev. is {sigma_max}') self.log(f'Upscale everything by {mc_factor}') self.log('-' * 80) # 6. Final verification seg_unit_final = int(np.ceil(tristate_seg * mc_factor)) seg_unit_final += seg_unit_final & 1 # layout constraint seg_buf_final = int(np.ceil(seg_buf_final * mc_factor)) seg_buf_final += seg_buf_final & 1 # layout constraint delay_cell_params_scale = copy.deepcopy(delay_cell_params) for key in delay_cell_params['seg_dict']: delay_cell_params_scale['seg_dict'][key] = int( np.ceil(delay_cell_params['seg_dict'][key] * mc_factor)) nand_seg = int(np.ceil(nand_seg * mc_factor)) inv_seg = int(np.ceil(inv_seg * mc_factor)) dut_params = self._update_dut_params( pinfo, nbits, seg_unit_final, seg_buf=seg_buf_final, seg_inv=inv_seg, seg_nand=nand_seg, num_cores=num_cores, dc_params=delay_cell_params_scale) results = await self._measure_times(dsn_env_names, tbm_dict, dut_params, tbm_params, tbit, nbits, name='sim_sized') mc_results = await self._measure_times(mc_envs, mc_tbm_dict, dut_params, tbm_params, tbit, nbits, name='sim_mc_post') _, sigma_max = self._get_mc_factor(mc_results, td_sigma) self.log(f'Final Sigma: {sigma_max}') self.log('-' * 80) else: seg_unit_final = tristate_seg delay_cell_params_scale = delay_cell_params self.log('-' * 80) self.log(f'dsn_envs: {dsn_env_names}') self.log(f'final results:\n{pprint.pformat(results, width=100)}') if plot_result: from matplotlib import pyplot as plt plt.figure(1) ax: Any = plt.subplot(2, 1, 1) xvec = np.arange(0, results['tdr_step'].shape[1]) for idx, sim_env in enumerate(dsn_env_names): tdr = results['tdrs'][idx, :-1].flatten() plt.step(xvec, tdr, where='mid', label=sim_env) ax.legend() ax.set_ylabel('Rise Delay (s)') ax = plt.subplot(2, 1, 2) for idx, sim_env in enumerate(dsn_env_names): tdr_step = results['tdr_step'][idx, :].flatten() ax.scatter(xvec, tdr_step, label=sim_env) ax.set_ylim(ymin=td_min, ymax=td_max) ax.legend() ax.set_ylabel('Rise Delay Step (s)') ax.set_xlabel('Code') plt.show() if gen_specs is not None and gen_cell_args is not None: gen_cell_specs = dict( lay_class=IPMarginTemplate.get_qualified_name(), params=dict( cls_name=GenericWrapper.get_qualified_name(), params=dict( cls_name=PhaseInterpolatorWithDelay.get_qualified_name( ), params=dut_params, ), ), **gen_specs, ) return dict(gen_specs=gen_cell_specs, gen_args=gen_cell_args) return dict( seg_unit=seg_unit_final, seg_buf=seg_buf_final, seg_dc=delay_cell_params_scale['seg_dict'], nand_seg=nand_seg, inv_seg=inv_seg, )
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 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 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'] half_blk_y = self.params['half_blk_y'] half_blk_x = self.params['half_blk_x'] 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, half_blk_y=half_blk_y, half_blk_x=half_blk_x) # 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 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
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
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