Source code for bag3_digital.design.lvl_shift_de

# 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.

from __future__ import annotations

from typing import Mapping, Dict, Any, Tuple, Sequence, Optional

import math
import pprint

from bag.simulation.cache import DesignInstance

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

from bag3_testbenches.measurement.digital.comb import CombLogicTimingMM

from ..layout.stdcells.levelshifter import LevelShifterCoreOutBuffer
from ..measurement.cap.delay_match import CapDelayMatch
from .base import DigitalDesigner, BinSearchSegWidth


[docs]class InvDelayMatch(BinSearchSegWidth): def __init__(self, dsn: LvlShiftDEDesigner, dut_params: Dict[str, Any], w_list: Sequence[int], size_p: bool, err_targ: float, search_step: int = 1) -> None: super().__init__(w_list, err_targ, search_step=search_step) self._dsn = dsn self._params = dut_params self._size_p = size_p @classmethod
[docs] def get_bin_val(cls, data: Tuple[float, float]) -> float: return data[1] - data[0]
[docs] def get_bin_search_info(self, data: Tuple[float, float]) -> Tuple[float, bool]: diff = self.get_bin_val(data) return diff, (diff > 0) == self._size_p
[docs] def get_error(self, data: Tuple[float, float]) -> float: diff = self.get_bin_val(data) return abs(diff) / (data[0] + data[1])
[docs] def set_size(self, seg: int, w: int) -> None: if self._size_p: self._params['buf_segp_list'][0] = seg self._params['w_dict']['invp'] = w else: self._params['buf_segn_list'][0] = seg self._params['w_dict']['invn'] = w
[docs] async def get_data(self, seg: int, w: int) -> Tuple[float, float]: self._dsn.log(f'size_p={self._size_p}, set seg={seg}, w={w}') self.set_size(seg, w) return await self._dsn.get_delays(self._params)
[docs]class LvlShiftDEDesigner(DigitalDesigner): """Designer class for Level Shifter for differential signals in the RX """ def __init__(self, *args: Any, **kwargs: Any) -> None: self._pinfo: Optional[MOSBasePlaceInfo] = None self._w_p_list: Sequence[int] = [] self._w_n_list: Sequence[int] = [] self._td_specs: Dict[str, Any] = {} self._cin_specs: Dict[str, Any] = {} if 'w_p_list' in kwargs: self._w_p_list = kwargs['w_p_list'] if 'w_n_list' in kwargs: self._w_n_list = kwargs['w_n_list'] super().__init__(*args, **kwargs)
[docs] def commit(self) -> None: super().commit() specs = self.dsn_specs c_load: float = specs['c_load'] tile_name: str = specs['tile_name'] ridx_n: int = specs['ridx_n'] ridx_p: int = specs['ridx_p'] w_min: int = specs['w_min'] w_res: int = specs['w_res'] buf_config: Mapping[str, Any] = specs['buf_config'] search_params: Mapping[str, Any] = specs['search_params'] self._pinfo = self.get_tile(tile_name) w_n_max = self._pinfo.get_row_place_info(ridx_n).row_info.width w_p_max = self._pinfo.get_row_place_info(ridx_p).row_info.width if 'w_n_list' in specs: for wn in specs['w_n_list']: if wn <= w_n_max: self._w_n_list.append(wn) else: self._w_n_list = list(range(w_min, w_n_max + 1, w_res)) if 'w_p_list' in specs: for wp in specs['w_p_list']: if wp <= w_p_max: self._w_p_list.append(wp) else: self._w_p_list = list(range(w_min, w_p_max + 1, w_res)) pwr_tup = ('VSS', 'VDD') pwr_tup_in = ('VSS', 'VDDI') pwr_domain = {'in': pwr_tup_in, 'inb': pwr_tup_in} for name in ['rst_out', 'rst_outb', 'rst_casc', 'out', 'outb']: pwr_domain[name] = pwr_tup supply_map = dict(VDDI='VDDI', VDD='VDD', VSS='VSS') pin_values = dict(rst_outb=0) reset_list = [('rst_out', True)] diff_list = [(['rst_out'], ['rst_casc']), (['in'], ['inb']), (['out'], ['outb'])] tbm_specs = self.get_dig_tran_specs(pwr_domain, supply_map, pin_values=pin_values, reset_list=reset_list, diff_list=diff_list) tbm_specs['sim_params'] = sim_params = dict(**tbm_specs['sim_params']) sim_params['c_load'] = c_load self._td_specs = dict( in_pin='in', out_pin='out', tbm_specs=tbm_specs, out_invert=False, add_src_res=False, load_list=[], ) self._cin_specs = dict( in_pin='in', buf_config=dict(**buf_config), search_params=search_params, tbm_specs=tbm_specs, load_list=[dict(pin='out', type='cap', value='c_load')],
)
[docs] async def async_design(self, **kwargs: Any) -> Mapping[str, Any]: lv_params = self.get_init_lv_params() dut, td, err = await self.resize_inv(lv_params) c_in = await self.get_cap(dut, 'in') ans = dict(lv_params=lv_params, td=td, err=err, c_in=c_in) if lv_params['has_rst']: c_rst_out = await self.get_cap(dut, 'rst_out') c_rst_casc = await self.get_cap(dut, 'rst_casc') ans['c_rst_out'] = c_rst_out ans['c_rst_casc'] = c_rst_casc return ans
[docs] def get_init_lv_params(self) -> Dict[str, Any]: """Get nominal level shifter size based on fanout""" specs = self.dsn_specs fanout_inv: float = specs['fanout_inv'] fanout_core: float = specs['fanout_core'] c_load: float = specs['c_load'] k_ratio_core: float = specs['k_ratio_core'] lv_params: Mapping[str, Any] = specs['lv_params'] ridx_n: int = specs['ridx_n'] ridx_p: int = specs['ridx_p'] w_n = specs.get('w_n_inv', self._w_n_list[-1]) w_p = specs.get('w_p_inv', self._w_p_list[-1]) stack_p: int = lv_params.get('stack_p', 1) has_rst: bool = lv_params.get('has_rst', False) in_upper: bool = lv_params.get('in_upper', True) dual_output: bool = lv_params.get('dual_output', True) seg_prst: int = lv_params.get('seg_prst', 0) extra_params: Mapping[str, Any] = {} default_keys = ['stack_p', 'has_rst', 'in_upper', 'dual_output', 'seg_prst'] for cur_key in lv_params.keys(): if cur_key not in default_keys: extra_params[cur_key] = lv_params[cur_key] c_unit_n_seg = self._get_c_in_guess(0, 1, w_p, w_n) c_unit_p_seg = self._get_c_in_guess(1, 0, w_p, w_n) c_unit_inv = c_unit_n_seg + c_unit_p_seg seg_inv = int(math.ceil(c_load / fanout_inv / c_unit_inv)) c_inv = seg_inv * c_unit_inv p_scale = 2 if stack_p == 2 else 1 seg_p = int(math.ceil(c_inv / fanout_core / c_unit_p_seg * p_scale)) seg_n = int(math.ceil(seg_p * w_p * k_ratio_core / w_n)) c_in_guess = seg_n * c_unit_n_seg seg_dict = dict(pd=seg_n, pu=seg_p) if has_rst: rst_ratio: float = specs['rst_ratio'] seg_dict['rst'] = int(math.ceil(seg_n * rst_ratio)) if stack_p == 2 and seg_prst > 0: seg_dict['prst'] = seg_prst lv_shift_params = dict( pinfo=self._pinfo, seg_dict=seg_dict, w_dict=dict(pd=w_n, pu=w_p, rst=w_n, invn=w_n, invp=w_p), stack_p=stack_p, buf_segn_list=[seg_inv], buf_segp_list=[seg_inv], has_rst=has_rst, dual_output=dual_output, in_upper=in_upper, ridx_n=ridx_n, ridx_p=ridx_p, **extra_params, ) self.log(f'init c_in={c_in_guess:.4g}, lv_params:\n' f'{pprint.pformat(lv_shift_params, width=100)}') return lv_shift_params
[docs] async def resize_inv(self, dut_params: Dict[str, Any] ) -> Tuple[DesignInstance, Tuple[float, float], float]: specs = self.dsn_specs err_targ: float = specs['err_targ'] search_step: int = specs.get('search_step', 1) td = await self.get_delays(dut_params) # equalize rise/fall delays by slowing down fast edge if td[1] < td[0]: search = InvDelayMatch(self, dut_params, self._w_p_list, True, err_targ, search_step=search_step) seg = dut_params['buf_segp_list'][0] w = dut_params['w_dict']['invp'] else: search = InvDelayMatch(self, dut_params, self._w_n_list, False, err_targ, search_step=search_step) seg = dut_params['buf_segn_list'][0] w = dut_params['w_dict']['invn'] low_bnd = search.get_bin_search_info(td)[1] if low_bnd: seg_max = None seg_min = seg td_max = None td_min = td else: seg_max = seg seg_min = 1 td_max = td td_min = None td, seg, w = await search.get_seg_width(w, seg_min, seg_max, td_min, td_max) err = search.get_error(td) self.log(f'final result:\ntd_fall={td[0]:.4g}, td_rise={td[1]:.4g}, err={err:.4g}') dut = await self.async_wrapper_dut('LV_SHIFT_DIFF', LevelShifterCoreOutBuffer, dut_params) return dut, td, err
[docs] async def get_cap(self, dut: DesignInstance, pin_name: str) -> float: cin_specs = self._cin_specs params = dut.lay_master.params['params'] seg_dict = params['seg_dict'] w_dict = params['w_dict'] if pin_name == 'in': seg_p = seg_dict['pu'] if params['stack_p'] == 2 else 0 seg_n = seg_dict['pd'] w_p = w_dict['pu'] w_n = w_dict['pd'] elif pin_name == 'rst_out': seg_p = 0 seg_n = seg_dict['rst'] w_p = 0 w_n = w_dict['rst'] else: seg_p = 0 seg_n = seg_dict['pd'] w_p = 0 w_n = w_dict['pd'] cin_specs['in_pin'] = pin_name cin_specs['buf_config']['cin_guess'] = self._get_c_in_guess(seg_p, seg_n, w_p, w_n) mm = self.make_mm(CapDelayMatch, cin_specs) data = (await self.async_simulate_mm_obj(f'c_{pin_name}_{dut.cache_name}', dut, mm)).data cap_fall = data['cap_fall'] cap_rise = data['cap_rise'] cap_avg = (cap_fall + cap_rise) / 2 self.log(f'{pin_name} cap_fall={cap_fall:.4g}, cap_rise={cap_rise:.4g}, ' f'cap_avg={cap_avg:.4g}') return cap_avg
[docs] async def get_delays(self, dut_params: Dict[str, Any]) -> Tuple[float, float]: dut = await self.async_wrapper_dut('LV_SHIFT_DIFF', LevelShifterCoreOutBuffer, dut_params) self.log(f'dut params:\n{pprint.pformat(dut_params, width=100)}') mm = self.make_mm(CombLogicTimingMM, self._td_specs) result = await self.async_simulate_mm_obj(f'td_{dut.cache_name}', dut, mm) timing_data = result.data['timing_data'] out_data = timing_data['out'] td_fall = out_data['cell_fall'].item() td_rise = out_data['cell_rise'].item() self.log(f'delays:\ntd_fall={td_fall:.4g}, td_rise={td_rise:.4g}') return td_fall, td_rise
[docs] def _get_c_in_guess(self, seg_p: int, seg_n: int, w_p: int, w_n: int) -> float: specs = self.dsn_specs c_unit_p: float = specs['c_unit_p'] c_unit_n: float = specs['c_unit_n'] return seg_p * w_p * c_unit_p + seg_n * w_n * c_unit_n