Source code for xbase.layout.array.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 __future__ import annotations

from typing import Any, Optional, Mapping, List, Union, Type, TypeVar, Tuple, cast

import abc
import copy

from pybag.enum import Orientation
from pybag.core import Transform, BBox, BBoxArray

from bag.util.math import HalfInt
from bag.util.immutable import Param, combine_hash, ImmutableSortedDict
from bag.layout.tech import TechInfo
from bag.layout.core import PyLayInstance
from bag.layout.routing.base import TrackID, TrackManager, WDictType, SpDictType, WireArray
from bag.layout.template import TemplateBase, TemplateDB
from bag.layout.routing.grid import RoutingGrid, TrackSpec

from ..enum import ExtendMode
from ..wires import WireLookup
from .tech import ArrayTech
from .data import ArrayLayInfo
from .primitives import ArrayUnit

[docs]A = TypeVar('A', bound='ArrayPlaceInfo')
[docs]T = TypeVar('T', bound='ArrayTech')
[docs]class ArrayPlaceInfo: def __init__(self, parent_grid: RoutingGrid, wire_specs: Mapping[int, Any], tr_widths: WDictType, tr_spaces: SpDictType, top_layer: int, nx: int, ny: int, tech_cls: T, *, conn_layer: Optional[int] = None, tr_specs: Optional[List[TrackSpec]] = None, half_space: bool = True, ext_mode: ExtendMode = ExtendMode.AREA, base_orient: Orientation = Orientation.R0, is_top: bool = True, **kwargs: Any) -> None: self._tech_cls: ArrayTech = tech_cls # update routing grid if conn_layer is None: conn_layer = self._tech_cls.conn_layer if tr_specs is None: tr_specs = self._tech_cls.get_track_specs(conn_layer, top_layer) grid: RoutingGrid = parent_grid.get_copy_with(top_ignore_lay=conn_layer - 1, tr_specs=tr_specs) self._tr_manager = TrackManager(grid, tr_widths, tr_spaces, half_space=half_space) # initialize parameters self._conn_layer = conn_layer self._top_layer = top_layer self._blk_options = Param(kwargs) self._nx = nx self._ny = ny self._base_orient = base_orient self._is_top = is_top self._wire_specs = wire_specs self._ext_mode = ext_mode self._w = None self._h = None self._wlookup = None self._blk_info = None self._hash = None self.commit()
[docs] def commit(self): tmp = self._tech_cls.size_unit_block(self._conn_layer, self._top_layer, self._nx, self._ny, self._tr_manager, self._wire_specs, self._ext_mode, **self._blk_options) self._w: int = tmp[0] self._h: int = tmp[1] self._wlookup: ImmutableSortedDict[int, WireLookup] = ImmutableSortedDict(tmp[2]) self._blk_info: ArrayLayInfo = tmp[3] self._hash = self.compute_hash()
[docs] def compute_hash(self): seed = combine_hash(hash(self._tr_manager), self._conn_layer) seed = combine_hash(seed, self._top_layer) seed = combine_hash(seed, self._nx) seed = combine_hash(seed, self._ny) seed = combine_hash(seed, self._base_orient) seed = combine_hash(seed, self._is_top) seed = combine_hash(seed, self._w) seed = combine_hash(seed, self._h) seed = combine_hash(seed, hash(self._wlookup)) seed = combine_hash(seed, hash(self._blk_options)) return seed
[docs] def __hash__(self) -> int: return self._hash
[docs] def __eq__(self, other: Any) -> bool: return (isinstance(other, self.__class__) and self._tr_manager == other._tr_manager and self._conn_layer == other._conn_layer and self._top_layer == other._top_layer and self._nx == other._nx and self._ny == other._ny and self._w == other._w and self._h == other._h and self._wlookup == other._wlookup and self._blk_options == other._blk_options)
@classmethod @abc.abstractmethod
[docs] def get_tech_cls(cls, tech_info: TechInfo, **kwargs: Any) -> ArrayTech: raise NotImplementedError('Not implemented.')
@classmethod
[docs] def get_conn_layer(cls, tech_info: TechInfo, **kwargs: Any) -> int: return cls.get_tech_cls(tech_info, **kwargs).conn_layer
@classmethod
[docs] def make_place_info(cls: Type[A], grid: RoutingGrid, val: Union[ArrayPlaceInfo, Mapping[str, Any]]) -> A: if isinstance(val, ArrayPlaceInfo): return val return cls(grid, **val)
@property
[docs] def grid(self) -> RoutingGrid: return self._tr_manager.grid
@property
[docs] def tr_manager(self) -> TrackManager: return self._tr_manager
@property
[docs] def blk_options(self) -> Param: return self._blk_options
@property
[docs] def tech_cls(self) -> T: return self._tech_cls
@property
[docs] def top_layer(self) -> int: return self._top_layer
@property
[docs] def conn_layer(self) -> int: return self._conn_layer
@property
[docs] def width(self) -> int: """Width of a unit cell, in resolution units""" return self._w
@property
[docs] def height(self) -> int: """Height of a unit cell, in resolution units""" return self._h
@property
[docs] def nx(self) -> int: return self._nx
@property
[docs] def ny(self) -> int: return self._ny
@property
[docs] def is_top(self) -> bool: return self._is_top
@property
[docs] def blk_info(self) -> ArrayLayInfo: return self._blk_info
[docs] def get_wire_track_info(self, layer: int, wire_name: str, wire_idx: int) -> Tuple[HalfInt, int]: return self._wlookup[layer].get_track_info(wire_name, wire_idx)
[docs] def get_sub_place_info(self, nx: Optional[int] = None, ny: Optional[int] = None, top_layer: Optional[int] = None, row: int = 0, col: int = 0, base_orient: Optional[Orientation] = None, is_top: bool = False) -> ArrayPlaceInfo: ans = copy.copy(self) if nx is not None: ans._nx = nx if ny is not None: ans._ny = ny if top_layer is not None: ans._top_layer = top_layer if base_orient is None: base_orient = self._base_orient if row & 1: base_orient = base_orient.flip_ud() if col & 1: base_orient = base_orient.flip_lr() ans._base_orient = base_orient ans._is_top = is_top ans.commit() return ans
[docs]class ArrayBase(TemplateBase, abc.ABC): def __init__(self, temp_db: TemplateDB, params: Param, **kwargs: Any) -> None: TemplateBase.__init__(self, temp_db, params, **kwargs) self._info: Optional[ArrayPlaceInfo] = None self._unit: Optional[ArrayUnit] = None @property
[docs] def tech_cls(self) -> ArrayTech: return self._info.tech_cls
@property
[docs] def place_info(self) -> Optional[ArrayPlaceInfo]: return self._info
@property
[docs] def conn_layer(self) -> int: return self._info.conn_layer
@property
[docs] def tr_manager(self) -> TrackManager: return self._info.tr_manager
@property
[docs] def nx(self) -> int: return self._info.nx
@property
[docs] def ny(self) -> int: return self._info.ny
[docs] def draw_base(self, info: ArrayPlaceInfo, flip_lr: bool = False, flip_ud: bool = False, commit: bool = None) -> ArrayUnit: self._info = info self.grid = info.grid # By default, only commit unit instances if info is the top ArrayPlaceInfo. # If info is not top (i.e. a subset of another ArrayPlaceInfo), the unit array should be drawn at the top level. if commit is None: commit = info.is_top self._unit = master = self.new_template(ArrayUnit, params=dict(desc=self.tech_cls.desc, blk_info=info.blk_info)) nxo = info.nx // 2 nxe = info.nx - nxo nyo = info.ny // 2 nye = info.ny - nyo spx = 2 * info.width spy = 2 * info.height orient_list = [Orientation.R0, Orientation.MY, Orientation.MX, Orientation.R180] if flip_lr: orient_list = [orient.flip_lr() for orient in orient_list] dx_list = [spx // 2, spx // 2, spx // 2, spx // 2] else: dx_list = [0, spx, 0, spx] if flip_ud: orient_list = [orient.flip_ud() for orient in orient_list] dy_list = [spy // 2, spy // 2, spy // 2, spy // 2] else: dy_list = [0, 0, spy, spy] xform_ll, xform_lr, xform_ul, xform_ur = [Transform(dx, dy, orient) for dx, dy, orient in zip(dx_list, dy_list, orient_list)] self.add_instance(master, inst_name='XLL', xform=xform_ll, nx=nxe, ny=nye, spx=spx, spy=spy, commit=commit) if nxo > 0: self.add_instance(master, inst_name='XLR', xform=xform_lr, nx=nxo, ny=nye, spx=spx, spy=spy, commit=commit) if nyo > 0: self.add_instance(master, inst_name='XUL', xform=xform_ul, nx=nxe, ny=nyo, spx=spx, spy=spy, commit=commit) if nxo > 0: self.add_instance(master, inst_name='XUR', xform=xform_ur, nx=nxo, ny=nyo, spx=spx, spy=spy, commit=commit) bbox = BBox(0, 0, info.nx * info.width, info.ny * info.height) self.set_size_from_bound_box(info.top_layer, bbox) return master
[docs] def get_track_info(self, wire_name: str, wire_idx: int = 0, layer: Optional[int] = None ) -> Tuple[HalfInt, int]: if layer is None: layer = self.conn_layer + 1 return self._info.get_wire_track_info(layer, wire_name, wire_idx)
[docs] def get_track_id(self, wire_name: str, wire_idx: int = 0, layer: Optional[int] = None ) -> TrackID: if layer is None: layer = self.conn_layer + 1 idx, w = self.get_track_info(wire_name, wire_idx=wire_idx, layer=layer) return TrackID(layer, idx, width=w, grid=self.grid)
[docs] def get_track_index(self, wire_name: str, wire_idx: int = 0, layer: Optional[int] = None ) -> HalfInt: return self.get_track_info(wire_name, wire_idx=wire_idx, layer=layer)[0]
[docs] def get_device_port(self, xidx: int, yidx: int, name: str) -> Union[WireArray, BBox, BBoxArray]: w = self._info.width h = self._info.height orient = Orientation.R0 dx = w * xidx dy = h * yidx if (xidx & 1) != 0: dx += w orient = orient.flip_lr() if (yidx & 1) != 0: dy += h orient = orient.flip_ud() xform = Transform(dx, dy, orient) pins = self._unit.get_port(name).get_pins() nx = ny = len(pins) if nx == 1: return pins[0].get_transform(xform) else: if isinstance(pins[0], WireArray): # Combine into 1 WireArray with num > 1 if possible pins = self.connect_wires(pins[0]) return pins[0].get_transform(xform) else: # List of BBox pins = [cast(BBox, bbox) for bbox in pins] # First, try to make BBoxArray with nx > 1 yl = pins[0].yl yh = pins[0].yh w = pins[0].w spx = pins[1].xm - pins[0].xm for pidx, pin in enumerate(pins[1:]): if not (pin.yl == yl and pin.yh == yh and pin.w == w): # cannot be combined into BBoxArray if yl, yh, w are not same break if pidx != nx - 2: if pins[pidx + 2].xm - pin.xm != spx: # cannot be combined into BBoxArray if spx is not same break else: return BBoxArray(pins[0], nx=nx, spx=spx).get_transform(xform) # Next, try to make BBoxArray with ny > 1 xl = pins[0].xl xh = pins[0].xh h = pins[0].h spy = pins[1].ym - pins[0].ym for pidx, pin in enumerate(pins[1:]): if not (pin.xl == xl and pin.xh == xh and pin.h == h): # cannot be combined into BBoxArray if xl, xh, h are not same break if pidx != ny - 2: if pins[pidx + 2].ym - pin.ym != spy: # cannot be combined into BBoxArray if spy is not same break else: return BBoxArray(pins[0], ny=ny, spy=spy).get_transform(xform) # both the for loops encountered break return pins[0].get_transform(xform)
[docs] def add_tile(self, master: ArrayBase, row_idx: int, col_idx: int, *, flip_lr: bool = False, flip_ud: bool = False, commit: bool = True) -> PyLayInstance: self._row_check(row_idx, master.ny, flip_ud) self._col_check(col_idx, master.nx, flip_lr) unit_h, unit_w = self._info.height, self._info.width y0 = row_idx * unit_h x0 = col_idx * unit_w if flip_ud: orient = Orientation.MX y0 += unit_h else: orient = Orientation.R0 if flip_lr: orient = orient.flip_lr() x0 += unit_w return self.add_instance(master, inst_name=f'XR{row_idx}C{col_idx}', xform=Transform(x0, y0, orient), commit=commit)
[docs] def _row_check(self, row_idx: int, num_rows: int, flip: bool): if flip: row_bot, row_top = row_idx - num_rows + 1, row_idx else: row_bot, row_top = row_idx, row_idx + num_rows - 1 if row_bot < 0: raise ValueError(f"Bottom row {row_bot} cannot be less than 0") if row_top >= self.ny: raise ValueError(f"Top row {row_top} cannot be greater than or equal to ny {self.ny}")
[docs] def _col_check(self, col_idx: int, num_cols: int, flip: bool): if flip: col_left, col_right = col_idx - num_cols + 1, col_idx else: col_left, col_right = col_idx, col_idx + num_cols - 1 if col_left < 0: raise ValueError(f"left col {col_left} cannot be less than 0") if col_right > self.nx: raise ValueError(f"right col {col_right} cannot be greater than or equal to nx {self.nx}")