def _get_unit_cell_params(self, pinfo: Any, seg_p: int, seg_n: int, seg_nand: int, seg_nor: int, nand_p_w_del: int, nor_n_w_del: int, beta_ratio: Optional[float] = None, w_min: Optional[int] = None) -> Dict[str, Any]: if w_min is None: w_n = get_tech_global_info('aib_ams')['w_minn'] else: w_n = w_min if beta_ratio is None: beta_ratio = get_tech_global_info('aib_ams')['inv_beta'] return dict(cls_name='aib_ams.layout.driver.OutputDriverCore', draw_taps=True, params=dict( pinfo=pinfo, seg_p=seg_p, seg_n=seg_n, seg_nand=seg_nand, seg_nor=seg_nor, w_p=self.out_w_p, w_n=self.out_w_n, w_p_nand=int(beta_ratio * w_n) + nand_p_w_del, w_n_nand=w_n, w_p_nor=int(beta_ratio * w_n), w_n_nor=w_n + nor_n_w_del, export_pins=True, ))
def _size_input_inv_for_fanout(inv_pseg: int, inv_nseg: int, pseg: int, nseg: int, fanout: float, has_rst: bool) -> Tuple[int, int]: beta = get_tech_global_info('bag3_digital')['inv_beta'] seg_load = inv_pseg + inv_nseg + nseg + (pseg if has_rst else 0) iinv_nseg = int(np.round(seg_load / (1 + beta) / fanout)) iinv_nseg = 1 if iinv_nseg < get_tech_global_info( 'bag3_digital')['seg_min'] else iinv_nseg iinv_pseg = int(np.round(seg_load * beta / (1 + beta) / fanout)) iinv_pseg = 1 if iinv_pseg < get_tech_global_info( 'bag3_digital')['seg_min'] else iinv_pseg return iinv_nseg, iinv_pseg
async def verify_design(self, dut_params: Mapping[str, Any]) -> Mapping[str, Any]: dut = await self.async_new_dut('phase_det', GenericWrapper, dut_params, flat=True) offset_netlist = Path(*dut.netlist_path.parts[:-1], 'netlist_with_offsets.scs') v_offset_map = add_mismatch_offsets(dut.netlist_path, offset_netlist, self._sim_db._sim.netlist_type) designed_dut_with_offsets = DesignInstance(dut.cell_name, dut.sch_master, dut.lay_master, offset_netlist, dut.cv_info_list) seg_dict: Mapping[str, Any] = dut_params['params']['flop_params']['sa_params']['seg_dict'] global_params = get_tech_global_info("aib_ams") a_vt_per_fin = global_params['A_vt_fin_n'] seg_in = seg_dict['in'] seg_tail = seg_dict['tail'] offset_tail = 3*(a_vt_per_fin/seg_tail)**(1/2) offset_in = 3*(a_vt_per_fin/seg_in)**(1/2) strong_arm_offsets = dict( XFLOPD_XSA_XTAIL=-offset_tail, XFLOPU_XSA_XTAIL=-offset_tail, XFLOPD_XSA_XINP=offset_in, XFLOPD_XSA_XINN=-offset_in, XFLOPU_XSA_XINP=offset_in, XFLOPU_XSA_XINN=-offset_in, ) for mos_name, voff_name in v_offset_map.items(): found = False for base_name, offset in strong_arm_offsets.items(): if mos_name.startswith(base_name): self._phase_det_mm_params['tbm_specs']['sim_params'][voff_name] = offset found = True if not found: self._phase_det_mm_params['tbm_specs']['sim_params'][voff_name] = 0 mm = self.make_mm(PhaseDetMeasManager, self._phase_det_mm_params) results = await self.async_simulate_mm_obj(f'phase_det_timing_with_offset', designed_dut_with_offsets, mm) return results.data
def _build_env_vars(env_str: str, vin: str, vout: str) -> Tuple[List[str], float, float]: dsn_env_info = get_tech_global_info( 'bag3_digital')['dsn_envs'][env_str] design_sim_env = dsn_env_info['env'] vdd_in = dsn_env_info[vin] vdd_out = dsn_env_info[vout] return design_sim_env, vdd_in, vdd_out
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.global_info = get_tech_global_info('aib_ams') lch = self.global_info['lch_min'] w_p = self.global_info['w_minp'] w_n = self.global_info['w_minn'] th = self.global_info['thresholds'][0] self._tech_dsn_base_params = dict(lch=lch, w_p=w_p, w_n=w_n, th_p=th, th_n=th)
async def _get_resistance( self, sim_id: str, dut_params: Mapping[str, Any], mm_specs: Dict[str, Any]) -> Tuple[np.ndarray, np.ndarray]: """Compute resistance from DC I-V measurements. """ gen_params = dict( cls_name=PullUpDown.get_qualified_name(), draw_taps=True, params=dut_params, ) dut = await self.async_new_dut('unit_cell', STDCellWrapper, gen_params, flat=True) extract = self._sim_db.extract netlist_type = DesignOutput.CDL if extract else self._sim_db.prj.sim_access.netlist_type # Create a new netlist that allows for the insertion of dc sources offset_netlist = Path( *dut.netlist_path.parts[:-1], f'netlist_with_sources.{netlist_type.extension}') v_offset_map = add_internal_sources(dut.netlist_path, offset_netlist, netlist_type, ['d']) mm_specs['v_offset_map'] = v_offset_map mm_specs['extract'] = extract # Create a DesignInstance with the newly created netlist new_dut = DesignInstance(dut.cell_name, dut.sch_master, dut.lay_master, offset_netlist, dut.cv_info_list) all_corners = get_tech_global_info( 'aib_ams')['signoff_envs']['all_corners'] res_pd = np.array([]) res_pu = np.array([]) helper = GatherHelper() for env in all_corners['envs']: helper.append( self._run_get_resistance(env, sim_id, new_dut, mm_specs, all_corners)) results = await helper.gather_err() for idx in range(len(results)): res_pu = np.append(res_pu, results[idx][0]) res_pd = np.append(res_pd, results[idx][1]) return res_pu, res_pd
def _get_lvl_shift_core_params_dict( pinfo: Any, seg_p: int, seg_n: int, has_rst: bool, is_ctrl: bool = False) -> Dict[str, Any]: """ Creates a dictionary of parameters for the layout class LevelShifterCore seg_n : nmos Pull down nseg seg_p : pmos Pull up nseg pinfo : pinfo Note: This will let the width be passed through the pinfo, currently no rst """ global_info = get_tech_global_info('bag3_digital') wn = global_info['w_minn'] if is_ctrl else 2 * global_info['w_minn'] wp = global_info['w_minp'] if is_ctrl else 2 * global_info['w_minp'] if has_rst: seg_dict = dict(pd=seg_n, pu=seg_p, rst=int(np.ceil(seg_n / 2)), prst=seg_p) w_dict = dict(pd=wn, pu=wp, rst=wn) else: seg_dict = dict(pd=seg_n, pu=seg_p) w_dict = dict(pd=wn, pu=wp) lv_params = dict(cls_name=LevelShifterCore.get_qualified_name(), draw_taps=True, params=dict( pinfo=pinfo, seg_dict=seg_dict, w_dict=w_dict, has_rst=has_rst, in_upper=has_rst, )) if has_rst: lv_params['params']['lv_params']['stack_p'] = 2 return lv_params
def _design_lvl_shift_core_size(cload: float, k_ratio: float, inv_input_cap: float, fanout: float, is_ctrl: bool) -> Tuple[int, int, int]: """ Size the core of the LVL Shifter given K_ratio, the ratio of the NMOS to PMOS """ out_inv_input_cap = cload / fanout print(f'cload = {cload}') inv_m = int(round(out_inv_input_cap / inv_input_cap)) inv_m = max(1, inv_m) beta = get_tech_global_info('bag3_digital')['inv_beta'] # pseg = int(round(2*inv_m / fanout)) # total equivalent inv load is inv_m so the PMOS size should be inv_m/fanout times beta pseg = int(round(beta * inv_m / fanout)) pseg = max(1, pseg) if pseg == 1 and not is_ctrl: print("=" * 80) print( "WARNING: LvShift Designer: pseg has been set to 1; might want to remove output inverter." ) print("=" * 80) nseg = int(np.round(pseg * k_ratio)) return inv_m, pseg, nseg
async def async_design(self, num_units: int, num_units_nom: int, num_units_min: int, trf_max: float, r_targ: float, r_min_weak: float, c_ext: float, freq: float, trf_in: float, rel_err: float, del_err: float, tile_name: str, tile_specs: Mapping[str, Any], res_mm_specs: Dict[str, Any], ridx_p: int = 1, ridx_n: int = 1, stack_max: int = 10, tran_options: Optional[Mapping[str, Any]] = None, max_iter: int = 10, **kwargs: Any) -> Mapping[str, Any]: """ Design the Output Driver Cell 1) Calls functions to get initial design for main driver unit cell and weak driver 2) Characterize design 3) Iterate on main driver design to account for additional loading from other unit cells Parameters ---------- num_units: int Number of unit cells. Must be between 3 and 6 num_units_nom: int ??? num_units_min: int Min. number of unit cells trf_max: float Max. output rise / fall time r_targ: float Target output resistance r_min_weak: float Target output resistance for weak driver c_ext: float Load capacitance freq: float Operating switching frequency trf_in: float Input rise / fall time rel_err: float Output resistance error tolerance, used in DriverPullUpDownDesigner del_err: float Delay mismatch tolerance, used in DriverUnitCellDesigner for sizing NAND + NOR tile_name: str Tile name for layout. tile_specs: Mapping[str, Any] Tile specifications for layout. res_mm_specs: Mapping[str, Any] MeasurementManager specs for DriverPullUpDownMM, used in DriverPullUpDownDesigner ridx_n: int NMOS transistor row ridx_p: int PMOS transistor row stack_max: int Max. number of stacks possible in pull up / pull down driver tran_options: Optional[Mapping[str, Any]] Additional transient simulation options dictionary, used in DriverPullUpDownDesigner max_iter: int Max. number of iterations for final main driver resizing kwargs: Any Additional keyword arguments. Unused here Returns ------- ans: Mapping[str, Any] Design summary, including generator parameters and performance summary """ tech_info = get_tech_global_info('aib_ams') w_p = tech_info['w_maxp'] w_n = tech_info['w_maxn'] tile_specs['place_info'][tile_name]['row_specs'][0]['width'] = w_n tile_specs['place_info'][tile_name]['row_specs'][1]['width'] = w_p self._dsn_specs['tile_specs']['place_info'][tile_name]['row_specs'][0][ 'width'] = w_n self._dsn_specs['tile_specs']['place_info'][tile_name]['row_specs'][1][ 'width'] = w_p core_params = dict( r_targ=r_targ * num_units, stack_max=stack_max, trf_max=trf_max, c_min=c_ext / num_units, c_max=c_ext / num_units_min, w_p=w_p, w_n=w_n, freq=freq, vdd=tech_info['dsn_envs']['slow_io']['vddio'], vdd_max=tech_info['signoff_envs']['vmax']['vddio'], trf_in=trf_in, rel_err=rel_err, del_err=del_err, tile_specs=tile_specs, tile_name=tile_name, tran_options=tran_options, res_mm_specs=res_mm_specs, ) weak_params = core_params.copy() weak_params['w_p'] = tech_info['w_minp'] weak_params['w_n'] = tech_info['w_minn'] weak_params['r_targ'] = r_min_weak weak_params['is_weak'] = True # Design weak pull-up/pull-down weak_designer = DriverPullUpDownDesigner(self._root_dir, self._sim_db, self._dsn_specs) weak_designer.set_dsn_specs(weak_params) weak_results = await weak_designer.async_design(**weak_params) # Design main output driver core_designer = DriverUnitCellDesigner(self._root_dir, self._sim_db, self._dsn_specs) core_designer.set_dsn_specs(core_params) summary = await core_designer.async_design(**core_params) # Characterize the initial design result_params = summary['dut_params']['params'] tinfo_table = TileInfoTable.make_tiles(self.grid, tile_specs) pinfo = tinfo_table[tile_name] dut_params = dict(cls_name='aib_ams.layout.driver.AIBOutputDriver', draw_taps=True, params=dict( pinfo=pinfo, pupd_params=dict( seg_p=weak_results['seg_p'], seg_n=weak_results['seg_n'], stack=weak_results['stack'], w_p=weak_results['w_p'], w_n=weak_results['w_n'], ), unit_params=dict( seg_p=result_params['seg_p'], seg_n=result_params['seg_n'], seg_nand=result_params['seg_nand'], seg_nor=result_params['seg_nor'], w_p=result_params['w_p'], w_n=result_params['w_n'], w_p_nand=result_params['w_p_nand'], w_n_nand=result_params['w_n_nand'], w_p_nor=result_params['w_p_nor'], w_n_nor=result_params['w_n_nor']), export_pins=True, )) tbm_specs: Dict[str, Any] = dict(thres_lo=0.1, thres_hi=0.9, tstep=None, sim_params=dict( cload=c_ext, tbit=20 * r_targ * c_ext, trf=trf_in, ), rtol=1e-8, atol=1e-22, tran_options=tran_options, save_outputs=['out', 'in']) if num_units == 3: n_enb_str = 'VDD' p_en_str = 'VSS' elif num_units == 4: n_enb_str = 'VDD,VSS' p_en_str = 'VSS,VDD' elif num_units == 5: n_enb_str = 'VSS,VDD' p_en_str = 'VDD,VSS' elif num_units == 6: n_enb_str = 'VSS,VSS' p_en_str = 'VDD,VDD' else: raise ValueError('Number of units must be between 3 and 6.') tb_params = dict(load_list=[('out', 'cload')], dut_conns={ 'txpadout': 'out', 'din': 'in', 'n_enb_drv<1:0>': n_enb_str, 'p_en_drv<1:0>': p_en_str, 'tristate': 'VSS', 'tristateb': 'VDD', 'weak_pden': 'VSS', 'weak_puenb': 'VDD' }) all_corners = tech_info['signoff_envs']['all_corners'] trf_worst = -float('inf') tdr_worst = -float('inf') tdf_worst = -float('inf') tf_worst = -float('inf') tr_worst = -float('inf') worst_env = '' duty_err_worst = 0 dut = await self.async_new_dut('output_driver', STDCellWrapper, dut_params) for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd'] = all_corners['vddio'][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_final_{env}', dut, tbm, tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay( sim_results.data, tbm_specs, 'in', 'out', False) if tdr[0] > tdr_worst: tdr_worst = tdr[0] if tdf[0] > tdf_worst: tdf_worst = tdf[0] duty_err = tdr[0] - tdf[0] if np.abs(duty_err) > np.abs(duty_err_worst): duty_err_worst = duty_err tr, tf = CombLogicTimingTB.get_output_trf(sim_results.data, tbm_specs, 'out') trf = max(np.max(tr), np.max(tf)) if trf > trf_worst: trf_worst = trf tr_worst = tr[0] tf_worst = tf[0] worst_env = env self.logger.info('---') self.logger.info("Completed initial design.") self.logger.info(f'Target R per segment was: {core_params["r_targ"]}') self.logger.info( f'Worst trf was: {trf_worst} / rise: {tr_worst}, fall: {tf_worst}' + f'in corner {worst_env}') self.logger.info(f'Worst delay was: {max(tdr_worst, tdf_worst)}') self.logger.info(f'Worst duty cycle error was: {duty_err_worst}') self.logger.info('---') # Loop on trf to take into account loading from unit cells that are nominally off # but not included in single unit-cell design script. for i in range(1, max_iter + 1): scale_error = trf_worst / trf_max if scale_error < 1: break core_params['r_targ'] = core_params['r_targ'] / scale_error * ( 1 - 1 / (2 * max_iter)) self.logger.info(f'Scale error set to {scale_error}.') self.logger.info( f'New target R per segment set to {core_params["r_targ"]}') core_designer.set_dsn_specs(core_params) # TODO: Allow designer to start from previous design point and/or guess at scaled # design (improve runtime) summary = await core_designer.async_design(**core_params) result_params = summary['dut_params']['params'] dut_params['params']['unit_params'] = dict( seg_p=result_params['seg_p'], seg_n=result_params['seg_n'], seg_nand=result_params['seg_nand'], seg_nor=result_params['seg_nor'], w_p=result_params['w_p'], w_n=result_params['w_n'], w_p_nand=result_params['w_p_nand'], w_n_nand=result_params['w_n_nand'], w_p_nor=result_params['w_p_nor'], w_n_nor=result_params['w_n_nor'], ) tbm_specs['sim_params']['tbit'] = 1 / freq # tbm_specs['sim_params']['tbit'] = 20 * core_params['r_targ'] * c_ext trf_worst = -float('inf') tdr_worst = -float('inf') tdf_worst = -float('inf') duty_err_worst = 0 worst_env = '' duty_env = '' dut = await self.async_new_dut('output_driver', STDCellWrapper, dut_params) for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd'] = all_corners['vddio'][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_final_{i}_{env}', dut, tbm, tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay( sim_results.data, tbm_specs, 'in', 'out', False) if tdr[0] > tdr_worst: tdr_worst = tdr[0] if tdf[0] > tdf_worst: tdf_worst = tdf[0] duty_err = tdr[0] - tdf[0] if np.abs(duty_err) > np.abs(duty_err_worst): duty_err_worst = duty_err duty_env = env tr, tf = CombLogicTimingTB.get_output_trf( sim_results.data, tbm_specs, 'out') trf = max(np.max(tr), np.max(tf)) if trf > trf_worst: trf_worst = trf tr_worst = tr[0] tf_worst = tf[0] worst_env = env self.logger.info('---') self.logger.info(f'Completed design iteration {i}.') self.logger.info( f'Worst trf was: {trf_worst} / rise: {tr_worst}, fall: {tf_worst}' + f'in corner {worst_env}') self.logger.info( f'Worst delay was: {max(tdr_worst, tdf_worst)} in corner {worst_env}' ) self.logger.info( f'Worst duty cycle error was: {duty_err_worst} in corner {duty_env}' ) self.logger.info('') return dict(dut_params=dut_params, tdr=tdr_worst, tdf=tdf_worst, trf_worst=trf_worst, duty_err=duty_err_worst)
async def _resize_transistors(self, dut_params: Dict[str, Any], mm_specs: Dict[str, Any], r_targ: float, r_pu: np.ndarray, r_pd: np.ndarray, seg_min: int, seg_even: bool, rel_err: float, max_iter: int = 4, is_weak: Optional[bool] = False, seg_p: int = 0, seg_n: int = 0, seg_max: int = 20) -> Dict[str, Any]: """Iteratively searches transistor segments and width to hit target r_targ output resistance. If no result is found up to seg_max, width is adjusted, and then this function is recursively called. Parameters ---------- dut_params: Mapping[str, Any] Driver generator parameters mm_specs: Dict[str, Any] Specs for DriverPullUpDownMM r_targ: float Target output resistance r_pu: np.ndarray Measured pull-up output resistance across given corners, from previous search r_pd: np.ndarray Measured pull-down output resistance across given corners, from previous search seg_min: int Min. allowed segments seg_even: bool True to force number of segments to be even rel_err: float Output resistance error tolerance max_iter: int Maximum allowed number of iteration is_weak: Optional[bool] Deprecated: True if sizing weak driver seg_p: int If given, used as initial number of PMOS segments instead of seg_min seg_n: int If given, used as initial number of NMOS segments instead of seg_min seg_max: int Max. allowed segments Returns ------- ans: Mapping[str, Any] Design summary, including generator parameters and performance summary """ seg_p = seg_min if seg_p == 0 else seg_p seg_n = seg_min if seg_n == 0 else seg_n seg_p_best = seg_p seg_n_best = seg_n err_best_p = float('inf') err_best_n = float('inf') visited = set() cnt = 0 r_pu_best = float('inf') r_pd_best = float('inf') for cnt in range(seg_max): if np.max(r_pu) > (1 + rel_err) * r_targ: seg_p += 1 elif np.max(r_pu) < (1 - rel_err) * r_targ and seg_p >= 2: seg_p -= 1 if np.max(r_pd) > (1 + rel_err) * r_targ: seg_n += 1 elif np.max(r_pd) < (1 - rel_err) * r_targ and seg_n >= 2: seg_n -= 1 new_tuple = (seg_p, seg_n) if new_tuple in visited: break else: visited.add(new_tuple) dut_params['seg_p'] = seg_p dut_params['seg_n'] = seg_n r_pu, r_pd = await self._get_resistance(f'resize_{cnt}', dut_params, mm_specs) err_p = np.abs(np.max(r_pu) - r_targ) / r_targ err_n = np.abs(np.max(r_pd) - r_targ) / r_targ self.log(f'Iter = {cnt}, err_p={err_p:.4g}, err_n={err_n:.4g}.') self.log(f'Iter = {cnt}, seg_p={seg_p:.4g}, seg_n={seg_n:.4g}.') if err_p < err_best_p: err_best_p = err_p seg_p_best = seg_p r_pu_best = np.max(r_pu) if err_n < err_best_n: err_best_n = err_n seg_n_best = seg_n r_pd_best = np.max(r_pd) if err_p <= rel_err and err_n <= rel_err: break if cnt == max_iter - 1 or err_best_n > rel_err or err_best_p > rel_err: tech_globals = get_tech_global_info('aib_ams') dut_params['seg_p'] = seg_p_best dut_params['seg_n'] = seg_n_best # TODO: below code needs to be updated to reflect width selection if err_best_p > rel_err: dut_params['w_p'] = dut_params['w_p'] - 1 if err_best_n > rel_err: dut_params['w_n'] = dut_params['w_n'] - 1 if dut_params['w_p'] >= tech_globals['w_minp'] and \ dut_params['w_n'] >= tech_globals['w_minn']: msg = f'Recursing through resize transistors with w_p: {dut_params["w_p"]} and ' \ f'w_n: {dut_params["w_n"]}' self.log(msg) r_pu, r_pd = await self._get_resistance( f'get_res_for_new_w', dut_params, mm_specs) return await self._resize_transistors(dut_params, mm_specs, r_targ, r_pu, r_pd, seg_min, seg_even, rel_err, max_iter, is_weak, seg_p_best, seg_n_best) else: msg = f'Unable to find sizing for output driver ' + \ f'that meets {rel_err*100:.2f}% error.' raise RuntimeError(msg) return dict( stack=dut_params['stack'], r_targ=r_targ, r_pu=r_pu_best, r_pd=r_pd_best, w_p=dut_params['w_p'], w_n=dut_params['w_n'], seg_p=seg_p_best, seg_n=seg_n_best, err_p=err_best_p, err_n=err_best_n, )
async def async_design(self, c_max: float, trf_in: float, w_p: int, w_n: int, r_targ: float, tile_specs: Mapping[str, Any], del_err: float, tile_name: str, seg_even: Optional[bool] = False, **kwargs: Any) -> Mapping[str, Any]: """This function designs the main output driver unit cell 1) Calls DriverPullUpDownDesigner to design output pull up / pull down 2) Design input NAND and NOR 3) Characterize and sign off Parameters ---------- c_max: float Target load capacitance trf_in: float Input rise / fall time in simulation w_p: int Initial output PMOS width w_n: int Initial output NMOS width r_targ: float: Target nominal (strong) output resistance. tile_name: str Tile name for layout. del_err: float Delay mismatch tolerance, for sizing NAND + NOR tile_specs: Mapping[str, Any] Tile specifications for layout. seg_even: Optional[bool] True to force segments to be even kwargs: Any Additional keyword arguments. Unused here Returns ------- ans: Mapping[str, Any] Design summary, including performance specs and generator parameters """ tinfo_table = TileInfoTable.make_tiles(self.grid, tile_specs) self.pinfo = tinfo_table[tile_name] self.out_w_p = w_p self.out_w_n = w_n self.dsn_specs['w_p'] = self.out_w_p self.dsn_specs['w_n'] = self.out_w_n driver_designer = DriverPullUpDownDesigner(self._root_dir, self._sim_db, self.dsn_specs) summary = await driver_designer.async_design( **driver_designer.dsn_specs) seg_p: int = summary['seg_p'] seg_n: int = summary['seg_n'] self.out_w_p = summary['w_p'] self.out_w_n = summary['w_n'] tbm_specs: dict = dict(sim_envs=get_tech_global_info('aib_ams') ['dsn_envs']['slow_io']['env'], thres_lo=0.1, thres_hi=0.9, tstep=None, sim_params=dict( vdd=get_tech_global_info('aib_ams') ['dsn_envs']['slow_io']['vddio'], cload=c_max, tbit=20 * r_targ * c_max, trf=trf_in, ), rtol=1e-8, atol=1e-22, save_outputs=['out', 'nand_pu', 'nor_pd', 'in']) gate_sizes = await self._size_nand_nor(seg_p, seg_n, trf_in, tbm_specs, del_err, seg_even) nand_seg, nor_seg, nand_p_w_del, nor_n_w_del, w_nom = gate_sizes # Characterize Final design dut_params = self._get_unit_cell_params(self.pinfo, seg_p, seg_n, nand_seg, nor_seg, nand_p_w_del, nor_n_w_del, w_min=w_nom) dut = await self.async_new_dut('unit_cell', STDCellWrapper, dut_params) all_corners = get_tech_global_info( 'aib_ams')['signoff_envs']['all_corners'] tdr_worst = -float('inf') tdf_worst = -float('inf') pu_tr_worst = -float('inf') pu_tf_worst = -float('inf') pd_tr_worst = -float('inf') pd_tf_worst = -float('inf') duty_err_worst = 0 for env in all_corners['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd'] = all_corners['vddio'][env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'sim_final_{env}', dut, tbm, self._tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay( sim_results.data, tbm_specs, 'in', 'out', False) pu_tr, pu_tf = CombLogicTimingTB.get_output_trf( sim_results.data, tbm_specs, 'nand_pu') pd_tr, pd_tf = CombLogicTimingTB.get_output_trf( sim_results.data, tbm_specs, 'nor_pd') tdr_worst = self.set_worst_spec(tdr, tdr_worst) tdf_worst = self.set_worst_spec(tdf, tdf_worst) pu_tr_worst = self.set_worst_spec(pu_tr, pu_tr_worst) pu_tf_worst = self.set_worst_spec(pu_tf, pu_tf_worst) pd_tr_worst = self.set_worst_spec(pd_tr, pd_tr_worst) pd_tf_worst = self.set_worst_spec(pd_tf, pd_tf_worst) duty_err = tdr - tdf if np.abs(duty_err) > np.abs(duty_err_worst): duty_err_worst = duty_err return dict(tdr=tdr_worst, tdf=tdf_worst, pu_tr=pu_tr_worst, pu_tf=pu_tf_worst, pd_tr=pd_tr_worst, pd_tf=pd_tf_worst, duty_err=duty_err_worst, dut_params=dut_params)
async def _find_tgate_and_tint(self, inv_in_pseg, inv_in_nseg, pseg, nseg, inv_nseg, inv_pseg, out_inv_m, pseg_off, inv_input_cap, cload, k_ratio, pinfo, tbm_specs, is_ctrl, has_rst, dual_output, vin, vout, worst_env) -> Tuple[dict, dict, float]: # Setup nominal parameters and delay lv_params = dict( pinfo=pinfo, seg_p=pseg, seg_n=nseg, seg_inv_p=inv_pseg, seg_inv_n=inv_nseg, seg_in_inv_n=inv_in_nseg, seg_in_inv_p=inv_in_pseg, out_inv_m=out_inv_m, out_pseg_off=pseg_off, ) tb_params = self._get_full_tb_params() dut_params = self._get_lvl_shift_params_dict(**lv_params, has_rst=has_rst, dual_output=False, skew_out=True) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) all_corners = get_tech_global_info( 'bag3_digital')['signoff_envs']['all_corners'] tbm_specs['sim_envs'] = [worst_env] tbm_specs['sim_params']['vdd_in'] = all_corners[vin][worst_env] tbm_specs['sim_params']['vdd'] = all_corners[vout][worst_env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results_orig = await self.async_simulate_tbm_obj( f'sim_output_inv_pseg_{pseg_off}', dut, tbm, tb_params) tdr_nom, tdf_nom = CombLogicTimingTB.get_output_delay( sim_results_orig.data, tbm.specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') slope_dict = dict() tot_sense_dict = dict() tint_dict = dict() segs_out = cload / inv_input_cap tweak_vars = [ ('seg_in_inv_p', 'in', 'inb_buf', 'fall', True, 'vdd_in', 'vdd_in', inv_in_pseg + inv_in_nseg, nseg + inv_nseg + inv_pseg + (pseg if has_rst else 0), 0), ('seg_n', 'inb_buf', 'midp', 'rise', True, 'vdd_in', 'vdd', nseg, pseg, 1 / k_ratio), ('seg_p', 'midp', 'midn', 'fall', True, 'vdd', 'vdd', pseg, 2 * out_inv_m - pseg_off, 1), ('out_inv_m', 'midn', 'out', 'rise', True, 'vdd', 'vdd', 2 * out_inv_m - pseg_off, segs_out, 0) ] tint_tot = 0 for var_tuple in tweak_vars: var, node_in, node_out, in_edge, invert, in_sup, out_sup, seg_in, seg_load, fan_min = var_tuple tdr_stg_nom, tdf_stg_nom = CombLogicTimingTB.get_output_delay( sim_results_orig.data, tbm.specs, node_in, node_out, invert, in_pwr=in_sup, out_pwr=out_sup) nom_var = lv_params[var] lv_params[var] = nom_var + 1 dut_params = self._get_lvl_shift_params_dict(**lv_params, has_rst=has_rst, dual_output=False, skew_out=True) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) sim_results = await self.async_simulate_tbm_obj( f'sim_check_tgate_tint', dut, tbm, tb_params) tdr_new, tdf_new = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') tdr_stg_new, tdf_stg_new = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, node_in, node_out, invert, in_pwr=in_sup, out_pwr=out_sup) td_new, td_nom = (tdr_stg_new, tdr_stg_nom) if in_edge == 'rise' else ( tdf_stg_new, tdf_stg_nom) slope_dict[var] = (td_nom[0] - td_new[0]) / (seg_load / seg_in - seg_load / (seg_in + 1)) tot_sense_dict[var] = tdf_nom[0] - tdf_new[0] tint_dict[var] = td_nom[0] - slope_dict[var] * seg_load / seg_in lv_params[var] = nom_var tint_tot += fan_min * slope_dict[var] + tint_dict[var] return slope_dict, tint_dict, tint_tot
async def async_design(self, r_targ: float, c_max: float, freq: float, trf_in: float, rel_err: float, tile_specs: Mapping[str, Any], tile_name: str, w_p: int, w_n: int, res_mm_specs: Dict[str, Any], is_weak: Optional[bool] = False, stack_max: Optional[int] = 10, seg_even: bool = True, em_options: Optional[Mapping[str, Any]] = None, ridx_p: int = -1, ridx_n: int = 0, max_iter: int = 10, **kwargs: Any) -> Mapping[str, Any]: """Design a driver unit cell. will try to achieve the given maximum trf and meet EM specs at c_max. The main driver increases the segments and width to achieve small output resistance The weak driver uses stacking to achieve large output resistance. 1) Determines minimum transistor segments for output current for given load cap and frequency 2) For weak driver, determines transistor stacking to meet output resistance spec 3) For main driver, determines transistor segments and width to meet output resistance spec Parameters ---------- r_targ: float Target unit cell output resistance c_max: float Load capacitance freq: float Operating switching frequency trf_in: float Input rise / fall time rel_err: float Output resistance error tolerance tile_name: str Tile name for layout. tile_specs: Mapping[str, Any] Tile specifications for layout. w_p: int Initial output PMOS width w_n: int Initial output NMOS width res_mm_specs: Dict[str, Any] Specs for DriverPullUpDownMM is_weak: Optional[bool] True if designing weak driver stack_max: int Maximum allowed transistor stack size seg_even: bool True to force number of segments to be even em_options: Optional[Mapping[str, Any]] Additional arguments for calculating minimum segments based on EM specs ridx_n: int NMOS transistor row ridx_p: int PMOS transistor row max_iter: int Maximum allowed number of iteration kwargs: Any Additional keyword arguments. Unused here Returns ------- ans: Mapping[str, Any] Design summary, including generator parameters and performance summary """ 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) if em_options is None: em_options = {} tinfo_table = TileInfoTable.make_tiles(self.grid, tile_specs) arr_info = tinfo_table.arr_info pinfo = tinfo_table[tile_name] mos_tech = arr_info.tech_cls # get transistor parameters vdd_max = get_tech_global_info( 'aib_ams')['signoff_envs']['vmax']['vddio'] iac_peak = c_max * vdd_max * freq iac_rms = iac_peak / math.sqrt(2) seg_min = mos_tech.get_segments_from_em( arr_info.conn_layer, 0, iac_rms, iac_peak, even=True, ** em_options) if not is_weak else 1 self.log(f'seg_min = {seg_min}') dut_params = dict( pinfo=pinfo, seg_p=seg_min, seg_n=seg_min, w_p=w_p, w_n=w_n, stack=1, ridx_p=ridx_p, ridx_n=ridx_n, export_pins=True, ) # get number of stacks # Modified to set effectively skip stacking for main driver in order to improve run time r_pu, r_pd = await self._get_stack(dut_params, res_mm_specs, r_targ, stack_max if is_weak else None) self.log(f'stack = {dut_params["stack"]}') if is_weak: if gen_specs is not None and gen_cell_args is not None: gen_cell_specs = dict( lay_class=STDCellWrapper.get_qualified_name(), params=dict( cls_name=PullUpDown.get_qualified_name(), params=dut_params, ), **gen_specs, ) return dict(gen_specs=gen_cell_specs, gen_args=gen_cell_args) return dict( stack=dut_params['stack'], r_targ=r_targ, r_pu=r_pu, r_pd=r_pd, seg_p=seg_min, seg_n=seg_min, w_p=w_p, w_n=w_n, ) # get widths/number of segments ans = await self._resize_transistors(dut_params, res_mm_specs, r_targ, r_pu, r_pd, seg_min, seg_even, rel_err, max_iter=6) if gen_specs is not None and gen_cell_args is not None: gen_cell_specs = dict( lay_class=STDCellWrapper.get_qualified_name(), cls_name=PullUpDown.get_qualified_name(), params=dut_params, **gen_specs, ) return dict(gen_specs=gen_cell_specs, gen_args=gen_cell_args) return ans
async def async_design(self, nsync: int, cload: float, fmax: float, fmin: float, dcd_max: float, rel_del_max: float, pinfo: Mapping[str, any], core_params: Optional[Dict[str, Any]] = None, sync_params: Optional[Dict[str, Any]] = None, buf_params: Optional[Dict[str, Any]] = None, **kwargs: Mapping[str, Any]) -> Mapping[str, Any]: """ This design method is basically a logical-effort sizing and a sign-off in the end. If there are sizes passed in through optional parameters they will be used and this function will only execute the sign-off part. Parameters ---------- nsync: int Number of synchronizer flops. cload: float Loading cap. fmax: float Max. frequency for sign-off. fmin: float Min. frequency for sign-off. dcd_max: float Max. duty cycle distortion allowed. rel_del_max: float Max. relative delay requirement between launch and measure edges at output. pinfo: Mapping[str, any]: pinfo for layout. core_params: Dict[str, Any] Optional. If provided core design part will be skipped. sync_params: Dict[str, Any] Optional. If provided synchronizer design part will be skipped. buf_params: Dict[str, Any] Optional. If provided buffer design part will be skipped. Returns ------- summary: Mapping[str, Any] Design summary. """ tech_globals = get_tech_global_info('aib_ams') core_params = self._default_core_params( ) if core_params is None else core_params sync_params = self._default_sync_params( ) if sync_params is None else sync_params buf_params = self._default_buf_params( ) if buf_params is None else buf_params 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) if 'width' not in pinfo['row_specs'][0] and 'width' not in pinfo[ 'row_specs'][1]: pinfo['row_specs'][0]['width'] = 2 * tech_globals['w_minn'] pwidth = int( np.round(tech_globals['inv_beta'] * 2 * tech_globals['w_minn'])) pinfo['row_specs'][1]['width'] = pwidth gen_params = dict( cls_name=self.get_dut_lay_class().get_qualified_name(), draw_taps=True, params=dict( pinfo=pinfo, core_params=core_params, sync_params=sync_params, buf_params=buf_params, nsync=nsync, ), ) dut = await self.async_new_dut("dcc_helper", STDCellWrapper, gen_params) helper = GatherHelper() for env_str in tech_globals['signoff_envs']['all_corners']['envs']: for freq in [fmin, fmax]: helper.append( self._sim_and_check_specs(dut, env_str, freq, cload, dcd_max, fmax, rel_del_max)) results = await helper.gather_err() dcd_dict = {} del_rel_dict = {} idx = 0 for env_str in tech_globals['signoff_envs']['all_corners']['envs']: for freq in ['fmin', 'fmax']: if freq == 'fmax': dcd_dict[env_str] = results[idx][1] del_rel_dict[env_str] = results[idx][2] idx += 1 dcd_min = min(dcd_dict.values()) dcd_max = max(dcd_dict.values()) del_rel_min = min(del_rel_dict.values()) del_rel_max = max(del_rel_dict.values()) self.log( f'|DCD| ranges from {dcd_min*100:.4f}% to {dcd_max*100:.4f}%.') self.log( f'Relative delay ranges from {del_rel_min*100:.4f}% to {del_rel_max*100:.4f}%' ) if gen_specs is not None and gen_cell_args is not None: gen_cell_specs = dict( lay_class=STDCellWrapper.get_qualified_name(), cls_name=DCCHelper.get_qualified_name(), params=gen_params, **gen_specs, ) return dict(gen_specs=gen_cell_specs, gen_args=gen_cell_args) return gen_params
async def _size_nand_nor( self, seg_p: int, seg_n: int, trf: float, tbm_specs: dict, del_err: float, seg_even: Optional[bool] = False, nand_p_w_del: Optional[int] = None, nor_n_w_del: Optional[int] = None, w_nom: Optional[int] = -1) -> Tuple[int, int, int, int, int]: # Check for defaults and set them if needed. tech_globals = get_tech_global_info('aib_ams') if nand_p_w_del is None: nand_p_w_del = 0 if nor_n_w_del is None: nor_n_w_del = 0 if w_nom == -1: w_nom = max(tech_globals['w_nomp'], tech_globals['w_nomn']) # binary search: up size both nand and nor to meet the slope rate trf nand_seg = nor_seg = 1 max_nand_seg = int(np.round(seg_p / tech_globals['min_fanout'])) max_nor_seg = int(np.round(seg_n / tech_globals['min_fanout'])) dut_params = self._get_unit_cell_params(self.pinfo, seg_p, seg_n, nand_seg, nor_seg, nand_p_w_del, nor_n_w_del, w_min=w_nom) tbm_specs['sim_envs'] = tech_globals['dsn_envs']['slow_io']['env'] tbm_specs['sim_params']['vdd'] = tech_globals['dsn_envs']['slow_io'][ 'vddio'] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) nand_seg, _, tf = await self._upsize_gate_for_trf( dut_params, trf, nand_seg, True, tbm, seg_even, max_nand_seg) nor_seg, tr, _ = await self._upsize_gate_for_trf( dut_params, trf, nor_seg, False, tbm, seg_even, max_nor_seg) # Change tbm_spec to design for delay matching in tt corner dut_params_new = self._get_unit_cell_params(self.pinfo, seg_p, seg_n, nand_seg, nor_seg, nand_p_w_del, nor_n_w_del, w_min=w_nom) tbm_specs['sim_envs'] = tech_globals['dsn_envs']['center']['env'] tbm_specs['sim_params']['vdd'] = tech_globals['dsn_envs']['center'][ 'vddio'] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) dut = await self.async_new_dut('nand_nor_check_delay', STDCellWrapper, dut_params_new) sim_results = await self.async_simulate_tbm_obj( 'check_delay', dut, tbm, self._tb_params) nand_tdf, nand_tdr = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'nand_pu', out_invert=True, out_pwr='vdd') nor_tdf, nor_tdr = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'in', 'nor_pd', out_invert=True, out_pwr='vdd') err_cur = np.abs(nand_tdf - nor_tdr) if np.max(err_cur) > del_err: # up_size the slower side to make it faster if np.max(nor_tdr) < np.max(nand_tdf): nand_seg, nand_tdr, nand_tdf = await self._upsize_gate_for_delay( dut_params, np.max(nor_tdr), nand_seg, True, tbm, seg_even, max_nand_seg) else: nor_seg, nor_tdr, nor_tdf = await self._upsize_gate_for_delay( dut_params, np.max(nand_tdf), nor_seg, False, tbm, seg_even, max_nor_seg) # check if t_rise_nand < t_rise_nor and t_fall_nor < t_fall_nand rerun = False if not np.all(nand_tdr < nor_tdr): rerun = nand_p_w_del + 1 + w_nom <= tech_globals['w_maxp'] new_nand_p_w_del = nand_p_w_del + 1 if rerun else nand_p_w_del else: new_nand_p_w_del = nand_p_w_del if not np.all(nor_tdf < nand_tdf): rerun = nor_n_w_del + 1 + w_nom <= tech_globals['w_maxn'] new_nor_n_w_del = nor_n_w_del + 1 if rerun else nor_n_w_del else: new_nor_n_w_del = nor_n_w_del if rerun: await self._size_nand_nor(seg_p, seg_n, trf, tbm_specs, del_err, seg_even, new_nand_p_w_del, new_nor_n_w_del, w_nom) await self.run_nand_nor_signoff(dut, del_err, tbm_specs, tech_globals) return nand_seg, nor_seg, nand_p_w_del, nor_n_w_del, w_nom
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
def _build_meas_params(cls, sim_env: str, freq: float, cload: float) -> Dict[str, Any]: """ Creates parameter dictionary for ClockDelayMM measurement manager class. ClockDelayMM uses DigitalTranTB related test-bench manager parameters. Parameters ---------- sim_env: str Corner-temperature environment. freq: float Frequency of clk in simulation. cload: float Loading cap. Returns ------- meas_params: Dict[str, Any] Measurement dictionary params. """ trf_nom = get_tech_global_info('aib_ams')['trf_nom'] sim_env_info = get_tech_global_info( 'aib_ams')['signoff_envs']['all_corners'] meas_params = dict( tbm_specs=dict( sim_envs=[sim_env], sim_params=dict( # simulation parameters t_rst=1.1 / freq, t_rst_rf=trf_nom, t_bit=1 / freq, ), thres_lo=0.1, thres_hi=0.9, rtol=1.0e-8, atol=1.0e-22, tran_options=dict( maxstep=1.0e-12, errpreset='conservative', ), swp_info=[ # list of parameters to sweep ['t_rf', { 'type': 'LIST', 'values': [trf_nom] }], ['c_load', { 'type': 'LIST', 'values': [cload] }] ], pwr_domain=dict( # pin's low/high pwr domains (defined later) clk_dcd=('VSS', 'VDD'), dcc_byp=('VSS', 'VDD'), launch=('VSS', 'VDD'), measure=('VSS', 'VDD'), rstb=('VSS', 'VDD'), ckout=('VSS', 'VDD')), sup_values=dict( # pwr domain definition with proper values VDD=sim_env_info['vdd'][sim_env], VSS=0.0, ), pin_values= dict( # low(0) or high(1) value for pins with constant voltage clk_dcd=0, dcc_byp=0, launch=0, measure=0, ), reset_list=[['rstb', False]], # reset rstb (active low) ), fake=False, # make this true to get fake data (i.e. for debugging) ) return meas_params
async def signoff_dut( self, dut, cload, vin, vout, dmax, trf_in, is_ctrl, has_rst, exception_on_dmax: bool = True ) -> Tuple[float, float, str, float, str]: tech_info = get_tech_global_info('bag3_digital') all_corners = tech_info['signoff_envs']['all_corners'] # Run level shifter extreme corner signoff envs = tech_info['signoff_envs']['lvl_func']['env'] vdd_out = tech_info['signoff_envs']['lvl_func']['vddo'] vdd_in = tech_info['signoff_envs']['lvl_func']['vddi'] tbm_specs = self._get_tbm_params(envs, vdd_in, vdd_out, trf_in, cload, 10 * dmax) tbm_specs['save_outputs'] = [ 'in', 'inbar', 'inb_buf', 'in_buf', 'midn', 'midp', 'out', 'outb' ] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) # sign off signal path tb_params = self._get_full_tb_params() sim_results = await self.async_simulate_tbm_obj( f'signoff_lvlshift_extreme', dut, tbm, tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay(sim_results.data, tbm_specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') td = max(tdr, tdf) if td < float('inf'): self.log( 'Level shifter signal path passed extreme corner signoff.') else: plt.plot(sim_results.data['time'].flatten(), sim_results.data['in'].flatten(), 'b') plt.plot(sim_results.data['time'].flatten(), sim_results.data['out'].flatten(), 'g') plt.show(block=False) raise ValueError( 'Level shifter design failed extreme corner signoff.') # sign off reset if has_rst: tbm_specs['stimuli_pwr'] = 'vdd' tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) rst_tb_params = self._get_rst_tb_params() sim_results = await self.async_simulate_tbm_obj( f'signoff_lvlshift_rst_extreme', dut, tbm, rst_tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay(sim_results.data, tbm_specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') self.log(f"Reset Delay Overall: tdr: {tdr}, tdf: {tdf} ") td = max(tdr, tdf) if td < float('inf'): self.log( 'Level shifter reset path passed extreme corner signoff.') else: plt.plot(sim_results.data['time'].flatten(), sim_results.data['in'].flatten(), 'b') plt.plot(sim_results.data['time'].flatten(), sim_results.data['out'].flatten(), 'g') plt.show(block=False) raise ValueError( 'Level shifter design failed reset extreme corner signoff.' ) envs = all_corners['envs'] worst_trst = -float('inf') worst_td = -float('inf') worst_tdf = -float('inf') worst_tdr = -float('inf') worst_var = 0 worst_env = '' worst_var_env = '' for env in envs: vdd_in = all_corners[vin][env] vdd_out = all_corners[vout][env] tbm_specs = self._get_tbm_params([env], vdd_in, vdd_out, trf_in, cload, 10 * dmax) tbm_specs['stimuli_pwr'] = 'vdd_in' tbm_specs['save_outputs'] = [ 'in', 'inb_buf', 'in_buf', 'midn', 'midp', 'out' ] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) # sign off signal path tb_params = self._get_full_tb_params() sim_results = await self.async_simulate_tbm_obj( f'signoff_lvlshift_{env}', dut, tbm, tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay(sim_results.data, tbm_specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') self.log(f"Delay Overall: tdr: {tdr}, tdf: {tdf} ") td = max(tdr, tdf) if td > worst_td: worst_td = td worst_tdf = tdf worst_tdr = tdr worst_env = env if not is_ctrl: delay_var = (tdr - tdf) if np.abs(delay_var) > np.abs(worst_var): worst_var = delay_var worst_var_env = env ''' # Debug # ----- td_iinv_r, td_iinv_f = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'in', 'inb_buf', True, in_pwr='vdd_in', out_pwr='vdd_in') td_minv_r, td_minv_f = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'inb_buf', 'in_buf', True, in_pwr='vdd_in', out_pwr='vdd_in') td_pdn_r, td_pdn_f = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'in_buf', 'midn', True, in_pwr='vdd_in', out_pwr='vdd') td_oinv_r, td_oinv_f = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'midn', 'out', True, in_pwr='vdd_in', out_pwr='vdd') td_pdp_r, td_pdp_f = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'inb_buf', 'midp', True, in_pwr='vdd_in', out_pwr='vdd') td_pun_r, td_pun_f = CombLogicTimingTB.get_output_delay(sim_results.data, tbm.specs, 'midp', 'midn', True, in_pwr='vdd', out_pwr='vdd') if env == 'ss_125': 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'{env}') plt.show(block=False) print(f'Path rise out: {td_iinv_r} + {td_minv_f} + {td_pdn_r} + {td_oinv_f}') print(f'Path fall out: {td_iinv_f} + {td_pdp_r} + {td_pun_f} + {td_oinv_r}') breakpoint() # ---- ''' # sign off reset if has_rst: tbm_specs['stimuli_pwr'] = 'vdd' tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) rst_tb_params = self._get_rst_tb_params() sim_results = await self.async_simulate_tbm_obj( f'signoff_lvlshift_rst_{env}', dut, tbm, rst_tb_params) tdr, tdf = CombLogicTimingTB.get_output_delay(sim_results.data, tbm_specs, 'in', 'out', False, in_pwr='vdd_in', out_pwr='vdd') self.log(f"Reset Delay Overall: tdr: {tdr}, tdf: {tdf} ") td = max(tdr, tdf) if td > worst_trst: worst_trst = td worst_trst_env = env td_target = 20 * trf_in if is_ctrl else dmax self.log( f'td_target = {td_target}, worst_tdr = {worst_tdr}, worst_tdf = {worst_tdf}, ' f'worst_env = {worst_env}') #breakpoint() if worst_tdr > td_target or worst_tdf > td_target: msg = 'Level shifter delay did not meet target.' if exception_on_dmax: raise RuntimeError(msg) else: self.log(msg) if has_rst: self.log( f'worst_trst = {worst_trst}, worst_trst_env = {worst_trst_env}' ) if worst_tdr > 20 * trf_in or worst_tdf > 20 * trf_in: raise RuntimeError( "Level shifter reset delay exceeded simulation period.") return worst_tdr, worst_tdf, worst_env, worst_var, worst_var_env
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 async_design(self, cload: float, dmax: float, trf_in: float, tile_specs: Mapping[str, Any], k_ratio: float, tile_name: str, inv_input_cap: float, inv_input_cap_per_fin: float, fanout: float, vin: str, vout: str, w_p: int = 0, w_n: int = 0, ridx_p: int = -1, ridx_n: int = 0, has_rst: bool = False, is_ctrl: bool = False, dual_output: bool = False, exception_on_dmax: bool = True, del_scale: float = 1, **kwargs: Any) -> Mapping[str, Any]: """ Design a Level Shifter This will try to design a level shifter to meet a maximum nominal delay, given the load cap """ tech_info = get_tech_global_info('bag3_digital') w_p = tech_info['w_maxp'] if w_p == 0 else w_p w_n = tech_info['w_maxn'] if w_n == 0 else w_n if not 'lch' in tile_specs['arr_info']: tile_specs['arr_info']['lch'] = tech_info['lch_min'] tile_specs['place_info'][tile_name]['row_specs'][0]['width'] = w_n tile_specs['place_info'][tile_name]['row_specs'][1]['width'] = w_p tinfo_table = TileInfoTable.make_tiles(self.grid, tile_specs) pinfo = tinfo_table[tile_name] # Design the output inverter, and the level shift core design_sim_env, vdd_in, vdd_out = self._build_env_vars( 'center', vin, vout) tbm_specs = self._get_tbm_params(design_sim_env, vdd_in, vdd_out, trf_in, cload, 10 * dmax) tbm_specs['save_outputs'] = [ 'in', 'inbar', 'out', 'outb', 'inb_buf', 'in_buf', 'midn', 'midp' ] out_inv_m, pseg, nseg = self._design_lvl_shift_core_size( cload, k_ratio, inv_input_cap, fanout, is_ctrl) # Design the inverter creating the inverted input to the leveler inv_pseg, inv_nseg = await self._design_lvl_shift_internal_inv( pseg, nseg, out_inv_m, fanout, pinfo, tbm_specs, is_ctrl, has_rst, dual_output, vin, vout) # Design input inverter inv_in_nseg, inv_in_pseg = self._size_input_inv_for_fanout( inv_pseg, inv_nseg, pseg, nseg, fanout, has_rst) # Adjust the output inverter beta ratio to further reduce duty cycle distortion if not is_ctrl: pseg_off = await self._design_output_inverter( inv_in_pseg, inv_in_nseg, pseg, nseg, inv_nseg, inv_pseg, out_inv_m, fanout, pinfo, tbm_specs, has_rst, vin, vout) else: pseg_off, worst_env = 0, '' # Final Simulation 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, is_ctrl, skew_out=not is_ctrl, out_pseg_off=pseg_off) dut = await self.async_new_dut('lvshift', STDCellWrapper, dut_params) tdr, tdf, worst_env, worst_var, worst_var_env = await self.signoff_dut( dut, cload, vin, vout, dmax, trf_in, is_ctrl, has_rst, exception_on_dmax) if not is_ctrl and max(tdr, tdf) > dmax: # Find intrinsic delay based on stage-by-stage characterization tgate_dict, tint_dict, tint_tot = await self._find_tgate_and_tint( inv_in_pseg, inv_in_nseg, pseg, nseg, inv_nseg, inv_pseg, out_inv_m, pseg_off, inv_input_cap, cload, k_ratio, pinfo, tbm_specs, is_ctrl, has_rst, dual_output, vin, vout, worst_env) else: tint_tot = 0 return dict(dut_params=dut_params, tdr=tdr, tdf=tdf, tint=tint_tot, worst_var=worst_var)
def __init__(self, *args: Any, **kwargs: Any) -> None: DesignerBase.__init__(self, *args, **kwargs) self.dsn_tech_info = get_tech_global_info('aib_ams') self._txanlg_dsnr = None self._rxanlg_dsnr = None
def _get_lvl_shift_params_dict(pinfo: Any, seg_p: int, seg_n: int, seg_inv_p: int, seg_inv_n: int, seg_in_inv_p: int, seg_in_inv_n: int, out_inv_m: int, has_rst: bool, dual_output: bool, is_ctrl: bool = False, skew_out: bool = False, out_pseg_off: int = 0) -> Dict[str, Any]: """ Creates a dictionary of parameters for the layout class LevelShifter seg_n : nmos Pull down nseg seg_p : pmos Pull up nseg seg_inv : Inb_buf to In_buf inverter segments seg_in_inv : In to Inb_buf inverter segments pinfo : pinfo # TODO: UPDATE THIS DOCUMENTATION Note: This will let the width be passed through the pinfo, currently no rst """ tech_info = get_tech_global_info('bag3_digital') wn = tech_info['w_minn'] if is_ctrl else 2 * tech_info['w_minn'] wp = tech_info['w_minp'] if is_ctrl else 2 * tech_info['w_minp'] if has_rst: seg_dict = dict(pd=seg_n, pu=seg_p, rst=int(np.ceil(seg_n / 2)), prst=seg_p) w_dict = dict(pd=wn, pu=wp, rst=wn) else: seg_dict = dict(pd=seg_n, pu=seg_p) w_dict = dict(pd=wn, pu=wp) lv_params = dict(cls_name=LevelShifter.get_qualified_name(), draw_taps=True, pwr_gnd_list=[('VDD_in', 'VSS'), ('VDD', 'VSS')], params=dict( pinfo=pinfo, lv_params=dict( seg_dict=seg_dict, w_dict=w_dict, has_rst=has_rst, in_upper=has_rst, dual_output=dual_output, ), in_buf_params=dict( segp_list=[seg_in_inv_p, seg_inv_p], segn_list=[seg_in_inv_n, seg_inv_n], w_p=wp, w_n=wn), export_pins=True, )) # Note that setting stack_p = 2 actually changes the topology of the level shifter to include PMOS devices # tied to the input and in series with the cross-coupled PMOS pull-ups. if has_rst: lv_params['params']['lv_params']['stack_p'] = 2 if skew_out: lv_params['params']['lv_params']['buf_segn_list'] = [out_inv_m] lv_params['params']['lv_params']['buf_segp_list'] = [ out_inv_m + out_pseg_off ] else: lv_params['params']['lv_params']['buf_seg_list'] = [out_inv_m] return lv_params