diff --git a/compiler/router/bbox.py b/compiler/router/bbox.py new file mode 100644 index 00000000..56f53be0 --- /dev/null +++ b/compiler/router/bbox.py @@ -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 diff --git a/compiler/router/bbox_node.py b/compiler/router/bbox_node.py new file mode 100644 index 00000000..e5c1aadd --- /dev/null +++ b/compiler/router/bbox_node.py @@ -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 diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 85158ef7..d8017999 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -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