mirror of https://github.com/VLSIDA/OpenRAM.git
655 lines
20 KiB
Python
655 lines
20 KiB
Python
# See LICENSE for licensing information.
|
|
#
|
|
# Copyright (c) 2016-2023 Regents of the University of California and The Board
|
|
# of Regents for the Oklahoma Agricultural and Mechanical College
|
|
# (acting for and on behalf of Oklahoma State University)
|
|
# All rights reserved.
|
|
#
|
|
import math
|
|
from openram import debug
|
|
from openram.tech import GDS, drc
|
|
from openram.tech import layer, layer_indices
|
|
from .vector import vector
|
|
|
|
|
|
class pin_layout:
|
|
"""
|
|
A class to represent a rectangular design pin. It is limited to a
|
|
single shape.
|
|
"""
|
|
|
|
def __init__(self, name, rect, layer_name_pp):
|
|
self.name = name
|
|
# repack the rect as a vector, just in case
|
|
if type(rect[0]) == vector:
|
|
self._rect = rect
|
|
else:
|
|
self._rect = [vector(rect[0]), vector(rect[1])]
|
|
# snap the rect to the grid
|
|
self._rect = [x.snap_to_grid() for x in self.rect]
|
|
|
|
debug.check(self.width() > 0, "Zero width pin.")
|
|
debug.check(self.height() > 0, "Zero height pin.")
|
|
|
|
# These are the valid pin layers
|
|
valid_layers = {x: layer[x] for x in layer_indices.keys()}
|
|
|
|
# if it's a string, use the name
|
|
if type(layer_name_pp) == str:
|
|
self._layer = layer_name_pp
|
|
# else it is required to be a lpp
|
|
else:
|
|
for (layer_name, lpp) in valid_layers.items():
|
|
if not lpp:
|
|
continue
|
|
if self.same_lpp(layer_name_pp, lpp):
|
|
self._layer = layer_name
|
|
break
|
|
|
|
else:
|
|
try:
|
|
from openram.tech import layer_override
|
|
from openram.tech import layer_override_name
|
|
if layer_override[name]:
|
|
self.lpp = layer_override[name]
|
|
self.layer = "pwellp"
|
|
self._recompute_hash()
|
|
return
|
|
except:
|
|
debug.error("Layer {} is not a valid routing layer in the tech file.".format(layer_name_pp), -1)
|
|
|
|
self.lpp = layer[self.layer]
|
|
self._recompute_hash()
|
|
|
|
@property
|
|
def layer(self):
|
|
return self._layer
|
|
|
|
@layer.setter
|
|
def layer(self, l):
|
|
self._layer = l
|
|
self._recompute_hash()
|
|
|
|
@property
|
|
def rect(self):
|
|
return self._rect
|
|
|
|
@rect.setter
|
|
def rect(self, r):
|
|
self._rect = r
|
|
self._recompute_hash()
|
|
|
|
def _recompute_hash(self):
|
|
""" Recompute the hash for our hash cache """
|
|
self._hash = hash(repr(self))
|
|
|
|
def __str__(self):
|
|
""" override print function output """
|
|
return "({} layer={} ll={} ur={})".format(self.name,
|
|
self.layer,
|
|
self.rect[0],
|
|
self.rect[1])
|
|
|
|
def __repr__(self):
|
|
"""
|
|
override repr function output (don't include
|
|
name since pin shapes could have same shape but diff name e.g. blockage vs A)
|
|
"""
|
|
return "(layer={} ll={} ur={})".format(self.layer,
|
|
self.rect[0],
|
|
self.rect[1])
|
|
|
|
def __hash__(self):
|
|
"""
|
|
Implement the hash function for sets etc. We only return a cached
|
|
value, that is updated when either 'rect' or 'layer' are changed. This
|
|
is a major speedup, if pin_layout is used as a key for dicts.
|
|
"""
|
|
return self._hash
|
|
|
|
def __lt__(self, other):
|
|
""" Provide a function for ordering items by the ll point """
|
|
(ll, ur) = self.rect
|
|
(oll, our) = other.rect
|
|
|
|
if ll.x < oll.x and ll.y < oll.y:
|
|
return True
|
|
|
|
return False
|
|
|
|
def __eq__(self, other):
|
|
""" Check if these are the same pins for duplicate checks """
|
|
if isinstance(other, self.__class__):
|
|
return (self.lpp == other.lpp and self.rect == other.rect)
|
|
else:
|
|
return False
|
|
|
|
def bbox(self, pin_list):
|
|
"""
|
|
Given a list of layout pins, create a bounding box layout.
|
|
"""
|
|
(ll, ur) = self.rect
|
|
min_x = ll.x
|
|
max_x = ur.x
|
|
min_y = ll.y
|
|
max_y = ur.y
|
|
|
|
for pin in pin_list:
|
|
min_x = min(min_x, pin.ll().x)
|
|
max_x = max(max_x, pin.ur().x)
|
|
min_y = min(min_y, pin.ll().y)
|
|
max_y = max(max_y, pin.ur().y)
|
|
|
|
self.rect = [vector(min_x, min_y), vector(max_x, max_y)]
|
|
|
|
def fix_minarea(self):
|
|
"""
|
|
Try to fix minimum area rule.
|
|
"""
|
|
min_area = drc("{}_minarea".format(self.layer))
|
|
pass
|
|
|
|
def inflate(self, spacing=None, multiple=0.5):
|
|
"""
|
|
Inflate the rectangle by the spacing (or other rule)
|
|
and return the new rectangle.
|
|
"""
|
|
if not spacing:
|
|
spacing = multiple*drc("{0}_to_{0}".format(self.layer))
|
|
|
|
(ll, ur) = self.rect
|
|
spacing = vector(spacing, spacing)
|
|
newll = ll - spacing
|
|
newur = ur + spacing
|
|
|
|
return (newll, newur)
|
|
|
|
def inflated_pin(self, spacing=None, multiple=0.5):
|
|
"""
|
|
Inflate the rectangle by the spacing (or other rule)
|
|
and return the new rectangle.
|
|
"""
|
|
inflated_area = self.inflate(spacing, multiple)
|
|
return pin_layout(self.name, inflated_area, self.layer)
|
|
|
|
def intersection(self, other):
|
|
""" Check if a shape overlaps with a rectangle """
|
|
|
|
if not self.overlaps(other):
|
|
return None
|
|
|
|
(ll, ur) = self.rect
|
|
(oll, our) = other.rect
|
|
|
|
min_x = max(ll.x, oll.x)
|
|
max_x = min(ur.x, our.x)
|
|
min_y = max(ll.y, oll.y)
|
|
max_y = min(ur.y, our.y)
|
|
|
|
if max_x - min_x == 0 or max_y - min_y == 0:
|
|
return None
|
|
|
|
return pin_layout("", [vector(min_x, min_y), vector(max_x, max_y)], self.layer)
|
|
|
|
def xoverlaps(self, other):
|
|
""" Check if shape has x overlap """
|
|
(ll, ur) = self.rect
|
|
(oll, our) = other.rect
|
|
x_overlaps = False
|
|
# check if self is within other x range
|
|
if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x):
|
|
x_overlaps = True
|
|
# check if other is within self x range
|
|
if (oll.x >= ll.x and oll.x <= ur.x) or (our.x >= ll.x and our.x <= ur.x):
|
|
x_overlaps = True
|
|
|
|
return x_overlaps
|
|
|
|
def yoverlaps(self, other):
|
|
""" Check if shape has x overlap """
|
|
(ll, ur) = self.rect
|
|
(oll, our) = other.rect
|
|
y_overlaps = False
|
|
|
|
# check if self is within other y range
|
|
if (ll.y >= oll.y and ll.y <= our.y) or (ur.y >= oll.y and ur.y <= our.y):
|
|
y_overlaps = True
|
|
# check if other is within self y range
|
|
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
|
|
y_overlaps = True
|
|
|
|
return y_overlaps
|
|
|
|
def xcontains(self, other):
|
|
""" Check if shape contains the x overlap """
|
|
(ll, ur) = self.rect
|
|
(oll, our) = other.rect
|
|
|
|
return (oll.x >= ll.x and our.x <= ur.x)
|
|
|
|
def ycontains(self, other):
|
|
""" Check if shape contains the y overlap """
|
|
(ll, ur) = self.rect
|
|
(oll, our) = other.rect
|
|
|
|
return (oll.y >= ll.y and our.y <= ur.y)
|
|
|
|
def contains(self, other):
|
|
""" Check if a shape contains another rectangle """
|
|
# If it is the same shape entirely, it is contained!
|
|
if self == other:
|
|
return True
|
|
|
|
# Can only overlap on the same layer
|
|
if not self.same_lpp(self.lpp, other.lpp):
|
|
return False
|
|
|
|
if not self.xcontains(other):
|
|
return False
|
|
|
|
if not self.ycontains(other):
|
|
return False
|
|
|
|
return True
|
|
|
|
def contained_by_any(self, shape_list):
|
|
""" Checks if shape is contained by any in the list """
|
|
for shape in shape_list:
|
|
if shape.contains(self):
|
|
return True
|
|
return False
|
|
|
|
def overlaps(self, other):
|
|
""" Check if a shape overlaps with a rectangle """
|
|
# Can only overlap on the same layer
|
|
if not self.same_lpp(self.lpp, other.lpp):
|
|
return False
|
|
|
|
x_overlaps = self.xoverlaps(other)
|
|
y_overlaps = self.yoverlaps(other)
|
|
|
|
return x_overlaps and y_overlaps
|
|
|
|
def area(self):
|
|
""" Return the area. """
|
|
return self.height()*self.width()
|
|
|
|
def height(self):
|
|
""" Return height. Abs is for pre-normalized value."""
|
|
return abs(self.rect[1].y-self.rect[0].y)
|
|
|
|
def width(self):
|
|
""" Return width. Abs is for pre-normalized value."""
|
|
return abs(self.rect[1].x-self.rect[0].x)
|
|
|
|
def normalize(self):
|
|
""" Re-find the LL and UR points after a transform """
|
|
(first, second) = self.rect
|
|
ll = vector(min(first[0], second[0]), min(first[1], second[1]))
|
|
ur = vector(max(first[0], second[0]), max(first[1], second[1]))
|
|
self.rect=[ll, ur]
|
|
|
|
def transform(self, offset, mirror, rotate):
|
|
"""
|
|
Transform with offset, mirror and rotation
|
|
to get the absolute pin location.
|
|
We must then re-find the ll and ur.
|
|
The master is the cell instance.
|
|
"""
|
|
(ll, ur) = self.rect
|
|
if mirror == "MX":
|
|
ll = ll.scale(1, -1)
|
|
ur = ur.scale(1, -1)
|
|
elif mirror == "MY":
|
|
ll = ll.scale(-1, 1)
|
|
ur = ur.scale(-1, 1)
|
|
elif mirror == "XY":
|
|
ll = ll.scale(-1, -1)
|
|
ur = ur.scale(-1, -1)
|
|
|
|
if rotate == 90:
|
|
ll = ll.rotate_scale(-1, 1)
|
|
ur = ur.rotate_scale(-1, 1)
|
|
elif rotate == 180:
|
|
ll = ll.scale(-1, -1)
|
|
ur = ur.scale(-1, -1)
|
|
elif rotate == 270:
|
|
ll = ll.rotate_scale(1, -1)
|
|
ur = ur.rotate_scale(1, -1)
|
|
|
|
self.rect = [offset + ll, offset + ur]
|
|
self.normalize()
|
|
|
|
def center(self):
|
|
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
|
0.5*(self.rect[0].y+self.rect[1].y))
|
|
|
|
def cx(self):
|
|
""" Center x """
|
|
return 0.5*(self.rect[0].x+self.rect[1].x)
|
|
|
|
def cy(self):
|
|
""" Center y """
|
|
return 0.5*(self.rect[0].y+self.rect[1].y)
|
|
|
|
# The four possible corners
|
|
def ll(self):
|
|
""" Lower left point """
|
|
return self.rect[0]
|
|
|
|
def ul(self):
|
|
""" Upper left point """
|
|
return vector(self.rect[0].x, self.rect[1].y)
|
|
|
|
def lr(self):
|
|
""" Lower right point """
|
|
return vector(self.rect[1].x, self.rect[0].y)
|
|
|
|
def ur(self):
|
|
""" Upper right point """
|
|
return self.rect[1]
|
|
|
|
# The possible y edge values
|
|
def uy(self):
|
|
""" Upper y value """
|
|
return self.rect[1].y
|
|
|
|
def by(self):
|
|
""" Bottom y value """
|
|
return self.rect[0].y
|
|
|
|
# The possible x edge values
|
|
|
|
def lx(self):
|
|
""" Left x value """
|
|
return self.rect[0].x
|
|
|
|
def rx(self):
|
|
""" Right x value """
|
|
return self.rect[1].x
|
|
|
|
# The edge centers
|
|
def rc(self):
|
|
""" Right center point """
|
|
return vector(self.rect[1].x,
|
|
0.5*(self.rect[0].y+self.rect[1].y))
|
|
|
|
def lc(self):
|
|
""" Left center point """
|
|
return vector(self.rect[0].x,
|
|
0.5*(self.rect[0].y+self.rect[1].y))
|
|
|
|
def uc(self):
|
|
""" Upper center point """
|
|
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
|
self.rect[1].y)
|
|
|
|
def bc(self):
|
|
""" Bottom center point """
|
|
return vector(0.5*(self.rect[0].x+self.rect[1].x),
|
|
self.rect[0].y)
|
|
|
|
def gds_write_file(self, newLayout):
|
|
"""Writes the pin shape and label to GDS"""
|
|
debug.info(4, "writing pin (" + str(self.layer) + "):"
|
|
+ str(self.width()) + "x"
|
|
+ str(self.height()) + " @ " + str(self.ll()))
|
|
|
|
# Try to use the pin layer if it exists, otherwise
|
|
# use the regular layer
|
|
try:
|
|
(pin_layer_num, pin_purpose) = layer[self.layer + "p"]
|
|
except KeyError:
|
|
(pin_layer_num, pin_purpose) = layer[self.layer]
|
|
(layer_num, purpose) = layer[self.layer]
|
|
|
|
# Try to use a global pin purpose if it exists,
|
|
# otherwise, use the regular purpose
|
|
try:
|
|
from openram.tech import pin_purpose as global_pin_purpose
|
|
pin_purpose = global_pin_purpose
|
|
except ImportError:
|
|
pass
|
|
|
|
try:
|
|
from openram.tech import label_purpose
|
|
try:
|
|
from openram.tech import layer_override_purpose
|
|
if pin_layer_num in layer_override_purpose:
|
|
layer_num = layer_override_purpose[pin_layer_num][0]
|
|
label_purpose = layer_override_purpose[pin_layer_num][1]
|
|
except:
|
|
pass
|
|
except ImportError:
|
|
label_purpose = purpose
|
|
|
|
newLayout.addBox(layerNumber=layer_num,
|
|
purposeNumber=purpose,
|
|
offsetInMicrons=self.ll(),
|
|
width=self.width(),
|
|
height=self.height(),
|
|
center=False)
|
|
# Draw a second pin shape too if it is different
|
|
if not self.same_lpp((pin_layer_num, pin_purpose), (layer_num, purpose)):
|
|
newLayout.addBox(layerNumber=pin_layer_num,
|
|
purposeNumber=pin_purpose,
|
|
offsetInMicrons=self.ll(),
|
|
width=self.width(),
|
|
height=self.height(),
|
|
center=False)
|
|
# Add the text in the middle of the pin.
|
|
# This fixes some pin label offsetting when GDS gets
|
|
# imported into Magic.
|
|
try:
|
|
zoom = GDS["zoom"]
|
|
except KeyError:
|
|
zoom = None
|
|
newLayout.addText(text=self.name,
|
|
layerNumber=layer_num,
|
|
purposeNumber=label_purpose,
|
|
magnification=zoom,
|
|
offsetInMicrons=self.center())
|
|
|
|
def compute_overlap(self, other):
|
|
""" Calculate the rectangular overlap of two rectangles. """
|
|
(r1_ll, r1_ur) = self.rect
|
|
(r2_ll, r2_ur) = other.rect
|
|
|
|
# ov_ur = vector(min(r1_ur.x,r2_ur.x),min(r1_ur.y,r2_ur.y))
|
|
# ov_ll = vector(max(r1_ll.x,r2_ll.x),max(r1_ll.y,r2_ll.y))
|
|
|
|
dy = min(r1_ur.y, r2_ur.y) - max(r1_ll.y, r2_ll.y)
|
|
dx = min(r1_ur.x, r2_ur.x) - max(r1_ll.x, r2_ll.x)
|
|
|
|
if dx >= 0 and dy >= 0:
|
|
return [dx, dy]
|
|
else:
|
|
return [0, 0]
|
|
|
|
def distance(self, other):
|
|
"""
|
|
Calculate the distance to another pin layout.
|
|
"""
|
|
(r1_ll, r1_ur) = self.rect
|
|
(r2_ll, r2_ur) = other.rect
|
|
|
|
def dist(x1, y1, x2, y2):
|
|
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
|
|
|
left = r2_ur.x < r1_ll.x
|
|
right = r1_ur.x < r2_ll.x
|
|
bottom = r2_ur.y < r1_ll.y
|
|
top = r1_ur.y < r2_ll.y
|
|
|
|
if top and left:
|
|
return dist(r1_ll.x, r1_ur.y, r2_ur.x, r2_ll.y)
|
|
elif left and bottom:
|
|
return dist(r1_ll.x, r1_ll.y, r2_ur.x, r2_ur.y)
|
|
elif bottom and right:
|
|
return dist(r1_ur.x, r1_ll.y, r2_ll.x, r2_ur.y)
|
|
elif right and top:
|
|
return dist(r1_ur.x, r1_ur.y, r2_ll.x, r2_ll.y)
|
|
elif left:
|
|
return r1_ll.x - r2_ur.x
|
|
elif right:
|
|
return r2_ll.x - r1_ur.x
|
|
elif bottom:
|
|
return r1_ll.y - r2_ur.y
|
|
elif top:
|
|
return r2_ll.y - r1_ur.y
|
|
else:
|
|
# rectangles intersect
|
|
return 0
|
|
|
|
def overlap_length(self, other):
|
|
"""
|
|
Calculate the intersection segment and determine its length
|
|
"""
|
|
|
|
if self.contains(other):
|
|
return math.inf
|
|
elif other.contains(self):
|
|
return math.inf
|
|
else:
|
|
intersections = set(self.compute_overlap_segment(other))
|
|
# This is the common case where two pairs of edges overlap
|
|
# at two points, so just find the distance between those two points
|
|
if len(intersections) == 2:
|
|
(p1, p2) = intersections
|
|
return math.sqrt(pow(p1[0]-p2[0], 2) + pow(p1[1]-p2[1], 2))
|
|
# If we have a rectangular overlap region
|
|
elif len(intersections) == 4:
|
|
points = intersections
|
|
ll = vector(min(p.x for p in points), min(p.y for p in points))
|
|
ur = vector(max(p.x for p in points), max(p.y for p in points))
|
|
new_shape = pin_layout("", [ll, ur], self.lpp)
|
|
return max(new_shape.height(), new_shape.width())
|
|
else:
|
|
# This is where we had a corner intersection or none
|
|
return 0
|
|
|
|
|
|
def compute_overlap_segment(self, other):
|
|
"""
|
|
Calculate the intersection segment of two rectangles
|
|
(if any)
|
|
"""
|
|
(r1_ll, r1_ur) = self.rect
|
|
(r2_ll, r2_ur) = other.rect
|
|
|
|
# The other corners besides ll and ur
|
|
r1_ul = vector(r1_ll.x, r1_ur.y)
|
|
r1_lr = vector(r1_ur.x, r1_ll.y)
|
|
r2_ul = vector(r2_ll.x, r2_ur.y)
|
|
r2_lr = vector(r2_ur.x, r2_ll.y)
|
|
|
|
from itertools import tee
|
|
|
|
def pairwise(iterable):
|
|
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
|
|
a, b = tee(iterable)
|
|
next(b, None)
|
|
return zip(a, b)
|
|
|
|
# R1 edges CW
|
|
r1_cw_points = [r1_ll, r1_ul, r1_ur, r1_lr, r1_ll]
|
|
r1_edges = []
|
|
for (p, q) in pairwise(r1_cw_points):
|
|
r1_edges.append([p, q])
|
|
|
|
# R2 edges CW
|
|
r2_cw_points = [r2_ll, r2_ul, r2_ur, r2_lr, r2_ll]
|
|
r2_edges = []
|
|
for (p, q) in pairwise(r2_cw_points):
|
|
r2_edges.append([p, q])
|
|
|
|
# There are 4 edges on each rectangle
|
|
# so just brute force check intersection of each
|
|
# Two pairs of them should intersect
|
|
intersections = []
|
|
for r1e in r1_edges:
|
|
for r2e in r2_edges:
|
|
i = self.segment_intersection(r1e, r2e)
|
|
if i:
|
|
intersections.append(i)
|
|
|
|
return intersections
|
|
|
|
def on_segment(self, p, q, r):
|
|
"""
|
|
Given three co-linear points, determine if q lies on segment pr
|
|
"""
|
|
if q.x <= max(p.x, r.x) and \
|
|
q.x >= min(p.x, r.x) and \
|
|
q.y <= max(p.y, r.y) and \
|
|
q.y >= min(p.y, r.y):
|
|
return True
|
|
|
|
return False
|
|
|
|
def segment_intersection(self, s1, s2):
|
|
"""
|
|
Determine the intersection point of two segments
|
|
Return the a segment if they overlap.
|
|
Return None if they don't.
|
|
"""
|
|
(a, b) = s1
|
|
(c, d) = s2
|
|
# Line AB represented as a1x + b1y = c1
|
|
a1 = b.y - a.y
|
|
b1 = a.x - b.x
|
|
c1 = a1*a.x + b1*a.y
|
|
|
|
# Line CD represented as a2x + b2y = c2
|
|
a2 = d.y - c.y
|
|
b2 = c.x - d.x
|
|
c2 = a2*c.x + b2*c.y
|
|
|
|
determinant = a1*b2 - a2*b1
|
|
|
|
if determinant != 0:
|
|
x = (b2*c1 - b1*c2)/determinant
|
|
y = (a1*c2 - a2*c1)/determinant
|
|
|
|
r = vector(x, y).snap_to_grid()
|
|
if self.on_segment(a, r, b) and self.on_segment(c, r, d):
|
|
return r
|
|
|
|
return None
|
|
|
|
def cut(self, shape):
|
|
"""
|
|
Return a set of shapes that are this shape minus the argument shape.
|
|
"""
|
|
# Make the unique coordinates in X and Y directions
|
|
x_offsets = sorted([self.lx(), self.rx(), shape.lx(), shape.rx()])
|
|
y_offsets = sorted([self.by(), self.uy(), shape.by(), shape.uy()])
|
|
|
|
new_shapes = []
|
|
# Create all of the shapes
|
|
for x1, x2 in zip(x_offsets[0:], x_offsets[1:]):
|
|
if x1==x2:
|
|
continue
|
|
for y1, y2 in zip(y_offsets[0:], y_offsets[1:]):
|
|
if y1==y2:
|
|
continue
|
|
new_shape = pin_layout("", [vector(x1, y1), vector(x2, y2)], self.lpp)
|
|
# Don't add the existing shape in if it overlaps the pin shape
|
|
if new_shape.contains(shape):
|
|
continue
|
|
# Only add non-zero shapes
|
|
if new_shape.area() > 0:
|
|
new_shapes.append(new_shape)
|
|
|
|
return new_shapes
|
|
|
|
def same_lpp(self, lpp1, lpp2):
|
|
"""
|
|
Check if the layers and purposes are the same.
|
|
Ignore if purpose is a None.
|
|
"""
|
|
if lpp1[1] == None or lpp2[1] == None:
|
|
return lpp1[0] == lpp2[0]
|
|
|
|
return lpp1[0] == lpp2[0] and lpp1[1] == lpp2[1]
|