# SPDX-License-Identifier: BSD-3-Clause AND Apache-2.0
# Copyright 2018 Regents of the University of California
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# 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.
"""This module defines layout template classes.
"""
from __future__ import annotations
from typing import (
TYPE_CHECKING, Union, Dict, Any, List, TypeVar, Type, Optional, Tuple, Iterable, Mapping,
Sequence, Set, cast
)
from bag.typing import PointType
from bag.util.search import BinaryIterator
from bag.env import create_routing_grid_from_file
import abc
from itertools import product
import os
from pathlib import Path
from pybag.enum import (
PathStyle, BlockageType, BoundaryType, DesignOutput, Orient2D, SupplyWrapMode,
Orientation, Direction, MinLenMode, RoundMode, PinMode, Direction2D, LogLevel
)
from pybag.core import (
BBox, BBoxArray, PyLayCellView, Transform, PyLayInstRef, PyPath, PyBlockage, PyBoundary,
PyRect, PyVia, PyPolygon, PyPolygon90, PyPolygon45, ViaParam, COORD_MIN, COORD_MAX,
RTree, BBoxCollection, TrackColoring, make_tr_colors
)
from ..util.immutable import ImmutableSortedDict, Param
from ..util.cache import DesignMaster, MasterDB, format_cell_name
from ..util.interval import IntervalSet
from ..util.math import HalfInt
from ..design.module import Module
from .core import PyLayInstance
from .tech import TechInfo
from .routing.base import Port, TrackID, WireArray, TrackManager
from .routing.grid import RoutingGrid
from .data import MOMCapInfo, TemplateEdgeInfo
[docs]GeoType = Union[PyRect, PyPolygon90, PyPolygon45, PyPolygon]
[docs]TemplateType = TypeVar('TemplateType', bound='TemplateBase')
[docs]DiffWarrType = Tuple[Optional[WireArray], Optional[WireArray]]
if TYPE_CHECKING:
from ..core import BagProject
from ..typing import TrackType, SizeType
[docs]class TemplateDB(MasterDB):
"""A database of all templates.
This class is a subclass of MasterDB that defines some extra properties/function
aliases to make creating layouts easier.
Parameters
----------
routing_grid : RoutingGrid
the default RoutingGrid object.
lib_name : str
the cadence library to put all generated templates in.
log_file: str
the log file path.
prj : Optional[BagProject]
the BagProject instance.
name_prefix : str
generated layout name prefix.
name_suffix : str
generated layout name suffix.
log_level : LogLevel
the logging level.
"""
def __init__(self, routing_grid: RoutingGrid, lib_name: str, log_file: str, prj: Optional[BagProject] = None,
name_prefix: str = '', name_suffix: str = '', log_level: LogLevel = LogLevel.DEBUG) -> None:
MasterDB.__init__(self, lib_name, log_file, prj=prj, name_prefix=name_prefix, name_suffix=name_suffix,
log_level=log_level)
self._grid = routing_grid
self._tr_colors = make_tr_colors(self._grid.tech_info)
@property
[docs] def grid(self) -> RoutingGrid:
"""RoutingGrid: The global RoutingGrid instance."""
return self._grid
@property
[docs] def tech_info(self) -> TechInfo:
return self._grid.tech_info
@property
[docs] def tr_colors(self) -> TrackColoring:
return self._tr_colors
[docs] def new_template(self, temp_cls: Type[TemplateType], params: Optional[Mapping[str, Any]] = None,
**kwargs: Any) -> TemplateType:
"""Alias for new_master() for backwards compatibility.
"""
return self.new_master(temp_cls, params=params, **kwargs)
[docs] def instantiate_layout(self, template: TemplateBase, top_cell_name: str = '',
output: DesignOutput = DesignOutput.LAYOUT, **kwargs: Any) -> None:
"""Alias for instantiate_master(), with default output type of LAYOUT.
"""
self.instantiate_master(output, template, top_cell_name, **kwargs)
[docs] def batch_layout(self, info_list: Sequence[Tuple[TemplateBase, str]],
output: DesignOutput = DesignOutput.LAYOUT, **kwargs: Any) -> None:
"""Alias for batch_output(), with default output type of LAYOUT.
"""
self.batch_output(output, info_list, **kwargs)
[docs]def get_cap_via_extensions(info: MOMCapInfo, grid: RoutingGrid, bot_layer: int,
top_layer: int) -> Dict[int, int]:
via_ext_dict: Dict[int, int] = {lay: 0 for lay in range(bot_layer, top_layer + 1)}
# get via extensions on each layer
for lay0 in range(bot_layer, top_layer):
lay1 = lay0 + 1
# port-to-port via extension
bot_tr_w = info.get_port_tr_w(lay0)
top_tr_w = info.get_port_tr_w(lay1)
ext_pp = grid.get_via_extensions(Direction.LOWER, lay0, bot_tr_w, top_tr_w)
w0, sp0, _, _ = info.get_cap_specs(lay0)
w1, sp1, _, _ = info.get_cap_specs(lay1)
# cap-to-cap via extension
ext_cc = grid.get_via_extensions_dim(Direction.LOWER, lay0, w0, w1)
# cap-to-port via extension
ext_cp = grid.get_via_extensions_dim_tr(Direction.LOWER, lay0, w0, top_tr_w)
# port-to-cap via extension
ext_pc = grid.get_via_extensions_dim_tr(Direction.UPPER, lay1, w1, bot_tr_w)
via_ext_dict[lay0] = max(via_ext_dict[lay0], ext_pp[0], ext_cc[0], ext_cp[0], ext_pc[0])
via_ext_dict[lay1] = max(via_ext_dict[lay1], ext_pp[1], ext_cc[1], ext_cp[1], ext_pc[1])
return via_ext_dict
[docs]class TemplateBase(DesignMaster):
"""The base template class.
Parameters
----------
temp_db : TemplateDB
the template database.
params : Param
the parameter values.
log_file: str
the log file path.
log_level : LogLevel
the logging level.
**kwargs : Any
dictionary of the following optional parameters:
grid : RoutingGrid
the routing grid to use for this template.
"""
def __init__(self, temp_db: TemplateDB, params: Param, log_file: str,
log_level: LogLevel = LogLevel.DEBUG, **kwargs: Any) -> None:
# add hidden parameters
DesignMaster.__init__(self, temp_db, params, log_file, log_level, **kwargs)
# private attributes
self._size: Optional[SizeType] = None
self._ports: Dict[str, Port] = {}
self._port_params: Dict[str, Dict[str, Any]] = {}
self._array_box: Optional[BBox] = None
self._fill_box: Optional[BBox] = None
self._sch_params: Optional[Param] = None
self._cell_boundary_added: bool = False
self._instances: Dict[str, PyLayInstance] = {}
self._use_color: bool = False
self._blackbox_gds: List[Path] = []
# public attributes
self.prim_top_layer: Optional[int] = None
self.prim_bound_box: Optional[BBox] = None
# get private attributes from parameters
tmp_grid: Union[RoutingGrid, str] = self.params['grid']
if tmp_grid is None:
self._grid: RoutingGrid = temp_db.grid
else:
if isinstance(tmp_grid, RoutingGrid):
self._grid: RoutingGrid = tmp_grid
else:
self._grid: RoutingGrid = create_routing_grid_from_file(tmp_grid)
tmp_colors: TrackColoring = self.params['tr_colors']
if tmp_colors is None:
self._tr_colors: TrackColoring = temp_db.tr_colors
else:
self._tr_colors: TrackColoring = tmp_colors
self._show_pins: bool = self.params['show_pins']
self._edge_info: Optional[TemplateEdgeInfo] = None
# create Cython wrapper object
self._layout: PyLayCellView = PyLayCellView(self._grid, self._tr_colors, self.cell_name)
@property
[docs] def blackbox_gds(self) -> List[Path]:
return self._blackbox_gds
@classmethod
[docs] def get_hidden_params(cls) -> Dict[str, Any]:
ans = DesignMaster.get_hidden_params()
ans['grid'] = None
ans['tr_colors'] = None
ans['show_pins'] = True
return ans
@classmethod
[docs] def get_schematic_class(cls) -> Optional[Type[Module]]:
return None
@abc.abstractmethod
[docs] def draw_layout(self) -> None:
"""Draw the layout of this template.
Override this method to create the layout.
WARNING: you should never call this method yourself.
"""
pass
[docs] def get_schematic_class_inst(self) -> Optional[Type[Module]]:
return self.get_schematic_class()
[docs] def get_master_basename(self) -> str:
"""Returns the base name to use for this instance.
Returns
-------
basename : str
the base name for this instance.
"""
return self.get_layout_basename()
[docs] def get_layout_basename(self) -> str:
"""Returns the base name for this template.
Returns
-------
base_name : str
the base name of this template.
"""
return self.__class__.__name__
[docs] def get_content(self, output_type: DesignOutput, rename_dict: Dict[str, str], name_prefix: str,
name_suffix: str, shell: bool, exact_cell_names: Set[str],
supply_wrap_mode: SupplyWrapMode) -> Tuple[str, Any]:
if not self.finalized:
raise ValueError('This template is not finalized yet')
cell_name = format_cell_name(self.cell_name, rename_dict, name_prefix, name_suffix,
exact_cell_names, supply_wrap_mode)
return cell_name, self._layout
[docs] def finalize(self) -> None:
"""Finalize this master instance.
"""
# create layout
self.draw_layout()
# finalize this template
grid = self.grid
grid.tech_info.finalize_template(self)
# construct port objects
for net_name, port_params in self._port_params.items():
pin_dict = port_params['pins']
label = port_params['label']
hide = port_params['hide']
if port_params['show']:
label = port_params['label']
for lay, geo_list in pin_dict.items():
if isinstance(lay, int):
for warr in geo_list:
self._layout.add_pin_arr(net_name, label, warr.track_id,
warr.lower, warr.upper)
else:
for box in geo_list:
self._layout.add_pin(lay, net_name, label, box)
self._ports[net_name] = Port(net_name, pin_dict, label, hide)
# call super finalize routine
DesignMaster.finalize(self)
@property
[docs] def show_pins(self) -> bool:
"""bool: True to show pins."""
return self._show_pins
@property
[docs] def sch_params(self) -> Optional[Param]:
"""Optional[Dict[str, Any]]: The schematic parameters dictionary."""
return self._sch_params
@sch_params.setter
def sch_params(self, new_params: Dict[str, Any]) -> None:
self._sch_params = ImmutableSortedDict(new_params)
@property
[docs] def template_db(self) -> TemplateDB:
"""TemplateDB: The template database object"""
# noinspection PyTypeChecker
return self.master_db
@property
[docs] def is_empty(self) -> bool:
"""bool: True if this template is empty."""
return self._layout.is_empty
@property
[docs] def grid(self) -> RoutingGrid:
"""RoutingGrid: The RoutingGrid object"""
return self._grid
@grid.setter
def grid(self, new_grid: RoutingGrid) -> None:
self._layout.set_grid(new_grid)
self._grid = new_grid
@property
[docs] def tr_colors(self) -> TrackColoring:
return self._tr_colors
@property
[docs] def array_box(self) -> Optional[BBox]:
"""Optional[BBox]: The array/abutment bounding box of this template."""
return self._array_box
@array_box.setter
def array_box(self, new_array_box: BBox) -> None:
if not self._finalized:
self._array_box = new_array_box
else:
raise RuntimeError('Template already finalized.')
@property
[docs] def fill_box(self) -> Optional[BBox]:
"""Optional[BBox]: The dummy fill bounding box of this template."""
return self._fill_box
@fill_box.setter
def fill_box(self, new_box: BBox) -> None:
if not self._finalized:
self._fill_box = new_box
else:
raise RuntimeError('Template already finalized.')
@property
[docs] def top_layer(self) -> int:
"""int: The top layer ID used in this template."""
if self.size is None:
if self.prim_top_layer is None:
raise Exception('Both size and prim_top_layer are unset.')
return self.prim_top_layer
return self.size[0]
@property
[docs] def size(self) -> Optional[SizeType]:
"""Optional[SizeType]: The size of this template, in (layer, nx_blk, ny_blk) format."""
return self._size
@property
[docs] def size_defined(self) -> bool:
"""bool: True if size or bounding box has been set."""
return self.size is not None or self.prim_bound_box is not None
@property
[docs] def bound_box(self) -> Optional[BBox]:
"""Optional[BBox]: Returns the template BBox. None if size not set yet."""
mysize = self.size
if mysize is None:
if self.prim_bound_box is None:
raise ValueError('Both size and prim_bound_box are unset.')
return self.prim_bound_box
wblk, hblk = self.grid.get_size_dimension(mysize)
return BBox(0, 0, wblk, hblk)
@size.setter
def size(self, new_size: SizeType) -> None:
if not self._finalized:
self._size = new_size
else:
raise RuntimeError('Template already finalized.')
@property
[docs] def layout_cellview(self) -> PyLayCellView:
"""PyLayCellView: The internal layout object."""
return self._layout
@property
[docs] def edge_info(self) -> Optional[TemplateEdgeInfo]:
return self._edge_info
@property
[docs] def use_color(self) -> bool:
return self._use_color
@edge_info.setter
def edge_info(self, new_info: TemplateEdgeInfo) -> None:
self._edge_info = new_info
[docs] def get_margin(self, top_layer: int, edge_dir: Direction2D,
half_blk_x: bool = True, half_blk_y: bool = True) -> int:
grid = self.grid
tech_info = grid.tech_info
edge_info = self.edge_info
if edge_info is None:
# TODO: implement this. Need to recurse down instance hierarchy
raise ValueError('Not implemented yet. See developer.')
my_edge = self.edge_info.get_edge_params(edge_dir)
is_vertical = edge_dir.is_vertical
margin = tech_info.get_margin(is_vertical, my_edge, None)
blk_size = grid.get_block_size(top_layer, half_blk_x=half_blk_x, half_blk_y=half_blk_y)
q = blk_size[is_vertical]
return -(-margin // q) * q
[docs] def get_rect_bbox(self, lay_purp: Tuple[str, str]) -> BBox:
"""Returns the overall bounding box of all rectangles on the given layer.
Note: currently this does not check primitive instances or vias.
Parameters
----------
lay_purp: Tuple[str, str]
the layer/purpose pair.
Returns
-------
box : BBox
the overall bounding box of the given layer.
"""
return self._layout.get_rect_bbox(lay_purp[0], lay_purp[1])
[docs] def new_template_with(self, **kwargs: Any) -> TemplateBase:
"""Create a new template with the given parameters.
This method will update the parameter values with the given dictionary,
then create a new template with those parameters and return it.
Parameters
----------
**kwargs : Any
a dictionary of new parameter values.
Returns
-------
new_temp : TemplateBase
A new layout master object.
"""
# get new parameter dictionary.
new_params = self.params.copy(append=kwargs)
return self.template_db.new_template(self.__class__, params=new_params)
[docs] def set_size_from_bound_box(self, top_layer_id: int, bbox: BBox, *, round_up: bool = False,
half_blk_x: bool = True, half_blk_y: bool = True):
"""Compute the size from overall bounding box.
Parameters
----------
top_layer_id : int
the top level routing layer ID that array box is calculated with.
bbox : BBox
the overall bounding box
round_up: bool
True to round up bounding box if not quantized properly
half_blk_x : bool
True to allow half-block widths.
half_blk_y : bool
True to allow half-block heights.
"""
grid = self.grid
if bbox.xl != 0 or bbox.yl != 0:
raise ValueError('lower-left corner of overall bounding box must be (0, 0).')
if grid.size_defined(top_layer_id):
self.size = grid.get_size_tuple(top_layer_id, bbox.w, bbox.h, round_up=round_up,
half_blk_x=half_blk_x, half_blk_y=half_blk_y)
else:
self.prim_top_layer = top_layer_id
self.prim_bound_box = bbox
[docs] def set_size_from_array_box(self, top_layer_id: int) -> None:
"""Automatically compute the size from array_box.
Assumes the array box is exactly in the center of the template.
Parameters
----------
top_layer_id : int
the top level routing layer ID that array box is calculated with.
"""
grid = self.grid
array_box = self.array_box
if array_box is None:
raise ValueError("array_box is not set")
dx = array_box.xl
dy = array_box.yl
if dx < 0 or dy < 0:
raise ValueError('lower-left corner of array box must be in first quadrant.')
# noinspection PyAttributeOutsideInit
self.size = grid.get_size_tuple(top_layer_id, 2 * dx + self.array_box.w,
2 * dy + self.array_box.h)
[docs] def get_pin_name(self, name: str) -> str:
"""Get the actual name of the given pin from the renaming dictionary.
Given a pin name, If this Template has a parameter called 'rename_dict',
return the actual pin name from the renaming dictionary.
Parameters
----------
name : str
the pin name.
Returns
-------
actual_name : str
the renamed pin name.
"""
rename_dict = self.params.get('rename_dict', {})
return rename_dict.get(name, name)
[docs] def get_port(self, name: str = '') -> Port:
"""Returns the port object with the given name.
Parameters
----------
name : str
the port terminal name. If None or empty, check if this template has only one port,
then return it.
Returns
-------
port : Port
the port object.
"""
if not name:
if len(self._ports) != 1:
raise ValueError('Template has %d ports != 1.' % len(self._ports))
name = next(iter(self._ports))
return self._ports[name]
[docs] def has_port(self, port_name: str) -> bool:
"""Returns True if this template has the given port."""
return port_name in self._ports
[docs] def port_names_iter(self) -> Iterable[str]:
"""Iterates over port names in this template.
Yields
------
port_name : str
name of a port in this template.
"""
return self._ports.keys()
[docs] def new_template(self, temp_cls: Type[TemplateType], *,
params: Optional[Mapping[str, Any]] = None,
show_pins: bool = False,
grid: Optional[RoutingGrid] = None) -> TemplateType:
"""Create a new template.
Parameters
----------
temp_cls : Type[TemplateType]
the template class to instantiate.
params : Optional[Mapping[str, Any]]
the parameter dictionary.
show_pins : bool
True to pass show_pins in the generated template, if params does not already have
show_pins.
grid: Optional[RoutingGrid]
routing grid for this cell.
Returns
-------
template : TemplateType
the new template instance.
"""
if grid is None:
grid = params.get('grid', self.grid)
show_pins = params.get('show_pins', show_pins)
if isinstance(params, ImmutableSortedDict):
params = params.copy(append=dict(grid=grid, show_pins=show_pins))
else:
params['grid'] = grid
params['show_pins'] = show_pins
return self.template_db.new_template(params=params, temp_cls=temp_cls)
[docs] def add_instance(self,
master: TemplateBase,
*,
inst_name: str = '',
xform: Optional[Transform] = None,
nx: int = 1,
ny: int = 1,
spx: int = 0,
spy: int = 0,
commit: bool = True,
) -> PyLayInstance:
"""Adds a new (arrayed) instance to layout.
Parameters
----------
master : TemplateBase
the master template object.
inst_name : Optional[str]
instance name. If None or an instance with this name already exists,
a generated unique name is used.
xform : Optional[Transform]
the transformation object.
nx : int
number of columns. Must be positive integer.
ny : int
number of rows. Must be positive integer.
spx : CoordType
column pitch. Used for arraying given instance.
spy : CoordType
row pitch. Used for arraying given instance.
commit : bool
True to commit the object immediately.
Returns
-------
inst : PyLayInstance
the added instance.
"""
if xform is None:
xform = Transform()
ref = self._layout.add_instance(master.layout_cellview, inst_name, xform, nx, ny,
spx, spy, False)
ans = PyLayInstance(self, master, ref)
if commit:
ans.commit()
self._instances[ans.name] = ans
self._use_color = self._use_color or master.use_color
return ans
[docs] def add_instance_primitive(self,
lib_name: str,
cell_name: str,
*,
xform: Optional[Transform] = None,
view_name: str = 'layout',
inst_name: str = '',
nx: int = 1,
ny: int = 1,
spx: int = 0,
spy: int = 0,
params: Optional[Dict[str, Any]] = None,
commit: bool = True,
**kwargs: Any,
) -> PyLayInstRef:
"""Adds a new (arrayed) primitive instance to layout.
Parameters
----------
lib_name : str
instance library name.
cell_name : str
instance cell name.
xform : Optional[Transform]
the transformation object.
view_name : str
instance view name. Defaults to 'layout'.
inst_name : Optional[str]
instance name. If None or an instance with this name already exists,
a generated unique name is used.
nx : int
number of columns. Must be positive integer.
ny : int
number of rows. Must be positive integer.
spx : CoordType
column pitch. Used for arraying given instance.
spy : CoordType
row pitch. Used for arraying given instance.
params : Optional[Dict[str, Any]]
the parameter dictionary. Used for adding pcell instance.
commit : bool
True to commit the object immediately.
**kwargs : Any
additional arguments. Usually implementation specific.
Returns
-------
ref : PyLayInstRef
A reference to the primitive instance.
"""
if not params:
params = kwargs
else:
params.update(kwargs)
if xform is None:
xform = Transform()
# TODO: support pcells
if params:
raise ValueError("layout pcells not supported yet; see developer")
blackbox_gds: Path = Path(os.environ['BAG_TECH_CONFIG_DIR']) / 'blackbox_gds' / lib_name / f'{cell_name}.gds'
if blackbox_gds not in self._blackbox_gds:
self._blackbox_gds.append(blackbox_gds)
return self._layout.add_prim_instance(lib_name, cell_name, view_name, inst_name, xform,
nx, ny, spx, spy, commit)
[docs] def is_horizontal(self, layer: str) -> bool:
"""Returns True if the given layer has no direction or is horizontal."""
lay_id = self._grid.tech_info.get_layer_id(layer)
return (lay_id is None) or self._grid.is_horizontal(lay_id)
[docs] def add_rect(self, lay_purp: Tuple[str, str], bbox: BBox, commit: bool = True) -> PyRect:
"""Add a new rectangle.
Parameters
----------
lay_purp: Tuple[str, str]
the layer/purpose pair.
bbox : BBox
the rectangle bounding box.
commit : bool
True to commit the object immediately.
Returns
-------
rect : PyRect
the added rectangle.
"""
return self._layout.add_rect(lay_purp[0], lay_purp[1], bbox, commit)
[docs] def add_rect_array(self, lay_purp: Tuple[str, str], bbox: BBox,
nx: int = 1, ny: int = 1, spx: int = 0, spy: int = 0) -> None:
"""Add a new rectangle array.
"""
self._layout.add_rect_arr(lay_purp[0], lay_purp[1], bbox, nx, ny, spx, spy)
[docs] def add_bbox_array(self, lay_purp: Tuple[str, str], barr: BBoxArray) -> None:
"""Add a new rectangle array.
Parameters
----------
lay_purp: Tuple[str, str]
the layer/purpose pair.
barr : BBoxArray
the rectangle bounding box array.
"""
self._layout.add_rect_arr(lay_purp[0], lay_purp[1], barr)
[docs] def add_bbox_collection(self, lay_purp: Tuple[str, str], bcol: BBoxCollection) -> None:
self._layout.add_rect_list(lay_purp[0], lay_purp[1], bcol)
[docs] def add_path(self, lay_purp: Tuple[str, str], width: int, points: List[PointType],
start_style: PathStyle, *, join_style: PathStyle = PathStyle.round,
stop_style: Optional[PathStyle] = None, commit: bool = True) -> PyPath:
"""Add a new path.
Parameters
----------
lay_purp: Tuple[str, str]
the layer/purpose pair.
width : int
the path width.
points : List[PointType]
points defining this path.
start_style : PathStyle
the path beginning style.
join_style : PathStyle
path style for the joints.
stop_style : Optional[PathStyle]
the path ending style. Defaults to start style.
commit : bool
True to commit the object immediately.
Returns
-------
path : PyPath
the added path object.
"""
if stop_style is None:
stop_style = start_style
half_width = width // 2
return self._layout.add_path(lay_purp[0], lay_purp[1], points, half_width, start_style,
stop_style, join_style, commit)
[docs] def add_path45_bus(self, lay_purp: Tuple[str, str], points: List[PointType], widths: List[int],
spaces: List[int], start_style: PathStyle, *,
join_style: PathStyle = PathStyle.round,
stop_style: Optional[PathStyle] = None, commit: bool = True) -> PyPath:
"""Add a path bus that only contains 45 degree turns.
Parameters
----------
lay_purp: Tuple[str, str]
the layer/purpose pair.
points : List[PointType]
points defining this path.
widths : List[int]
width of each path in the bus.
spaces : List[int]
space between each path.
start_style : PathStyle
the path beginning style.
join_style : PathStyle
path style for the joints.
stop_style : Optional[PathStyle]
the path ending style. Defaults to start style.
commit : bool
True to commit the object immediately.
Returns
-------
path : PyPath
the added path object.
"""
if stop_style is None:
stop_style = start_style
return self._layout.add_path45_bus(lay_purp[0], lay_purp[1], points, widths, spaces,
start_style, stop_style, join_style, commit)
[docs] def add_polygon(self, lay_purp: Tuple[str, str], points: List[PointType],
commit: bool = True) -> PyPolygon:
"""Add a new polygon.
Parameters
----------
lay_purp: Tuple[str, str]
the layer/purpose pair.
points : List[PointType]
vertices of the polygon.
commit : bool
True to commit the object immediately.
Returns
-------
polygon : PyPolygon
the added polygon object.
"""
return self._layout.add_poly(lay_purp[0], lay_purp[1], points, commit)
[docs] def add_blockage(self, layer: str, blk_type: BlockageType, points: List[PointType],
commit: bool = True) -> PyBlockage:
"""Add a new blockage object.
Parameters
----------
layer : str
the layer name.
blk_type : BlockageType
the blockage type.
points : List[PointType]
vertices of the blockage object.
commit : bool
True to commit the object immediately.
Returns
-------
blockage : PyBlockage
the added blockage object.
"""
return self._layout.add_blockage(layer, blk_type, points, commit)
[docs] def add_boundary(self, bnd_type: BoundaryType, points: List[PointType],
commit: bool = True) -> PyBoundary:
"""Add a new boundary.
Parameters
----------
bnd_type : str
the boundary type.
points : List[PointType]
vertices of the boundary object.
commit : bool
True to commit the object immediately.
Returns
-------
boundary : PyBoundary
the added boundary object.
"""
return self._layout.add_boundary(bnd_type, points, commit)
[docs] def add_cell_boundary(self, bbox: BBox) -> None:
"""Adds cell boundary in this template.
By default, this method is called when finalizing a template (although the process
implementation may override this behavior) to set the cell boundary, which is generally
used for DRC or P&R purposes.
This method can only be called once from the template. All calls after the first one will
be ignored. Therefore, if you need to set the cell boundary to be something other than
the template's bounding box, you can call this in the draw_layout() method.
Parameters
----------
bbox : BBox
the cell boundary bounding box.
"""
if not self._cell_boundary_added:
self._cell_boundary_added = True
self.grid.tech_info.add_cell_boundary(self, bbox)
[docs] def disable_cell_boundary(self) -> None:
"""Disable cell boundary drawing in this template."""
self._cell_boundary_added = True
[docs] def reexport(self, port: Port, *,
net_name: str = '', label: str = '', show: Optional[bool] = None,
hide: Optional[bool] = None, connect: bool = False) -> None:
"""Re-export the given port object.
Add all geometries in the given port as pins with optional new name
and label.
Parameters
----------
port : Port
the Port object to re-export.
net_name : str
the new net name. If not given, use the port's current net name.
label : str
the label. If not given, use net_name.
show : Optional[bool]
True to draw the pin in layout. If None, use self.show_pins
hide: Optional[bool]
if given, it overrides the hide flag of the port, otherwise the default is used.
connect : bool
True to enable connection by name.
"""
if show is None:
show = self._show_pins
net_name = net_name or port.net_name
if not label:
if net_name != port.net_name:
if port.label.endswith(':'):
# inherit connect setting of the port
label = net_name + ':'
else:
label = net_name
else:
label = port.label
if connect and label[-1] != ':':
label += ':'
if hide is None:
hide = port.hidden
show = show and not hide
if net_name not in self._port_params:
self._port_params[net_name] = dict(label=label, pins={}, show=show, hide=hide)
port_params = self._port_params[net_name]
# check labels is consistent.
if port_params['label'] != label:
msg = 'Current port label = %s != specified label = %s'
raise ValueError(msg % (port_params['label'], label))
if port_params['show'] != show:
raise ValueError('Conflicting show port specification.')
if port_params['hide'] != hide:
raise ValueError('Conflicting hide port specification.')
# export all port geometries
port_pins = port_params['pins']
for lay, geo_list in port.items():
cur_geo_list = port_pins.get(lay, None)
if cur_geo_list is None:
port_pins[lay] = cur_geo_list = []
cur_geo_list.extend(geo_list)
[docs] def add_pin_primitive(self, net_name: str, layer: str, bbox: BBox, *,
label: str = '', show: Optional[bool] = None, hide: bool = False,
connect: bool = False):
"""Add a primitive pin to the layout.
Parameters
----------
net_name : str
the net name associated with the pin.
layer : str
the pin layer name.
bbox : BBox
the pin bounding box.
label : str
the label of this pin. If None or empty, defaults to be the net_name.
this argument is used if you need the label to be different than net name
for LVS purposes. For example, unconnected pins usually need a colon after
the name to indicate that LVS should short those pins together.
show : Optional[bool]
True to draw the pin in layout. If None, use self.show_pins
hide : bool
True to add a hidden pin.
connect : bool
True to enable connection by name.
"""
if show is None:
show = self._show_pins
show = show and not hide
label = label or net_name
if connect and label[-1] != ':':
label += ':'
port_params = self._port_params.get(net_name, None)
if port_params is None:
self._port_params[net_name] = port_params = dict(label=label, pins={},
show=show, hide=hide)
else:
# check labels is consistent.
if port_params['label'] != label:
msg = 'Current port label = %s != specified label = %s'
raise ValueError(msg % (port_params['label'], label))
if port_params['show'] != show:
raise ValueError('Conflicting show port specification.')
if port_params['hide'] != hide:
raise ValueError('Conflicting hide port specification.')
port_pins = port_params['pins']
if layer in port_pins:
port_pins[layer].append(bbox)
else:
port_pins[layer] = [bbox]
[docs] def add_label(self, label: str, lay_purp: Tuple[str, str], bbox: BBox) -> None:
"""Adds a label to the layout.
This is mainly used to add voltage text labels.
Parameters
----------
label : str
the label text.
lay_purp: Tuple[str, str]
the layer/purpose pair.
bbox : BBox
the label bounding box.
"""
w = bbox.w
text_h = bbox.h
if text_h > w:
orient = Orientation.R90
text_h = w
else:
orient = Orientation.R0
xform = Transform(bbox.xm, bbox.ym, orient)
self._layout.add_label(lay_purp[0], lay_purp[1], xform, label, text_h)
[docs] def add_pin(self, net_name: str, wire_arr_list: Union[WireArray, List[WireArray]],
*, label: str = '', show: Optional[bool] = None,
mode: PinMode = PinMode.ALL, hide: bool = False, connect: bool = False) -> None:
"""Add new pin to the layout.
If one or more pins with the same net name already exists,
they'll be grouped under the same port.
Parameters
----------
net_name : str
the net name associated with the pin.
wire_arr_list : Union[WireArray, List[WireArray]]
WireArrays representing the pin geometry.
label : str
the label of this pin. If None or empty, defaults to be the net_name.
this argument is used if you need the label to be different than net name
for LVS purposes. For example, unconnected pins usually need a colon after
the name to indicate that LVS should short those pins together.
show : Optional[bool]
if True, draw the pin in layout. If None, use self.show_pins
mode : PinMode
location of the pin relative to the WireArray.
hide : bool
True if this is a hidden pin.
connect : bool
True to enable connection by name.
"""
if show is None:
show = self._show_pins
show = show and not hide
label = label or net_name
if connect and label[-1] != ':':
label += ':'
port_params = self._port_params.get(net_name, None)
if port_params is None:
self._port_params[net_name] = port_params = dict(label=label, pins={},
show=show, hide=hide)
else:
# check labels is consistent.
if port_params['label'] != label:
msg = 'Current port label = %s != specified label = %s'
raise ValueError(msg % (port_params['label'], label))
if port_params['show'] != show:
raise ValueError('Conflicting show port specification.')
if port_params['hide'] != hide:
raise ValueError('Conflicting hide port specification.')
grid = self._grid
for warr in WireArray.wire_grp_iter(wire_arr_list):
# add pin array to port_pins
tid = warr.track_id.copy_with(grid)
layer_id = tid.layer_id
if mode is not PinMode.ALL:
# create new pin WireArray that's snapped to the edge
cur_w = grid.get_wire_total_width(layer_id, tid.width)
wl = warr.lower
wu = warr.upper
pin_len = min(grid.get_next_length(layer_id, tid.width, cur_w, even=True),
wu - wl)
if mode is PinMode.LOWER:
wu = wl + pin_len
elif mode is PinMode.UPPER:
wl = wu - pin_len
else:
wl = (wl + wu - pin_len) // 2
wu = wl + pin_len
warr = WireArray(tid, wl, wu)
port_pins = port_params['pins']
if layer_id not in port_pins:
port_pins[layer_id] = [warr]
else:
port_pins[layer_id].append(warr)
self._use_color = True
[docs] def add_via(self, bbox: BBox, bot_lay_purp: Tuple[str, str], top_lay_purp: Tuple[str, str],
bot_dir: Orient2D, *, extend: bool = True, top_dir: Optional[Orient2D] = None,
add_layers: bool = False, commit: bool = True) -> PyVia:
"""Adds an arrayed via object to the layout.
Parameters
----------
bbox : BBox
the via bounding box, not including extensions.
bot_lay_purp : Tuple[str. str]
the bottom layer/purpose pair.
top_lay_purp : Tuple[str, str]
the top layer/purpose pair.
bot_dir : Orient2D
the bottom layer extension direction.
extend : bool
True if via extension can be drawn outside of the box.
top_dir : Optional[Orient2D]
top layer extension direction. Defaults to be perpendicular to bottom layer direction.
add_layers : bool
True to add metal rectangles on top and bottom layers.
commit : bool
True to commit via immediately.
Returns
-------
via : PyVia
the new via object.
"""
tech_info = self._grid.tech_info
via_info = tech_info.get_via_info(bbox, Direction.LOWER, bot_lay_purp[0],
top_lay_purp[0],
bot_dir, purpose=bot_lay_purp[1],
adj_purpose=top_lay_purp[1],
extend=extend, adj_ex_dir=top_dir)
if via_info is None:
raise ValueError('Cannot create via between layers {} and {} '
'with BBox: {}'.format(bot_lay_purp, top_lay_purp, bbox))
table = via_info['params']
via_id = table['id']
xform = table['xform']
via_param = table['via_param']
return self._layout.add_via(xform, via_id, via_param, add_layers, commit)
[docs] def add_via_arr(self, barr: BBoxArray, bot_lay_purp: Tuple[str, str],
top_lay_purp: Tuple[str, str], bot_dir: Orient2D, *, extend: bool = True,
top_dir: Optional[Orient2D] = None, add_layers: bool = False) -> Dict[str, Any]:
"""Adds an arrayed via object to the layout.
Parameters
----------
barr : BBoxArray
the BBoxArray representing the via bounding boxes, not including extensions.
bot_lay_purp : Tuple[str. str]
the bottom layer/purpose pair.
top_lay_purp : Tuple[str, str]
the top layer/purpose pair.
bot_dir : Orient2D
the bottom layer extension direction.
extend : bool
True if via extension can be drawn outside of the box.
top_dir : Optional[Orient2D]
top layer extension direction. Defaults to be perpendicular to bottom layer direction.
add_layers : bool
True to add metal rectangles on top and bottom layers.
Returns
-------
via_info : Dict[str, Any]
the via information dictionary.
"""
tech_info = self._grid.tech_info
base_box = barr.base
via_info = tech_info.get_via_info(base_box, Direction.LOWER, bot_lay_purp[0],
top_lay_purp[0], bot_dir, purpose=bot_lay_purp[1],
adj_purpose=top_lay_purp[1], extend=extend,
adj_ex_dir=top_dir)
if via_info is None:
raise ValueError('Cannot create via between layers {} and {} '
'with BBox: {}'.format(bot_lay_purp, top_lay_purp, base_box))
table = via_info['params']
via_id = table['id']
xform = table['xform']
via_param = table['via_param']
self._layout.add_via_arr(xform, via_id, via_param, add_layers, barr.nx, barr.ny,
barr.spx, barr.spy)
return via_info
[docs] def add_via_primitive(self, via_type: str, xform: Transform, cut_width: int, cut_height: int,
*, num_rows: int = 1, num_cols: int = 1, sp_rows: int = 0,
sp_cols: int = 0, enc1: Tuple[int, int, int, int] = (0, 0, 0, 0),
enc2: Tuple[int, int, int, int] = (0, 0, 0, 0), nx: int = 1, ny: int = 1,
spx: int = 0, spy: int = 0, priority: int = 1) -> None:
"""Adds via(s) by specifying all parameters.
Parameters
----------
via_type : str
the via type name.
xform: Transform
the transformation object.
cut_width : CoordType
via cut width. This is used to create rectangle via.
cut_height : CoordType
via cut height. This is used to create rectangle via.
num_rows : int
number of via cut rows.
num_cols : int
number of via cut columns.
sp_rows : CoordType
spacing between via cut rows.
sp_cols : CoordType
spacing between via cut columns.
enc1 : Optional[List[CoordType]]
a list of left, right, top, and bottom enclosure values on bottom layer.
Defaults to all 0.
enc2 : Optional[List[CoordType]]
a list of left, right, top, and bottom enclosure values on top layer.
Defaults to all 0.
nx : int
number of columns.
ny : int
number of rows.
spx : int
column pitch.
spy : int
row pitch.
priority : int
via priority, defaults to 1
"""
l1, r1, t1, b1 = enc1
l2, r2, t2, b2 = enc2
param = ViaParam(num_cols, num_rows, cut_width, cut_height, sp_cols, sp_rows,
l1, r1, t1, b1, l2, r2, t2, b2, priority)
self._layout.add_via_arr(xform, via_type, param, True, nx, ny, spx, spy)
[docs] def add_via_on_grid(self, tid1: TrackID, tid2: TrackID, *, extend: bool = True
) -> Tuple[Tuple[int, int], Tuple[int, int]]:
"""Add a via on the routing grid.
Parameters
----------
tid1 : TrackID
the first TrackID
tid2 : TrackID
the second TrackID
extend : bool
True to extend outside the via bounding box.
"""
return self._layout.add_via_on_intersections(tid1, tid2, COORD_MIN, COORD_MAX,
COORD_MIN, COORD_MAX, extend, False)
[docs] def extend_wires(self, warr_list: Union[WireArray, List[Optional[WireArray]]], *,
lower: Optional[int] = None, upper: Optional[int] = None,
min_len_mode: Optional[int] = None) -> List[Optional[WireArray]]:
"""Extend the given wires to the given coordinates.
Parameters
----------
warr_list : Union[WireArray, List[Optional[WireArray]]]
the wires to extend.
lower : Optional[int]
the wire lower coordinate.
upper : Optional[int]
the wire upper coordinate.
min_len_mode : Optional[int]
If not None, will extend track so it satisfy minimum length requirement.
Use -1 to extend lower bound, 1 to extend upper bound, 0 to extend both equally.
Returns
-------
warr_list : List[Optional[WireArray]]
list of added wire arrays.
If any elements in warr_list were None, they will be None in the return.
"""
grid = self._grid
new_warr_list = []
for warr in WireArray.wire_grp_iter(warr_list):
if warr is None:
new_warr_list.append(None)
else:
tid = warr.track_id.copy_with(grid)
wlower = warr.lower
wupper = warr.upper
if lower is None:
cur_lower = wlower
else:
cur_lower = min(lower, wlower)
if upper is None:
cur_upper = wupper
else:
cur_upper = max(upper, wupper)
if min_len_mode is not None:
# extend track to meet minimum length
# make sure minimum length is even so that middle coordinate exists
tr_len = cur_upper - cur_lower
next_len = grid.get_next_length(tid.layer_id, tid.width, tr_len, even=True)
if next_len > tr_len:
ext = next_len - tr_len
if min_len_mode < 0:
cur_lower -= ext
elif min_len_mode > 0:
cur_upper += ext
else:
cur_lower -= ext // 2
cur_upper = cur_lower + next_len
new_warr = WireArray(tid, cur_lower, cur_upper)
self._layout.add_warr(tid, cur_lower, cur_upper)
new_warr_list.append(new_warr)
self._use_color = True
return new_warr_list
[docs] def add_wires(self, layer_id: int, track_idx: TrackType, lower: int, upper: int, *,
width: int = 1, num: int = 1, pitch: TrackType = 1) -> WireArray:
"""Add the given wire(s) to this layout.
Parameters
----------
layer_id : int
the wire layer ID.
track_idx : TrackType
the smallest wire track index.
lower : CoordType
the wire lower coordinate.
upper : CoordType
the wire upper coordinate.
width : int
the wire width in number of tracks.
num : int
number of wires.
pitch : TrackType
the wire pitch.
Returns
-------
warr : WireArray
the added WireArray object.
"""
tid = TrackID(layer_id, track_idx, width=width, num=num, pitch=pitch, grid=self._grid)
warr = WireArray(tid, lower, upper)
self._layout.add_warr(tid, lower, upper)
self._use_color = True
return warr
[docs] def add_matched_wire(self, warr: WireArray, coord: int, layer_id: int) -> WireArray:
"""Adds a wire (without any via), matched to a provided wire array.
The mirroring takes place with respect to a coordinate and the track direction on the
layer of the coordinate
Parameters
----------
warr : WireArray
the original wire array for which a matched wire should be drawn
coord : int
the coordinate which is used for mirroring
layer_id : int
the layer_id of the coordinate. this argument is used to figure out the axis around
which things should be mirrored
Returns
-------
warr : WireArray
the added WireArray object.
"""
grid = self._grid
tid = warr.track_id
wire_layer = warr.layer_id
wire_dir = grid.get_direction(wire_layer)
ref_dir = grid.get_direction(layer_id)
if wire_dir is not ref_dir:
# if wire and reference have different orientation
new_lower = 2 * coord - warr.upper
new_upper = 2 * coord - warr.lower
self.add_wires(wire_layer, tid.base_index, new_lower, new_upper)
return WireArray(tid, new_lower, new_upper)
coord_tidx = grid.coord_to_track(layer_id, coord)
new_tidx = 2 * coord_tidx - tid.base_index
new_tid = TrackID(wire_layer, new_tidx, width=tid.width, num=tid.num,
pitch=tid.pitch, grid=grid)
self.add_wires(wire_layer, new_tidx, warr.lower, warr.upper)
return WireArray(new_tid, warr.lower, warr.upper)
[docs] def connect_to_tracks_with_dummy_wires(self,
wire_arr_list: Union[WireArray, List[WireArray]],
track_id: TrackID,
ref_coord: int,
ref_layer_id: int,
*,
wire_lower: Optional[int] = None,
wire_upper: Optional[int] = None,
track_lower: Optional[int] = None,
track_upper: Optional[int] = None,
min_len_mode: MinLenMode = None,
ret_wire_list: Optional[List[WireArray]] = None,
debug: bool = False) -> Optional[WireArray]:
"""Implements connect_to_tracks but with matched wires drawn simultaneously
Parameters
----------
wire_arr_list : Union[WireArray, List[WireArray]]
list of WireArrays to connect to track.
track_id : TrackID
TrackID that specifies the track(s) to connect the given wires to.
ref_coord: int
the coordinate which is used for mirroring
ref_layer_id: int
the layer_id of the coordinate. this argument is used to figure out the axis around
which things should be mirrored
wire_lower : Optional[CoordType]
if given, extend wire(s) to this lower coordinate.
wire_upper : Optional[CoordType]
if given, extend wire(s) to this upper coordinate.
track_lower : Optional[CoordType]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[CoordType]
if given, extend track(s) to this upper coordinate.
min_len_mode : MinLenMode
the minimum length extension mode.
ret_wire_list : Optional[List[WireArray]]
If not none, extended wires that are created will be appended to this list.
debug : bool
True to print debug messages.
Returns
-------
wire_arr : Optional[WireArray]
WireArray representing the tracks created.
"""
if ret_wire_list is None:
ret_wire_list = []
track_warr = self.connect_to_tracks(wire_arr_list,
track_id,
wire_lower=wire_lower,
wire_upper=wire_upper,
track_lower=track_lower,
track_upper=track_upper,
min_len_mode=min_len_mode,
ret_wire_list=ret_wire_list,
debug=debug)
self.add_matched_wire(track_warr, ref_coord, ref_layer_id)
for warr in ret_wire_list:
self.add_matched_wire(warr, ref_coord, ref_layer_id)
return track_warr
[docs] def connect_wire_to_coord(self, wire: WireArray, layer_id: int, coord: int,
min_len_mode: MinLenMode = MinLenMode.NONE,
round_mode: RoundMode = RoundMode.NONE) -> WireArray:
""" Connects a given wire to a wire on the next/previous layer aligned with a given
coordinate.
Parameters
----------
wire : WireArray
wire object to be connected.
layer_id: int
the wire layer ID.
coord : CoordType
the coordinate to be used for alignment.
min_len_mode : MinLenMode
the minimum length extension mode used in connect_to_tracks.
round_mode: RoundMode
the rounding mode used in coord_to_track conversion.
Returns
-------
warr : WireArray
the added WireArray object.
"""
if layer_id not in [wire.layer_id + 1, wire.layer_id - 1]:
raise ValueError(f'cannot connect wire of layer {wire.layer_id} to layer {layer_id}')
tidx = self.grid.coord_to_track(layer_id, coord, round_mode)
tid = TrackID(layer_id, tidx, width=wire.track_id.width)
warr = self.connect_to_tracks(wire, tid, min_len_mode=min_len_mode)
return warr
[docs] def add_mom_cap(self, cap_box: BBox, bot_layer: int, num_layer: int, *,
port_widths: Optional[Mapping[int, int]] = None,
port_plow: Optional[Mapping[int, bool]] = None,
array: bool = False,
cap_wires_list: Optional[List[Tuple[Tuple[str, str], Tuple[str, str],
BBoxArray, BBoxArray]]] = None,
cap_type: str = 'standard'
) -> Dict[int, Tuple[List[WireArray], List[WireArray]]]:
"""Draw mom cap in the defined bounding box."""
empty_dict = {}
if num_layer <= 1:
raise ValueError('Must have at least 2 layers for MOM cap.')
if port_widths is None:
port_widths = empty_dict
if port_plow is None:
port_plow = empty_dict
grid = self.grid
tech_info = grid.tech_info
top_layer = bot_layer + num_layer - 1
cap_info = MOMCapInfo(tech_info.tech_params['mom_cap'][cap_type], port_widths, port_plow)
via_ext_dict = get_cap_via_extensions(cap_info, grid, bot_layer, top_layer)
# find port locations and cap boundaries.
port_tracks: Dict[int, Tuple[List[int], List[int]]] = {}
cap_bounds: Dict[int, Tuple[int, int]] = {}
cap_exts: Dict[int, Tuple[int, int]] = {}
for cur_layer in range(bot_layer, top_layer + 1):
cap_w, cap_sp, cap_margin, num_ports = cap_info.get_cap_specs(cur_layer)
port_tr_w = cap_info.get_port_tr_w(cur_layer)
port_tr_sep = grid.get_sep_tracks(cur_layer, port_tr_w, port_tr_w)
dir_idx = grid.get_direction(cur_layer).value
coord0, coord1 = cap_box.get_interval(1 - dir_idx)
# get max via extension on adjacent layers
adj_via_ext = 0
if cur_layer != bot_layer:
adj_via_ext = via_ext_dict[cur_layer - 1]
if cur_layer != top_layer:
adj_via_ext = max(adj_via_ext, via_ext_dict[cur_layer + 1])
# find track indices
if array:
tidx0 = grid.coord_to_track(cur_layer, coord0)
tidx1 = grid.coord_to_track(cur_layer, coord1)
else:
tidx0 = grid.find_next_track(cur_layer, coord0 + adj_via_ext, tr_width=port_tr_w,
mode=RoundMode.GREATER_EQ)
tidx1 = grid.find_next_track(cur_layer, coord1 - adj_via_ext, tr_width=port_tr_w,
mode=RoundMode.LESS_EQ)
if tidx0 + 2 * num_ports * port_tr_sep >= tidx1:
raise ValueError('Cannot draw MOM cap; '
f'not enough space between ports on layer {cur_layer}.')
# compute space from MOM cap wires to port wires
cap_margin = max(cap_margin, grid.get_min_space(cur_layer, port_tr_w))
lower_tracks = [tidx0 + idx * port_tr_sep for idx in range(num_ports)]
upper_tracks = [tidx1 - idx * port_tr_sep for idx in range(num_ports - 1, -1, -1)]
tr_ll = grid.get_wire_bounds(cur_layer, lower_tracks[0], width=port_tr_w)[0]
tr_lu = grid.get_wire_bounds(cur_layer, lower_tracks[num_ports - 1], width=port_tr_w)[1]
tr_ul = grid.get_wire_bounds(cur_layer, upper_tracks[0], width=port_tr_w)[0]
tr_uu = grid.get_wire_bounds(cur_layer, upper_tracks[num_ports - 1], width=port_tr_w)[1]
port_tracks[cur_layer] = (lower_tracks, upper_tracks)
cap_bounds[cur_layer] = (tr_lu + cap_margin, tr_ul - cap_margin)
cap_exts[cur_layer] = (tr_ll, tr_uu)
port_dict: Dict[int, Tuple[List[WireArray], List[WireArray]]] = {}
cap_wire_dict: Dict[int, Tuple[Tuple[str, str], Tuple[str, str], BBoxArray, BBoxArray]] = {}
# draw ports/wires
for cur_layer in range(bot_layer, top_layer + 1):
port_plow = cap_info.get_port_plow(cur_layer)
port_tr_w = cap_info.get_port_tr_w(cur_layer)
cap_w, cap_sp, cap_margin, num_ports = cap_info.get_cap_specs(cur_layer)
# find port/cap wires lower/upper coordinates
lower = COORD_MAX
upper = COORD_MIN
if cur_layer != top_layer:
lower, upper = cap_exts[cur_layer + 1]
if cur_layer != bot_layer:
tmpl, tmpu = cap_exts[cur_layer - 1]
lower = min(lower, tmpl)
upper = max(upper, tmpu)
via_ext = via_ext_dict[cur_layer]
lower -= via_ext
upper += via_ext
# draw ports
lower_tracks, upper_tracks = port_tracks[cur_layer]
lower_warrs = [self.add_wires(cur_layer, tr_idx, lower, upper, width=port_tr_w)
for tr_idx in lower_tracks]
upper_warrs = [self.add_wires(cur_layer, tr_idx, lower, upper, width=port_tr_w)
for tr_idx in upper_tracks]
# assign port wires to positive/negative terminals
num_ports = len(lower_warrs)
if port_plow:
if num_ports == 1:
plist = lower_warrs
nlist = upper_warrs
else:
plist = [lower_warrs[0], upper_warrs[0]]
nlist = [lower_warrs[1], upper_warrs[1]]
else:
if num_ports == 1:
plist = upper_warrs
nlist = lower_warrs
else:
plist = [lower_warrs[1], upper_warrs[1]]
nlist = [lower_warrs[0], upper_warrs[0]]
# save ports
port_dict[cur_layer] = plist, nlist
# compute cap wires BBoxArray
cap_bndl, cap_bndh = cap_bounds[cur_layer]
cap_tot_space = cap_bndh - cap_bndl
cap_pitch = cap_w + cap_sp
num_cap_wires = cap_tot_space // cap_pitch
cap_bndl += (cap_tot_space - (num_cap_wires * cap_pitch - cap_sp)) // 2
cur_dir = grid.get_direction(cur_layer)
cap_box0 = BBox(cur_dir, lower, upper, cap_bndl, cap_bndl + cap_w)
lay_purp_list = tech_info.get_lay_purp_list(cur_layer)
num_lay_purp = len(lay_purp_list)
assert num_lay_purp <= 2, 'This method now only works for 1 or 2 colors.'
num0 = (num_cap_wires + 1) // 2
num1 = num_cap_wires - num0
barr_pitch = cap_pitch * 2
cap_box1 = cap_box0.get_move_by_orient(cur_dir, dt=0, dp=cap_pitch)
barr0 = BBoxArray(cap_box0, cur_dir, np=num0, spp=barr_pitch)
barr1 = BBoxArray(cap_box1, cur_dir, np=num1, spp=barr_pitch)
if port_plow:
capp_barr = barr1
capn_barr = barr0
capp_lp = lay_purp_list[-1]
capn_lp = lay_purp_list[0]
else:
capp_barr = barr0
capn_barr = barr1
capp_lp = lay_purp_list[0]
capn_lp = lay_purp_list[-1]
# draw cap wires
self.add_bbox_array(capp_lp, capp_barr)
self.add_bbox_array(capn_lp, capn_barr)
# save caps
cap_barr_tuple = (capp_lp, capn_lp, capp_barr, capn_barr)
cap_wire_dict[cur_layer] = cap_barr_tuple
if cap_wires_list is not None:
cap_wires_list.append(cap_barr_tuple)
# connect port/cap wires to bottom port/cap
if cur_layer != bot_layer:
# connect ports to layer below
bplist, bnlist = port_dict[cur_layer - 1]
bcapp_lp, bcapn_lp, bcapp, bcapn = cap_wire_dict[cur_layer - 1]
self._add_mom_cap_connect_ports(bplist, plist)
self._add_mom_cap_connect_ports(bnlist, nlist)
self._add_mom_cap_connect_cap_to_port(Direction.UPPER, capp_lp, capp_barr, bplist)
self._add_mom_cap_connect_cap_to_port(Direction.UPPER, capn_lp, capn_barr, bnlist)
self._add_mom_cap_connect_cap_to_port(Direction.LOWER, bcapp_lp, bcapp, plist)
self._add_mom_cap_connect_cap_to_port(Direction.LOWER, bcapn_lp, bcapn, nlist)
return port_dict
[docs] def _add_mom_cap_connect_cap_to_port(self, cap_dir: Direction, cap_lp: Tuple[str, str],
barr: BBoxArray, ports: List[WireArray]) -> None:
num_ports = len(ports)
if num_ports == 1:
self.connect_bbox_to_tracks(cap_dir, cap_lp, barr, ports[0].track_id)
else:
port_dir = self.grid.get_direction(ports[0].layer_id)
for idx, warr in enumerate(ports):
new_barr = barr.get_sub_array(port_dir, num_ports, idx)
self.connect_bbox_to_tracks(cap_dir, cap_lp, new_barr, warr.track_id)
[docs] def _add_mom_cap_connect_ports(self, bot_ports: List[WireArray], top_ports: List[WireArray]
) -> None:
for bot_warr, top_warr in product(bot_ports, top_ports):
self.add_via_on_grid(bot_warr.track_id, top_warr.track_id, extend=True)
[docs] def reserve_tracks(self, layer_id: int, track_idx: TrackType, *,
width: int = 1, num: int = 1, pitch: int = 0) -> None:
"""Reserve the given routing tracks so that power fill will not fill these tracks.
Note: the size of this template should be set before calling this method.
Parameters
----------
layer_id : int
the wire layer ID.
track_idx : TrackType
the smallest wire track index.
width : int
the wire width in number of tracks.
num : int
number of wires.
pitch : TrackType
the wire pitch.
"""
# TODO: fix this method
raise ValueError('Not implemented yet.')
[docs] def get_available_tracks(self, layer_id: int, tid_lo: TrackType, tid_hi: TrackType,
lower: int, upper: int, width: int = 1, sep: HalfInt = HalfInt(1),
include_last: bool = False, sep_margin: Optional[HalfInt] = None,
uniform_grid: bool = False) -> List[HalfInt]:
"""Returns a list of available tracks between the given bounds.
Parameters
----------
layer_id : int
the layer ID.
tid_lo : TrackType
the lower track index, inclusive.
tid_hi : TrackType
the upper track index, exclusive by default.
lower : int
the lower wire coordinate.
upper: int
the upper wire coordinate.
width : int
the track width.
sep : HalfInt
the track separation
include_last : bool
True to make "upper" inclusive.
sep_margin : Optional[HalfInt]
the margin between available tracks and surrounding wires, in number of tracks.
uniform_grid : bool
True to get available tracks on a uniform grid; False to get densely packed available tracks
Returns
-------
tidx_list : List[HalfInt]
list of available tracks.
"""
grid = self.grid
orient = grid.get_direction(layer_id)
tr_info = grid.get_track_info(layer_id)
if sep_margin is None:
sep_margin = grid.get_sep_tracks(layer_id, width, 1, same_color=False)
bl, bu = grid.get_wire_bounds_htr(layer_id, 0, width)
tr_w2 = (bu - bl) // 2
margin = tr_info.pitch * sep_margin - (tr_info.width // 2) - tr_w2
sp_list = [0, 0]
sp_list[orient.value ^ 1] = margin
spx, spy = sp_list
htr0 = HalfInt.convert(tid_lo).dbl_value
htr1 = HalfInt.convert(tid_hi).dbl_value
if include_last:
htr1 += 1
htr_sep = HalfInt.convert(sep).dbl_value
ans = []
cur_htr = htr0
while cur_htr < htr1:
mid = grid.htr_to_coord(layer_id, cur_htr)
box = BBox(orient, lower, upper, mid - tr_w2, mid + tr_w2)
if not self._layout.get_intersect(layer_id, box, spx, spy, False):
ans.append(HalfInt(cur_htr))
cur_htr += htr_sep
else:
cur_htr += htr_sep if uniform_grid else 1
return ans
[docs] def connect_wires(self, wire_arr_list: Union[WireArray, List[WireArray]], *,
lower: Optional[int] = None,
upper: Optional[int] = None,
debug: bool = False,
) -> List[WireArray]:
"""Connect all given WireArrays together.
all WireArrays must be on the same layer.
Parameters
----------
wire_arr_list : Union[WireArr, List[WireArr]]
WireArrays to connect together.
lower : Optional[CoordType]
if given, extend connection wires to this lower coordinate.
upper : Optional[CoordType]
if given, extend connection wires to this upper coordinate.
debug : bool
True to print debug messages.
Returns
-------
conn_list : List[WireArray]
list of connection wires created.
"""
grid = self._grid
if lower is None:
lower = COORD_MAX
if upper is None:
upper = COORD_MIN
# record all wire ranges
layer_id = None
intv_set = IntervalSet()
for wire_arr in WireArray.wire_grp_iter(wire_arr_list):
# NOTE: no need to copy with new grid, this TrackID is not used to create WireArrays
tid = wire_arr.track_id
lay_id = tid.layer_id
tr_w = tid.width
if layer_id is None:
layer_id = lay_id
elif lay_id != layer_id:
raise ValueError('WireArray layer ID != {}'.format(layer_id))
cur_range = wire_arr.lower, wire_arr.upper
for tidx in tid:
intv = grid.get_wire_bounds(lay_id, tidx, width=tr_w)
intv_rang_item = intv_set.get_first_overlap_item(intv)
if intv_rang_item is None:
range_set = IntervalSet()
range_set.add(cur_range)
intv_set.add(intv, val=(range_set, tidx, tr_w))
elif intv_rang_item[0] == intv:
tmp_rang_set: IntervalSet = intv_rang_item[1][0]
tmp_rang_set.add(cur_range, merge=True, abut=True)
else:
raise ValueError(f'wire on lay={lay_id}, track={tidx} overlap existing wires. '
f'wire interval={intv}, overlapped wire '
f'interval={intv_rang_item[0]}')
# draw wires, group into arrays
new_warr_list = []
base_start = None # type: Optional[int]
base_end = None # type: Optional[int]
base_tidx = None # type: Optional[HalfInt]
base_width = None # type: Optional[int]
count = 0
pitch = 0
last_tidx = 0
for set_item in intv_set.items():
intv = set_item[0]
range_set: IntervalSet = set_item[1][0]
cur_tidx: HalfInt = set_item[1][1]
cur_tr_w: int = set_item[1][2]
cur_start = min(lower, range_set.start)
cur_end = max(upper, range_set.stop)
if debug:
print('wires intv: %s, range: (%d, %d)' % (intv, cur_start, cur_end))
if count == 0:
base_tidx = cur_tidx
base_start = cur_start
base_end = cur_end
base_width = cur_tr_w
count = 1
pitch = 0
else:
assert base_tidx is not None, "count == 0 should have set base_intv"
assert base_width is not None, "count == 0 should have set base_width"
assert base_start is not None, "count == 0 should have set base_start"
assert base_end is not None, "count == 0 should have set base_end"
if cur_start == base_start and cur_end == base_end and base_width == cur_tr_w:
# length and width matches
cur_pitch = cur_tidx - last_tidx
if count == 1:
# second wire, set half pitch
pitch = cur_pitch
count += 1
elif pitch == cur_pitch:
# pitch matches
count += 1
else:
# pitch does not match, add current wires and start anew
track_id = TrackID(layer_id, base_tidx, width=base_width,
num=count, pitch=pitch, grid=grid)
warr = WireArray(track_id, base_start, base_end)
new_warr_list.append(warr)
self._layout.add_warr(track_id, base_start, base_end)
base_tidx = cur_tidx
count = 1
pitch = 0
else:
# length/width does not match, add cumulated wires and start anew
track_id = TrackID(layer_id, base_tidx, width=base_width,
num=count, pitch=pitch, grid=grid)
warr = WireArray(track_id, base_start, base_end)
new_warr_list.append(warr)
self._layout.add_warr(track_id, base_start, base_end)
base_start = cur_start
base_end = cur_end
base_tidx = cur_tidx
base_width = cur_tr_w
count = 1
pitch = 0
# update last lower coordinate
last_tidx = cur_tidx
if base_tidx is None:
# no wires given at all
return []
assert base_tidx is not None, "count == 0 should have set base_intv"
assert base_start is not None, "count == 0 should have set base_start"
assert base_end is not None, "count == 0 should have set base_end"
# add last wires
track_id = TrackID(layer_id, base_tidx, base_width, num=count, pitch=pitch, grid=grid)
warr = WireArray(track_id, base_start, base_end)
self._layout.add_warr(track_id, base_start, base_end)
new_warr_list.append(warr)
self._use_color = True
return new_warr_list
[docs] def connect_bbox_to_tracks(self, layer_dir: Direction, lay_purp: Tuple[str, str],
box_arr: Union[BBox, BBoxArray], track_id: TrackID, *,
track_lower: Optional[int] = None,
track_upper: Optional[int] = None,
min_len_mode: MinLenMode = MinLenMode.NONE,
wire_lower: Optional[int] = None,
wire_upper: Optional[int] = None,
ret_bnds: Optional[List[int]] = None) -> WireArray:
"""Connect the given primitive wire to given tracks.
Parameters
----------
layer_dir : Direction
the primitive wire layer direction relative to the given tracks. LOWER if
the wires are below tracks, UPPER if the wires are above tracks.
lay_purp : Tuple[str, str]
the primitive wire layer/purpose name.
box_arr : Union[BBox, BBoxArray]
bounding box of the wire(s) to connect to tracks.
track_id : TrackID
TrackID that specifies the track(s) to connect the given wires to.
track_lower : Optional[int]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[int]
if given, extend track(s) to this upper coordinate.
min_len_mode : MinLenMode
The minimum length extension mode.
wire_lower : Optional[int]
if given, extend wire(s) to this lower coordinate.
wire_upper : Optional[int]
if given, extend wire(s) to this upper coordinate.
ret_bnds : Optional[List[int]]
if given, return the bounds on the bounding box layer.
Returns
-------
wire_arr : WireArray
WireArray representing the tracks created.
"""
if isinstance(box_arr, BBox):
box_arr = BBoxArray(box_arr)
track_id = track_id.copy_with(self._grid)
bnds = self._layout.connect_barr_to_tracks(layer_dir, lay_purp[0], lay_purp[1], box_arr,
track_id, track_lower, track_upper, min_len_mode,
wire_lower, wire_upper)
tr_idx = 1 - layer_dir.value
if ret_bnds is not None:
ret_bnds[0] = bnds[layer_dir.value][0]
ret_bnds[1] = bnds[layer_dir.value][1]
self._use_color = True
return WireArray(track_id, bnds[tr_idx][0], bnds[tr_idx][1])
[docs] def connect_bbox_to_track_wires(self, layer_dir: Direction, lay_purp: Tuple[str, str],
box_arr: Union[BBox, BBoxArray],
track_wires: Union[WireArray, List[WireArray]], *,
min_len_mode: MinLenMode = MinLenMode.NONE,
ret_bnds: Optional[List[int]] = None
) -> Union[Optional[WireArray], List[Optional[WireArray]]]:
ans = []
bnds = [COORD_MAX, COORD_MIN]
for warr in WireArray.wire_grp_iter(track_wires):
cur_bnds = [0, 0]
tr = self.connect_bbox_to_tracks(layer_dir, lay_purp, box_arr,
warr.track_id, track_lower=warr.lower,
track_upper=warr.upper, min_len_mode=min_len_mode,
ret_bnds=cur_bnds)
ans.append(tr)
bnds[0] = min(bnds[0], cur_bnds[0])
bnds[1] = max(bnds[1], cur_bnds[1])
if ret_bnds is not None:
ret_bnds[0] = bnds[0]
ret_bnds[1] = bnds[1]
if isinstance(track_wires, WireArray):
return ans[0]
return ans
[docs] def connect_bbox_to_differential_tracks(self, p_lay_dir: Direction, n_lay_dir: Direction,
p_lay_purp: Tuple[str, str],
n_lay_purp: Tuple[str, str],
pbox: Union[BBox, BBoxArray],
nbox: Union[BBox, BBoxArray], tr_layer_id: int,
ptr_idx: TrackType, ntr_idx: TrackType, *,
width: int = 1, track_lower: Optional[int] = None,
track_upper: Optional[int] = None,
min_len_mode: MinLenMode = MinLenMode.NONE
) -> DiffWarrType:
"""Connect the given differential primitive wires to two tracks symmetrically.
This method makes sure the connections are symmetric and have identical parasitics.
Parameters
----------
p_lay_dir : Direction
positive signal layer direction.
n_lay_dir : Direction
negative signal layer direction.
p_lay_purp : Tuple[str, str]
positive signal layer/purpose pair.
n_lay_purp : Tuple[str, str]
negative signal layer/purpose pair.
pbox : Union[BBox, BBoxArray]
positive signal wires to connect.
nbox : Union[BBox, BBoxArray]
negative signal wires to connect.
tr_layer_id : int
track layer ID.
ptr_idx : TrackType
positive track index.
ntr_idx : TrackType
negative track index.
width : int
track width in number of tracks.
track_lower : Optional[int]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[int]
if given, extend track(s) to this upper coordinate.
min_len_mode : MinLenMode
the minimum length extension mode.
Returns
-------
p_track : Optional[WireArray]
the positive track.
n_track : Optional[WireArray]
the negative track.
"""
track_list = self.connect_bbox_to_matching_tracks([p_lay_dir, n_lay_dir],
[p_lay_purp, n_lay_purp], [pbox, nbox],
tr_layer_id, [ptr_idx, ntr_idx],
width=width, track_lower=track_lower,
track_upper=track_upper,
min_len_mode=min_len_mode)
return track_list[0], track_list[1]
[docs] def fix_track_min_length(self, tr_layer_id: int, width: int, track_lower: int, track_upper: int,
min_len_mode: MinLenMode) -> Tuple[int, int]:
even = min_len_mode is MinLenMode.MIDDLE
tr_len = self.grid.get_next_length(tr_layer_id, width, track_upper - track_lower, even=even)
if min_len_mode is MinLenMode.LOWER:
track_lower = track_upper - tr_len
elif min_len_mode is MinLenMode.UPPER:
track_upper = track_lower + tr_len
elif min_len_mode is MinLenMode.MIDDLE:
track_lower = (track_upper + track_lower - tr_len) // 2
track_upper = track_lower + tr_len
return track_lower, track_upper
[docs] def connect_bbox_to_matching_tracks(self, lay_dir_list: List[Direction],
lay_purp_list: List[Tuple[str, str]],
box_arr_list: List[Union[BBox, BBoxArray]],
tr_layer_id: int, tr_idx_list: List[TrackType], *,
width: int = 1, track_lower: Optional[int] = None,
track_upper: Optional[int] = None,
min_len_mode: MinLenMode = MinLenMode.NONE,
) -> List[Optional[WireArray]]:
"""Connect the given primitive wire to given tracks.
Parameters
----------
lay_dir_list : List[Direction]
the primitive wire layer direction list.
lay_purp_list : List[Tuple[str, str]]
the primitive wire layer/purpose list.
box_arr_list : List[Union[BBox, BBoxArray]]
bounding box of the wire(s) to connect to tracks.
tr_layer_id : int
track layer ID.
tr_idx_list : List[TrackType]
list of track indices.
width : int
track width in number of tracks.
track_lower : Optional[int]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[int]
if given, extend track(s) to this upper coordinate.
min_len_mode : MinLenMode
the minimum length extension mode.
Returns
-------
wire_arr : List[Optional[WireArray]]
WireArrays representing the tracks created.
"""
grid = self._grid
tr_dir = grid.get_direction(tr_layer_id)
w_dir = tr_dir.perpendicular()
num = len(lay_dir_list)
if len(lay_purp_list) != num or len(box_arr_list) != num or len(tr_idx_list) != num:
raise ValueError('Connection list parameters have mismatch length.')
if num == 0:
raise ValueError('Connection lists are empty.')
wl = None
wu = None
for lay_dir, (lay, purp), box_arr, tr_idx in zip(lay_dir_list, lay_purp_list,
box_arr_list, tr_idx_list):
if isinstance(box_arr, BBox):
box_arr = BBoxArray(box_arr)
tid = TrackID(tr_layer_id, tr_idx, width=width, grid=self._grid)
bnds = self._layout.connect_barr_to_tracks(lay_dir, lay, purp, box_arr, tid,
track_lower, track_upper, MinLenMode.NONE,
wl, wu)
w_idx = lay_dir.value
tr_idx = 1 - w_idx
wl = bnds[w_idx][0]
wu = bnds[w_idx][1]
track_lower = bnds[tr_idx][0]
track_upper = bnds[tr_idx][1]
# fix min_len_mode
track_lower, track_upper = self.fix_track_min_length(tr_layer_id, width, track_lower,
track_upper, min_len_mode)
# extend wires
ans = []
for (lay, purp), box_arr, tr_idx in zip(lay_purp_list, box_arr_list, tr_idx_list):
if isinstance(box_arr, BBox):
box_arr = BBoxArray(box_arr)
else:
box_arr = BBoxArray(box_arr.base, tr_dir, nt=box_arr.get_num(tr_dir),
spt=box_arr.get_sp(tr_dir))
box_arr.set_interval(w_dir, wl, wu)
self._layout.add_rect_arr(lay, purp, box_arr)
cur_tid = TrackID(tr_layer_id, tr_idx, width=width, grid=grid)
warr = WireArray(cur_tid, track_lower, track_upper)
self._layout.add_warr(cur_tid, track_lower, track_upper)
ans.append(warr)
self._use_color = True
return ans
[docs] def connect_to_tracks(self, wire_arr_list: Union[WireArray, List[WireArray]],
track_id: TrackID, *, wire_lower: Optional[int] = None,
wire_upper: Optional[int] = None, track_lower: Optional[int] = None,
track_upper: Optional[int] = None, min_len_mode: MinLenMode = None,
ret_wire_list: Optional[List[WireArray]] = None,
debug: bool = False) -> Optional[WireArray]:
"""Connect all given WireArrays to the given track(s).
All given wires should be on adjacent layers of the track.
Parameters
----------
wire_arr_list : Union[WireArray, List[WireArray]]
list of WireArrays to connect to track.
track_id : TrackID
TrackID that specifies the track(s) to connect the given wires to.
wire_lower : Optional[CoordType]
if given, extend wire(s) to this lower coordinate.
wire_upper : Optional[CoordType]
if given, extend wire(s) to this upper coordinate.
track_lower : Optional[CoordType]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[CoordType]
if given, extend track(s) to this upper coordinate.
min_len_mode : MinLenMode
the minimum length extension mode.
ret_wire_list : Optional[List[WireArray]]
If not none, extended wires that are created will be appended to this list.
debug : bool
True to print debug messages.
Returns
-------
wire_arr : Optional[WireArray]
WireArray representing the tracks created.
"""
if track_lower is None:
track_lower = COORD_MAX
if track_upper is None:
track_upper = COORD_MIN
# find min/max track Y coordinates
track_id = track_id.copy_with(self._grid)
tr_layer_id = track_id.layer_id
tr_w = track_id.width
# get top wire and bottom wire list
warr_list_list = [[], []]
for wire_arr in WireArray.wire_grp_iter(wire_arr_list):
cur_layer_id = wire_arr.layer_id
if cur_layer_id == tr_layer_id + 1:
warr_list_list[1].append(wire_arr)
elif cur_layer_id == tr_layer_id - 1:
warr_list_list[0].append(wire_arr)
else:
raise ValueError(
'WireArray layer %d cannot connect to layer %d' % (cur_layer_id, tr_layer_id))
if not warr_list_list[0] and not warr_list_list[1]:
# no wires at all
return None
# connect wires together
tmp = self._connect_to_tracks_helper(warr_list_list[0], track_id, wire_lower, wire_upper,
track_lower, track_upper, ret_wire_list, 0, debug)
track_lower, track_upper = tmp
tmp = self._connect_to_tracks_helper(warr_list_list[1], track_id, wire_lower, wire_upper,
track_lower, track_upper, ret_wire_list, 1, debug)
track_lower, track_upper = tmp
# fix min_len_mode
track_lower, track_upper = self.fix_track_min_length(tr_layer_id, tr_w, track_lower,
track_upper, min_len_mode)
result = WireArray(track_id, track_lower, track_upper)
self._layout.add_warr(track_id, track_lower, track_upper)
self._use_color = True
return result
[docs] def _connect_to_tracks_helper(self, warr_list: List[WireArray], track_id: TrackID,
wire_lower: Optional[int], wire_upper: Optional[int],
track_lower: int, track_upper: int,
ret_wire_list: Optional[List[WireArray]], idx: int,
debug: bool) -> Tuple[Optional[int], Optional[int]]:
# precondition: track_id has correct routing grid, but not WireArrays in warr_list
for warr in self.connect_wires(warr_list, lower=wire_lower, upper=wire_upper,
debug=debug):
bnds = self._layout.connect_warr_to_tracks(warr.track_id, track_id,
warr.lower, warr.upper)
if ret_wire_list is not None:
new_tid = warr.track_id.copy_with(self._grid)
ret_wire_list.append(WireArray(new_tid, bnds[idx][0], bnds[idx][1]))
track_lower = min(track_lower, bnds[1 - idx][0])
track_upper = max(track_upper, bnds[1 - idx][1])
return track_lower, track_upper
[docs] def connect_to_track_wires(self, wire_arr_list: Union[WireArray, List[WireArray]],
track_wires: Union[WireArray, List[WireArray]], *,
min_len_mode: Optional[MinLenMode] = None,
ret_wire_list: Optional[List[WireArray]] = None,
debug: bool = False) -> Union[Optional[WireArray],
List[Optional[WireArray]]]:
"""Connect all given WireArrays to the given WireArrays on adjacent layer.
Parameters
----------
wire_arr_list : Union[WireArray, List[WireArray]]
list of WireArrays to connect to track.
track_wires : Union[WireArray, List[WireArray]]
list of tracks as WireArrays.
min_len_mode : MinLenMode
the minimum length extension mode.
ret_wire_list : Optional[List[WireArray]]
If not none, extended wires that are created will be appended to this list.
debug : bool
True to print debug messages.
Returns
-------
wire_arr : Union[Optional[WireArray], List[Optional[WireArray]]]
WireArrays representing the tracks created. None if nothing to do.
"""
ans = [] # type: List[Optional[WireArray]]
for warr in WireArray.wire_grp_iter(track_wires):
tr = self.connect_to_tracks(wire_arr_list, warr.track_id, track_lower=warr.lower,
track_upper=warr.upper, min_len_mode=min_len_mode, ret_wire_list=ret_wire_list,
debug=debug)
ans.append(tr)
if isinstance(track_wires, WireArray):
return ans[0]
return ans
[docs] def connect_differential_tracks(self, pwarr_list: Union[WireArray, List[WireArray]],
nwarr_list: Union[WireArray, List[WireArray]],
tr_layer_id: int, ptr_idx: TrackType, ntr_idx: TrackType, *,
width: int = 1, track_lower: Optional[int] = None,
track_upper: Optional[int] = None
) -> Tuple[Optional[WireArray], Optional[WireArray]]:
"""Connect the given differential wires to two tracks symmetrically.
This method makes sure the connections are symmetric and have identical parasitics.
Parameters
----------
pwarr_list : Union[WireArray, List[WireArray]]
positive signal wires to connect.
nwarr_list : Union[WireArray, List[WireArray]]
negative signal wires to connect.
tr_layer_id : int
track layer ID.
ptr_idx : TrackType
positive track index.
ntr_idx : TrackType
negative track index.
width : int
track width in number of tracks.
track_lower : Optional[int]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[int]
if given, extend track(s) to this upper coordinate.
Returns
-------
p_track : Optional[WireArray]
the positive track.
n_track : Optional[WireArray]
the negative track.
"""
track_list = self.connect_matching_tracks([pwarr_list, nwarr_list], tr_layer_id,
[ptr_idx, ntr_idx], width=width,
track_lower=track_lower, track_upper=track_upper)
return track_list[0], track_list[1]
[docs] def connect_differential_wires(self, pin_warrs: Union[WireArray, List[WireArray]],
nin_warrs: Union[WireArray, List[WireArray]],
pout_warr: WireArray, nout_warr: WireArray, *,
track_lower: Optional[int] = None,
track_upper: Optional[int] = None
) -> Tuple[Optional[WireArray], Optional[WireArray]]:
"""Connect the given differential wires to two WireArrays symmetrically.
This method makes sure the connections are symmetric and have identical parasitics.
Parameters
----------
pin_warrs : Union[WireArray, List[WireArray]]
positive signal wires to connect.
nin_warrs : Union[WireArray, List[WireArray]]
negative signal wires to connect.
pout_warr : WireArray
positive track wires.
nout_warr : WireArray
negative track wires.
track_lower : Optional[int]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[int]
if given, extend track(s) to this upper coordinate.
Returns
-------
p_track : Optional[WireArray]
the positive track.
n_track : Optional[WireArray]
the negative track.
"""
p_tid = pout_warr.track_id
lay_id = p_tid.layer_id
pidx = p_tid.base_index
nidx = nout_warr.track_id.base_index
width = p_tid.width
if track_lower is None:
tr_lower = pout_warr.lower
else:
tr_lower = min(track_lower, pout_warr.lower)
if track_upper is None:
tr_upper = pout_warr.upper
else:
tr_upper = max(track_upper, pout_warr.upper)
return self.connect_differential_tracks(pin_warrs, nin_warrs, lay_id, pidx, nidx,
width=width, track_lower=tr_lower,
track_upper=tr_upper)
[docs] def connect_matching_tracks(self, warr_list_list: List[Union[WireArray, List[WireArray]]],
tr_layer_id: int, tr_idx_list: List[TrackType], *,
width: int = 1,
track_lower: Optional[int] = None,
track_upper: Optional[int] = None,
min_len_mode: MinLenMode = MinLenMode.NONE
) -> List[Optional[WireArray]]:
"""Connect wires to tracks with optimal matching.
This method connects the wires to tracks in a way that minimizes the parasitic mismatches.
Parameters
----------
warr_list_list : List[Union[WireArray, List[WireArray]]]
list of signal wires to connect.
tr_layer_id : int
track layer ID.
tr_idx_list : List[TrackType]
list of track indices.
width : int
track width in number of tracks.
track_lower : Optional[int]
if given, extend track(s) to this lower coordinate.
track_upper : Optional[int]
if given, extend track(s) to this upper coordinate.
min_len_mode : MinLenMode
the minimum length extension mode.
Returns
-------
track_list : List[WireArray]
list of created tracks.
"""
# simple error checking
num_tracks = len(tr_idx_list) # type: int
if num_tracks != len(warr_list_list):
raise ValueError('Connection list parameters have mismatch length.')
if num_tracks == 0:
raise ValueError('Connection lists are empty.')
if track_lower is None:
track_lower = COORD_MAX
if track_upper is None:
track_upper = COORD_MIN
wbounds = [[COORD_MAX, COORD_MIN], [COORD_MAX, COORD_MIN]]
for warr_list, tr_idx in zip(warr_list_list, tr_idx_list):
tid = TrackID(tr_layer_id, tr_idx, width=width, grid=self._grid)
for warr in WireArray.wire_grp_iter(warr_list):
cur_lay_id = warr.layer_id
if cur_lay_id == tr_layer_id + 1:
wb_idx = 1
elif cur_lay_id == tr_layer_id - 1:
wb_idx = 0
else:
raise ValueError(
'WireArray layer {} cannot connect to layer {}'.format(cur_lay_id,
tr_layer_id))
bnds = self._layout.connect_warr_to_tracks(warr.track_id, tid,
warr.lower, warr.upper)
wbounds[wb_idx][0] = min(wbounds[wb_idx][0], bnds[wb_idx][0])
wbounds[wb_idx][1] = max(wbounds[wb_idx][1], bnds[wb_idx][1])
track_lower = min(track_lower, bnds[1 - wb_idx][0])
track_upper = max(track_upper, bnds[1 - wb_idx][1])
# fix min_len_mode
track_lower, track_upper = self.fix_track_min_length(tr_layer_id, width, track_lower,
track_upper, min_len_mode)
# extend wires
ans = []
for warr_list, tr_idx in zip(warr_list_list, tr_idx_list):
for warr in WireArray.wire_grp_iter(warr_list):
wb_idx = (warr.layer_id - tr_layer_id + 1) // 2
self._layout.add_warr(warr.track_id, wbounds[wb_idx][0], wbounds[wb_idx][1])
cur_tid = TrackID(tr_layer_id, tr_idx, width=width, grid=self._grid)
warr = WireArray(cur_tid, track_lower, track_upper)
self._layout.add_warr(cur_tid, track_lower, track_upper)
ans.append(warr)
self._use_color = True
return ans
[docs] def draw_vias_on_intersections(self, bot_warr_list: Union[WireArray, List[WireArray]],
top_warr_list: Union[WireArray, List[WireArray]]) -> None:
"""Draw vias on all intersections of the two given wire groups.
Parameters
----------
bot_warr_list : Union[WireArray, List[WireArray]]
the bottom wires.
top_warr_list : Union[WireArray, List[WireArray]]
the top wires.
"""
for bwarr in WireArray.wire_grp_iter(bot_warr_list):
for twarr in WireArray.wire_grp_iter(top_warr_list):
self._layout.add_via_on_intersections(bwarr.track_id, twarr.track_id,
bwarr.lower, bwarr.upper,
twarr.lower, twarr.upper, True, True)
[docs] def mark_bbox_used(self, layer_id: int, bbox: BBox) -> None:
"""Marks the given bounding-box region as used in this Template."""
# TODO: Fix this
raise ValueError('Not implemented yet')
[docs] def do_max_space_fill(self, layer_id: int, bound_box: Optional[BBox] = None,
fill_boundary: bool = True) -> None:
"""Draw density fill on the given layer."""
if bound_box is None:
bound_box = self.bound_box
fill_info = self.grid.tech_info.get_max_space_fill_info(layer_id)
self._layout.do_max_space_fill(layer_id, bound_box, fill_boundary, fill_info.info)
self._use_color = True
[docs] def do_device_fill(self, fill_cls: Type[TemplateBase], **kwargs: Any) -> None:
"""Fill empty region with device fills."""
bbox = self.bound_box
if bbox is None:
raise ValueError('bound_box attribute is not set.')
lookup = RTree()
ed = ImmutableSortedDict()
lookup.insert(None, bbox)
# subtract instance bounding boxes
for inst in self._instances.values():
if inst.committed:
inst_box = inst.bound_box
inst_edges = inst.master.edge_info
if inst_edges is None:
# TODO: implement this. Need to recurse down instance hierarchy
raise ValueError('Not implemented, see developer.')
# save items in list, because we'll remove them from the index
item_list = list(lookup.intersect_iter(inst_box))
for box, item_id in item_list:
if box.get_intersect(inst_box).is_physical():
box_edges = cast(Optional[TemplateEdgeInfo], lookup.pop(item_id))
_update_device_fill_area(lookup, ed, inst_box, inst_edges, box, box_edges)
# draw fill
cnt = 0
for box, obj_id in lookup:
kwargs['width'] = box.w
kwargs['height'] = box.h
kwargs['edges'] = lookup[obj_id]
master = self.new_template(fill_cls, params=kwargs)
self.add_instance(master, inst_name=f'XFILL{cnt}', xform=Transform(box.xl, box.yl))
cnt += 1
[docs] def do_power_fill(self, layer_id: int, tr_manager: TrackManager,
vdd_warrs: Optional[Union[WireArray, List[WireArray]]] = None,
vss_warrs: Optional[Union[WireArray, List[WireArray]]] = None, bound_box: Optional[BBox] = None,
x_margin: int = 0, y_margin: int = 0, sup_type: str = 'both', flip: bool = False,
uniform_grid: bool = False) -> Tuple[List[WireArray], List[WireArray]]:
"""Draw power fill on the given layer. Wrapper around do_multi_power_fill method
that only returns the VDD and VSS wires.
Parameters
----------
layer_id : int
the layer ID on which to draw power fill.
tr_manager : TrackManager
the TrackManager object.
sup_list : List[Union[WireArray, List[WireArray]]]
a list of supply wires to draw power fill for.
bound_box : Optional[BBox]
bound box over which to draw the power fill
x_margin : int
keepout margin on the x-axis. Fill is centered within margin.
y_margin : int
keepout margin on the y-axis. Fill is centered within margin.
uniform_grid : bool
draw power fill on a common grid instead of dense packing.
flip : bool
true to reverse order of power fill. Default (False) is {VDD, VSS}.
Returns
-------
Tuple[List[WireArray], List[WireArray]]
Tuple of VDD and VSS wires. If only one supply was specified, the other will be an empty list.
"""
# Value checks
if sup_type.lower() not in ['vdd', 'vss', 'both']:
raise ValueError('sup_type has to be "VDD" or "VSS" or "both"(default)')
if not vdd_warrs and not vss_warrs:
raise ValueError('At least one of vdd_warrs or vss_warrs must be given.')
# Build supply lists based on specficiation
if sup_type.lower() == 'both' and vdd_warrs and vss_warrs:
top_lists = [vdd_warrs, vss_warrs]
elif sup_type.lower() == 'vss' and vss_warrs:
top_lists = [vss_warrs]
elif sup_type.lower() == 'vdd' and vdd_warrs:
top_lists = [vdd_warrs]
else:
raise RuntimeError('Provided supply type and supply wires do not match.')
# Run the actual power fill using the multi_power_fill function
ret_warrs = self.do_multi_power_fill(layer_id, tr_manager, top_lists, bound_box,
x_margin, y_margin, flip, uniform_grid)
# Reorganize return values
if sup_type.lower() == 'both':
top_vdd, top_vss = (ret_warrs[0], ret_warrs[1]) if not flip else (ret_warrs[1], ret_warrs[0])
elif sup_type.lower() == 'vss':
top_vdd = []
top_vss = ret_warrs[0]
elif sup_type.lower() == 'vdd':
top_vss = []
top_vdd = ret_warrs[0]
return top_vdd, top_vss
[docs] def do_multi_power_fill(self, layer_id: int, tr_manager: TrackManager, sup_list: List[Union[WireArray, List[WireArray]]],
bound_box: Optional[BBox] = None, x_margin: int = 0, y_margin: int = 0, flip: bool = False,
uniform_grid: bool = False) -> List[List[WireArray]]:
"""Draw power fill on the given layer. Accepts as many different supply nets as provided.
Parameters
----------
layer_id : int
the layer ID on which to draw power fill.
tr_manager : TrackManager
the TrackManager object.
sup_list : List[Union[WireArray, List[WireArray]]]
a list of supply wires to draw power fill for.
bound_box : Optional[BBox]
bound box over which to draw the power fill
x_margin : int
keepout margin on the x-axis. Fill is centered within margin.
y_margin : int
keepout margin on the y-axis. Fill is centered within margin.
uniform_grid : bool
draw power fill on a common grid instead of dense packing.
flip : bool
true to reverse order of power fill. Default is False.
Returns
-------
List[List[WireArray]]
List of the wire arrays for each supply in sup_list, given in the
same order as sup_list.
"""
if bound_box is None:
if self.bound_box is None:
raise ValueError("bound_box is not set")
bound_box = self.bound_box
bound_box = bound_box.expand(dx=-x_margin, dy=-y_margin)
is_horizontal = (self.grid.get_direction(layer_id) == 0)
num_sups = len(sup_list)
if is_horizontal:
cl, cu = bound_box.yl, bound_box.yh
lower, upper = bound_box.xl, bound_box.xh
else:
cl, cu = bound_box.xl, bound_box.xh
lower, upper = bound_box.yl, bound_box.yh
fill_width = tr_manager.get_width(layer_id, 'sup')
fill_space = tr_manager.get_sep(layer_id, ('sup', 'sup'))
sep_margin = tr_manager.get_sep(layer_id, ('sup', ''))
tr_bot = self.grid.coord_to_track(layer_id, cl, mode=RoundMode.GREATER_EQ)
tr_top = self.grid.coord_to_track(layer_id, cu, mode=RoundMode.LESS_EQ)
trs = self.get_available_tracks(layer_id, tid_lo=tr_bot, tid_hi=tr_top, lower=lower, upper=upper,
width=fill_width, sep=fill_space, sep_margin=sep_margin,
uniform_grid=uniform_grid)
all_warrs = [[] for _ in range(num_sups)]
htr_sep = HalfInt.convert(fill_space).dbl_value
if len(trs) < num_sups:
raise ValueError('Not enough available tracks to fill for all provided supplies')
for ncur, tr_idx in enumerate(trs):
warr = self.add_wires(layer_id, tr_idx, lower, upper, width=fill_width)
_ncur = HalfInt.convert(tr_idx).dbl_value // htr_sep if uniform_grid else ncur
if not flip:
all_warrs[_ncur % num_sups].append(warr)
else:
all_warrs[(num_sups - 1) - (_ncur % num_sups)].append(warr)
for top_warr, bot_warr in zip(sup_list, all_warrs):
self.draw_vias_on_intersections(top_warr, bot_warr)
return all_warrs
[docs] def get_lef_options(self, options: Dict[str, Any], config: Mapping[str, Any]) -> None:
"""Populate the LEF options dictionary.
Parameters
----------
options : Mapping[str, Any]
the result LEF options dictionary.
config : Mapping[str, Any]
the LEF configuration dictionary.
"""
if not self.finalized:
raise ValueError('This method only works on finalized master.')
detail_layers_inc = config.get('detail_layers', [])
top_layer = self.top_layer
tech_info = self.grid.tech_info
cover_layers = set(range(tech_info.bot_layer, top_layer + 1))
detail_layers = set()
for lay in detail_layers_inc:
detail_layers.add(lay)
cover_layers. discard(lay)
options['detailed_layers'] = [lay for lay_id in sorted(detail_layers)
for lay, _ in tech_info.get_lay_purp_list(lay_id)]
options['cover_layers'] = [lay for lay_id in sorted(cover_layers)
for lay, _ in tech_info.get_lay_purp_list(lay_id)]
options['cell_type'] = config.get('cell_type', 'block')
[docs] def find_track_width(self, layer_id: int, width: int) -> int:
"""Find the track width corresponding to the physical width
Parameters
----------
layer_id: int
The metal layer ID
width: int
Physical width of the wire, in resolution units
Returns
-------
tr_width: int
"""
bin_iter = BinaryIterator(low=1)
while bin_iter.has_next():
cur = bin_iter.get_next()
cur_width = self.grid.get_wire_total_width(layer_id, cur)
if cur_width == width:
return cur
if cur_width < width:
bin_iter.up()
else: # cur_width > width
bin_iter.down()
raise ValueError(f'Cannot find track width for width={width} on layer={layer_id}.')
[docs] def connect_via_stack(self, tr_manager: TrackManager, warr: WireArray, top_layer: int, w_type: str = 'sig',
alignment_p: int = 0, alignment_o: int = 0,
mlm_dict: Optional[Mapping[int, MinLenMode]] = None,
ret_warr_dict: Optional[Mapping[int, WireArray]] = None,
coord_list_p_override: Optional[Sequence[int]] = None,
coord_list_o_override: Optional[Sequence[int]] = None, alternate_o: bool = False
) -> WireArray:
"""Helper method to draw via stack and connections upto top layer, assuming connections can be on grid.
Should work regardless of direction of top layer and bot layer.
This method supports equally spaced WireArrays only. Needs modification for non uniformly spaced WireArrays.
Parameters
----------
tr_manager: TrackManager
the track manager for this layout generator
warr: WireArray
The bot_layer wire array that has to via up
top_layer: int
The top_layer upto which stacked via has to go
w_type: str
The wire type, for querying widths from track manager
alignment_p: int
alignment for wire arrays which are parallel to bot_layer warr
If alignment == -1, will "left adjust" the wires (left is the lower index direction).
If alignment == 0, will center the wires in the middle.
If alignment == 1, will "right adjust" the wires.
alignment_o: int
alignment for wire arrays which are orthogonal to bot_layer warr
mlm_dict: Optional[Mapping[int, MinLenMode]]
Dictionary of MinLenMode for every metal layer. Uses MinLenMode.MIDDLE by default
ret_warr_dict: Optional[Mapping[int, WireArray]]
If provided, this dictionary will contain all the WireArrays created during via stacking
coord_list_p_override: Optional[Sequence[int]]
List of co-ordinates for WireArrays parallel to bot_layer wire, assumed to be equally spaced
coord_list_o_override: Optional[Sequence[int]]
List of co-ordinates for WireArrays orthogonal to bot_layer wire, assumed to be equally spaced
alternate_o: bool
If coord_o_list is computed (i.e. coord_o_list_override is not used) then every other track is skipped
for via spacing. Using alternate_o, we can choose which set of tracks is used and which is skipped.
This is useful to avoid line end spacing issues when two adjacent wires require via stacks.
Returns
-------
top_warr: WireArray
The top_layer warr after via stacking all the way up
"""
if ret_warr_dict is None:
ret_warr_dict = {}
if mlm_dict is None:
mlm_dict = {}
bot_layer = warr.layer_id
# assert bot_layer + 2 <= top_layer, f'top_layer={top_layer} must be at least 2 higher than ' \
# f'bot_layer={bot_layer} to have via stack'
# Make sure widths are enough so that we can via up to top_layer
top_layer_w = tr_manager.get_width(top_layer, w_type)
w_dict = {top_layer: top_layer_w}
for _layer in range(top_layer - 1, bot_layer - 1, -1):
top_layer_w = w_dict[_layer] = self.grid.get_min_track_width(_layer, top_ntr=top_layer_w)
assert warr.track_id.width >= w_dict[bot_layer], f'It is not possible to via up from given WireArray to the ' \
f'top_layer={top_layer} with width={top_layer_w} specified ' \
f'by the TrackManager.'
# Find number of tracks to be used for layers with direction orthogonal to bot_layer, based on topmost
# layer in that direction, called as top_layer_o ("_o" means orthogonal to bot_layer)
bot_dir = self.grid.get_direction(bot_layer)
top_dir = self.grid.get_direction(top_layer)
top_layer_o = top_layer - 1 if bot_dir == top_dir else top_layer
if coord_list_o_override is None:
tidx_l = self.grid.coord_to_track(top_layer_o, warr.lower, RoundMode.GREATER_EQ)
tidx_r = self.grid.coord_to_track(top_layer_o, warr.upper, RoundMode.LESS_EQ)
# Divide by 2 for via separation
num_wires_o = tr_manager.get_num_wires_between(top_layer_o, w_type, tidx_l, w_type, tidx_r, w_type) + 2
num_wires_o = max(-(- num_wires_o // 2), 1)
if num_wires_o == 1:
tidx_list_o = [self.grid.coord_to_track(top_layer_o, warr.middle, RoundMode.NEAREST)]
else:
tidx_list_o = tr_manager.spread_wires(top_layer_o, [w_type] * (2 * num_wires_o - 1), tidx_l, tidx_r,
(w_type, w_type), alignment=alignment_o)
tidx_list_o = tidx_list_o[1::2] if alternate_o else tidx_list_o[0::2]
num_wires_o = len(tidx_list_o)
# need to compute coord_list for conversion to tidx in layers which are same direction as top_layer_o
coord_list_o = [self.grid.track_to_coord(top_layer_o, tidx) for tidx in tidx_list_o]
else:
num_wires_o = len(coord_list_o_override)
coord_list_o = list(coord_list_o_override)
coord_list_o.sort()
if coord_list_p_override is None:
# Find coord_list_p for co-ordinates of tracks of layers that are parallel to bot_layer
coord_list_p = [self.grid.track_to_coord(bot_layer, tidx) for tidx in warr.track_id]
else:
coord_list_p = list(coord_list_p_override)
coord_list_p.sort()
for _layer in range(bot_layer + 1, top_layer + 1):
_dir = self.grid.get_direction(_layer)
_w = tr_manager.get_width(_layer, w_type)
assert _w >= w_dict[_layer], f'It is not possible to via up because of width={_w} of layer={_layer} ' \
f'specified by TrackManager.'
_mlm = mlm_dict.get(_layer, MinLenMode.MIDDLE)
if _dir == bot_dir:
if coord_list_p_override is None and len(coord_list_p) > 1:
tidx_l = self.grid.coord_to_track(_layer, coord_list_p[0], RoundMode.NEAREST)
tidx_r = self.grid.coord_to_track(_layer, coord_list_p[-1], RoundMode.NEAREST)
# Divide by 2 for via separation
num_wires_p = tr_manager.get_num_wires_between(_layer, w_type, tidx_l, w_type, tidx_r, w_type) + 2
num_wires_p = max(-(- num_wires_p // 2), 1)
num_avail = len(coord_list_p)
if num_wires_p < num_avail:
# adjust num_wires_p to ensure that vias are formed in a stack
num_wires_p = min(num_wires_p, (num_avail + 1) // 2)
if num_avail % 2 == 0:
if alignment_p > 0:
tidx_l = self.grid.coord_to_track(_layer, coord_list_p[1], RoundMode.NEAREST)
else:
tidx_r = self.grid.coord_to_track(_layer, coord_list_p[-2], RoundMode.NEAREST)
else: # num_wires_p >= num_avail:
# use entire coord_list_p
num_wires_p = num_avail
_tidx_list = tr_manager.spread_wires(_layer, [w_type] * num_wires_p, tidx_l, tidx_r,
(w_type, w_type),
alignment=0)
_num = num_wires_p
else:
_num = len(coord_list_p)
_tidx_list = [self.grid.coord_to_track(_layer, coord, RoundMode.NEAREST) for coord in coord_list_p]
else:
_tidx_list = [self.grid.coord_to_track(_layer, coord, RoundMode.NEAREST) for coord in coord_list_o]
_num = num_wires_o
sep = _tidx_list[1] - _tidx_list[0] if _num > 1 else 0
# TODO: support non-uniformly spaced list of WireArrays
warr = self.connect_to_tracks(warr, TrackID(_layer, _tidx_list[0], _w, num=_num, pitch=sep),
min_len_mode=_mlm)
ret_warr_dict[_layer] = warr
return warr
@property
[docs] def has_guard_ring(self) -> bool:
return self._grid.tech_info.has_guard_ring
[docs]def _update_device_fill_area(lookup: RTree, ed: Param, inst_box: BBox, inst_edges: TemplateEdgeInfo,
sp_box: BBox, sp_edges: Optional[TemplateEdgeInfo]) -> None:
# find instance edge with no constraints
cut_edge_dir: Optional[Direction2D] = None
cut_edge_dir_backup: Optional[Direction2D] = None
two_backup = False
# start at 1 so we prefer cutting horizontally
for edir in (Direction2D.SOUTH, Direction2D.EAST, Direction2D.NORTH, Direction2D.WEST):
if not inst_edges.get_edge_params(edir):
if inst_edges.get_edge_params(edir.flip()):
two_backup = cut_edge_dir_backup is not None
if not two_backup:
cut_edge_dir_backup = edir
else:
cut_edge_dir = edir
break
bxl = sp_box.xl
byl = sp_box.yl
bxh = sp_box.xh
byh = sp_box.yh
ixl = inst_box.xl
iyl = inst_box.yl
ixh = inst_box.xh
iyh = inst_box.yh
if sp_edges is None:
bel = beb = ber = bet = ed
else:
bel, beb, ber, bet = sp_edges.to_tuple()
iel, ieb, ier, iet = inst_edges.to_tuple()
sq_list = [(BBox(bxl, byl, ixl, iyl), (bel, beb, ed, ed)),
(BBox(ixl, byl, ixh, iyl), (ed, beb, ed, iet)),
(BBox(ixh, byl, bxh, iyl), (ed, beb, ber, ed)),
(BBox(ixh, iyl, bxh, iyh), (ier, ed, ber, ed)),
(BBox(ixh, iyh, bxh, byh), (ed, ed, ber, bet)),
(BBox(ixl, iyh, ixh, byh), (ed, ieb, ed, bet)),
(BBox(bxl, iyh, ixl, byh), (bel, ed, ed, bet)),
(BBox(bxl, iyl, ixl, iyh), (bel, ed, iel, ed)),
]
if cut_edge_dir is not None:
# found opposite edges with no constraints, we're done
if cut_edge_dir.is_vertical:
# cut horizontally
tile_list = [sq_list[3], sq_list[7], _fill_merge(sq_list, 0, True),
_fill_merge(sq_list, 4, True)]
else:
# cut vertically
tile_list = [sq_list[1], sq_list[5], _fill_merge(sq_list, 2, True),
_fill_merge(sq_list, 6, True)]
elif cut_edge_dir_backup is not None:
if two_backup:
# two adjacent cut idx. Cut horizontally
istart = 2 * cut_edge_dir_backup.value + 3
istop = istart + 3
if istop > 8:
tile_list = sq_list[istart:]
tile_list.extend(sq_list[:istop - 8])
else:
tile_list = sq_list[istart:istop]
istart = istop % 8
tile_list.append(_fill_merge(sq_list, istart, True))
tile_list.append(_fill_merge(sq_list, (istart + 3) % 8, False))
else:
istart = 2 * cut_edge_dir_backup.value + 1
istop = istart + 5
if istop > 8:
tile_list = sq_list[istart:]
tile_list.extend(sq_list[:istop - 8])
else:
tile_list = sq_list[istart:istop]
istart = istop % 8
tile_list.append(_fill_merge(sq_list, istart, True))
else:
tile_list = sq_list
for box, edges in tile_list:
if box.is_physical():
lookup.insert(edges, box)
[docs]def _fill_merge(sq_list: List[Tuple[BBox, Tuple[Param, Param, Param, Param]]],
istart: int, merge_two: bool) -> Tuple[BBox, Tuple[Param, Param, Param, Param]]:
box = sq_list[istart][0]
edges = list(sq_list[istart][1])
istop = istart + 3 if merge_two else istart + 2
for idx in range(istart + 1, istop):
cur_box, cur_edges = sq_list[idx % 8]
if not box.is_physical():
box = cur_box
edges = list(cur_edges)
elif cur_box.is_physical():
if cur_box.xl < box.xl:
edges[0] = cur_edges[0]
if cur_box.yl < box.yl:
edges[1] = cur_edges[1]
if cur_box.xh > box.xh:
edges[2] = cur_edges[2]
if cur_box.yh > box.yh:
edges[3] = cur_edges[3]
box.merge(cur_box)
return box, (edges[0], edges[1], edges[2], edges[3])