Source code for laygo2.object.physical

#!/usr/bin/python
########################################################################################################################
#
# Copyright (c) 2020, Nifty Chips Laboratory, Hanyang University
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
#   disclaimer.
# 2. 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.
#
# 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.
#
########################################################################################################################

"""
**laygo2.object.physical** module provides classes for physical objects 
used in IC layout design. The supported physical objects include:

:obj:`PhysicalObject` - Base class for all physical layout objects.

:obj:`IterablePhysicalObject` - Base class for physical objects that can be iterated (eg. arrayed instances).

:obj:`PhysicalObjectGroup` - Defines a group of physical objects (currently not implemented).

:obj:`Rect` - Represents a rectangular shape.

:obj:`Path` - Defines a path.

:obj:`Pin` - Defines a pin.

:obj:`Text` - Defines a text label.

:obj:`Instance` - Represents an instance of a design element.

:obj:`VirtualInstance` - Represents a virtual instance composed of multiple physical objects, acting as a single instance.

Examples of the physical objects supported by this module are shown in the followng figure.

.. image:: ../assets/img/user_guide_physical.png



Check the following links for further details.
"""

__author__ = "Jaeduk Han"
__maintainer__ = "Jaeduk Han"
__status__ = "Prototype"

import numpy as np

# from copy import deepcopy
import laygo2.util.transform as tf


