Use bbox trees to iterate over shapes in routing region efficiently

This commit is contained in:
Eren Dogan 2023-09-01 20:37:07 -07:00
parent d9004f6de6
commit 775922774a
3 changed files with 207 additions and 9 deletions

63
compiler/router/bbox.py Normal file
View File

@ -0,0 +1,63 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from openram.base.vector import vector
from .graph_utils import snap
class bbox:
"""
This class represents a bounding box object that is used in `bbox_node`
class. We are using bbox objects to group shapes in the router graphs.
"""
def __init__(self, shape=None):
self.shape = shape
self.rect = None
if self.shape:
self.rect = self.shape.rect
def area(self):
""" Return the area of this bbox. """
ll, ur = self.rect
width = ur.x - ll.x
height = ur.y - ll.y
return snap(width * height)
def merge(self, other):
""" Return the bbox created by merging two bbox objects. """
ll, ur = self.rect
oll, our = other.rect
min_x = min(ll.x, oll.x)
max_x = max(ur.x, our.x)
min_y = min(ll.y, oll.y)
max_y = max(ur.y, our.y)
rect = [vector(min_x, min_y), vector(max_x, max_y)]
merged = bbox()
merged.rect = rect
return merged
def overlap(self, other):
""" Return the bbox created by overlapping two bbox objects. """
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 and max_y >= min_y:
rect = [vector(min_x, min_y), vector(max_x, max_y)]
else:
return None
overlapped = bbox()
overlapped.rect = rect
return overlapped

View File

@ -0,0 +1,117 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz
# All rights reserved.
#
from .bbox import bbox
class bbox_node:
"""
This class represents a node in the bbox tree structure. Bbox trees are
binary trees we use to partition the shapes in the routing region so that
we can detect overlaps faster in a binary search-like manner.
"""
def __init__(self, bbox, left=None, right=None):
self.bbox = bbox
self.is_leaf = not left and not right
self.left = left
self.right = right
def iterate_point(self, point, check_done=False):
""" Iterate over shapes in the tree that overlap the given point. """
px, py = point.x, point.y
# Return this shape if it's a leaf
if self.is_leaf:
ll, ur = self.bbox.rect
if check_done or (ll.x <= px and px <= ur.x and ll.y <= py and py <= ur.y):
yield self.bbox.shape
else:
# Check the left child
if self.left:
ll, ur = self.left.bbox.rect
if ll.x <= px and px <= ur.x and ll.y <= py and py <= ur.y:
yield from self.left.iterate_point(point, True)
# Check the right child
if self.right:
ll, ur = self.right.bbox.rect
if ll.x <= px and px <= ur.x and ll.y <= py and py <= ur.y:
yield from self.right.iterate_point(point, True)
def iterate_shape(self, shape, check_done=False):
""" Iterate over shapes in the tree that overlap the given shape. """
sll, sur = shape.rect
# Return this shape if it's a leaf
if self.is_leaf:
ll, ur = self.bbox.rect
if check_done or (ll.x <= sur.x and sll.x <= ur.x and ll.y <= sur.y and sll.y <= ur.y):
yield self.bbox.shape
else:
# Check the left child
if self.left:
ll, ur = self.left.bbox.rect
if ll.x <= sur.x and sll.x <= ur.x and ll.y <= sur.y and sll.y <= ur.y:
yield from self.left.iterate_shape(shape, True)
# Check the right child
if self.right:
ll, ur = self.right.bbox.rect
if ll.x <= sur.x and sll.x <= ur.x and ll.y <= sur.y and sll.y <= ur.y:
yield from self.right.iterate_shape(shape, True)
def get_costs(self, bbox):
""" Return the costs of bbox nodes after merging the given bbox. """
# Find the new areas for all possible cases
self_merge = bbox.merge(self.bbox)
left_merge = bbox.merge(self.left.bbox)
right_merge = bbox.merge(self.right.bbox)
# Add the change in areas as cost
self_cost = self_merge.area()
left_cost = self_merge.area() - self.bbox.area()
left_cost += left_merge.area() - self.left.bbox.area()
right_cost = self_merge.area() - self.bbox.area()
right_cost += right_merge.area() - self.right.bbox.area()
# Add the overlaps in areas as cost
self_overlap = self.bbox.overlap(bbox)
left_overlap = left_merge.overlap(self.right.bbox)
right_overlap = right_merge.overlap(self.left.bbox)
if self_overlap:
self_cost += self_overlap.area()
if left_overlap:
left_cost += left_overlap.area()
if right_overlap:
right_cost += right_overlap.area()
return self_cost, left_cost, right_cost
def insert(self, bbox):
""" Insert a bbox to the bbox tree. """
if self.is_leaf:
# Put the current bbox to the left child
self.left = bbox_node(self.bbox)
# Put the new bbox to the right child
self.right = bbox_node(bbox)
else:
# Calculate the costs of adding the new bbox
self_cost, left_cost, right_cost = self.get_costs(bbox)
if self_cost < left_cost and self_cost < right_cost: # Add here
self.left = bbox_node(self.bbox, left=self.left, right=self.right)
self.right = bbox_node(bbox)
elif left_cost < right_cost: # Add to the left
self.left.insert(bbox)
else: # Add to the right
self.right.insert(bbox)
# Update the current bbox
self.bbox = self.left.bbox.merge(self.right.bbox)
self.is_leaf = False

