Source code for bag3_digital.design.lvl_shift

# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 Blue Cheetah Analog Design Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import math
from typing import Mapping, Dict, Any, Tuple, Optional, List, Type, Sequence, cast

from pathlib import Path

import numpy as np
from pandocfilters import Math
import matplotlib.pyplot as plt

from bag.util.search import BinaryIterator, FloatBinaryIterator, BinaryIteratorInterval

from xbase.layout.mos.placement.data import TileInfoTable

from bag3_testbenches.measurement.digital.timing import CombLogicTimingTB
from bag.simulation.design import DesignerBase

from bag3_digital.layout.stdcells.util import STDCellWrapper
from bag3_digital.layout.stdcells.levelshifter import LevelShifter, LevelShifterCore

from bag.env import get_tech_global_info


[docs]class LvlShiftDesigner(DesignerBase):
[docs] 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 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 gen_specs is not None and gen_cell_args is not None: gen_cell_specs = dict( lay_class=STDCellWrapper.get_qualified_name(), cls_name=LevelShifter.get_qualified_name(), params=dut_params, **gen_specs, ) return dict(gen_specs=gen_cell_specs, gen_args=gen_cell_args) return dict(dut_params=dut_params, tdr=tdr, tdf=tdf, tint=tint_tot, worst_var=worst_var)
[docs] 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
[docs] 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 # 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}') 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
@staticmethod
[docs] 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
@staticmethod
[docs] 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
@staticmethod
[docs] 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
[docs] async def _design_lvl_shift_internal_inv(self, pseg: int, nseg: int, out_inv_m: int, fanout: float, pinfo: Any, tbm_specs: Dict[str, Any], is_ctrl: bool, has_rst: bool, dual_output: bool, vin: str, vout: str) -> Tuple[int, int]: """ Given the NMOS segments and the PMOS segements ratio for the core, this function designs the internal inverter. For control level shifter, we don't care about matching rise / fall delay, so we just size for fanout. """ if is_ctrl: # size with fanout inv_nseg = int(np.round(nseg / fanout)) inv_nseg = 1 if inv_nseg == 0 else inv_nseg inv_pseg = int(np.round(pseg / fanout)) inv_pseg = 1 if inv_pseg == 0 else inv_pseg self.log(f"Calculated inv to need nseg : {inv_nseg}") self.log(f"Calculated inv to need pseg : {inv_pseg}") return inv_pseg, inv_nseg # First size the NMOS in the inverter assuming a reasonably sized PMOS inv_nseg = await self._design_lvl_shift_inv_pdn(pseg, nseg, out_inv_m, fanout, pinfo, tbm_specs, has_rst, dual_output, vin, vout) self.log(f"Calculated inv to need at least nseg: {inv_nseg}") # Now using the inverter pull down size, we size the inverter pull up PMOS inv_pseg, inv_nseg = await self._design_lvl_shift_inv_pun(pseg, nseg, inv_nseg, out_inv_m, fanout, pinfo, tbm_specs, has_rst, dual_output, vin, vout) self.log(f"Calculated inv to need pseg: {inv_pseg} and nseg: {inv_nseg}") return inv_pseg, inv_nseg
[docs] 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 """ 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/((1+inv_beta)*min_fanout))) # iterator = BinaryIteratorInterval(get_tech_global_info('bag3_digital')['width_interval_list_n'], # 1, max_nseg) 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') print("Balance internal delays inv_pdn Info: ", env, tdr_cur, target_cur) # 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] 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
[docs] 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
[docs] 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() 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_pseg, inv_in_nseg, 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') print('Info from simulation Output driver : ', env, tdr_cur, tdf_cur) 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 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
@staticmethod
[docs] 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
@staticmethod
[docs] 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 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'] beta = get_tech_global_info('bag3_digital')['inv_beta'] 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, invp=wp, invn=wn) else: seg_dict = dict(pd=seg_n, pu=seg_p) w_dict = dict(pd=wn, pu=wp, invp=wp, invn=wn) 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, sep_stages=True), 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 - out_pseg_off] lv_params['params']['lv_params']['buf_segp_list'] = [out_inv_m * beta + out_pseg_off] else: #lv_params['params']['lv_params']['buf_seg_list'] = [out_inv_m] lv_params['params']['lv_params']['buf_segn_list'] = [out_inv_m] lv_params['params']['lv_params']['buf_segp_list'] = [out_inv_m * beta] return lv_params
@staticmethod
[docs] def _get_full_tb_params() -> Dict[str, Any]: return dict( load_list=[('out', 'cload'), ('outb', 'cload')], vbias_list=[('VDD', 'vdd'), ('VDD_in', 'vdd_in'), ('rst', 'vrst'), ('rstb', 'vrst_b')], dut_conns={'in': 'in', 'rst_out': 'rst', 'rst_outb': 'rst', 'VDD': 'VDD', 'VDD_in': 'VDD_in', 'VSS': 'VSS', 'rst_casc': 'rstb', 'out': 'out', 'outb': 'outb', 'inb_buf': 'inb_buf', 'in_buf': 'in_buf', 'midn': 'midn', 'midp': 'midp'},
) @staticmethod
[docs] def _get_rst_tb_params() -> Dict[str, Any]: return dict( load_list=[('out', 'cload'), ('outb', 'cload')], vbias_list=[('VDD', 'vdd'), ('VDD_in', 'vdd_in')], dut_conns={'in': 'VDD', 'rst_out': 'inbar', 'rst_outb': 'in', 'VDD': 'VDD', 'VDD_in': 'VDD_in', 'VSS': 'VSS', 'rst_casc': 'VSS', 'out': 'out', 'outb': 'outb', 'inb_buf': 'inb_buf', 'in_buf': 'in_buf', 'midn': 'midn', 'midp': 'midp'},
) @staticmethod
[docs] def _get_core_tb_params() -> Dict[str, Any]: return dict( load_list=[('out', 'cload'), ('outb', 'cload')], vbias_list=[('VDD', 'vdd'), ('VDD_in', 'vdd_in'), ('rst', 'vrst'), ('rstb', 'vrst_b')], dut_conns={'inp': 'in', 'inn': 'VDD_in', 'rst_outp': 'rst', 'rst_outn': 'rst', 'VDD': 'VDD', 'VSS': 'VSS', 'rst_casc': 'rstb', 'outp': 'out', 'outn': 'outb'}
) @staticmethod
[docs] def _get_tbm_params(sim_envs: Sequence[str], vdd_in: float, vdd_out: float, trf: float, cload: float, tbit: float) -> Dict[str, Any]: return dict( sim_envs=sim_envs, thres_lo=0.1, thres_hi=0.9, stimuli_pwr='vdd_in', tstep=None, gen_invert=True, sim_params=dict( vdd=vdd_out, vdd_in=vdd_in, vrst=0.0, vrst_b=vdd_out, cload=cload, tbit=tbit, trf=trf, ), rtol=1e-8, atol=1e-22,
)