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