From cd339ebbd0d3b5f7be99ebc842524ca3e16cdaa5 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 9 May 2023 13:23:01 -0700 Subject: [PATCH] Add A* algorithm for navigation router --- compiler/router/navigation_blockage.py | 32 +++-- compiler/router/navigation_graph.py | 168 +++++++++++++++++++++---- compiler/router/navigation_node.py | 28 ++++- compiler/router/navigation_router.py | 82 ++++++++---- 4 files changed, 244 insertions(+), 66 deletions(-) diff --git a/compiler/router/navigation_blockage.py b/compiler/router/navigation_blockage.py index cd9cd554..8cf5ac0a 100644 --- a/compiler/router/navigation_blockage.py +++ b/compiler/router/navigation_blockage.py @@ -3,29 +3,35 @@ # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # -from openram.base.vector import vector from .navigation_node import navigation_node class navigation_blockage: """ This class represents a blockage on the navigation graph. """ - def __init__(self, ll, ur): + def __init__(self, ll, ur, layer=0): self.ll = ll self.ur = ur + self.layer = layer - def create_corner_nodes(self): + @property + def rect(self): + """ """ + + return self.ll, self.ur + + + def create_corner_nodes(self, offset): """ Create nodes on all 4 corners of this blockage. """ - corners = [] - corners.append(navigation_node(vector(self.ll[0], self.ll[1]))) - corners.append(navigation_node(vector(self.ll[0], self.ur[1]))) - corners.append(navigation_node(vector(self.ur[0], self.ll[1]))) - corners.append(navigation_node(vector(self.ur[0], self.ur[1]))) - corners[0].add_neighbor(corners[1]) - corners[0].add_neighbor(corners[2]) - corners[3].add_neighbor(corners[1]) - corners[3].add_neighbor(corners[2]) - return corners + self.corners = [] + self.corners.append(navigation_node([self.ll[0], self.ll[1], self.layer], offset, -1, -1)) + self.corners.append(navigation_node([self.ll[0], self.ur[1], self.layer], offset, -1, 1)) + self.corners.append(navigation_node([self.ur[0], self.ll[1], self.layer], offset, 1, -1)) + self.corners.append(navigation_node([self.ur[0], self.ur[1], self.layer], offset, 1, 1)) + self.corners[0].add_neighbor(self.corners[1]) + self.corners[0].add_neighbor(self.corners[2]) + self.corners[3].add_neighbor(self.corners[1]) + self.corners[3].add_neighbor(self.corners[2]) diff --git a/compiler/router/navigation_graph.py b/compiler/router/navigation_graph.py index 793c9b1c..eeaffce2 100644 --- a/compiler/router/navigation_graph.py +++ b/compiler/router/navigation_graph.py @@ -3,8 +3,8 @@ # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # +import heapq from openram import debug -from openram.base.vector import vector from .navigation_node import navigation_node from .navigation_blockage import navigation_blockage @@ -12,8 +12,9 @@ from .navigation_blockage import navigation_blockage class navigation_graph: """ This is the navigation graph created from the blockages. """ - def __init__(self): - pass + def __init__(self, track_width): + + self.track_width = track_width def is_probe_blocked(self, p1, p2): @@ -43,11 +44,10 @@ class navigation_graph: debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target)) # Find the region to be routed and only include objects inside that region - s_ll, sou_ur = layout_source.rect - t_ll, tar_ur = layout_target.rect - ll = vector(min(s_ll.x, t_ll.x), max(s_ll.y, t_ll.y)) - ur = vector(max(sou_ur.x, tar_ur.x), min(sou_ur.y, tar_ur.y)) - region = (ll, ur) + s_ll, s_ur = layout_source.rect + t_ll, t_ur = layout_target.rect + region = (s_ll.min(t_ll), s_ur.min(t_ur)) + debug.info(0, "Routing region is ll: '{0}' ur: '{1}'".format(region[0], region[1])) # Instantiate "navigation blockage" objects from layout blockages self.nav_blockages = [] @@ -65,37 +65,155 @@ class navigation_graph: # Create the corner nodes for blockage in self.nav_blockages: - self.nodes.extend(blockage.create_corner_nodes()) + blockage.create_corner_nodes(self.track_width / 2) + + # These nodes will be connected to create the final graph + connect_objs = [] + connect_objs.extend(self.nodes) + connect_objs.extend(self.nav_blockages) # Create intersection nodes # NOTE: Intersection nodes are used to connect boundaries of blockages # perpendicularly. - new_nodes = [] - debug.info(0, "Number of blockages: {}".format(len(self.nav_blockages))) - debug.info(0, "Number of nodes: {}".format(len(self.nodes))) - for i in range(len(self.nodes)): - debug.info(3, "Creating intersections for node #{}".format(i)) - node1 = self.nodes[i] - for j in range(i + 1, len(self.nodes)): - node2 = self.nodes[j] - # Skip if the nodes are already connected - if node1 in node2.neighbors: - continue + debug.info(0, "Number of objects: {}".format(len(connect_objs))) + for i in range(len(connect_objs)): + obj1 = connect_objs[i] + for j in range(i + 1, len(connect_objs)): + obj2 = connect_objs[j] + node1, node2 = get_closest_nodes(obj1, obj2) # Try two different corners for k in [0, 1]: # Create a node at the perpendicular corner of these two nodes - corner = navigation_node(vector(node1.position[k], node2.position[int(not k)])) + x_node = node1 if k else node2 + y_node = node2 if k else node1 + corner = navigation_node([x_node.center[0], y_node.center[1], 0]) # Skip this corner if the perpendicular connection is blocked - if self.is_probe_blocked(corner.position, node1.position) or self.is_probe_blocked(corner.position, node2.position): + if self.is_probe_blocked(corner.center, node1.center) or self.is_probe_blocked(corner.center, node2.center): continue + # Check if this new node stands on an existing connection + self.remove_intersected_neighbors(node1, corner, k) + self.remove_intersected_neighbors(node2, corner, not(k)) + # Add this new node to the graph corner.add_neighbor(node1) corner.add_neighbor(node2) - new_nodes.append(corner) - self.nodes.extend(new_nodes) - debug.info(0, "Number of nodes after intersections: {}".format(len(self.nodes))) + self.nodes.append(corner) + + # Add corner nodes from blockages after intersections + for blockage in self.nav_blockages: + self.nodes.extend(blockage.corners) + debug.info(0, "Number of nodes after corners: {}".format(len(self.nodes))) + + + def remove_intersected_neighbors(self, node, corner, axis): + """ """ + + a = node.center + mid = corner.center + for neighbor in node.neighbors: + b = neighbor.center + if a[not(axis)] == b[not(axis)] and is_between(a[axis], b[axis], mid[axis]): + neighbor.remove_neighbor(node) + neighbor.add_neighbor(corner) + + + def find_shortest_path(self, source, target): + """ + Find the shortest path from the source node to target node using the + A* algorithm. + """ + + source = self.nodes[0] + target = self.nodes[1] + + # Heuristic function to calculate the scores + h = lambda node: target.center.distance(node.center) + + queue = [] + close_set = set() + came_from = {} + g_scores = {} + f_scores = {} + + # Initialize score values for the source node + g_scores[source.id] = 0 + f_scores[source.id] = h(source) + + heapq.heappush(queue, (f_scores[source.id], source.id, source)) + + # Run the A* algorithm + while len(queue) > 0: + # Get the closest node from the queue + current = heapq.heappop(queue)[2] + + # Return if already discovered + if current in close_set: + continue + close_set.add(current) + + # Check if we've reached the target + if current == target: + path = [] + while current.id in came_from: + path.append(current) + current = came_from[current.id] + path.append(current) + return path + + # Update neighbor scores + for node in current.neighbors: + tentative_score = current.get_edge_cost(node) + g_scores[current.id] + if node.id not in g_scores or tentative_score < g_scores[node.id]: + came_from[node.id] = current + g_scores[node.id] = tentative_score + f_scores[node.id] = tentative_score + h(node) + heapq.heappush(queue, (f_scores[node.id], node.id, node)) + + # Return None if not connected + return None def is_between(a, b, mid): """ Return if 'mid' is between 'a' and 'b'. """ return (a < mid and mid < b) or (b < mid and mid < a) + + +def get_closest_nodes(a, b): + """ """ + + if isinstance(a, navigation_node) and isinstance(b, navigation_node): + return a, b + if isinstance(a, navigation_blockage) and isinstance(b, navigation_blockage): + min_dist = float("inf") + min_a = None + min_b = None + for node_a in a.corners: + for node_b in b.corners: + dist = node_a.center.distance(node_b.center) + if dist < min_dist: + min_dist = dist + min_a = node_a + min_b = node_b + return min_a, min_b + if isinstance(a, navigation_node): + min_dist = float("inf") + min_a = None + min_b = None + for node_b in b.corners: + dist = a.center.distance(node_b.center) + if dist < min_dist: + min_dist = dist + min_a = a + min_b = node_b + return min_a, min_b + if isinstance(b, navigation_node): + min_dist = float("inf") + min_a = None + min_b = None + for node_a in a.corners: + dist = b.center.distance(node_a.center) + if dist < min_dist: + min_dist = dist + min_a = node_a + min_b = b + return min_a, min_b diff --git a/compiler/router/navigation_node.py b/compiler/router/navigation_node.py index 73d16267..7808cc1b 100644 --- a/compiler/router/navigation_node.py +++ b/compiler/router/navigation_node.py @@ -3,13 +3,28 @@ # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # +from openram.base.vector import vector +from openram.base.vector3d import vector3d + class navigation_node: """ This class represents a node on the navigation graph. """ - def __init__(self, position): + # This is used to assign unique ids to nodes + next_id = 0 - self.position = position + def __init__(self, center, offset=None, horizontal=1, vertical=1): + + self.id = navigation_node.next_id + navigation_node.next_id += 1 + if isinstance(center, vector): + self.center = vector3d(center[0], center[1], 0) + elif isinstance(center, vector3d): + self.center = center + else: + self.center = vector3d(center) + if offset: + self.center += vector3d(offset * horizontal, offset * vertical, 0) self.neighbors = [] @@ -26,3 +41,12 @@ class navigation_node: if node in self.neighbors: self.neighbors.remove(node) node.neighbors.remove(self) + + + def get_edge_cost(self, node): + """ Return the cost of going to node. """ + + if node in self.neighbors: + return self.center.distance(node.center) + else: + return float("inf") diff --git a/compiler/router/navigation_router.py b/compiler/router/navigation_router.py index 201b5b29..ea9b43bd 100644 --- a/compiler/router/navigation_router.py +++ b/compiler/router/navigation_router.py @@ -6,6 +6,7 @@ from openram import debug from openram.base.pin_layout import pin_layout from openram.base.vector import vector +from openram.base.vector3d import vector3d from openram.gdsMill import gdsMill from openram.tech import GDS from openram.tech import layer as tech_layer @@ -26,6 +27,7 @@ class navigation_router(router_tech): self.layers = layers self.design = design self.gds_filename = OPTS.openram_temp + "temp.gds" + self.track_width = 1 self.pins = {} self.all_pins = set() self.blockages = [] @@ -49,12 +51,18 @@ class navigation_router(router_tech): self.find_blockages() # Create the navigation graph - self.nav = navigation_graph() pin_iter = iter(self.pins["vdd"]) vdd_0 = next(pin_iter) vdd_1 = next(pin_iter) + self.nav = navigation_graph(self.track_width) self.nav.create_graph(vdd_0, vdd_1, self.blockages) + # Find the shortest path from source to target + path = self.nav.find_shortest_path(vdd_0, vdd_1) + + # Create the path shapes on layout + self.add_path(path) + self.write_debug_gds(source=vdd_0, target=vdd_1) @@ -68,8 +76,8 @@ class navigation_router(router_tech): layer, boundary = shape # gdsMill boundaries are in (left, bottom, right, top) order # so repack and snap to the grid - ll = vector(boundary[0], boundary[1]).snap_to_grid() - ur = vector(boundary[2], boundary[3]).snap_to_grid() + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) rect = [ll, ur] pin = pin_layout(pin_name, rect, layer) pin_set.add(pin) @@ -80,7 +88,7 @@ class navigation_router(router_tech): def find_blockages(self): """ """ - debug.info(1, "Finding all blockages") + debug.info(1, "Finding all blockages...") for lpp in [self.vert_lpp, self.horiz_lpp]: shapes = self.layout.getAllShapes(lpp) @@ -92,8 +100,7 @@ class navigation_router(router_tech): rect = [ll, ur] new_shape = pin_layout("blockage{}".format(len(self.blockages)), rect, - lpp) - + lpp).inflated_pin() # If there is a rectangle that is the same in the pins, # it isn't a blockage if new_shape not in self.all_pins and not self.pin_contains(new_shape): @@ -107,6 +114,30 @@ class navigation_router(router_tech): return False + def add_path(self, path): + """ """ + + for i in range(len(path) - 1): + self.connect_nodes(path[i], path[i + 1]) + + + def connect_nodes(self, a, b): + """ Connect nodes 'a' and 'b' with a wire. """ + + # Calculate the shape of the wire + track_offset = vector3d(self.track_width / 2, self.track_width / 2, 0) + ll = a.center.min(b.center) - track_offset + ur = a.center.max(b.center) + track_offset + + debug.info(0, "Adding wire: ({}, {})".format(ll, ur)) + + # Add the shape to the layout + self.design.add_rect(layer="text", + offset=(ll[0], ll[1]), + width=ur.x - ll.x, + height=ur.y - ll.y) + + def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): """ """ @@ -120,30 +151,16 @@ class navigation_router(router_tech): # Display the inflated blockage for blockage in self.nav.nav_blockages: - ll, ur = blockage.ll, blockage.ur - self.design.add_rect(layer="text", - offset=ll, - width=ur.x - ll.x, - height=ur.y - ll.y) - self.design.add_label(text="blockage", - layer="text", - offset=ll) + self.add_object_info(blockage, "blockage") for node in self.nav.nodes: - self.design.add_rect_center(layer="text", - offset=node.position, - width=1, - height=1) - self.design.add_label(text="-0-", + offset = (node.center.x, node.center.y) + self.design.add_label(text="O", layer="text", - offset=node.position) + offset=offset) if source: - self.design.add_label(text="source", - layer="text", - offset=source.rect[0]) + self.add_object_info(source, "source") if target: - self.design.add_label(text="target", - layer="text", - offset=target.rect[0]) + self.add_object_info(target, "target") def del_router_info(self): @@ -151,3 +168,16 @@ class navigation_router(router_tech): lpp = tech_layer["text"] self.design.objs = [x for x in self.design.objs if x.lpp != lpp] + + + def add_object_info(self, obj, label): + """ """ + + ll, ur = obj.rect + self.design.add_rect(layer="text", + offset=ll, + width=ur.x - ll.x, + height=ur.y - ll.y) + self.design.add_label(text=label, + layer="text", + offset=ll)