Source code for xbase.layout.mos.data

# 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, Tuple, Optional, Mapping, Sequence

from dataclasses import dataclass, field
from bisect import bisect_left

from bag.util.math import HalfInt
from bag.util.immutable import ImmutableSortedDict, ImmutableList
from bag.layout.routing.base import WireArray, TrackManager

from ..data import LayoutInfo
from ..wires import WireData, WireLookup
from ..enum import MOSType, MOSWireType, MOSPortType, Alignment


@dataclass(eq=True, frozen=True, init=False)
[docs]class MOSRowSpecs: """specification for a transistor row. Includes unit transistor dimensions and wire info for determining track locations on conn_layer + 1"""
[docs] mos_type: MOSType
[docs] width: int
[docs] threshold: str
[docs] bot_wires: WireData
[docs] mid_wires: WireData
[docs] top_wires: WireData
[docs] options: ImmutableSortedDict[str, Any]
[docs] flip: bool
[docs] sub_width: int
[docs] double_gate: bool
def __init__(self, mos_type: MOSType, width: int, threshold: str, bot_wires: WireData, top_wires: WireData, mid_wires: Optional[WireData] = None, options: Optional[Mapping[str, Any]] = None, flip: bool = False, sub_width: int = 0, double_gate: bool = False) -> None: if sub_width == 0 or mos_type.is_substrate: sub_width = width # work around: this is how you set attributes for frozen data classes object.__setattr__(self, 'mos_type', mos_type) object.__setattr__(self, 'width', width) object.__setattr__(self, 'threshold', threshold) object.__setattr__(self, 'bot_wires', bot_wires) object.__setattr__(self, 'mid_wires', mid_wires) object.__setattr__(self, 'top_wires', top_wires) object.__setattr__(self, 'options', ImmutableSortedDict(options)) object.__setattr__(self, 'flip', flip) object.__setattr__(self, 'sub_width', sub_width) object.__setattr__(self, 'double_gate', double_gate) @classmethod
[docs] def make_row_specs(cls, val: Mapping[str, Any]) -> MOSRowSpecs: mos_type = MOSType[val['mos_type']] width = val['width'] threshold = val['threshold'] bot_wires = val['bot_wires'] mid_wires = val.get('mid_wires') top_wires = val['top_wires'] options = val.get('options', None) flip = val.get('flip', False) sub_width = val.get('sub_width', 0) double_gate = val.get('double_gate', False) if double_gate: ds_type = MOSWireType.DS.name g_type = MOSWireType.G.name g2_type = MOSWireType.G2.name bot_wd = WireData.make_wire_data(bot_wires, Alignment.UPPER_COMPACT, g_type) mid_wd = WireData.make_wire_data(mid_wires, Alignment.CENTER_COMPACT, ds_type) top_wd = WireData.make_wire_data(top_wires, Alignment.LOWER_COMPACT, g2_type) return MOSRowSpecs(mos_type, width, threshold, bot_wd, top_wd, mid_wires=mid_wd, options=options, flip=flip, sub_width=sub_width, double_gate=double_gate) else: ds_type = MOSWireType.DS_GATE.name g_type = MOSWireType.G.name if flip: bot_ptype = ds_type top_ptype = g_type else: bot_ptype = g_type top_ptype = ds_type bot_wd = WireData.make_wire_data(bot_wires, Alignment.UPPER_COMPACT, bot_ptype) mid_wd = WireData.make_wire_data({'data': []}, Alignment.CENTER_COMPACT, bot_ptype) top_wd = WireData.make_wire_data(top_wires, Alignment.LOWER_COMPACT, top_ptype) return MOSRowSpecs(mos_type, width, threshold, bot_wd, top_wd, mid_wires=mid_wd, options=options, flip=flip, sub_width=sub_width, double_gate=double_gate)
@property
[docs] def ignore_bot_vm_sp_le(self) -> bool: return self.options.get('ignore_bot_vm_sp_le', False)
@property
[docs] def ignore_top_vm_sp_le(self) -> bool: return self.options.get('ignore_top_vm_sp_le', False)
@dataclass(eq=True, frozen=True)
[docs]class RowExtInfo: """Information about top or bottom boundary of a transistor block. This class contains information needed to draw an extension region adjacent to the transistor row. """
[docs] row_type: MOSType
[docs] threshold: str
[docs] info: ImmutableSortedDict[str, Any]
@classmethod
[docs] def from_dict(cls, table: Mapping[str, Any]) -> RowExtInfo: return RowExtInfo(MOSType[table['row_type']], table['threshold'], ImmutableSortedDict(table['info']))
[docs] def __getitem__(self, item: str) -> Any: return self.info[item]
[docs] def get(self, item: str, def_val: Optional[Any] = None) -> Any: return self.info.get(item, def_val)
[docs] def copy_with(self, row_type: Optional[MOSType] = None, threshold: Optional[str] = None, **kwargs) -> RowExtInfo: if row_type is None: row_type = self.row_type if threshold is None: threshold = self.threshold info_dict = self.info.to_dict() info_dict.update(kwargs) return RowExtInfo(row_type, threshold, ImmutableSortedDict(info_dict))
[docs] def to_dict(self) -> Mapping[str, Any]: return dict( row_type=self.row_type.name, threshold=self.threshold, info=self.info.to_yaml(),
) @dataclass(eq=True, frozen=True)
[docs]class BlkExtInfo: """Information about top or bottom boundary of a transistor row"""
[docs] row_type: MOSType
[docs] threshold: str
[docs] guard_ring: bool
[docs] fg_dev: ImmutableList[Tuple[int, MOSType]]
[docs] info: ImmutableSortedDict[str, Any]
[docs] def __getitem__(self, item: str) -> Any: return self.info[item]
[docs] def __contains__(self, item: str) -> bool: return item in self.info
@property
[docs] def fg(self) -> int: return sum((val[0] for val in self.fg_dev))
[docs] def get(self, item: str, def_val: Optional[Any] = None) -> Any: return self.info.get(item, def_val)
@dataclass(eq=True, frozen=True, init=False)
[docs]class MOSEdgeInfo: """Information about left or right boundary of a transistor row. """
[docs] info: ImmutableSortedDict[str, Any]
def __init__(self, **kwargs: Any) -> None: object.__setattr__(self, 'info', ImmutableSortedDict(kwargs))
[docs] def __bool__(self) -> bool: return bool(self.info)
[docs] def get(self, item: str, def_val: Optional[Any] = None) -> Any: return self.info.get(item, def_val)
[docs] def __getitem__(self, item: str) -> Any: return self.info[item]
[docs] def copy_with(self, **kwargs) -> MOSEdgeInfo: info_dict = self.info.to_dict() info_dict.update(kwargs) return MOSEdgeInfo(**info_dict)
@dataclass(eq=True, frozen=True)
[docs]class MOSRowInfo: """Information about a transistor row."""
[docs] lch: int
[docs] width: int
[docs] sub_width: int
[docs] row_type: MOSType
[docs] threshold: str
[docs] height: int
[docs] flip: bool
[docs] top_ext_info: RowExtInfo
[docs] bot_ext_info: RowExtInfo
[docs] info: ImmutableSortedDict[str, Any]
# yt, yb for each connection
[docs] g_conn_y: Tuple[int, int] = (0, 0)
[docs] g_m_conn_y: Tuple[int, int] = (0, 0)
[docs] ds_conn_y: Tuple[int, int] = (0, 0)
[docs] ds_m_conn_y: Tuple[int, int] = (0, 0)
[docs] ds_g_conn_y: Tuple[int, int] = (0, 0)
[docs] sub_conn_y: Tuple[int, int] = (0, 0)
[docs] guard_ring: bool = False
[docs] guard_ring_col: bool = False
[docs] double_gate: bool = False
[docs] g2_conn_y: Tuple[int, int] = (0, 0)
[docs] g2_m_conn_y: Tuple[int, int] = (0, 0)
@classmethod
[docs] def from_dict(cls, table: Mapping[str, Any]) -> MOSRowInfo: row_type = MOSType[table['row_type']] top_ext_info = RowExtInfo.from_dict(table['top_ext_info']) bot_ext_info = RowExtInfo.from_dict(table['bot_ext_info']) return MOSRowInfo(table['lch'], table['width'], table['sub_width'], row_type, table['threshold'], table['height'], table['flip'], top_ext_info, bot_ext_info, ImmutableSortedDict(table['info']), table['g_conn_y'], table['g_m_conn_y'], table['ds_conn_y'], table['ds_m_conn_y'], table['ds_g_conn_y'], table['sub_conn_y'], guard_ring=table.get('guard_ring', False), guard_ring_col=table.get('guard_ring_col', False), double_gate=table.get('double_gate', False), g2_conn_y=table.get('g2_conn_y', (0, 0)), g2_m_conn_y=table.get('g2_m_conn_y', (0, 0)))
@property
[docs] def bot_conn_types(self) -> Sequence[MOSWireType]: """Return sequence of bottom wire connection types. index 0 is the default type. """ if self.flip: if self.double_gate: return MOSWireType.G2, MOSWireType.G2_MATCH return MOSWireType.DS_GATE, MOSWireType.DS, MOSWireType.DS_MATCH else: return MOSWireType.G, MOSWireType.G_MATCH
@property
[docs] def top_conn_types(self) -> Sequence[MOSWireType]: """Return sequence of top wire connection types. index 0 is the default type. """ if self.flip: return MOSWireType.G, MOSWireType.G_MATCH else: if self.double_gate: return MOSWireType.G2, MOSWireType.G2_MATCH return MOSWireType.DS_GATE, MOSWireType.DS, MOSWireType.DS_MATCH
@property
[docs] def mid_conn_types(self) -> Sequence[MOSWireType]: """Return sequence of top wire connection types. index 0 is the default type. """ if self.double_gate: return MOSWireType.DS_GATE, MOSWireType.DS, MOSWireType.DS_MATCH raise RuntimeError("trying to use mid conn, when its not a double gate")
[docs] def get_ext_info(self, top_edge: bool) -> RowExtInfo: return self.top_ext_info if top_edge ^ self.flip else self.bot_ext_info
[docs] def get_conn_y(self, wtype: MOSWireType) -> Tuple[int, int]: if wtype is MOSWireType.G: ans = self.g_conn_y elif wtype is MOSWireType.G_MATCH: ans = self.g_m_conn_y elif wtype is MOSWireType.DS: ans = self.ds_conn_y elif wtype is MOSWireType.DS_MATCH: ans = self.ds_m_conn_y elif wtype is MOSWireType.DS_GATE: ans = self.ds_g_conn_y elif wtype is MOSWireType.G2: ans = self.g2_conn_y elif wtype is MOSWireType.G2_MATCH: ans = self.g2_m_conn_y else: raise ValueError(f'Unsupported MOSWireType: {wtype.name}') if self.flip: return self.height - ans[1], self.height - ans[0] return ans
[docs] def get_all_conn_y(self, wtype: MOSWireType) -> Sequence[Tuple[int, int]]: """get list of all possible Y coordinates the given wire type could connect to""" if wtype is MOSWireType.G or wtype is MOSWireType.G_MATCH: ans = [self.g_conn_y] elif wtype is MOSWireType.G2 or wtype is MOSWireType.G2_MATCH: ans = [self.g2_conn_y] elif wtype is MOSWireType.DS: ans = [self.ds_conn_y] elif wtype is MOSWireType.DS_MATCH or wtype is MOSWireType.DS_GATE: ans = [self.ds_conn_y, self.ds_g_conn_y] else: raise ValueError(f'Unsupported MOSWireType: {wtype.name}') if self.flip: return [(self.height - v1, self.height - v0) for v0, v1 in ans] return ans
[docs] def __getitem__(self, name: str) -> Any: return self.info[name]
[docs] def to_dict(self) -> Mapping[str, Any]: key_list = ['lch', 'width', 'sub_width', 'threshold', 'height', 'flip', 'g_conn_y', 'g_m_conn_y', 'ds_conn_y', 'ds_m_conn_y', 'ds_g_conn_y', 'sub_conn_y', 'guard_ring', 'double_gate', 'g2_conn_y', 'g2_m_conn_y'] ans = {key: getattr(self, key) for key in key_list} ans['row_type'] = self.row_type.name ans['top_ext_info'] = self.top_ext_info.to_dict() ans['bot_ext_info'] = self.bot_ext_info.to_dict() ans['info'] = self.info.to_yaml() return ans
@dataclass(eq=True, frozen=True)
[docs]class MOSPorts:
[docs] g: WireArray
[docs] d: WireArray
[docs] s: WireArray
[docs] shorted_ports: ImmutableList[MOSPortType]
[docs] m: Optional[WireArray] = None
[docs] g2: Optional[WireArray] = None
@property
[docs] def num_s(self) -> int: return self.s.track_id.num
@property
[docs] def num_d(self) -> int: return self.d.track_id.num
@property
[docs] def num_g(self) -> int: return self.g.track_id.num
@property
[docs] def g0(self) -> WireArray: return self.g[0::2]
@property
[docs] def g1(self) -> WireArray: return self.g[1::2]
[docs] def __getitem__(self, item: MOSPortType) -> WireArray: if item is MOSPortType.G: return self.g if item is MOSPortType.D: return self.d return self.s
@dataclass(eq=True, frozen=True)
[docs]class NAND2Ports:
[docs] g0: Sequence[WireArray]
[docs] g1: Sequence[WireArray]
[docs] d: WireArray
[docs] s: WireArray
[docs] m: Optional[WireArray]
@dataclass(eq=True, frozen=True)
[docs]class RowPlaceInfo: """Information about a transistor row, placement data included. (yb_blk, yt_blk) describe the y dimensions of the block, i.e. the MOS row. (yb, yt) describe the y dimensions of the block plus any edge extensions. """
[docs] row_info: MOSRowInfo
[docs] bot_wires: WireLookup = field(compare=False)
[docs] top_wires: WireLookup = field(compare=False)
[docs] yb: int
[docs] yt: int
[docs] yb_blk: int
[docs] yt_blk: int
[docs] y_conn: Tuple[int, int]
[docs] mid_wires: Optional[WireLookup] = None
@classmethod
[docs] def from_dict(cls, table: Mapping[str, Any]) -> RowPlaceInfo: row_info: Mapping[str, Any] = table['row_info'] bot_wires: Mapping[Tuple[str, int], Tuple[float, int]] = table['bot_wires'] top_wires: Mapping[Tuple[str, int], Tuple[float, int]] = table['top_wires'] yb: int = table['yb'] yt: int = table['yt'] yb_blk: int = table['yb_blk'] yt_blk: int = table['yt_blk'] y_conn: Tuple[int, int] = tuple(table['y_conn']) mid_wires: Optional[Mapping[Tuple[str, int], Tuple[float, int]]] = table.get('mid_wires', {}) return RowPlaceInfo(MOSRowInfo.from_dict(row_info), WireLookup.from_dict(bot_wires), WireLookup.from_dict(top_wires), yb, yt, yb_blk, yt_blk, y_conn, WireLookup.from_dict(mid_wires))
[docs] def to_dict(self) -> Mapping[str, Any]: return dict( row_info=self.row_info.to_dict(), bot_wires=self.bot_wires.to_dict(), top_wires=self.top_wires.to_dict(), yb=self.yb, yt=self.yt, yb_blk=self.yb_blk, yt_blk=self.yt_blk, y_conn=self.y_conn, mid_wires=self.mid_wires.to_dict()
)
[docs] def get_extend(self, tr_pitch, delta: int, top_edge: bool, shared: Sequence[str] ) -> RowPlaceInfo: """Returns a copy of self, with either the top or bottom edge of the bound box extended delta in the y direction. Uses tr_pitch to quantize delta for shifting the tracks. `shared` lists tracks shared with block abut on the extending edge. When top_edge==True, the top edge is shifted by delta and the top tracks are shifted. When top_edge==False, the top edge is shifted by delta, the active area is shifted by delta, and the tracks are shifted by delta. This maintains alignment with y=0. """ tr_shift = HalfInt((2 * delta) // tr_pitch) if top_edge: top_wires = self.top_wires.get_move_shared(tr_shift, shared) return RowPlaceInfo(self.row_info, self.bot_wires, top_wires, self.yb, self.yt + delta, self.yb_blk, self.yt_blk, self.y_conn, mid_wires=self.mid_wires) else: return RowPlaceInfo(self.row_info, self.bot_wires.get_move(tr_shift, shared), self.top_wires.get_move(tr_shift, []), self.yb, self.yt + delta, self.yb_blk + delta, self.yt_blk + delta, (self.y_conn[0] + delta, self.y_conn[1] + delta), mid_wires=self.mid_wires.get_move(tr_shift, []))
[docs] def get_move(self, tr_pitch: int, delta: int) -> RowPlaceInfo: """Returns a copy of self, shifted delta in the y direction. Uses tr_pitch to quantize delta for shifting the tracks. """ tr_shift = HalfInt((2 * delta) // tr_pitch) el = [] return RowPlaceInfo(self.row_info, self.bot_wires.get_move(tr_shift, el), self.top_wires.get_move(tr_shift, el), self.yb + delta, self.yt + delta, self.yb_blk + delta, self.yt_blk + delta, (self.y_conn[0] + delta, self.y_conn[1] + delta), mid_wires=self.mid_wires.get_move(tr_shift, el))
[docs] def get_ext_margin(self, top_edge: bool) -> int: return self.yt - self.yt_blk if top_edge else self.yb_blk - self.yb
[docs] def get_abut_info(self, rhs: RowPlaceInfo, top_edge: bool, rhs_top_edge: bool, shared: Sequence[str], rhs_shared: Sequence[str], tr_manager: TrackManager, layer: int) -> Tuple[int, RowExtInfo, RowExtInfo]: """Returns the margin needed to abut this row with the given row. Parameters ---------- rhs : RowPlaceInfo the other row. top_edge : bool True if rhs is abutting to top edge of this row. rhs_top_edge : bool True if we're abutting to top edge of the other row. shared: Sequence[str] list of edge wires shared with the other tile. rhs_shared : Sequence[str] list of edge wires from the other tile shared with this tile. tr_manager : TrackManager the TrackManager object layer : int layer ID of the wires. Returns ------- margin : int the margin in resolution units. einfo1 : RowExtInfo the row extension information object of this RowPlaceInfo. einfo2 : RowExtInfo the row extension information object of the other RowPlaceInfo. """ grid = tr_manager.grid # get margin needed between wires if top_edge: wires = self.top_wires if self.top_wires else self.bot_wires conn_margin = self.yt - self.y_conn[1] else: wires = self.bot_wires if self.bot_wires else self.top_wires conn_margin = self.y_conn[0] - self.yb if rhs_top_edge: rhs_wires = rhs.top_wires if rhs.top_wires else rhs.bot_wires rhs_conn_margin = rhs.yt - rhs.y_conn[1] else: rhs_wires = rhs.bot_wires if rhs.bot_wires else rhs.top_wires rhs_conn_margin = rhs.y_conn[0] - rhs.yb conn_m, wire_m = wires.get_wire_margin_info(grid, layer, self.yb, self.yt, top_edge, shared) rhs_conn_m, rhs_wire_m = rhs_wires.get_wire_margin_info(grid, layer, rhs.yb, rhs.yt, rhs_top_edge, rhs_shared) conn_margin = min(conn_margin, conn_m) rhs_conn_margin = min(rhs_conn_margin, rhs_conn_m) pitch = grid.get_track_pitch(layer) wire_margin = 0 for name1, margin1 in wire_m: for name2, margin2 in rhs_wire_m: tot_space = int(pitch * tr_manager.get_sep(layer, (name1, name2))) wire_margin = max(wire_margin, tot_space - margin1 - margin2) conn_sp_le = grid.get_line_end_space(layer - 1, 1) wire_margin = max(wire_margin, conn_sp_le - conn_margin - rhs_conn_margin) return (wire_margin, self.row_info.get_ext_info(top_edge), rhs.row_info.get_ext_info(rhs_top_edge))
@dataclass(eq=True, frozen=True)
[docs]class MOSBaseEndInfo:
[docs] h_mos_end: Tuple[int, int]
[docs] h_blk: Tuple[int, int]
@dataclass(eq=True, frozen=True)
[docs]class MOSLayInfo: """The transistor block layout information object."""
[docs] lay_info: LayoutInfo
[docs] left_info: MOSEdgeInfo
[docs] right_info: MOSEdgeInfo
[docs] top_info: BlkExtInfo
[docs] bottom_info: BlkExtInfo
[docs] g_info: Tuple[int, int, int]
[docs] d_info: Tuple[int, int, int]
[docs] s_info: Tuple[int, int, int]
[docs] shorted_ports: ImmutableList[MOSPortType]
[docs] m_info: Optional[Tuple[int, int, int]] = None
@dataclass(eq=True, frozen=True)
[docs]class ExtEndLayInfo: """The extension block layout information object."""
[docs] lay_info: LayoutInfo
[docs] edge_info: MOSEdgeInfo
@dataclass(eq=True, frozen=True)
[docs]class MOSAbutInfo:
[docs] row_flat: int
[docs] col: int
[docs] edgel: MOSEdgeInfo
[docs] edger: MOSEdgeInfo
[docs]class ExtWidthInfo: def __init__(self, discrete_w_list: Sequence[int], w_min: int, step_size: int = 1): self._discrete_widths = discrete_w_list self._wmin = w_min self._step = step_size
[docs] def is_valid(self, w: int) -> bool: if w >= self._wmin: return (w - self._wmin) % self._step == 0 else: idx = bisect_left(self._discrete_widths, w) return idx != len(self._discrete_widths) and self._discrete_widths[idx] == w
[docs] def get_next_width(self, w: int, even: bool = False) -> int: if w >= self._wmin: diff = w - self._wmin q, r = divmod(diff, self._step) ans = self._wmin + (q + (r != 0)) * self._step if even and ans & 1 == 1: if self._step & 1 == 0: raise ValueError('Error: impossible to achieve even extension width. ' 'See developer.') return ans + self._step return ans else: idx = bisect_left(self._discrete_widths, w) disc_len = len(self._discrete_widths) if idx == disc_len: if even: return self.get_next_width(self._wmin, even=True) else: return self._wmin else: ans = self._discrete_widths[idx] if even: while ans & 1 == 1: idx += 1 if idx == disc_len: return self.get_next_width(self._wmin, even=True) ans = self._discrete_widths[idx] return ans