Source code for bag3_digital.design.base

# 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 typing import Any, Mapping, Optional, Sequence, Tuple, Type, Union, Iterable, Dict

import abc
import math

from bag.util.search import BinaryIterator
from bag.simulation.cache import DesignInstance

from xbase.layout.mos.placement.data import (
    TileInfoTable, MOSArrayPlaceInfo, MOSBasePlaceInfo, TilePattern
)
from xbase.layout.mos.base import MOSBase

from bag.simulation.design import DesignerBase

from ..layout.stdcells.util import STDCellWrapper


[docs]class DigitalDesigner(DesignerBase, abc.ABC): def __init__(self, *args: Any, **kwargs: Any) -> None: self._tinfo_table: Optional[TileInfoTable] = None self._dig_tran_specs: Mapping[str, Any] = {} self._sup_values: Mapping[str, Union[float, Mapping[str, float]]] = {} super().__init__(*args, **kwargs) @property
[docs] def arr_info(self) -> MOSArrayPlaceInfo: return self._tinfo_table.arr_info
[docs] def commit(self) -> None: super().commit() specs = self.dsn_specs tile_specs: Mapping[str, Any] = specs['tile_specs'] dig_tran_specs: Mapping[str, Any] = specs['dig_tran_specs'] sup_values: Mapping[str, Union[float, Mapping[str, float]]] = specs['sup_values'] self._tinfo_table: TileInfoTable = TileInfoTable.make_tiles(self.grid, tile_specs) self._dig_tran_specs = dig_tran_specs self._sup_values = sup_values
[docs] def get_tile(self, name: str) -> MOSBasePlaceInfo: return self._tinfo_table[name]
[docs] def make_tile_pattern(self, tiles: Iterable[Mapping[str, Any]] ) -> Tuple[TilePattern, TileInfoTable]: return self._tinfo_table.make_tile_pattern(tiles), self._tinfo_table
[docs] def get_dig_tran_specs(self, pwr_domain: Mapping[str, Tuple[str, str]], supply_map: Mapping[str, str], pin_values: Optional[Mapping[str, int]] = None, reset_list: Optional[Sequence[Tuple[str, bool]]] = None, diff_list: Optional[Sequence[Tuple[Sequence[str], Sequence[str]]]] = None ) -> Dict[str, Any]: sup_values = {k: self._sup_values[v] for k, v in supply_map.items()} ans = dict(pwr_domain=pwr_domain, sup_values=sup_values, **self._dig_tran_specs) if pin_values: ans['pin_values'] = pin_values else: ans['pin_values'] = {} if reset_list: ans['reset_list'] = reset_list if diff_list: ans['diff_list'] = diff_list return ans
[docs] async def async_wrapper_dut(self, impl_cell: str, dut_cls: Type[MOSBase], dut_params: Mapping[str, Any], draw_taps: bool = True, pwr_gnd_list: Optional[Sequence[Tuple[str, str]]] = None, extract: Optional[bool] = None, name_prefix: str = '', name_suffix: str = '', flat: bool = False ) -> DesignInstance: wrap_params = dict(cls_name=dut_cls.get_qualified_name(), draw_taps=draw_taps, pwr_gnd_list=pwr_gnd_list, params=dut_params) return await self.async_new_dut(impl_cell, STDCellWrapper, wrap_params, extract=extract, name_prefix=name_prefix, name_suffix=name_suffix, flat=flat)
[docs] async def async_batch_wrapper_dut(self, dut_specs: Sequence[Mapping[str, Any]], ) -> Sequence[DesignInstance]: wrap_specs = [] for info in dut_specs: cls_name = info['dut_cls'].get_qualified_name() draw_taps = info.get('draw_taps', True) pwr_gnd_list = info.get('pwr_gnd_list', None) wrap_params = dict(cls_name=cls_name, draw_taps=draw_taps, pwr_gnd_list=pwr_gnd_list, params=info['dut_params']) wrap_info = dict(**info) wrap_info['dut_cls'] = STDCellWrapper wrap_info['dut_params'] = wrap_params wrap_specs.append(wrap_info) return await self.async_batch_dut(wrap_specs)
[docs]class BinSearchSegWidth(abc.ABC): def __init__(self, w_list: Sequence[int], err_targ: float, search_step: int = 1) -> None: self._w_list = w_list self._err_targ = err_targ self._search_step = search_step @abc.abstractmethod
[docs] def get_bin_search_info(self, data: Any) -> Tuple[float, bool]: pass
@abc.abstractmethod
[docs] def get_error(self, data: Any) -> float: pass
@abc.abstractmethod
[docs] def set_size(self, seg: int, w: int) -> None: pass
@abc.abstractmethod
[docs] async def get_data(self, seg: int, w: int) -> Any: pass
[docs] async def get_seg_width(self, w: int, seg_min: int, seg_max: Optional[int], data_min: Optional[Any], data_max: Optional[Any], no_throw: bool = False) -> Tuple[Any, int, int]: data, seg, a_min, a_max = await self._search_helper(w, seg_min, seg_max, data_min, data_max) err = self.get_error(data) if err <= self._err_targ: self.set_size(seg, w) return data, seg, w # tweak width to reduce error best_err = [err, seg, w, data] for w_new in reversed(self._w_list): if w_new == w: # skip to new width continue # try to find seg_min lower bound seg_min = max(1, int(math.floor(a_min / w_new))) seg_max = None data_min = await self.get_data(seg_min, w_new) low_bnd = self.get_bin_search_info(data_min)[1] data_max = None while not low_bnd: seg_max = seg_min data_max = data_min next_seg_min = max(seg_min // 2, 1) if next_seg_min == seg_min: # we're stuck, break seg_min = None break data_min = await self.get_data(next_seg_min, w_new) low_bnd = self.get_bin_search_info(data_min)[1] seg_min = next_seg_min if seg_min is None: # weird, no minimum solution found, ignore this width continue if seg_max is None: # try to see if we can get upper bound from a_max seg_test = int(math.ceil(a_max / w_new)) data_test = await self.get_data(seg_test, w_new) low_bnd = self.get_bin_search_info(data_test)[1] if low_bnd: # seg_test is a lower bound, not a upper bound seg_min = seg_test data_min = data_test else: # seg_test is a upper bound seg_max = seg_test data_max = data_test # do binary search at this width data, seg, a_min_new, a_max_new = await self._search_helper( w_new, seg_min, seg_max, data_min, data_max) err = self.get_error(data) if err <= self._err_targ: self.set_size(seg, w) return data, seg, w_new elif err < best_err[0]: best_err[0] = err best_err[1] = seg best_err[2] = w_new best_err[3] = data a_min = min(a_min, a_min_new) a_max = max(a_max, a_max_new) self.set_size(best_err[1], best_err[2]) if no_throw: return best_err[3], best_err[1], best_err[2] else: raise ValueError('Cannot meet error spec. '
f'Best err = {best_err[0]:.4g} at seg={best_err[1]}, w={best_err[2]}')
[docs] async def _search_helper(self, w: int, seg_min: int, seg_max: Optional[int], data_min: Optional[Any], data_max: Optional[Any], ) -> Tuple[Any, int, int, int]: # first, binary search on segments without changing width bin_iter = BinaryIterator(seg_min, seg_max, search_step=self._search_step) bval_min = bval_max = None if data_max is not None: bval_max = self.get_bin_search_info(data_max)[0] if data_min is not None: bval_min = self.get_bin_search_info(data_min)[0] bin_iter.set_current(seg_min) bin_iter.up(val=bval_min) elif seg_max is not None and data_max is not None: bin_iter.set_current(seg_max) bin_iter.down(val=bval_max) bounds = [[seg_min, bval_min, data_min], [seg_max, bval_max, data_max]] while bin_iter.has_next(): cur_seg = bin_iter.get_next() cur_data = await self.get_data(cur_seg, w) cur_bval, up = self.get_bin_search_info(cur_data) if up: bounds[0][0] = cur_seg bounds[0][1] = cur_bval bounds[0][2] = cur_data bin_iter.up(val=cur_bval) else: bounds[1][0] = cur_seg bounds[1][1] = cur_bval bounds[1][2] = cur_data bin_iter.down(val=cur_bval) if bounds[1][1] is None: idx = 0 seg_min = seg_max = bounds[0][0] elif bounds[0][1] is None: idx = 1 seg_min = seg_max = bounds[1][0] else: idx = int(abs(bounds[1][1]) < abs(bounds[0][1])) seg_min = bounds[0][0] seg_max = bounds[1][0] opt_bnd = bounds[idx] opt_seg = opt_bnd[0] opt_data = opt_bnd[2] a_min = seg_min * w a_max = seg_max * w return opt_data, opt_seg, a_min, a_max