Source code for xbase.layout.mos.guardring

# 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, Dict, cast, Type, Optional, List, Tuple

from bisect import bisect_left

from bag.design.module import Module
from bag.util.immutable import Param
from bag.util.importlib import import_class
from bag.layout.routing.base import WireArray
from bag.layout.template import TemplateDB, PyLayInstance

from ..enum import MOSWireType, MOSType

from .placement.data import TilePatternElement, TilePattern
from .base import MOSBase


[docs]class GuardRing(MOSBase): """A layout generator that adds substrate rows on top and bottom of a MOSBase instance.""" def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: MOSBase.__init__(self, temp_db, params, **kwargs) self._sch_cls: Optional[Type[Module]] = None self._core: Optional[MOSBase] = None @property
[docs] def core(self) -> MOSBase: return self._core
@classmethod
[docs] def get_params_info(cls) -> Dict[str, str]: return dict( cls_name='instance class name.', params='parameters for the instance.', pmos_gr='pmos guard ring tile name.', nmos_gr='nmos guard ring tile name.', sep_ncol='tuple of separation between guard ring edge and block.', edge_ncol='Number of columns on guard ring edge. Use 0 for default.',
) @classmethod
[docs] def get_default_param_values(cls) -> Dict[str, Any]: return dict( pmos_gr='pgr', nmos_gr='ngr', sep_ncol=(-1, -1), edge_ncol=0,
)
[docs] def get_schematic_class_inst(self) -> Optional[Type[Module]]: return self._sch_cls
[docs] def get_layout_basename(self) -> str: cls_name: str = self.params['cls_name'] cls_name = cls_name.split('.')[-1] return cls_name + 'GuardRing'
[docs] def draw_layout(self) -> None: params = self.params cls_name: str = params['cls_name'] params: Param = params['params'] pmos_gr: str = params['pmos_gr'] nmos_gr: str = params['nmos_gr'] sep_ncol: Tuple[int, int] = params['sep_ncol'] edge_ncol: int = params['edge_ncol'] gen_cls = cast(Type[MOSBase], import_class(cls_name)) master: MOSBase = self.new_template(gen_cls, params=params) _, sup_list = self.draw_guard_ring(master, pmos_gr, nmos_gr, sep_ncol, edge_ncol) for tile_idx, (vss_list, vdd_list) in enumerate(sup_list): for ridx, warr in enumerate(vss_list): self.add_pin(f'VSS_guard_{tile_idx}_{ridx}', warr) for ridx, warr in enumerate(vdd_list): self.add_pin(f'VDD_guard_{tile_idx}_{ridx}', warr)
[docs] def draw_guard_ring(self, master: MOSBase, pmos_gr: str, nmos_gr: str, sep_ncol: Tuple[int, int], edge_ncol: int, export_pins: bool = True ) -> Tuple[PyLayInstance, List[Tuple[List[WireArray], List[WireArray]]]]: self._core = master self._sch_cls = master.get_schematic_class_inst() tinfo_table = master.tile_table # construct TilePattern object, and call draw_base() bot_pinfo = tinfo_table[self._get_gr_name(master, False)] top_pinfo = tinfo_table[self._get_gr_name(master, True)] # construct TilePattern object, and call draw_base() tile_list = [TilePatternElement(bot_pinfo), master.get_tile_pattern_element(), TilePatternElement(top_pinfo, flip=True)] self.draw_base((TilePattern(tile_list), tinfo_table)) # get nmos/pmos guard ring type pmos_sub_type = tinfo_table[pmos_gr].get_row_place_info(0).row_info.row_type nmos_sub_type = tinfo_table[nmos_gr].get_row_place_info(0).row_info.row_type tech_cls = self.tech_cls if edge_ncol == 0: edge_ncol = tech_cls.gr_edge_col sep_l, sep_r = sep_ncol if sep_l < 0: sep_l = tech_cls.sub_sep_col if sep_r < 0: sep_r = tech_cls.sub_sep_col ncol = master.num_cols + 2 * edge_ncol + sep_l + sep_r ntile = master.num_tile_rows + 2 master_col = edge_ncol + sep_l if self.has_double_guard_ring: ncol += 2 * edge_ncol + sep_l + sep_r master_col += edge_ncol + sep_l inst = self.add_tile(master, 1, master_col) ncol += ncol & 1 # make ncol even for symmetry self.set_mos_size(num_cols=ncol, num_tiles=ntile) if export_pins: for name in inst.port_names_iter(): self.reexport(inst.get_port(name)) sup_list = [] vdd_vm_list = [] vss_vm_list = [] vdd_hm_keys = [] vss_hm_keys = [] vdd_hm_dict = {} vss_hm_dict = {} grid = self.grid hm_layer = self.conn_layer + 1 for tile_idx in range(ntile): cur_pinfo = self.get_tile_pinfo(tile_idx) vdd_hm_list = [] vss_hm_list = [] for ridx in range(cur_pinfo.num_rows): row_info = cur_pinfo.get_row_place_info(ridx).row_info row_type = row_info.row_type if row_type.is_substrate and row_info.guard_ring: tid = self.get_track_id(ridx, MOSWireType.DS, 'guard', tile_idx=tile_idx) coord = grid.track_to_coord(hm_layer, tid.base_index) if row_info.guard_ring_col: # both guard_ring and guard_ring_col are True, # so this is the top or bottom row of inner guard ring assert self.has_double_guard_ring, 'If the PDK does not have double guard ring, the mos row ' \ 'cannot have both guard_ring=True and guard_ring_col=True' sub_col = edge_ncol + sep_l sub_seg = ncol - 2 * edge_ncol - sep_l - sep_r sub_lr_type = pmos_sub_type if row_type.is_n_plus else nmos_sub_type sub_l = self.add_substrate_contact(ridx, 0, tile_idx=tile_idx, seg=edge_ncol, guard_ring=True, sub_type=sub_lr_type) sub_r = self.add_substrate_contact(ridx, ncol, tile_idx=tile_idx, seg=edge_ncol, guard_ring=True, flip_lr=True, sub_type=sub_lr_type) if sub_lr_type.is_pwell: vss_vm_list.extend([sub_l, sub_r]) else: vdd_vm_list.extend([sub_l, sub_r]) else: # this is the top or bottom row of outer guard ring sub_col = 0 sub_seg = ncol sub = self.add_substrate_contact(ridx, sub_col, tile_idx=tile_idx, seg=sub_seg) warr = self.connect_to_tracks(sub, tid) if row_type.is_pwell: vss_hm_list.append(warr) vss_hm_keys.append(coord) vss_hm_dict[coord] = warr else: vdd_hm_list.append(warr) vdd_hm_keys.append(coord) vdd_hm_dict[coord] = warr elif row_info.guard_ring_col: if row_type.is_substrate: sub_type = pmos_sub_type if row_type.is_n_plus else nmos_sub_type else: sub_type = nmos_sub_type if row_type.is_n_plus else pmos_sub_type sub0 = self.add_substrate_contact(ridx, 0, tile_idx=tile_idx, seg=edge_ncol, guard_ring=True, sub_type=sub_type) sub1 = self.add_substrate_contact(ridx, ncol, tile_idx=tile_idx, seg=edge_ncol, guard_ring=True, flip_lr=True, sub_type=sub_type) if sub_type.is_pwell: vss_vm_list.extend([sub0, sub1]) else: vdd_vm_list.extend([sub0, sub1]) if self.has_double_guard_ring: sub_lr_type = MOSType.ptap if sub_type is MOSType.ntap else MOSType.ntap sub_l = self.add_substrate_contact(ridx, edge_ncol + sep_l, tile_idx=tile_idx, seg=edge_ncol, guard_ring=True, sub_type=sub_lr_type) sub_r = self.add_substrate_contact(ridx, ncol - edge_ncol - sep_r, tile_idx=tile_idx, seg=edge_ncol, guard_ring=True, flip_lr=True, sub_type=sub_lr_type) if sub_lr_type.is_pwell: vss_vm_list.extend([sub_l, sub_r]) else: vdd_vm_list.extend([sub_l, sub_r]) else: self.error('mos row must have guard_ring=True or guard_ring_col=True.') sup_list.append((vss_hm_list, vdd_hm_list)) self._connect_vm(vss_vm_list, vss_hm_keys, vss_hm_dict) self._connect_vm(vdd_vm_list, vdd_hm_keys, vdd_hm_dict) self.sch_params = master.sch_params return inst, sup_list
[docs] def _connect_vm(self, warr_list: List[WireArray], keys: List[int], table: Dict[int, WireArray]) -> None: keys.sort() for warr in warr_list: idx = bisect_left(keys, warr.middle) if idx == 0: raise ValueError('Cannot find a lower horizontal wire to connect guard ring edge') self.connect_to_track_wires(warr, [table[keys[idx - 1]], table[keys[idx]]])
[docs] def _get_gr_name(self, master: MOSBase, is_top: bool) -> str: if is_top: tinfo, _, flip = master.get_tile_info(master.num_tile_rows - 1) ridx = 0 if flip else tinfo.num_rows - 1 else: tinfo, _, flip = master.get_tile_info(0) ridx = tinfo.num_rows - 1 if flip else 0 mos_type = tinfo.get_row_place_info(ridx).row_info.row_type # if mos_type.is_substrate: # raise ValueError('top and bottom row must be transistor row.') # return self.params['nmos_gr'] if mos_type.is_n_plus else self.params['pmos_gr'] if mos_type.is_substrate: return self.params['pmos_gr'] if mos_type.is_n_plus else self.params['nmos_gr'] else: return self.params['nmos_gr'] if mos_type.is_n_plus else self.params['pmos_gr']