[docs] class PhysicalObject: """ Base class for physical layout objects with physical coordinate info. """
[docs] def _get_xy(self): """numpy.ndarray(dtype=numpy.int): Retrive x,y coordinate values of the object.""" return self._xy
[docs] def _set_xy(self, value): """numpy.ndarray(dtype=numpy.int): Update x,y coordinates of the object.""" self._xy = np.asarray(value, dtype=int) self._update_pointers()
name = None """str: Object name. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]], name="test", params={'maxI': 0.005}) >>> obj.name “test” .. image:: ../assets/img/object_physical_PhysicalObject_name.png :height: 250 """ _xy = np.zeros(2, dtype=int) """numpy.ndarray(dtype=numpy.int): The x and y coordinate values stored within.""" xy = property(_get_xy, _set_xy) """numpy.ndarray: Physical coordinate values of the object in the form of [bottom_left, top_right]. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]], name="test", params={'maxI': 0.005}) >>> obj.xy array([[ 0, 0], [200, 200]]) .. image:: ../assets/img/object_physical_PhysicalObject_xy.png :height: 250 """ master = None """numpy.ndarray: Master ojbect for current object (for arrays and pins). Example ------- >>> import laygo2 >>> obj1 = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]], name="test1", params=None) >>> obj2 = laygo2.object.physical.Pin(xy = [[0, 0], [100, 100]], layer = ["M1", "drawing"], master=obj1) >>> obj2.master <laygo2.object.physical.PhysicalObject object at 0x00000204AAF3C7C0> """ params = None """dict: Dictionary storing the parameters associated with the object Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]], name="test", params={'maxI': 0.005}) >>> obj.params {‘maxI’: 0.005 } """ pointers = None """dict: The dictionary containing the key-value pairs of the major physical coordinates of the object, such as 'left', 'right', 'top', 'bottom', 'bottom_left', 'center', etc. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.pointers {'left': array([0, 100]), 'right': array([200, 100]), 'bottom': array([100, 0]), 'top': array([100, 200]), 'bottom_left': array([0, 0]), 'bottom_right': array([200, 0]), 'top_left': array([0, 200]), 'top_right': array([200, 200]), ‘center’: array( [100, 100] ) } .. image:: ../assets/img/object_physical_PhysicalObject_pointers.png :height: 250 """ # Frequently used pointers left = None """numpy.ndarray: The left-center coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.left array([ 0, 100]) .. image:: ../assets/img/object_physical_PhysicalObject_left.png :height: 250 """ right = None """numpy.ndarray: The right-center coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.right array([200, 100]) .. image:: ../assets/img/object_physical_PhysicalObject_right.png :height: 250 """ top = None """numpy.ndarray: The top-center coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.top array([100, 200]) .. image:: ../assets/img/object_physical_PhysicalObject_top.png :height: 250 """ bottom = None """numpy.ndarray: The bottom-center coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.top array([100, 0]) .. image:: ../assets/img/object_physical_PhysicalObject_bottom.png :height: 250 """ center = None """numpy.ndarray: The center-center coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.center array([100, 100]) .. image:: ../assets/img/object_physical_PhysicalObject_center.png :height: 250 """ bottom_left = None """numpy.ndarray: The bottom-left coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.bottom_left array([ 0, 0]) .. image:: ../assets/img/object_physical_PhysicalObject_bottom_left.png :height: 250 """ bottom_right = None """numpy.ndarray: The bottom-right coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.bottom_right array([200, 0]) .. image:: ../assets/img/object_physical_PhysicalObject_bottom_right.png :height: 250 """ top_left = None """numpy.ndarray: The top-left coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.top_left array([ 0, 200]) .. image:: ../assets/img/object_physical_PhysicalObject_top_left.png :height: 250 """ top_right = None """numpy.ndarray: The top-right coordinate of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.top_right array([200, 200]) .. image:: ../assets/img/object_physical_PhysicalObject_top_right.png :height: 250 """ @property def bbox(self): """numpy.ndarray: The physical bounding box of the object. Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]]) >>> obj.bbox array([[ 0, 0], [200, 200]]) .. image:: ../assets/img/object_physical_PhysicalObject_bbox.png :height: 250 """ # if self.xy.ndim == 2: if isinstance(self.xy[0], np.ndarray): return np.sort(np.array([self.xy[0, :], self.xy[1, :]]), axis=0) else: # single-point object. return np.array([self.xy, self.xy])
[docs] def __init__(self, xy, name=None, params=None): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. name : str Object name. params : dict Dictionary storing the parameters associated with the object. Returns ------- PhysicalObject Example ------- >>> import laygo2 >>> obj = laygo2.object.physical.PhysicalObject(xy = [[0, 0], [200, 200]], name="test", params={'maxI': 0.005}) >>> print(obj) <laygo2.object.physical.PhysicalObject object at 0x000001ECF0022948> name: test, class: PhysicalObject, xy: [[0, 0], [200, 200]], params: {'maxI': 0.005}, """ self.name = name # Initialize pointers. self.pointers = dict() self.pointers["left"] = np.array([0, 0], dtype=int) self.pointers["right"] = np.array([0, 0], dtype=int) self.pointers["bottom"] = np.array([0, 0], dtype=int) self.pointers["top"] = np.array([0, 0], dtype=int) self.pointers["bottom_left"] = np.array([0, 0], dtype=int) self.pointers["bottom_right"] = np.array([0, 0], dtype=int) self.pointers["top_left"] = np.array([0, 0], dtype=int) self.pointers["top_right"] = np.array([0, 0], dtype=int) self.left = self.pointers["left"] self.right = self.pointers["right"] self.bottom = self.pointers["bottom"] self.top = self.pointers["top"] self.bottom_left = self.pointers["bottom_left"] self.bottom_right = self.pointers["bottom_right"] self.top_left = self.pointers["top_left"] self.top_right = self.pointers["top_right"] self.params = params # deepcopy(params) # deepcopy increases the memory usage. self.xy = xy
def __str__(self): """Return the summary of the object information.""" return self.summarize()
[docs] def summarize(self): """Return the summary of the object information.""" name = "None" if self.name is None else self.name return ( self.__repr__() + " \n" " name: " + name + ", \n" + " class: " + self.__class__.__name__ + ", \n" + " xy: " + str(self.xy.tolist()) + ", \n" + " params: " + str(self.params) + ", \n" )
[docs] def _update_pointers(self): """The internal function that updates the object's pointers after a change in its physical coordinates.""" xy_left = np.diag(np.dot(np.array([[1, 0], [0.5, 0.5]]), self.bbox)) xy_right = np.diag(np.dot(np.array([[0, 1], [0.5, 0.5]]), self.bbox)) xy_bottom = np.diag(np.dot(np.array([[0.5, 0.5], [1, 0]]), self.bbox)) xy_top = np.diag(np.dot(np.array([[0.5, 0.5], [0, 1]]), self.bbox)) xy_bottom_left = np.diag(np.dot(np.array([[1, 0], [1, 0]]), self.bbox)) xy_bottom_right = np.diag(np.dot(np.array([[0, 1], [1, 0]]), self.bbox)) xy_top_left = np.diag(np.dot(np.array([[1, 0], [0, 1]]), self.bbox)) xy_top_right = np.diag(np.dot(np.array([[0, 1], [0, 1]]), self.bbox)) xy_center = np.diag(np.dot(np.array([[0.5, 0.5], [0.5, 0.5]]), self.bbox)) self.pointers["left"] = xy_left.astype(int) self.pointers["right"] = xy_right.astype(int) self.pointers["bottom"] = xy_bottom.astype(int) self.pointers["top"] = xy_top.astype(int) self.pointers["bottom_left"] = xy_bottom_left.astype(int) self.pointers["bottom_right"] = xy_bottom_right.astype(int) self.pointers["top_left"] = xy_top_left.astype(int) self.pointers["top_right"] = xy_top_right.astype(int) self.pointers["center"] = xy_center.astype(int) self.left = self.pointers["left"] self.right = self.pointers["right"] self.bottom = self.pointers["bottom"] self.top = self.pointers["top"] self.bottom_left = self.pointers["bottom_left"] self.bottom_right = self.pointers["bottom_right"] self.top_left = self.pointers["top_left"] self.top_right = self.pointers["top_right"] self.center = self.pointers["center"]
[docs] class IterablePhysicalObject(PhysicalObject): """ The base class of entities capable of iterable operations among elements. """ elements = None """ dict: Numpy array containing its element objects. Example ------- >>> import laygo2 >>> phy0 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [100, 100]]) >>> phy1 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [200, 200]]) >>> phy2 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [300, 300]]) >>> element = [phy0, phy1, phy2] >>> iphy0 = laygo2.object.physical.IterablePhysicalObject( xy=[[0, 0], [300, 300]], elements = elements) >>> iphy0.elements array([<laygo2.object.physical.PhysicalObject object at 0x000002049A77FDF0>, <laygo2.object.physical.PhysicalObject object at 0x000002049A77F3D0>, <laygo2.object.physical.PhysicalObject object at 0x000002049A77FF40>], dtype=object) """
[docs] def _get_xy(self): """numpy.ndarray(dtype=numpy.int): Retrive x,y coordinate values of the object.""" return self._xy
[docs] def _set_xy(self, value): """numpy.ndarray(dtype=numpy.int): Update x,y coordinates of the object.""" # Update the coordinate value of its elements. self._update_elements(xy_ofst=value - self.xy) # Update the coordinate value of the object itself. PhysicalObject._set_xy(self, value=value)
xy = property(_get_xy, _set_xy) @property def shape(self): """Array size of the object. Example ------- >>> import laygo2 >>> phy0 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [100, 100]]) >>> phy1 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [200, 200]]) >>> phy2 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [300, 300]]) >>> element = [phy0, phy1, phy2] >>> iphy0 = laygo2.object.physical.IterablePhysicalObject( xy=[[0, 0], [300, 300]], elements = elements) >>> iphy0.shape array([3]) """ if self.elements is None: return None else: return np.array(self.elements.shape, dtype=int)
[docs] def __init__(self, xy, name=None, params=None, elements=None): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. name : str Object name. params : dict Dictionary storing the parameters associated with the object. elements : list List containing its element objects. Returns ------- IterablePhysicalObject Example ------- >>> import laygo2 >>> phy0 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [100, 100]]) >>> phy1 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [200, 200]]) >>> phy2 = laygo2.object.physical.PhysicalObject(xy=[[0, 0], [300, 300]]) >>> element = [phy0, phy1, phy2] >>> iphy0 = laygo2.object.physical.IterablePhysicalObject( xy=[[0, 0], [300, 300]], elements = elements) >>> print(iphy0) <laygo2.object.physical.IterablePhysicalObject object at 0x000002049A77E380> name: None, class: IterablePhysicalObject, xy: [[0, 0], [300, 300]], params: None, """ PhysicalObject.__init__(self, xy=xy, name=name, params=params) if elements is None: self.elements = None else: self.elements = np.asarray(elements)
def __getitem__(self, pos): """Return the sub-elements of the object, based on the pos parameter.""" return self.elements[pos] def __setitem__(self, pos, item): """Update the sub-elements of the object, based on the pos and item parameters.""" self.elements[pos] = item def __iter__(self): """Iterator that maps directly to the elements attribute of this object.""" return self.elements.__iter__() def __next__(self): """Iterator that maps directly to the iterator function of the elements attribute of this object.""" return self.elements.__next__()
[docs] def ndenumerate(self): """Enumerate over the element array. Calls np.ndenumerate() of the elements of this object.""" return np.ndenumerate(self.elements)
[docs] def _update_elements(self, xy_ofst): """Update the xy coordinates of the elements of this object, used internally by the _set_xy() function.""" if np.all(self.elements is not None): # Update the x and y coordinate values of elements. for n, e in self.ndenumerate(): if e is not None: e.xy = e.xy + xy_ofst
[docs] class PhysicalObjectGroup(IterablePhysicalObject): """ A class for grouped physical objects, designed to be generated as a group in Cadence Virtuoso (currently not implemented). """ # TODO: implement this.
[docs] def summarize(self): """Get object information summary.""" return IterablePhysicalObject.summarize(self) + " elements: " + str(self.elements) + ", \n"
[docs] def __init__(self, xy, name=None, params=None, elements=None): """ The constructor function. Parameters ---------- xy : numpy.ndarray(dtype=int) The coordinate of this object represented as a Numpy array [x, y]. name : str, optional The name of the object. params : dict or None The dictionary that contains the parameters of this object, with parameter names as keys. elements : numpy.ndarray(dtype=LayoutObject) or None The iterable elements of the object. """ IterablePhysicalObject.__init__(self, xy=xy, name=name, params=params, elements=elements)
''' # Deprecated as PhysicalObjectGroup can be used instead in most cases. # But the code is preserved for reference. class PhysicalObjectArray(np.ndarray): """LayoutObject array class for containing multiple layout objects. Subclassing ndarray to utilize advance slicing functions.""" name = None """str: the name of the object.""" params = None """dict or None: parameters of the object. """ _xy = None # np.zeros(2, dtype=int) """numpy.ndarray(dtype=numpy.int): the internal variable of xy.""" def get_xy(self): """numpy.ndarray(dtype=numpy.int): gets the x and y coordinate values of this object.""" return self._xy def set_xy(self, value): """numpy.ndarray(dtype=numpy.int): sets the x and y coordinate values of this object.""" if value is None: self._xy = value else: self._xy = np.asarray(value, dtype=int) xy = property(get_xy, set_xy) """numpy.ndarray(detype=numpy.int): the x and y coordinate values of the object.""" def moveby(self, delta): """move the array and child objects by delta.""" self.xy = self.xy + delta for i in self: i.xy = i.xy + delta def __new__(cls, input_array, name=None, xy=None, params=None): """ Constructor for ndarray subclasses - check the NumPy manual for details. Parameters ---------- input_array : np.ndarray An array of LayoutObject objects. name : str The name of the array. xy : numpy.ndarray(dtype=int) The xy-coordinate of the object. The format is [x0, y0]. params : dict Additional parameters of the array. """ # Input array is an already formed ndarray instance # We first cast to be our class type obj = np.asarray(input_array).view(cls) # add the new attribute to the created instance obj.name = name obj.xy = None if xy is None else np.asarray(xy, dtype=int) obj.params = params # Finally, we must return the newly created object: return obj def __array_finalize__(self, obj): """ Array finalizing function for subclassing ndarray - check the NumPy manual for details """ if obj is None: return # Transfer parameters self.name = getattr(obj, 'name', None) self.xy = getattr(obj, 'xy', None) self.params = getattr(obj, 'params', None) def __str__(self): """Return the summary of the object information.""" return self.summarize() def summarize(self): """Return the summary of the object information.""" return " " + \ "name:" + self.name + ", " + \ "class:" + self.__class__.__name__ + ", " + \ "shape:" + str(self.shape) + ", " + \ "xy:" + str(self.xy) + ", " + \ "params:" + str(self.params) + "\n" + \ " elements:" + str(np.ndarray.__str__(self)) + "\n" '''
[docs] class Rect(PhysicalObject): """ Rectangle object class. Example ------- >>> from laygo2.object.physical import Rect >>> rect0 = Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing']) >>> print(rect0) <laygo2.object.physical.Rect object at 0x000002049A77F3A0> name: None, class: Rect, xy: [[0, 0], [100, 100]], params: None, , layer: ['M1', 'drawing'] """ layer = None """ numpy.ndarray: The physical layer information of the object, represented as a list with two elements: [name, purpose]. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', hextension=20, vextension=20) >>> rect0.layer ['M1', 'drawing'] """ netname = None """ str: The net name associated with the object. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', hextension=20, vextension=20) >>> rect0.netname “net0” """ hextension = 0 """ int: The horizontal extension of the rectangle object above its bounding box. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', hextension=20, vextension=20) >>> rect0.hextension 20 .. image:: ../assets/img/object_physical_rect_hextension.png :height: 250 """ vextension = 0 """ int: The vertical extension of the rectangle object above its bounding box. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', hextension=20, vextension=20) >>> rect0.vextension 20 .. image:: ../assets/img/object_physical_rect_vextension.png :height: 250 """ color = None """ int or None or "not_MPT": The color (multi-patterning identifier) parameter of the object. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', color=1) >>> rect0.color 1 """ @property def height(self): """ int: The height of the object. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing']) >>> rect0.height 100 .. image:: ../assets/img/object_physical_rect_height.png :height: 250 """ return abs(self.xy[0, 1] - self.xy[1, 1]) @property def width(self): """ int: The width of the object. Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing']) >>> rect0.width 100 .. image:: ../assets/img/object_physical_rect_width.png :height: 250 """ return abs(self.xy[0, 0] - self.xy[1, 0]) @property def height_vec(self): """numpy.ndarray(dtype=int): The height direction vector [0, self.height] of the object.""" return np.array([0, self.height]) @property def width_vec(self): """numpy.ndarray(dtype=int): The width direction vector [self.width, 0] of the object.""" return np.array([self.width, 0]) @property def size(self): """ numpy.ndarray: The size of the object ([self.width, self.height]). Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing']) >>> rect0.size array([100, 100]) .. image:: ../assets/img/object_physical_rect_size.png :height: 250 """ return np.array([self.width, self.height])
[docs] def __init__( self, xy, layer, color=None, hextension=0, vextension=0, name=None, netname=None, params=None, ): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. layer : list The physical layer information of the object, represented as a list with two elements: [name, purpose]. hextension : int The horizontal extension value of the object. vextension : int The vertical extension value of the object. name : str Object name. netname : str The net name associated with the object. params : dict Dictionary storing the parameters associated with the object. color : str, optional. The color (multi-patterning identifier) parameter of the object. Returns ------- Rect See Also -------- PhysicalObject Example ------- >>> import laygo2 >>> rect0 = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', color=1) >>> print(rect0) <laygo2.object.physical.Rect object at 0x000002049A77F3A0> name: None, class: Rect, xy: [[0, 0], [100, 100]], params: None, , layer: ['M1', 'drawing'], netname: net0 .. image:: ../assets/img/object_physical_rect_init.png :height: 250 """ self.layer = layer if netname is None: self.netname = name else: self.netname = netname self.hextension = hextension self.vextension = vextension self.color = color PhysicalObject.__init__(self, xy=xy, name=name, params=params)
[docs] def align(self, rect2): """ Match the length of the self and rect2 objects, if either object has a width or height of 0. Parameters ---------- rect2 : Rect The rect object to be aligned with `self`. """ index = 0 r0 = self r1 = rect2 if r0.xy[0][0] == r0.xy[1][0]: # width is zero index = 1 pnt = np.zeros([2, 2], dtype=int) pnt[0][1] = r0.bbox[1][index] # tr pnt[1][1] = r1.bbox[1][index] # tr pnt[0][0] = r0.bbox[0][index] # bl pnt[1][0] = r1.bbox[0][index] # bl if pnt[1][1] > pnt[0][1]: # p1-top is upper then p0-top _xy = r0.bbox # r0 correction _xy[1][index] = pnt[1][1] r0.xy = _xy elif pnt[1][1] < pnt[0][1]: # p1-top is lower then p0-top _xy = r1.bbox # r1 correction _xy[1][index] = pnt[0][1] r1.xy = _xy if pnt[1][0] < pnt[0][0]: # p1-bottom is lower then p0-bottom _xy = r0.bbox # r0 correction _xy[0][index] = pnt[1][0] r0.xy = _xy elif pnt[1][0] > pnt[0][0]: _xy = r1.bbox # r1 correction _xy[0][index] = pnt[0][0] r1.xy = _xy
[docs] def summarize(self): """Get object information summary.""" return ( PhysicalObject.summarize(self) + " layer: " + str(self.layer) + ", \n" + " netname: " + str(self.netname) + ", \n" )
[docs] class Path(PhysicalObject): """ Path object class. Example ------- >>> from laygo2.object.physical.Path import Path >>> path0 = Path(xy=[[0, 0], [0, 100]], width=10, extension=5, layer=['M1', 'drawing']) >>> print(path0) <laygo2.object.physical.Path object at 0x00000280D1F3CE88> name: None, class: Path, xy: [[0, 0], [0, 100]], params: None, width: 10, extension: 5, layer: ['M1', 'drawing'], """ # TODO: implement pointers. layer = None """numpy.ndarray: The physical layer information of the object, represented as a list with two elements: [name, purpose]. """ netname = None """ str: The net name associated with the object. Example ------- >>> import laygo2 >>> path0 = laygo2.object.physical.Path(xy=[[0, 0], [0, 100]], width=10, extension=5, layer=['M1', 'drawing'], netname='net0’) >>> path0.netname “net0” .. image:: ../assets/img/object_physical_path_netname.png :height: 250 """ width = None """ int: the width of the object. Example ------- >>> import laygo2 >>> path0 = laygo2.object.physical.Path(xy=[[0, 0], [0, 100]], width=10, extension=5, layer=['M1', 'drawing'], netname='net0’) >>> path0.width 10 .. image:: ../assets/img/object_physical_path_width.png :height: 250 """ extension = 0 """ int: The path extension from its endpoints. Example ------- >>> import laygo2 >>> path0 = laygo2.object.physical.Path(xy=[[0, 0], [0, 100]], width=10, extension=5, layer=['M1', 'drawing'], netname='net0’) >>> path0.extension 5 .. image:: ../assets/img/object_physical_path_extension.png :height: 250 """ @property def bbox(self): """The physical bounding box of the object.""" return np.sort(np.array([self.xy[0], self.xy[-1]]), axis=0)
[docs] def _update_pointers(self): """The internal function that updates the object's pointers after a change in its physical coordinates.""" pass
[docs] def __init__(self, xy, layer, width, extension=0, name=None, netname=None, params=None): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. layer : list The physical layer information of the object, represented as a list with two elements: [name, purpose]. width : int The width of the object. extension : int The path extension from its endpoints. name : str Object name. netname : str The net name associated with the object. params : dict Dictionary storing the parameters associated with the object. Returns ------- Path Example ------- >>> import laygo2 >>> path0 = laygo2.object.physical.Path(xy=[[0, 0], [0, 100]], width=10, extension=5, layer=['M1', 'drawing'], netname='net0’) >>> print(path0) <laygo2.object.physical.Path object at 0x00000280D1F3CE88> name: None, class: Path, xy: [[0, 0], [0, 100]], params: None, width: 10, extension: 5, layer: ['M1', 'drawing'], netname: net0 .. image:: ../assets/img/object_physical_path_init.png :height: 250 """ self.layer = layer self.width = width self.extension = extension self.netname = netname PhysicalObject.__init__(self, xy=xy, name=name, params=params) self.pointers = dict() # Pointers are invalid for Path objects.
[docs] def summarize(self): """Get object information summary.""" return ( PhysicalObject.summarize(self) + " width: " + str(self.width) + ", \n" + " extension: " + str(self.extension) + ", \n" + " layer: " + str(self.layer) + ", \n" + " netname: " + str(self.netname) + ", \n" )
[docs] class Pin(IterablePhysicalObject): """ Pin object class. Example ------- >>> from laygo2.object.physical import Pin >>> pin0 = Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> print(pin0) <laygo2.object.physical.Pin object at 0x000002049A77FF70> name: None, class: Pin, xy: [[0, 0], [100, 100]], params: {'direction': 'input'}, , layer: ['M1' 'drawing'], netname: net0, shape: None, master: None """ layer = None """ numpy.ndarray: The physical layer information of the object, represented as a list with two elements: [name, purpose]. Example ------- >>> import laygo2 >>> pin0 = laygo2.object.physical.Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> pin0.layer ['M1', 'drawing'] numpy.ndarray: 객체의 layer 정보 [name, purpose]. """ netname = None """ str: The net name associated with the object. Example ------- >>> import laygo2 >>> pin0 = laygo2.object.physical.Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> pin0.netname “net0” str: 객체의 노드 명. """ master = None """ Instance: The instance that the pin belongs to. Used for pins of instances only. """ @property def height(self): """ int: The height of the object. Example ------- >>> import laygo2 >>> pin0 = laygo2.object.physical.Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> pin0.height 100 """ return abs(self.xy[0, 1] - self.xy[1, 1]) @property def width(self): """ int: The width of the object. Example ------- >>> import laygo2 >>> pin0 = laygo2.object.physical.Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> pin0.width 100 """ return abs(self.xy[0, 0] - self.xy[1, 0]) @property def size(self): """ numpy.ndarray: The size of the object. Example ------- >>> import laygo2 >>> pin0 = laygo2.object.physical.Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> pin0.size [100, 100] """ return np.array([self.width, self.height]) @property def height_vec(self): """numpy.ndarray(dtype=int): The height direction vector [0, self.height] of the object.""" return np.array([0, self.height]) @property def width_vec(self): """numpy.ndarray(dtype=int): The width direction vector [self.width, 0] of the object.""" return np.array([self.width, 0])
[docs] def __init__( self, xy, layer, name=None, netname=None, params=None, master=None, elements=None, ): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. layer : list The physical layer information of the object, represented as a list with two elements: [name, purpose]. name : str Object name. netname : str The net name associated with the object. params : dict Dictionary storing the parameters associated with the object. Returns ------- Pin Example ------- >>> import laygo2 >>> pin0 = laygo2.object.physical.Pin(xy=[[0, 0], [100, 100]], layer=['M1', 'drawing'], netname='net0', params={'direction': 'input'}) >>> print(pin0) <laygo2.object.physical.Pin object at 0x000002049A77FF70> name: None, class: Pin, xy: [[0, 0], [100, 100]], params: {'direction': 'input'}, , layer: ['M1' 'drawing'], netname: net0, shape: None, master: None """ self.layer = np.asarray(layer) if netname is None: netname = name self.netname = netname self.master = master IterablePhysicalObject.__init__(self, xy=xy, name=name, params=params, elements=elements)
[docs] def summarize(self): """Get object information summary.""" return ( IterablePhysicalObject.summarize(self) + " layer: " + str(self.layer) + ", \n" + " netname: " + str(self.netname) + ", \n" + " shape: " + str(self.shape) + ", \n" + " master: " + str(self.master) + ", \n" )
[docs] def export_to_dict(self): db = dict() db["xy"] = self.xy.tolist() db["layer"] = self.layer.tolist() db["name"] = self.name db["netname"] = self.netname return db
[docs] class Text(PhysicalObject): """ Text object class. Example ------- >>> import laygo2 >>> text0 = laygo2.object.physical.Text(xy=[0, 0], layer=['text', 'drawing'], text='test', params=None) >>> print(text0) <laygo2.object.physical.Text object at 0x000002049A77FD90> name: None, class: Text, xy: [0, 0], params: None, layer: ['text', 'drawing'], text: test """ layer = None """ list: The physical layer information of the object, represented as a list with two elements: [name, purpose]. Example ------- >>> import laygo2 >>> text0 = laygo2.object.physical.Text(xy=[0, 0], layer=['text', 'drawing'], text='test', params=None) >>> text0.layer ['text', 'drawing'] """ text = None """ str: Text content of the object. Example ------- >>> import laygo2 >>> text0 = laygo2.object.physical.Text(xy=[0, 0], layer=['text', 'drawing'], text='test', params=None) >>> text0.text 'test' """
[docs] def __init__(self, xy, layer, text, name=None, params=None): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. layer : list The physical layer information of the object, represented as a list with two elements: [name, purpose]. text : str The text content. name : str Object name. params : dict Dictionary storing the parameters associated with the object. Returns ------- Text See Also -------- PhysicalObject : base class. Example ------- >>> import laygo2 >>> text0 = laygo2.object.physical.Text(xy=[0, 0], layer=['text', 'drawing'], text='test', params=None) >>> print(text0) <laygo2.object.physical.Text object at 0x000002049A77FD90> name: None, class: Text, xy: [0, 0], params: None, layer: ['text', 'drawing'], text: test """ self.layer = layer self.text = text PhysicalObject.__init__(self, xy=xy, name=name, params=params)
[docs] def summarize(self): """Get object information summary.""" return ( PhysicalObject.summarize(self) + " layer: " + str(self.layer) + ", \n" + " text: " + str(self.text) + ", \n" )
[docs] class Instance(IterablePhysicalObject): """ Instance object class. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> print(inst0) <laygo2.object.physical.Instance object at 0x000001AF458AF8E0> name: I0, class: Instance, xy: [100, 100], params: None, size: [500, 300], shape: [3, 2], pitch: [200, 200], transform: R0, pins: {'in': <laygo2.object.physical.Pin object at 0x000001AF560D6170>, 'out': <laygo2.object.physical.Pin object at 0x000001AF560D5F30>}, >>> print( inst0[1,0].xy0 ) array([300, 100]) """ # TODO: update (maybe) xy and sub-elements after transform property is updated. libname = None """ str: The library name of the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.libname 'mylib' .. image:: ../assets/img/object_physical_instance_libname.png :height: 250 """ cellname = None """ str: The cellname of the instance object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.cellname 'mycell' .. image:: ../assets/img/object_physical_instance_cellname.png :height: 250 """ viewname = None """str: The view name of the instance.""" shape = None """np.array([int, int]) or None: The mosaic shape of the instance. None if the instance is non-mosaic.""" _pitch = None """np.array([int, int]) or None: The internal variable for self.pitch.""" unit_size = None """ numpy.ndarray: The array unit size for the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.unit_size array([100, 100]) .. image:: ../assets/img/object_physical_instance_unit_size.png :height: 250 """ transform = "R0" """ str: The attribute of the object that defines its transformation (rotation and mirroring). Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.transform "R0" .. image:: ../assets/img/object_physical_instance_transform.png :height: 250 """ pins = None """ dict: Dictionary of pins belonging to the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.pins {'in': <laygo2.object.physical.Pin object at 0x000001CA76EE1348>, 'out': <laygo2.object.physical.Pin object at 0x000001CA7709BD48>} >>> inst0.pins["in"].shape array([3, 2]) >>> inst0.pins["out"].shape array([3, 2] ) >>> inst0.pins["in"][1, 1].xy array([[300, 300], [310, 310]]) .. image:: ../assets/img/object_physical_instance_pins.png :height: 250 """
[docs] def get_p(self): return self.pins
[docs] def set_p(self, val): self.pins = val
p = property(get_p, set_p) """str: Alias of pins."""
[docs] def _update_pins(self, xy_ofst): """ Internal function to update x,y coordinates of the object's pins. Used as part of _set_xy(). """ if self.pins is not None: for pn, p in self.pins.items(): if np.all(p is not None): # Update the x and y coordinate values of elements. for n, e in np.ndenumerate(p): if e is not None: e.xy = e.xy + xy_ofst
[docs] def _get_xy(self): """numpy.ndarray(dtype=numpy.int): Retrive x,y coordinate values of the object.""" return self._xy
[docs] def _set_xy(self, value): """numpy.ndarray(dtype=numpy.int): Update x,y coordinates of the object.""" # Update the coordinate value of its pins. self._update_pins(xy_ofst=value - self.xy) IterablePhysicalObject._set_xy(self, value=value)
xy = property(_get_xy, _set_xy) @property def xy0(self): """ numpy.ndarray: The x,y coordinates of the primary corner of the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.xy0 array([100, 100]) .. image:: ../assets/img/object_physical_instance_xy0.png :height: 250 """ return self.xy @property def xy1(self): """ numpy.ndarray: The x,y coordinates of the secondary corner of the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.xy1 array([600, 400]) .. image:: ../assets/img/object_physical_instance_xy1.png :height: 250 """ if self.size is None: return self.xy else: return self.xy + np.dot(self.size, tf.Mt(self.transform).T) @property def size(self): """ numpy.ndarray: The size of the object ([self.width, self.height]). Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.size array([500, 300]) .. image:: ../assets/img/object_physical_instance_size.png :height: 250 """ if self.shape is None: return self.unit_size else: return (self.shape - np.array([1, 1])) * self.pitch + self.unit_size
[docs] def get_pitch(self): """numpy.ndarray(dtype=int): Retrive the pitch of the instance.""" if self._pitch is None: return self.unit_size else: return self._pitch
[docs] def set_pitch(self, value): """numpy.ndarray(dtype=int): Update the pitch of the instance.""" self._pitch = value
pitch = property(get_pitch, set_pitch) """ numpy.ndarray: The pitch between unit objects in the array. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.pitch array([200, 200]) .. image:: ../assets/img/object_physical_instance_pitch.png :height: 250 See Also -------- Instance.spacing """
[docs] def get_spacing(self): return self.pitch
[docs] def set_spacing(self, value): self.pitch = value
spacing = property(get_spacing, set_spacing) """ numpy.ndarray: Spacing between unit object of the object in array. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.spacing array([200, 200]) .. image:: ../assets/img/object_physical_instance_spacing.png :height: 250 See Also -------- Instance.pitch """ @property def bbox(self): """The physical bounding box of the object.""" bbox = np.array([self.xy0, self.xy1]) # return bbox # return self.xy + np.dot(self.size, tf.Mt(self.transform).T) return np.sort(bbox, axis=0) @property def height(self): """ int: The height of the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.height 300 .. image:: ../assets/img/object_physical_instance_height.png :height: 250 """ return abs(self.bbox[1][1] - self.bbox[0][1]) @property def width(self): """ int: The width of the object. Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> inst0.width 500 .. image:: ../assets/img/object_physical_instance_width.png :height: 250 """ return abs(self.bbox[1][0] - self.bbox[0][0]) @property def height_vec(self): """numpy.ndarray(dtype=int): The height direction vector [0, self.height] of the object.""" return np.array([0, self.height]) @property def width_vec(self): """numpy.ndarray(dtype=int): The width direction vector [self.width, 0] of the object.""" return np.array([self.width, 0])
[docs] def __init__( self, xy, libname, cellname, viewname="layout", shape=None, pitch=None, transform="R0", unit_size=np.array([0, 0]), pins=None, name=None, params=None, ): """ The constructor function. Parameters ---------- xy : numpy.ndarray The primary coordinate ([x0, y0]) of the object. libname : str The library name of the object. cellname : str The cell name of the object. shape : numpy.ndarray The shape [col, row] of the elements. pitch : numpy.ndarray The pitch between unit objects in the array. transform : str The attribute of the object that defines its transformation (rotation and mirroring). unit_size : list The array unit size for the object. pins : dict Dictionary of pins belonging to the object. name : str Object name. params : dict (optional) Dictionary storing the parameters associated with the object. Returns ------- Instance : The constructed Instance object. See Also -------- IterablePhysicalObject Example ------- >>> import laygo2 >>> inst0_pins = dict() >>> inst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10,10]], layer = ['M1', 'drawing'], netname = 'in') >>> inst0_pins['out']= laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> inst0 = laygo2.object.physical.Instance(name="I0", xy=[100,100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[200,200], unit_size=[100, 100], pins=inst0_pins, transform='R0') >>> print(inst0) <laygo2.object.physical.Instance object at 0x000001AF458AF8E0> name: I0, class: Instance, xy: [100, 100], params: None, size: [500, 300], shape: [3, 2], pitch: [200, 200], transform: R0, pins: {'in': <laygo2.object.physical.Pin object at 0x000001AF560D6170>, 'out': <laygo2.object.physical.Pin object at 0x000001AF560D5F30>}, >>> print( inst0[1,0].xy0 ) array([300, 100]) .. image:: ../assets/img/object_physical_instance_init.png :height: 250 """ # Assign parameters. xy = np.asarray(xy) self.libname = libname self.cellname = cellname self.viewname = viewname if shape is not None: _shape = np.asarray(shape) if _shape.shape != (2,): raise ValueError("Instance shape should be a (2, ) numpy array or None.") self.shape = _shape if pitch is not None: self.pitch = np.asarray(pitch) self.transform = transform self.unit_size = np.asarray(unit_size) # Construct an array for elements. if shape is None: # elements = self # self-referencing causes recursion errors. # (Deprecated). elements = None else: _shape = tuple(shape) elements = np.zeros(_shape, dtype=object) # elements = LayoutObjectArray(np.zeros(_shape, dtype=np.object)) _it = np.nditer(elements, flags=["multi_index", "refs_ok"]) while not _it.finished: _idx = _it.multi_index _xy = xy + np.dot(self.pitch * np.array(_idx), tf.Mt(self.transform).T) inst = Instance( xy=_xy, libname=libname, cellname=cellname, shape=None, pitch=pitch, transform=self.transform, unit_size=self.unit_size, pins=pins, name=name, params=params, ) elements[_idx] = inst _it.iternext() IterablePhysicalObject.__init__(self, xy=xy, name=name, params=params, elements=elements) # Create the pin dictionary. Can we do the same thing without generating # these many Pin objects? self.pins = dict() if pins is not None: if not isinstance(pins, dict): raise ValueError("The pins parameter for Instance objects should be a dictionary.") for pn, p in pins.items(): _xy0 = xy + np.dot(p.xy, tf.Mt(transform).T) if shape is not None: elements = [] for i in range(shape[0]): elements.append([]) for j in range(shape[1]): _xy = _xy0 + np.dot(self.pitch * np.array([i, j]), tf.Mt(transform).T) # If p has elements, they need to be copied and # transferred to the new pin. _pelem = None if p.elements is not None: _pelem = np.empty(p.elements.shape, dtype=object) for _idx, _pe in np.ndenumerate(p.elements): _pexy0 = ( xy + np.dot(_pe.xy, tf.Mt(transform).T) + np.dot( self.pitch * np.array([i, j]), tf.Mt(transform).T, ) ) _pelem[_idx] = Pin( xy=_pexy0, netname=_pe.netname, layer=_pe.layer, name=_pe.name, master=self, ) pin = Pin( xy=_xy, netname=p.netname, layer=p.layer, name=p.name, master=self, elements=_pelem, ) # master uses self instead of self.elements[i, j]. elements[i].append(pin) elements = np.array(elements) else: # If p has elements, they need to be copied and transferred # to the new pin. _pelem = None if p.elements is not None: _pelem = np.empty(p.elements.shape, dtype=object) for _idx, _pe in np.ndenumerate(p.elements): _pexy0 = xy + np.dot(_pe.xy, tf.Mt(transform).T) _pelem[_idx] = Pin( xy=_pexy0, netname=_pe.netname, layer=_pe.layer, name=_pe.name, master=self, ) elements = _pelem self.pins[pn] = Pin( xy=_xy0, netname=p.netname, layer=p.layer, name=p.name, master=self, elements=elements, )
[docs] def summarize(self): """Return the summary of the object information.""" _shape = str(None if self.shape is None else self.shape.tolist()) return ( PhysicalObject.summarize(self) + " size: " + str(self.size.tolist()) + ", \n" + " shape: " + _shape + ", \n" + " pitch: " + str(self.pitch.tolist()) + ", \n" + " transform: " + str(self.transform) + ", \n" + " pins: " + str(self.pins) + ", \n" )
[docs] def update_netname(self, netmap: dict): """ Update the netname information for all pins belonging to this object. """ for pn, p in self.pins.items(): if p.netname in netmap: p.netname = netmap[p.netname] # update netname information
[docs] class VirtualInstance(Instance): # IterablePhysicalObject): """ The VirtualInstance class implements functions for a group of objects to be treated as a single instance with dedicated dimensional, port, and related parameters. Example ------- >>> import laygo2 >>> vinst0_pins = dict() >>> # Pin information >>> vinst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10, 10]], layer=['M1', 'drawing'], netname='in') >>> vinst0_pins['out'] = laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> # Element information >>> native_elements = dict() >>> native_elements['R0'] = laygo2.object.physical.Rect(xy=[[0, 0], [10, 10]], layer=['M1', 'drawing']) >>> native_elements['R1'] = laygo2.object.physical.Rect(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing']) >>> native_elements['R2'] = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['prBoundary', 'drawing']) >>> vinst0 = laygo2.object.physical.VirtualInstance(name='I0', libname='mylib', cellname='myvcell', xy=[500, 500], native_elements=native_elements, shape=[3, 2], pitch=[100, 100], unit_size=[100, 100], pins=vinst0_pins, transform='R0') >>> vinst0.native_elements {'R0': <laygo2.object.physical.Rect object at 0x00000204AAFCE170>, 'R1': <laygo2.object.physical.Rect object at 0x00000204AAFCEA40>, 'R2': <laygo2.object.physical.Rect object at 0x00000204AAFCE0B0>} """ native_elements = None """ dict: Dictionary that holds the physical element entities of the VirtualInstance object. Example ------- >>> import laygo2 >>> vinst0_pins = dict() >>> # Pin information >>> vinst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10, 10]], layer=['M1', 'drawing'], netname='in') >>> vinst0_pins['out'] = laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> # Element information >>> native_elements = dict() >>> native_elements['R0'] = laygo2.object.physical.Rect(xy=[[0, 0], [10, 10]], layer=['M1', 'drawing']) >>> native_elements['R1'] = laygo2.object.physical.Rect(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing']) >>> native_elements['R2'] = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['prBoundary', 'drawing']) >>> vinst0 = laygo2.object.physical.VirtualInstance(name='I0', libname='mylib', cellname='myvcell', xy=[500, 500], native_elements=native_elements, shape=[3, 2], pitch=[100, 100], unit_size=[100, 100], pins=vinst0_pins, transform='R0') >>> vinst0.native_elements {'R0': <laygo2.object.physical.Rect object at 0x00000204AAFCE170>, 'R1': <laygo2.object.physical.Rect object at 0x00000204AAFCEA40>, 'R2': <laygo2.object.physical.Rect object at 0x00000204AAFCE0B0>} .. image:: ../assets/img/object_physical_VirtualInstance_native_elements.png :height: 250 """ # Dict[PhysicalObject] the elements that compose the virtual instance. Its keys represent the names of the elements.
[docs] def __init__( self, xy, libname, cellname, native_elements, viewname="layout", shape=None, pitch=None, transform="R0", unit_size=np.array([0, 0]), pins=None, name=None, params=None, ): """ The constructor function. Parameters ---------- xy : numpy.ndarray Physical coordinate values of the object in the form of [bottom_left, top_right]. libname : str The library name of the object. cellname : str The cell name of the object. native_elements : dict Dictionary that holds the physical element entities of the VirtualInstance object. shape : numpy.ndarray The shape [col, row] of the elements. pitch : numpy.ndarray The pitch between unit objects in the array. transform : str The attribute of the object that defines its transformation (rotation and mirroring). unit_size : list Unit size of object. pins : dict The array unit size for the object. name : str Object name. params : dict (optional) Dictionary storing the parameters associated with the object. Returns ------- laygo2.VirtualInstance : The constructed VirtualInstance object. See Also -------- Instance Example ------- >>> import laygo2 >>> vinst0_pins = dict() >>> # Pin information >>> vinst0_pins['in'] = laygo2.object.physical.Pin(xy=[[0, 0], [10, 10]], layer=['M1', 'drawing'], netname='in') >>> vinst0_pins['out'] = laygo2.object.physical.Pin(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing'], netname='out') >>> # Element information >>> native_elements = dict() >>> native_elements['R0'] = laygo2.object.physical.Rect(xy=[[0, 0], [10, 10]], layer=['M1', 'drawing']) >>> native_elements['R1'] = laygo2.object.physical.Rect(xy=[[90, 90], [100, 100]], layer=['M1', 'drawing']) >>> native_elements['R2'] = laygo2.object.physical.Rect(xy=[[0, 0], [100, 100]], layer=['prBoundary', 'drawing']) >>> vinst0 = laygo2.object.physical.VirtualInstance(name='I0', libname='mylib', cellname='myvcell', xy=[500, 500], native_elements=native_elements, shape=[3, 2], pitch=[100, 100], unit_size=[100, 100], pins=vinst0_pins, transform='R0') >>> vinst0.native_elements {'R0': <laygo2.object.physical.Rect object at 0x00000204AAFCE170>, 'R1': <laygo2.object.physical.Rect object at 0x00000204AAFCEA40>, 'R2': <laygo2.object.physical.Rect object at 0x00000204AAFCE0B0>} .. image:: ../assets/img/object_physical_VirtualInstance_init.png :height: 250 """ self.native_elements = native_elements Instance.__init__( self, xy=xy, libname=libname, cellname=cellname, viewname=viewname, shape=shape, pitch=pitch, transform=transform, unit_size=unit_size, pins=pins, name=name, params=params, )
[docs] def summarize(self): """Return the summary of the object information.""" return Instance.summarize(self) + " native elements: " + str(self.native_elements) + "\n"
[docs] def get_element_position(self, obj): """ Get x,y coordinates of the element obj (which belongs to the object) relative to the origin (0, 0). Parameters ---------- obj : element The element belongs to the object. """ vinst = self tr = vinst.transform coners = np.zeros((4, 2)) v_r = np.zeros(2) # for rotation bbox_raw = obj.bbox offset = vinst.xy if tr == "R0": v_r = v_r + (1, 1) coners[0] = offset + v_r * bbox_raw[0] coners[2] = offset + v_r * bbox_raw[1] elif tr == "MX": v_r = v_r + (1, -1) coners[1] = offset + v_r * bbox_raw[0] coners[3] = offset + v_r * bbox_raw[1] coners[0] = coners[0] + (coners[1][0], coners[3][1]) coners[2] = coners[2] + (coners[3][0], coners[1][1]) elif tr == "MY": v_r = v_r + (-1, 1) coners[3] = offset + v_r * bbox_raw[0] coners[1] = offset + v_r * bbox_raw[1] coners[0] = coners[0] + (coners[1][0], coners[3][1]) coners[2] = coners[2] + (coners[3][0], coners[1][1]) elif tr == "R90": v_r = v_r + (-1, -1) coners[2] = offset + v_r * bbox_raw[0] coners[0] = offset + v_r * bbox_raw[1] else: raise ValueError(" Others transfom not implemented") return coners[0], coners[2]
class Via(IterablePhysicalObject): """ Via object class. Attributes ---------- netname : str color : str size : np.array(dtype=np.int) Methods ------- """ netname = None size = np.array([0,0]) unit_size = None _pitch = None transform = 'R0' color = None def _get_xy(self): """numpy.ndarray(dtype=numpy.int): Get the x and y coordinate values of this object.""" return self._xy def _set_xy(self, value): """numpy.ndarray(dtype=numpy.int): Set the x and y coordinate values of this object.""" # Update the coordinate value of its pins. IterablePhysicalObject._set_xy(self, value=value) xy = property(_get_xy, _set_xy) @property def xy0(self): """attribute numpy.ndarray: Coordinates of major corner of object. Examples -------- """ return self.xy @property def xy1(self): """attribute numpy.ndarray: Coordinates of minor corner of object. Examples -------- """ if self.size is None: return self.xy else: return self.xy + np.dot(self.size, tf.Mt(self.transform).T) @property def bbox(self): bbox = np.array([self.xy0, self.xy1]) #return bbox #return self.xy + np.dot(self.size, tf.Mt(self.transform).T) return np.sort(bbox, axis=0) def __init__(self, xy, color=None, name=None, netname=None, params=None, transform= 'R0',elements=None): xy = np.asarray(xy) if netname is None: self.netname = name else: self.netname = netname self.color = color IterablePhysicalObject.__init__(self, xy=xy, name=name, params=params, elements=elements) def summarize(self): """Return the summary of the object information.""" return IterablePhysicalObject.summarize(self) + ", " + \ "netname: " + str(self.netname) # Test if __name__ == "__main__": test_rect = False test_path = False test_pin = False test_text = False test_pointer = False test_instance = True test_virtual_instance = False # You can create various objects by running part of the following commands. if test_rect: print("Rect test") rect0 = Rect( xy=[[0, 0], [100, 100]], layer=["M1", "drawing"], netname="net0", params={"maxI": 0.005}, ) print(rect0) if test_path: print("Path test") path0 = Path( xy=[[0, 0], [0, 100]], width=10, extension=5, layer=["M1", "drawing"], netname="net0", ) print(path0) if test_pin: print("Pin test") pin0 = Pin( xy=[[0, 0], [100, 100]], layer=["M1", "drawing"], netname="net0", master=rect0, params={"direction": "input"}, ) print(pin0) if test_text: print("Text test") text0 = Text(xy=[0, 0], layer=["text", "drawing"], text="test", params=None) print(text0) if test_instance: print("Instance test - creating a vanilla instance.") inst0_pins = dict() inst0_pins["in"] = Pin(xy=[[0, 0], [10, 10]], layer=["M1", "drawing"], netname="in") inst0_pins["out"] = Pin(xy=[[90, 90], [100, 100]], layer=["M1", "drawing"], netname="out") inst0 = Instance( name="I0", xy=[100, 100], libname="mylib", cellname="mycell", shape=[3, 2], pitch=[100, 100], unit_size=[100, 100], pins=inst0_pins, transform="R0", ) print(" ", inst0) print(" ", inst0.pointers) print(inst0.elements) for idx, it in inst0.ndenumerate(): print("what?") print(" ", idx, it) print(" ", idx, it.pins["in"]) print("Instance test - updating the instance's coordinate values.") inst0.xy = [200, 200] print(" ", inst0) print(" ", inst0.pointers) for idx, it in inst0.ndenumerate(): print(" ", idx, it) print(" ", idx, it.pins["in"]) if test_virtual_instance: print("VirtualInstance test - creating a vanilla virtual instance.") inst1_pins = dict() inst1_pins["in"] = Pin(xy=[[0, 0], [10, 10]], layer=["M1", "drawing"], netname="in") inst1_pins["out"] = Pin(xy=[[90, 90], [100, 100]], layer=["M1", "drawing"], netname="out") inst1_native_elements = dict() inst1_native_elements["R0"] = Rect(xy=[[0, 0], [10, 10]], layer=["M1", "drawing"]) inst1_native_elements["R1"] = Rect(xy=[[90, 90], [100, 100]], layer=["M1", "drawing"]) inst1_native_elements["R2"] = Rect(xy=[[0, 0], [100, 100]], layer=["prBoundary", "drawing"]) inst1 = VirtualInstance( name="I0", libname="mylib", cellname="myvcell", xy=[500, 500], native_elements=inst1_native_elements, shape=[3, 2], pitch=[100, 100], unit_size=[100, 100], pins=inst1_pins, transform="R0", ) print(" ", inst1) for idx, it in inst1.ndenumerate(): print(" ", idx, it.pins["in"]) for idx, it in inst1.pins["in"].ndenumerate(): print(" ", idx, it)