View File

@ -9,6 +9,8 @@ from openram import debug
from openram.base.vector import vector
from openram.base.vector3d import vector3d
from openram.tech import drc
from .bbox import bbox
from .bbox_node import bbox_node
from .graph_node import graph_node
from .graph_probe import graph_probe
from .graph_utils import snap
@ -80,11 +82,8 @@ class graph:
probe_shape = graph_probe(p1, p2, self.router.get_lpp(p1.z))
pll, pur = probe_shape.rect
# Check if any blockage blocks this probe
for blockage in self.graph_blockages:
for blockage in self.blockage_bbox_tree.iterate_shape(probe_shape):
bll, bur = blockage.rect
# Not overlapping
if bll.x > pur.x or pll.x > bur.x or bll.y > pur.y or pll.y > bur.y:
continue
# Not on the same layer
if not blockage.same_lpp(blockage.lpp, probe_shape.lpp):
continue
@ -116,11 +115,8 @@ class graph:
half_wide = self.router.half_wire
spacing = snap(self.router.track_space + half_wide + drc["grid"])
blocked = False
for blockage in self.graph_blockages:
for blockage in self.blockage_bbox_tree.iterate_point(p):
ll, ur = blockage.rect
# Not overlapping
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
continue
# Not on the same layer
if self.router.get_zindex(blockage.lpp) != z:
continue
@ -168,11 +164,16 @@ class graph:
for node in nodes:
if self.is_node_blocked(node, pin_safe=False):
return True
# Skip if no via is present
if len(self.graph_vias) == 0:
return False
# If the nodes are blocked by a via
x = node.center.x
y = node.center.y
z = node.center.z
for via in self.graph_vias:
for via in self.via_bbox_tree.iterate_point(node.center):
ll, ur = via.rect
# Not overlapping
if ll.x > x or x > ur.x or ll.y > y or y > ur.y:
@ -212,6 +213,8 @@ class graph:
region.bbox(self.graph_blockages)
# Find and include edge shapes to prevent DRC errors
self.find_graph_blockages(region)
# Build the bbox tree
self.build_bbox_trees()
# Generate the graph nodes from cartesian values
self.generate_graph_nodes(x_values, y_values)
# Save the graph nodes that lie in source and target shapes
@ -257,6 +260,21 @@ class graph:
self.graph_vias.append(via)
def build_bbox_trees(self):
""" Build bbox trees for blockages and vias in the routing region. """
# Bbox tree for blockages
self.blockage_bbox_tree = bbox_node(bbox(self.graph_blockages[0]))
for i in range(1, len(self.graph_blockages)):
self.blockage_bbox_tree.insert(bbox(self.graph_blockages[i]))
# Bbox tree for vias
if len(self.graph_vias) == 0:
return
self.via_bbox_tree = bbox_node(bbox(self.graph_vias[0]))
for i in range(1, len(self.graph_vias)):
self.via_bbox_tree.insert(bbox(self.graph_vias[i]))
def generate_cartesian_values(self):
"""
Generate x and y values from all the corners of the shapes in the