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
async def run_nand_nor_signoff(self, dut, del_err, tbm_specs, tech_globals): tbm_specs['env_params'] = dict(vdd=dict()) tbm_specs['env_params']['vdd'] = tech_globals['signoff_envs'][ 'all_corners']['vddio'] nand_tdf = [] nand_tdr = [] nor_tdf = [] nor_tdr = [] for env in tech_globals['signoff_envs']['all_corners']['envs']: tbm_specs['sim_envs'] = [env] tbm_specs['sim_params']['vdd'] = tbm_specs['env_params']['vdd'][ env] tbm = cast(CombLogicTimingTB, self.make_tbm(CombLogicTimingTB, tbm_specs)) sim_results = await self.async_simulate_tbm_obj( f'signoff_delay_match_{env}', dut, tbm, self._tb_params) pu_tdf, pu_tdr = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'nand_pu', out_invert=True, out_pwr='vdd') pd_tdf, pd_tdr = CombLogicTimingTB.get_output_delay( sim_results.data, tbm.specs, 'in', 'nor_pd', out_invert=True, out_pwr='vdd') nand_tdf.append(pu_tdf) nand_tdr.append(pu_tdr) nor_tdf.append(pd_tdf) nor_tdr.append(pd_tdr) err_cur = np.abs(np.subtract(nand_tdf, nor_tdr)) msg = f'del_err: {np.max(err_cur)} [wanted: {del_err}]' if np.any(err_cur > del_err): self.error( f'Unable to match NAND/NOR gate delays to within target, {msg}' ) self.log(msg)
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 _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. # 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_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_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 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 _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, 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 _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