From 909ac6ce687c0333814c189b4008e061ac86f5c4 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Thu, 4 May 2023 20:51:30 -0700 Subject: [PATCH 01/91] Add initial files for navigation router --- compiler/modules/sram_1bank.py | 2 + compiler/router/__init__.py | 1 + compiler/router/navigation_blockage.py | 31 +++++ compiler/router/navigation_graph.py | 101 ++++++++++++++++ compiler/router/navigation_node.py | 28 +++++ compiler/router/navigation_router.py | 153 +++++++++++++++++++++++++ 6 files changed, 316 insertions(+) create mode 100644 compiler/router/navigation_blockage.py create mode 100644 compiler/router/navigation_graph.py create mode 100644 compiler/router/navigation_node.py create mode 100644 compiler/router/navigation_router.py diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 8cc7cd8c..c5049b40 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -255,6 +255,8 @@ class sram_1bank(design, verilog, lef): return elif OPTS.route_supplies == "grid": from openram.router import supply_grid_router as router + elif OPTS.route_supplies == "navigation": + from openram.router import navigation_router as router else: from openram.router import supply_tree_router as router rtr=router(layers=self.supply_stack, diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index e428adda..e8da5264 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -8,3 +8,4 @@ from .signal_escape_router import * from .signal_router import * from .supply_grid_router import * from .supply_tree_router import * +from .navigation_router import * diff --git a/compiler/router/navigation_blockage.py b/compiler/router/navigation_blockage.py new file mode 100644 index 00000000..cd9cd554 --- /dev/null +++ b/compiler/router/navigation_blockage.py @@ -0,0 +1,31 @@ +# 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 .navigation_node import navigation_node + + +class navigation_blockage: + """ This class represents a blockage on the navigation graph. """ + + def __init__(self, ll, ur): + + self.ll = ll + self.ur = ur + + + def create_corner_nodes(self): + """ 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 diff --git a/compiler/router/navigation_graph.py b/compiler/router/navigation_graph.py new file mode 100644 index 00000000..793c9b1c --- /dev/null +++ b/compiler/router/navigation_graph.py @@ -0,0 +1,101 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from .navigation_node import navigation_node +from .navigation_blockage import navigation_blockage + + +class navigation_graph: + """ This is the navigation graph created from the blockages. """ + + def __init__(self): + pass + + + def is_probe_blocked(self, p1, p2): + """ + Return if a probe sent from p1 to p2 encounters a blockage. + The probe must be sent vertically or horizontally. + This method assumes that blockages are rectangular. + """ + + # Check if any blockage blocks this probe + for blockage in self.nav_blockages: + right_x = blockage.ur[0] + upper_y = blockage.ur[1] + left_x = blockage.ll[0] + lower_y = blockage.ll[1] + # Check if blocked vertically + if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)): + return True + # Check if blocked horizontally + if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)): + return True + return False + + + def create_graph(self, layout_source, layout_target, layout_blockages): + """ """ + 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) + + # Instantiate "navigation blockage" objects from layout blockages + self.nav_blockages = [] + for layout_blockage in layout_blockages: + ll, ur = layout_blockage.rect + if (is_between(region[0].x, region[1].x, ll.x) and is_between(region[0].y, region[1].y, ll.y)) or \ + (is_between(region[0].x, region[1].x, ur.x) and is_between(region[0].y, region[1].y, ur.y)): + self.nav_blockages.append(navigation_blockage(ll, ur)) + + self.nodes = [] + + # Add source and target for this graph + self.nodes.append(navigation_node(layout_source.center())) + self.nodes.append(navigation_node(layout_target.center())) + + # Create the corner nodes + for blockage in self.nav_blockages: + self.nodes.extend(blockage.create_corner_nodes()) + + # 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 + # 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)])) + # 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): + continue + 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))) + + +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) diff --git a/compiler/router/navigation_node.py b/compiler/router/navigation_node.py new file mode 100644 index 00000000..73d16267 --- /dev/null +++ b/compiler/router/navigation_node.py @@ -0,0 +1,28 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# + +class navigation_node: + """ This class represents a node on the navigation graph. """ + + def __init__(self, position): + + self.position = position + self.neighbors = [] + + + def add_neighbor(self, node): + """ Connect two nodes. """ + + self.neighbors.append(node) + node.neighbors.append(self) + + + def remove_neighbor(self, node): + """ Disconnect two nodes. """ + + if node in self.neighbors: + self.neighbors.remove(node) + node.neighbors.remove(self) diff --git a/compiler/router/navigation_router.py b/compiler/router/navigation_router.py new file mode 100644 index 00000000..201b5b29 --- /dev/null +++ b/compiler/router/navigation_router.py @@ -0,0 +1,153 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.pin_layout import pin_layout +from openram.base.vector import vector +from openram.gdsMill import gdsMill +from openram.tech import GDS +from openram.tech import layer as tech_layer +from openram import OPTS +from .router_tech import router_tech +from .navigation_graph import navigation_graph + + +class navigation_router(router_tech): + """ + This is the router class that implements navigation graph routing algorithm. + """ + + def __init__(self, layers, design, bbox=None, pin_type=None): + + router_tech.__init__(self, layers, route_track_width=1) + + self.layers = layers + self.design = design + self.gds_filename = OPTS.openram_temp + "temp.gds" + self.pins = {} + self.all_pins = set() + self.blockages = [] + + + def route(self, vdd_name="vdd", gnd_name="gnd"): + """ Route the given pins in the given order. """ + #debug.info(0, "Running router for {}...".format(pins)) + + # Prepare gdsMill to find pins and blockages + self.design.gds_write(self.gds_filename) + self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) + self.reader = gdsMill.Gds2reader(self.layout) + self.reader.loadFromFile(self.gds_filename) + + # Find pins to be routed + self.find_pins(vdd_name) + self.find_pins(gnd_name) + + # Find blockages + 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.create_graph(vdd_0, vdd_1, self.blockages) + + self.write_debug_gds(source=vdd_0, target=vdd_1) + + + def find_pins(self, pin_name): + """ """ + debug.info(1, "Finding all pins for {}".format(pin_name)) + + shape_list = self.layout.getAllPinShapes(str(pin_name)) + pin_set = set() + for shape in shape_list: + 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() + rect = [ll, ur] + pin = pin_layout(pin_name, rect, layer) + pin_set.add(pin) + # Add these pins to the 'pins' dict + self.pins[pin_name] = pin_set + self.all_pins.update(pin_set) + + + def find_blockages(self): + """ """ + debug.info(1, "Finding all blockages") + + for lpp in [self.vert_lpp, self.horiz_lpp]: + shapes = self.layout.getAllShapes(lpp) + for boundary in shapes: + # gdsMill boundaries are in (left, bottom, right, top) order + # so repack and snap to the grid + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_shape = pin_layout("blockage{}".format(len(self.blockages)), + rect, + lpp) + + # 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): + self.blockages.append(new_shape) + + + def pin_contains(self, shape): + for pin in self.all_pins: + if pin.contains(shape): + return True + return False + + + def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): + """ """ + + self.add_router_info(source, target) + self.design.gds_write(gds_name) + self.del_router_info() + + + def add_router_info(self, source=None, target=None): + """ """ + + # 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) + 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-", + layer="text", + offset=node.position) + if source: + self.design.add_label(text="source", + layer="text", + offset=source.rect[0]) + if target: + self.design.add_label(text="target", + layer="text", + offset=target.rect[0]) + + + def del_router_info(self): + """ """ + + lpp = tech_layer["text"] + self.design.objs = [x for x in self.design.objs if x.lpp != lpp] From cd339ebbd0d3b5f7be99ebc842524ca3e16cdaa5 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 9 May 2023 13:23:01 -0700 Subject: [PATCH 02/91] 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) From 648a631a286f7425149b5ae0594362b873a6e65e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 22 May 2023 13:08:21 -0700 Subject: [PATCH 03/91] Use Hanan points to generate the routing graph --- compiler/router/navigation_blockage.py | 37 ---- compiler/router/navigation_graph.py | 229 ++++++++++--------------- compiler/router/navigation_node.py | 18 +- compiler/router/navigation_router.py | 26 +-- compiler/router/navigation_utils.py | 21 +++ 5 files changed, 132 insertions(+), 199 deletions(-) delete mode 100644 compiler/router/navigation_blockage.py create mode 100644 compiler/router/navigation_utils.py diff --git a/compiler/router/navigation_blockage.py b/compiler/router/navigation_blockage.py deleted file mode 100644 index 8cf5ac0a..00000000 --- a/compiler/router/navigation_blockage.py +++ /dev/null @@ -1,37 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz -# All rights reserved. -# -from .navigation_node import navigation_node - - -class navigation_blockage: - """ This class represents a blockage on the navigation graph. """ - - def __init__(self, ll, ur, layer=0): - - self.ll = ll - self.ur = ur - self.layer = layer - - - @property - def rect(self): - """ """ - - return self.ll, self.ur - - - def create_corner_nodes(self, offset): - """ Create nodes on all 4 corners of this blockage. """ - - 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 eeaffce2..b9864067 100644 --- a/compiler/router/navigation_graph.py +++ b/compiler/router/navigation_graph.py @@ -5,41 +5,21 @@ # import heapq from openram import debug +from openram.base.vector3d import vector3d +from .direction import direction from .navigation_node import navigation_node -from .navigation_blockage import navigation_blockage +from .navigation_utils import * class navigation_graph: """ This is the navigation graph created from the blockages. """ - def __init__(self, track_width): + def __init__(self, router): - self.track_width = track_width + self.router = router - def is_probe_blocked(self, p1, p2): - """ - Return if a probe sent from p1 to p2 encounters a blockage. - The probe must be sent vertically or horizontally. - This method assumes that blockages are rectangular. - """ - - # Check if any blockage blocks this probe - for blockage in self.nav_blockages: - right_x = blockage.ur[0] - upper_y = blockage.ur[1] - left_x = blockage.ll[0] - lower_y = blockage.ll[1] - # Check if blocked vertically - if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)): - return True - # Check if blocked horizontally - if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)): - return True - return False - - - def create_graph(self, layout_source, layout_target, layout_blockages): + def create_graph(self, layout_source, layout_target): """ """ debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target)) @@ -49,71 +29,78 @@ class navigation_graph: 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 = [] - for layout_blockage in layout_blockages: - ll, ur = layout_blockage.rect - if (is_between(region[0].x, region[1].x, ll.x) and is_between(region[0].y, region[1].y, ll.y)) or \ - (is_between(region[0].x, region[1].x, ur.x) and is_between(region[0].y, region[1].y, ur.y)): - self.nav_blockages.append(navigation_blockage(ll, ur)) + # Find the blockages that are in the routing area + self.graph_blockages = [] + for blockage in self.router.blockages: + ll, ur = blockage.rect + if is_in_region(ll, region) or is_in_region(ur, region): + self.graph_blockages.append(blockage) + debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) + # Obtain the x and y points for Hanan grid + x_values = [] + y_values = [] + offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 + for shape in [layout_source, layout_target]: + center = shape.center() + x_values.append(center.x) + y_values.append(center.y) + for blockage in self.graph_blockages: + ll, ur = blockage.rect + x_values.extend([ll.x - offset, ur.x + offset]) + y_values.extend([ll.y - offset, ur.y + offset]) + + # Generate Hanan points here (cartesian product of all x and y values) + hanan_points = [] + for x in x_values: + for y in y_values: + hanan_points.append(vector3d(x, y, 0)) + hanan_points.append(vector3d(x, y, 1)) + + # Remove blocked points + for point in hanan_points.copy(): + for blockage in self.graph_blockages: + ll, ur = blockage.rect + if self.router.get_zindex(blockage.lpp) == point.z and is_in_region(point, blockage.rect): + hanan_points.remove(point) + break + + # Create graph nodes from Hanan points self.nodes = [] + for point in hanan_points: + self.nodes.append(navigation_node(point)) - # Add source and target for this graph - self.nodes.append(navigation_node(layout_source.center())) - self.nodes.append(navigation_node(layout_target.center())) - - # Create the corner nodes - for blockage in self.nav_blockages: - 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. - 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 - 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.center, node1.center) or self.is_probe_blocked(corner.center, node2.center): + # Connect closest points avoiding blockages + for i in range(len(self.nodes)): + node = self.nodes[i] + for d in direction.cardinal_offsets(): + min_dist = float("inf") + min_neighbor = None + for j in range(i + 1, len(self.nodes)): + neighbor = self.nodes[j] + if node.center.z != neighbor.center.z: 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) - self.nodes.append(corner) + distance_vector = neighbor.center - node.center + distance = node.center.distance(neighbor.center) + if (distance_vector.x or (distance_vector.y * d.y <= 0)) and \ + (distance_vector.y or (distance_vector.x * d.x <= 0)): + continue + if distance < min_dist: + min_dist = distance + min_neighbor = neighbor + if min_neighbor: + node.add_neighbor(min_neighbor) - # 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) + # Connect nodes that are on top of each other + for i in range(len(self.nodes)): + node = self.nodes[i] + for j in range(i + 1, len(self.nodes)): + neighbor = self.nodes[j] + if node.center.x == neighbor.center.x and \ + node.center.y == neighbor.center.y and \ + node.center.z != neighbor.center.z: + node.add_neighbor(neighbor) + debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) def find_shortest_path(self, source, target): @@ -122,11 +109,19 @@ class navigation_graph: A* algorithm. """ - source = self.nodes[0] - target = self.nodes[1] + # Find source and target nodes + source_center = source.center() + source_center = vector3d(source_center.x, source_center.y, self.router.get_zindex(source.lpp)) + target_center = target.center() + target_center = vector3d(target_center.x, target_center.y, self.router.get_zindex(target.lpp)) + for node in self.nodes: + if node.center == source_center: + source = node + if node.center == target_center: + target = node # Heuristic function to calculate the scores - h = lambda node: target.center.distance(node.center) + h = lambda node: target.center.distance(node.center) + abs(target.center.z - node.center.z) queue = [] close_set = set() @@ -161,7 +156,7 @@ class navigation_graph: # Update neighbor scores for node in current.neighbors: - tentative_score = current.get_edge_cost(node) + g_scores[current.id] + tentative_score = self.get_edge_cost(current, 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 @@ -172,48 +167,14 @@ class navigation_graph: return None -def is_between(a, b, mid): - """ Return if 'mid' is between 'a' and 'b'. """ + def get_edge_cost(self, source, target): + """ """ - 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 + if target in source.neighbors: + is_vertical = source.center.x == target.center.x + layer_dist = source.center.distance(target.center) + if is_vertical != bool(source.center.z): + layer_dist *= 2 + via_dist = abs(source.center.z - target.center.z) * 2 + return layer_dist + via_dist + return float("inf") diff --git a/compiler/router/navigation_node.py b/compiler/router/navigation_node.py index 7808cc1b..87a5935d 100644 --- a/compiler/router/navigation_node.py +++ b/compiler/router/navigation_node.py @@ -13,26 +13,23 @@ class navigation_node: # This is used to assign unique ids to nodes next_id = 0 - def __init__(self, center, offset=None, horizontal=1, vertical=1): + def __init__(self, center): 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): + if isinstance(center, vector3d): self.center = center else: self.center = vector3d(center) - if offset: - self.center += vector3d(offset * horizontal, offset * vertical, 0) self.neighbors = [] def add_neighbor(self, node): """ Connect two nodes. """ - self.neighbors.append(node) - node.neighbors.append(self) + if node not in self.neighbors: + self.neighbors.append(node) + node.neighbors.append(self) def remove_neighbor(self, node): @@ -47,6 +44,5 @@ class navigation_node: """ Return the cost of going to node. """ if node in self.neighbors: - return self.center.distance(node.center) - else: - return float("inf") + return self.center.distance(node.center) + abs(self.center.z - node.center.z) + return float("inf") diff --git a/compiler/router/navigation_router.py b/compiler/router/navigation_router.py index ea9b43bd..8827ba3c 100644 --- a/compiler/router/navigation_router.py +++ b/compiler/router/navigation_router.py @@ -35,7 +35,7 @@ class navigation_router(router_tech): def route(self, vdd_name="vdd", gnd_name="gnd"): """ Route the given pins in the given order. """ - #debug.info(0, "Running router for {}...".format(pins)) + debug.info(1, "Running router for {}...".format(pins)) # Prepare gdsMill to find pins and blockages self.design.gds_write(self.gds_filename) @@ -54,8 +54,8 @@ class navigation_router(router_tech): 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) + self.nav = navigation_graph(self) + self.nav.create_graph(vdd_0, vdd_1) # Find the shortest path from source to target path = self.nav.find_shortest_path(vdd_0, vdd_1) @@ -100,7 +100,7 @@ class navigation_router(router_tech): rect = [ll, ur] new_shape = pin_layout("blockage{}".format(len(self.blockages)), rect, - lpp).inflated_pin() + lpp).inflated_pin(multiple=1) # 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): @@ -124,18 +124,10 @@ class navigation_router(router_tech): 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) + if a.center.x == b.center.x and a.center.y == b.center.y: + self.design.add_via_center(self.layers, vector(a.center.x, b.center.y)) + else: + self.design.add_path(self.get_layer(a.center.z), [a.center, b.center]) def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): @@ -150,7 +142,7 @@ class navigation_router(router_tech): """ """ # Display the inflated blockage - for blockage in self.nav.nav_blockages: + for blockage in self.nav.graph_blockages: self.add_object_info(blockage, "blockage") for node in self.nav.nodes: offset = (node.center.x, node.center.y) diff --git a/compiler/router/navigation_utils.py b/compiler/router/navigation_utils.py new file mode 100644 index 00000000..cf4e93c3 --- /dev/null +++ b/compiler/router/navigation_utils.py @@ -0,0 +1,21 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +""" +Utility functions for navigation router. +""" + +def is_in_region(point, region): + """""" + + if is_between(region[0].x, region[1].x, point.x) and is_between(region[0].y, region[1].y, point.y): + return True + return False + + +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) From 33f1b924a4bef749b7e4fa79aa115f103a8e32ad Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 22 May 2023 18:16:49 -0700 Subject: [PATCH 04/91] Avoid blockages when connecting Hanan points --- compiler/router/navigation_graph.py | 21 +++++++-------------- compiler/router/navigation_node.py | 13 +++++++++---- compiler/router/navigation_router.py | 20 +++++++++++--------- compiler/router/navigation_utils.py | 23 +++++++++++++++++++++++ 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/compiler/router/navigation_graph.py b/compiler/router/navigation_graph.py index b9864067..9985f1dd 100644 --- a/compiler/router/navigation_graph.py +++ b/compiler/router/navigation_graph.py @@ -78,13 +78,19 @@ class navigation_graph: min_neighbor = None for j in range(i + 1, len(self.nodes)): neighbor = self.nodes[j] + # Skip if not on the same layer if node.center.z != neighbor.center.z: continue + # Calculate the distance vector and distance value distance_vector = neighbor.center - node.center distance = node.center.distance(neighbor.center) + # Skip if not connected rectilinearly if (distance_vector.x or (distance_vector.y * d.y <= 0)) and \ (distance_vector.y or (distance_vector.x * d.x <= 0)): continue + # Skip if this connection is blocked by a blockage + if is_probe_blocked(node.center, neighbor.center, self.graph_blockages): + continue if distance < min_dist: min_dist = distance min_neighbor = neighbor @@ -156,7 +162,7 @@ class navigation_graph: # Update neighbor scores for node in current.neighbors: - tentative_score = self.get_edge_cost(current, node) + g_scores[current.id] + 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 @@ -165,16 +171,3 @@ class navigation_graph: # Return None if not connected return None - - - def get_edge_cost(self, source, target): - """ """ - - if target in source.neighbors: - is_vertical = source.center.x == target.center.x - layer_dist = source.center.distance(target.center) - if is_vertical != bool(source.center.z): - layer_dist *= 2 - via_dist = abs(source.center.z - target.center.z) * 2 - return layer_dist + via_dist - return float("inf") diff --git a/compiler/router/navigation_node.py b/compiler/router/navigation_node.py index 87a5935d..e8acdd9b 100644 --- a/compiler/router/navigation_node.py +++ b/compiler/router/navigation_node.py @@ -40,9 +40,14 @@ class navigation_node: node.neighbors.remove(self) - def get_edge_cost(self, node): - """ Return the cost of going to node. """ + def get_edge_cost(self, other): + """ Get the cost of going from this node to the other node. """ - if node in self.neighbors: - return self.center.distance(node.center) + abs(self.center.z - node.center.z) + if other in self.neighbors: + is_vertical = self.center.x == other.center.x + layer_dist = self.center.distance(other.center) + if is_vertical != bool(self.center.z): + layer_dist *= 2 + via_dist = abs(self.center.z - other.center.z) * 2 + return layer_dist + via_dist return float("inf") diff --git a/compiler/router/navigation_router.py b/compiler/router/navigation_router.py index 8827ba3c..a6228d43 100644 --- a/compiler/router/navigation_router.py +++ b/compiler/router/navigation_router.py @@ -35,7 +35,8 @@ class navigation_router(router_tech): def route(self, vdd_name="vdd", gnd_name="gnd"): """ Route the given pins in the given order. """ - debug.info(1, "Running router for {}...".format(pins)) + #debug.info(1, "Running router for {}...".format(pins)) + self.write_debug_gds(gds_name="before.gds") # Prepare gdsMill to find pins and blockages self.design.gds_write(self.gds_filename) @@ -63,7 +64,7 @@ class navigation_router(router_tech): # Create the path shapes on layout self.add_path(path) - self.write_debug_gds(source=vdd_0, target=vdd_1) + self.write_debug_gds(gds_name="after.gds", source=vdd_0, target=vdd_1) def find_pins(self, pin_name): @@ -142,13 +143,14 @@ class navigation_router(router_tech): """ """ # Display the inflated blockage - for blockage in self.nav.graph_blockages: - self.add_object_info(blockage, "blockage") - for node in self.nav.nodes: - offset = (node.center.x, node.center.y) - self.design.add_label(text="O", - layer="text", - offset=offset) + if "nav" in self.__dict__: + for blockage in self.nav.graph_blockages: + self.add_object_info(blockage, "blockage") + for node in self.nav.nodes: + offset = (node.center.x, node.center.y) + self.design.add_label(text="O", + layer="text", + offset=offset) if source: self.add_object_info(source, "source") if target: diff --git a/compiler/router/navigation_utils.py b/compiler/router/navigation_utils.py index cf4e93c3..9ce2cba5 100644 --- a/compiler/router/navigation_utils.py +++ b/compiler/router/navigation_utils.py @@ -7,6 +7,29 @@ Utility functions for navigation router. """ +def is_probe_blocked(p1, p2, blockages): + """ + Return if a probe sent from p1 to p2 encounters a blockage. + The probe must be sent vertically or horizontally. + This method assumes that blockages are rectangular. + """ + + # Check if any blockage blocks this probe + for blockage in blockages: + ll, ur = blockage.rect + right_x = ur[0] + upper_y = ur[1] + left_x = ll[0] + lower_y = ll[1] + # Check if blocked vertically + if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)): + return True + # Check if blocked horizontally + if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)): + return True + return False + + def is_in_region(point, region): """""" From 533c1c9472e100f431a96e842da5b5ca0aeb19c0 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 28 May 2023 21:25:11 -0700 Subject: [PATCH 05/91] Fix gridless router for tall and fat pins --- compiler/router/navigation_graph.py | 89 +++++++++++++++++++--------- compiler/router/navigation_node.py | 6 ++ compiler/router/navigation_router.py | 37 +++++++----- compiler/router/navigation_utils.py | 5 +- 4 files changed, 93 insertions(+), 44 deletions(-) diff --git a/compiler/router/navigation_graph.py b/compiler/router/navigation_graph.py index 9985f1dd..0a1e6462 100644 --- a/compiler/router/navigation_graph.py +++ b/compiler/router/navigation_graph.py @@ -4,7 +4,10 @@ # All rights reserved. # import heapq +from copy import deepcopy 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 .direction import direction from .navigation_node import navigation_node @@ -19,32 +22,57 @@ class navigation_graph: self.router = router + def is_on_same_layer(self, point, shape): + """ Return if the point is on the same layer as the shape. """ + + return point.z == self.router.get_zindex(shape.lpp) + + def create_graph(self, layout_source, layout_target): - """ """ + """ Create the Hanan graph to run routing on later. """ 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, 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])) + region = deepcopy(layout_source) + region.bbox([layout_source, layout_target]) + debug.info(0, "Routing region is {}".format(region.rect)) # Find the blockages that are in the routing area self.graph_blockages = [] for blockage in self.router.blockages: - ll, ur = blockage.rect - if is_in_region(ll, region) or is_in_region(ur, region): + if region.overlaps(blockage): self.graph_blockages.append(blockage) debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) - # Obtain the x and y points for Hanan grid + # Obtain the x and y values for Hanan grid x_values = [] y_values = [] offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 + # Add the source and target pins first for shape in [layout_source, layout_target]: - center = shape.center() - x_values.append(center.x) - y_values.append(center.y) + aspect_ratio = shape.width() / shape.height() + # If the pin is tall or fat, add two points on the ends + if aspect_ratio <= 0.5: # Tall pin + uc = shape.uc() + bc = shape.bc() + points = [vector(uc.x, uc.y - offset), + vector(bc.x, bc.y + offset)] + for p in points: + x_values.append(p.x) + y_values.append(p.y) + elif aspect_ratio >= 2: # Fat pin + lc = shape.lc() + rc = shape.rc() + points = [vector(lc.x + offset, lc.y), + vector(rc.x - offset, rc.y)] + for p in points: + x_values.append(p.x) + y_values.append(p.y) + else: # Square-like pin + center = shape.center() + x_values.append(center.x) + y_values.append(center.y) + # Add corners for blockages for blockage in self.graph_blockages: ll, ur = blockage.rect x_values.extend([ll.x - offset, ur.x + offset]) @@ -61,7 +89,7 @@ class navigation_graph: for point in hanan_points.copy(): for blockage in self.graph_blockages: ll, ur = blockage.rect - if self.router.get_zindex(blockage.lpp) == point.z and is_in_region(point, blockage.rect): + if self.is_on_same_layer(point, blockage) and is_in_region(point, blockage): hanan_points.remove(point) break @@ -116,30 +144,37 @@ class navigation_graph: """ # Find source and target nodes - source_center = source.center() - source_center = vector3d(source_center.x, source_center.y, self.router.get_zindex(source.lpp)) - target_center = target.center() - target_center = vector3d(target_center.x, target_center.y, self.router.get_zindex(target.lpp)) + sources = [] for node in self.nodes: - if node.center == source_center: - source = node - if node.center == target_center: - target = node + if self.is_on_same_layer(node.center, source) and is_in_region(node.center, source): + sources.append(node) + targets = [] + for node in self.nodes: + if self.is_on_same_layer(node.center, target) and is_in_region(node.center, target): + targets.append(node) # Heuristic function to calculate the scores - h = lambda node: target.center.distance(node.center) + abs(target.center.z - node.center.z) + def h(node): + """ Return the estimated distance to closest target. """ + min_dist = float("inf") + for t in targets: + dist = t.center.distance(node.center) + abs(t.center.z - node.center.z) + if dist < min_dist: + min_dist = dist + return min_dist + # Initialize data structures to be used for A* search 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)) + # Initialize score values for the source nodes + for node in sources: + g_scores[node.id] = 0 + f_scores[node.id] = h(node) + heapq.heappush(queue, (f_scores[node.id], node.id, node)) # Run the A* algorithm while len(queue) > 0: @@ -152,7 +187,7 @@ class navigation_graph: close_set.add(current) # Check if we've reached the target - if current == target: + if current in targets: path = [] while current.id in came_from: path.append(current) diff --git a/compiler/router/navigation_node.py b/compiler/router/navigation_node.py index e8acdd9b..21b10757 100644 --- a/compiler/router/navigation_node.py +++ b/compiler/router/navigation_node.py @@ -24,6 +24,12 @@ class navigation_node: self.neighbors = [] + def __lt__(self, other): + """ """ + + return self.center < other.center + + def add_neighbor(self, node): """ Connect two nodes. """ diff --git a/compiler/router/navigation_router.py b/compiler/router/navigation_router.py index a6228d43..01147a43 100644 --- a/compiler/router/navigation_router.py +++ b/compiler/router/navigation_router.py @@ -52,8 +52,17 @@ class navigation_router(router_tech): self.find_blockages() # Create the navigation graph - pin_iter = iter(self.pins["vdd"]) + # TODO: Remove this part later and route all pins + vdds = list(self.pins["vdd"]) + vdds.sort() + pin_iter = iter(vdds) vdd_0 = next(pin_iter) + next(pin_iter) + next(pin_iter) + next(pin_iter) + next(pin_iter) + next(pin_iter) + next(pin_iter) vdd_1 = next(pin_iter) self.nav = navigation_graph(self) self.nav.create_graph(vdd_0, vdd_1) @@ -62,7 +71,11 @@ class navigation_router(router_tech): path = self.nav.find_shortest_path(vdd_0, vdd_1) # Create the path shapes on layout - self.add_path(path) + if path: + self.add_path(path) + debug.info(0, "Successfully routed") + else: + debug.info(0, "No path was found!") self.write_debug_gds(gds_name="after.gds", source=vdd_0, target=vdd_1) @@ -101,16 +114,17 @@ class navigation_router(router_tech): rect = [ll, ur] new_shape = pin_layout("blockage{}".format(len(self.blockages)), rect, - lpp).inflated_pin(multiple=1) + lpp) # 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): + new_shape = new_shape.inflated_pin(multiple=1) self.blockages.append(new_shape) def pin_contains(self, shape): for pin in self.all_pins: - if pin.contains(shape): + if pin.contains(shape) or shape.contains(pin): return True return False @@ -118,17 +132,10 @@ class navigation_router(router_tech): 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. """ - - if a.center.x == b.center.x and a.center.y == b.center.y: - self.design.add_via_center(self.layers, vector(a.center.x, b.center.y)) - else: - self.design.add_path(self.get_layer(a.center.z), [a.center, b.center]) + coordinates = [x.center for x in path] + self.design.add_route(layers=self.layers, + coordinates=coordinates, + layer_widths=self.layer_widths) def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): diff --git a/compiler/router/navigation_utils.py b/compiler/router/navigation_utils.py index 9ce2cba5..30f8460a 100644 --- a/compiler/router/navigation_utils.py +++ b/compiler/router/navigation_utils.py @@ -31,9 +31,10 @@ def is_probe_blocked(p1, p2, blockages): def is_in_region(point, region): - """""" + """ Return if a point is in the given region. """ - if is_between(region[0].x, region[1].x, point.x) and is_between(region[0].y, region[1].y, point.y): + ll, ur = region.rect + if is_between(ll.x, ur.x, point.x) and is_between(ll.y, ur.y, point.y): return True return False From e1e24f6d061d2919b33bf4bb660560fe7a7b16dd Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 29 May 2023 09:18:55 -0700 Subject: [PATCH 06/91] Rename gridless router --- compiler/modules/sram_1bank.py | 2 +- compiler/router/__init__.py | 2 +- .../{navigation_graph.py => hanan_graph.py} | 12 ++++++------ .../{navigation_node.py => hanan_node.py} | 8 ++++---- .../{navigation_router.py => hanan_router.py} | 18 +++++++++--------- .../{navigation_utils.py => hanan_utils.py} | 2 +- 6 files changed, 22 insertions(+), 22 deletions(-) rename compiler/router/{navigation_graph.py => hanan_graph.py} (95%) rename compiler/router/{navigation_node.py => hanan_node.py} (89%) rename compiler/router/{navigation_router.py => hanan_router.py} (92%) rename compiler/router/{navigation_utils.py => hanan_utils.py} (97%) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index c5049b40..c40b0d3e 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -256,7 +256,7 @@ class sram_1bank(design, verilog, lef): elif OPTS.route_supplies == "grid": from openram.router import supply_grid_router as router elif OPTS.route_supplies == "navigation": - from openram.router import navigation_router as router + from openram.router import hanan_router as router else: from openram.router import supply_tree_router as router rtr=router(layers=self.supply_stack, diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index e8da5264..2bd70c4b 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -8,4 +8,4 @@ from .signal_escape_router import * from .signal_router import * from .supply_grid_router import * from .supply_tree_router import * -from .navigation_router import * +from .hanan_router import * diff --git a/compiler/router/navigation_graph.py b/compiler/router/hanan_graph.py similarity index 95% rename from compiler/router/navigation_graph.py rename to compiler/router/hanan_graph.py index 0a1e6462..4adbd28f 100644 --- a/compiler/router/navigation_graph.py +++ b/compiler/router/hanan_graph.py @@ -10,12 +10,12 @@ from openram.base.pin_layout import pin_layout from openram.base.vector import vector from openram.base.vector3d import vector3d from .direction import direction -from .navigation_node import navigation_node -from .navigation_utils import * +from .hanan_node import hanan_node +from .hanan_utils import * -class navigation_graph: - """ This is the navigation graph created from the blockages. """ +class hanan_graph: + """ This is the Hanan graph created from the blockages. """ def __init__(self, router): @@ -30,7 +30,7 @@ class navigation_graph: def create_graph(self, layout_source, layout_target): """ Create the Hanan graph to run routing on later. """ - debug.info(0, "Creating the navigation graph for source '{0}' and target'{1}'.".format(layout_source, layout_target)) + debug.info(0, "Creating the Hanan 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 region = deepcopy(layout_source) @@ -96,7 +96,7 @@ class navigation_graph: # Create graph nodes from Hanan points self.nodes = [] for point in hanan_points: - self.nodes.append(navigation_node(point)) + self.nodes.append(hanan_node(point)) # Connect closest points avoiding blockages for i in range(len(self.nodes)): diff --git a/compiler/router/navigation_node.py b/compiler/router/hanan_node.py similarity index 89% rename from compiler/router/navigation_node.py rename to compiler/router/hanan_node.py index 21b10757..18ecf008 100644 --- a/compiler/router/navigation_node.py +++ b/compiler/router/hanan_node.py @@ -7,16 +7,16 @@ from openram.base.vector import vector from openram.base.vector3d import vector3d -class navigation_node: - """ This class represents a node on the navigation graph. """ +class hanan_node: + """ This class represents a node on the Hanan graph. """ # This is used to assign unique ids to nodes next_id = 0 def __init__(self, center): - self.id = navigation_node.next_id - navigation_node.next_id += 1 + self.id = hanan_node.next_id + hanan_node.next_id += 1 if isinstance(center, vector3d): self.center = center else: diff --git a/compiler/router/navigation_router.py b/compiler/router/hanan_router.py similarity index 92% rename from compiler/router/navigation_router.py rename to compiler/router/hanan_router.py index 01147a43..66180ee1 100644 --- a/compiler/router/navigation_router.py +++ b/compiler/router/hanan_router.py @@ -12,12 +12,12 @@ from openram.tech import GDS from openram.tech import layer as tech_layer from openram import OPTS from .router_tech import router_tech -from .navigation_graph import navigation_graph +from .hanan_graph import hanan_graph -class navigation_router(router_tech): +class hanan_router(router_tech): """ - This is the router class that implements navigation graph routing algorithm. + This is the router class that implements Hanan graph routing algorithm. """ def __init__(self, layers, design, bbox=None, pin_type=None): @@ -51,7 +51,7 @@ class navigation_router(router_tech): # Find blockages self.find_blockages() - # Create the navigation graph + # Create the hanan graph # TODO: Remove this part later and route all pins vdds = list(self.pins["vdd"]) vdds.sort() @@ -64,11 +64,11 @@ class navigation_router(router_tech): next(pin_iter) next(pin_iter) vdd_1 = next(pin_iter) - self.nav = navigation_graph(self) - self.nav.create_graph(vdd_0, vdd_1) + self.hg = hanan_graph(self) + self.hg.create_graph(vdd_0, vdd_1) # Find the shortest path from source to target - path = self.nav.find_shortest_path(vdd_0, vdd_1) + path = self.hg.find_shortest_path(vdd_0, vdd_1) # Create the path shapes on layout if path: @@ -151,9 +151,9 @@ class navigation_router(router_tech): # Display the inflated blockage if "nav" in self.__dict__: - for blockage in self.nav.graph_blockages: + for blockage in self.hg.graph_blockages: self.add_object_info(blockage, "blockage") - for node in self.nav.nodes: + for node in self.hg.nodes: offset = (node.center.x, node.center.y) self.design.add_label(text="O", layer="text", diff --git a/compiler/router/navigation_utils.py b/compiler/router/hanan_utils.py similarity index 97% rename from compiler/router/navigation_utils.py rename to compiler/router/hanan_utils.py index 30f8460a..051b7740 100644 --- a/compiler/router/navigation_utils.py +++ b/compiler/router/hanan_utils.py @@ -4,7 +4,7 @@ # All rights reserved. # """ -Utility functions for navigation router. +Utility functions for Hanan router. """ def is_probe_blocked(p1, p2, blockages): From 6079152092df8efc670c3087c8e4f397ac3ff348 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 29 May 2023 12:43:43 -0700 Subject: [PATCH 07/91] Cleanup Hanan router --- compiler/router/hanan_graph.py | 11 +++++------ compiler/router/hanan_node.py | 18 +++++++++--------- compiler/router/hanan_router.py | 7 +++++-- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 4adbd28f..c10a82f5 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -19,6 +19,7 @@ class hanan_graph: def __init__(self, router): + # This is the Hanan router that uses this graph self.router = router @@ -57,21 +58,19 @@ class hanan_graph: bc = shape.bc() points = [vector(uc.x, uc.y - offset), vector(bc.x, bc.y + offset)] - for p in points: - x_values.append(p.x) - y_values.append(p.y) elif aspect_ratio >= 2: # Fat pin lc = shape.lc() rc = shape.rc() points = [vector(lc.x + offset, lc.y), vector(rc.x - offset, rc.y)] - for p in points: - x_values.append(p.x) - y_values.append(p.y) else: # Square-like pin center = shape.center() x_values.append(center.x) y_values.append(center.y) + continue + for p in points: + x_values.append(p.x) + y_values.append(p.y) # Add corners for blockages for blockage in self.graph_blockages: ll, ur = blockage.rect diff --git a/compiler/router/hanan_node.py b/compiler/router/hanan_node.py index 18ecf008..3eecc351 100644 --- a/compiler/router/hanan_node.py +++ b/compiler/router/hanan_node.py @@ -25,25 +25,25 @@ class hanan_node: def __lt__(self, other): - """ """ + """ Override the default less than behavior. """ return self.center < other.center - def add_neighbor(self, node): + def add_neighbor(self, other): """ Connect two nodes. """ - if node not in self.neighbors: - self.neighbors.append(node) - node.neighbors.append(self) + if other not in self.neighbors: + self.neighbors.append(other) + other.neighbors.append(self) - def remove_neighbor(self, node): + def remove_neighbor(self, other): """ Disconnect two nodes. """ - if node in self.neighbors: - self.neighbors.remove(node) - node.neighbors.remove(self) + if other in self.neighbors: + self.neighbors.remove(other) + other.neighbors.remove(self) def get_edge_cost(self, other): diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 66180ee1..707b2e41 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -27,7 +27,6 @@ class hanan_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 = [] @@ -123,6 +122,10 @@ class hanan_router(router_tech): def pin_contains(self, shape): + """ + Return if this pin contains another pin or is contained by another pin. + """ + for pin in self.all_pins: if pin.contains(shape) or shape.contains(pin): return True @@ -130,7 +133,7 @@ class hanan_router(router_tech): def add_path(self, path): - """ """ + """ Add the route path to the layout. """ coordinates = [x.center for x in path] self.design.add_route(layers=self.layers, From 9f75e68a926aca1db477cc4eee05d841ebdc5bbf Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 29 May 2023 21:49:00 -0700 Subject: [PATCH 08/91] Simplify Hanan graph generation --- compiler/router/hanan_graph.py | 95 +++++++++++++--------------------- compiler/router/hanan_node.py | 14 ++--- 2 files changed, 44 insertions(+), 65 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index c10a82f5..21668bc8 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -46,8 +46,8 @@ class hanan_graph: debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) # Obtain the x and y values for Hanan grid - x_values = [] - y_values = [] + x_values = set() + y_values = set() offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 # Add the source and target pins first for shape in [layout_source, layout_target]: @@ -65,74 +65,52 @@ class hanan_graph: vector(rc.x - offset, rc.y)] else: # Square-like pin center = shape.center() - x_values.append(center.x) - y_values.append(center.y) + x_values.add(center.x) + y_values.add(center.y) continue for p in points: - x_values.append(p.x) - y_values.append(p.y) + x_values.add(p.x) + y_values.add(p.y) # Add corners for blockages for blockage in self.graph_blockages: ll, ur = blockage.rect - x_values.extend([ll.x - offset, ur.x + offset]) - y_values.extend([ll.y - offset, ur.y + offset]) + x_values.update([ll.x - offset, ur.x + offset]) + y_values.update([ll.y - offset, ur.y + offset]) + + # Sort x and y values + x_values = list(x_values) + y_values = list(y_values) + x_values.sort() + y_values.sort() # Generate Hanan points here (cartesian product of all x and y values) - hanan_points = [] + y_len = len(y_values) + self.nodes = [] for x in x_values: for y in y_values: - hanan_points.append(vector3d(x, y, 0)) - hanan_points.append(vector3d(x, y, 1)) + below_node = hanan_node([x, y, 0]) + above_node = hanan_node([x, y, 1]) + # Connect these two neighbors + below_node.add_neighbor(above_node) + # Connect down and left nodes + count = len(self.nodes) // 2 + if count % y_len: # Down + below_node.add_neighbor(self.nodes[-2]) + above_node.add_neighbor(self.nodes[-1]) + if count >= y_len: # Left + below_node.add_neighbor(self.nodes[-(y_len * 2)]) + above_node.add_neighbor(self.nodes[-(y_len * 2) + 1]) + self.nodes.append(below_node) + self.nodes.append(above_node) # Remove blocked points - for point in hanan_points.copy(): + for node in self.nodes.copy(): + point = node.center for blockage in self.graph_blockages: - ll, ur = blockage.rect if self.is_on_same_layer(point, blockage) and is_in_region(point, blockage): - hanan_points.remove(point) + node.remove_all_neighbors() + self.nodes.remove(node) break - - # Create graph nodes from Hanan points - self.nodes = [] - for point in hanan_points: - self.nodes.append(hanan_node(point)) - - # Connect closest points avoiding blockages - for i in range(len(self.nodes)): - node = self.nodes[i] - for d in direction.cardinal_offsets(): - min_dist = float("inf") - min_neighbor = None - for j in range(i + 1, len(self.nodes)): - neighbor = self.nodes[j] - # Skip if not on the same layer - if node.center.z != neighbor.center.z: - continue - # Calculate the distance vector and distance value - distance_vector = neighbor.center - node.center - distance = node.center.distance(neighbor.center) - # Skip if not connected rectilinearly - if (distance_vector.x or (distance_vector.y * d.y <= 0)) and \ - (distance_vector.y or (distance_vector.x * d.x <= 0)): - continue - # Skip if this connection is blocked by a blockage - if is_probe_blocked(node.center, neighbor.center, self.graph_blockages): - continue - if distance < min_dist: - min_dist = distance - min_neighbor = neighbor - if min_neighbor: - node.add_neighbor(min_neighbor) - - # Connect nodes that are on top of each other - for i in range(len(self.nodes)): - node = self.nodes[i] - for j in range(i + 1, len(self.nodes)): - neighbor = self.nodes[j] - if node.center.x == neighbor.center.x and \ - node.center.y == neighbor.center.y and \ - node.center.z != neighbor.center.z: - node.add_neighbor(neighbor) debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) @@ -144,12 +122,11 @@ class hanan_graph: # Find source and target nodes sources = [] + targets = [] for node in self.nodes: if self.is_on_same_layer(node.center, source) and is_in_region(node.center, source): sources.append(node) - targets = [] - for node in self.nodes: - if self.is_on_same_layer(node.center, target) and is_in_region(node.center, target): + elif self.is_on_same_layer(node.center, target) and is_in_region(node.center, target): targets.append(node) # Heuristic function to calculate the scores diff --git a/compiler/router/hanan_node.py b/compiler/router/hanan_node.py index 3eecc351..fdc86fe9 100644 --- a/compiler/router/hanan_node.py +++ b/compiler/router/hanan_node.py @@ -24,12 +24,6 @@ class hanan_node: self.neighbors = [] - def __lt__(self, other): - """ Override the default less than behavior. """ - - return self.center < other.center - - def add_neighbor(self, other): """ Connect two nodes. """ @@ -46,6 +40,14 @@ class hanan_node: other.neighbors.remove(self) + def remove_all_neighbors(self): + """ Disconnect all current neighbors. """ + + for neighbor in self.neighbors: + self.neighbors.remove(neighbor) + neighbor.neighbors.remove(self) + + def get_edge_cost(self, other): """ Get the cost of going from this node to the other node. """ From 136d4564a2c588fdf7b6001d85a773c7a557a3b7 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 30 May 2023 11:10:34 -0700 Subject: [PATCH 09/91] Use less memory when removing blocked Hanan points --- compiler/router/hanan_graph.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 21668bc8..38c5ec35 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -104,7 +104,8 @@ class hanan_graph: self.nodes.append(above_node) # Remove blocked points - for node in self.nodes.copy(): + for i in range(len(self.nodes) - 1, -1, -1): + node = self.nodes[i] point = node.center for blockage in self.graph_blockages: if self.is_on_same_layer(point, blockage) and is_in_region(point, blockage): From 2799c106bd45306f9c363bd8f447151fb7072818 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 30 May 2023 13:36:38 -0700 Subject: [PATCH 10/91] Divide long code into sub-functions --- compiler/router/hanan_graph.py | 39 ++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 38c5ec35..96f7c818 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -29,13 +29,13 @@ class hanan_graph: return point.z == self.router.get_zindex(shape.lpp) - def create_graph(self, layout_source, layout_target): + def create_graph(self, source, target): """ Create the Hanan graph to run routing on later. """ - debug.info(0, "Creating the Hanan graph for source '{0}' and target'{1}'.".format(layout_source, layout_target)) + debug.info(0, "Creating the Hanan graph for source '{0}' and target'{1}'.".format(source, target)) # Find the region to be routed and only include objects inside that region - region = deepcopy(layout_source) - region.bbox([layout_source, layout_target]) + region = deepcopy(source) + region.bbox([source, target]) debug.info(0, "Routing region is {}".format(region.rect)) # Find the blockages that are in the routing area @@ -45,12 +45,26 @@ class hanan_graph: self.graph_blockages.append(blockage) debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) + # Create the Hanan graph + x_values, y_values = self.generate_cartesian_values(source, target) + self.generate_hanan_nodes(x_values, y_values) + self.remove_blocked_nodes() + debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) + + + def generate_cartesian_values(self, source, target): + """ + Generate x and y values from all the corners of the shapes in this + region. + """ + # Obtain the x and y values for Hanan grid x_values = set() y_values = set() offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 + # Add the source and target pins first - for shape in [layout_source, layout_target]: + for shape in [source, target]: aspect_ratio = shape.width() / shape.height() # If the pin is tall or fat, add two points on the ends if aspect_ratio <= 0.5: # Tall pin @@ -71,6 +85,7 @@ class hanan_graph: for p in points: x_values.add(p.x) y_values.add(p.y) + # Add corners for blockages for blockage in self.graph_blockages: ll, ur = blockage.rect @@ -83,6 +98,15 @@ class hanan_graph: x_values.sort() y_values.sort() + return x_values, y_values + + + def generate_hanan_nodes(self, x_values, y_values): + """ + Generate all Hanan nodes using the cartesian values and connect the + orthogonal neighbors. + """ + # Generate Hanan points here (cartesian product of all x and y values) y_len = len(y_values) self.nodes = [] @@ -103,6 +127,10 @@ class hanan_graph: self.nodes.append(below_node) self.nodes.append(above_node) + + def remove_blocked_nodes(self): + """ Remove the Hanan nodes that are blocked by a blockage. """ + # Remove blocked points for i in range(len(self.nodes) - 1, -1, -1): node = self.nodes[i] @@ -112,7 +140,6 @@ class hanan_graph: node.remove_all_neighbors() self.nodes.remove(node) break - debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) def find_shortest_path(self, source, target): From e3d8ad13b222390ba552f5f4dfbe47e026f8a315 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 30 May 2023 20:09:10 -0700 Subject: [PATCH 11/91] Remove blocked Hanan node connections --- compiler/router/hanan_graph.py | 52 +++++++++++++++++++++++++++++---- compiler/router/hanan_router.py | 21 ++++++++----- compiler/router/hanan_utils.py | 23 --------------- 3 files changed, 60 insertions(+), 36 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 96f7c818..67698d6a 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -29,6 +29,32 @@ class hanan_graph: return point.z == self.router.get_zindex(shape.lpp) + def is_probe_blocked(self, p1, p2): + """ + Return if a probe sent from p1 to p2 encounters a blockage. + The probe must be sent vertically or horizontally. + This function assumes that p1 and p2 are on the same layer. + This function assumes that blockages are rectangular. + """ + + # Check if any blockage blocks this probe + for blockage in self.graph_blockages: + if not self.is_on_same_layer(p1, blockage): + continue + ll, ur = blockage.rect + right_x = ur[0] + upper_y = ur[1] + left_x = ll[0] + lower_y = ll[1] + # Check if blocked vertically + if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)): + return True + # Check if blocked horizontally + if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)): + return True + return False + + def create_graph(self, source, target): """ Create the Hanan graph to run routing on later. """ debug.info(0, "Creating the Hanan graph for source '{0}' and target'{1}'.".format(source, target)) @@ -114,16 +140,31 @@ class hanan_graph: for y in y_values: below_node = hanan_node([x, y, 0]) above_node = hanan_node([x, y, 1]) + # Connect these two neighbors below_node.add_neighbor(above_node) - # Connect down and left nodes + + # Find potential neighbor nodes + belows = [] + aboves = [] count = len(self.nodes) // 2 if count % y_len: # Down - below_node.add_neighbor(self.nodes[-2]) - above_node.add_neighbor(self.nodes[-1]) + belows.append(-2) + aboves.append(-1) if count >= y_len: # Left - below_node.add_neighbor(self.nodes[-(y_len * 2)]) - above_node.add_neighbor(self.nodes[-(y_len * 2) + 1]) + belows.append(-(y_len * 2)) + aboves.append(-(y_len * 2) + 1) + + # Add these connections if not blocked by a blockage + for i in belows: + node = self.nodes[i] + if not self.is_probe_blocked(below_node.center, node.center): + below_node.add_neighbor(node) + for i in aboves: + node = self.nodes[i] + if not self.is_probe_blocked(above_node.center, node.center): + above_node.add_neighbor(node) + self.nodes.append(below_node) self.nodes.append(above_node) @@ -136,6 +177,7 @@ class hanan_graph: node = self.nodes[i] point = node.center for blockage in self.graph_blockages: + # Remove if the node is inside a blockage if self.is_on_same_layer(point, blockage) and is_in_region(point, blockage): node.remove_all_neighbors() self.nodes.remove(node) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 707b2e41..9b4d6c75 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -116,18 +116,23 @@ class hanan_router(router_tech): lpp) # 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): + if new_shape not in self.all_pins and not new_shape.contained_by_any(self.all_pins) and not self.blockage_contains(new_shape): new_shape = new_shape.inflated_pin(multiple=1) + # Remove blockages contained by this new blockage + for i in range(len(self.blockages) - 1, -1, -1): + blockage = self.blockages[i] + if new_shape.contains(blockage): + self.blockages.remove(blockage) self.blockages.append(new_shape) - def pin_contains(self, shape): + def blockage_contains(self, shape): """ - Return if this pin contains another pin or is contained by another pin. + Return if this shape is contained by a blockage. """ - for pin in self.all_pins: - if pin.contains(shape) or shape.contains(pin): + for blockage in self.blockages: + if blockage.contains(shape): return True return False @@ -153,12 +158,12 @@ class hanan_router(router_tech): """ """ # Display the inflated blockage - if "nav" in self.__dict__: + if "hg" in self.__dict__: for blockage in self.hg.graph_blockages: - self.add_object_info(blockage, "blockage") + self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) for node in self.hg.nodes: offset = (node.center.x, node.center.y) - self.design.add_label(text="O", + self.design.add_label(text="n{}".format(node.center.z), layer="text", offset=offset) if source: diff --git a/compiler/router/hanan_utils.py b/compiler/router/hanan_utils.py index 051b7740..14092bbe 100644 --- a/compiler/router/hanan_utils.py +++ b/compiler/router/hanan_utils.py @@ -7,29 +7,6 @@ Utility functions for Hanan router. """ -def is_probe_blocked(p1, p2, blockages): - """ - Return if a probe sent from p1 to p2 encounters a blockage. - The probe must be sent vertically or horizontally. - This method assumes that blockages are rectangular. - """ - - # Check if any blockage blocks this probe - for blockage in blockages: - ll, ur = blockage.rect - right_x = ur[0] - upper_y = ur[1] - left_x = ll[0] - lower_y = ll[1] - # Check if blocked vertically - if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)): - return True - # Check if blocked horizontally - if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)): - return True - return False - - def is_in_region(point, region): """ Return if a point is in the given region. """ From 4fe5aa49e4026e73ed976f1860e771a7ffcd96ba Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Thu, 1 Jun 2023 14:24:40 -0700 Subject: [PATCH 12/91] Reorganize utility functions for Hanan router --- compiler/router/hanan_graph.py | 70 ++++++++++++++++----------------- compiler/router/hanan_probe.py | 16 ++++++++ compiler/router/hanan_router.py | 2 +- compiler/router/hanan_utils.py | 22 ----------- 4 files changed, 51 insertions(+), 59 deletions(-) create mode 100644 compiler/router/hanan_probe.py delete mode 100644 compiler/router/hanan_utils.py diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 67698d6a..eabdaa07 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -11,7 +11,7 @@ from openram.base.vector import vector from openram.base.vector3d import vector3d from .direction import direction from .hanan_node import hanan_node -from .hanan_utils import * +from .hanan_probe import hanan_probe class hanan_graph: @@ -21,12 +21,19 @@ class hanan_graph: # This is the Hanan router that uses this graph self.router = router + self.source_nodes = [] + self.target_nodes = [] - def is_on_same_layer(self, point, shape): - """ Return if the point is on the same layer as the shape. """ + def inside_shape(self, point, shape): + """ Return if the point is inside the shape. """ - return point.z == self.router.get_zindex(shape.lpp) + # Check if they're on the same layer + if point.z != self.router.get_zindex(shape.lpp): + return False + # Check if the point is inside the shape + ll, ur = shape.rect + return shape.on_segment(ll, point, ur) def is_probe_blocked(self, p1, p2): @@ -34,23 +41,13 @@ class hanan_graph: Return if a probe sent from p1 to p2 encounters a blockage. The probe must be sent vertically or horizontally. This function assumes that p1 and p2 are on the same layer. - This function assumes that blockages are rectangular. """ + probe_shape = hanan_probe(p1, p2, self.router.vert_lpp if p1.z else self.router.horiz_lpp) # Check if any blockage blocks this probe for blockage in self.graph_blockages: - if not self.is_on_same_layer(p1, blockage): - continue - ll, ur = blockage.rect - right_x = ur[0] - upper_y = ur[1] - left_x = ll[0] - lower_y = ll[1] - # Check if blocked vertically - if is_between(left_x, right_x, p1.x) and (is_between(p1.y, p2.y, upper_y) or is_between(p1.y, p2.y, lower_y)): - return True - # Check if blocked horizontally - if is_between(upper_y, lower_y, p1.y) and (is_between(p1.x, p2.x, left_x) or is_between(p1.x, p2.x, right_x)): + # Check if two shapes overlap + if blockage.overlaps(probe_shape): return True return False @@ -59,6 +56,9 @@ class hanan_graph: """ Create the Hanan graph to run routing on later. """ debug.info(0, "Creating the Hanan graph for source '{0}' and target'{1}'.".format(source, target)) + self.source = source + self.target = target + # Find the region to be routed and only include objects inside that region region = deepcopy(source) region.bbox([source, target]) @@ -72,13 +72,13 @@ class hanan_graph: debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) # Create the Hanan graph - x_values, y_values = self.generate_cartesian_values(source, target) + x_values, y_values = self.generate_cartesian_values() self.generate_hanan_nodes(x_values, y_values) self.remove_blocked_nodes() debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) - def generate_cartesian_values(self, source, target): + def generate_cartesian_values(self): """ Generate x and y values from all the corners of the shapes in this region. @@ -90,7 +90,7 @@ class hanan_graph: offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 # Add the source and target pins first - for shape in [source, target]: + for shape in [self.source, self.target]: aspect_ratio = shape.width() / shape.height() # If the pin is tall or fat, add two points on the ends if aspect_ratio <= 0.5: # Tall pin @@ -165,6 +165,13 @@ class hanan_graph: if not self.is_probe_blocked(above_node.center, node.center): above_node.add_neighbor(node) + # Save source and target nodes + for node in [below_node, above_node]: + if self.inside_shape(node.center, self.source): + self.source_nodes.append(node) + elif self.inside_shape(node.center, self.target): + self.target_nodes.append(node) + self.nodes.append(below_node) self.nodes.append(above_node) @@ -178,32 +185,23 @@ class hanan_graph: point = node.center for blockage in self.graph_blockages: # Remove if the node is inside a blockage - if self.is_on_same_layer(point, blockage) and is_in_region(point, blockage): + if self.inside_shape(point, blockage): node.remove_all_neighbors() self.nodes.remove(node) break - def find_shortest_path(self, source, target): + def find_shortest_path(self): """ Find the shortest path from the source node to target node using the A* algorithm. """ - # Find source and target nodes - sources = [] - targets = [] - for node in self.nodes: - if self.is_on_same_layer(node.center, source) and is_in_region(node.center, source): - sources.append(node) - elif self.is_on_same_layer(node.center, target) and is_in_region(node.center, target): - targets.append(node) - # Heuristic function to calculate the scores def h(node): - """ Return the estimated distance to closest target. """ + """ Return the estimated distance to the closest target. """ min_dist = float("inf") - for t in targets: + for t in self.target_nodes: dist = t.center.distance(node.center) + abs(t.center.z - node.center.z) if dist < min_dist: min_dist = dist @@ -217,7 +215,7 @@ class hanan_graph: f_scores = {} # Initialize score values for the source nodes - for node in sources: + for node in self.source_nodes: g_scores[node.id] = 0 f_scores[node.id] = h(node) heapq.heappush(queue, (f_scores[node.id], node.id, node)) @@ -227,13 +225,13 @@ class hanan_graph: # Get the closest node from the queue current = heapq.heappop(queue)[2] - # Return if already discovered + # Continue if already discovered if current in close_set: continue close_set.add(current) # Check if we've reached the target - if current in targets: + if current in self.target_nodes: path = [] while current.id in came_from: path.append(current) diff --git a/compiler/router/hanan_probe.py b/compiler/router/hanan_probe.py new file mode 100644 index 00000000..da3da979 --- /dev/null +++ b/compiler/router/hanan_probe.py @@ -0,0 +1,16 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# + +class hanan_probe: + """ + This class represents a probe sent from one point to another on Hanan graph. + This is used to mimic the pin_layout class to utilize its methods. + """ + + def __init__(self, p1, p2, lpp): + + self.rect = (p1, p2) + self.lpp = lpp diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 9b4d6c75..b609c7df 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -67,7 +67,7 @@ class hanan_router(router_tech): self.hg.create_graph(vdd_0, vdd_1) # Find the shortest path from source to target - path = self.hg.find_shortest_path(vdd_0, vdd_1) + path = self.hg.find_shortest_path() # Create the path shapes on layout if path: diff --git a/compiler/router/hanan_utils.py b/compiler/router/hanan_utils.py deleted file mode 100644 index 14092bbe..00000000 --- a/compiler/router/hanan_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz -# All rights reserved. -# -""" -Utility functions for Hanan router. -""" - -def is_in_region(point, region): - """ Return if a point is in the given region. """ - - ll, ur = region.rect - if is_between(ll.x, ur.x, point.x) and is_between(ll.y, ur.y, point.y): - return True - return False - - -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) From 021da25cd6cf51164236e6f4372dcc8640661e45 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 4 Jun 2023 08:46:59 -0700 Subject: [PATCH 13/91] Include all blockages inside the routing region --- compiler/router/hanan_graph.py | 25 +++++++++++---------- compiler/router/hanan_router.py | 13 ++++++++--- compiler/router/hanan_shape.py | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 15 deletions(-) create mode 100644 compiler/router/hanan_shape.py diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index eabdaa07..1c094fb5 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -6,7 +6,6 @@ import heapq from copy import deepcopy 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 .direction import direction @@ -61,12 +60,16 @@ class hanan_graph: # Find the region to be routed and only include objects inside that region region = deepcopy(source) - region.bbox([source, target]) + region.bbox([target]) + region = region.inflated_pin(multiple=1) debug.info(0, "Routing region is {}".format(region.rect)) # Find the blockages that are in the routing area self.graph_blockages = [] for blockage in self.router.blockages: + # Set the region's lpp to current blockage's lpp so that the + # overlaps method works + region.lpp = blockage.lpp if region.overlaps(blockage): self.graph_blockages.append(blockage) debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) @@ -80,16 +83,15 @@ class hanan_graph: def generate_cartesian_values(self): """ - Generate x and y values from all the corners of the shapes in this - region. + Generate x and y values from all the corners of the shapes in the + routing region. """ - # Obtain the x and y values for Hanan grid x_values = set() y_values = set() offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 - # Add the source and target pins first + # Add the source and target values for shape in [self.source, self.target]: aspect_ratio = shape.width() / shape.height() # If the pin is tall or fat, add two points on the ends @@ -133,8 +135,8 @@ class hanan_graph: orthogonal neighbors. """ - # Generate Hanan points here (cartesian product of all x and y values) y_len = len(y_values) + left_offset = -(y_len * 2) self.nodes = [] for x in x_values: for y in y_values: @@ -144,7 +146,7 @@ class hanan_graph: # Connect these two neighbors below_node.add_neighbor(above_node) - # Find potential neighbor nodes + # Find potential orthogonal neighbor nodes belows = [] aboves = [] count = len(self.nodes) // 2 @@ -152,8 +154,8 @@ class hanan_graph: belows.append(-2) aboves.append(-1) if count >= y_len: # Left - belows.append(-(y_len * 2)) - aboves.append(-(y_len * 2) + 1) + belows.append(left_offset) + aboves.append(left_offset + 1) # Add these connections if not blocked by a blockage for i in belows: @@ -179,7 +181,6 @@ class hanan_graph: def remove_blocked_nodes(self): """ Remove the Hanan nodes that are blocked by a blockage. """ - # Remove blocked points for i in range(len(self.nodes) - 1, -1, -1): node = self.nodes[i] point = node.center @@ -225,7 +226,7 @@ class hanan_graph: # Get the closest node from the queue current = heapq.heappop(queue)[2] - # Continue if already discovered + # Skip this node if already discovered if current in close_set: continue close_set.add(current) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index b609c7df..c27f8e7b 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -4,7 +4,6 @@ # All rights reserved. # 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 @@ -13,6 +12,7 @@ from openram.tech import layer as tech_layer from openram import OPTS from .router_tech import router_tech from .hanan_graph import hanan_graph +from .hanan_shape import hanan_shape class hanan_router(router_tech): @@ -92,7 +92,7 @@ class hanan_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - pin = pin_layout(pin_name, rect, layer) + pin = hanan_shape(pin_name, rect, layer) pin_set.add(pin) # Add these pins to the 'pins' dict self.pins[pin_name] = pin_set @@ -111,7 +111,7 @@ class hanan_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - new_shape = pin_layout("blockage{}".format(len(self.blockages)), + new_shape = hanan_shape("blockage{}".format(len(self.blockages)), rect, lpp) # If there is a rectangle that is the same in the pins, @@ -121,8 +121,15 @@ class hanan_router(router_tech): # Remove blockages contained by this new blockage for i in range(len(self.blockages) - 1, -1, -1): blockage = self.blockages[i] + # Remove the previous blockage contained by this new + # blockage if new_shape.contains(blockage): self.blockages.remove(blockage) + # Merge the previous blockage into this new blockage if + # they are aligning + elif new_shape.aligns(blockage): + new_shape.bbox([blockage]) + self.blockages.remove(blockage) self.blockages.append(new_shape) diff --git a/compiler/router/hanan_shape.py b/compiler/router/hanan_shape.py new file mode 100644 index 00000000..65dabe96 --- /dev/null +++ b/compiler/router/hanan_shape.py @@ -0,0 +1,39 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram.base.pin_layout import pin_layout + + +class hanan_shape(pin_layout): + """ + This class inherits the pin_layout class to change some of its behavior for + the Hanan router. + """ + + def __init__(self, name, rect, layer_name_pp): + + pin_layout.__init__(self, name, rect, layer_name_pp) + + + def inflated_pin(self, spacing=None, multiple=0.5): + """ Override the default inflated_pin behavior. """ + + inflated_area = self.inflate(spacing, multiple) + return hanan_shape(self.name, inflated_area, self.layer) + + + def aligns(self, other): + """ Return if the other shape aligns with this shape. """ + + # Shapes must overlap to be able to align + if not self.overlaps(other): + return False + ll, ur = self.rect + oll, our = other.rect + if ll.x == oll.x and ur.x == our.x: + return True + if ll.y == oll.y and ur.y == our.y: + return True + return False From 8f1af0ebb713ad78bb19b2f7beb26c74a31fc6f3 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 4 Jun 2023 10:56:50 -0700 Subject: [PATCH 14/91] Reduce the number of shapes on Hanan paths --- compiler/router/hanan_router.py | 61 +++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index c27f8e7b..0b03b3fa 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -53,16 +53,22 @@ class hanan_router(router_tech): # Create the hanan graph # TODO: Remove this part later and route all pins vdds = list(self.pins["vdd"]) - vdds.sort() - pin_iter = iter(vdds) - vdd_0 = next(pin_iter) - next(pin_iter) - next(pin_iter) - next(pin_iter) - next(pin_iter) - next(pin_iter) - next(pin_iter) - vdd_1 = next(pin_iter) + for pin in vdds: + ll, ur = pin.rect + if ll.x == -11 and ll.y == -8.055: + vdd_0 = pin + if ll.x == 10.557500000000001 and ll.y == 11.22: + vdd_1 = pin + #vdds.sort() + #pin_iter = iter(vdds) + #vdd_0 = next(pin_iter) + #next(pin_iter) + #next(pin_iter) + #next(pin_iter) + #next(pin_iter) + #next(pin_iter) + #next(pin_iter) + #vdd_1 = next(pin_iter) self.hg = hanan_graph(self) self.hg.create_graph(vdd_0, vdd_1) @@ -147,12 +153,45 @@ class hanan_router(router_tech): def add_path(self, path): """ Add the route path to the layout. """ - coordinates = [x.center for x in path] + coordinates = self.prepare_path(path) self.design.add_route(layers=self.layers, coordinates=coordinates, layer_widths=self.layer_widths) + def prepare_path(self, path): + """ + Remove unnecessary nodes on the path to reduce the number of shapes in + the layout. + """ + + def get_direction(a, b): + """ Return the direction of path from a to b. """ + horiz = a.center.x == b.center.x + vert = a.center.y == b.center.y + return (horiz, vert) + + last_added = path[0] + coordinates = [path[0].center] + direction = get_direction(path[0], path[1]) + candidate = path[1] + for i in range(2, len(path)): + node = path[i] + current_direction = get_direction(candidate, node) + # Skip the previous candidate since the current node follows the + # same direction + if direction == current_direction: + candidate = node + else: + last_added = candidate + coordinates.append(candidate.center) + direction = current_direction + candidate = node + if candidate.center not in coordinates: + coordinates.append(candidate.center) + return coordinates + + def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): """ """ From 48a148003ae476bd2c59c06dbb960556446677c2 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 5 Jun 2023 11:27:05 -0700 Subject: [PATCH 15/91] Include other pins as blockages in Hanan router --- compiler/router/hanan_graph.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 1c094fb5..b66e4943 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -66,7 +66,7 @@ class hanan_graph: # Find the blockages that are in the routing area self.graph_blockages = [] - for blockage in self.router.blockages: + for blockage in self.get_blockages(source.name): # Set the region's lpp to current blockage's lpp so that the # overlaps method works region.lpp = blockage.lpp @@ -81,6 +81,23 @@ class hanan_graph: debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) + def get_blockages(self, pin_name): + """ + Return all blockages for this routing region, including pins with + different name. + """ + + # Create a copy of blockages + blockages = self.router.blockages[:] + # Create a copy of pins with different name than the routed pins + for name, pins, in self.router.pins.items(): + if name == pin_name: + continue + for pin in pins: + blockages.append(deepcopy(pin).inflated_pin(multiple=1)) + return blockages + + def generate_cartesian_values(self): """ Generate x and y values from all the corners of the shapes in the From 15b4e4dbe8785ca7d119a3c38aaa9da97541a719 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 5 Jun 2023 19:33:45 -0700 Subject: [PATCH 16/91] Fix DRC spacing in Hanan router --- compiler/router/hanan_graph.py | 24 +++++++---------- compiler/router/hanan_router.py | 47 +++++++++++++++------------------ compiler/router/hanan_shape.py | 11 +++++++- 3 files changed, 40 insertions(+), 42 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index b66e4943..04c4ab31 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -8,6 +8,7 @@ from copy import deepcopy from openram import debug from openram.base.vector import vector from openram.base.vector3d import vector3d +from openram.tech import drc from .direction import direction from .hanan_node import hanan_node from .hanan_probe import hanan_probe @@ -90,11 +91,12 @@ class hanan_graph: # Create a copy of blockages blockages = self.router.blockages[:] # Create a copy of pins with different name than the routed pins - for name, pins, in self.router.pins.items(): + offset = self.router.layer_widths[0] / 2 + for name, pins in self.router.pins.items(): if name == pin_name: continue for pin in pins: - blockages.append(deepcopy(pin).inflated_pin(multiple=1)) + blockages.append(deepcopy(pin).inflated_pin(spacing=offset, multiple=1)) return blockages @@ -106,34 +108,26 @@ class hanan_graph: x_values = set() y_values = set() - offset = max(self.router.horiz_track_width, self.router.vert_track_width) / 2 # Add the source and target values for shape in [self.source, self.target]: aspect_ratio = shape.width() / shape.height() # If the pin is tall or fat, add two points on the ends if aspect_ratio <= 0.5: # Tall pin - uc = shape.uc() - bc = shape.bc() - points = [vector(uc.x, uc.y - offset), - vector(bc.x, bc.y + offset)] + points = [shape.uc(), shape.bc()] elif aspect_ratio >= 2: # Fat pin - lc = shape.lc() - rc = shape.rc() - points = [vector(lc.x + offset, lc.y), - vector(rc.x - offset, rc.y)] + points = [shape.lc(), shape.rc()] else: # Square-like pin - center = shape.center() - x_values.add(center.x) - y_values.add(center.y) - continue + points = [shape.center()] for p in points: x_values.add(p.x) y_values.add(p.y) # Add corners for blockages + offset = drc["grid"] for blockage in self.graph_blockages: ll, ur = blockage.rect + # Add minimum offset to the blockage corner nodes to prevent overlaps x_values.update([ll.x - offset, ur.x + offset]) y_values.update([ll.y - offset, ur.y + offset]) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 0b03b3fa..629a1dbd 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -109,6 +109,7 @@ class hanan_router(router_tech): """ """ debug.info(1, "Finding all blockages...") + blockages = [] for lpp in [self.vert_lpp, self.horiz_lpp]: shapes = self.layout.getAllShapes(lpp) for boundary in shapes: @@ -117,37 +118,31 @@ class hanan_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - new_shape = hanan_shape("blockage{}".format(len(self.blockages)), + new_shape = hanan_shape("blockage{}".format(len(blockages)), rect, lpp) # 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 new_shape.contained_by_any(self.all_pins) and not self.blockage_contains(new_shape): - new_shape = new_shape.inflated_pin(multiple=1) - # Remove blockages contained by this new blockage - for i in range(len(self.blockages) - 1, -1, -1): - blockage = self.blockages[i] - # Remove the previous blockage contained by this new - # blockage - if new_shape.contains(blockage): - self.blockages.remove(blockage) - # Merge the previous blockage into this new blockage if - # they are aligning - elif new_shape.aligns(blockage): - new_shape.bbox([blockage]) - self.blockages.remove(blockage) - self.blockages.append(new_shape) + if new_shape.contained_by_any(self.all_pins) or new_shape.contained_by_any(blockages): + continue + # Remove blockages contained by this new blockage + for i in range(len(blockages) - 1, -1, -1): + blockage = blockages[i] + # Remove the previous blockage contained by this new + # blockage + if new_shape.contains(blockage): + blockages.remove(blockage) + # Merge the previous blockage into this new blockage if + # they are aligning + elif new_shape.aligns(blockage): + new_shape.bbox([blockage]) + blockages.remove(blockage) + blockages.append(new_shape) - - def blockage_contains(self, shape): - """ - Return if this shape is contained by a blockage. - """ - - for blockage in self.blockages: - if blockage.contains(shape): - return True - return False + # Inflate the shapes to prevent DRC errors + offset = self.layer_widths[0] / 2 + for blockage in blockages: + self.blockages.append(blockage.inflated_pin(spacing=offset, multiple=1)) def add_path(self, path): diff --git a/compiler/router/hanan_shape.py b/compiler/router/hanan_shape.py index 65dabe96..087ab02a 100644 --- a/compiler/router/hanan_shape.py +++ b/compiler/router/hanan_shape.py @@ -4,6 +4,8 @@ # All rights reserved. # from openram.base.pin_layout import pin_layout +from openram.base.vector import vector +from openram.tech import drc class hanan_shape(pin_layout): @@ -20,7 +22,14 @@ class hanan_shape(pin_layout): def inflated_pin(self, spacing=None, multiple=0.5): """ Override the default inflated_pin behavior. """ - inflated_area = self.inflate(spacing, multiple) + if not spacing: + spacing = 0 + drc_spacing = multiple * drc("{0}_to_{0}".format(self.layer)) + spacing = vector([spacing + drc_spacing] * 2) + (ll, ur) = self.rect + newll = ll - spacing + newur = ur + spacing + inflated_area = (newll, newur) return hanan_shape(self.name, inflated_area, self.layer) From a47bc7ebee0ff3a7628bed941249a879506be5a7 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Thu, 15 Jun 2023 11:08:13 -0700 Subject: [PATCH 17/91] Prevent multiple dog-legs in non-preferred direction --- compiler/router/hanan_graph.py | 16 ++++++++++++---- compiler/router/hanan_node.py | 17 ++++++++++++++++- compiler/router/hanan_router.py | 12 +++--------- compiler/router/hanan_shape.py | 13 +++++-------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 04c4ab31..d1bd5974 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -96,7 +96,7 @@ class hanan_graph: if name == pin_name: continue for pin in pins: - blockages.append(deepcopy(pin).inflated_pin(spacing=offset, multiple=1)) + blockages.append(deepcopy(pin).inflated_pin(multiple=1, extra_spacing=offset)) return blockages @@ -110,13 +110,16 @@ class hanan_graph: y_values = set() # Add the source and target values + offset = self.router.layer_widths[0] / 2 + x_offset = vector(offset, 0) + y_offset = vector(0, offset) for shape in [self.source, self.target]: aspect_ratio = shape.width() / shape.height() # If the pin is tall or fat, add two points on the ends if aspect_ratio <= 0.5: # Tall pin - points = [shape.uc(), shape.bc()] + points = [shape.bc() + y_offset, shape.uc() - y_offset] elif aspect_ratio >= 2: # Fat pin - points = [shape.lc(), shape.rc()] + points = [shape.lc() + x_offset, shape.rc() - x_offset] else: # Square-like pin points = [shape.center()] for p in points: @@ -251,9 +254,14 @@ class hanan_graph: path.append(current) return path + # Get the previous node to better calculate the next costs + prev_node = None + if current.id in came_from: + prev_node = came_from[current.id] + # Update neighbor scores for node in current.neighbors: - tentative_score = current.get_edge_cost(node) + g_scores[current.id] + tentative_score = current.get_edge_cost(node, prev_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 diff --git a/compiler/router/hanan_node.py b/compiler/router/hanan_node.py index fdc86fe9..16c2e124 100644 --- a/compiler/router/hanan_node.py +++ b/compiler/router/hanan_node.py @@ -5,6 +5,7 @@ # from openram.base.vector import vector from openram.base.vector3d import vector3d +from openram.tech import drc class hanan_node: @@ -48,14 +49,28 @@ class hanan_node: neighbor.neighbors.remove(self) - def get_edge_cost(self, other): + def get_direction(self, b): + """ Return the direction of path from a to b. """ + + horiz = self.center.x == b.center.x + vert = self.center.y == b.center.y + return (horiz, vert) + + + def get_edge_cost(self, other, prev_node=None): """ Get the cost of going from this node to the other node. """ if other in self.neighbors: is_vertical = self.center.x == other.center.x layer_dist = self.center.distance(other.center) + # Edge is on non-preferred direction if is_vertical != bool(self.center.z): layer_dist *= 2 + # Wire bends towards non-preferred direction + # NOTE: Adding a fixed cost prevents multiple dog-legs in + # non-preferred direction + if prev_node and self.get_direction(prev_node) != self.get_direction(other): + layer_dist += drc["grid"] via_dist = abs(self.center.z - other.center.z) * 2 return layer_dist + via_dist return float("inf") diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 629a1dbd..10d27042 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -142,7 +142,7 @@ class hanan_router(router_tech): # Inflate the shapes to prevent DRC errors offset = self.layer_widths[0] / 2 for blockage in blockages: - self.blockages.append(blockage.inflated_pin(spacing=offset, multiple=1)) + self.blockages.append(blockage.inflated_pin(multiple=1, extra_spacing=offset)) def add_path(self, path): @@ -160,19 +160,13 @@ class hanan_router(router_tech): the layout. """ - def get_direction(a, b): - """ Return the direction of path from a to b. """ - horiz = a.center.x == b.center.x - vert = a.center.y == b.center.y - return (horiz, vert) - last_added = path[0] coordinates = [path[0].center] - direction = get_direction(path[0], path[1]) + direction = path[0].get_direction(path[1]) candidate = path[1] for i in range(2, len(path)): node = path[i] - current_direction = get_direction(candidate, node) + current_direction = node.get_direction(candidate) # Skip the previous candidate since the current node follows the # same direction if direction == current_direction: diff --git a/compiler/router/hanan_shape.py b/compiler/router/hanan_shape.py index 087ab02a..c76f61dc 100644 --- a/compiler/router/hanan_shape.py +++ b/compiler/router/hanan_shape.py @@ -19,16 +19,13 @@ class hanan_shape(pin_layout): pin_layout.__init__(self, name, rect, layer_name_pp) - def inflated_pin(self, spacing=None, multiple=0.5): + def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0): """ Override the default inflated_pin behavior. """ - if not spacing: - spacing = 0 - drc_spacing = multiple * drc("{0}_to_{0}".format(self.layer)) - spacing = vector([spacing + drc_spacing] * 2) - (ll, ur) = self.rect - newll = ll - spacing - newur = ur + spacing + ll, ur = self.inflate(spacing, multiple) + extra = vector([extra_spacing] * 2) + newll = ll - extra + newur = ur + extra inflated_area = (newll, newur) return hanan_shape(self.name, inflated_area, self.layer) From 5bf629f3e5854b3408afa7f2dccf4f8d41abc020 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 28 Jun 2023 20:55:49 -0700 Subject: [PATCH 18/91] Prevent DRC violations for vdd and gnd pins --- compiler/router/hanan_graph.py | 27 ++++++--------------------- compiler/router/hanan_router.py | 5 +++++ compiler/router/hanan_shape.py | 8 +++++--- 3 files changed, 16 insertions(+), 24 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index d1bd5974..fa92e4bf 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -47,7 +47,8 @@ class hanan_graph: # Check if any blockage blocks this probe for blockage in self.graph_blockages: # Check if two shapes overlap - if blockage.overlaps(probe_shape): + # Inflated blockages of pins don't block probes + if blockage.overlaps(probe_shape) and blockage.name != self.source.name: return True return False @@ -67,7 +68,7 @@ class hanan_graph: # Find the blockages that are in the routing area self.graph_blockages = [] - for blockage in self.get_blockages(source.name): + for blockage in self.router.blockages: # Set the region's lpp to current blockage's lpp so that the # overlaps method works region.lpp = blockage.lpp @@ -82,24 +83,6 @@ class hanan_graph: debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) - def get_blockages(self, pin_name): - """ - Return all blockages for this routing region, including pins with - different name. - """ - - # Create a copy of blockages - blockages = self.router.blockages[:] - # Create a copy of pins with different name than the routed pins - offset = self.router.layer_widths[0] / 2 - for name, pins in self.router.pins.items(): - if name == pin_name: - continue - for pin in pins: - blockages.append(deepcopy(pin).inflated_pin(multiple=1, extra_spacing=offset)) - return blockages - - def generate_cartesian_values(self): """ Generate x and y values from all the corners of the shapes in the @@ -200,7 +183,9 @@ class hanan_graph: point = node.center for blockage in self.graph_blockages: # Remove if the node is inside a blockage - if self.inside_shape(point, blockage): + # If the blockage is an inflated routable, remove if outside + # the routable shape + if self.inside_shape(point, blockage) and (blockage.name != self.source.name or not self.inside_shape(point, blockage.inflated_from)): node.remove_all_neighbors() self.nodes.remove(node) break diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 10d27042..14ecb92e 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -144,6 +144,11 @@ class hanan_router(router_tech): for blockage in blockages: self.blockages.append(blockage.inflated_pin(multiple=1, extra_spacing=offset)) + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(pin.inflated_pin(multiple=1, extra_spacing=offset, keep_link=True)) + def add_path(self, path): """ Add the route path to the layout. """ diff --git a/compiler/router/hanan_shape.py b/compiler/router/hanan_shape.py index c76f61dc..39af5bbf 100644 --- a/compiler/router/hanan_shape.py +++ b/compiler/router/hanan_shape.py @@ -14,12 +14,14 @@ class hanan_shape(pin_layout): the Hanan router. """ - def __init__(self, name, rect, layer_name_pp): + def __init__(self, name, rect, layer_name_pp, inflated_from=None): pin_layout.__init__(self, name, rect, layer_name_pp) + self.inflated_from = inflated_from - def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0): + + def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0, keep_link=False): """ Override the default inflated_pin behavior. """ ll, ur = self.inflate(spacing, multiple) @@ -27,7 +29,7 @@ class hanan_shape(pin_layout): newll = ll - extra newur = ur + extra inflated_area = (newll, newur) - return hanan_shape(self.name, inflated_area, self.layer) + return hanan_shape(self.name, inflated_area, self.layer, self if keep_link else None) def aligns(self, other): From 78be525ea02c91a47736e5674c8726f68438ea0f Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 1 Jul 2023 16:14:56 -0700 Subject: [PATCH 19/91] Use minimum spanning tree to route same type of pins together --- compiler/router/hanan_router.py | 92 +++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 14ecb92e..d222b37e 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -50,39 +50,21 @@ class hanan_router(router_tech): # Find blockages self.find_blockages() - # Create the hanan graph - # TODO: Remove this part later and route all pins - vdds = list(self.pins["vdd"]) - for pin in vdds: - ll, ur = pin.rect - if ll.x == -11 and ll.y == -8.055: - vdd_0 = pin - if ll.x == 10.557500000000001 and ll.y == 11.22: - vdd_1 = pin - #vdds.sort() - #pin_iter = iter(vdds) - #vdd_0 = next(pin_iter) - #next(pin_iter) - #next(pin_iter) - #next(pin_iter) - #next(pin_iter) - #next(pin_iter) - #next(pin_iter) - #vdd_1 = next(pin_iter) - self.hg = hanan_graph(self) - self.hg.create_graph(vdd_0, vdd_1) + # Route vdd and gnd + for pin_name in [vdd_name, gnd_name]: + pins = self.pins[pin_name] + # Create minimum spanning tree connecting all pins + for source, target in self.get_mst_pairs(list(pins)): + # Create the hanan graph + hg = hanan_graph(self) + hg.create_graph(source, target) + # Find the shortest path from source to target + path = hg.find_shortest_path() + debug.check(path is not None, "Couldn't route {} to {}".format(source, target)) + # Create the path shapes on layout + self.add_path(path) - # Find the shortest path from source to target - path = self.hg.find_shortest_path() - - # Create the path shapes on layout - if path: - self.add_path(path) - debug.info(0, "Successfully routed") - else: - debug.info(0, "No path was found!") - - self.write_debug_gds(gds_name="after.gds", source=vdd_0, target=vdd_1) + self.write_debug_gds(gds_name="after.gds") def find_pins(self, pin_name): @@ -150,6 +132,52 @@ class hanan_router(router_tech): self.blockages.append(pin.inflated_pin(multiple=1, extra_spacing=offset, keep_link=True)) + def get_mst_pairs(self, pins): + """ + Return the pin pairs from the minimum spanning tree in a graph that + connects all pins together. + """ + + pin_count = len(pins) + + # Create an adjacency matrix that connects all pins + edges = [[0] * pin_count for i in range(pin_count)] + for i in range(pin_count): + for j in range(pin_count): + if i == j: + continue + edges[i][j] = pins[i].distance(pins[j]) + + pin_connected = [False] * pin_count + pin_connected[0] = True + + # Add the minimum cost edge in each iteration (Prim's) + mst_pairs = [] + for i in range(pin_count - 1): + min_cost = float("inf") + s = 0 + t = 0 + # Iterate over already connected pins + for m in range(pin_count): + # Skip if not connected + if not pin_connected[m]: + continue + # Iterate over this pin's neighbors + for n in range(pin_count): + # Skip if already connected or isn't a neighbor + if pin_connected[n] or edges[m][n] == 0: + continue + # Choose this edge if it's better the the current one + if edges[m][n] < min_cost: + min_cost = edges[m][n] + s = m + t = n + pin_connected[t] = True + mst_pairs.append((pins[s], pins[t])) + + return mst_pairs + + def add_path(self, path): """ Add the route path to the layout. """ From 0938e7ec9a96e4386154e0db51bf79da1934e7a3 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 3 Jul 2023 13:34:27 -0700 Subject: [PATCH 20/91] Fix probes not being blocked correctly --- compiler/router/hanan_probe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/router/hanan_probe.py b/compiler/router/hanan_probe.py index da3da979..87788227 100644 --- a/compiler/router/hanan_probe.py +++ b/compiler/router/hanan_probe.py @@ -12,5 +12,5 @@ class hanan_probe: def __init__(self, p1, p2, lpp): - self.rect = (p1, p2) + self.rect = (p1.min(p2), p1.max(p2)) self.lpp = lpp From bb35ac2f905b6ca1a90da42c4cfc8a58124b1f98 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 3 Jul 2023 14:04:26 -0700 Subject: [PATCH 21/91] Include new wires while routing the pins --- compiler/router/hanan_graph.py | 12 ++-- compiler/router/hanan_router.py | 105 +++++++++++++++++++++++--------- 2 files changed, 84 insertions(+), 33 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index fa92e4bf..02b17aa5 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -48,7 +48,7 @@ class hanan_graph: for blockage in self.graph_blockages: # Check if two shapes overlap # Inflated blockages of pins don't block probes - if blockage.overlaps(probe_shape) and blockage.name != self.source.name: + if blockage.overlaps(probe_shape) and (blockage.name != self.source.name or not blockage.inflated_from.overlaps(probe_shape)): return True return False @@ -69,6 +69,9 @@ class hanan_graph: # Find the blockages that are in the routing area self.graph_blockages = [] for blockage in self.router.blockages: + # FIXME: Include pins as blockages as well to prevent DRC errors + if blockage.name == self.source.name: + continue # Set the region's lpp to current blockage's lpp so that the # overlaps method works region.lpp = blockage.lpp @@ -92,10 +95,9 @@ class hanan_graph: x_values = set() y_values = set() - # Add the source and target values - offset = self.router.layer_widths[0] / 2 - x_offset = vector(offset, 0) - y_offset = vector(0, offset) + # Add inner values for blockages of the routed type + x_offset = vector(self.router.offset, 0) + y_offset = vector(0, self.router.offset) for shape in [self.source, self.target]: aspect_ratio = shape.width() / shape.height() # If the pin is tall or fat, add two points on the ends diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index d222b37e..439d113b 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -31,6 +31,9 @@ class hanan_router(router_tech): self.all_pins = set() self.blockages = [] + # Set the offset here + self.offset = self.layer_widths[0] / 2 + def route(self, vdd_name="vdd", gnd_name="gnd"): """ Route the given pins in the given order. """ @@ -38,10 +41,7 @@ class hanan_router(router_tech): self.write_debug_gds(gds_name="before.gds") # Prepare gdsMill to find pins and blockages - self.design.gds_write(self.gds_filename) - self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) - self.reader = gdsMill.Gds2reader(self.layout) - self.reader.loadFromFile(self.gds_filename) + self.prepare_gds_reader() # Find pins to be routed self.find_pins(vdd_name) @@ -50,23 +50,40 @@ class hanan_router(router_tech): # Find blockages self.find_blockages() + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(pin.inflated_pin(multiple=1, extra_spacing=self.offset, keep_link=True)) + # Route vdd and gnd for pin_name in [vdd_name, gnd_name]: pins = self.pins[pin_name] # Create minimum spanning tree connecting all pins for source, target in self.get_mst_pairs(list(pins)): - # Create the hanan graph + # Create the Hanan graph hg = hanan_graph(self) hg.create_graph(source, target) # Find the shortest path from source to target path = hg.find_shortest_path() - debug.check(path is not None, "Couldn't route {} to {}".format(source, target)) + debug.check(path is not None, "Couldn't route from {} to {}".format(source, target)) # Create the path shapes on layout self.add_path(path) + # Find the recently added shapes + self.prepare_gds_reader() + self.find_blockages(pin_name) self.write_debug_gds(gds_name="after.gds") + def prepare_gds_reader(self): + """ """ + + self.design.gds_write(self.gds_filename) + self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) + self.reader = gdsMill.Gds2reader(self.layout) + self.reader.loadFromFile(self.gds_filename) + + def find_pins(self, pin_name): """ """ debug.info(1, "Finding all pins for {}".format(pin_name)) @@ -80,16 +97,28 @@ class hanan_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - pin = hanan_shape(pin_name, rect, layer) - pin_set.add(pin) + new_pin = hanan_shape(pin_name, rect, layer) + # Skip this pin if it's contained by another pin of the same type + if new_pin.contained_by_any(pin_set): + continue + # Remove any previous pin of the same type contained by this new pin + for pin in list(pin_set): + if new_pin.contains(pin): + pin_set.remove(pin) + elif new_pin.aligns(pin): + new_pin.bbox([pin]) + pin_set.remove(pin) + pin_set.add(new_pin) # Add these pins to the 'pins' dict self.pins[pin_name] = pin_set self.all_pins.update(pin_set) - def find_blockages(self): + def find_blockages(self, shape_name=None): """ """ - debug.info(1, "Finding all blockages...") + debug.info(1, "Finding blockages...") + + prev_blockages = self.blockages[:] blockages = [] for lpp in [self.vert_lpp, self.horiz_lpp]: @@ -100,12 +129,16 @@ class hanan_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - new_shape = hanan_shape("blockage{}".format(len(blockages)), - rect, - lpp) + if shape_name is None: + name = "blockage{}".format(len(blockages)) + else: + name = shape_name + new_shape = hanan_shape(name, rect, lpp) # If there is a rectangle that is the same in the pins, # it isn't a blockage - if new_shape.contained_by_any(self.all_pins) or new_shape.contained_by_any(blockages): + if new_shape.contained_by_any(self.all_pins) or \ + new_shape.contained_by_any(prev_blockages) or \ + new_shape.contained_by_any(blockages): continue # Remove blockages contained by this new blockage for i in range(len(blockages) - 1, -1, -1): @@ -122,14 +155,24 @@ class hanan_router(router_tech): blockages.append(new_shape) # Inflate the shapes to prevent DRC errors - offset = self.layer_widths[0] / 2 for blockage in blockages: - self.blockages.append(blockage.inflated_pin(multiple=1, extra_spacing=offset)) - - # Add vdd and gnd pins as blockages as well - # NOTE: This is done to make vdd and gnd pins DRC-safe - for pin in self.all_pins: - self.blockages.append(pin.inflated_pin(multiple=1, extra_spacing=offset, keep_link=True)) + self.blockages.append(blockage.inflated_pin(multiple=1, + extra_spacing=self.offset, + keep_link=shape_name is not None)) + # Remove blockages contained by this new blockage + for i in range(len(prev_blockages) - 1, -1, -1): + prev_blockage = prev_blockages[i] + # Remove the previous blockage contained by this new + # blockage + if blockage.contains(prev_blockage): + prev_blockages.remove(prev_blockage) + self.blockages.remove(prev_blockage) + # Merge the previous blockage into this new blockage if + # they are aligning + elif blockage.aligns(prev_blockage): + blockage.bbox([prev_blockage]) + prev_blockages.remove(prev_blockage) + self.blockages.remove(prev_blockage) def get_mst_pairs(self, pins): @@ -214,26 +257,32 @@ class hanan_router(router_tech): return coordinates - def write_debug_gds(self, gds_name="debug_route.gds", source=None, target=None): + def write_debug_gds(self, gds_name="debug_route.gds", hg=None, source=None, target=None): """ """ - self.add_router_info(source, target) + self.add_router_info(hg, source, target) self.design.gds_write(gds_name) self.del_router_info() - def add_router_info(self, source=None, target=None): + def add_router_info(self, hg=None, source=None, target=None): """ """ # Display the inflated blockage - if "hg" in self.__dict__: - for blockage in self.hg.graph_blockages: - self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) - for node in self.hg.nodes: + if hg: + for blockage in self.blockages: + if blockage in hg.graph_blockages: + self.add_object_info(blockage, "blockage{}++[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) + else: + self.add_object_info(blockage, "blockage{}[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) + for node in hg.nodes: offset = (node.center.x, node.center.y) self.design.add_label(text="n{}".format(node.center.z), layer="text", offset=offset) + else: + for blockage in self.blockages: + self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) if source: self.add_object_info(source, "source") if target: From 4a61874888069ffdc623401d50368b22f6a256ec Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 9 Jul 2023 18:53:21 -0700 Subject: [PATCH 22/91] Add supply ring pins around the layout area --- compiler/router/hanan_router.py | 101 ++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index 439d113b..b1fa0376 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -26,6 +26,7 @@ class hanan_router(router_tech): self.layers = layers self.design = design + self.pin_type = pin_type self.gds_filename = OPTS.openram_temp + "temp.gds" self.pins = {} self.all_pins = set() @@ -40,6 +41,9 @@ class hanan_router(router_tech): #debug.info(1, "Running router for {}...".format(pins)) self.write_debug_gds(gds_name="before.gds") + self.vdd_name = vdd_name + self.gnd_name = gnd_name + # Prepare gdsMill to find pins and blockages self.prepare_gds_reader() @@ -50,6 +54,18 @@ class hanan_router(router_tech): # Find blockages self.find_blockages() + # Add side pins + if self.pin_type in ["top", "bottom", "right", "left"]: + self.add_side_pin(vdd_name) + self.add_side_pin(gnd_name) + elif self.pin_type == "ring": + self.add_ring_pin(vdd_name) + self.add_ring_pin(gnd_name) + else: + debug.warning("Side supply pins aren't created.") + + self.write_debug_gds(gds_name="after.gds") + # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: @@ -175,6 +191,91 @@ class hanan_router(router_tech): self.blockages.remove(prev_blockage) + def add_side_pin(self, pin_name, side, width=3, bbox=None): + """ Add supply pin to one side of the layout. """ + + vertical = side in ["left", "right"] + inner = pin_name == self.gnd_name + + if bbox is None: + bbox = self.design.get_bbox() + ll, ur = bbox + + # Calculate wires' wideness + wideness = self.track_wire * width + self.track_space * (width - 1) + + # Calculate the offset for the inner ring + if inner: + margin = wideness * 2 + else: + margin = 0 + + if side == "top": + offset = vector(ll.x + margin, ur.y - wideness - margin) + elif side == "bottom": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "left": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "right": + offset = vector(ur.x - wideness - margin, ll.y + margin) + + shape = ur - ll + + if vertical: + shape_width = wideness + shape_height = shape.y + else: + shape_width = shape.x + shape_height = wideness + + if inner: + if vertical: + shape_height -= margin * 2 + else: + shape_width -= margin * 2 + + # Add this new pin + pin = self.design.add_layout_pin(text=pin_name, + layer=self.get_layer(int(vertical)), + offset=offset, + width=shape_width, + height=shape_height) + return pin + + + def add_ring_pin(self, pin_name, width=3): + """ Add suply ring to the layout. """ + + bbox = self.design.get_bbox() + + # Add side pins + new_pins = [] + for side in ["top", "bottom", "right", "left"]: + new_shape = self.add_side_pin(pin_name, side, width, bbox) + ll, ur = new_shape.rect + rect = [ll, ur] + layer = self.get_layer(side in ["left", "right"]) + new_pin = hanan_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + new_pins.append(new_pin) + + # Add vias to the corners + shift = self.track_wire + self.track_space + half_wide = self.track_wire / 2 + for i in range(4): + ll, ur = new_pins[i].rect + if i % 2: + top_left = vector(ur.x - (width - 1) * shift - half_wide, ll.y + (width - 1) * shift + half_wide) + else: + top_left = vector(ll.x + half_wide, ur.y - half_wide) + for j in range(width): + for k in range(width): + offset = vector(top_left.x + j * shift, top_left.y - k * shift) + self.design.add_via_center(layers=self.layers, + offset=offset) + + def get_mst_pairs(self, pins): """ Return the pin pairs from the minimum spanning tree in a graph that From 6b0b4c2defa8217fb3176a9b3079eb3ecb4d7026 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 10 Jul 2023 09:24:16 -0700 Subject: [PATCH 23/91] Create fake pins on the ring and route others to them --- compiler/router/hanan_router.py | 57 ++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index b1fa0376..ede1af70 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -31,6 +31,8 @@ class hanan_router(router_tech): self.pins = {} self.all_pins = set() self.blockages = [] + self.new_pins = {} + self.fake_pins = [] # Set the offset here self.offset = self.layer_widths[0] / 2 @@ -152,6 +154,7 @@ class hanan_router(router_tech): new_shape = hanan_shape(name, rect, lpp) # If there is a rectangle that is the same in the pins, # it isn't a blockage + # Also ignore the new pins if new_shape.contained_by_any(self.all_pins) or \ new_shape.contained_by_any(prev_blockages) or \ new_shape.contained_by_any(blockages): @@ -191,7 +194,7 @@ class hanan_router(router_tech): self.blockages.remove(prev_blockage) - def add_side_pin(self, pin_name, side, width=3, bbox=None): + def add_side_pin(self, pin_name, side, width=3, num_connects=4, bbox=None): """ Add supply pin to one side of the layout. """ vertical = side in ["left", "right"] @@ -235,15 +238,38 @@ class hanan_router(router_tech): shape_width -= margin * 2 # Add this new pin + layer = self.get_layer(int(vertical)) pin = self.design.add_layout_pin(text=pin_name, - layer=self.get_layer(int(vertical)), + layer=layer, offset=offset, width=shape_width, height=shape_height) - return pin + # Add fake pins on this new pin evenly + fake_pins = [] + if vertical: + space = (shape_height - (2 * wideness) - num_connects * self.track_wire) / (num_connects + 1) + start_offset = vector(offset.x, offset.y + wideness) + else: + space = (shape_width - (2 * wideness) - num_connects * self.track_wire) / (num_connects + 1) + start_offset = vector(offset.x + wideness, offset.y) + for i in range(1, num_connects + 1): + if vertical: + offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) + ll = vector(offset.x, offset.y - self.track_wire) + ur = vector(offset.x + wideness, offset.y) + else: + offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y) + ll = vector(offset.x - self.track_wire, offset.y) + ur = vector(offset.x, offset.y + wideness) + rect = [ll, ur] + fake_pin = hanan_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + fake_pins.append(fake_pin) + return pin, fake_pins - def add_ring_pin(self, pin_name, width=3): + def add_ring_pin(self, pin_name, width=3, num_connects=4): """ Add suply ring to the layout. """ bbox = self.design.get_bbox() @@ -251,7 +277,7 @@ class hanan_router(router_tech): # Add side pins new_pins = [] for side in ["top", "bottom", "right", "left"]: - new_shape = self.add_side_pin(pin_name, side, width, bbox) + new_shape, fake_pins = self.add_side_pin(pin_name, side, width, num_connects, bbox) ll, ur = new_shape.rect rect = [ll, ur] layer = self.get_layer(side in ["left", "right"]) @@ -259,6 +285,8 @@ class hanan_router(router_tech): rect=rect, layer_name_pp=layer) new_pins.append(new_pin) + self.pins[pin_name].update(fake_pins) + self.fake_pins.extend(fake_pins) # Add vias to the corners shift = self.track_wire + self.track_space @@ -275,6 +303,13 @@ class hanan_router(router_tech): self.design.add_via_center(layers=self.layers, offset=offset) + # Save side pins for routing + self.new_pins[pin_name] = new_pins + for pin in new_pins: + self.blockages.append(pin.inflated_pin(multiple=1, + extra_spacing=self.offset, + keep_link=True)) + def get_mst_pairs(self, pins): """ @@ -288,8 +323,12 @@ class hanan_router(router_tech): edges = [[0] * pin_count for i in range(pin_count)] for i in range(pin_count): for j in range(pin_count): + # Skip if they're the same pin if i == j: continue + # Skip if both pins are fake + if pins[i] in self.fake_pins and pins[j] in self.fake_pins: + continue edges[i][j] = pins[i].distance(pins[j]) pin_connected = [False] * pin_count @@ -358,6 +397,12 @@ class hanan_router(router_tech): return coordinates + def get_new_pins(self, name): + """ """ + + return self.new_pins[name] + + def write_debug_gds(self, gds_name="debug_route.gds", hg=None, source=None, target=None): """ """ @@ -384,6 +429,8 @@ class hanan_router(router_tech): else: for blockage in self.blockages: self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) + for pin in self.fake_pins: + self.add_object_info(pin, "fake") if source: self.add_object_info(source, "source") if target: From 813a67fea90f47e07dfdc7d75cc383d48b0b5c1b Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Thu, 13 Jul 2023 11:29:51 -0700 Subject: [PATCH 24/91] Add more comments for gridless router --- compiler/router/hanan_graph.py | 8 +++---- compiler/router/hanan_router.py | 41 +++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/compiler/router/hanan_graph.py b/compiler/router/hanan_graph.py index 02b17aa5..d82f1fec 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/hanan_graph.py @@ -55,7 +55,7 @@ class hanan_graph: def create_graph(self, source, target): """ Create the Hanan graph to run routing on later. """ - debug.info(0, "Creating the Hanan graph for source '{0}' and target'{1}'.".format(source, target)) + debug.info(2, "Creating the Hanan graph for source '{}' and target'{}'.".format(source, target)) self.source = source self.target = target @@ -64,7 +64,7 @@ class hanan_graph: region = deepcopy(source) region.bbox([target]) region = region.inflated_pin(multiple=1) - debug.info(0, "Routing region is {}".format(region.rect)) + debug.info(3, "Routing region is {}".format(region.rect)) # Find the blockages that are in the routing area self.graph_blockages = [] @@ -77,13 +77,13 @@ class hanan_graph: region.lpp = blockage.lpp if region.overlaps(blockage): self.graph_blockages.append(blockage) - debug.info(0, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) + debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) # Create the Hanan graph x_values, y_values = self.generate_cartesian_values() self.generate_hanan_nodes(x_values, y_values) self.remove_blocked_nodes() - debug.info(0, "Number of nodes in the routing graph: {}".format(len(self.nodes))) + debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes))) def generate_cartesian_values(self): diff --git a/compiler/router/hanan_router.py b/compiler/router/hanan_router.py index ede1af70..db508773 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/hanan_router.py @@ -22,16 +22,29 @@ class hanan_router(router_tech): def __init__(self, layers, design, bbox=None, pin_type=None): + # `router_tech` contains tech constants for the router router_tech.__init__(self, layers, route_track_width=1) + # Layers that can be used for routing self.layers = layers + # This is the `hierarchy_layout` object self.design = design + # Side supply pin type + # (can be "top", "bottom", "right", "left", and "ring") self.pin_type = pin_type + # Temporary GDSII file name to find pins and blockages self.gds_filename = OPTS.openram_temp + "temp.gds" + # Dictionary for vdd and gnd pins self.pins = {} + # Set of all the pins self.all_pins = set() + # This is all the blockages including the pins. The graph class handles + # pins as blockages while considering their routability self.blockages = [] + # New pins are the side supply pins self.new_pins = {} + # Fake pins are imaginary pins on the side supply pins to route other + # pins to them self.fake_pins = [] # Set the offset here @@ -40,9 +53,11 @@ class hanan_router(router_tech): def route(self, vdd_name="vdd", gnd_name="gnd"): """ Route the given pins in the given order. """ - #debug.info(1, "Running router for {}...".format(pins)) + debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) + # FIXME: Comment-out later self.write_debug_gds(gds_name="before.gds") + # Save pin names self.vdd_name = vdd_name self.gnd_name = gnd_name @@ -66,17 +81,20 @@ class hanan_router(router_tech): else: debug.warning("Side supply pins aren't created.") + # FIXME: Comment-out later self.write_debug_gds(gds_name="after.gds") # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: - self.blockages.append(pin.inflated_pin(multiple=1, extra_spacing=self.offset, keep_link=True)) + self.blockages.append(pin.inflated_pin(multiple=1, + extra_spacing=self.offset, + keep_link=True)) # Route vdd and gnd for pin_name in [vdd_name, gnd_name]: pins = self.pins[pin_name] - # Create minimum spanning tree connecting all pins + # Route closest pins according to the minimum spanning tree for source, target in self.get_mst_pairs(list(pins)): # Create the Hanan graph hg = hanan_graph(self) @@ -90,11 +108,12 @@ class hanan_router(router_tech): self.prepare_gds_reader() self.find_blockages(pin_name) + # FIXME: Comment-out later self.write_debug_gds(gds_name="after.gds") def prepare_gds_reader(self): - """ """ + """ Write the current layout to a temporary file to read the layout. """ self.design.gds_write(self.gds_filename) self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) @@ -103,8 +122,8 @@ class hanan_router(router_tech): def find_pins(self, pin_name): - """ """ - debug.info(1, "Finding all pins for {}".format(pin_name)) + """ Find the pins with the given name. """ + debug.info(2, "Finding all pins for {}".format(pin_name)) shape_list = self.layout.getAllPinShapes(str(pin_name)) pin_set = set() @@ -133,9 +152,10 @@ class hanan_router(router_tech): def find_blockages(self, shape_name=None): - """ """ + """ Find all blockages in the routing layers. """ debug.info(1, "Finding blockages...") + # Keep current blockages here prev_blockages = self.blockages[:] blockages = [] @@ -213,6 +233,7 @@ class hanan_router(router_tech): else: margin = 0 + # Calculate the lower left coordinate if side == "top": offset = vector(ll.x + margin, ur.y - wideness - margin) elif side == "bottom": @@ -222,15 +243,14 @@ class hanan_router(router_tech): elif side == "right": offset = vector(ur.x - wideness - margin, ll.y + margin) + # Calculate width and height shape = ur - ll - if vertical: shape_width = wideness shape_height = shape.y else: shape_width = shape.x shape_height = wideness - if inner: if vertical: shape_height -= margin * 2 @@ -244,6 +264,7 @@ class hanan_router(router_tech): offset=offset, width=shape_width, height=shape_height) + # Add fake pins on this new pin evenly fake_pins = [] if vertical: @@ -270,7 +291,7 @@ class hanan_router(router_tech): def add_ring_pin(self, pin_name, width=3, num_connects=4): - """ Add suply ring to the layout. """ + """ Add the supply ring to the layout. """ bbox = self.design.get_bbox() From 71e4a5ab6c547624ce58b870e6b3d349b80df32d Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Thu, 13 Jul 2023 12:07:55 -0700 Subject: [PATCH 25/91] Rename gridless router files --- compiler/router/{hanan_graph.py => graph.py} | 30 +++++++++---------- .../router/{hanan_node.py => graph_node.py} | 8 ++--- .../router/{hanan_probe.py => graph_probe.py} | 2 +- .../{hanan_router.py => graph_router.py} | 21 ++++++------- .../router/{hanan_shape.py => graph_shape.py} | 6 ++-- 5 files changed, 34 insertions(+), 33 deletions(-) rename compiler/router/{hanan_graph.py => graph.py} (91%) rename compiler/router/{hanan_node.py => graph_node.py} (93%) rename compiler/router/{hanan_probe.py => graph_probe.py} (95%) rename compiler/router/{hanan_router.py => graph_router.py} (97%) rename compiler/router/{hanan_shape.py => graph_shape.py} (91%) diff --git a/compiler/router/hanan_graph.py b/compiler/router/graph.py similarity index 91% rename from compiler/router/hanan_graph.py rename to compiler/router/graph.py index d82f1fec..3835f366 100644 --- a/compiler/router/hanan_graph.py +++ b/compiler/router/graph.py @@ -10,16 +10,16 @@ from openram.base.vector import vector from openram.base.vector3d import vector3d from openram.tech import drc from .direction import direction -from .hanan_node import hanan_node -from .hanan_probe import hanan_probe +from .graph_node import graph_node +from .graph_probe import graph_probe -class hanan_graph: - """ This is the Hanan graph created from the blockages. """ +class graph: + """ This is the graph created from the blockages. """ def __init__(self, router): - # This is the Hanan router that uses this graph + # This is the graph router that uses this graph self.router = router self.source_nodes = [] self.target_nodes = [] @@ -43,7 +43,7 @@ class hanan_graph: This function assumes that p1 and p2 are on the same layer. """ - probe_shape = hanan_probe(p1, p2, self.router.vert_lpp if p1.z else self.router.horiz_lpp) + probe_shape = graph_probe(p1, p2, self.router.vert_lpp if p1.z else self.router.horiz_lpp) # Check if any blockage blocks this probe for blockage in self.graph_blockages: # Check if two shapes overlap @@ -54,8 +54,8 @@ class hanan_graph: def create_graph(self, source, target): - """ Create the Hanan graph to run routing on later. """ - debug.info(2, "Creating the Hanan graph for source '{}' and target'{}'.".format(source, target)) + """ Create the graph to run routing on later. """ + debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target)) self.source = source self.target = target @@ -79,9 +79,9 @@ class hanan_graph: self.graph_blockages.append(blockage) debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) - # Create the Hanan graph + # Create the graph x_values, y_values = self.generate_cartesian_values() - self.generate_hanan_nodes(x_values, y_values) + self.generate_graph_nodes(x_values, y_values) self.remove_blocked_nodes() debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes))) @@ -128,9 +128,9 @@ class hanan_graph: return x_values, y_values - def generate_hanan_nodes(self, x_values, y_values): + def generate_graph_nodes(self, x_values, y_values): """ - Generate all Hanan nodes using the cartesian values and connect the + Generate all graph nodes using the cartesian values and connect the orthogonal neighbors. """ @@ -139,8 +139,8 @@ class hanan_graph: self.nodes = [] for x in x_values: for y in y_values: - below_node = hanan_node([x, y, 0]) - above_node = hanan_node([x, y, 1]) + below_node = graph_node([x, y, 0]) + above_node = graph_node([x, y, 1]) # Connect these two neighbors below_node.add_neighbor(above_node) @@ -178,7 +178,7 @@ class hanan_graph: def remove_blocked_nodes(self): - """ Remove the Hanan nodes that are blocked by a blockage. """ + """ Remove the graph nodes that are blocked by a blockage. """ for i in range(len(self.nodes) - 1, -1, -1): node = self.nodes[i] diff --git a/compiler/router/hanan_node.py b/compiler/router/graph_node.py similarity index 93% rename from compiler/router/hanan_node.py rename to compiler/router/graph_node.py index 16c2e124..78642b22 100644 --- a/compiler/router/hanan_node.py +++ b/compiler/router/graph_node.py @@ -8,16 +8,16 @@ from openram.base.vector3d import vector3d from openram.tech import drc -class hanan_node: - """ This class represents a node on the Hanan graph. """ +class graph_node: + """ This class represents a node on the graph. """ # This is used to assign unique ids to nodes next_id = 0 def __init__(self, center): - self.id = hanan_node.next_id - hanan_node.next_id += 1 + self.id = graph_node.next_id + graph_node.next_id += 1 if isinstance(center, vector3d): self.center = center else: diff --git a/compiler/router/hanan_probe.py b/compiler/router/graph_probe.py similarity index 95% rename from compiler/router/hanan_probe.py rename to compiler/router/graph_probe.py index 87788227..31f06a57 100644 --- a/compiler/router/hanan_probe.py +++ b/compiler/router/graph_probe.py @@ -4,7 +4,7 @@ # All rights reserved. # -class hanan_probe: +class graph_probe: """ This class represents a probe sent from one point to another on Hanan graph. This is used to mimic the pin_layout class to utilize its methods. diff --git a/compiler/router/hanan_router.py b/compiler/router/graph_router.py similarity index 97% rename from compiler/router/hanan_router.py rename to compiler/router/graph_router.py index db508773..85f9de3b 100644 --- a/compiler/router/hanan_router.py +++ b/compiler/router/graph_router.py @@ -11,13 +11,14 @@ from openram.tech import GDS from openram.tech import layer as tech_layer from openram import OPTS from .router_tech import router_tech -from .hanan_graph import hanan_graph -from .hanan_shape import hanan_shape +from .graph import graph +from .graph_shape import graph_shape -class hanan_router(router_tech): +class graph_router(router_tech): """ - This is the router class that implements Hanan graph routing algorithm. + This is the router class that uses the Hanan grid method to route pins using + a graph. """ def __init__(self, layers, design, bbox=None, pin_type=None): @@ -96,8 +97,8 @@ class hanan_router(router_tech): pins = self.pins[pin_name] # Route closest pins according to the minimum spanning tree for source, target in self.get_mst_pairs(list(pins)): - # Create the Hanan graph - hg = hanan_graph(self) + # Create the graph + hg = graph(self) hg.create_graph(source, target) # Find the shortest path from source to target path = hg.find_shortest_path() @@ -134,7 +135,7 @@ class hanan_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - new_pin = hanan_shape(pin_name, rect, layer) + new_pin = graph_shape(pin_name, rect, layer) # Skip this pin if it's contained by another pin of the same type if new_pin.contained_by_any(pin_set): continue @@ -171,7 +172,7 @@ class hanan_router(router_tech): name = "blockage{}".format(len(blockages)) else: name = shape_name - new_shape = hanan_shape(name, rect, lpp) + new_shape = graph_shape(name, rect, lpp) # If there is a rectangle that is the same in the pins, # it isn't a blockage # Also ignore the new pins @@ -283,7 +284,7 @@ class hanan_router(router_tech): ll = vector(offset.x - self.track_wire, offset.y) ur = vector(offset.x, offset.y + wideness) rect = [ll, ur] - fake_pin = hanan_shape(name=pin_name, + fake_pin = graph_shape(name=pin_name, rect=rect, layer_name_pp=layer) fake_pins.append(fake_pin) @@ -302,7 +303,7 @@ class hanan_router(router_tech): ll, ur = new_shape.rect rect = [ll, ur] layer = self.get_layer(side in ["left", "right"]) - new_pin = hanan_shape(name=pin_name, + new_pin = graph_shape(name=pin_name, rect=rect, layer_name_pp=layer) new_pins.append(new_pin) diff --git a/compiler/router/hanan_shape.py b/compiler/router/graph_shape.py similarity index 91% rename from compiler/router/hanan_shape.py rename to compiler/router/graph_shape.py index 39af5bbf..448319b3 100644 --- a/compiler/router/hanan_shape.py +++ b/compiler/router/graph_shape.py @@ -8,10 +8,10 @@ from openram.base.vector import vector from openram.tech import drc -class hanan_shape(pin_layout): +class graph_shape(pin_layout): """ This class inherits the pin_layout class to change some of its behavior for - the Hanan router. + the graph router. """ def __init__(self, name, rect, layer_name_pp, inflated_from=None): @@ -29,7 +29,7 @@ class hanan_shape(pin_layout): newll = ll - extra newur = ur + extra inflated_area = (newll, newur) - return hanan_shape(self.name, inflated_area, self.layer, self if keep_link else None) + return graph_shape(self.name, inflated_area, self.layer, self if keep_link else None) def aligns(self, other): From 094e71764ae5364833f05e247ac09e8da1885b68 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Thu, 13 Jul 2023 12:16:58 -0700 Subject: [PATCH 26/91] Change option name for the gridless router --- compiler/modules/sram_1bank.py | 4 ++-- compiler/router/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index c40b0d3e..4c71d244 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -255,8 +255,8 @@ class sram_1bank(design, verilog, lef): return elif OPTS.route_supplies == "grid": from openram.router import supply_grid_router as router - elif OPTS.route_supplies == "navigation": - from openram.router import hanan_router as router + elif OPTS.route_supplies == "graph": + from openram.router import graph_router as router else: from openram.router import supply_tree_router as router rtr=router(layers=self.supply_stack, diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index 2bd70c4b..af90ccba 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -8,4 +8,4 @@ from .signal_escape_router import * from .signal_router import * from .supply_grid_router import * from .supply_tree_router import * -from .hanan_router import * +from .graph_router import * From 983cf13ccfbcca146639026105b8a9f91cce07b7 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 16 Jul 2023 20:25:15 -0700 Subject: [PATCH 27/91] Fix spacing for gridless router --- compiler/router/graph.py | 4 +++- compiler/router/graph_router.py | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 3835f366..14008f4e 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -57,13 +57,15 @@ class graph: """ Create the graph to run routing on later. """ debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target)) + # Save source and target nodes self.source = source self.target = target # Find the region to be routed and only include objects inside that region region = deepcopy(source) region.bbox([target]) - region = region.inflated_pin(multiple=1) + region = region.inflated_pin(spacing=self.router.track_space, + multiple=1) debug.info(3, "Routing region is {}".format(region.rect)) # Find the blockages that are in the routing area diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 85f9de3b..a7d58d75 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -82,13 +82,11 @@ class graph_router(router_tech): else: debug.warning("Side supply pins aren't created.") - # FIXME: Comment-out later - self.write_debug_gds(gds_name="after.gds") - # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: - self.blockages.append(pin.inflated_pin(multiple=1, + self.blockages.append(pin.inflated_pin(spacing=self.track_space, + multiple=1, extra_spacing=self.offset, keep_link=True)) @@ -196,7 +194,8 @@ class graph_router(router_tech): # Inflate the shapes to prevent DRC errors for blockage in blockages: - self.blockages.append(blockage.inflated_pin(multiple=1, + self.blockages.append(blockage.inflated_pin(spacing=self.track_space, + multiple=1, extra_spacing=self.offset, keep_link=shape_name is not None)) # Remove blockages contained by this new blockage @@ -328,7 +327,8 @@ class graph_router(router_tech): # Save side pins for routing self.new_pins[pin_name] = new_pins for pin in new_pins: - self.blockages.append(pin.inflated_pin(multiple=1, + self.blockages.append(pin.inflated_pin(spacing=self.track_space, + multiple=1, extra_spacing=self.offset, keep_link=True)) From e501e0ef4f3a8e2a1072f92ac77384702ec05022 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 16 Jul 2023 20:41:58 -0700 Subject: [PATCH 28/91] Cleanup graph for gridless router --- compiler/router/graph.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 14008f4e..ae7ef136 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -25,6 +25,12 @@ class graph: self.target_nodes = [] + def is_routable(self, shape): + """ Return if a shape is routable in this graph. """ + + return shape.name == self.source.name + + def inside_shape(self, point, shape): """ Return if the point is inside the shape. """ @@ -48,7 +54,7 @@ class graph: for blockage in self.graph_blockages: # Check if two shapes overlap # Inflated blockages of pins don't block probes - if blockage.overlaps(probe_shape) and (blockage.name != self.source.name or not blockage.inflated_from.overlaps(probe_shape)): + if blockage.overlaps(probe_shape) and (not self.is_routable(blockage) or not blockage.inflated_from.overlaps(probe_shape)): return True return False @@ -57,7 +63,7 @@ class graph: """ Create the graph to run routing on later. """ debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target)) - # Save source and target nodes + # Save source and target information self.source = source self.target = target @@ -72,7 +78,7 @@ class graph: self.graph_blockages = [] for blockage in self.router.blockages: # FIXME: Include pins as blockages as well to prevent DRC errors - if blockage.name == self.source.name: + if self.is_routable(blockage): continue # Set the region's lpp to current blockage's lpp so that the # overlaps method works @@ -85,6 +91,7 @@ class graph: x_values, y_values = self.generate_cartesian_values() self.generate_graph_nodes(x_values, y_values) self.remove_blocked_nodes() + self.save_end_nodes() debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes))) @@ -168,13 +175,6 @@ class graph: if not self.is_probe_blocked(above_node.center, node.center): above_node.add_neighbor(node) - # Save source and target nodes - for node in [below_node, above_node]: - if self.inside_shape(node.center, self.source): - self.source_nodes.append(node) - elif self.inside_shape(node.center, self.target): - self.target_nodes.append(node) - self.nodes.append(below_node) self.nodes.append(above_node) @@ -189,12 +189,22 @@ class graph: # Remove if the node is inside a blockage # If the blockage is an inflated routable, remove if outside # the routable shape - if self.inside_shape(point, blockage) and (blockage.name != self.source.name or not self.inside_shape(point, blockage.inflated_from)): + if self.inside_shape(point, blockage) and (not self.is_routable(blockage) or not self.inside_shape(point, blockage.inflated_from)): node.remove_all_neighbors() self.nodes.remove(node) break + def save_end_nodes(self): + """ Save graph nodes that are inside source and target pins. """ + + for node in self.nodes: + if self.inside_shape(node.center, self.source): + self.source_nodes.append(node) + elif self.inside_shape(node.center, self.target): + self.target_nodes.append(node) + + def find_shortest_path(self): """ Find the shortest path from the source node to target node using the From 38110a55e1e434a80aa80ef54006c0719b5d28fa Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 17 Jul 2023 15:02:36 -0700 Subject: [PATCH 29/91] Connect graph nodes better by hopping over removed nodes --- compiler/router/graph.py | 78 +++++++++++++++++++-------------- compiler/router/graph_router.py | 2 +- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index ae7ef136..5594d0ec 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -90,7 +90,6 @@ class graph: # Create the graph x_values, y_values = self.generate_cartesian_values() self.generate_graph_nodes(x_values, y_values) - self.remove_blocked_nodes() self.save_end_nodes() debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes))) @@ -109,6 +108,7 @@ class graph: y_offset = vector(0, self.router.offset) for shape in [self.source, self.target]: aspect_ratio = shape.width() / shape.height() + # FIXME: Aspect ratio may not be the best way to determine this # If the pin is tall or fat, add two points on the ends if aspect_ratio <= 0.5: # Tall pin points = [shape.bc() + y_offset, shape.uc() - y_offset] @@ -124,7 +124,7 @@ class graph: offset = drc["grid"] for blockage in self.graph_blockages: ll, ur = blockage.rect - # Add minimum offset to the blockage corner nodes to prevent overlaps + # Add minimum offset to the blockage corner nodes to prevent overlap x_values.update([ll.x - offset, ur.x + offset]) y_values.update([ll.y - offset, ur.y + offset]) @@ -143,44 +143,45 @@ class graph: orthogonal neighbors. """ - y_len = len(y_values) - left_offset = -(y_len * 2) + # Generate all nodes self.nodes = [] for x in x_values: for y in y_values: - below_node = graph_node([x, y, 0]) - above_node = graph_node([x, y, 1]) + for z in [0, 1]: + self.nodes.append(graph_node([x, y, z])) - # Connect these two neighbors - below_node.add_neighbor(above_node) + # Mark nodes that will be removed + self.mark_blocked_nodes() - # Find potential orthogonal neighbor nodes - belows = [] - aboves = [] - count = len(self.nodes) // 2 - if count % y_len: # Down - belows.append(-2) - aboves.append(-1) - if count >= y_len: # Left - belows.append(left_offset) - aboves.append(left_offset + 1) + # Connect closest nodes that won't be removed + def search(index, condition, shift): + """ Search and connect neighbor nodes. """ + base_nodes = self.nodes[index:index+2] + found = [hasattr(base_nodes[0], "remove"), + hasattr(base_nodes[1], "remove")] + while condition(index) and not all(found): + nodes = self.nodes[index - shift:index - shift + 2] + for k in range(2): + if not found[k] and not hasattr(nodes[k], "remove"): + found[k] = True + if not self.is_probe_blocked(base_nodes[k].center, nodes[k].center): + base_nodes[k].add_neighbor(nodes[k]) + index -= shift + y_len = len(y_values) + for i in range(0, len(self.nodes), 2): + search(i, lambda count: (count / 2) % y_len, 2) # Down + search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left + # FIXME: Avoid vias for inter-layer edges + if not hasattr(self.nodes[i], "remove") and \ + not hasattr(self.nodes[i + 1], "remove"): + self.nodes[i].add_neighbor(self.nodes[i + 1]) - # Add these connections if not blocked by a blockage - for i in belows: - node = self.nodes[i] - if not self.is_probe_blocked(below_node.center, node.center): - below_node.add_neighbor(node) - for i in aboves: - node = self.nodes[i] - if not self.is_probe_blocked(above_node.center, node.center): - above_node.add_neighbor(node) - - self.nodes.append(below_node) - self.nodes.append(above_node) + # Remove marked nodes + self.remove_blocked_nodes() - def remove_blocked_nodes(self): - """ Remove the graph nodes that are blocked by a blockage. """ + def mark_blocked_nodes(self): + """ Mark graph nodes to be removed that are blocked by a blockage. """ for i in range(len(self.nodes) - 1, -1, -1): node = self.nodes[i] @@ -190,11 +191,20 @@ class graph: # If the blockage is an inflated routable, remove if outside # the routable shape if self.inside_shape(point, blockage) and (not self.is_routable(blockage) or not self.inside_shape(point, blockage.inflated_from)): - node.remove_all_neighbors() - self.nodes.remove(node) + node.remove = True break + def remove_blocked_nodes(self): + """ Remove graph nodes that are marked to be removed. """ + + for i in range(len(self.nodes) - 1, -1, -1): + node = self.nodes[i] + if hasattr(node, "remove"): + node.remove_all_neighbors() + self.nodes.remove(node) + + def save_end_nodes(self): """ Save graph nodes that are inside source and target pins. """ diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index a7d58d75..136933e1 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -152,7 +152,7 @@ class graph_router(router_tech): def find_blockages(self, shape_name=None): """ Find all blockages in the routing layers. """ - debug.info(1, "Finding blockages...") + debug.info(2, "Finding blockages...") # Keep current blockages here prev_blockages = self.blockages[:] From 6e051e7f06474b2eedf3c467f2232c5d3c1e08c1 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 17 Jul 2023 15:43:48 -0700 Subject: [PATCH 30/91] Avoid DRC errors when routing the same type of pin --- compiler/router/graph.py | 50 ++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 5594d0ec..7e0b77ff 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -53,9 +53,33 @@ class graph: # Check if any blockage blocks this probe for blockage in self.graph_blockages: # Check if two shapes overlap - # Inflated blockages of pins don't block probes - if blockage.overlaps(probe_shape) and (not self.is_routable(blockage) or not blockage.inflated_from.overlaps(probe_shape)): - return True + if blockage.overlaps(probe_shape): + # Probe is blocked if the shape isn't routable + if not self.is_routable(blockage): + return True + elif blockage.inflated_from is None: + continue + elif blockage.inflated_from.overlaps(probe_shape): + continue + else: + return True + return False + + + def is_node_blocked(self, node): + """ Return if a node is blocked by a blockage. """ + + for blockage in self.graph_blockages: + # Check if two shapes overlap + if self.inside_shape(node.center, blockage): + if not self.is_routable(blockage): + return True + elif blockage.inflated_from is None: + return False + elif self.inside_shape(node.center, blockage.inflated_from): + return False + else: + return True return False @@ -77,14 +101,14 @@ class graph: # Find the blockages that are in the routing area self.graph_blockages = [] for blockage in self.router.blockages: - # FIXME: Include pins as blockages as well to prevent DRC errors - if self.is_routable(blockage): - continue # Set the region's lpp to current blockage's lpp so that the # overlaps method works region.lpp = blockage.lpp if region.overlaps(blockage): self.graph_blockages.append(blockage) + for shape in [source, target]: + if shape not in self.graph_blockages: + self.graph_blockages.append(shape) debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) # Create the graph @@ -106,7 +130,9 @@ class graph: # Add inner values for blockages of the routed type x_offset = vector(self.router.offset, 0) y_offset = vector(0, self.router.offset) - for shape in [self.source, self.target]: + for shape in self.graph_blockages: + if not self.is_routable(shape): + continue aspect_ratio = shape.width() / shape.height() # FIXME: Aspect ratio may not be the best way to determine this # If the pin is tall or fat, add two points on the ends @@ -185,14 +211,8 @@ class graph: for i in range(len(self.nodes) - 1, -1, -1): node = self.nodes[i] - point = node.center - for blockage in self.graph_blockages: - # Remove if the node is inside a blockage - # If the blockage is an inflated routable, remove if outside - # the routable shape - if self.inside_shape(point, blockage) and (not self.is_routable(blockage) or not self.inside_shape(point, blockage.inflated_from)): - node.remove = True - break + if self.is_node_blocked(node): + node.remove = True def remove_blocked_nodes(self): From b5983fbfd61292dbddbf55eee96064be89190cc8 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 17 Jul 2023 16:30:19 -0700 Subject: [PATCH 31/91] Prevent via DRC errors --- compiler/router/graph.py | 26 ++++++++++++++++++++++++-- compiler/router/graph_router.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 7e0b77ff..75acfe77 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -83,6 +83,18 @@ class graph: return False + def is_via_blocked(self, point): + """ Return if a via on the given point is blocked by another via. """ + + for via in self.graph_vias: + ll, ur = via.rect + center = via.center() + if via.on_segment(ll, point, ur) and \ + (center.x != point.x or center.y != point.y): + return True + return False + + def create_graph(self, source, target): """ Create the graph to run routing on later. """ debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target)) @@ -109,7 +121,17 @@ class graph: for shape in [source, target]: if shape not in self.graph_blockages: self.graph_blockages.append(shape) + + # Find the vias that are in the routing area + self.graph_vias = [] + for via in self.router.vias: + # Set the regions's lpp to current via's lpp so that the + # overlaps method works + region.lpp = via.lpp + if region.overlaps(via): + self.graph_vias.append(via) debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) + debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias))) # Create the graph x_values, y_values = self.generate_cartesian_values() @@ -197,9 +219,9 @@ class graph: for i in range(0, len(self.nodes), 2): search(i, lambda count: (count / 2) % y_len, 2) # Down search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left - # FIXME: Avoid vias for inter-layer edges if not hasattr(self.nodes[i], "remove") and \ - not hasattr(self.nodes[i + 1], "remove"): + not hasattr(self.nodes[i + 1], "remove") and \ + not self.is_via_blocked(self.nodes[i].center): self.nodes[i].add_neighbor(self.nodes[i + 1]) # Remove marked nodes diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 136933e1..fcae5ea8 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -42,6 +42,8 @@ class graph_router(router_tech): # This is all the blockages including the pins. The graph class handles # pins as blockages while considering their routability self.blockages = [] + # This is all the vias between routing layers + self.vias = [] # New pins are the side supply pins self.new_pins = {} # Fake pins are imaginary pins on the side supply pins to route other @@ -69,8 +71,9 @@ class graph_router(router_tech): self.find_pins(vdd_name) self.find_pins(gnd_name) - # Find blockages + # Find blockages and vias self.find_blockages() + self.find_vias() # Add side pins if self.pin_type in ["top", "bottom", "right", "left"]: @@ -106,6 +109,7 @@ class graph_router(router_tech): # Find the recently added shapes self.prepare_gds_reader() self.find_blockages(pin_name) + self.find_vias() # FIXME: Comment-out later self.write_debug_gds(gds_name="after.gds") @@ -214,6 +218,33 @@ class graph_router(router_tech): self.blockages.remove(prev_blockage) + def find_vias(self): + """ """ + debug.info(2, "Finding vias...") + + # Prepare lpp values here + from openram.tech import layer + via_lpp = layer[self.via_layer_name] + valid_lpp = self.horiz_lpp + + shapes = self.layout.getAllShapes(via_lpp) + for boundary in shapes: + # gdsMill boundaries are in (left, bottom, right, top) order + # so repack and snap to the grid + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_shape = graph_shape("via", rect, valid_lpp) + # If there is a rectangle that is the same in the pins, + # it isn't a blockage + # Also ignore the new pins + if new_shape.contained_by_any(self.vias): + continue + self.vias.append(new_shape.inflated_pin(spacing=self.track_space, + multiple=1, + extra_spacing=self.offset)) + + def add_side_pin(self, pin_name, side, width=3, num_connects=4, bbox=None): """ Add supply pin to one side of the layout. """ From e8c3cf0a947c5fc7d8e2d29800a786ff1104c326 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 18 Jul 2023 22:36:14 -0700 Subject: [PATCH 32/91] Remove nodes inside routables that can cause DRC errors --- compiler/router/graph.py | 40 +++++++++++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 75acfe77..4c8494e9 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -69,18 +69,44 @@ class graph: def is_node_blocked(self, node): """ Return if a node is blocked by a blockage. """ + def diff(a, b): + """ + Return the absolute difference of two numbers avoiding precision + errors. + """ + decimals = len(str(drc["grid"]).split(".")[1]) + return round(abs(a - b), decimals) + + blocked = False for blockage in self.graph_blockages: # Check if two shapes overlap if self.inside_shape(node.center, blockage): if not self.is_routable(blockage): - return True - elif blockage.inflated_from is None: - return False - elif self.inside_shape(node.center, blockage.inflated_from): - return False + blocked = True + continue + if blockage.inflated_from is not None: + blockage = blockage.inflated_from + if self.inside_shape(node.center, blockage): + offset = self.router.offset + p = node.center + lengths = [blockage.width(), blockage.height()] + centers = blockage.center() + ll, ur = blockage.rect + safe = [True, True] + for i in range(2): + if lengths[i] >= offset * 2: + min_diff = min(diff(ll[i], p[i]), diff(ur[i], p[i])) + if min_diff < offset: + safe[i] = False + elif diff(centers[i], p[i]) > 0: + safe[i] = False + if not all(safe): + blocked = True + elif blockage in [self.source, self.target]: + return False else: - return True - return False + blocked = True + return blocked def is_via_blocked(self, point): From 5ce193c2dd855f720a059ba20c405324d6b51014 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 19 Jul 2023 18:32:22 -0700 Subject: [PATCH 33/91] Snap node vectors to grid to prevent precision errors --- compiler/router/graph.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 4c8494e9..9d7e7afb 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -57,12 +57,11 @@ class graph: # Probe is blocked if the shape isn't routable if not self.is_routable(blockage): return True - elif blockage.inflated_from is None: + if blockage.inflated_from is not None: + blockage = blockage.inflated_from + if blockage.overlaps(probe_shape): continue - elif blockage.inflated_from.overlaps(probe_shape): - continue - else: - return True + return True return False @@ -90,7 +89,7 @@ class graph: offset = self.router.offset p = node.center lengths = [blockage.width(), blockage.height()] - centers = blockage.center() + centers = blockage.center().snap_to_grid() ll, ur = blockage.rect safe = [True, True] for i in range(2): @@ -114,7 +113,7 @@ class graph: for via in self.graph_vias: ll, ur = via.rect - center = via.center() + center = via.center().snap_to_grid() if via.on_segment(ll, point, ur) and \ (center.x != point.x or center.y != point.y): return True @@ -191,6 +190,7 @@ class graph: else: # Square-like pin points = [shape.center()] for p in points: + p.snap_to_grid() x_values.add(p.x) y_values.add(p.y) From 7ee1dcef543c1aa641dc8eb8141a37ca4fe09dcd Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Fri, 21 Jul 2023 08:26:54 -0700 Subject: [PATCH 34/91] Simplify inflated_from logic --- compiler/router/graph.py | 6 ++---- compiler/router/graph_shape.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 9d7e7afb..604456b8 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -57,8 +57,7 @@ class graph: # Probe is blocked if the shape isn't routable if not self.is_routable(blockage): return True - if blockage.inflated_from is not None: - blockage = blockage.inflated_from + blockage = blockage.get_inflated_from() if blockage.overlaps(probe_shape): continue return True @@ -83,8 +82,7 @@ class graph: if not self.is_routable(blockage): blocked = True continue - if blockage.inflated_from is not None: - blockage = blockage.inflated_from + blockage = blockage.get_inflated_from() if self.inside_shape(node.center, blockage): offset = self.router.offset p = node.center diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 448319b3..76a780c6 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -21,6 +21,17 @@ class graph_shape(pin_layout): self.inflated_from = inflated_from + def get_inflated_from(self): + """ + Return `self` if `self.inflated_from` is None. Otherwise, return + `self.inflated_from`. + """ + + if self.inflated_from is None: + return self + return self.inflated_from + + def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0, keep_link=False): """ Override the default inflated_pin behavior. """ From 44b2e4589cd5fb0b96d84fb8bd6268f59434a2a2 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Fri, 21 Jul 2023 08:30:46 -0700 Subject: [PATCH 35/91] Don't use multiple when inflating shapes --- compiler/router/graph.py | 3 +-- compiler/router/graph_router.py | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 604456b8..e7e62da8 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -129,8 +129,7 @@ class graph: # Find the region to be routed and only include objects inside that region region = deepcopy(source) region.bbox([target]) - region = region.inflated_pin(spacing=self.router.track_space, - multiple=1) + region = region.inflated_pin(spacing=self.router.track_space) debug.info(3, "Routing region is {}".format(region.rect)) # Find the blockages that are in the routing area diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index fcae5ea8..8c5a4164 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -89,7 +89,6 @@ class graph_router(router_tech): # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: self.blockages.append(pin.inflated_pin(spacing=self.track_space, - multiple=1, extra_spacing=self.offset, keep_link=True)) @@ -199,7 +198,6 @@ class graph_router(router_tech): # Inflate the shapes to prevent DRC errors for blockage in blockages: self.blockages.append(blockage.inflated_pin(spacing=self.track_space, - multiple=1, extra_spacing=self.offset, keep_link=shape_name is not None)) # Remove blockages contained by this new blockage @@ -241,7 +239,6 @@ class graph_router(router_tech): if new_shape.contained_by_any(self.vias): continue self.vias.append(new_shape.inflated_pin(spacing=self.track_space, - multiple=1, extra_spacing=self.offset)) @@ -359,7 +356,6 @@ class graph_router(router_tech): self.new_pins[pin_name] = new_pins for pin in new_pins: self.blockages.append(pin.inflated_pin(spacing=self.track_space, - multiple=1, extra_spacing=self.offset, keep_link=True)) From 3e3265c416991d3020c296788b34a93b9e453846 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Fri, 21 Jul 2023 09:55:36 -0700 Subject: [PATCH 36/91] Add new snap_to_grid function for graph router --- compiler/router/graph.py | 7 ++++--- compiler/router/graph_shape.py | 4 ++++ compiler/router/graph_utils.py | 25 +++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 compiler/router/graph_utils.py diff --git a/compiler/router/graph.py b/compiler/router/graph.py index e7e62da8..92c5e1fd 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -12,6 +12,7 @@ from openram.tech import drc from .direction import direction from .graph_node import graph_node from .graph_probe import graph_probe +from .graph_utils import snap_to_grid class graph: @@ -87,7 +88,7 @@ class graph: offset = self.router.offset p = node.center lengths = [blockage.width(), blockage.height()] - centers = blockage.center().snap_to_grid() + centers = snap_to_grid(blockage.center()) ll, ur = blockage.rect safe = [True, True] for i in range(2): @@ -111,7 +112,7 @@ class graph: for via in self.graph_vias: ll, ur = via.rect - center = via.center().snap_to_grid() + center = snap_to_grid(via.center()) if via.on_segment(ll, point, ur) and \ (center.x != point.x or center.y != point.y): return True @@ -187,7 +188,7 @@ class graph: else: # Square-like pin points = [shape.center()] for p in points: - p.snap_to_grid() + p = snap_to_grid(p) x_values.add(p.x) y_values.add(p.y) diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 76a780c6..8801a974 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -6,6 +6,7 @@ from openram.base.pin_layout import pin_layout from openram.base.vector import vector from openram.tech import drc +from .graph_utils import snap_to_grid class graph_shape(pin_layout): @@ -18,6 +19,9 @@ class graph_shape(pin_layout): pin_layout.__init__(self, name, rect, layer_name_pp) + # Snap the shape to the grid here + ll, ur = self.rect + self.rect = [snap_to_grid(ll), snap_to_grid(ur)] self.inflated_from = inflated_from diff --git a/compiler/router/graph_utils.py b/compiler/router/graph_utils.py new file mode 100644 index 00000000..a8b491a5 --- /dev/null +++ b/compiler/router/graph_utils.py @@ -0,0 +1,25 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +""" +Utility functions for graph router. +""" +from openram.base import vector +from openram.tech import drc + + +def snap_to_grid(v): + """ Use custom `snap_to_grid` since `vector.snap_to_grid` isn't working. """ + + return vector(snap_offset_to_grid(v.x), snap_offset_to_grid(v.y)) + + +def snap_offset_to_grid(offset): + """ + Use custom `snap_offset_to_grid` since `vector.snap_offset_to_grid` isn't + working. + """ + + return round(offset, len(str(drc["grid"]).split('.')[1])) From 8354de654fb12004a43618302463a18423ef752e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Fri, 21 Jul 2023 12:24:20 -0700 Subject: [PATCH 37/91] Fix precision of blockage node centers --- compiler/router/graph.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 92c5e1fd..16f3954f 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -12,7 +12,7 @@ from openram.tech import drc from .direction import direction from .graph_node import graph_node from .graph_probe import graph_probe -from .graph_utils import snap_to_grid +from .graph_utils import * class graph: @@ -68,14 +68,6 @@ class graph: def is_node_blocked(self, node): """ Return if a node is blocked by a blockage. """ - def diff(a, b): - """ - Return the absolute difference of two numbers avoiding precision - errors. - """ - decimals = len(str(drc["grid"]).split(".")[1]) - return round(abs(a - b), decimals) - blocked = False for blockage in self.graph_blockages: # Check if two shapes overlap @@ -93,10 +85,10 @@ class graph: safe = [True, True] for i in range(2): if lengths[i] >= offset * 2: - min_diff = min(diff(ll[i], p[i]), diff(ur[i], p[i])) + min_diff = snap_offset_to_grid(min(abs(ll[i] - p[i]), abs(ur[i] - p[i]))) if min_diff < offset: safe[i] = False - elif diff(centers[i], p[i]) > 0: + elif centers[i] != p[i]: safe[i] = False if not all(safe): blocked = True @@ -193,12 +185,14 @@ class graph: y_values.add(p.y) # Add corners for blockages - offset = drc["grid"] + offset = vector(drc["grid"], drc["grid"]) for blockage in self.graph_blockages: ll, ur = blockage.rect + nll = snap_to_grid(ll - offset) + nur = snap_to_grid(ur + offset) # Add minimum offset to the blockage corner nodes to prevent overlap - x_values.update([ll.x - offset, ur.x + offset]) - y_values.update([ll.y - offset, ur.y + offset]) + x_values.update([nll.x, nur.x]) + y_values.update([nll.y, nur.y]) # Sort x and y values x_values = list(x_values) From 48b556c43aa27603f01591dcc0dfeda3eb35f384 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 22 Jul 2023 14:07:27 -0700 Subject: [PATCH 38/91] Fix even more DRC errors in graph router --- compiler/router/graph.py | 14 +++++++------- compiler/router/graph_router.py | 3 ++- compiler/router/graph_shape.py | 22 ++++++++++++++++++++-- compiler/router/graph_utils.py | 17 +++++------------ 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 16f3954f..d0c43d6d 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -12,7 +12,7 @@ from openram.tech import drc from .direction import direction from .graph_node import graph_node from .graph_probe import graph_probe -from .graph_utils import * +from .graph_utils import snap class graph: @@ -80,12 +80,12 @@ class graph: offset = self.router.offset p = node.center lengths = [blockage.width(), blockage.height()] - centers = snap_to_grid(blockage.center()) + centers = blockage.center() ll, ur = blockage.rect safe = [True, True] for i in range(2): if lengths[i] >= offset * 2: - min_diff = snap_offset_to_grid(min(abs(ll[i] - p[i]), abs(ur[i] - p[i]))) + min_diff = snap(min(abs(ll[i] - p[i]), abs(ur[i] - p[i]))) if min_diff < offset: safe[i] = False elif centers[i] != p[i]: @@ -104,7 +104,7 @@ class graph: for via in self.graph_vias: ll, ur = via.rect - center = snap_to_grid(via.center()) + center = via.center() if via.on_segment(ll, point, ur) and \ (center.x != point.x or center.y != point.y): return True @@ -180,7 +180,7 @@ class graph: else: # Square-like pin points = [shape.center()] for p in points: - p = snap_to_grid(p) + p = snap(p) x_values.add(p.x) y_values.add(p.y) @@ -188,8 +188,8 @@ class graph: offset = vector(drc["grid"], drc["grid"]) for blockage in self.graph_blockages: ll, ur = blockage.rect - nll = snap_to_grid(ll - offset) - nur = snap_to_grid(ur + offset) + nll = snap(ll - offset) + nur = snap(ur + offset) # Add minimum offset to the blockage corner nodes to prevent overlap x_values.update([nll.x, nur.x]) y_values.update([nll.y, nur.y]) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 8c5a4164..d01d9f0c 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -13,6 +13,7 @@ from openram import OPTS from .router_tech import router_tech from .graph import graph from .graph_shape import graph_shape +from .graph_utils import snap class graph_router(router_tech): @@ -51,7 +52,7 @@ class graph_router(router_tech): self.fake_pins = [] # Set the offset here - self.offset = self.layer_widths[0] / 2 + self.offset = snap(self.layer_widths[0] / 2) def route(self, vdd_name="vdd", gnd_name="gnd"): diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 8801a974..2405d11f 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -6,7 +6,7 @@ from openram.base.pin_layout import pin_layout from openram.base.vector import vector from openram.tech import drc -from .graph_utils import snap_to_grid +from .graph_utils import snap class graph_shape(pin_layout): @@ -21,10 +21,28 @@ class graph_shape(pin_layout): # Snap the shape to the grid here ll, ur = self.rect - self.rect = [snap_to_grid(ll), snap_to_grid(ur)] + self.rect = [snap(ll), snap(ur)] self.inflated_from = inflated_from + def center(self): + """ Override the default `center` behavior. """ + + return snap(super().center()) + + + def height(self): + """ Override the default `height` behavior. """ + + return snap(super().height()) + + + def width(self): + """ Override the default `width` behavior. """ + + return snap(super().width()) + + def get_inflated_from(self): """ Return `self` if `self.inflated_from` is None. Otherwise, return diff --git a/compiler/router/graph_utils.py b/compiler/router/graph_utils.py index a8b491a5..4fa9a301 100644 --- a/compiler/router/graph_utils.py +++ b/compiler/router/graph_utils.py @@ -10,16 +10,9 @@ from openram.base import vector from openram.tech import drc -def snap_to_grid(v): - """ Use custom `snap_to_grid` since `vector.snap_to_grid` isn't working. """ +def snap(a): + """ Use custom `snap` since `vector.snap_to_grid` isn't working. """ - return vector(snap_offset_to_grid(v.x), snap_offset_to_grid(v.y)) - - -def snap_offset_to_grid(offset): - """ - Use custom `snap_offset_to_grid` since `vector.snap_offset_to_grid` isn't - working. - """ - - return round(offset, len(str(drc["grid"]).split('.')[1])) + if isinstance(a, vector): + return vector(snap(a.x), snap(a.y)) + return round(a, len(str(drc["grid"]).split('.')[1])) From 06b8f3b2bef628f009467cc82e1ec331f7172c8a Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 22 Jul 2023 19:25:26 -0700 Subject: [PATCH 39/91] Prevent supply ring from overlapping existing pins --- compiler/router/graph_router.py | 37 ++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index d01d9f0c..8ef449fd 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -77,6 +77,7 @@ class graph_router(router_tech): self.find_vias() # Add side pins + self.calculate_ring_bbox() if self.pin_type in ["top", "bottom", "right", "left"]: self.add_side_pin(vdd_name) self.add_side_pin(gnd_name) @@ -243,16 +244,38 @@ class graph_router(router_tech): extra_spacing=self.offset)) - def add_side_pin(self, pin_name, side, width=3, num_connects=4, bbox=None): + def calculate_ring_bbox(self, width=3): + """ Calculate the ring-safe bounding box of the layout. """ + + ll, ur = self.design.get_bbox() + wideness = self.track_wire * width + self.track_space * (width - 1) + total_wideness = wideness * 4 + for blockage in self.blockages: + bll, bur = blockage.rect + if self.get_zindex(blockage.lpp) == 1: # Vertical + diff = ll.x + total_wideness - bll.x + if diff > 0: + ll = vector(ll.x - diff, ll.y) + diff = ur.x - total_wideness - bur.x + if diff < 0: + ur = vector(ur.x - diff, ur.y) + else: # Horizontal + diff = ll.y + total_wideness - bll.y + if diff > 0: + ll = vector(ll.x, ll.y - diff) + diff = ur.y - total_wideness - bur.y + if diff < 0: + ur = vector(ur.x, ur.y - diff) + self.ring_bbox = [ll, ur] + + + def add_side_pin(self, pin_name, side, width=3, num_connects=4): """ Add supply pin to one side of the layout. """ + ll, ur = self.ring_bbox vertical = side in ["left", "right"] inner = pin_name == self.gnd_name - if bbox is None: - bbox = self.design.get_bbox() - ll, ur = bbox - # Calculate wires' wideness wideness = self.track_wire * width + self.track_space * (width - 1) @@ -322,12 +345,10 @@ class graph_router(router_tech): def add_ring_pin(self, pin_name, width=3, num_connects=4): """ Add the supply ring to the layout. """ - bbox = self.design.get_bbox() - # Add side pins new_pins = [] for side in ["top", "bottom", "right", "left"]: - new_shape, fake_pins = self.add_side_pin(pin_name, side, width, num_connects, bbox) + new_shape, fake_pins = self.add_side_pin(pin_name, side, width, num_connects) ll, ur = new_shape.rect rect = [ll, ur] layer = self.get_layer(side in ["left", "right"]) From 1c274afa46fb5a99f6d7624b942caf88a418cbef Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 22 Jul 2023 21:30:40 -0700 Subject: [PATCH 40/91] Include blockages in the routing area after generating nodes --- compiler/router/graph.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index d0c43d6d..2e9d8457 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -127,12 +127,7 @@ class graph: # Find the blockages that are in the routing area self.graph_blockages = [] - for blockage in self.router.blockages: - # Set the region's lpp to current blockage's lpp so that the - # overlaps method works - region.lpp = blockage.lpp - if region.overlaps(blockage): - self.graph_blockages.append(blockage) + self.find_graph_blockages(region) for shape in [source, target]: if shape not in self.graph_blockages: self.graph_blockages.append(shape) @@ -145,16 +140,31 @@ class graph: region.lpp = via.lpp if region.overlaps(via): self.graph_vias.append(via) - debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) - debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias))) # Create the graph x_values, y_values = self.generate_cartesian_values() + region.bbox(self.graph_blockages) + self.find_graph_blockages(region) self.generate_graph_nodes(x_values, y_values) self.save_end_nodes() + debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) + debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias))) debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes))) + def find_graph_blockages(self, region): + """ Find blockages that overlap the routing region. """ + + for blockage in self.router.blockages: + # Set the region's lpp to current blockage's lpp so that the + # overlaps method works + if blockage in self.graph_blockages: + continue + region.lpp = blockage.lpp + if region.overlaps(blockage): + self.graph_blockages.append(blockage) + + def generate_cartesian_values(self): """ Generate x and y values from all the corners of the shapes in the From 7a7ddebcca5002d12bfaed102331dc5652079e1a Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 23 Jul 2023 10:22:43 -0700 Subject: [PATCH 41/91] Add more spacing to inflated pins to prevent unroutables --- compiler/router/graph_router.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 8ef449fd..95de4707 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -8,6 +8,7 @@ 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 drc from openram.tech import layer as tech_layer from openram import OPTS from .router_tech import router_tech @@ -90,7 +91,13 @@ class graph_router(router_tech): # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: - self.blockages.append(pin.inflated_pin(spacing=self.track_space, + xdiff = self.layer_widths[0] - pin.width() + ydiff = self.layer_widths[0] - pin.height() + diff = max(xdiff, ydiff) + spacing = self.track_space + drc["grid"] + if diff > 0: + spacing += diff + self.blockages.append(pin.inflated_pin(spacing=spacing, extra_spacing=self.offset, keep_link=True)) From a90fd36f57383bdf6c986c5dc0eb4d1296771e66 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 23 Jul 2023 10:43:02 -0700 Subject: [PATCH 42/91] Change spacing rule for pins --- compiler/router/graph_router.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 95de4707..f537e0a9 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -206,7 +206,11 @@ class graph_router(router_tech): # Inflate the shapes to prevent DRC errors for blockage in blockages: - self.blockages.append(blockage.inflated_pin(spacing=self.track_space, + if self.get_zindex(blockage.lpp) == 1: + spacing = self.vert_layer_spacing + else: + spacing = self.horiz_layer_spacing + self.blockages.append(blockage.inflated_pin(spacing=spacing, extra_spacing=self.offset, keep_link=shape_name is not None)) # Remove blockages contained by this new blockage From 542e1a5e038a2917ae7195ebdb0a9a61b36d184e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 23 Jul 2023 18:43:26 -0700 Subject: [PATCH 43/91] Use half pin's size difference for new spacing rule --- compiler/router/graph_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index f537e0a9..ae76eb0f 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -93,7 +93,7 @@ class graph_router(router_tech): for pin in self.all_pins: xdiff = self.layer_widths[0] - pin.width() ydiff = self.layer_widths[0] - pin.height() - diff = max(xdiff, ydiff) + diff = max(xdiff, ydiff) / 2 spacing = self.track_space + drc["grid"] if diff > 0: spacing += diff From 7119f9a131650164e23a9ac66ed46d2f4ade53f8 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 23 Jul 2023 18:45:02 -0700 Subject: [PATCH 44/91] Fix spacing rule for non-max-width layer in router_tech --- compiler/router/router_tech.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index 6c8c7d4c..eafdb82e 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -67,6 +67,11 @@ class router_tech: self.vert_layer_minwidth = max(self.vert_layer_minwidth, max_via_size) self.horiz_layer_minwidth = max(self.horiz_layer_minwidth, max_via_size) + # Update spacing for the new widths + max_width = max(self.vert_layer_minwidth, self.horiz_layer_minwidth) + self.vert_layer_spacing = self.get_layer_space(1, max_width) + self.horiz_layer_spacing = self.get_layer_space(0, max_width) + self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing @@ -114,18 +119,21 @@ class router_tech: These are the width and spacing of a supply layer given a supply rail of the given number of min wire widths. """ - if zindex==1: - layer_name = self.vert_layer_name - elif zindex==0: - layer_name = self.horiz_layer_name - else: - debug.error("Invalid zindex for track", -1) - - min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) - - min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) - min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf) - + min_width = self.get_layer_width(zindex) + min_spacing = self.get_layer_space(zindex, min_width) return (min_width, min_spacing) + def get_layer_width(self, zindex): + """ """ + layer_name = self.get_layer(zindex) + min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) + min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) + return min_width + def get_layer_space(self, zindex, width): + """ """ + if width is None: + width = self.get_layer_width(zindex) + layer_name = self.get_layer(zindex) + min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * width, math.inf) + return min_spacing From 54f2e73214bbfd4728ae0a02bb7e9a0c554f31c2 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 23 Jul 2023 21:09:15 -0700 Subject: [PATCH 45/91] Simplify inflating shapes in graph router --- compiler/router/graph_router.py | 58 ++++++++++++++++++--------------- compiler/router/graph_shape.py | 4 +-- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index ae76eb0f..7b3f7782 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -59,8 +59,6 @@ class graph_router(router_tech): def route(self, vdd_name="vdd", gnd_name="gnd"): """ Route the given pins in the given order. """ debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) - # FIXME: Comment-out later - self.write_debug_gds(gds_name="before.gds") # Save pin names self.vdd_name = vdd_name @@ -91,15 +89,7 @@ class graph_router(router_tech): # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: - xdiff = self.layer_widths[0] - pin.width() - ydiff = self.layer_widths[0] - pin.height() - diff = max(xdiff, ydiff) / 2 - spacing = self.track_space + drc["grid"] - if diff > 0: - spacing += diff - self.blockages.append(pin.inflated_pin(spacing=spacing, - extra_spacing=self.offset, - keep_link=True)) + self.blockages.append(self.inflate_shape(pin, is_pin=True)) # Route vdd and gnd for pin_name in [vdd_name, gnd_name]: @@ -111,6 +101,8 @@ class graph_router(router_tech): hg.create_graph(source, target) # Find the shortest path from source to target path = hg.find_shortest_path() + # TODO: Exponentially increase the routing area and retry if no + # path was found debug.check(path is not None, "Couldn't route from {} to {}".format(source, target)) # Create the path shapes on layout self.add_path(path) @@ -119,9 +111,6 @@ class graph_router(router_tech): self.find_blockages(pin_name) self.find_vias() - # FIXME: Comment-out later - self.write_debug_gds(gds_name="after.gds") - def prepare_gds_reader(self): """ Write the current layout to a temporary file to read the layout. """ @@ -206,13 +195,7 @@ class graph_router(router_tech): # Inflate the shapes to prevent DRC errors for blockage in blockages: - if self.get_zindex(blockage.lpp) == 1: - spacing = self.vert_layer_spacing - else: - spacing = self.horiz_layer_spacing - self.blockages.append(blockage.inflated_pin(spacing=spacing, - extra_spacing=self.offset, - keep_link=shape_name is not None)) + self.blockages.append(self.inflate_shape(blockage)) # Remove blockages contained by this new blockage for i in range(len(prev_blockages) - 1, -1, -1): prev_blockage = prev_blockages[i] @@ -251,8 +234,33 @@ class graph_router(router_tech): # Also ignore the new pins if new_shape.contained_by_any(self.vias): continue - self.vias.append(new_shape.inflated_pin(spacing=self.track_space, - extra_spacing=self.offset)) + self.vias.append(self.inflate_shape(new_shape, is_via=True)) + + + def inflate_shape(self, shape, is_pin=False, is_via=False): + """ Inflate a given shape with spacing rules. """ + + # FIXME: Shapes might be wider than the route_tech settings + # Pins must keep their center lines away from any blockage to prevent + # the nodes from being unconnected + if is_pin: + xdiff = self.layer_widths[0] - shape.width() + ydiff = self.layer_widths[0] - shape.height() + diff = max(xdiff, ydiff) / 2 + spacing = self.track_space + drc["grid"] + if diff > 0: + spacing += diff + # Vias are inflated by the maximum spacing rule + elif is_via: + spacing = self.track_space + # Blockages are inflated by their layer's corresponding spacing rule + else: + if self.get_zindex(shape.lpp) == 1: + spacing = self.vert_layer_spacing + else: + spacing = self.horiz_layer_spacing + return shape.inflated_pin(spacing=spacing, + extra_spacing=self.offset) def calculate_ring_bbox(self, width=3): @@ -388,9 +396,7 @@ class graph_router(router_tech): # Save side pins for routing self.new_pins[pin_name] = new_pins for pin in new_pins: - self.blockages.append(pin.inflated_pin(spacing=self.track_space, - extra_spacing=self.offset, - keep_link=True)) + self.blockages.append(self.inflate_shape(pin, is_pin=True)) def get_mst_pairs(self, pins): diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 2405d11f..29f1f872 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -54,7 +54,7 @@ class graph_shape(pin_layout): return self.inflated_from - def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0, keep_link=False): + def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0): """ Override the default inflated_pin behavior. """ ll, ur = self.inflate(spacing, multiple) @@ -62,7 +62,7 @@ class graph_shape(pin_layout): newll = ll - extra newur = ur + extra inflated_area = (newll, newur) - return graph_shape(self.name, inflated_area, self.layer, self if keep_link else None) + return graph_shape(self.name, inflated_area, self.layer, self) def aligns(self, other): From 947e94323d2d453685848f6f7368f66224fd5f62 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 24 Jul 2023 08:03:08 -0700 Subject: [PATCH 46/91] Cleanup graph router --- compiler/router/graph.py | 40 +++++++++++++++++++++++---------- compiler/router/graph_router.py | 33 +++++++++++++-------------- compiler/router/router_tech.py | 4 ++-- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 2e9d8457..2987bd6e 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -128,24 +128,20 @@ class graph: # Find the blockages that are in the routing area self.graph_blockages = [] self.find_graph_blockages(region) - for shape in [source, target]: - if shape not in self.graph_blockages: - self.graph_blockages.append(shape) # Find the vias that are in the routing area self.graph_vias = [] - for via in self.router.vias: - # Set the regions's lpp to current via's lpp so that the - # overlaps method works - region.lpp = via.lpp - if region.overlaps(via): - self.graph_vias.append(via) + self.find_graph_vias(region) - # Create the graph + # Generate the cartesian values from shapes in the area x_values, y_values = self.generate_cartesian_values() + # Adjust the routing region to include "edge" shapes region.bbox(self.graph_blockages) + # Find and include edge shapes to prevent DRC errors self.find_graph_blockages(region) + # 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 self.save_end_nodes() debug.info(3, "Number of blockages detected in the routing region: {}".format(len(self.graph_blockages))) debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias))) @@ -156,13 +152,33 @@ class graph: """ Find blockages that overlap the routing region. """ for blockage in self.router.blockages: - # Set the region's lpp to current blockage's lpp so that the - # overlaps method works + # Skip if already included if blockage in self.graph_blockages: continue + # Set the region's lpp to current blockage's lpp so that the + # overlaps method works region.lpp = blockage.lpp if region.overlaps(blockage): self.graph_blockages.append(blockage) + # FIXME: Don't include source and target if they're already included + # in inflated form + for shape in [self.source, self.target]: + if shape not in self.graph_blockages: + self.graph_blockages.append(shape) + + + def find_graph_vias(self, region): + """ Find vias that overlap the routing region. """ + + for via in self.router.vias: + # Skip if already included + if via in self.graph_vias: + continue + # Set the regions's lpp to current via's lpp so that the + # overlaps method works + region.lpp = via.lpp + if region.overlaps(via): + self.graph_vias.append(via) def generate_cartesian_values(self): diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 7b3f7782..9166b8e1 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -97,10 +97,10 @@ class graph_router(router_tech): # Route closest pins according to the minimum spanning tree for source, target in self.get_mst_pairs(list(pins)): # Create the graph - hg = graph(self) - hg.create_graph(source, target) + g = graph(self) + g.create_graph(source, target) # Find the shortest path from source to target - path = hg.find_shortest_path() + path = g.find_shortest_path() # TODO: Exponentially increase the routing area and retry if no # path was found debug.check(path is not None, "Couldn't route from {} to {}".format(source, target)) @@ -130,7 +130,6 @@ class graph_router(router_tech): for shape in shape_list: 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]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] @@ -163,7 +162,6 @@ class graph_router(router_tech): shapes = self.layout.getAllShapes(lpp) for boundary in shapes: # gdsMill boundaries are in (left, bottom, right, top) order - # so repack and snap to the grid ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] @@ -224,7 +222,6 @@ class graph_router(router_tech): shapes = self.layout.getAllShapes(via_lpp) for boundary in shapes: # gdsMill boundaries are in (left, bottom, right, top) order - # so repack and snap to the grid ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] @@ -486,30 +483,32 @@ class graph_router(router_tech): def get_new_pins(self, name): - """ """ + """ Return the new supply pins added by this router. """ return self.new_pins[name] - def write_debug_gds(self, gds_name="debug_route.gds", hg=None, source=None, target=None): - """ """ + def write_debug_gds(self, gds_name="debug_route.gds", g=None, source=None, target=None): + """ Write the debug GDSII file for the router. """ - self.add_router_info(hg, source, target) + self.add_router_info(g, source, target) self.design.gds_write(gds_name) self.del_router_info() - def add_router_info(self, hg=None, source=None, target=None): - """ """ + def add_router_info(self, g=None, source=None, target=None): + """ + Add debug information to the text layer about the graph and router. + """ # Display the inflated blockage - if hg: + if g: for blockage in self.blockages: - if blockage in hg.graph_blockages: + if blockage in g.graph_blockages: self.add_object_info(blockage, "blockage{}++[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) else: self.add_object_info(blockage, "blockage{}[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) - for node in hg.nodes: + for node in g.nodes: offset = (node.center.x, node.center.y) self.design.add_label(text="n{}".format(node.center.z), layer="text", @@ -526,14 +525,14 @@ class graph_router(router_tech): def del_router_info(self): - """ """ + """ Delete router information from the text layer. """ 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): - """ """ + """ Add debug information to the text layer about an object. """ ll, ur = obj.rect self.design.add_rect(layer="text", diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index eafdb82e..326a60a9 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -124,14 +124,14 @@ class router_tech: return (min_width, min_spacing) def get_layer_width(self, zindex): - """ """ + """ Return the minimum width of a layer. """ layer_name = self.get_layer(zindex) min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) return min_width def get_layer_space(self, zindex, width): - """ """ + """ Return the minimum spacing of a layer given wire width. """ if width is None: width = self.get_layer_width(zindex) layer_name = self.get_layer(zindex) From 5de7b9cda7575c03c741fe03edb3f207c83de779 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 24 Jul 2023 13:07:43 -0700 Subject: [PATCH 47/91] Make graph router the default supply router --- compiler/modules/sram_1bank.py | 6 +++--- compiler/options.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 9062b421..3670438b 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -257,10 +257,10 @@ class sram_1bank(design, verilog, lef): return elif OPTS.route_supplies == "grid": from openram.router import supply_grid_router as router - elif OPTS.route_supplies == "graph": - from openram.router import graph_router as router - else: + elif OPTS.route_supplies == "tree": from openram.router import supply_tree_router as router + else: + from openram.router import graph_router as router rtr=router(layers=self.supply_stack, design=self, bbox=bbox, diff --git a/compiler/options.py b/compiler/options.py index 3a9bb3a8..2b578783 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -113,7 +113,7 @@ class options(optparse.Values): # When enabled, layout is not generated (and no DRC or LVS are performed) netlist_only = False # Whether we should do the final power routing - route_supplies = "tree" + route_supplies = "graph" supply_pin_type = "ring" # This determines whether LVS and DRC is checked at all. check_lvsdrc = False From fa74a45d8c5d8d032207838a745ab2a378858253 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 24 Jul 2023 13:08:02 -0700 Subject: [PATCH 48/91] Fix spacing rule for wider blockages --- compiler/router/graph_router.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 9166b8e1..733f2804 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -237,7 +237,6 @@ class graph_router(router_tech): def inflate_shape(self, shape, is_pin=False, is_via=False): """ Inflate a given shape with spacing rules. """ - # FIXME: Shapes might be wider than the route_tech settings # Pins must keep their center lines away from any blockage to prevent # the nodes from being unconnected if is_pin: @@ -256,6 +255,11 @@ class graph_router(router_tech): spacing = self.vert_layer_spacing else: spacing = self.horiz_layer_spacing + # If the shape is wider than the supply wire width, its spacing can be + # different + wide = min(shape.width(), shape.height()) + if wide > self.layer_widths[0]: + spacing = self.get_layer_space(self.get_zindex(shape.lpp), wide) return shape.inflated_pin(spacing=spacing, extra_spacing=self.offset) From 2b15289daf76fbc8a5bef21965243ce8e63eeb27 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 24 Jul 2023 19:21:31 -0700 Subject: [PATCH 49/91] Enable power routing for failing FreePDK45 tests (VLSIDA/PrivateRAM#97) --- compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py | 3 --- compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py | 3 --- compiler/tests/20_sram_1bank_2mux_global_test.py | 3 --- compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py | 3 --- compiler/tests/20_sram_1bank_2mux_wmask_test.py | 3 --- compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py | 3 --- compiler/tests/20_sram_1bank_4mux_test.py | 3 --- compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py | 3 --- 8 files changed, 24 deletions(-) diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py index 704204dd..71d4a3eb 100755 --- a/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_spare_cols_test.py @@ -23,9 +23,6 @@ class sram_1bank_2mux_1rw_1r_spare_cols_test(openram_test): openram.init_openram(config_file, is_unit_test=True) from openram import sram_config - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 diff --git a/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py index ae5ee11b..ce3fef2a 100755 --- a/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1w_1r_spare_cols_test.py @@ -23,9 +23,6 @@ class sram_1bank_2mux_1w_1r_spare_cols_test(openram_test): openram.init_openram(config_file, is_unit_test=True) from openram import sram_config - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - OPTS.num_rw_ports = 0 OPTS.num_w_ports = 1 OPTS.num_r_ports = 1 diff --git a/compiler/tests/20_sram_1bank_2mux_global_test.py b/compiler/tests/20_sram_1bank_2mux_global_test.py index f8b73aca..01d831ee 100755 --- a/compiler/tests/20_sram_1bank_2mux_global_test.py +++ b/compiler/tests/20_sram_1bank_2mux_global_test.py @@ -31,9 +31,6 @@ class sram_1bank_2mux_global_test(openram_test): num_spare_rows = 0 num_spare_cols = 0 - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - c = sram_config(word_size=8, num_words=32, num_banks=1, diff --git a/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py b/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py index 419390a5..0f8a1d7f 100755 --- a/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py +++ b/compiler/tests/20_sram_1bank_2mux_wmask_spare_cols_test.py @@ -30,9 +30,6 @@ class sram_1bank_2mux_wmask_spare_cols_test(openram_test): num_spare_rows = 0 num_spare_cols = 0 - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - c = sram_config(word_size=8, write_size=4, num_words=64, diff --git a/compiler/tests/20_sram_1bank_2mux_wmask_test.py b/compiler/tests/20_sram_1bank_2mux_wmask_test.py index 05d89040..cc3c2322 100755 --- a/compiler/tests/20_sram_1bank_2mux_wmask_test.py +++ b/compiler/tests/20_sram_1bank_2mux_wmask_test.py @@ -30,9 +30,6 @@ class sram_1bank_2mux_wmask_test(openram_test): num_spare_rows = 0 num_spare_cols = 0 - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - c = sram_config(word_size=8, write_size=4, num_words=64, diff --git a/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py index 1ab0d745..ffe7a673 100755 --- a/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_4mux_1rw_1r_test.py @@ -23,9 +23,6 @@ class sram_1bank_4mux_1rw_1r_test(openram_test): openram.init_openram(config_file, is_unit_test=True) from openram import sram_config - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 diff --git a/compiler/tests/20_sram_1bank_4mux_test.py b/compiler/tests/20_sram_1bank_4mux_test.py index 15f9f31d..3f1f0886 100755 --- a/compiler/tests/20_sram_1bank_4mux_test.py +++ b/compiler/tests/20_sram_1bank_4mux_test.py @@ -30,9 +30,6 @@ class sram_1bank_4mux_test(openram_test): num_spare_rows = 0 num_spare_cols = 0 - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - c = sram_config(word_size=4, num_words=64, num_banks=1, diff --git a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py index 7ce6ca8d..c95fec84 100755 --- a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py @@ -23,9 +23,6 @@ class sram_1bank_8mux_1rw_1r_test(openram_test): openram.init_openram(config_file, is_unit_test=True) from openram import sram_config - if OPTS.tech_name == "freepdk45": - OPTS.route_supplies = False - OPTS.num_rw_ports = 1 OPTS.num_r_ports = 1 OPTS.num_w_ports = 0 From 587d44e536904de6323a0440d61fb52845934fcc Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 24 Jul 2023 21:15:15 -0700 Subject: [PATCH 50/91] Include pins as blockages properly --- compiler/router/graph.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 2987bd6e..119f7b1e 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -160,10 +160,13 @@ class graph: region.lpp = blockage.lpp if region.overlaps(blockage): self.graph_blockages.append(blockage) - # FIXME: Don't include source and target if they're already included - # in inflated form + # Make sure that the source or target fake pins are included as blockage for shape in [self.source, self.target]: - if shape not in self.graph_blockages: + for blockage in self.graph_blockages: + blockage = blockage.get_inflated_from() + if shape == blockage: + break + else: self.graph_blockages.append(shape) From 47185f6085bc6cb5d0cd333ab26c34616a5de673 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 25 Jul 2023 10:18:43 -0700 Subject: [PATCH 51/91] Generate cartesian values for pins correctly --- compiler/router/graph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 119f7b1e..b54898e3 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -199,6 +199,7 @@ class graph: for shape in self.graph_blockages: if not self.is_routable(shape): continue + shape = shape.get_inflated_from() aspect_ratio = shape.width() / shape.height() # FIXME: Aspect ratio may not be the best way to determine this # If the pin is tall or fat, add two points on the ends From ed404a3ad276a4eee74029c06318478d950b6da5 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 25 Jul 2023 10:26:23 -0700 Subject: [PATCH 52/91] Rename 'inflated_from' to 'core' --- compiler/router/graph.py | 8 ++++---- compiler/router/graph_shape.py | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index b54898e3..687b682a 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -58,7 +58,7 @@ class graph: # Probe is blocked if the shape isn't routable if not self.is_routable(blockage): return True - blockage = blockage.get_inflated_from() + blockage = blockage.get_core() if blockage.overlaps(probe_shape): continue return True @@ -75,7 +75,7 @@ class graph: if not self.is_routable(blockage): blocked = True continue - blockage = blockage.get_inflated_from() + blockage = blockage.get_core() if self.inside_shape(node.center, blockage): offset = self.router.offset p = node.center @@ -163,7 +163,7 @@ class graph: # Make sure that the source or target fake pins are included as blockage for shape in [self.source, self.target]: for blockage in self.graph_blockages: - blockage = blockage.get_inflated_from() + blockage = blockage.get_core() if shape == blockage: break else: @@ -199,7 +199,7 @@ class graph: for shape in self.graph_blockages: if not self.is_routable(shape): continue - shape = shape.get_inflated_from() + shape = shape.get_core() aspect_ratio = shape.width() / shape.height() # FIXME: Aspect ratio may not be the best way to determine this # If the pin is tall or fat, add two points on the ends diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 29f1f872..f38c9f91 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -15,14 +15,15 @@ class graph_shape(pin_layout): the graph router. """ - def __init__(self, name, rect, layer_name_pp, inflated_from=None): + def __init__(self, name, rect, layer_name_pp, core=None): pin_layout.__init__(self, name, rect, layer_name_pp) # Snap the shape to the grid here ll, ur = self.rect self.rect = [snap(ll), snap(ur)] - self.inflated_from = inflated_from + # Core is the original shape from which this shape is inflated + self.core = core def center(self): @@ -43,15 +44,14 @@ class graph_shape(pin_layout): return snap(super().width()) - def get_inflated_from(self): + def get_core(self): """ - Return `self` if `self.inflated_from` is None. Otherwise, return - `self.inflated_from`. + Return `self` if `self.core` is None. Otherwise, return `self.core`. """ - if self.inflated_from is None: + if self.core is None: return self - return self.inflated_from + return self.core def inflated_pin(self, spacing=None, multiple=0.5, extra_spacing=0): From 6cda5415a4565a74bd69a28b961b35255f9b0f06 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 25 Jul 2023 18:40:07 -0700 Subject: [PATCH 53/91] Try routing in larger regions if no path is found --- compiler/router/graph.py | 6 ++++- compiler/router/graph_router.py | 43 ++++++++++++++++++++++----------- compiler/router/graph_shape.py | 11 +++++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 687b682a..cc6890cd 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -111,7 +111,7 @@ class graph: return False - def create_graph(self, source, target): + def create_graph(self, source, target, scale=1): """ Create the graph to run routing on later. """ debug.info(2, "Creating the graph for source '{}' and target'{}'.".format(source, target)) @@ -122,6 +122,7 @@ class graph: # Find the region to be routed and only include objects inside that region region = deepcopy(source) region.bbox([target]) + region.multiply(scale) region = region.inflated_pin(spacing=self.router.track_space) debug.info(3, "Routing region is {}".format(region.rect)) @@ -147,6 +148,9 @@ class graph: debug.info(3, "Number of vias detected in the routing region: {}".format(len(self.graph_vias))) debug.info(3, "Number of nodes in the routing graph: {}".format(len(self.nodes))) + # Return the region to scale later if no path is found + return region.rect + def find_graph_blockages(self, region): """ Find blockages that overlap the routing region. """ diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 733f2804..74fc63de 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -96,20 +96,35 @@ class graph_router(router_tech): pins = self.pins[pin_name] # Route closest pins according to the minimum spanning tree for source, target in self.get_mst_pairs(list(pins)): - # Create the graph - g = graph(self) - g.create_graph(source, target) - # Find the shortest path from source to target - path = g.find_shortest_path() - # TODO: Exponentially increase the routing area and retry if no - # path was found - debug.check(path is not None, "Couldn't route from {} to {}".format(source, target)) - # Create the path shapes on layout - self.add_path(path) - # Find the recently added shapes - self.prepare_gds_reader() - self.find_blockages(pin_name) - self.find_vias() + # This is the routing region scale + scale = 1 + while True: + # Create the graph + g = graph(self) + region = g.create_graph(source, target, scale) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If there is no path found, exponentially try again with a + # larger routing region + if path is None: + rll, rur = region + bll, bur = self.ring_bbox + # Stop scaling the region and throw an error + if rll.x < bll.x and rll.y < bll.y and \ + rur.x > bur.x and rur.y > bur.y: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Exponentially scale the region + scale *= 2 + debug.info(0, "Retry routing in larger routing region with scale {}".format(scale)) + continue + # Create the path shapes on layout + self.add_path(path) + # Find the recently added shapes + self.prepare_gds_reader() + self.find_blockages(pin_name) + self.find_vias() + break def prepare_gds_reader(self): diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index f38c9f91..b24c0a06 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -65,6 +65,17 @@ class graph_shape(pin_layout): return graph_shape(self.name, inflated_area, self.layer, self) + def multiply(self, scale): + """ Multiply the width and height with the scale value. """ + + width = (self.width() * (scale - 1)) / 2 + height = (self.height() * (scale - 1)) / 2 + ll, ur = self.rect + newll = vector(ll.x - width, ll.y - height) + newur = vector(ur.x + width, ur.y + height) + self.rect = [snap(newll), snap(newur)] + + def aligns(self, other): """ Return if the other shape aligns with this shape. """ From b1f4f0887e65568d2282d22d0a9ef2dbc2ae2f91 Mon Sep 17 00:00:00 2001 From: vlsida-bot Date: Wed, 26 Jul 2023 01:44:26 +0000 Subject: [PATCH 54/91] Bump version: 1.2.25 -> 1.2.26 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8060c02a..cba64f43 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.25 +1.2.26 From 62a04ce874d5236e3f617a7448192e84187f310a Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 25 Jul 2023 21:01:32 -0700 Subject: [PATCH 55/91] Add get_lpp() function to router_tech --- compiler/router/graph.py | 2 +- compiler/router/router_tech.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index cc6890cd..4a058fc9 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -50,7 +50,7 @@ class graph: This function assumes that p1 and p2 are on the same layer. """ - probe_shape = graph_probe(p1, p2, self.router.vert_lpp if p1.z else self.router.horiz_lpp) + probe_shape = graph_probe(p1, p2, self.router.get_lpp(p1.z)) # Check if any blockage blocks this probe for blockage in self.graph_blockages: # Check if two shapes overlap diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index 326a60a9..92366e3c 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -114,6 +114,14 @@ class router_tech: else: debug.error("Invalid zindex {}".format(zindex), -1) + def get_lpp(self, zindex): + if zindex == 1: + return self.vert_lpp + elif zindex == 0: + return self.horiz_lpp + else: + debug.error("Invalid zindex {}".format(zindex), -1) + def get_layer_width_space(self, zindex): """ These are the width and spacing of a supply layer given a supply rail From d609ef9243bdff2bf1c80016e8b05bb8428d47d8 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 25 Jul 2023 22:21:26 -0700 Subject: [PATCH 56/91] Make sure via probes can also be blocked by other blockages --- compiler/router/graph.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 4a058fc9..a9bb7f08 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -99,9 +99,15 @@ class graph: return blocked - def is_via_blocked(self, point): + def is_via_blocked(self, nodes): """ Return if a via on the given point is blocked by another via. """ + # If the nodes are blocked by a blockage other than a via + for node in nodes: + if self.is_node_blocked(node): + return True + # If the nodes are blocked by a via + point = node.center for via in self.graph_vias: ll, ur = via.rect center = via.center() @@ -273,7 +279,7 @@ class graph: search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left if not hasattr(self.nodes[i], "remove") and \ not hasattr(self.nodes[i + 1], "remove") and \ - not self.is_via_blocked(self.nodes[i].center): + not self.is_via_blocked(self.nodes[i:i+2]): self.nodes[i].add_neighbor(self.nodes[i + 1]) # Remove marked nodes From 53505e2ed294957071e101ed8e2fd59ac94d08bd Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 26 Jul 2023 16:41:46 -0700 Subject: [PATCH 57/91] Add "pin safe" functionality to is_node_blocked() --- compiler/router/graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index a9bb7f08..adb98ea1 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -65,7 +65,7 @@ class graph: return False - def is_node_blocked(self, node): + def is_node_blocked(self, node, pin_safe=True): """ Return if a node is blocked by a blockage. """ blocked = False @@ -92,7 +92,7 @@ class graph: safe[i] = False if not all(safe): blocked = True - elif blockage in [self.source, self.target]: + elif pin_safe and blockage in [self.source, self.target]: return False else: blocked = True @@ -104,7 +104,7 @@ class graph: # If the nodes are blocked by a blockage other than a via for node in nodes: - if self.is_node_blocked(node): + if self.is_node_blocked(node, pin_safe=False): return True # If the nodes are blocked by a via point = node.center From 4d835e98b701d944331690bf8c9a9fd55e681cc2 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 26 Jul 2023 16:44:57 -0700 Subject: [PATCH 58/91] Simplify merging new shapes in graph router --- compiler/router/graph_router.py | 72 ++++++++++++--------------------- compiler/router/graph_shape.py | 14 +++++++ 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 74fc63de..d7075831 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -136,6 +136,23 @@ class graph_router(router_tech): self.reader.loadFromFile(self.gds_filename) + def merge_shapes(self, merger, shape_list): + """ + Merge shapes in the list into the merger if they are contained or + aligned by the merger. + """ + + merger_core = merger.get_core() + for shape in list(shape_list): + shape_core = shape.get_core() + if merger_core.contains(shape_core): + shape_list.remove(shape) + elif merger_core.aligns(shape_core): + merger.bbox([shape]) + merger_core.bbox([shape_core]) + shape_list.remove(shape) + + def find_pins(self, pin_name): """ Find the pins with the given name. """ debug.info(2, "Finding all pins for {}".format(pin_name)) @@ -150,15 +167,10 @@ class graph_router(router_tech): rect = [ll, ur] new_pin = graph_shape(pin_name, rect, layer) # Skip this pin if it's contained by another pin of the same type - if new_pin.contained_by_any(pin_set): + if new_pin.core_contained_by_any(pin_set): continue # Remove any previous pin of the same type contained by this new pin - for pin in list(pin_set): - if new_pin.contains(pin): - pin_set.remove(pin) - elif new_pin.aligns(pin): - new_pin.bbox([pin]) - pin_set.remove(pin) + self.merge_shapes(new_pin, pin_set) pin_set.add(new_pin) # Add these pins to the 'pins' dict self.pins[pin_name] = pin_set @@ -169,10 +181,6 @@ class graph_router(router_tech): """ Find all blockages in the routing layers. """ debug.info(2, "Finding blockages...") - # Keep current blockages here - prev_blockages = self.blockages[:] - - blockages = [] for lpp in [self.vert_lpp, self.horiz_lpp]: shapes = self.layout.getAllShapes(lpp) for boundary in shapes: @@ -181,52 +189,24 @@ class graph_router(router_tech): ur = vector(boundary[2], boundary[3]) rect = [ll, ur] if shape_name is None: - name = "blockage{}".format(len(blockages)) + name = "blockage" else: name = shape_name new_shape = graph_shape(name, rect, lpp) + new_shape = self.inflate_shape(new_shape) # If there is a rectangle that is the same in the pins, # it isn't a blockage # Also ignore the new pins - if new_shape.contained_by_any(self.all_pins) or \ - new_shape.contained_by_any(prev_blockages) or \ - new_shape.contained_by_any(blockages): + if new_shape.core_contained_by_any(self.all_pins) or \ + new_shape.core_contained_by_any(self.blockages): continue # Remove blockages contained by this new blockage - for i in range(len(blockages) - 1, -1, -1): - blockage = blockages[i] - # Remove the previous blockage contained by this new - # blockage - if new_shape.contains(blockage): - blockages.remove(blockage) - # Merge the previous blockage into this new blockage if - # they are aligning - elif new_shape.aligns(blockage): - new_shape.bbox([blockage]) - blockages.remove(blockage) - blockages.append(new_shape) - - # Inflate the shapes to prevent DRC errors - for blockage in blockages: - self.blockages.append(self.inflate_shape(blockage)) - # Remove blockages contained by this new blockage - for i in range(len(prev_blockages) - 1, -1, -1): - prev_blockage = prev_blockages[i] - # Remove the previous blockage contained by this new - # blockage - if blockage.contains(prev_blockage): - prev_blockages.remove(prev_blockage) - self.blockages.remove(prev_blockage) - # Merge the previous blockage into this new blockage if - # they are aligning - elif blockage.aligns(prev_blockage): - blockage.bbox([prev_blockage]) - prev_blockages.remove(prev_blockage) - self.blockages.remove(prev_blockage) + self.merge_shapes(new_shape, self.blockages) + self.blockages.append(new_shape) def find_vias(self): - """ """ + """ Find all vias in the routing layers. """ debug.info(2, "Finding vias...") # Prepare lpp values here diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index b24c0a06..13a572c1 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -76,6 +76,20 @@ class graph_shape(pin_layout): self.rect = [snap(newll), snap(newur)] + def core_contained_by_any(self, shape_list): + """ + Return if the core of this shape is contained by any shape's core in the + given list. + """ + + self_core = self.get_core() + for shape in shape_list: + shape_core = shape.get_core() + if shape_core.contains(self_core): + return True + return False + + def aligns(self, other): """ Return if the other shape aligns with this shape. """ From 8522e0108c9328606142e83c506040c96118bf17 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 26 Jul 2023 21:45:35 -0700 Subject: [PATCH 59/91] Add center nodes for existing vias --- compiler/router/graph.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index adb98ea1..e4afc813 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -234,6 +234,12 @@ class graph: x_values.update([nll.x, nur.x]) y_values.update([nll.y, nur.y]) + # Add center values for existing vias + for via in self.graph_vias: + point = via.center() + x_values.add(p.x) + y_values.add(p.y) + # Sort x and y values x_values = list(x_values) y_values = list(y_values) From 091d0f8775591b8247287427d81a4b3f75789381 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 26 Jul 2023 21:46:30 -0700 Subject: [PATCH 60/91] Convert pins and blockages for graph router --- compiler/router/graph_router.py | 38 +++++++++++++++++++++++++++++++++ compiler/router/graph_shape.py | 7 ++++++ 2 files changed, 45 insertions(+) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index d7075831..1423870e 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -75,6 +75,10 @@ class graph_router(router_tech): self.find_blockages() self.find_vias() + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + # Add side pins self.calculate_ring_bbox() if self.pin_type in ["top", "bottom", "right", "left"]: @@ -229,6 +233,40 @@ class graph_router(router_tech): self.vias.append(self.inflate_shape(new_shape, is_via=True)) + def convert_vias(self): + """ Convert the vias that overlap a pin. """ + + for via in self.vias: + via_core = via.get_core() + for pin in self.all_pins: + pin_core = pin.get_core() + via_core.lpp = pin_core.lpp + if via_core.overlaps(pin_core): + via.rename(pin.name) + break + + + def convert_blockages(self): + """ Convert the blockages that overlap a pin. """ + + for blockage in self.blockages: + blockage_core = blockage.get_core() + for pin in self.all_pins: + pin_core = pin.get_core() + if blockage_core.overlaps(pin_core): + blockage.rename(pin.name) + break + else: + for via in self.vias: + if via.name == "via": + continue + via_core = via.get_core() + via_core.lpp = blockage_core.lpp + if blockage_core.overlaps(via_core): + blockage.rename(via.name) + break + + def inflate_shape(self, shape, is_pin=False, is_via=False): """ Inflate a given shape with spacing rules. """ diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 13a572c1..1221dabe 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -44,6 +44,13 @@ class graph_shape(pin_layout): return snap(super().width()) + def rename(self, new_name): + """ Change the name of `self` and `self.core`. """ + + self.name = new_name + self.get_core().name = new_name + + def get_core(self): """ Return `self` if `self.core` is None. Otherwise, return `self.core`. From 4c73d3aa7c0f8e293f5fc4d5c33414ac06cc08c5 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 29 Jul 2023 08:17:00 -0700 Subject: [PATCH 61/91] Use safe regions to protect pin nodes --- compiler/router/graph.py | 71 +++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index e4afc813..9cbf8292 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -43,6 +43,30 @@ class graph: return shape.on_segment(ll, point, ur) + def get_safe_pin_values(self, pin): + """ Get the safe x and y values of the given pin. """ + + pin = pin.get_core() + offset = self.router.offset + spacing = self.router.track_space + size_limit = snap(offset * 4 + spacing) + + x_values = [] + y_values = [] + if pin.width() > size_limit: + x_values.append(snap(pin.lx() + offset)) + x_values.append(snap(pin.rx() - offset)) + else: + x_values.append(snap(pin.cx())) + if pin.height() > size_limit: + y_values.append(snap(pin.by() + offset)) + y_values.append(snap(pin.uy() - offset)) + else: + y_values.append(snap(pin.cy())) + + return x_values, y_values + + def is_probe_blocked(self, p1, p2): """ Return if a probe sent from p1 to p2 encounters a blockage. @@ -68,6 +92,17 @@ class graph: def is_node_blocked(self, node, pin_safe=True): """ Return if a node is blocked by a blockage. """ + def closest(value, checklist): + """ Return the distance of the closest value in the checklist. """ + min_diff = float("inf") + for other in checklist: + diff = snap(abs(value - other)) + if diff < min_diff: + min_diff = diff + return min_diff + + offset = self.router.offset + spacing = self.router.track_space + offset + drc["grid"] blocked = False for blockage in self.graph_blockages: # Check if two shapes overlap @@ -77,7 +112,6 @@ class graph: continue blockage = blockage.get_core() if self.inside_shape(node.center, blockage): - offset = self.router.offset p = node.center lengths = [blockage.width(), blockage.height()] centers = blockage.center() @@ -92,8 +126,15 @@ class graph: safe[i] = False if not all(safe): blocked = True - elif pin_safe and blockage in [self.source, self.target]: - return False + continue + xs, ys = self.get_safe_pin_values(blockage) + xdiff = closest(p.x, xs) + ydiff = closest(p.y, ys) + if xdiff == 0 and ydiff == 0: + if pin_safe and blockage in [self.source, self.target]: + return False + elif xdiff < spacing and ydiff < spacing: + blocked = False else: blocked = True return blocked @@ -204,25 +245,15 @@ class graph: y_values = set() # Add inner values for blockages of the routed type - x_offset = vector(self.router.offset, 0) - y_offset = vector(0, self.router.offset) + offset = self.router.offset + spacing = self.router.track_space + size_limit = snap(offset * 4 + spacing) for shape in self.graph_blockages: if not self.is_routable(shape): continue - shape = shape.get_core() - aspect_ratio = shape.width() / shape.height() - # FIXME: Aspect ratio may not be the best way to determine this - # If the pin is tall or fat, add two points on the ends - if aspect_ratio <= 0.5: # Tall pin - points = [shape.bc() + y_offset, shape.uc() - y_offset] - elif aspect_ratio >= 2: # Fat pin - points = [shape.lc() + x_offset, shape.rc() - x_offset] - else: # Square-like pin - points = [shape.center()] - for p in points: - p = snap(p) - x_values.add(p.x) - y_values.add(p.y) + xs, ys = self.get_safe_pin_values(shape) + x_values.update(xs) + y_values.update(ys) # Add corners for blockages offset = vector(drc["grid"], drc["grid"]) @@ -236,7 +267,7 @@ class graph: # Add center values for existing vias for via in self.graph_vias: - point = via.center() + p = via.center() x_values.add(p.x) y_values.add(p.y) From 3b0997e7cf95752acfcc0d275feac56b371e5563 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 29 Jul 2023 08:18:49 -0700 Subject: [PATCH 62/91] Implement custom add_route() for the graph router --- compiler/router/graph_router.py | 40 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 1423870e..27a903ff 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -486,10 +486,8 @@ class graph_router(router_tech): def add_path(self, path): """ Add the route path to the layout. """ - coordinates = self.prepare_path(path) - self.design.add_route(layers=self.layers, - coordinates=coordinates, - layer_widths=self.layer_widths) + nodes = self.prepare_path(path) + self.add_route(nodes) def prepare_path(self, path): @@ -499,7 +497,7 @@ class graph_router(router_tech): """ last_added = path[0] - coordinates = [path[0].center] + nodes = [path[0]] direction = path[0].get_direction(path[1]) candidate = path[1] for i in range(2, len(path)): @@ -511,12 +509,36 @@ class graph_router(router_tech): candidate = node else: last_added = candidate - coordinates.append(candidate.center) + nodes.append(candidate) direction = current_direction candidate = node - if candidate.center not in coordinates: - coordinates.append(candidate.center) - return coordinates + if candidate not in nodes: + nodes.append(candidate) + return nodes + + + def add_route(self, nodes): + """ + Custom `add_route` function since `hierarchy_layout.add_route` isn't + working for this router. + """ + + for i in range(0, len(nodes) - 1): + start = nodes[i].center + end = nodes[i + 1].center + direction = nodes[i].get_direction(nodes[i + 1]) + diff = start - end + offset = start.min(end) + offset = vector(offset.x - self.offset, offset.y - self.offset) + if direction == (1, 1): # Via + offset = vector(start.x, start.y) + self.design.add_via_center(layers=self.layers, + offset=offset) + else: # Wire + self.design.add_rect(layer=self.get_layer(start.z), + offset=offset, + width=abs(diff.x) + self.track_wire, + height=abs(diff.y) + self.track_wire) def get_new_pins(self, name): From 821c763a1e06cf787284d94e6f0487528e46ff51 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 29 Jul 2023 20:01:58 -0700 Subject: [PATCH 63/91] Cleanup graph router --- compiler/router/graph.py | 12 +++++-- compiler/router/graph_router.py | 64 ++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 9cbf8292..d65f8d48 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -46,6 +46,7 @@ class graph: def get_safe_pin_values(self, pin): """ Get the safe x and y values of the given pin. """ + # Constant values pin = pin.get_core() offset = self.router.offset spacing = self.router.track_space @@ -53,6 +54,9 @@ class graph: x_values = [] y_values = [] + # If one axis size of the pin is greater than the limit, we will take + # two points at both ends. Otherwise, we will only take the center of + # this pin. if pin.width() > size_limit: x_values.append(snap(pin.lx() + offset)) x_values.append(snap(pin.rx() - offset)) @@ -105,14 +109,16 @@ class graph: spacing = self.router.track_space + offset + drc["grid"] blocked = False for blockage in self.graph_blockages: - # Check if two shapes overlap + # Check if the node is inside the blockage if self.inside_shape(node.center, blockage): if not self.is_routable(blockage): blocked = True continue blockage = blockage.get_core() + # Check if the node is inside the blockage's core if self.inside_shape(node.center, blockage): p = node.center + # Check if the node is too close to one edge of the shape lengths = [blockage.width(), blockage.height()] centers = blockage.center() ll, ur = blockage.rect @@ -127,6 +133,7 @@ class graph: if not all(safe): blocked = True continue + # Check if the node is in a safe region of the shape xs, ys = self.get_safe_pin_values(blockage) xdiff = closest(p.x, xs) ydiff = closest(p.y, ys) @@ -251,6 +258,7 @@ class graph: for shape in self.graph_blockages: if not self.is_routable(shape): continue + # Get the safe pin values xs, ys = self.get_safe_pin_values(shape) x_values.update(xs) y_values.update(ys) @@ -259,9 +267,9 @@ class graph: offset = vector(drc["grid"], drc["grid"]) for blockage in self.graph_blockages: ll, ur = blockage.rect + # Add minimum offset to the blockage corner nodes to prevent overlap nll = snap(ll - offset) nur = snap(ur + offset) - # Add minimum offset to the blockage corner nodes to prevent overlap x_values.update([nll.x, nur.x]) y_values.update([nll.y, nur.y]) diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 27a903ff..b7bb81cb 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -53,7 +53,7 @@ class graph_router(router_tech): self.fake_pins = [] # Set the offset here - self.offset = snap(self.layer_widths[0] / 2) + self.offset = snap(self.track_wire / 2) def route(self, vdd_name="vdd", gnd_name="gnd"): @@ -149,8 +149,11 @@ class graph_router(router_tech): merger_core = merger.get_core() for shape in list(shape_list): shape_core = shape.get_core() + # If merger contains the shape, remove it from the list if merger_core.contains(shape_core): shape_list.remove(shape) + # If the merger aligns with the shape, expand the merger and remove + # the shape from the list elif merger_core.aligns(shape_core): merger.bbox([shape]) merger_core.bbox([shape_core]) @@ -173,7 +176,7 @@ class graph_router(router_tech): # Skip this pin if it's contained by another pin of the same type if new_pin.core_contained_by_any(pin_set): continue - # Remove any previous pin of the same type contained by this new pin + # Merge previous pins into this one if possible self.merge_shapes(new_pin, pin_set) pin_set.add(new_pin) # Add these pins to the 'pins' dict @@ -181,7 +184,7 @@ class graph_router(router_tech): self.all_pins.update(pin_set) - def find_blockages(self, shape_name=None): + def find_blockages(self, name="blockage"): """ Find all blockages in the routing layers. """ debug.info(2, "Finding blockages...") @@ -192,19 +195,14 @@ class graph_router(router_tech): ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - if shape_name is None: - name = "blockage" - else: - name = shape_name new_shape = graph_shape(name, rect, lpp) new_shape = self.inflate_shape(new_shape) - # If there is a rectangle that is the same in the pins, - # it isn't a blockage - # Also ignore the new pins + # Skip this blockage if it's contained by a pin or an existing + # blockage if new_shape.core_contained_by_any(self.all_pins) or \ new_shape.core_contained_by_any(self.blockages): continue - # Remove blockages contained by this new blockage + # Merge previous blockages into this one if possible self.merge_shapes(new_shape, self.blockages) self.blockages.append(new_shape) @@ -225,43 +223,47 @@ class graph_router(router_tech): ur = vector(boundary[2], boundary[3]) rect = [ll, ur] new_shape = graph_shape("via", rect, valid_lpp) - # If there is a rectangle that is the same in the pins, - # it isn't a blockage - # Also ignore the new pins + # Skip this via if it's contained by an existing via blockage if new_shape.contained_by_any(self.vias): continue self.vias.append(self.inflate_shape(new_shape, is_via=True)) def convert_vias(self): - """ Convert the vias that overlap a pin. """ + """ Convert vias that overlap a pin. """ for via in self.vias: via_core = via.get_core() for pin in self.all_pins: pin_core = pin.get_core() via_core.lpp = pin_core.lpp + # If the via overlaps a pin, change its name if via_core.overlaps(pin_core): via.rename(pin.name) break def convert_blockages(self): - """ Convert the blockages that overlap a pin. """ + """ Convert blockages that overlap a pin. """ + # NOTE: You need to run `convert_vias()` before since a blockage may + # be connected to a pin through a via. for blockage in self.blockages: blockage_core = blockage.get_core() for pin in self.all_pins: pin_core = pin.get_core() + # If the blockage overlaps a pin, change its name if blockage_core.overlaps(pin_core): blockage.rename(pin.name) break else: for via in self.vias: + # Skip if this via isn't connected to a pin if via.name == "via": continue via_core = via.get_core() via_core.lpp = blockage_core.lpp + # If the blockage overlaps a pin via, change its name if blockage_core.overlaps(via_core): blockage.rename(via.name) break @@ -297,11 +299,14 @@ class graph_router(router_tech): extra_spacing=self.offset) - def calculate_ring_bbox(self, width=3): + def calculate_ring_bbox(self, num_vias=3): """ Calculate the ring-safe bounding box of the layout. """ ll, ur = self.design.get_bbox() - wideness = self.track_wire * width + self.track_space * (width - 1) + # Calculate the "wideness" of a side supply pin + wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) + # Total wideness is used to find it any pin overlaps in this region. If + # so, the bbox is shifted to prevent this overlap. total_wideness = wideness * 4 for blockage in self.blockages: bll, bur = blockage.rect @@ -322,7 +327,7 @@ class graph_router(router_tech): self.ring_bbox = [ll, ur] - def add_side_pin(self, pin_name, side, width=3, num_connects=4): + def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): """ Add supply pin to one side of the layout. """ ll, ur = self.ring_bbox @@ -330,7 +335,7 @@ class graph_router(router_tech): inner = pin_name == self.gnd_name # Calculate wires' wideness - wideness = self.track_wire * width + self.track_space * (width - 1) + wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) # Calculate the offset for the inner ring if inner: @@ -373,12 +378,12 @@ class graph_router(router_tech): # Add fake pins on this new pin evenly fake_pins = [] if vertical: - space = (shape_height - (2 * wideness) - num_connects * self.track_wire) / (num_connects + 1) + space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) start_offset = vector(offset.x, offset.y + wideness) else: - space = (shape_width - (2 * wideness) - num_connects * self.track_wire) / (num_connects + 1) + space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) start_offset = vector(offset.x + wideness, offset.y) - for i in range(1, num_connects + 1): + for i in range(1, num_fake_pins + 1): if vertical: offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) ll = vector(offset.x, offset.y - self.track_wire) @@ -395,13 +400,13 @@ class graph_router(router_tech): return pin, fake_pins - def add_ring_pin(self, pin_name, width=3, num_connects=4): + def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): """ Add the supply ring to the layout. """ # Add side pins new_pins = [] for side in ["top", "bottom", "right", "left"]: - new_shape, fake_pins = self.add_side_pin(pin_name, side, width, num_connects) + new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) ll, ur = new_shape.rect rect = [ll, ur] layer = self.get_layer(side in ["left", "right"]) @@ -418,11 +423,11 @@ class graph_router(router_tech): for i in range(4): ll, ur = new_pins[i].rect if i % 2: - top_left = vector(ur.x - (width - 1) * shift - half_wide, ll.y + (width - 1) * shift + half_wide) + top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide) else: top_left = vector(ll.x + half_wide, ur.y - half_wide) - for j in range(width): - for k in range(width): + for j in range(num_vias): + for k in range(num_vias): offset = vector(top_left.x + j * shift, top_left.y - k * shift) self.design.add_via_center(layers=self.layers, offset=offset) @@ -572,6 +577,9 @@ class graph_router(router_tech): self.design.add_label(text="n{}".format(node.center.z), layer="text", offset=offset) + #debug.info(0, "Neighbors of {}".format(node.center)) + #for neighbor in node.neighbors: + # debug.info(0, " {}".format(neighbor.center)) else: for blockage in self.blockages: self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) From d487f788e38dec191e40b96f55b5a28b25695d94 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sat, 29 Jul 2023 21:48:33 -0700 Subject: [PATCH 64/91] Add constant cost for all non-preferred edges --- compiler/router/graph_node.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/router/graph_node.py b/compiler/router/graph_node.py index 78642b22..acb40d6f 100644 --- a/compiler/router/graph_node.py +++ b/compiler/router/graph_node.py @@ -63,14 +63,12 @@ class graph_node: if other in self.neighbors: is_vertical = self.center.x == other.center.x layer_dist = self.center.distance(other.center) - # Edge is on non-preferred direction + # Double the cost if the edge is in non-preferred direction if is_vertical != bool(self.center.z): layer_dist *= 2 - # Wire bends towards non-preferred direction - # NOTE: Adding a fixed cost prevents multiple dog-legs in - # non-preferred direction - if prev_node and self.get_direction(prev_node) != self.get_direction(other): - layer_dist += drc["grid"] - via_dist = abs(self.center.z - other.center.z) * 2 + # Add a constant wire cost to prevent dog-legs + if prev_node and self.get_direction(prev_node) != self.get_direction(other): + layer_dist += drc["grid"] + via_dist = abs(self.center.z - other.center.z) return layer_dist + via_dist return float("inf") From e5bc7b4e95037c8507ea943aa00b82067717f8c4 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 30 Jul 2023 10:08:57 -0700 Subject: [PATCH 65/91] Fix logic typo --- compiler/router/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index d65f8d48..36f455ef 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -140,7 +140,7 @@ class graph: if xdiff == 0 and ydiff == 0: if pin_safe and blockage in [self.source, self.target]: return False - elif xdiff < spacing and ydiff < spacing: + elif xdiff < spacing or ydiff < spacing: blocked = False else: blocked = True From 5cf774b53e846c183355c0bbd0b5840f52b16fd7 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 30 Jul 2023 10:09:13 -0700 Subject: [PATCH 66/91] Remove unnecessary lines --- compiler/router/graph.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 36f455ef..476026ba 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -252,9 +252,6 @@ class graph: y_values = set() # Add inner values for blockages of the routed type - offset = self.router.offset - spacing = self.router.track_space - size_limit = snap(offset * 4 + spacing) for shape in self.graph_blockages: if not self.is_routable(shape): continue From 4b2659a5e2b6651bab0863f5a4f842491e19694a Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Sun, 30 Jul 2023 18:27:17 -0700 Subject: [PATCH 67/91] Fix another logic typo --- compiler/router/graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 476026ba..3af19ea9 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -141,7 +141,7 @@ class graph: if pin_safe and blockage in [self.source, self.target]: return False elif xdiff < spacing or ydiff < spacing: - blocked = False + blocked = True else: blocked = True return blocked From 7be6f2783b41698926a08a2d92088bc24df4333f Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 31 Jul 2023 12:24:31 -0700 Subject: [PATCH 68/91] Refix logic mistake in graph router --- compiler/router/graph.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 3af19ea9..de0139f8 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -105,8 +105,9 @@ class graph: min_diff = diff return min_diff + wide = self.router.track_wire offset = self.router.offset - spacing = self.router.track_space + offset + drc["grid"] + spacing = snap(self.router.track_space + offset + drc["grid"]) blocked = False for blockage in self.graph_blockages: # Check if the node is inside the blockage @@ -124,7 +125,7 @@ class graph: ll, ur = blockage.rect safe = [True, True] for i in range(2): - if lengths[i] >= offset * 2: + if lengths[i] >= wide: min_diff = snap(min(abs(ll[i] - p[i]), abs(ur[i] - p[i]))) if min_diff < offset: safe[i] = False @@ -140,7 +141,7 @@ class graph: if xdiff == 0 and ydiff == 0: if pin_safe and blockage in [self.source, self.target]: return False - elif xdiff < spacing or ydiff < spacing: + elif xdiff < spacing and ydiff < spacing: blocked = True else: blocked = True From db2a276077a072b485caee53b9bf404cf0dc6068 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 31 Jul 2023 19:43:09 -0700 Subject: [PATCH 69/91] Split graph router class to use it for signal escaping later --- compiler/modules/sram_1bank.py | 2 +- compiler/router/__init__.py | 2 +- compiler/router/graph_router.py | 282 +---------------------- compiler/router/supply_graph_router.py | 298 +++++++++++++++++++++++++ 4 files changed, 302 insertions(+), 282 deletions(-) create mode 100644 compiler/router/supply_graph_router.py diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 3670438b..799c7b0d 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -260,7 +260,7 @@ class sram_1bank(design, verilog, lef): elif OPTS.route_supplies == "tree": from openram.router import supply_tree_router as router else: - from openram.router import graph_router as router + from openram.router import supply_graph_router as router rtr=router(layers=self.supply_stack, design=self, bbox=bbox, diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index af90ccba..6fa3774c 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -8,4 +8,4 @@ from .signal_escape_router import * from .signal_router import * from .supply_grid_router import * from .supply_tree_router import * -from .graph_router import * +from .supply_graph_router import * diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index b7bb81cb..42886b29 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -5,25 +5,22 @@ # from openram import debug 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 drc from openram.tech import layer as tech_layer from openram import OPTS from .router_tech import router_tech -from .graph import graph from .graph_shape import graph_shape from .graph_utils import snap class graph_router(router_tech): """ - This is the router class that uses the Hanan grid method to route pins using - a graph. + This is the base class for routers that use the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None, pin_type=None): + def __init__(self, layers, design, bbox=None): # `router_tech` contains tech constants for the router router_tech.__init__(self, layers, route_track_width=1) @@ -32,9 +29,6 @@ class graph_router(router_tech): self.layers = layers # This is the `hierarchy_layout` object self.design = design - # Side supply pin type - # (can be "top", "bottom", "right", "left", and "ring") - self.pin_type = pin_type # Temporary GDSII file name to find pins and blockages self.gds_filename = OPTS.openram_temp + "temp.gds" # Dictionary for vdd and gnd pins @@ -46,8 +40,6 @@ class graph_router(router_tech): self.blockages = [] # This is all the vias between routing layers self.vias = [] - # New pins are the side supply pins - self.new_pins = {} # Fake pins are imaginary pins on the side supply pins to route other # pins to them self.fake_pins = [] @@ -56,81 +48,6 @@ class graph_router(router_tech): self.offset = snap(self.track_wire / 2) - def route(self, vdd_name="vdd", gnd_name="gnd"): - """ Route the given pins in the given order. """ - debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) - - # Save pin names - self.vdd_name = vdd_name - self.gnd_name = gnd_name - - # Prepare gdsMill to find pins and blockages - self.prepare_gds_reader() - - # Find pins to be routed - self.find_pins(vdd_name) - self.find_pins(gnd_name) - - # Find blockages and vias - self.find_blockages() - self.find_vias() - - # Convert blockages and vias if they overlap a pin - self.convert_vias() - self.convert_blockages() - - # Add side pins - self.calculate_ring_bbox() - if self.pin_type in ["top", "bottom", "right", "left"]: - self.add_side_pin(vdd_name) - self.add_side_pin(gnd_name) - elif self.pin_type == "ring": - self.add_ring_pin(vdd_name) - self.add_ring_pin(gnd_name) - else: - debug.warning("Side supply pins aren't created.") - - # Add vdd and gnd pins as blockages as well - # NOTE: This is done to make vdd and gnd pins DRC-safe - for pin in self.all_pins: - self.blockages.append(self.inflate_shape(pin, is_pin=True)) - - # Route vdd and gnd - for pin_name in [vdd_name, gnd_name]: - pins = self.pins[pin_name] - # Route closest pins according to the minimum spanning tree - for source, target in self.get_mst_pairs(list(pins)): - # This is the routing region scale - scale = 1 - while True: - # Create the graph - g = graph(self) - region = g.create_graph(source, target, scale) - # Find the shortest path from source to target - path = g.find_shortest_path() - # If there is no path found, exponentially try again with a - # larger routing region - if path is None: - rll, rur = region - bll, bur = self.ring_bbox - # Stop scaling the region and throw an error - if rll.x < bll.x and rll.y < bll.y and \ - rur.x > bur.x and rur.y > bur.y: - self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) - debug.error("Couldn't route from {} to {}.".format(source, target), -1) - # Exponentially scale the region - scale *= 2 - debug.info(0, "Retry routing in larger routing region with scale {}".format(scale)) - continue - # Create the path shapes on layout - self.add_path(path) - # Find the recently added shapes - self.prepare_gds_reader() - self.find_blockages(pin_name) - self.find_vias() - break - - def prepare_gds_reader(self): """ Write the current layout to a temporary file to read the layout. """ @@ -299,195 +216,6 @@ class graph_router(router_tech): extra_spacing=self.offset) - def calculate_ring_bbox(self, num_vias=3): - """ Calculate the ring-safe bounding box of the layout. """ - - ll, ur = self.design.get_bbox() - # Calculate the "wideness" of a side supply pin - wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) - # Total wideness is used to find it any pin overlaps in this region. If - # so, the bbox is shifted to prevent this overlap. - total_wideness = wideness * 4 - for blockage in self.blockages: - bll, bur = blockage.rect - if self.get_zindex(blockage.lpp) == 1: # Vertical - diff = ll.x + total_wideness - bll.x - if diff > 0: - ll = vector(ll.x - diff, ll.y) - diff = ur.x - total_wideness - bur.x - if diff < 0: - ur = vector(ur.x - diff, ur.y) - else: # Horizontal - diff = ll.y + total_wideness - bll.y - if diff > 0: - ll = vector(ll.x, ll.y - diff) - diff = ur.y - total_wideness - bur.y - if diff < 0: - ur = vector(ur.x, ur.y - diff) - self.ring_bbox = [ll, ur] - - - def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): - """ Add supply pin to one side of the layout. """ - - ll, ur = self.ring_bbox - vertical = side in ["left", "right"] - inner = pin_name == self.gnd_name - - # Calculate wires' wideness - wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) - - # Calculate the offset for the inner ring - if inner: - margin = wideness * 2 - else: - margin = 0 - - # Calculate the lower left coordinate - if side == "top": - offset = vector(ll.x + margin, ur.y - wideness - margin) - elif side == "bottom": - offset = vector(ll.x + margin, ll.y + margin) - elif side == "left": - offset = vector(ll.x + margin, ll.y + margin) - elif side == "right": - offset = vector(ur.x - wideness - margin, ll.y + margin) - - # Calculate width and height - shape = ur - ll - if vertical: - shape_width = wideness - shape_height = shape.y - else: - shape_width = shape.x - shape_height = wideness - if inner: - if vertical: - shape_height -= margin * 2 - else: - shape_width -= margin * 2 - - # Add this new pin - layer = self.get_layer(int(vertical)) - pin = self.design.add_layout_pin(text=pin_name, - layer=layer, - offset=offset, - width=shape_width, - height=shape_height) - - # Add fake pins on this new pin evenly - fake_pins = [] - if vertical: - space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) - start_offset = vector(offset.x, offset.y + wideness) - else: - space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) - start_offset = vector(offset.x + wideness, offset.y) - for i in range(1, num_fake_pins + 1): - if vertical: - offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) - ll = vector(offset.x, offset.y - self.track_wire) - ur = vector(offset.x + wideness, offset.y) - else: - offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y) - ll = vector(offset.x - self.track_wire, offset.y) - ur = vector(offset.x, offset.y + wideness) - rect = [ll, ur] - fake_pin = graph_shape(name=pin_name, - rect=rect, - layer_name_pp=layer) - fake_pins.append(fake_pin) - return pin, fake_pins - - - def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): - """ Add the supply ring to the layout. """ - - # Add side pins - new_pins = [] - for side in ["top", "bottom", "right", "left"]: - new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) - ll, ur = new_shape.rect - rect = [ll, ur] - layer = self.get_layer(side in ["left", "right"]) - new_pin = graph_shape(name=pin_name, - rect=rect, - layer_name_pp=layer) - new_pins.append(new_pin) - self.pins[pin_name].update(fake_pins) - self.fake_pins.extend(fake_pins) - - # Add vias to the corners - shift = self.track_wire + self.track_space - half_wide = self.track_wire / 2 - for i in range(4): - ll, ur = new_pins[i].rect - if i % 2: - top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide) - else: - top_left = vector(ll.x + half_wide, ur.y - half_wide) - for j in range(num_vias): - for k in range(num_vias): - offset = vector(top_left.x + j * shift, top_left.y - k * shift) - self.design.add_via_center(layers=self.layers, - offset=offset) - - # Save side pins for routing - self.new_pins[pin_name] = new_pins - for pin in new_pins: - self.blockages.append(self.inflate_shape(pin, is_pin=True)) - - - def get_mst_pairs(self, pins): - """ - Return the pin pairs from the minimum spanning tree in a graph that - connects all pins together. - """ - - pin_count = len(pins) - - # Create an adjacency matrix that connects all pins - edges = [[0] * pin_count for i in range(pin_count)] - for i in range(pin_count): - for j in range(pin_count): - # Skip if they're the same pin - if i == j: - continue - # Skip if both pins are fake - if pins[i] in self.fake_pins and pins[j] in self.fake_pins: - continue - edges[i][j] = pins[i].distance(pins[j]) - - pin_connected = [False] * pin_count - pin_connected[0] = True - - # Add the minimum cost edge in each iteration (Prim's) - mst_pairs = [] - for i in range(pin_count - 1): - min_cost = float("inf") - s = 0 - t = 0 - # Iterate over already connected pins - for m in range(pin_count): - # Skip if not connected - if not pin_connected[m]: - continue - # Iterate over this pin's neighbors - for n in range(pin_count): - # Skip if already connected or isn't a neighbor - if pin_connected[n] or edges[m][n] == 0: - continue - # Choose this edge if it's better the the current one - if edges[m][n] < min_cost: - min_cost = edges[m][n] - s = m - t = n - pin_connected[t] = True - mst_pairs.append((pins[s], pins[t])) - - return mst_pairs - - def add_path(self, path): """ Add the route path to the layout. """ @@ -546,12 +274,6 @@ class graph_router(router_tech): height=abs(diff.y) + self.track_wire) - def get_new_pins(self, name): - """ Return the new supply pins added by this router. """ - - return self.new_pins[name] - - def write_debug_gds(self, gds_name="debug_route.gds", g=None, source=None, target=None): """ Write the debug GDSII file for the router. """ diff --git a/compiler/router/supply_graph_router.py b/compiler/router/supply_graph_router.py new file mode 100644 index 00000000..d3506108 --- /dev/null +++ b/compiler/router/supply_graph_router.py @@ -0,0 +1,298 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from openram import OPTS +from .graph import graph +from .graph_router import graph_router +from .graph_shape import graph_shape + + +class supply_graph_router(graph_router): + """ + This is the supply router that uses the Hanan grid graph method. + """ + + def __init__(self, layers, design, bbox=None, pin_type=None): + + # `router_tech` contains tech constants for the router + graph_router.__init__(self, layers, design, bbox) + + # Side supply pin type + # (can be "top", "bottom", "right", "left", and "ring") + self.pin_type = pin_type + # New pins are the side supply pins + self.new_pins = {} + + + def route(self, vdd_name="vdd", gnd_name="gnd"): + """ Route the given pins in the given order. """ + debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) + + # Save pin names + self.vdd_name = vdd_name + self.gnd_name = gnd_name + + # Prepare gdsMill to find pins and blockages + self.prepare_gds_reader() + + # Find pins to be routed + self.find_pins(vdd_name) + self.find_pins(gnd_name) + + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + # Add side pins + self.calculate_ring_bbox() + if self.pin_type in ["top", "bottom", "right", "left"]: + self.add_side_pin(vdd_name) + self.add_side_pin(gnd_name) + elif self.pin_type == "ring": + self.add_ring_pin(vdd_name) + self.add_ring_pin(gnd_name) + else: + debug.warning("Side supply pins aren't created.") + + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(self.inflate_shape(pin, is_pin=True)) + + # Route vdd and gnd + for pin_name in [vdd_name, gnd_name]: + pins = self.pins[pin_name] + # Route closest pins according to the minimum spanning tree + for source, target in self.get_mst_pairs(list(pins)): + # This is the routing region scale + scale = 1 + while True: + # Create the graph + g = graph(self) + region = g.create_graph(source, target, scale) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If there is no path found, exponentially try again with a + # larger routing region + if path is None: + rll, rur = region + bll, bur = self.ring_bbox + # Stop scaling the region and throw an error + if rll.x < bll.x and rll.y < bll.y and \ + rur.x > bur.x and rur.y > bur.y: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Exponentially scale the region + scale *= 2 + debug.info(0, "Retry routing in larger routing region with scale {}".format(scale)) + continue + # Create the path shapes on layout + self.add_path(path) + # Find the recently added shapes + self.prepare_gds_reader() + self.find_blockages(pin_name) + self.find_vias() + break + + + def calculate_ring_bbox(self, num_vias=3): + """ Calculate the ring-safe bounding box of the layout. """ + + ll, ur = self.design.get_bbox() + # Calculate the "wideness" of a side supply pin + wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) + # Total wideness is used to find it any pin overlaps in this region. If + # so, the bbox is shifted to prevent this overlap. + total_wideness = wideness * 4 + for blockage in self.blockages: + bll, bur = blockage.rect + if self.get_zindex(blockage.lpp) == 1: # Vertical + diff = ll.x + total_wideness - bll.x + if diff > 0: + ll = vector(ll.x - diff, ll.y) + diff = ur.x - total_wideness - bur.x + if diff < 0: + ur = vector(ur.x - diff, ur.y) + else: # Horizontal + diff = ll.y + total_wideness - bll.y + if diff > 0: + ll = vector(ll.x, ll.y - diff) + diff = ur.y - total_wideness - bur.y + if diff < 0: + ur = vector(ur.x, ur.y - diff) + self.ring_bbox = [ll, ur] + + + def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): + """ Add supply pin to one side of the layout. """ + + ll, ur = self.ring_bbox + vertical = side in ["left", "right"] + inner = pin_name == self.gnd_name + + # Calculate wires' wideness + wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) + + # Calculate the offset for the inner ring + if inner: + margin = wideness * 2 + else: + margin = 0 + + # Calculate the lower left coordinate + if side == "top": + offset = vector(ll.x + margin, ur.y - wideness - margin) + elif side == "bottom": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "left": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "right": + offset = vector(ur.x - wideness - margin, ll.y + margin) + + # Calculate width and height + shape = ur - ll + if vertical: + shape_width = wideness + shape_height = shape.y + else: + shape_width = shape.x + shape_height = wideness + if inner: + if vertical: + shape_height -= margin * 2 + else: + shape_width -= margin * 2 + + # Add this new pin + layer = self.get_layer(int(vertical)) + pin = self.design.add_layout_pin(text=pin_name, + layer=layer, + offset=offset, + width=shape_width, + height=shape_height) + + # Add fake pins on this new pin evenly + fake_pins = [] + if vertical: + space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) + start_offset = vector(offset.x, offset.y + wideness) + else: + space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) + start_offset = vector(offset.x + wideness, offset.y) + for i in range(1, num_fake_pins + 1): + if vertical: + offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) + ll = vector(offset.x, offset.y - self.track_wire) + ur = vector(offset.x + wideness, offset.y) + else: + offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y) + ll = vector(offset.x - self.track_wire, offset.y) + ur = vector(offset.x, offset.y + wideness) + rect = [ll, ur] + fake_pin = graph_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + fake_pins.append(fake_pin) + return pin, fake_pins + + + def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): + """ Add the supply ring to the layout. """ + + # Add side pins + new_pins = [] + for side in ["top", "bottom", "right", "left"]: + new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) + ll, ur = new_shape.rect + rect = [ll, ur] + layer = self.get_layer(side in ["left", "right"]) + new_pin = graph_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + new_pins.append(new_pin) + self.pins[pin_name].update(fake_pins) + self.fake_pins.extend(fake_pins) + + # Add vias to the corners + shift = self.track_wire + self.track_space + half_wide = self.track_wire / 2 + for i in range(4): + ll, ur = new_pins[i].rect + if i % 2: + top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide) + else: + top_left = vector(ll.x + half_wide, ur.y - half_wide) + for j in range(num_vias): + for k in range(num_vias): + offset = vector(top_left.x + j * shift, top_left.y - k * shift) + self.design.add_via_center(layers=self.layers, + offset=offset) + + # Save side pins for routing + self.new_pins[pin_name] = new_pins + for pin in new_pins: + self.blockages.append(self.inflate_shape(pin, is_pin=True)) + + + def get_mst_pairs(self, pins): + """ + Return the pin pairs from the minimum spanning tree in a graph that + connects all pins together. + """ + + pin_count = len(pins) + + # Create an adjacency matrix that connects all pins + edges = [[0] * pin_count for i in range(pin_count)] + for i in range(pin_count): + for j in range(pin_count): + # Skip if they're the same pin + if i == j: + continue + # Skip if both pins are fake + if pins[i] in self.fake_pins and pins[j] in self.fake_pins: + continue + edges[i][j] = pins[i].distance(pins[j]) + + pin_connected = [False] * pin_count + pin_connected[0] = True + + # Add the minimum cost edge in each iteration (Prim's) + mst_pairs = [] + for i in range(pin_count - 1): + min_cost = float("inf") + s = 0 + t = 0 + # Iterate over already connected pins + for m in range(pin_count): + # Skip if not connected + if not pin_connected[m]: + continue + # Iterate over this pin's neighbors + for n in range(pin_count): + # Skip if already connected or isn't a neighbor + if pin_connected[n] or edges[m][n] == 0: + continue + # Choose this edge if it's better the the current one + if edges[m][n] < min_cost: + min_cost = edges[m][n] + s = m + t = n + pin_connected[t] = True + mst_pairs.append((pins[s], pins[t])) + + return mst_pairs + + + def get_new_pins(self, name): + """ Return the new supply pins added by this router. """ + + return self.new_pins[name] From da24c52c526a23370e82dcb4e105c995690b433e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 31 Jul 2023 21:49:14 -0700 Subject: [PATCH 70/91] Cleanup graph router --- compiler/router/graph.py | 20 ++++++++------------ compiler/router/graph_router.py | 12 +++++------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index de0139f8..1d250b8b 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -48,7 +48,7 @@ class graph: # Constant values pin = pin.get_core() - offset = self.router.offset + offset = self.router.half_wire spacing = self.router.track_space size_limit = snap(offset * 4 + spacing) @@ -98,16 +98,12 @@ class graph: def closest(value, checklist): """ Return the distance of the closest value in the checklist. """ - min_diff = float("inf") - for other in checklist: - diff = snap(abs(value - other)) - if diff < min_diff: - min_diff = diff - return min_diff + diffs = [abs(value - other) for other in checklist] + return snap(min(diffs)) wide = self.router.track_wire - offset = self.router.offset - spacing = snap(self.router.track_space + offset + drc["grid"]) + half_wide = self.router.half_wire + spacing = snap(self.router.track_space + half_wide + drc["grid"]) blocked = False for blockage in self.graph_blockages: # Check if the node is inside the blockage @@ -126,8 +122,8 @@ class graph: safe = [True, True] for i in range(2): if lengths[i] >= wide: - min_diff = snap(min(abs(ll[i] - p[i]), abs(ur[i] - p[i]))) - if min_diff < offset: + min_diff = closest(p[i], [ll[i], ur[i]]) + if min_diff < half_wide: safe[i] = False elif centers[i] != p[i]: safe[i] = False @@ -262,7 +258,7 @@ class graph: y_values.update(ys) # Add corners for blockages - offset = vector(drc["grid"], drc["grid"]) + offset = vector([drc["grid"]] * 2) for blockage in self.graph_blockages: ll, ur = blockage.rect # Add minimum offset to the blockage corner nodes to prevent overlap diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py index 42886b29..da36f63f 100644 --- a/compiler/router/graph_router.py +++ b/compiler/router/graph_router.py @@ -45,7 +45,7 @@ class graph_router(router_tech): self.fake_pins = [] # Set the offset here - self.offset = snap(self.track_wire / 2) + self.half_wire = snap(self.track_wire / 2) def prepare_gds_reader(self): @@ -213,7 +213,7 @@ class graph_router(router_tech): if wide > self.layer_widths[0]: spacing = self.get_layer_space(self.get_zindex(shape.lpp), wide) return shape.inflated_pin(spacing=spacing, - extra_spacing=self.offset) + extra_spacing=self.half_wire) def add_path(self, path): @@ -262,7 +262,8 @@ class graph_router(router_tech): direction = nodes[i].get_direction(nodes[i + 1]) diff = start - end offset = start.min(end) - offset = vector(offset.x - self.offset, offset.y - self.offset) + offset = vector(offset.x - self.half_wire, + offset.y - self.half_wire) if direction == (1, 1): # Via offset = vector(start.x, start.y) self.design.add_via_center(layers=self.layers, @@ -274,7 +275,7 @@ class graph_router(router_tech): height=abs(diff.y) + self.track_wire) - def write_debug_gds(self, gds_name="debug_route.gds", g=None, source=None, target=None): + def write_debug_gds(self, gds_name, g=None, source=None, target=None): """ Write the debug GDSII file for the router. """ self.add_router_info(g, source, target) @@ -299,9 +300,6 @@ class graph_router(router_tech): self.design.add_label(text="n{}".format(node.center.z), layer="text", offset=offset) - #debug.info(0, "Neighbors of {}".format(node.center)) - #for neighbor in node.neighbors: - # debug.info(0, " {}".format(neighbor.center)) else: for blockage in self.blockages: self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) From 6c70396a05b9f473b6399fc08834363e710c0dc3 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 10:59:55 -0700 Subject: [PATCH 71/91] Remove grid-based routers and replace them with the gridless router --- compiler/router/__init__.py | 6 +- compiler/router/graph_router.py | 331 ---- compiler/router/grid.py | 215 --- compiler/router/grid_cell.py | 52 - compiler/router/grid_path.py | 215 --- compiler/router/grid_utils.py | 167 -- compiler/router/pin_group.py | 689 ------- compiler/router/router.py | 1694 +++-------------- compiler/router/signal_escape_router.py | 104 - compiler/router/signal_grid.py | 165 -- compiler/router/signal_router.py | 69 - compiler/router/supply_grid.py | 79 - compiler/router/supply_grid_router.py | 394 ---- ...upply_graph_router.py => supply_router.py} | 4 +- compiler/router/supply_tree_router.py | 208 -- 15 files changed, 250 insertions(+), 4142 deletions(-) delete mode 100644 compiler/router/graph_router.py delete mode 100644 compiler/router/grid.py delete mode 100644 compiler/router/grid_cell.py delete mode 100644 compiler/router/grid_path.py delete mode 100644 compiler/router/grid_utils.py delete mode 100644 compiler/router/pin_group.py delete mode 100644 compiler/router/signal_escape_router.py delete mode 100644 compiler/router/signal_grid.py delete mode 100644 compiler/router/signal_router.py delete mode 100644 compiler/router/supply_grid.py delete mode 100644 compiler/router/supply_grid_router.py rename compiler/router/{supply_graph_router.py => supply_router.py} (99%) delete mode 100644 compiler/router/supply_tree_router.py diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index 6fa3774c..ac2a64ec 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -3,9 +3,5 @@ # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # -from .router import * from .signal_escape_router import * -from .signal_router import * -from .supply_grid_router import * -from .supply_tree_router import * -from .supply_graph_router import * +from .supply_router import * diff --git a/compiler/router/graph_router.py b/compiler/router/graph_router.py deleted file mode 100644 index da36f63f..00000000 --- a/compiler/router/graph_router.py +++ /dev/null @@ -1,331 +0,0 @@ -# See LICENSE for licensing information. -# -# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz -# All rights reserved. -# -from openram import debug -from openram.base.vector import vector -from openram.gdsMill import gdsMill -from openram.tech import GDS -from openram.tech import drc -from openram.tech import layer as tech_layer -from openram import OPTS -from .router_tech import router_tech -from .graph_shape import graph_shape -from .graph_utils import snap - - -class graph_router(router_tech): - """ - This is the base class for routers that use the Hanan grid graph method. - """ - - def __init__(self, layers, design, bbox=None): - - # `router_tech` contains tech constants for the router - router_tech.__init__(self, layers, route_track_width=1) - - # Layers that can be used for routing - self.layers = layers - # This is the `hierarchy_layout` object - self.design = design - # Temporary GDSII file name to find pins and blockages - self.gds_filename = OPTS.openram_temp + "temp.gds" - # Dictionary for vdd and gnd pins - self.pins = {} - # Set of all the pins - self.all_pins = set() - # This is all the blockages including the pins. The graph class handles - # pins as blockages while considering their routability - self.blockages = [] - # This is all the vias between routing layers - self.vias = [] - # Fake pins are imaginary pins on the side supply pins to route other - # pins to them - self.fake_pins = [] - - # Set the offset here - self.half_wire = snap(self.track_wire / 2) - - - def prepare_gds_reader(self): - """ Write the current layout to a temporary file to read the layout. """ - - self.design.gds_write(self.gds_filename) - self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) - self.reader = gdsMill.Gds2reader(self.layout) - self.reader.loadFromFile(self.gds_filename) - - - def merge_shapes(self, merger, shape_list): - """ - Merge shapes in the list into the merger if they are contained or - aligned by the merger. - """ - - merger_core = merger.get_core() - for shape in list(shape_list): - shape_core = shape.get_core() - # If merger contains the shape, remove it from the list - if merger_core.contains(shape_core): - shape_list.remove(shape) - # If the merger aligns with the shape, expand the merger and remove - # the shape from the list - elif merger_core.aligns(shape_core): - merger.bbox([shape]) - merger_core.bbox([shape_core]) - shape_list.remove(shape) - - - def find_pins(self, pin_name): - """ Find the pins with the given name. """ - debug.info(2, "Finding all pins for {}".format(pin_name)) - - shape_list = self.layout.getAllPinShapes(str(pin_name)) - pin_set = set() - for shape in shape_list: - layer, boundary = shape - # gdsMill boundaries are in (left, bottom, right, top) order - ll = vector(boundary[0], boundary[1]) - ur = vector(boundary[2], boundary[3]) - rect = [ll, ur] - new_pin = graph_shape(pin_name, rect, layer) - # Skip this pin if it's contained by another pin of the same type - if new_pin.core_contained_by_any(pin_set): - continue - # Merge previous pins into this one if possible - self.merge_shapes(new_pin, pin_set) - pin_set.add(new_pin) - # Add these pins to the 'pins' dict - self.pins[pin_name] = pin_set - self.all_pins.update(pin_set) - - - def find_blockages(self, name="blockage"): - """ Find all blockages in the routing layers. """ - debug.info(2, "Finding blockages...") - - for lpp in [self.vert_lpp, self.horiz_lpp]: - shapes = self.layout.getAllShapes(lpp) - for boundary in shapes: - # gdsMill boundaries are in (left, bottom, right, top) order - ll = vector(boundary[0], boundary[1]) - ur = vector(boundary[2], boundary[3]) - rect = [ll, ur] - new_shape = graph_shape(name, rect, lpp) - new_shape = self.inflate_shape(new_shape) - # Skip this blockage if it's contained by a pin or an existing - # blockage - if new_shape.core_contained_by_any(self.all_pins) or \ - new_shape.core_contained_by_any(self.blockages): - continue - # Merge previous blockages into this one if possible - self.merge_shapes(new_shape, self.blockages) - self.blockages.append(new_shape) - - - def find_vias(self): - """ Find all vias in the routing layers. """ - debug.info(2, "Finding vias...") - - # Prepare lpp values here - from openram.tech import layer - via_lpp = layer[self.via_layer_name] - valid_lpp = self.horiz_lpp - - shapes = self.layout.getAllShapes(via_lpp) - for boundary in shapes: - # gdsMill boundaries are in (left, bottom, right, top) order - ll = vector(boundary[0], boundary[1]) - ur = vector(boundary[2], boundary[3]) - rect = [ll, ur] - new_shape = graph_shape("via", rect, valid_lpp) - # Skip this via if it's contained by an existing via blockage - if new_shape.contained_by_any(self.vias): - continue - self.vias.append(self.inflate_shape(new_shape, is_via=True)) - - - def convert_vias(self): - """ Convert vias that overlap a pin. """ - - for via in self.vias: - via_core = via.get_core() - for pin in self.all_pins: - pin_core = pin.get_core() - via_core.lpp = pin_core.lpp - # If the via overlaps a pin, change its name - if via_core.overlaps(pin_core): - via.rename(pin.name) - break - - - def convert_blockages(self): - """ Convert blockages that overlap a pin. """ - - # NOTE: You need to run `convert_vias()` before since a blockage may - # be connected to a pin through a via. - for blockage in self.blockages: - blockage_core = blockage.get_core() - for pin in self.all_pins: - pin_core = pin.get_core() - # If the blockage overlaps a pin, change its name - if blockage_core.overlaps(pin_core): - blockage.rename(pin.name) - break - else: - for via in self.vias: - # Skip if this via isn't connected to a pin - if via.name == "via": - continue - via_core = via.get_core() - via_core.lpp = blockage_core.lpp - # If the blockage overlaps a pin via, change its name - if blockage_core.overlaps(via_core): - blockage.rename(via.name) - break - - - def inflate_shape(self, shape, is_pin=False, is_via=False): - """ Inflate a given shape with spacing rules. """ - - # Pins must keep their center lines away from any blockage to prevent - # the nodes from being unconnected - if is_pin: - xdiff = self.layer_widths[0] - shape.width() - ydiff = self.layer_widths[0] - shape.height() - diff = max(xdiff, ydiff) / 2 - spacing = self.track_space + drc["grid"] - if diff > 0: - spacing += diff - # Vias are inflated by the maximum spacing rule - elif is_via: - spacing = self.track_space - # Blockages are inflated by their layer's corresponding spacing rule - else: - if self.get_zindex(shape.lpp) == 1: - spacing = self.vert_layer_spacing - else: - spacing = self.horiz_layer_spacing - # If the shape is wider than the supply wire width, its spacing can be - # different - wide = min(shape.width(), shape.height()) - if wide > self.layer_widths[0]: - spacing = self.get_layer_space(self.get_zindex(shape.lpp), wide) - return shape.inflated_pin(spacing=spacing, - extra_spacing=self.half_wire) - - - def add_path(self, path): - """ Add the route path to the layout. """ - - nodes = self.prepare_path(path) - self.add_route(nodes) - - - def prepare_path(self, path): - """ - Remove unnecessary nodes on the path to reduce the number of shapes in - the layout. - """ - - last_added = path[0] - nodes = [path[0]] - direction = path[0].get_direction(path[1]) - candidate = path[1] - for i in range(2, len(path)): - node = path[i] - current_direction = node.get_direction(candidate) - # Skip the previous candidate since the current node follows the - # same direction - if direction == current_direction: - candidate = node - else: - last_added = candidate - nodes.append(candidate) - direction = current_direction - candidate = node - if candidate not in nodes: - nodes.append(candidate) - return nodes - - - def add_route(self, nodes): - """ - Custom `add_route` function since `hierarchy_layout.add_route` isn't - working for this router. - """ - - for i in range(0, len(nodes) - 1): - start = nodes[i].center - end = nodes[i + 1].center - direction = nodes[i].get_direction(nodes[i + 1]) - diff = start - end - offset = start.min(end) - offset = vector(offset.x - self.half_wire, - offset.y - self.half_wire) - if direction == (1, 1): # Via - offset = vector(start.x, start.y) - self.design.add_via_center(layers=self.layers, - offset=offset) - else: # Wire - self.design.add_rect(layer=self.get_layer(start.z), - offset=offset, - width=abs(diff.x) + self.track_wire, - height=abs(diff.y) + self.track_wire) - - - def write_debug_gds(self, gds_name, g=None, source=None, target=None): - """ Write the debug GDSII file for the router. """ - - self.add_router_info(g, source, target) - self.design.gds_write(gds_name) - self.del_router_info() - - - def add_router_info(self, g=None, source=None, target=None): - """ - Add debug information to the text layer about the graph and router. - """ - - # Display the inflated blockage - if g: - for blockage in self.blockages: - if blockage in g.graph_blockages: - self.add_object_info(blockage, "blockage{}++[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) - else: - self.add_object_info(blockage, "blockage{}[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) - for node in g.nodes: - offset = (node.center.x, node.center.y) - self.design.add_label(text="n{}".format(node.center.z), - layer="text", - offset=offset) - else: - for blockage in self.blockages: - self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) - for pin in self.fake_pins: - self.add_object_info(pin, "fake") - if source: - self.add_object_info(source, "source") - if target: - self.add_object_info(target, "target") - - - def del_router_info(self): - """ Delete router information from the text layer. """ - - 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): - """ Add debug information to the text layer about an object. """ - - 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) diff --git a/compiler/router/grid.py b/compiler/router/grid.py deleted file mode 100644 index 0c97ba6c..00000000 --- a/compiler/router/grid.py +++ /dev/null @@ -1,215 +0,0 @@ -# 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. -# -from openram import debug -from openram.base.vector3d import vector3d -from .grid_cell import grid_cell - - -class grid: - """ - A two layer routing map. Each cell can be blocked in the vertical - or horizontal layer. - """ - # costs are relative to a unit grid - # non-preferred cost allows an off-direction jog of 1 grid - # rather than 2 vias + preferred direction (cost 5) - VIA_COST = 2 - NONPREFERRED_COST = 4 - PREFERRED_COST = 1 - - def __init__(self, ll, ur, track_width): - """ Initialize the map and define the costs. """ - - # list of the source/target grid coordinates - self.source = set() - self.target = set() - - self.track_width = track_width - self.track_widths = [self.track_width, self.track_width, 1.0] - self.track_factor = [1 / self.track_width, 1 / self.track_width, 1.0] - - # The bounds are in grids for this - # This is really lower left bottom layer and upper right top layer in 3D. - self.ll = vector3d(ll.x, ll.y, 0).scale(self.track_factor).round() - self.ur = vector3d(ur.x, ur.y, 0).scale(self.track_factor).round() - debug.info(1, "BBOX coords: ll=" + str(ll) + " ur=" + str(ur)) - debug.info(1, "BBOX grids: ll=" + str(self.ll) + " ur=" + str(self.ur)) - - # let's leave the map sparse, cells are created on demand to reduce memory - self.map={} - - def add_all_grids(self): - for x in range(self.ll.x, self.ur.x, 1): - for y in range(self.ll.y, self.ur.y, 1): - self.add_map(vector3d(x, y, 0)) - self.add_map(vector3d(x, y, 1)) - - def set_blocked(self, n, value=True): - if not isinstance(n, vector3d): - for item in n: - self.set_blocked(item, value) - else: - self.add_map(n) - self.map[n].blocked=value - - def is_blocked(self, n): - if not isinstance(n, vector3d): - for item in n: - if self.is_blocked(item): - return True - else: - return False - else: - self.add_map(n) - return self.map[n].blocked - - def is_inside(self, n): - if not isinstance(n, vector3d): - for item in n: - if self.is_inside(item): - return True - else: - return False - else: - return n.x >= self.ll.x and n.x <= self.ur.x and n.y >= self.ll.y and n.y <= self.ur.y - - def set_path(self, n, value=True): - if isinstance(n, (list, tuple, set, frozenset)): - for item in n: - self.set_path(item, value) - else: - self.add_map(n) - self.map[n].path=value - - def clear_blockages(self): - for k in self.map: - self.map[k].blocked=False - - def clear_source(self): - for k in self.map: - self.map[k].source=False - self.source = set() - - def set_source(self, n): - if not isinstance(n, vector3d): - for item in n: - self.set_source(item) - else: - self.add_map(n) - self.map[n].source=True - self.map[n].blocked=False - self.source.add(n) - - def clear_target(self): - for k in self.map: - self.map[k].target=False - self.target = set() - - def set_target(self, n): - if not isinstance(n, vector3d): - for item in n: - self.set_target(item) - else: - self.add_map(n) - self.map[n].target=True - self.map[n].blocked=False - self.target.add(n) - - def add_source(self, track_list): - debug.info(3, "Adding source list={0}".format(str(track_list))) - for n in track_list: - debug.info(4, "Adding source ={0}".format(str(n))) - self.set_source(n) - # self.set_blocked(n, False) - - def add_target(self, track_list): - debug.info(3, "Adding target list={0}".format(str(track_list))) - for n in track_list: - debug.info(4, "Adding target ={0}".format(str(n))) - self.set_target(n) - # self.set_blocked(n, False) - - def get_perimeter_list(self, side="left", layers=[0, 1], width=1, margin=0, offset=0): - """ - Side specifies which side. - Layer specifies horizontal (0) or vertical (1) - Width specifies how wide the perimeter "stripe" should be. - Works from the inside out from the bbox (ll, ur) - """ - if "ring" in side: - ring_width = width - else: - ring_width = 0 - - if "ring" in side: - ring_offset = offset - else: - ring_offset = 0 - - perimeter_list = [] - # Add the left/right columns - if side=="all" or "left" in side: - for x in range(self.ll.x - offset, self.ll.x - width - offset, -1): - for y in range(self.ll.y - ring_offset - margin - ring_width + 1, self.ur.y + ring_offset + margin + ring_width, 1): - for layer in layers: - perimeter_list.append(vector3d(x, y, layer)) - - if side=="all" or "right" in side: - for x in range(self.ur.x + offset, self.ur.x + width + offset, 1): - for y in range(self.ll.y - ring_offset - margin - ring_width + 1, self.ur.y + ring_offset + margin + ring_width, 1): - for layer in layers: - perimeter_list.append(vector3d(x, y, layer)) - - if side=="all" or "bottom" in side: - for y in range(self.ll.y - offset, self.ll.y - width - offset, -1): - for x in range(self.ll.x - ring_offset - margin - ring_width + 1, self.ur.x + ring_offset + margin + ring_width, 1): - for layer in layers: - perimeter_list.append(vector3d(x, y, layer)) - - if side=="all" or "top" in side: - for y in range(self.ur.y + offset, self.ur.y + width + offset, 1): - for x in range(self.ll.x - ring_offset - margin - ring_width + 1, self.ur.x + ring_offset + margin + ring_width, 1): - for layer in layers: - perimeter_list.append(vector3d(x, y, layer)) - - # Add them all to the map - self.add_map(perimeter_list) - - return perimeter_list - - def add_perimeter_target(self, side="all", layers=[0, 1]): - debug.info(3, "Adding perimeter target") - - perimeter_list = self.get_perimeter_list(side, layers) - - self.set_target(perimeter_list) - - def is_target(self, point): - """ - Point is in the target set, so we are done. - """ - return point in self.target - - def add_map(self, n): - """ - Add a point to the map if it doesn't exist. - """ - if not isinstance(n, vector3d): - for item in n: - self.add_map(item) - else: - if n not in self.map: - self.map[n]=grid_cell() - - def block_path(self, path): - """ - Mark the path in the routing grid as blocked. - Also unsets the path flag. - """ - path.set_path(False) - path.set_blocked(True) diff --git a/compiler/router/grid_cell.py b/compiler/router/grid_cell.py deleted file mode 100644 index 1ef7777a..00000000 --- a/compiler/router/grid_cell.py +++ /dev/null @@ -1,52 +0,0 @@ -# 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. -# - -class grid_cell: - """ - A single cell that can be occupied in a given layer, blocked, - visited, etc. - """ - def __init__(self): - self.path = False - self.blocked = False - self.source = False - self.target = False - # -1 means it isn't visited yet - self.min_cost = -1 - - def reset(self): - """ - Reset the dynamic info about routing. - """ - self.min_cost=-1 - self.min_path=None - self.blocked=False - self.source=False - self.target=False - - def get_cost(self): - # We can display the cost of the frontier - if self.min_cost > 0: - return self.min_cost - - def get_type(self): - type_string = "" - - if self.blocked: - type_string += "X" - - if self.source: - type_string += "S" - - if self.target: - type_string += "T" - - if self.path: - type_string += "P" - - return type_string diff --git a/compiler/router/grid_path.py b/compiler/router/grid_path.py deleted file mode 100644 index b7ea5ffc..00000000 --- a/compiler/router/grid_path.py +++ /dev/null @@ -1,215 +0,0 @@ -# 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. -# -from itertools import tee -from openram.base.vector3d import vector3d -from .grid import grid -from .direction import direction - - -class grid_path: - """ - A grid path is a list of lists of grid cells. - It can have a width that is more than one cell. - All of the sublists will be the same dimension. - Cells should be continguous. - It can have a name to define pin shapes as well. - """ - - def __init__(self, items=[], name=""): - self.name = name - if items: - self.pathlist = [items] - else: - self.pathlist = [] - - def __str__(self): - p = str(self.pathlist) - if self.name != "": - return (str(self.name) + " : " + p) - return p - - def __setitem__(self, index, value): - """ - override setitem function - can set value by pathinstance[index]=value - """ - self.pathlist[index]=value - - def __getitem__(self, index): - """ - override getitem function - can get value by value=pathinstance[index] - """ - return self.pathlist[index] - - def __contains__(self, key): - """ - Determine if cell exists in this path - """ - # FIXME: Could maintain a hash to make in O(1) - for sublist in self.pathlist: - for item in sublist: - if item == key: - return True - else: - return False - - def __add__(self, items): - """ - Override add to do append - """ - return self.pathlist.extend(items) - - def __len__(self): - return len(self.pathlist) - - def trim_last(self): - """ - Drop the last item - """ - if len(self.pathlist)>0: - self.pathlist.pop() - - def trim_first(self): - """ - Drop the first item - """ - if len(self.pathlist)>0: - self.pathlist.pop(0) - - def append(self,item): - """ - Append the list of items to the cells - """ - self.pathlist.append(item) - - def extend(self,item): - """ - Extend the list of items to the cells - """ - self.pathlist.extend(item) - - def set_path(self,value=True): - for sublist in self.pathlist: - for p in sublist: - p.path=value - - def set_blocked(self,value=True): - for sublist in self.pathlist: - for p in sublist: - p.blocked=value - - def get_grids(self): - """ - Return a set of all the grids in this path. - """ - newset = set() - for sublist in self.pathlist: - newset.update(sublist) - return newset - - def get_wire_grids(self, start_index, end_index): - """ - Return a set of all the wire grids in this path. - These are the indices in the wave path in a certain range. - """ - newset = set() - for sublist in self.pathlist: - newset.update(sublist[start_index:end_index]) - return newset - - def cost(self): - """ - The cost of the path is the length plus a penalty for the number - of vias. We assume that non-preferred direction is penalized. - This cost only works with 1 wide tracks. - """ - - # Ignore the source pin layer change, FIXME? - def pairwise(iterable): - "s -> (s0,s1), (s1,s2), (s2, s3), ..." - a, b = tee(iterable) - next(b, None) - return zip(a, b) - - plist = list(pairwise(self.pathlist)) - cost = 0 - for p0list,p1list in plist: - # This is because they are "waves" so pick the first item - p0=p0list[0] - p1=p1list[0] - - if p0.z != p1.z: # via - cost += grid.VIA_COST - elif p0.x != p1.x and p0.z==1: # horizontal on vertical layer - cost += grid.NONPREFERRED_COST - elif p0.y != p1.y and p0.z==0: # vertical on horizontal layer - cost += grid.NONPREFERRED_COST - else: - cost += grid.PREFERRED_COST - - return cost - - def expand_dirs(self): - """ - Expand from the end in each of the four cardinal directions plus up - or down but not expanding to blocked cells. Expands in all - directions regardless of preferred directions. - - If the width is more than one, it can only expand in one direction - (for now). This is assumed for the supply router for now. - - """ - neighbors = [] - - for d in direction.cardinal_directions(True): - n = self.neighbor(d) - if n: - neighbors.append(n) - - return neighbors - - def neighbor(self, d): - offset = direction.get_offset(d) - - newwave = [point + offset for point in self.pathlist[-1]] - - if newwave in self.pathlist: - return None - elif newwave[0].z>1 or newwave[0].z<0: - return None - - return newwave - - def set_layer(self, zindex): - new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave] - self.pathlist = new_pathlist - - def overlap(self, other): - """ - Return the overlap waves ignoring different layers - """ - - my_zindex = self.pathlist[0][0].z - other_flat_cells = [vector3d(item.x,item.y,my_zindex) for wave in other.pathlist for item in wave] - # This keeps the wave structure of the self layer - shared_waves = [] - for wave in self.pathlist: - for item in wave: - # If any item in the wave is not contained, skip it - if not item in other_flat_cells: - break - else: - shared_waves.append(wave) - - if len(shared_waves)>0: - ll = shared_waves[0][0] - ur = shared_waves[-1][-1] - return [ll,ur] - return None - diff --git a/compiler/router/grid_utils.py b/compiler/router/grid_utils.py deleted file mode 100644 index 97aaf1cd..00000000 --- a/compiler/router/grid_utils.py +++ /dev/null @@ -1,167 +0,0 @@ -# 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. -# -""" -Some utility functions for sets of grid cells. -""" - -import math -from openram.base.vector3d import vector3d -from .direction import direction - - -def increment_set(curset, direct): - """ - Return the cells incremented in given direction - """ - offset = direction.get_offset(direct) - - newset = set() - for c in curset: - newc = c+offset - newset.add(newc) - - return newset - - -def remove_border(curset, direct): - """ - Remove the cells on a given border. - """ - border = get_border(curset, direct) - curset.difference_update(border) - - -def get_upper_right(curset): - ur = None - for p in curset: - if ur == None or (p.x>=ur.x and p.y>=ur.y): - ur = p - return ur - - -def get_lower_left(curset): - ll = None - for p in curset: - if ll == None or (p.x<=ll.x and p.y<=ll.y): - ll = p - return ll - - -def get_border(curset, direct): - """ - Return the furthest cell(s) in a given direction. - """ - - # find direction-most cell(s) - maxc = [] - if direct==direction.NORTH: - for c in curset: - if len(maxc)==0 or c.y>maxc[0].y: - maxc = [c] - elif c.y==maxc[0].y: - maxc.append(c) - elif direct==direct.SOUTH: - for c in curset: - if len(maxc)==0 or c.ymaxc[0].x: - maxc = [c] - elif c.x==maxc[0].x: - maxc.append(c) - elif direct==direct.WEST: - for c in curset: - if len(maxc)==0 or c.x 0, - "Did not find any enclosures.") - - return new_pin_list - - def compute_connector(self, pin, enclosure): - """ - Compute a shape to connect the pin to the enclosure shape. - This assumes the shape will be the dimension of the pin. - """ - if pin.xoverlaps(enclosure): - # Is it vertical overlap, extend pin shape to enclosure - plc = pin.lc() - prc = pin.rc() - elc = enclosure.lc() - # erc = enclosure.rc() - ymin = min(plc.y, elc.y) - ymax = max(plc.y, elc.y) - ll = vector(plc.x, ymin) - ur = vector(prc.x, ymax) - elif pin.yoverlaps(enclosure): - # Is it horizontal overlap, extend pin shape to enclosure - pbc = pin.bc() - puc = pin.uc() - ebc = enclosure.bc() - # euc = enclosure.uc() - xmin = min(pbc.x, ebc.x) - xmax = max(pbc.x, ebc.x) - ll = vector(xmin, pbc.y) - ur = vector(xmax, puc.y) - else: - # Neither, so we must do a corner-to corner - pc = pin.center() - ec = enclosure.center() - xmin = min(pc.x, ec.x) - xmax = max(pc.x, ec.x) - ymin = min(pc.y, ec.y) - ymax = max(pc.y, ec.y) - ll = vector(xmin, ymin) - ur = vector(xmax, ymax) - - if ll.x == ur.x or ll.y == ur.y: - return None - p = pin_layout(pin.name, [ll, ur], pin.layer) - return p - - def find_above_connector(self, pin, enclosures): - """ - Find the enclosure that is to above the pin - and make a connector to it's upper edge. - """ - # Create the list of shapes that contain the pin edge - edge_list = [] - for shape in enclosures: - if shape.xcontains(pin): - edge_list.append(shape) - - # Sort them by their bottom edge - edge_list.sort(key=lambda x: x.by(), reverse=True) - - # Find the bottom edge that is next to the pin's top edge - above_item = None - for item in edge_list: - if item.by() >= pin.uy(): - above_item = item - else: - break - - # There was nothing - if not above_item: - return None - # If it already overlaps, no connector needed - if above_item.overlaps(pin): - return None - - # Otherwise, make a connector to the item - p = self.compute_connector(pin, above_item) - return p - - def find_below_connector(self, pin, enclosures): - """ - Find the enclosure that is below the pin - and make a connector to it's upper edge. - """ - # Create the list of shapes that contain the pin edge - edge_list = [] - for shape in enclosures: - if shape.xcontains(pin): - edge_list.append(shape) - - # Sort them by their upper edge - edge_list.sort(key=lambda x: x.uy()) - - # Find the upper edge that is next to the pin's bottom edge - bottom_item = None - for item in edge_list: - if item.uy() <= pin.by(): - bottom_item = item - else: - break - - # There was nothing to the left - if not bottom_item: - return None - # If it already overlaps, no connector needed - if bottom_item.overlaps(pin): - return None - - # Otherwise, make a connector to the item - p = self.compute_connector(pin, bottom_item) - return p - - def find_left_connector(self, pin, enclosures): - """ - Find the enclosure that is to the left of the pin - and make a connector to it's right edge. - """ - # Create the list of shapes that contain the pin edge - edge_list = [] - for shape in enclosures: - if shape.ycontains(pin): - edge_list.append(shape) - - # Sort them by their right edge - edge_list.sort(key=lambda x: x.rx()) - - # Find the right edge that is to the pin's left edge - left_item = None - for item in edge_list: - if item.rx() <= pin.lx(): - left_item = item - else: - break - - # There was nothing to the left - if not left_item: - return None - # If it already overlaps, no connector needed - if left_item.overlaps(pin): - return None - - # Otherwise, make a connector to the item - p = self.compute_connector(pin, left_item) - return p - - def find_right_connector(self, pin, enclosures): - """ - Find the enclosure that is to the right of the pin - and make a connector to it's left edge. - """ - # Create the list of shapes that contain the pin edge - edge_list = [] - for shape in enclosures: - if shape.ycontains(pin): - edge_list.append(shape) - - # Sort them by their right edge - edge_list.sort(key=lambda x: x.lx(), reverse=True) - - # Find the left edge that is next to the pin's right edge - right_item = None - for item in edge_list: - if item.lx() >= pin.rx(): - right_item = item - else: - break - - # There was nothing to the right - if not right_item: - return None - # If it already overlaps, no connector needed - if right_item.overlaps(pin): - return None - - # Otherwise, make a connector to the item - p = self.compute_connector(pin, right_item) - return p - - def find_smallest_connector(self, pin_list, shape_list): - """ - Compute all of the connectors between the overlapping - pins and enclosure shape list. - Return the smallest. - """ - smallest = None - for pin in pin_list: - for enclosure in shape_list: - new_enclosure = self.compute_connector(pin, enclosure) - if not smallest or new_enclosure.area() < smallest.area(): - smallest = new_enclosure - - return smallest - - def find_smallest_overlapping(self, pin_list, shape_list): - """ - Find the smallest area shape in shape_list that overlaps with any - pin in pin_list by a min width. - """ - - smallest_shape = None - for pin in pin_list: - overlap_shape = self.find_smallest_overlapping_pin(pin, shape_list) - if overlap_shape: - # overlap_length = pin.overlap_length(overlap_shape) - if not smallest_shape or overlap_shape.area() < smallest_shape.area(): - smallest_shape = overlap_shape - - return smallest_shape - - def find_smallest_overlapping_pin(self, pin, shape_list): - """ - Find the smallest area shape in shape_list that overlaps with any - pin in pin_list by a min width. - """ - - smallest_shape = None - zindex = self.router.get_zindex(pin.lpp[0]) - (min_width, min_space) = self.router.get_layer_width_space(zindex) - - # Now compare it with every other shape to check how much they overlap - for other in shape_list: - overlap_length = pin.overlap_length(other) - if overlap_length > min_width: - if not smallest_shape or other.area() < smallest_shape.area(): - smallest_shape = other - - return smallest_shape - - def overlap_any_shape(self, pin_list, shape_list): - """ - Does the given pin overlap any of the shapes in the pin list. - """ - for pin in pin_list: - for other in shape_list: - if pin.overlaps(other): - return True - - return False - - def max_pin_layout(self, pin_list): - """ - Return the max area pin_layout - """ - biggest = pin_list[0] - for pin in pin_list: - if pin.area() > biggest.area(): - biggest = pin - - return pin - - def enclose_pin_grids(self, ll, dir1=direction.NORTH, dir2=direction.EAST): - """ - This encloses a single pin component with a rectangle - starting with the seed and expanding dir1 until blocked - and then dir2 until blocked. - dir1 and dir2 should be two orthogonal directions. - """ - - offset1 = direction.get_offset(dir1) - offset2 = direction.get_offset(dir2) - - # We may have started with an empty set - debug.check(len(self.grids) > 0, "Cannot seed an grid empty set.") - - common_blockages = self.router.get_blocked_grids() & self.grids - - # Start with the ll and make the widest row - row = [ll] - # Move in dir1 while we can - while True: - next_cell = row[-1] + offset1 - # Can't move if not in the pin shape - if next_cell in self.grids and next_cell not in common_blockages: - row.append(next_cell) - else: - break - # Move in dir2 while we can - while True: - next_row = [x + offset2 for x in row] - for cell in next_row: - # Can't move if any cell is not in the pin shape - if cell not in self.grids or cell in common_blockages: - break - else: - row = next_row - # Skips the second break - continue - # Breaks from the nested break - break - - # Add a shape from ll to ur - ur = row[-1] - return (ll, ur) - - def enclose_pin(self): - """ - If there is one set of connected pin shapes, - this will find the smallest rectangle enclosure that - overlaps with any pin. - If there is not, it simply returns all the enclosures. - """ - self.enclosed = True - - # Compute the enclosure pin_layout list of the set of tracks - self.enclosures = self.compute_enclosures() - - # Find a connector to every pin and add it to the enclosures - for pin in self.pins: - - # If it is contained, it won't need a connector - if pin.contained_by_any(self.enclosures): - continue - - # Find a connector in the cardinal directions - # If there is overlap, but it isn't contained, - # these could all be None - # These could also be none if the pin is - # diagonal from the enclosure - left_connector = self.find_left_connector(pin, self.enclosures) - right_connector = self.find_right_connector(pin, self.enclosures) - above_connector = self.find_above_connector(pin, self.enclosures) - below_connector = self.find_below_connector(pin, self.enclosures) - connector_list = [left_connector, - right_connector, - above_connector, - below_connector] - filtered_list = list(filter(lambda x: x != None, connector_list)) - if (len(filtered_list) > 0): - import copy - bbox_connector = copy.copy(pin) - bbox_connector.bbox(filtered_list) - self.enclosures.append(bbox_connector) - - # Now, make sure each pin touches an enclosure. - # If not, add another (diagonal) connector. - # This could only happen when there was no enclosure - # in any cardinal direction from a pin - if not self.overlap_any_shape(self.pins, self.enclosures): - connector = self.find_smallest_connector(self.pins, - self.enclosures) - if not connector: - debug.error("Could not find a connector for {} with {}".format(self.pins, - self.enclosures)) - self.router.write_debug_gds("no_connector.gds") - self.enclosures.append(connector) - - # At this point, the pins are overlapping, - # but there might be more than one! - overlap_set = set() - for pin in self.pins: - overlap_set.update(self.transitive_overlap(pin, self.enclosures)) - # Use the new enclosures and recompute the grids - # that correspond to them - if len(overlap_set) < len(self.enclosures): - self.enclosures = overlap_set - self.grids = set() - # Also update the grid locations with the new - # (possibly pruned) enclosures - for enclosure in self.enclosures: - (sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name, - enclosure) - self.grids.update(sufficient) - - debug.info(3, "Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name, - self.pins, - self.grids, - self.enclosures)) - - def transitive_overlap(self, shape, shape_list): - """ - Given shape, find the elements in shape_list that overlap transitively. - I.e. if shape overlaps A and A overlaps B, return both A and B. - """ - - augmented_shape_list = set(shape_list) - old_connected_set = set() - connected_set = set([shape]) - # Repeat as long as we expand the set - while len(connected_set) > len(old_connected_set): - old_connected_set = connected_set - connected_set = set([shape]) - for old_shape in old_connected_set: - for cur_shape in augmented_shape_list: - if old_shape.overlaps(cur_shape): - connected_set.add(cur_shape) - - # Remove the original shape - connected_set.remove(shape) - - # if len(connected_set) 87.9 and lx < 87.99 and ly > 18.56 and ly < 18.6: - # breakpoint() - for pin in self.pins: - debug.info(4, " Converting {0}".format(pin)) - # Determine which tracks the pin overlaps - (sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name, - pin) - pin_set.update(sufficient) - partial_set.update(insufficient) - - # Blockages will be a super-set of pins since - # it uses the inflated pin shape. - blockage_in_tracks = self.router.convert_blockage(pin) - # Must include the pins here too because these are computed in a different - # way than blockages. - blockages = sufficient | insufficient | blockage_in_tracks - self.blockages.update(blockages) - - # If we have a blockage, we must remove the grids - # Remember, this excludes the pin blockages already - blocked_grids = self.router.get_blocked_grids() - pin_set.difference_update(blocked_grids) - partial_set.difference_update(blocked_grids) - - # At least one of the groups must have some valid tracks - if (len(pin_set) == 0 and len(partial_set) == 0): - # debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) - - for pin in self.pins: - debug.warning(" Expanding conversion {0}".format(pin)) - # Determine which tracks the pin overlaps - (sufficient, insufficient) = self.router.convert_pin_to_tracks(self.name, - pin, - expansion=1) - - # This time, don't remove blockages in the hopes that it might be ok. - # Could cause DRC problems! - pin_set.update(sufficient) - partial_set.update(insufficient) - - # If it's still empty, we must bail. - if len(pin_set) == 0 and len(partial_set) == 0: - debug.error("Unable to find unblocked pin {} {}".format(self.name, - self.pins)) - self.router.write_debug_gds("blocked_pin.gds") - - # Consider the fully connected set first and if not the partial set - # if len(pin_set) > 0: - # self.grids = pin_set - # else: - # self.grids = partial_set - # Just using the full set simplifies the enclosures, otherwise - # we get some pin enclose DRC errors due to off grid pins - self.grids = pin_set | partial_set - if len(self.grids) < 0: - debug.error("Did not find any unblocked grids: {}".format(str(self.pins))) - self.router.write_debug_gds("blocked_pin.gds") - - # Remember the secondary grids for removing adjacent pins - self.secondary_grids = partial_set - - debug.info(4, " pins {}".format(self.grids)) - debug.info(4, " secondary {}".format(self.secondary_grids)) diff --git a/compiler/router/router.py b/compiler/router/router.py index 0ec9665b..64f63d69 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -1,1531 +1,331 @@ # 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) +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # -import math -import itertools -from datetime import datetime from openram import debug from openram.base.vector import vector -from openram.base.vector3d import vector3d -from openram.base.pin_layout import pin_layout from openram.gdsMill import gdsMill -from openram.tech import drc, GDS -from openram.tech import layer as techlayer -from openram import OPTS, print_time +from openram.tech import GDS +from openram.tech import drc +from openram.tech import layer as tech_layer +from openram import OPTS from .router_tech import router_tech -from .pin_group import pin_group -from . import grid_utils +from .graph_shape import graph_shape +from .graph_utils import snap class router(router_tech): """ - A router class to read an obstruction map from a gds and plan a - route on a given layer. This is limited to two layer routes. - It populates blockages on a grid class. + This is the base class for routers that use the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None, margin=0, route_track_width=1): - """ - This will instantiate a copy of the gds file or the module at (0,0) and - route on top of this. The blockages from the gds/module will be - considered. - """ - router_tech.__init__(self, layers, route_track_width) + def __init__(self, layers, design, bbox=None): - self.cell = design + # `router_tech` contains tech constants for the router + router_tech.__init__(self, layers, route_track_width=1) + # Layers that can be used for routing + self.layers = layers + # This is the `hierarchy_layout` object + self.design = design + # Temporary GDSII file name to find pins and blockages self.gds_filename = OPTS.openram_temp + "temp.gds" - - # The pin data structures - # A map of pin names to a set of pin_layout structures - # (i.e. pins with a given label) + # Dictionary for vdd and gnd pins self.pins = {} - # This is a set of all pins (ignoring names) so that can quickly - # not create blockages for pins - # (They will be blocked when we are routing other - # nets based on their name.) + # Set of all the pins self.all_pins = set() - - # The labeled pins above categorized into pin groups - # that are touching/connected. - self.pin_groups = {} - - # The blockage data structures - # A list of metal shapes (using the same pin_layout structure) - # that could be blockages. - # This will include the pins above as well. + # This is all the blockages including the pins. The graph class handles + # pins as blockages while considering their routability self.blockages = [] - # The corresponding set of blocked grids for above blockage pin_layout shapes - # It is a cached set of grids that *could* be blocked, but may be unblocked - # depending on which pin we are routing. - self.blocked_grids = set() + # This is all the vias between routing layers + self.vias = [] + # Fake pins are imaginary pins on the side supply pins to route other + # pins to them + self.fake_pins = [] - # The routed data structures - # A list of paths that have been "routed" - self.paths = [] - # A list of path blockages (they might be expanded for wide metal DRC) - self.path_blockages = [] + # Set the offset here + self.half_wire = snap(self.track_wire / 2) - # The perimeter pins should be placed outside the SRAM macro by a distance - self.margin = margin - self.init_bbox(bbox, margin) - # New pins if we create a ring or side pins or etc. - self.new_pins = {} + def prepare_gds_reader(self): + """ Write the current layout to a temporary file to read the layout. """ - def init_bbox(self, bbox=None, margin=0): - """ - Initialize the ll,ur values with the paramter or using the layout boundary. - """ - if not bbox: - self.bbox = self.cell.get_bbox(margin) - else: - self.bbox = bbox - - (self.ll, self.ur) = self.bbox - - def get_bbox(self): - return self.bbox - - def create_routing_grid(self, router_type=None): - """ - Create (or recreate) a sprase routing grid with A* expansion functions. - """ - debug.check(router_type or hasattr(self, "router_type"), "Must specify a routing grid type.") - - self.init_bbox(self.bbox, self.margin) - - if router_type: - self.router_type = router_type - self.rg = router_type(self.ll, self.ur, self.track_width) - else: - self.rg = self.router_type(self.ll, self.ur, self.track_width) - - def clear_pins(self): - """ - Convert the routed path to blockages. - Keep the other blockages unchanged. - """ - self.pins = {} - self.all_pins = set() - self.pin_groups = {} - # DO NOT clear the blockages as these don't change - self.rg.reinit() - - def set_top(self, top_name): - """ If we want to route something besides the top-level cell.""" - self.top_name = top_name - - def is_wave(self, path): - """ - Determines if this is a multi-track width wave (True) - # or a normal route (False) - """ - return len(path[0]) > 1 - - def retrieve_pins(self, pin_name): - """ - Retrieve the pin shapes on metal 3 from the layout. - """ - debug.info(2, "Retrieving pins for {}.".format(pin_name)) - shape_list = self.layout.getAllPinShapes(str(pin_name)) - pin_set = set() - for shape in shape_list: - (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() - rect = [ll, ur] - pin = pin_layout(pin_name, rect, layer) - pin_set.add(pin) - - debug.check(len(pin_set) > 0, - "Did not find any pin shapes for {0}.".format(str(pin_name))) - - self.pins[pin_name] = pin_set - self.all_pins.update(pin_set) - - for pin in self.pins[pin_name]: - debug.info(3, "Retrieved pin {}".format(str(pin))) - - def find_blockages(self): - """ - Iterate through all the layers and write the obstacles to the routing grid. - This doesn't consider whether the obstacles will be pins or not. - They get reset later if they are not actually a blockage. - """ - debug.info(1, "Finding blockages.") - for lpp in [self.vert_lpp, self.horiz_lpp]: - self.retrieve_blockages(lpp) - - def find_pins_and_blockages(self, pin_list): - """ - Find the pins and blockages in the design - """ - - # If didn't specify a gds blockage file, write it out to read the gds - # This isn't efficient, but easy for now - # Load the gds file and read in all the shapes - self.cell.gds_write(self.gds_filename) + self.design.gds_write(self.gds_filename) self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) self.reader = gdsMill.Gds2reader(self.layout) self.reader.loadFromFile(self.gds_filename) - self.top_name = self.layout.rootStructureName - # print_time("GDS read",datetime.now(), start_time) - # This finds the pin shapes and sorts them into "groups" that - # are connected. This must come before the blockages, so we - # can not count the pins themselves - # as blockages. - start_time = datetime.now() - for pin_name in pin_list: - self.retrieve_pins(pin_name) - print_time("Retrieving pins", datetime.now(), start_time, 4) - start_time = datetime.now() - for pin_name in pin_list: - self.analyze_pins(pin_name) - print_time("Analyzing pins", datetime.now(), start_time, 4) - - # This will get all shapes as blockages and convert to grid units - # This ignores shapes that were pins - start_time = datetime.now() - self.find_blockages() - print_time("Finding blockages", datetime.now(), start_time, 4) - - # Convert the blockages to grid units - start_time = datetime.now() - self.convert_blockages() - print_time("Converting blockages", datetime.now(), start_time, 4) - - # This will convert the pins to grid units - # It must be done after blockages to ensure no DRCs - # between expanded pins and blocked grids - start_time = datetime.now() - for pin in pin_list: - self.convert_pins(pin) - print_time("Converting pins", datetime.now(), start_time, 4) - - # Combine adjacent pins into pin groups to reduce run-time - # by reducing the number of maze routes. - # This algorithm is > O(n^2) so remove it for now - # start_time = datetime.now() - # for pin in pin_list: - # self.combine_adjacent_pins(pin) - # print_time("Combining adjacent pins",datetime.now(), start_time, 4) - - # Separate any adjacent grids of differing net names - # that overlap - # Must be done before enclosing pins - start_time = datetime.now() - self.separate_adjacent_pins(0) - print_time("Separating adjacent pins", datetime.now(), start_time, 4) - - # Enclose the continguous grid units in a metal - # rectangle to fix some DRCs - #start_time = datetime.now() - #self.enclose_pins() - #print_time("Enclosing pins", datetime.now(), start_time, 4) - - # MRG: Removing this code for now. The later compute enclosure code - # assumes that all pins are touching and this may produce sets of pins - # that are not connected. - # def combine_adjacent_pins(self, pin_name): - # """ - # Find pins that have adjacent routing tracks and merge them into a - # single pin_group. The pins themselves may not be touching, but - # enclose_pins in the next step will ensure they are touching. - # """ - # debug.info(1,"Combining adjacent pins for {}.".format(pin_name)) - # # Find all adjacencies - # adjacent_pins = {} - # for index1,pg1 in enumerate(self.pin_groups[pin_name]): - # for index2,pg2 in enumerate(self.pin_groups[pin_name]): - # # Cannot combine with yourself, also don't repeat - # if index1<=index2: - # continue - # # Combine if at least 1 grid cell is adjacent - # if pg1.adjacent(pg2): - # if not index1 in adjacent_pins: - # adjacent_pins[index1] = set([index2]) - # else: - # adjacent_pins[index1].add(index2) - - # # Make a list of indices to ensure every group gets in the new set - # all_indices = set([x for x in range(len(self.pin_groups[pin_name]))]) - - # # Now reconstruct the new groups - # new_pin_groups = [] - # for index1,index2_set in adjacent_pins.items(): - # # Remove the indices if they are added to the new set - # all_indices.discard(index1) - # all_indices.difference_update(index2_set) - - # # Create the combined group starting with the first item - # combined = self.pin_groups[pin_name][index1] - # # Add all of the other items that overlapped - # for index2 in index2_set: - # pg = self.pin_groups[pin_name][index2] - # combined.add_group(pg) - # debug.info(3,"Combining {0} {1}:".format(pin_name, index2)) - # debug.info(3, " {0}\n {1}".format(combined.pins, pg.pins)) - # debug.info(3," --> {0}\n {1}".format(combined.pins,combined.grids)) - # new_pin_groups.append(combined) - - # # Add the pin groups that weren't added to the new set - # for index in all_indices: - # new_pin_groups.append(self.pin_groups[pin_name][index]) - - # old_size = len(self.pin_groups[pin_name]) - # # Use the new pin group! - # self.pin_groups[pin_name] = new_pin_groups - # removed_pairs = old_size - len(new_pin_groups) - # debug.info(1, - # "Combined {0} pin groups for {1}".format(removed_pairs,pin_name)) - - # return removed_pairs - - def separate_adjacent_pins(self, separation): + def merge_shapes(self, merger, shape_list): """ - This will try to separate all grid pins by the supplied - number of separation tracks (default is to prevent adjacency). - """ - pin_names = self.pin_groups.keys() - - for (pin_name1, pin_name2) in itertools.combinations(pin_names, 2): - self.separate_adjacent_pin(pin_name1, pin_name2, separation) - - def separate_adjacent_pin(self, pin_name1, pin_name2, separation): - """ - Go through all of the pin groups and check if any other pin group is - within a separation of it. - If so, reduce the pin group grid to not include the adjacent grid. - Try to do this intelligently to keep th pins enclosed. - """ - debug.info(2, - "Comparing {0} and {1} adjacency".format(pin_name1, - pin_name2)) - removed_grids = 0 - - for index1, pg1 in enumerate(self.pin_groups[pin_name1]): - for index2, pg2 in enumerate(self.pin_groups[pin_name2]): - adj_grids = pg1.adjacent_grids(pg2, separation) - removed_grids += len(adj_grids) - # These should have the same length, so... - if len(adj_grids) > 0: - debug.info(3, - "Adjacent grids {0} {1} adj={2}".format(index1, - index2, - adj_grids)) - self.remove_adjacent_grid(pg1, pg2, adj_grids) - - debug.info(2, "Removed {} adjacent grids.".format(removed_grids)) - - def remove_adjacent_grid(self, pg1, pg2, adj_grids): - """ - Remove one of the adjacent grids in a heuristic manner. - This will try to keep the groups similar sized by - removing from the bigger group. + Merge shapes in the list into the merger if they are contained or + aligned by the merger. """ - if pg1.size() > pg2.size(): - bigger = pg1 - smaller = pg2 - else: - bigger = pg2 - smaller = pg1 - - for adj in adj_grids: + merger_core = merger.get_core() + for shape in list(shape_list): + shape_core = shape.get_core() + # If merger contains the shape, remove it from the list + if merger_core.contains(shape_core): + shape_list.remove(shape) + # If the merger aligns with the shape, expand the merger and remove + # the shape from the list + elif merger_core.aligns(shape_core): + merger.bbox([shape]) + merger_core.bbox([shape_core]) + shape_list.remove(shape) - # If the adjacent grids are a subset of the secondary - # grids (i.e. not necessary) remove them from each - if adj in bigger.secondary_grids: - debug.info(3,"Removing {} from bigger secondary {}".format(adj, - bigger)) - bigger.grids.remove(adj) - bigger.secondary_grids.remove(adj) - self.blocked_grids.add(adj) - elif adj in smaller.secondary_grids: - debug.info(3,"Removing {} from smaller secondary {}".format(adj, - smaller)) - smaller.grids.remove(adj) - smaller.secondary_grids.remove(adj) - self.blocked_grids.add(adj) - else: - # If we couldn't remove from a secondary grid, - # we must remove from the primary - # grid of at least one pin - if adj in bigger.grids: - debug.info(3,"Removing {} from bigger primary {}".format(adj, - bigger)) - bigger.grids.remove(adj) - elif adj in smaller.grids: - debug.info(3,"Removing {} from smaller primary {}".format(adj, - smaller)) - smaller.grids.remove(adj) + def find_pins(self, pin_name): + """ Find the pins with the given name. """ + debug.info(2, "Finding all pins for {}".format(pin_name)) - def set_supply_rail_blocked(self, value): - # This is just a virtual function - pass - - def prepare_blockages(self, src=None, dest=None): - """ - Reset and add all of the blockages in the design. - Skip adding blockages from src and dest component if specified as a tuple of name,component. - """ - debug.info(3, "Preparing blockages.") - - # Start fresh. Not the best for run-time, but simpler. - self.clear_all_blockages() - - # This adds the initial blockges of the design - # which includes all blockages due to non-pin shapes - # print("BLOCKING:", self.blocked_grids) - self.set_blockages(self.blocked_grids, True) - - # Block all of the supply rails - # (some will be unblocked if they're a target) - try: - self.set_supply_rail_blocked(True) - except AttributeError: - # If function doesn't exist, it isn't a supply router - pass - - # Now go and block all of the blockages due to pin shapes. - # Some of these will get unblocked later if they are the source/target. - for name in self.pin_groups: - blockage_grids = [] - for component_idx, component in enumerate(self.pin_groups[name]): - # Skip adding source or dest blockages - if src and src[0] == name and src[1] == component_idx: - continue - if dest and dest[0] == name and dest[1] == component_idx: - continue - blockage_grids.extend(component.blockages) - self.set_blockages(blockage_grids, True) - - # If we have paths that were recently routed, add them as blockages as well. - # We might later do rip-up and reroute so they might not be metal shapes in the design yet. - # Also, this prevents having to reload an entire GDS and find the blockage shapes. - self.set_blockages(self.path_blockages) - - def convert_shape_to_units(self, shape): - """ - Scale a shape (two vector list) to user units - """ - unit_factor = [GDS["unit"][0]] * 2 - ll = shape[0].scale(unit_factor) - ur = shape[1].scale(unit_factor) - return [ll, ur] - - def min_max_coord(self, coord): - """ - Find the lowest and highest corner of a Rectangle - """ - coordinate = [] - minx = min(coord[0][0], coord[1][0], coord[2][0], coord[3][0]) - maxx = max(coord[0][0], coord[1][0], coord[2][0], coord[3][0]) - miny = min(coord[0][1], coord[1][1], coord[2][1], coord[3][1]) - maxy = max(coord[0][1], coord[1][1], coord[2][1], coord[3][1]) - coordinate += [vector(minx, miny)] - coordinate += [vector(maxx, maxy)] - return coordinate - - def get_inertia(self, p0, p1): - """ - Sets the direction based on the previous direction we came from. - """ - # direction (index) of movement - if p0.x != p1.x: - return 0 - elif p0.y != p1.y: - return 1 - else: - # z direction - return 2 - - def clear_blockages(self, pin_name): - """ - This function clears a given pin and all of its components from being blockages. - """ - blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.blockages} - self.set_blockages(blockage_grids, False) - - def clear_all_blockages(self): - """ - Clear all blockages on the grid. - """ - debug.info(3, "Clearing all blockages") - self.rg.clear_blockages() - - def set_blockages(self, blockages, value=True): - """ Flag the blockages in the grid """ - self.rg.set_blocked(blockages, value) - - def convert_to_tracks(self, ll, ur, z): - debug.info(3, "Converting ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) - - grid_list = [] - for x in range(int(ll[0]), int(ur[0])+1): - for y in range(int(ll[1]), int(ur[1])+1): - grid_list.append(vector3d(x, y, z)) - - return set(grid_list) - - def convert_blockage(self, blockage): - """ - Convert a pin layout blockage shape to routing grid tracks. - """ - # Inflate the blockage by half a spacing rule - [ll, ur] = self.convert_shape_to_tracks(blockage.inflate()) - zlayer = self.get_zindex(blockage.lpp) - blockage_tracks = self.convert_to_tracks(ll, ur, zlayer) - return blockage_tracks - - def convert_blockages(self): - """ Convert blockages to grid tracks. """ - debug.info(1, "Converting blockages.") - for blockage in self.blockages: - debug.info(3, "Converting blockage {}".format(str(blockage))) - blockage_list = self.convert_blockage(blockage) - self.blocked_grids.update(blockage_list) - - def get_blocked_grids(self): - """ - Return the blocked grids with their flag set - """ - #return set([x for x in self.blocked_grids if self.rg.is_blocked(x)]) - # These are all the non-pin blockages - return self.blocked_grids - - def retrieve_blockages(self, lpp): - """ - Recursive find boundaries as blockages to the routing grid. - """ - - shapes = self.layout.getAllShapes(lpp) - for boundary in shapes: + shape_list = self.layout.getAllPinShapes(str(pin_name)) + pin_set = set() + for shape in shape_list: + layer, boundary = shape + # gdsMill boundaries are in (left, bottom, right, top) order ll = vector(boundary[0], boundary[1]) ur = vector(boundary[2], boundary[3]) rect = [ll, ur] - new_shape = pin_layout("blockage{}".format(len(self.blockages)), - rect, - lpp) + new_pin = graph_shape(pin_name, rect, layer) + # Skip this pin if it's contained by another pin of the same type + if new_pin.core_contained_by_any(pin_set): + continue + # Merge previous pins into this one if possible + self.merge_shapes(new_pin, pin_set) + pin_set.add(new_pin) + # Add these pins to the 'pins' dict + self.pins[pin_name] = pin_set + self.all_pins.update(pin_set) - # 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): + + def find_blockages(self, name="blockage"): + """ Find all blockages in the routing layers. """ + debug.info(2, "Finding blockages...") + + for lpp in [self.vert_lpp, self.horiz_lpp]: + shapes = self.layout.getAllShapes(lpp) + for boundary in shapes: + # gdsMill boundaries are in (left, bottom, right, top) order + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_shape = graph_shape(name, rect, lpp) + new_shape = self.inflate_shape(new_shape) + # Skip this blockage if it's contained by a pin or an existing + # blockage + if new_shape.core_contained_by_any(self.all_pins) or \ + new_shape.core_contained_by_any(self.blockages): + continue + # Merge previous blockages into this one if possible + self.merge_shapes(new_shape, self.blockages) self.blockages.append(new_shape) - def pin_contains(self, shape): - for pin in self.all_pins: - if pin.contains(shape): - return True - return False - def convert_point_to_units(self, p): - """ - Convert a path set of tracks to center line path. - """ - pt = vector3d(p) - pt = pt.scale(self.track_widths[0], self.track_widths[1], 1) - return pt + def find_vias(self): + """ Find all vias in the routing layers. """ + debug.info(2, "Finding vias...") - def convert_wave_to_units(self, wave): - """ - Convert a wave to a set of center points - """ - return [self.convert_point_to_units(i) for i in wave] + # Prepare lpp values here + from openram.tech import layer + via_lpp = layer[self.via_layer_name] + valid_lpp = self.horiz_lpp - def convert_shape_to_tracks(self, shape): - """ - Convert a rectangular shape into track units. - """ - (ll, ur) = shape - ll = snap_to_grid(ll) - ur = snap_to_grid(ur) - - # to scale coordinates to tracks - debug.info(3, "Converting [ {0} , {1} ]".format(ll, ur)) - ll = ll.scale(self.track_factor) - ur = ur.scale(self.track_factor) - # We can round since we are using inflated shapes - # and the track points are at the center - ll = ll.round() - ur = ur.round() - return [ll, ur] - - def convert_pin_to_tracks(self, pin_name, pin, expansion=0): - """ - Convert a rectangular pin shape into a list of track locations,layers. - If no pins are "on-grid" (i.e. sufficient overlap) - it makes the one with most overlap if it is not blocked. - If expansion>0, expamine areas beyond the current pin - when it is blocked. - """ - (ll, ur) = pin.rect - debug.info(3, "Converting pin [ {0} , {1} ]".format(ll, ur)) - - # scale the size bigger to include neaby tracks - ll_scaled = ll.scale(self.track_factor).floor() - ur_scaled = ur.scale(self.track_factor).ceil() - - # Keep tabs on tracks with sufficient and insufficient overlap - sufficient_list = set() - insufficient_list = set() - - zindex = self.get_zindex(pin.lpp) - for x in range(int(ll_scaled[0]) - expansion, int(ur_scaled[0]) + 1 + expansion): - for y in range(int(ll_scaled[1] - expansion), int(ur_scaled[1]) + 1 + expansion): - cur_grid = vector3d(x, y, zindex) - (full_overlap, partial_overlap) = self.convert_pin_coord_to_tracks(pin, cur_grid) - if full_overlap: - sufficient_list.update([full_overlap]) - if partial_overlap: - insufficient_list.update([partial_overlap]) - debug.info(3, - "Converting [ {0} , {1} ] full={2}".format(x, - y, - full_overlap)) - - # Return all grids with any potential overlap (sufficient or not) - return (sufficient_list, insufficient_list) - - def get_all_offgrid_pin(self, pin, insufficient_list): - """ - Find a list of all pins with some overlap. - """ - # print("INSUFFICIENT LIST",insufficient_list) - # Find the coordinate with the most overlap - any_overlap = set() - for coord in insufficient_list: - full_pin = self.convert_track_to_pin(coord) - # Compute the overlap with that rectangle - overlap_rect = pin.compute_overlap(full_pin) - # Determine the max x or y overlap - max_overlap = max(overlap_rect) - if max_overlap > 0: - any_overlap.update([coord]) - - return any_overlap - - def get_best_offgrid_pin(self, pin, insufficient_list): - """ - Find a list of the single pin with the most overlap. - """ - # Find the coordinate with the most overlap - best_coord = None - best_overlap = -math.inf - for coord in insufficient_list: - full_pin = self.convert_track_to_pin(coord) - # Compute the overlap with that rectangle - overlap_rect = pin.compute_overlap(full_pin) - # Determine the min x or y overlap - min_overlap = min(overlap_rect) - if min_overlap > best_overlap: - best_overlap = min_overlap - best_coord = coord - - return set([best_coord]) - - def get_furthest_offgrid_pin(self, pin, insufficient_list): - """ - Get a grid cell that is the furthest from the blocked grids. - """ - - # Find the coordinate with the most overlap - best_coord = None - best_dist = math.inf - for coord in insufficient_list: - min_dist = grid_utils.distance_set(coord, self.blocked_grids) - if min_dist < best_dist: - best_dist = min_dist - best_coord = coord - - return set([best_coord]) - - def get_nearest_offgrid_pin(self, pin, insufficient_list): - """ - Given a pin and a list of grid cells (probably non-overlapping), - return the nearest grid cell (center to center). - """ - # Find the coordinate with the most overlap - best_coord = None - best_dist = math.inf - for coord in insufficient_list: - track_pin = self.convert_track_to_pin(coord) - min_dist = pin.distance(track_pin) - if min_dist < best_dist: - best_dist = min_dist - best_coord = coord - - return set([best_coord]) - - def break_on_grids(self, tracks, xvals, yvals): - track_list = [] - for x in xvals: - for y in yvals: - track_list.append(vector3d(x, y, 0)) - track_list.append(vector3d(x, y, 1)) - - for current in tracks: - if current in track_list: - breakpoint() - - def divide_pin_to_tracks(self, pin, tracks): - """ - Return a list of pin shape parts that are in the tracks. - """ - # If pin is smaller than a track, just return it. - track_pin = self.convert_track_to_shape_pin(list(tracks)[0]) - if pin.width() < track_pin.width() and pin.height() < track_pin.height(): - return [pin] - - overlap_pins = [] - for track in tracks: - track_pin = self.convert_track_to_shape_pin(track) - overlap_pin = track_pin.intersection(pin) - - # If pin is smaller than minwidth, in one dimension, skip it. - min_pin_width = drc("minwidth_{0}". format(pin.layer)) - if not overlap_pin or (overlap_pin.width() < min_pin_width and overlap_pin.height() < min_pin_width): + shapes = self.layout.getAllShapes(via_lpp) + for boundary in shapes: + # gdsMill boundaries are in (left, bottom, right, top) order + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_shape = graph_shape("via", rect, valid_lpp) + # Skip this via if it's contained by an existing via blockage + if new_shape.contained_by_any(self.vias): continue - else: - overlap_pins.append(overlap_pin) - - debug.check(len(overlap_pins) > 0, "No pins overlapped the tracks.") - - return overlap_pins + self.vias.append(self.inflate_shape(new_shape, is_via=True)) - def convert_pin_coord_to_tracks(self, pin, coord): - """ - Return all tracks that an inflated pin overlaps - """ - # This is using the full track shape rather - # than a single track pin shape - # because we will later patch a connector if there isn't overlap. - track_pin = self.convert_track_to_shape_pin(coord) + def convert_vias(self): + """ Convert vias that overlap a pin. """ - # This is the normal pin inflated by a minimum design rule - inflated_pin = pin_layout(pin.name, - pin.inflate(0.5 * self.track_space), - pin.layer) - - overlap_length = pin.overlap_length(track_pin) - debug.info(4,"Check overlap: {0} {1} . {2} = {3}".format(coord, - pin.rect, - track_pin, - overlap_length)) - inflated_overlap_length = inflated_pin.overlap_length(track_pin) - debug.info(4,"Check overlap: {0} {1} . {2} = {3}".format(coord, - inflated_pin.rect, - track_pin, - inflated_overlap_length)) - - # If it overlaps with the pin, it is sufficient - if overlap_length == math.inf or overlap_length > 0: - debug.info(4," Overlap: {0} >? {1}".format(overlap_length, 0)) - return (coord, None) - # If it overlaps with the inflated pin, it is partial - elif inflated_overlap_length == math.inf or inflated_overlap_length > 0: - debug.info(4," Partial overlap: {0} >? {1}".format(inflated_overlap_length, 0)) - return (None, coord) - else: - debug.info(4, " No overlap: {0} {1}".format(overlap_length, 0)) - return (None, None) - - def convert_track_to_pin(self, track): - """ - Convert a grid point into a rectangle shape that is centered - track in the track and leaves half a DRC space in each direction. - """ - # calculate lower left - x = track.x * self.track_width - 0.5 * self.track_width + 0.5 * self.track_space - y = track.y * self.track_width - 0.5 * self.track_width + 0.5 * self.track_space - ll = snap_to_grid(vector(x,y)) - - # calculate upper right - x = track.x * self.track_width + 0.5 * self.track_width - 0.5 * self.track_space - y = track.y * self.track_width + 0.5 * self.track_width - 0.5 * self.track_space - ur = snap_to_grid(vector(x, y)) - - p = pin_layout("", [ll, ur], self.get_layer(track[2])) - return p - - def convert_tracks_to_pin(self, tracks): - """ - Convert a list of grid point into a rectangle shape. - Must all be on the same layer. - """ - for t in tracks: - debug.check(t[2] == tracks[0][2], "Different layers used.") - - # For each shape, convert it to a pin - pins = [self.convert_track_to_pin(t) for t in tracks] - # Now find the bounding box - minx = min([p.lx() for p in pins]) - maxx = max([p.rx() for p in pins]) - miny = min([p.by() for p in pins]) - maxy = max([p.uy() for p in pins]) - ll = vector(minx, miny) - ur = vector(maxx, maxy) - - p = pin_layout("", [ll, ur], self.get_layer(tracks[0][2])) - return p - - def convert_track_to_shape_pin(self, track): - """ - Convert a grid point into a rectangle shape - that occupies the entire centered track. - """ - # to scale coordinates to tracks - x = track[0]*self.track_width - 0.5*self.track_width - y = track[1]*self.track_width - 0.5*self.track_width - # offset lowest corner object to to (-track halo,-track halo) - ll = snap_to_grid(vector(x, y)) - ur = snap_to_grid(ll + vector(self.track_width, self.track_width)) - - p = pin_layout("", [ll, ur], self.get_layer(track[2])) - return p - - def convert_track_to_shape(self, track): - """ - Convert a grid point into a rectangle shape - that occupies the entire centered track. - """ - # to scale coordinates to tracks - try: - x = track[0]*self.track_width - 0.5*self.track_width - except TypeError: - debug.warning("{} {} {} {}".format(track[0], type(track[0]), self.track_width, type(self.track_width))) - y = track[1]*self.track_width - 0.5*self.track_width - # offset lowest corner object to to (-track halo,-track halo) - ll = snap_to_grid(vector(x, y)) - ur = snap_to_grid(ll + vector(self.track_width, self.track_width)) - - return [ll, ur] - - def convert_track_to_inflated_pin(self, track): - """ - Convert a grid point into a rectangle shape - that is inflated by a half DRC space. - """ - # calculate lower left - x = track.x*self.track_width - 0.5*self.track_width - 0.5*self.track_space - y = track.y*self.track_width - 0.5*self.track_width - 0.5*self.track_space - ll = snap_to_grid(vector(x,y)) - - # calculate upper right - x = track.x*self.track_width + 0.5*self.track_width + 0.5*self.track_space - y = track.y*self.track_width + 0.5*self.track_width + 0.5*self.track_space - ur = snap_to_grid(vector(x, y)) - - p = pin_layout("", [ll, ur], self.get_layer(track[2])) - return p - - def analyze_pins(self, pin_name): - """ - Analyze the shapes of a pin and combine - them into pin_groups which are connected. - """ - debug.info(2, "Analyzing pin groups for {}.".format(pin_name)) - pin_set = self.pins[pin_name] - - # This will be a list of pin tuples that overlap - overlap_list = [] - - # Sort the rectangles into a list with lower/upper y coordinates - bottom_y_coordinates = [(x.by(), x, "bottom") for x in pin_set] - top_y_coordinates = [(x.uy(), x, "top") for x in pin_set] - y_coordinates = bottom_y_coordinates + top_y_coordinates - y_coordinates.sort(key=lambda x: x[0]) - - # Map the pins to the lower indices - bottom_index_map = {x[1]: i for i, x in enumerate(y_coordinates) if x[2] == "bottom"} - # top_index_map = {x[1]: i for i, x in enumerate(y_coordinates) if x[2] == "bottom"} - - # Sort the pin list by x coordinate - pin_list = list(pin_set) - pin_list.sort(key=lambda x: x.lx()) - - # for shapes in x order - for pin in pin_list: - # start at pin's lower y coordinate - bottom_index = bottom_index_map[pin] - compared_pins = set() - for i in range(bottom_index, len(y_coordinates)): - compare_pin = y_coordinates[i][1] - # Don't overlap yourself - if pin == compare_pin: - continue - # Done when we encounter any shape above the pin - if compare_pin.by() > pin.uy(): + for via in self.vias: + via_core = via.get_core() + for pin in self.all_pins: + pin_core = pin.get_core() + via_core.lpp = pin_core.lpp + # If the via overlaps a pin, change its name + if via_core.overlaps(pin_core): + via.rename(pin.name) break - # Don't double compare the same pin twice - if compare_pin in compared_pins: - continue - compared_pins.add(compare_pin) - # If we overlap, add them to the list - if pin.overlaps(compare_pin): - overlap_list.append((pin, compare_pin)) - # Initial unique group assignments - group_id = {} - gid = 1 - for pin in pin_list: - group_id[pin] = gid - gid += 1 - for p in overlap_list: - (p1, p2) = p - for pin in pin_list: - if group_id[pin] == group_id[p2]: - group_id[pin] = group_id[p1] + def convert_blockages(self): + """ Convert blockages that overlap a pin. """ - # For each pin add it to it's group - group_map = {} - for pin in pin_list: - gid = group_id[pin] - if gid not in group_map: - group_map[gid] = pin_group(name=pin_name, - pin_set=[], - router=self) - group_map[gid].add_pin(pin) + # NOTE: You need to run `convert_vias()` before since a blockage may + # be connected to a pin through a via. + for blockage in self.blockages: + blockage_core = blockage.get_core() + for pin in self.all_pins: + pin_core = pin.get_core() + # If the blockage overlaps a pin, change its name + if blockage_core.overlaps(pin_core): + blockage.rename(pin.name) + break + else: + for via in self.vias: + # Skip if this via isn't connected to a pin + if via.name == "via": + continue + via_core = via.get_core() + via_core.lpp = blockage_core.lpp + # If the blockage overlaps a pin via, change its name + if blockage_core.overlaps(via_core): + blockage.rename(via.name) + break - self.pin_groups[pin_name] = list(group_map.values()) - def convert_pins(self, pin_name): - """ - Convert the pin groups into pin tracks and blockage tracks. - """ - debug.info(2, "Converting pins for {}.".format(pin_name)) - for pg in self.pin_groups[pin_name]: - pg.convert_pin() + def inflate_shape(self, shape, is_pin=False, is_via=False): + """ Inflate a given shape with spacing rules. """ - def enclose_pins(self): - """ - This will find the biggest rectangle enclosing some grid squares and - put a rectangle over it. It does not enclose grid squares - that are blocked by other shapes. - """ - for pin_name in self.pin_groups: - debug.info(2, "Enclosing pins for {}".format(pin_name)) - for pg in self.pin_groups[pin_name]: - self.clear_blockages(pin_name) - pg.enclose_pin() - pg.add_enclosure(self.cell) - - def add_source(self, pin_name): - """ - This will mark the grids for all pin components as a source. - Marking as source or target also clears blockage status. - """ - self.source_name = pin_name - self.source_components = [] - for i in range(self.num_pin_components(pin_name)): - self.add_pin_component_source(pin_name, i) - - # Clearing the blockage of this pin requires the inflated pins - self.clear_blockages(pin_name) - - def add_target(self, pin_name): - """ - This will mark the grids for all pin components as a target. - Marking as source or target also clears blockage status. - """ - self.target_name = pin_name - self.target_components = [] - for i in range(self.num_pin_components(pin_name)): - self.add_pin_component_target(pin_name, i) - - # Clearing the blockage of this pin requires the inflated pins - self.clear_blockages(pin_name) - - def add_side_supply_pin(self, name, side="left", width=3, space=2): - """ - Adds a supply pin to the perimeter and resizes the bounding box. - """ - pg = pin_group(name, [], self) - # Offset two spaces inside and one between the rings - if name == "gnd": - offset = width + 2 * space + # Pins must keep their center lines away from any blockage to prevent + # the nodes from being unconnected + if is_pin: + xdiff = self.layer_widths[0] - shape.width() + ydiff = self.layer_widths[0] - shape.height() + diff = max(xdiff, ydiff) / 2 + spacing = self.track_space + drc["grid"] + if diff > 0: + spacing += diff + # Vias are inflated by the maximum spacing rule + elif is_via: + spacing = self.track_space + # Blockages are inflated by their layer's corresponding spacing rule else: - offset = space - if side in ["left", "right"]: - layers = [1] - else: - layers = [0] + if self.get_zindex(shape.lpp) == 1: + spacing = self.vert_layer_spacing + else: + spacing = self.horiz_layer_spacing + # If the shape is wider than the supply wire width, its spacing can be + # different + wide = min(shape.width(), shape.height()) + if wide > self.layer_widths[0]: + spacing = self.get_layer_space(self.get_zindex(shape.lpp), wide) + return shape.inflated_pin(spacing=spacing, + extra_spacing=self.half_wire) - pg.grids = set(self.rg.get_perimeter_list(side=side, - width=width, - margin=self.margin, - offset=offset, - layers=layers)) - pg.enclosures = pg.compute_enclosures() - pg.pins = set(pg.enclosures) - debug.check(len(pg.pins)==1, "Too many pins for a side supply.") - self.cell.pin_map[name].update(pg.pins) - self.pin_groups[name].append(pg) + def add_path(self, path): + """ Add the route path to the layout. """ - self.new_pins[name] = pg.pins + nodes = self.prepare_path(path) + self.add_route(nodes) - def add_ring_supply_pin(self, name, width=3, space=3): + + def prepare_path(self, path): """ - Adds a ring supply pin that goes outside the given bbox. - """ - pg = pin_group(name, [], self) - - # LEFT - left_grids = set(self.rg.get_perimeter_list(side="left_ring", - width=width, - margin=self.margin, - offset=space, - layers=[1])) - - # RIGHT - right_grids = set(self.rg.get_perimeter_list(side="right_ring", - width=width, - margin=self.margin, - offset=space, - layers=[1])) - # TOP - top_grids = set(self.rg.get_perimeter_list(side="top_ring", - width=width, - margin=self.margin, - offset=space, - layers=[0])) - # BOTTOM - bottom_grids = set(self.rg.get_perimeter_list(side="bottom_ring", - width=width, - margin=self.margin, - offset=space, - layers=[0])) - - horizontal_layer_grids = left_grids | right_grids - - # Must move to the same layer to find layer 1 corner grids - vertical_layer_grids = set() - for x in top_grids | bottom_grids: - vertical_layer_grids.add(vector3d(x.x, x.y, 1)) - - # Add vias in the overlap points - horizontal_corner_grids = vertical_layer_grids & horizontal_layer_grids - corners = [] - for g in horizontal_corner_grids: - self.add_via(g) - - # The big pin group, but exclude the corners from the pins - pg.grids = (left_grids | right_grids | top_grids | bottom_grids) - pg.enclosures = pg.compute_enclosures() - pg.pins = set(pg.enclosures) - - self.cell.pin_map[name].update(pg.pins) - self.pin_groups[name].append(pg) - self.new_pins[name] = pg.pins - - # Update the bbox so that it now includes the new pins - for p in pg.pins: - if p.lx() < self.ll.x or p.by() < self.ll.y: - self.ll = p.ll() - if p.rx() > self.ur.x or p.uy() > self.ur.y: - self.ur = p.ur() - self.bbox = (self.ll, self.ur) - self.create_routing_grid() - - def get_new_pins(self, name): - return self.new_pins[name] - - def add_perimeter_target(self, side="all"): - """ - This will mark all the cells on the perimeter of the original layout as a target. - """ - self.target_name = "" - self.target_components = [] - self.rg.add_perimeter_target(side=side) - - def num_pin_components(self, pin_name): - """ - This returns how many disconnected pin components there are. - """ - return len(self.pin_groups[pin_name]) - - def set_pin_component_source(self, pin_name, index): - """ - Add the pin component but also set it as the exclusive one. - Used by supply routing with multiple components. - """ - self.source_name = pin_name - self.source_components = [] - self.add_pin_component_source(pin_name, index) - - def add_pin_component_source(self, pin_name, index): - """ - This will mark only the pin tracks - from the indexed pin component as a source. - It also unsets it as a blockage. - """ - debug.check(index 1: - self.cell.add_route(layers=self.layers, - coordinates=abs_path, - layer_widths=self.layer_widths) - else: - self.cell.add_path(layer=self.layers[0], - coordinates=abs_path, - width=self.layer_widths[0]) - - def create_route_connector(self, path_tracks, pin_name, components): - """ - Find a rectangle to connect the track and the off-grid pin of a component. + Remove unnecessary nodes on the path to reduce the number of shapes in + the layout. """ - if len(path_tracks) == 0 or len(components) == 0: - return + last_added = path[0] + nodes = [path[0]] + direction = path[0].get_direction(path[1]) + candidate = path[1] + for i in range(2, len(path)): + node = path[i] + current_direction = node.get_direction(candidate) + # Skip the previous candidate since the current node follows the + # same direction + if direction == current_direction: + candidate = node + else: + last_added = candidate + nodes.append(candidate) + direction = current_direction + candidate = node + if candidate not in nodes: + nodes.append(candidate) + return nodes - # Find the track pin - track_pins = [self.convert_tracks_to_pin(x) for x in path_tracks] - # Convert the off-grid pin into parts in each routing grid - offgrid_pin_parts = [] - for component in components: - pg = self.pin_groups[pin_name][component] - for pin in pg.pins: - # Layer min with - min_width = drc("minwidth_{}".format(pin.layer)) - - # If we intersect, by a min_width, we are done! - for track_pin in track_pins: - intersection = pin.intersection(track_pin) - if intersection and intersection.width() > min_width and intersection.height() > min_width: - return - - #self.break_on_grids(pg.grids, xvals=[68], yvals=range(93,100)) - partial_pin_parts = self.divide_pin_to_tracks(pin, pg.grids) - offgrid_pin_parts.extend(partial_pin_parts) - - debug.check(len(offgrid_pin_parts) > 0, "No offgrid pin parts found.") - - # Find closest part - closest_track_pin, closest_part_pin = self.find_closest_pin(track_pins, offgrid_pin_parts) - - debug.check(closest_track_pin and closest_part_pin, "Found no closest pins.") - - # Find the bbox of the on-grid track and the off-grid pin part - closest_track_pin.bbox([closest_part_pin]) - - # Connect to off grid pin to track pin with closest shape - self.cell.add_rect(layer=closest_track_pin.layer, - offset=closest_track_pin.ll(), - width=closest_track_pin.width(), - height=closest_track_pin.height()) - - def find_closest_pin(self, first_list, second_list): + def add_route(self, nodes): """ - Find the closest pin in the lists. Does a stupid O(n^2). - """ - min_dist = None - min_item = (None, None) - for pin1 in first_list: - for pin2 in second_list: - if pin1.layer != pin2.layer: - continue - new_dist = pin1.distance(pin2) - if min_dist == None or new_dist < min_dist: - min_item = (pin1, pin2) - min_dist = new_dist - - return min_item - - - def add_single_enclosure(self, track): - """ - Add a metal enclosure that is the size of - the routing grid minus a spacing on each side. - """ - pin = self.convert_track_to_pin(track) - (ll, ur) = pin.rect - self.cell.add_rect(layer=self.get_layer(track.z), - offset=ll, - width=ur.x-ll.x, - height=ur.y-ll.y) - - def add_via(self, loc, size=1): - """ - Add a via centered at the current location - """ - loc = self.convert_point_to_units(vector3d(loc[0], loc[1], 0)) - self.cell.add_via_center(layers=self.layers, - offset=vector(loc.x, loc.y), - size=(size, size)) - - def compute_pin_enclosure(self, ll, ur, zindex, name=""): - """ - Enclose the tracks from ll to ur in a single rectangle that meets - the track DRC rules. - """ - layer = self.get_layer(zindex) - - # This finds the pin shape enclosed by the - # track with DRC spacing on the sides - pin = self.convert_track_to_pin(ll) - (abs_ll, unused) = pin.rect - pin = self.convert_track_to_pin(ur) - (unused, abs_ur) = pin.rect - - pin = pin_layout(name, [abs_ll, abs_ur], layer) - - return pin - - def contract_path(self, path): - """ - Remove intermediate points in a rectilinear path or a wave. - """ - # Waves are always linear, so just return the first and last. - if self.is_wave(path): - return [path[0], path[-1]] - - # Make a list only of points that change inertia of the path - newpath = [path[0]] - for i in range(1, len(path) - 1): - prev_inertia = self.get_inertia(path[i-1][0], path[i][0]) - next_inertia = self.get_inertia(path[i][0], path[i+1][0]) - # if we switch directions, add the point, otherwise don't - if prev_inertia != next_inertia: - newpath.append(path[i]) - - # always add the last path unless it was a single point - if len(path) > 1: - newpath.append(path[-1]) - return newpath - - def run_router(self, detour_scale): - """ - This assumes the blockages, source, and target are all set up. + Custom `add_route` function since `hierarchy_layout.add_route` isn't + working for this router. """ - # Double check source and taget are not same node, if so, we are done! - for k, v in self.rg.map.items(): - if v.source and v.target: - self.paths.append([k]) - return True + for i in range(0, len(nodes) - 1): + start = nodes[i].center + end = nodes[i + 1].center + direction = nodes[i].get_direction(nodes[i + 1]) + diff = start - end + offset = start.min(end) + offset = vector(offset.x - self.half_wire, + offset.y - self.half_wire) + if direction == (1, 1): # Via + offset = vector(start.x, start.y) + self.design.add_via_center(layers=self.layers, + offset=offset) + else: # Wire + self.design.add_rect(layer=self.get_layer(start.z), + offset=offset, + width=abs(diff.x) + self.track_wire, + height=abs(diff.y) + self.track_wire) - # returns the path in tracks - (path, cost) = self.rg.route(detour_scale) - if path: - debug.info(2, "Found path: cost={0} {1}".format(cost, str(path))) - self.paths.append(grid_utils.flatten_set(path)) - self.add_route(path) - self.create_route_connector(path, - self.source_name, - self.source_components) - self.create_route_connector(path, - self.target_name, - self.target_components) - self.path_blockages.append(self.paths[-1]) - #self.write_debug_gds("debug_route.gds", False) - #breakpoint() - return True - else: - return False + def write_debug_gds(self, gds_name, g=None, source=None, target=None): + """ Write the debug GDSII file for the router. """ - def annotate_pin_and_tracks(self, pin, tracks): - """" - Annotate some shapes for debug purposes - """ - debug.info(0, "Annotating\n pin {0}\n tracks {1}".format(pin, tracks)) - for coord in tracks: - (ll, ur) = self.convert_track_to_shape(coord) - self.cell.add_rect(layer="text", - offset=ll, - width=ur[0] - ll[0], - height=ur[1] - ll[1]) - # (ll, ur) = self.convert_track_to_pin(coord).rect - # self.cell.add_rect(layer="boundary", - # offset=ll, - # width=ur[0] - ll[0], - # height=ur[1] - ll[1]) - (ll, ur) = pin.rect - self.cell.add_rect(layer="text", - offset=ll, - width=ur[0] - ll[0], - height=ur[1] - ll[1]) - - def write_debug_gds(self, gds_name="debug_route.gds", stop_program=True): - """ - Write out a GDS file with the routing grid and - search information annotated on it. - """ - debug.info(0, "Writing annotated router gds file to {}".format(gds_name)) - self.add_router_info() - self.cell.gds_write(gds_name) + self.add_router_info(g, source, target) + self.design.gds_write(gds_name) self.del_router_info() - if stop_program: - import sys - sys.exit(1) - def annotate_grid(self, g): + def add_router_info(self, g=None, source=None, target=None): """ - Display grid information in the GDS file for a single grid cell. + Add debug information to the text layer about the graph and router. """ - shape = self.convert_track_to_shape(g) - partial_track = vector(0, self.track_width / 6.0) - self.cell.add_rect(layer="text", - offset=shape[0], - width=shape[1].x - shape[0].x, - height=shape[1].y - shape[0].y) - t = self.rg.map[g].get_type() - # midpoint offset - off = vector((shape[1].x + shape[0].x) / 2, - (shape[1].y + shape[0].y) / 2) - if t: - if g[2] == 1: - # Upper layer is upper right label - type_off = off + partial_track - else: - # Lower layer is lower left label - type_off = off - partial_track - self.cell.add_label(text=str(t), - layer="text", - offset=type_off) + # Display the inflated blockage + if g: + for blockage in self.blockages: + if blockage in g.graph_blockages: + self.add_object_info(blockage, "blockage{}++[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) + else: + self.add_object_info(blockage, "blockage{}[{}]".format(self.get_zindex(blockage.lpp), blockage.name)) + for node in g.nodes: + offset = (node.center.x, node.center.y) + self.design.add_label(text="n{}".format(node.center.z), + layer="text", + offset=offset) + else: + for blockage in self.blockages: + self.add_object_info(blockage, "blockage{}".format(self.get_zindex(blockage.lpp))) + for pin in self.fake_pins: + self.add_object_info(pin, "fake") + if source: + self.add_object_info(source, "source") + if target: + self.add_object_info(target, "target") - t = self.rg.map[g].get_cost() - partial_track = vector(self.track_width/6.0, 0) - if t: - if g[2] == 1: - # Upper layer is right label - type_off = off + partial_track - else: - # Lower layer is left label - type_off = off - partial_track - self.cell.add_label(text=str(t), - layer="text", - offset=type_off) - - self.cell.add_label(text="{0},{1}".format(g[0], g[1]), - layer="text", - offset=shape[0]) def del_router_info(self): - """ - Erase all of the comments on the current level. - """ - debug.info(2, "Erasing router info") - lpp = techlayer["text"] - self.cell.objs = [x for x in self.cell.objs if x.lpp != lpp] + """ Delete router information from the text layer. """ - def add_router_info(self): - """ - Write the routing grid and router cost, blockage, pins on - the boundary layer for debugging purposes. This can only be - called once or the labels will overlap. - """ - debug.info(2, "Adding router info") - - show_bbox = False - show_blockages = False - show_blockage_grids = False - show_enclosures = False - show_all_grids = True - - if show_bbox: - self.cell.add_rect(layer="text", - offset=vector(self.ll.x, self.ll.y), - width=self.ur.x - self.ll.x, - height=self.ur.y - self.ll.y) - - if show_all_grids: - for g in self.rg.map: - self.annotate_grid(g) - - if show_blockages: - # Display the inflated blockage - for blockage in self.blockages: - debug.info(1, "Adding {}".format(blockage)) - (ll, ur) = blockage.inflate() - self.cell.add_rect(layer="text", - offset=ll, - width=ur.x - ll.x, - height=ur.y - ll.y) - if show_blockage_grids: - self.set_blockages(self.blocked_grids, True) - for g in self.rg.map: - self.annotate_grid(g) - - if show_enclosures: - for key in self.pin_groups: - for pg in self.pin_groups[key]: - if not pg.enclosed: - continue - for pin in pg.enclosures: - # print("enclosure: ", - # pin.name, - # pin.ll(), - # pin.width(), - # pin.height()) - self.cell.add_rect(layer="text", - offset=pin.ll(), - width=pin.width(), - height=pin.height()) - - def get_perimeter_pin(self): - """ Return the shape of the last routed path that was on the perimeter """ - lastpath = self.paths[-1] - for v in lastpath: - if self.rg.is_target(v): - # Find neighboring grid to make double wide pin - neighbor = v + vector3d(0, 1, 0) - if neighbor in lastpath: - return self.convert_tracks_to_pin([v, neighbor]) - neighbor = v + vector3d(0, -1, 0) - if neighbor in lastpath: - return self.convert_tracks_to_pin([v, neighbor]) - neighbor = v + vector3d(1, 0, 0) - if neighbor in lastpath: - return self.convert_tracks_to_pin([v, neighbor]) - neighbor = v + vector3d(-1, 0, 0) - if neighbor in lastpath: - return self.convert_tracks_to_pin([v, neighbor]) - - # Else if we came from a different layer, we can only add - # a signle grid - return self.convert_track_to_pin(v) - - return None - - def get_ll_pin(self, pin_name): - """ Return the lowest, leftest pin group """ - - keep_pin = None - for index, pg in enumerate(self.pin_groups[pin_name]): - for pin in pg.enclosures: - if not keep_pin: - keep_pin = pin - else: - if pin.lx() <= keep_pin.lx() and pin.by() <= keep_pin.by(): - keep_pin = pin - - return keep_pin - - def check_all_routed(self, pin_name): - """ - Check that all pin groups are routed. - """ - for pg in self.pin_groups[pin_name]: - if not pg.is_routed(): - return False + lpp = tech_layer["text"] + self.design.objs = [x for x in self.design.objs if x.lpp != lpp] -# FIXME: This should be replaced with vector.snap_to_grid at some point -def snap_to_grid(offset): - """ - Changes the coodrinate to match the grid settings - """ - xoff = snap_val_to_grid(offset[0]) - yoff = snap_val_to_grid(offset[1]) - return vector(xoff, yoff) + def add_object_info(self, obj, label): + """ Add debug information to the text layer about an object. """ - -def snap_val_to_grid(x): - grid = drc("grid") - xgrid = int(round(round((x / grid), 2), 0)) - xoff = xgrid * grid - return xoff + 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) diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py deleted file mode 100644 index 4b57cd18..00000000 --- a/compiler/router/signal_escape_router.py +++ /dev/null @@ -1,104 +0,0 @@ -# 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. -# -from datetime import datetime -from openram import debug -from openram import print_time -from .router import router -from .signal_grid import signal_grid - - -class signal_escape_router(router): - """ - A router that routes signals to perimeter and makes pins. - """ - - def __init__(self, layers, design, bbox=None, margin=0): - """ - This will route on layers in design. It will get the blockages from - either the gds file name or the design itself (by saving to a gds file). - """ - router.__init__(self, - layers=layers, - design=design, - bbox=bbox, - margin=margin) - - def perimeter_dist(self, pin_name): - """ - Return the shortest Manhattan distance to the bounding box perimeter. - """ - loc = self.cell.get_pin(pin_name).center() - x_dist = min(loc.x - self.ll.x, self.ur.x - loc.x) - y_dist = min(loc.y - self.ll.y, self.ur.y - loc.y) - - return min(x_dist, y_dist) - - def escape_route(self, pin_names): - """ - Takes a list of tuples (name, side) and routes them. After routing, - it removes the old pin and places a new one on the perimeter. - """ - self.create_routing_grid(signal_grid) - - start_time = datetime.now() - self.find_pins_and_blockages(pin_names) - print_time("Finding pins and blockages",datetime.now(), start_time, 3) - - # Order the routes by closest to the perimeter first - # This prevents some pins near the perimeter from being blocked by other pins - ordered_pin_names = sorted(pin_names, key=lambda x: self.perimeter_dist(x)) - - # Route the supply pins to the supply rails - # Route vdd first since we want it to be shorter - start_time = datetime.now() - for pin_name in ordered_pin_names: - self.route_signal(pin_name) - # if pin_name == "dout0[1]": - # self.write_debug_gds("postroute.gds", True) - - print_time("Maze routing pins",datetime.now(), start_time, 3) - - #self.write_debug_gds("final_escape_router.gds",False) - - return True - - def route_signal(self, pin_name, side="all"): - - for detour_scale in [5 * pow(2, x) for x in range(5)]: - debug.info(1, "Escape routing {0} with scale {1}".format(pin_name, detour_scale)) - - # Clear everything in the routing grid. - self.rg.reinit() - - # This is inefficient since it is non-incremental, but it was - # easier to debug. - self.prepare_blockages() - self.clear_blockages(pin_name) - - # Add the single component of the pin as the source - # which unmarks it as a blockage too - self.add_source(pin_name) - - # Marks the grid cells all along the perimeter as a target - self.add_perimeter_target(side) - - # if pin_name == "dout0[3]": - # self.write_debug_gds("pre_route.gds", False) - # breakpoint() - - # Actually run the A* router - if self.run_router(detour_scale=detour_scale): - new_pin = self.get_perimeter_pin() - self.cell.replace_layout_pin(pin_name, new_pin) - return - - # if pin_name == "dout0[3]": - # self.write_debug_gds("pre_route.gds", False) - # breakpoint() - - self.write_debug_gds("debug_route.gds", True) diff --git a/compiler/router/signal_grid.py b/compiler/router/signal_grid.py deleted file mode 100644 index 69ab2c94..00000000 --- a/compiler/router/signal_grid.py +++ /dev/null @@ -1,165 +0,0 @@ -# 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. -# -from copy import deepcopy -from heapq import heappush,heappop -from openram import debug -from openram.base.vector3d import vector3d -from .grid import grid -from .grid_path import grid_path - - -class signal_grid(grid): - """ - Expand the two layer grid to include A* search functions for a source and target. - """ - - def __init__(self, ll, ur, track_factor): - """ Create a routing map of width x height cells and 2 in the z-axis. """ - grid.__init__(self, ll, ur, track_factor) - - def reinit(self): - """ Reinitialize everything for a new route. """ - - # Reset all the cells in the map - for p in self.map.values(): - p.reset() - - self.clear_source() - self.clear_target() - - def init_queue(self): - """ - Populate the queue with all the source pins with cost - to the target. Each item is a path of the grid cells. - We will use an A* search, so this cost must be pessimistic. - Cost so far will be the length of the path. - """ - # Counter is used to not require data comparison in Python 3.x - # Items will be returned in order they are added during cost ties - self.q = [] - self.counter = 0 - for s in self.source: - cost = self.cost_to_target(s) - debug.info(3, "Init: cost=" + str(cost) + " " + str([s])) - heappush(self.q, (cost, self.counter, grid_path([vector3d(s)]))) - self.counter += 1 - - def route(self, detour_scale): - """ - This does the A* maze routing with preferred direction routing. - This only works for 1 track wide routes! - """ - - # We set a cost bound of the HPWL for run-time. This can be - # over-ridden if the route fails due to pruning a feasible solution. - any_source_element = next(iter(self.source)) - cost_bound = detour_scale * self.cost_to_target(any_source_element) * grid.PREFERRED_COST - - # Check if something in the queue is already a source and a target! - for s in self.source: - if self.is_target(s): - return((grid_path([vector3d(s)]), 0)) - - # Put the source items into the queue - self.init_queue() - - # Keep expanding and adding to the priority queue until we are done - while len(self.q)>0: - # should we keep the path in the queue as well or just the final node? - (cost, count, curpath) = heappop(self.q) - debug.info(3, "Queue size: size=" + str(len(self.q)) + " " + str(cost)) - debug.info(4, "Expanding: cost=" + str(cost) + " " + str(curpath)) - - # expand the last element - neighbors = self.expand_dirs(curpath) - debug.info(4, "Neighbors: " + str(neighbors)) - - for n in neighbors: - # make a new copy of the path to not update the old ones - newpath = deepcopy(curpath) - # node is added to the map by the expand routine - newpath.append(n) - # check if we hit the target and are done - if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide - return (newpath, newpath.cost()) - else: - # current path cost + predicted cost - current_cost = newpath.cost() - target_cost = self.cost_to_target(n[0]) - predicted_cost = current_cost + target_cost - # only add the cost if it is less than our bound - if (predicted_cost < cost_bound): - if (self.map[n[0]].min_cost==-1 or predicted_cost self.ur.x: - return None - if wave[-1].y > self.ur.y: - return None - - while wave and self.is_wave_blocked(wave): - wf = grid_path(wave) - wave = wf.neighbor(direct) - # Bail out if we couldn't increment futher - if wave[0].x > self.ur.x or wave[-1].y > self.ur.y: - return None - # Return a start if it isn't blocked - if not self.is_wave_blocked(wave): - return wave - - return wave - - def is_wave_blocked(self, wave): - """ - Checks if any of the locations are blocked - """ - for v in wave: - if self.is_blocked(v): - return True - else: - return False - - def probe(self, wave, direct): - """ - Expand the wave until there is a blockage and return - the wave path. - """ - wave_path = grid_path() - while wave and not self.is_wave_blocked(wave): - if wave[0].x > self.ur.x or wave[-1].y > self.ur.y: - break - wave_path.append(wave) - wave = wave_path.neighbor(direct) - - return wave_path diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py deleted file mode 100644 index 0586485b..00000000 --- a/compiler/router/supply_grid_router.py +++ /dev/null @@ -1,394 +0,0 @@ -# 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. -# -from datetime import datetime -from openram import debug -from openram.base.vector3d import vector3d -from openram import print_time -from .router import router -from .direction import direction -from .supply_grid import supply_grid -from . import grid_utils - - -class supply_grid_router(router): - """ - A router class to read an obstruction map from a gds and - routes a grid to connect the supply on the two layers. - """ - - def __init__(self, layers, design, bbox=None, pin_type=None): - """ - This will route on layers in design. It will get the blockages from - either the gds file name or the design itself (by saving to a gds file). - """ - start_time = datetime.now() - - # Power rail width in minimum wire widths - self.route_track_width = 1 - - router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width) - - # The list of supply rails (grid sets) that may be routed - self.supply_rails = {} - # This is the same as above but as a sigle set for the all the rails - self.supply_rail_tracks = {} - - print_time("Init supply router", datetime.now(), start_time, 3) - - def route(self, vdd_name="vdd", gnd_name="gnd"): - """ - Add power supply rails and connect all pins to these rails. - """ - debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) - self.vdd_name = vdd_name - self.gnd_name = gnd_name - - # Clear the pins if we have previously routed - if (hasattr(self, 'rg')): - self.clear_pins() - else: - # Creat a routing grid over the entire area - # FIXME: This could be created only over the routing region, - # but this is simplest for now. - self.create_routing_grid(supply_grid) - - # Get the pin shapes - start_time = datetime.now() - self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) - print_time("Finding pins and blockages", datetime.now(), start_time, 3) - # Add the supply rails in a mesh network and connect H/V with vias - start_time = datetime.now() - # Block everything - self.prepare_blockages() - self.clear_blockages(self.gnd_name) - - - # Determine the rail locations - self.route_supply_rails(self.gnd_name, 0) - - # Block everything - self.prepare_blockages() - self.clear_blockages(self.vdd_name) - # Determine the rail locations - self.route_supply_rails(self.vdd_name, 1) - print_time("Routing supply rails", datetime.now(), start_time, 3) - - start_time = datetime.now() - self.route_simple_overlaps(vdd_name) - self.route_simple_overlaps(gnd_name) - print_time("Simple overlap routing", datetime.now(), start_time, 3) - - # Route the supply pins to the supply rails - # Route vdd first since we want it to be shorter - start_time = datetime.now() - self.route_pins_to_rails(vdd_name) - self.route_pins_to_rails(gnd_name) - print_time("Maze routing supplies", datetime.now(), start_time, 3) - # self.write_debug_gds("final.gds", False) - - # Did we route everything?? - if not self.check_all_routed(vdd_name): - return False - if not self.check_all_routed(gnd_name): - return False - - return True - - def route_simple_overlaps(self, pin_name): - """ - This checks for simple cases where a pin component already overlaps a supply rail. - It will add an enclosure to ensure the overlap in wide DRC rule cases. - """ - debug.info(1, "Routing simple overlap pins for {0}".format(pin_name)) - - # These are the wire tracks - wire_tracks = self.supply_rail_tracks[pin_name] - routed_count=0 - for pg in self.pin_groups[pin_name]: - if pg.is_routed(): - continue - - # First, check if we just overlap, if so, we are done. - overlap_grids = wire_tracks & pg.grids - if len(overlap_grids)>0: - routed_count += 1 - pg.set_routed() - continue - - # Else, if we overlap some of the space track, we can patch it with an enclosure - # pg.create_simple_overlap_enclosure(pg.grids) - # pg.add_enclosure(self.cell) - - debug.info(1, "Routed {} simple overlap pins".format(routed_count)) - - def finalize_supply_rails(self, name): - """ - Determine which supply rails overlap and can accomodate a via. - Remove any supply rails that do not have a via since they are disconnected. - NOTE: It is still possible though unlikely that there are disconnected groups of rails. - """ - - all_rails = self.supply_rails[name] - - connections = set() - via_areas = [] - for i1, r1 in enumerate(all_rails): - # Only consider r1 horizontal rails - e = next(iter(r1)) - if e.z==1: - continue - - # We need to move this rail to the other layer for the z indices to match - # during the intersection. This also makes a copy. - new_r1 = {vector3d(i.x, i.y, 1) for i in r1} - - for i2, r2 in enumerate(all_rails): - # Never compare to yourself - if i1==i2: - continue - - # Only consider r2 vertical rails - e = next(iter(r2)) - if e.z==0: - continue - - # Determine if we have sufficient overlap and, if so, - # remember: - # the indices to determine a rail is connected to another - # the overlap area for placement of a via - overlap = new_r1 & r2 - if len(overlap) >= 1: - debug.info(3, "Via overlap {0} {1}".format(len(overlap),overlap)) - connections.update([i1, i2]) - via_areas.append(overlap) - - # Go through and add the vias at the center of the intersection - for area in via_areas: - ll = grid_utils.get_lower_left(area) - ur = grid_utils.get_upper_right(area) - center = (ll + ur).scale(0.5, 0.5, 0) - self.add_via(center, 1) - - # Determien which indices were not connected to anything above - missing_indices = set([x for x in range(len(self.supply_rails[name]))]) - missing_indices.difference_update(connections) - - # Go through and remove those disconnected indices - # (No via was added, so that doesn't need to be removed) - for rail_index in sorted(missing_indices, reverse=True): - ll = grid_utils.get_lower_left(all_rails[rail_index]) - ur = grid_utils.get_upper_right(all_rails[rail_index]) - debug.info(1, "Removing disconnected supply rail {0} .. {1}".format(ll, ur)) - self.supply_rails[name].pop(rail_index) - - # Make the supply rails into a big giant set of grids for easy blockages. - # Must be done after we determine which ones are connected. - self.create_supply_track_set(name) - - def add_supply_rails(self, name): - """ - Add the shapes that represent the routed supply rails. - This is after the paths have been pruned and only include rails that are - connected with vias. - """ - for rail in self.supply_rails[name]: - ll = grid_utils.get_lower_left(rail) - ur = grid_utils.get_upper_right(rail) - z = ll.z - pin = self.compute_pin_enclosure(ll, ur, z, name) - debug.info(3, "Adding supply rail {0} {1}->{2} {3}".format(name, ll, ur, pin)) - self.cell.add_layout_pin(text=name, - layer=pin.layer, - offset=pin.ll(), - width=pin.width(), - height=pin.height()) - - def compute_supply_rails(self, name, supply_number): - """ - Compute the unblocked locations for the horizontal and vertical supply rails. - Go in a raster order from bottom to the top (for horizontal) and left to right - (for vertical). Start with an initial start_offset in x and y direction. - """ - - self.supply_rails[name]=[] - - max_yoffset = self.rg.ur.y - max_xoffset = self.rg.ur.x - min_yoffset = self.rg.ll.y - min_xoffset = self.rg.ll.x - - # Horizontal supply rails - start_offset = min_yoffset + supply_number - for offset in range(start_offset, max_yoffset, 2): - # Seed the function at the location with the given width - wave = [vector3d(min_xoffset, offset, 0)] - # While we can keep expanding east in this horizontal track - while wave and wave[0].x < max_xoffset: - added_rail = self.find_supply_rail(name, wave, direction.EAST) - if not added_rail: - # Just seed with the next one - wave = [x+vector3d(1, 0, 0) for x in wave] - else: - # Seed with the neighbor of the end of the last rail - wave = added_rail.neighbor(direction.EAST) - - # Vertical supply rails - start_offset = min_xoffset + supply_number - for offset in range(start_offset, max_xoffset, 2): - # Seed the function at the location with the given width - wave = [vector3d(offset, min_yoffset, 1)] - # While we can keep expanding north in this vertical track - while wave and wave[0].y < max_yoffset: - added_rail = self.find_supply_rail(name, wave, direction.NORTH) - if not added_rail: - # Just seed with the next one - wave = [x + vector3d(0, 1, 0) for x in wave] - else: - # Seed with the neighbor of the end of the last rail - wave = added_rail.neighbor(direction.NORTH) - - def find_supply_rail(self, name, seed_wave, direct): - """ - Find a start location, probe in the direction, and see if the rail is big enough - to contain a via, and, if so, add it. - """ - # Sweep to find an initial unblocked valid wave - start_wave = self.rg.find_start_wave(seed_wave, direct) - - # This means there were no more unblocked grids in the row/col - if not start_wave: - return None - - wave_path = self.probe_supply_rail(name, start_wave, direct) - - self.approve_supply_rail(name, wave_path) - - # Return the rail whether we approved it or not, - # as it will be used to find the next start location - return wave_path - - def probe_supply_rail(self, name, start_wave, direct): - """ - This finds the first valid starting location and routes a supply rail - in the given direction. - It returns the space after the end of the rail to seed another call for multiple - supply rails in the same "track" when there is a blockage. - """ - - # Expand the wave to the right - wave_path = self.rg.probe(start_wave, direct) - - if not wave_path: - return None - - # drop the first and last steps to leave escape routing room - # around the blockage that stopped the probe - # except, don't drop the first if it is the first in a row/column - if (direct==direction.NORTH and start_wave[0].y>0): - wave_path.trim_first() - elif (direct == direction.EAST and start_wave[0].x>0): - wave_path.trim_first() - - wave_path.trim_last() - - return wave_path - - def approve_supply_rail(self, name, wave_path): - """ - Check if the supply rail is sufficient (big enough) and add it to the - data structure. Return whether it was added or not. - """ - # We must have at least 2 tracks to drop plus 2 tracks for a via - if len(wave_path) >= 4 * self.route_track_width: - grid_set = wave_path.get_grids() - self.supply_rails[name].append(grid_set) - return True - - return False - - def route_supply_rails(self, name, supply_number): - """ - Route the horizontal and vertical supply rails across the entire design. - Must be done with lower left at 0,0 - """ - debug.info(1, "Routing supply rail {0}.".format(name)) - - # Compute the grid locations of the supply rails - self.compute_supply_rails(name, supply_number) - - # Add the supply rail vias (and prune disconnected rails) - self.finalize_supply_rails(name) - - # Add the rails themselves - self.add_supply_rails(name) - - def create_supply_track_set(self, pin_name): - """ - Make a single set of all the tracks for the rail and wire itself. - """ - rail_set = set() - for rail in self.supply_rails[pin_name]: - rail_set.update(rail) - self.supply_rail_tracks[pin_name] = rail_set - - def route_pins_to_rails(self, pin_name): - """ - This will route each of the remaining pin components to the supply rails. - After it is done, the cells are added to the pin blockage list. - """ - - remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name]) - debug.info(1, "Maze routing {0} with {1} pin components to connect.".format(pin_name, - remaining_components)) - - for index, pg in enumerate(self.pin_groups[pin_name]): - if pg.is_routed(): - continue - - debug.info(3, "Routing component {0} {1}".format(pin_name, index)) - - # Clear everything in the routing grid. - self.rg.reinit() - - # This is inefficient since it is non-incremental, but it was - # easier to debug. - self.prepare_blockages() - self.clear_blockages(self.vdd_name) - - # Add the single component of the pin as the source - # which unmarks it as a blockage too - self.add_pin_component_source(pin_name, index) - - # Add all of the rails as targets - # Don't add the other pins, but we could? - self.add_supply_rail_target(pin_name) - - # Actually run the A* router - if not self.run_router(detour_scale=5): - self.write_debug_gds("debug_route.gds") - - # if index==3 and pin_name=="vdd": - # self.write_debug_gds("route.gds",False) - - def add_supply_rail_target(self, pin_name): - """ - Add the supply rails of given name as a routing target. - """ - debug.info(4, "Add supply rail target {}".format(pin_name)) - # Add the wire itself as the target - self.rg.set_target(self.supply_rail_tracks[pin_name]) - # But unblock all the rail tracks including the space - self.rg.set_blocked(self.supply_rail_tracks[pin_name], False) - - def set_supply_rail_blocked(self, value=True): - """ - Add the supply rails of given name as a routing target. - """ - debug.info(4, "Blocking supply rail") - for rail_name in self.supply_rail_tracks: - self.rg.set_blocked(self.supply_rail_tracks[rail_name]) diff --git a/compiler/router/supply_graph_router.py b/compiler/router/supply_router.py similarity index 99% rename from compiler/router/supply_graph_router.py rename to compiler/router/supply_router.py index d3506108..2510103b 100644 --- a/compiler/router/supply_graph_router.py +++ b/compiler/router/supply_router.py @@ -7,11 +7,11 @@ from openram import debug from openram.base.vector import vector from openram import OPTS from .graph import graph -from .graph_router import graph_router +from .router import router from .graph_shape import graph_shape -class supply_graph_router(graph_router): +class supply_router(router): """ This is the supply router that uses the Hanan grid graph method. """ diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py deleted file mode 100644 index 77247152..00000000 --- a/compiler/router/supply_tree_router.py +++ /dev/null @@ -1,208 +0,0 @@ -# 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. -# -from datetime import datetime -from scipy.sparse import csr_matrix -from scipy.sparse.csgraph import minimum_spanning_tree -from openram import debug -from openram import print_time -from .router import router -from . import grid_utils -from .signal_grid import signal_grid - - -class supply_tree_router(router): - """ - A router class to read an obstruction map from a gds and - routes a grid to connect the supply on the two layers. - """ - - def __init__(self, layers, design, bbox=None, pin_type=None): - """ - This will route on layers in design. It will get the blockages from - either the gds file name or the design itself (by saving to a gds file). - """ - # Power rail width in minimum wire widths - # This is set to match the signal router so that the grids are aligned - # for prettier routes. - self.route_track_width = 1 - - # The pin escape router already made the bounding box big enough, - # so we can use the regular bbox here. - if pin_type: - debug.check(pin_type in ["left", "right", "top", "bottom", "single", "ring"], - "Invalid pin type {}".format(pin_type)) - self.pin_type = pin_type - router.__init__(self, - layers, - design, - bbox=bbox, - route_track_width=self.route_track_width) - - - def route(self, vdd_name="vdd", gnd_name="gnd"): - """ - Route the two nets in a single layer. - Setting pin stripe will make a power rail on the left side. - """ - debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) - self.vdd_name = vdd_name - self.gnd_name = gnd_name - - # Clear the pins if we have previously routed - if (hasattr(self, 'rg')): - self.clear_pins() - else: - # Creat a routing grid over the entire area - # FIXME: This could be created only over the routing region, - # but this is simplest for now. - self.create_routing_grid(signal_grid) - - start_time = datetime.now() - - # Get the pin shapes - self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) - print_time("Finding pins and blockages", datetime.now(), start_time, 3) - - # Add side pins if enabled - if self.pin_type in ["left", "right", "top", "bottom"]: - self.add_side_supply_pin(self.vdd_name, side=self.pin_type) - self.add_side_supply_pin(self.gnd_name, side=self.pin_type) - elif self.pin_type == "ring": - self.add_ring_supply_pin(self.vdd_name) - self.add_ring_supply_pin(self.gnd_name) - - #self.write_debug_gds("initial_tree_router.gds",False) - #breakpoint() - - # Route the supply pins to the supply rails - # Route vdd first since we want it to be shorter - start_time = datetime.now() - self.route_pins(vdd_name) - self.route_pins(gnd_name) - print_time("Maze routing supplies", datetime.now(), start_time, 3) - - # Did we route everything?? - if not self.check_all_routed(vdd_name): - return False - if not self.check_all_routed(gnd_name): - return False - - return True - - def route_pins(self, pin_name): - """ - This will route each of the remaining pin components to the other pins. - After it is done, the cells are added to the pin blockage list. - """ - - remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name]) - debug.info(1, "Routing {0} with {1} pins.".format(pin_name, - remaining_components)) - - # Save pin center locations - if False: - debug.info(2, "Creating location file {0}_{1}.csv".format(self.cell.name, pin_name)) - f = open("{0}_{1}.csv".format(self.cell.name, pin_name), "w") - pin_size = len(self.pin_groups[pin_name]) - for index1, pg1 in enumerate(self.pin_groups[pin_name]): - location = list(pg1.grids)[0] - f.write("{0},{1},{2}\n".format(location.x, location.y, location.z)) - f.close() - - # Create full graph - debug.info(2, "Creating adjacency matrix") - pin_size = len(self.pin_groups[pin_name]) - adj_matrix = [[0] * pin_size for i in range(pin_size)] - - for index1, pg1 in enumerate(self.pin_groups[pin_name]): - for index2, pg2 in enumerate(self.pin_groups[pin_name]): - if index1>=index2: - continue - dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids)) - adj_matrix[index1][index2] = dist - - # Find MST - debug.info(2, "Finding Minimum Spanning Tree") - X = csr_matrix(adj_matrix) - from scipy.sparse import save_npz - #print("Saving {}.npz".format(self.cell.name)) - #save_npz("{}.npz".format(self.cell.name), X) - #exit(1) - - Tcsr = minimum_spanning_tree(X) - mst = Tcsr.toarray().astype(int) - connections = [] - for x in range(pin_size): - for y in range(pin_size): - if x >= y: - continue - if mst[x][y]>0: - connections.append((x, y)) - - # Route MST components - level=99 - for index, (src, dest) in enumerate(connections): - if not (index % 25): - debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index)) - self.route_signal(pin_name, src, dest) - if False and pin_name == "gnd": - debug.info(level, "\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages)) - debug.info(level, ("DST {}: ".format(dest) + str(self.pin_groups[pin_name][dest].grids) + str(self.pin_groups[pin_name][dest].blockages))) - self.write_debug_gds("post_{0}_{1}.gds".format(src, dest), False) - - #self.write_debug_gds("final_tree_router_{}.gds".format(pin_name), False) - #return - - def route_signal(self, pin_name, src_idx, dest_idx): - - # First pass, try to route normally - # Second pass, clear prior pin blockages so that you can route over other metal - # of the same supply. Otherwise, this can create a lot of circular routes due to accidental overlaps. - for unblock_routes in [False, True]: - for detour_scale in [2 * pow(2, x) for x in range(5)]: - debug.info(2, "Routing {0} to {1} with scale {2}".format(src_idx, dest_idx, detour_scale)) - - # Clear everything in the routing grid. - self.rg.reinit() - - # This is inefficient since it is non-incremental, but it was - # easier to debug. - self.prepare_blockages(src=(pin_name, src_idx), dest=(pin_name, dest_idx)) - if unblock_routes: - msg = "Unblocking supply self blockages to improve access (may cause DRC errors):\n{0}\n{1})" - debug.warning(msg.format(pin_name, - self.pin_groups[pin_name][src_idx].pins)) - self.set_blockages(self.path_blockages, False) - - # Add the single component of the pin as the source - # which unmarks it as a blockage too - self.set_pin_component_source(pin_name, src_idx) - - # Marks all pin components except index as target - # which unmarks it as a blockage too - self.set_pin_component_target(pin_name, dest_idx) - - # Actually run the A* router - if self.run_router(detour_scale=detour_scale): - return - #if detour_scale > 2: - # self.write_debug_gds("route_{0}_{1}_d{2}.gds".format(src_idx, dest_idx, detour_scale), False) - - self.write_debug_gds("debug_route.gds", True) - - def add_io_pin(self, instance, pin_name, new_name=""): - """ - Add a signle input or output pin up to metal 3. - """ - pin = instance.get_pins(pin_name) - - if new_name == "": - new_name = pin_name - - # Just use the power pin function for now to save code - self.add_power_pin(name=new_name, loc=pin.center(), start_layer=pin.layer) From 93a6549539d1529c43814325bcb1853d10797850 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 11:10:32 -0700 Subject: [PATCH 72/91] Fix typo --- compiler/router/supply_router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index 2510103b..06f8fbb8 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -19,7 +19,7 @@ class supply_router(router): def __init__(self, layers, design, bbox=None, pin_type=None): # `router_tech` contains tech constants for the router - graph_router.__init__(self, layers, design, bbox) + router.__init__(self, layers, design, bbox) # Side supply pin type # (can be "top", "bottom", "right", "left", and "ring") From 993b47be4c65c89c59719b4ffaf0c248ab59521d Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 11:22:50 -0700 Subject: [PATCH 73/91] Remove old routers from sram_1bank --- compiler/modules/sram_1bank.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 799c7b0d..997666df 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -255,17 +255,12 @@ class sram_1bank(design, verilog, lef): if not OPTS.route_supplies: # Do not route the power supply (leave as must-connect pins) return - elif OPTS.route_supplies == "grid": - from openram.router import supply_grid_router as router - elif OPTS.route_supplies == "tree": - from openram.router import supply_tree_router as router else: - from openram.router import supply_graph_router as router - rtr=router(layers=self.supply_stack, - design=self, - bbox=bbox, - pin_type=OPTS.supply_pin_type) - + from openram.router import supply_router as router + rtr = router(layers=self.supply_stack, + design=self, + bbox=bbox, + pin_type=OPTS.supply_pin_type) rtr.route() if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]: From 42257fb7f8cc56502d83a4ef99afedc24b7717d0 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 11:25:12 -0700 Subject: [PATCH 74/91] Export router_tech again --- compiler/router/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index ac2a64ec..cb521a12 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -3,5 +3,6 @@ # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # +from .router_tech import * from .signal_escape_router import * from .supply_router import * From dd152da5c2f3e2fe908bf351953e355f0b52558f Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 11:26:25 -0700 Subject: [PATCH 75/91] Change signal escape router's high-level function name --- compiler/modules/sram_1bank.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 997666df..a230936f 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -367,10 +367,10 @@ class sram_1bank(design, verilog, lef): pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) from openram.router import signal_escape_router as router - rtr=router(layers=self.m3_stack, - design=self, - bbox=bbox) - rtr.escape_route(pins_to_route) + rtr = router(layers=self.m3_stack, + design=self, + bbox=bbox) + rtr.route(pins_to_route) def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ From 887a66553b737c4e82007ff12914311e7ec0c95f Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 12:46:02 -0700 Subject: [PATCH 76/91] Implement signal escape router using the new gridless router --- compiler/router/router.py | 4 + compiler/router/signal_escape_router.py | 146 ++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 compiler/router/signal_escape_router.py diff --git a/compiler/router/router.py b/compiler/router/router.py index 64f63d69..a6c02620 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -31,6 +31,10 @@ class router(router_tech): self.design = design # Temporary GDSII file name to find pins and blockages self.gds_filename = OPTS.openram_temp + "temp.gds" + # Bounding box can be given with margin, or created by default + if bbox is None: + bbox = self.design.get_bbox() + self.bbox = bbox # Dictionary for vdd and gnd pins self.pins = {} # Set of all the pins diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py new file mode 100644 index 00000000..570b3cf6 --- /dev/null +++ b/compiler/router/signal_escape_router.py @@ -0,0 +1,146 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from openram import OPTS +from .graph import graph +from .router import router +from .graph_shape import graph_shape + + +class signal_escape_router(router): + """ + This is the signal escape router that uses the Hanan grid graph method. + """ + + def __init__(self, layers, design, bbox=None, pin_type=None): + + # `router_tech` contains tech constants for the router + router.__init__(self, layers, design, bbox) + + # New pins are the side supply pins + self.new_pins = {} + + + def route(self, pin_names): + """ Route the given pins to the perimeter. """ + debug.info(1, "Running signal escape router...") + + # Prepare gdsMill to find pins and blockages + self.prepare_gds_reader() + + # Find pins to be routed + for name in pin_names: + self.find_pins(name) + + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + # Add fake pins on the perimeter to do the escape routing on + self.add_perimeter_fake_pins() + + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(self.inflate_shape(pin, is_pin=True)) + + # Route vdd and gnd + for name in pin_names: + # Route each pin to the perimeter + source = next(iter(self.pins[name])) + target = self.get_closest_perimeter_fake_pin(source) + # Change fake pin's name so the graph will treat it as routable + target.name = source.name + # This is the routing region scale + scale = 1 + while True: + # Create the graph + g = graph(self) + region = g.create_graph(source, target, scale) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If there is no path found, exponentially try again with a + # larger routing region + if path is None: + rll, rur = region + bll, bur = self.bbox + # Stop scaling the region and throw an error + if rll.x < bll.x and rll.y < bll.y and \ + rur.x > bur.x and rur.y > bur.y: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Exponentially scale the region + scale *= 2 + debug.info(0, "Retry routing in larger routing region with scale {}".format(scale)) + continue + # Create the path shapes on layout + self.add_path(path) + # Find the recently added shapes + self.prepare_gds_reader() + self.find_blockages(name) + self.find_vias() + break + + + def add_perimeter_fake_pins(self): + """ + Add the fake pins on the perimeter to where the signals will be routed. + """ + + ll, ur = self.bbox + wide = self.track_wire + + for side in ["top", "bottom", "left", "right"]: + vertical = side in ["left", "right"] + + # Calculate the lower left coordinate + if side == "top": + offset = vector(ll.x, ur.y - wide) + elif side == "bottom": + offset = vector(ll.x, ll.y) + elif side == "left": + offset = vector(ll.x, ll.y) + elif side == "right": + offset = vector(ur.x - wide, ll.y) + + # Calculate width and height + shape = ur - ll + if vertical: + shape_width = wide + shape_height = shape.y + else: + shape_width = shape.x + shape_height = wide + + # Add this new pin + # They must lie on the non-preferred direction since the side supply + # pins will lie on the preferred direction + layer = self.get_layer(int(not vertical)) + nll = vector(offset.x, offset.y) + nur = vector(offset.x + shape_width, offset.y + shape_height) + rect = [nll, nur] + pin = graph_shape(name="fake", + rect=rect, + layer_name_pp=layer) + self.fake_pins.append(pin) + + + def get_closest_perimeter_fake_pin(self, pin): + """ Return the closest fake pin for the given pin. """ + + min_dist = float("inf") + close_fake = None + for fake in self.fake_pins: + dist = pin.distance(fake) + if dist < min_dist: + min_dist = dist + close_fake = fake + return close_fake From 877f20e0718d7a0a923b5ae56c4217e9db66f36e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 19:10:02 -0700 Subject: [PATCH 77/91] Use the new routers in ROMs --- compiler/modules/rom_bank.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/compiler/modules/rom_bank.py b/compiler/modules/rom_bank.py index e4d22f1c..86a2215a 100644 --- a/compiler/modules/rom_bank.py +++ b/compiler/modules/rom_bank.py @@ -114,7 +114,6 @@ class rom_bank(design,rom_verilog): rt = router_tech(self.supply_stack, 1) init_bbox = self.get_bbox(side="ring", margin=rt.track_width) - self.route_supplies(init_bbox) # We need the initial bbox for the supply rings later # because the perimeter pins will change the bbox # Route the pins to the perimeter @@ -125,7 +124,7 @@ class rom_bank(design,rom_verilog): margin=11*rt.track_width) self.route_escape_pins(bbox) - + self.route_supplies(init_bbox) def setup_layout_constants(self): @@ -459,15 +458,12 @@ class rom_bank(design,rom_verilog): if not OPTS.route_supplies: # Do not route the power supply (leave as must-connect pins) return - elif OPTS.route_supplies == "grid": - from openram.router import supply_grid_router as router else: - from openram.router import supply_tree_router as router - rtr=router(layers=self.supply_stack, - design=self, - bbox=bbox, - pin_type=OPTS.supply_pin_type) - + from openram.router import supply_router as router + rtr = router(layers=self.supply_stack, + design=self, + bbox=bbox, + pin_type=OPTS.supply_pin_type) rtr.route() if OPTS.supply_pin_type in ["left", "right", "top", "bottom", "ring"]: @@ -507,7 +503,7 @@ class rom_bank(design,rom_verilog): pins_to_route.append("clk") pins_to_route.append("cs") from openram.router import signal_escape_router as router - rtr=router(layers=self.m3_stack, - design=self, - bbox=bbox) - rtr.escape_route(pins_to_route) + rtr = router(layers=self.m3_stack, + design=self, + bbox=bbox) + rtr.route(pins_to_route) From 937585d23c63105ed9a57122ab5fdaf82ec4d585 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 1 Aug 2023 21:17:43 -0700 Subject: [PATCH 78/91] Route signals to the perimeter in sorted order --- compiler/router/signal_escape_router.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 570b3cf6..d0bd99ca 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -53,10 +53,7 @@ class signal_escape_router(router): self.blockages.append(self.inflate_shape(pin, is_pin=True)) # Route vdd and gnd - for name in pin_names: - # Route each pin to the perimeter - source = next(iter(self.pins[name])) - target = self.get_closest_perimeter_fake_pin(source) + for source, target, _ in self.get_route_pairs(pin_names): # Change fake pin's name so the graph will treat it as routable target.name = source.name # This is the routing region scale @@ -144,3 +141,14 @@ class signal_escape_router(router): min_dist = dist close_fake = fake return close_fake + + + def get_route_pairs(self, pin_names): + """ """ + + to_route = [] + for name in pin_names: + pin = next(iter(self.pins[name])) + fake = self.get_closest_perimeter_fake_pin(pin) + to_route.append((pin, fake, pin.distance(fake))) + return sorted(to_route, key=lambda x: x[2]) From 5b0f97860a738fe5a8012b82c39d5c085c334b2e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 09:30:50 -0700 Subject: [PATCH 79/91] Calculate bbox inside the router --- compiler/modules/rom_bank.py | 20 +++++--------------- compiler/modules/sram_1bank.py | 22 +++++----------------- compiler/router/router.py | 8 +++----- compiler/router/signal_escape_router.py | 4 ++-- compiler/router/supply_router.py | 6 +++--- 5 files changed, 18 insertions(+), 42 deletions(-) diff --git a/compiler/modules/rom_bank.py b/compiler/modules/rom_bank.py index 86a2215a..aa790d6f 100644 --- a/compiler/modules/rom_bank.py +++ b/compiler/modules/rom_bank.py @@ -111,20 +111,12 @@ class rom_bank(design,rom_verilog): self.place_top_level_pins() self.route_output_buffers() - rt = router_tech(self.supply_stack, 1) - init_bbox = self.get_bbox(side="ring", - margin=rt.track_width) - # We need the initial bbox for the supply rings later - # because the perimeter pins will change the bbox + self.route_supplies() # Route the pins to the perimeter if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - bbox = self.get_bbox(side="ring", - margin=11*rt.track_width) - self.route_escape_pins(bbox) - - self.route_supplies(init_bbox) + self.route_escape_pins() def setup_layout_constants(self): @@ -449,7 +441,7 @@ class rom_bank(design,rom_verilog): pin_num = msb - self.col_bits self.add_io_pin(self.decode_inst, "A{}".format(pin_num), name) - def route_supplies(self, bbox=None): + def route_supplies(self): for pin_name in ["vdd", "gnd"]: for inst in self.insts: @@ -462,7 +454,6 @@ class rom_bank(design,rom_verilog): from openram.router import supply_router as router rtr = router(layers=self.supply_stack, design=self, - bbox=bbox, pin_type=OPTS.supply_pin_type) rtr.route() @@ -488,7 +479,7 @@ class rom_bank(design,rom_verilog): pin.width(), pin.height()) - def route_escape_pins(self, bbox): + def route_escape_pins(self): pins_to_route = [] for bit in range(self.col_bits): @@ -504,6 +495,5 @@ class rom_bank(design,rom_verilog): pins_to_route.append("cs") from openram.router import signal_escape_router as router rtr = router(layers=self.m3_stack, - design=self, - bbox=bbox) + design=self) rtr.route(pins_to_route) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index a230936f..95ea9b87 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -243,7 +243,7 @@ class sram_1bank(design, verilog, lef): def create_modules(self): debug.error("Must override pure virtual function.", -1) - def route_supplies(self, bbox=None): + def route_supplies(self): """ Route the supply grid and connect the pins to them. """ # Copy the pins to the top level @@ -259,7 +259,6 @@ class sram_1bank(design, verilog, lef): from openram.router import supply_router as router rtr = router(layers=self.supply_stack, design=self, - bbox=bbox, pin_type=OPTS.supply_pin_type) rtr.route() @@ -323,7 +322,7 @@ class sram_1bank(design, verilog, lef): # Grid is left with many top level pins pass - def route_escape_pins(self, bbox): + def route_escape_pins(self): """ Add the top-level pins for a single bank SRAM with control. """ @@ -368,8 +367,7 @@ class sram_1bank(design, verilog, lef): from openram.router import signal_escape_router as router rtr = router(layers=self.m3_stack, - design=self, - bbox=bbox) + design=self) rtr.route(pins_to_route) def compute_bus_sizes(self): @@ -1075,23 +1073,13 @@ class sram_1bank(design, verilog, lef): self.add_dnwell(inflate=2.5) # Route the supplies together and/or to the ring/stripes. - # This is done with the original bbox since the escape routes need to - # be outside of the ring for OpenLane - rt = router_tech(self.supply_stack, 1) - init_bbox = self.get_bbox(side="ring", - margin=rt.track_width) - - # We need the initial bbox for the supply rings later - # because the perimeter pins will change the bbox # Route the pins to the perimeter if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - bbox = self.get_bbox(side="ring", - margin=11*rt.track_width) - self.route_escape_pins(bbox) + self.route_escape_pins() - self.route_supplies(init_bbox) + self.route_supplies() def route_dffs(self, add_routes=True): diff --git a/compiler/router/router.py b/compiler/router/router.py index a6c02620..acf1a00c 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -20,7 +20,7 @@ class router(router_tech): This is the base class for routers that use the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None): + def __init__(self, layers, design): # `router_tech` contains tech constants for the router router_tech.__init__(self, layers, route_track_width=1) @@ -31,10 +31,8 @@ class router(router_tech): self.design = design # Temporary GDSII file name to find pins and blockages self.gds_filename = OPTS.openram_temp + "temp.gds" - # Bounding box can be given with margin, or created by default - if bbox is None: - bbox = self.design.get_bbox() - self.bbox = bbox + # Calculate the bounding box for routing around the perimeter + self.bbox = self.design.get_bbox(margin=11 * self.track_width) # Dictionary for vdd and gnd pins self.pins = {} # Set of all the pins diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index d0bd99ca..8e6479cb 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -16,10 +16,10 @@ class signal_escape_router(router): This is the signal escape router that uses the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None, pin_type=None): + def __init__(self, layers, design): # `router_tech` contains tech constants for the router - router.__init__(self, layers, design, bbox) + router.__init__(self, layers, design) # New pins are the side supply pins self.new_pins = {} diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index 06f8fbb8..aa774731 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -16,10 +16,10 @@ class supply_router(router): This is the supply router that uses the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None, pin_type=None): + def __init__(self, layers, design, pin_type=None): # `router_tech` contains tech constants for the router - router.__init__(self, layers, design, bbox) + router.__init__(self, layers, design) # Side supply pin type # (can be "top", "bottom", "right", "left", and "ring") @@ -106,7 +106,7 @@ class supply_router(router): def calculate_ring_bbox(self, num_vias=3): """ Calculate the ring-safe bounding box of the layout. """ - ll, ur = self.design.get_bbox() + ll, ur = self.bbox # Calculate the "wideness" of a side supply pin wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) # Total wideness is used to find it any pin overlaps in this region. If From 08dad8121447b452387e29a236317543159fcf9e Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 17:48:56 -0700 Subject: [PATCH 80/91] Use the same inflating rules for all shapes in router --- compiler/router/router.py | 37 ++++++++++++------------- compiler/router/signal_escape_router.py | 3 +- compiler/router/supply_router.py | 4 +-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/compiler/router/router.py b/compiler/router/router.py index acf1a00c..917c49d9 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -145,7 +145,7 @@ class router(router_tech): # Skip this via if it's contained by an existing via blockage if new_shape.contained_by_any(self.vias): continue - self.vias.append(self.inflate_shape(new_shape, is_via=True)) + self.vias.append(self.inflate_shape(new_shape)) def convert_vias(self): @@ -188,32 +188,31 @@ class router(router_tech): break - def inflate_shape(self, shape, is_pin=False, is_via=False): + def inflate_shape(self, shape): """ Inflate a given shape with spacing rules. """ - # Pins must keep their center lines away from any blockage to prevent - # the nodes from being unconnected - if is_pin: - xdiff = self.layer_widths[0] - shape.width() - ydiff = self.layer_widths[0] - shape.height() - diff = max(xdiff, ydiff) / 2 - spacing = self.track_space + drc["grid"] - if diff > 0: - spacing += diff - # Vias are inflated by the maximum spacing rule - elif is_via: - spacing = self.track_space - # Blockages are inflated by their layer's corresponding spacing rule + # Get the layer-specific spacing rule + if self.get_zindex(shape.lpp) == 1: + spacing = self.vert_layer_spacing else: - if self.get_zindex(shape.lpp) == 1: - spacing = self.vert_layer_spacing - else: - spacing = self.horiz_layer_spacing + spacing = self.horiz_layer_spacing # If the shape is wider than the supply wire width, its spacing can be # different wide = min(shape.width(), shape.height()) if wide > self.layer_widths[0]: spacing = self.get_layer_space(self.get_zindex(shape.lpp), wide) + + # Shapes must keep their center lines away from any blockage to prevent + # the nodes from being unconnected + xdiff = self.track_wire - shape.width() + ydiff = self.track_wire - shape.height() + diff = snap(max(xdiff, ydiff) / 2) + if diff > 0: + spacing += diff + + # Add minimum unit to the spacing to keep nodes out of inflated regions + spacing += drc["grid"] + return shape.inflated_pin(spacing=spacing, extra_spacing=self.half_wire) diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 8e6479cb..d5b7c929 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -50,9 +50,10 @@ class signal_escape_router(router): # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: - self.blockages.append(self.inflate_shape(pin, is_pin=True)) + self.blockages.append(self.inflate_shape(pin)) # Route vdd and gnd + i = 0 for source, target, _ in self.get_route_pairs(pin_names): # Change fake pin's name so the graph will treat it as routable target.name = source.name diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index aa774731..d33b9ad6 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -65,7 +65,7 @@ class supply_router(router): # Add vdd and gnd pins as blockages as well # NOTE: This is done to make vdd and gnd pins DRC-safe for pin in self.all_pins: - self.blockages.append(self.inflate_shape(pin, is_pin=True)) + self.blockages.append(self.inflate_shape(pin)) # Route vdd and gnd for pin_name in [vdd_name, gnd_name]: @@ -239,7 +239,7 @@ class supply_router(router): # Save side pins for routing self.new_pins[pin_name] = new_pins for pin in new_pins: - self.blockages.append(self.inflate_shape(pin, is_pin=True)) + self.blockages.append(self.inflate_shape(pin)) def get_mst_pairs(self, pins): From 87eca6b7db45c5def3de9abd5677528b378a3d65 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 18:01:09 -0700 Subject: [PATCH 81/91] Use the initial bbox to route supply and signals --- compiler/modules/rom_bank.py | 13 ++++++--- compiler/modules/sram_1bank.py | 11 +++++--- compiler/router/router.py | 10 +++++-- compiler/router/signal_escape_router.py | 5 ++-- compiler/router/supply_router.py | 37 +++---------------------- 5 files changed, 30 insertions(+), 46 deletions(-) diff --git a/compiler/modules/rom_bank.py b/compiler/modules/rom_bank.py index aa790d6f..7cf30dc3 100644 --- a/compiler/modules/rom_bank.py +++ b/compiler/modules/rom_bank.py @@ -111,12 +111,15 @@ class rom_bank(design,rom_verilog): self.place_top_level_pins() self.route_output_buffers() - self.route_supplies() + # FIXME: Somehow ROM layout behaves weird and doesn't add all the pin + # shapes before routing supplies + init_bbox = self.get_bbox() + self.route_supplies(init_bbox) # Route the pins to the perimeter if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - self.route_escape_pins() + self.route_escape_pins(init_bbox) def setup_layout_constants(self): @@ -441,7 +444,7 @@ class rom_bank(design,rom_verilog): pin_num = msb - self.col_bits self.add_io_pin(self.decode_inst, "A{}".format(pin_num), name) - def route_supplies(self): + def route_supplies(self, bbox): for pin_name in ["vdd", "gnd"]: for inst in self.insts: @@ -454,6 +457,7 @@ class rom_bank(design,rom_verilog): from openram.router import supply_router as router rtr = router(layers=self.supply_stack, design=self, + bbox=bbox, pin_type=OPTS.supply_pin_type) rtr.route() @@ -479,7 +483,7 @@ class rom_bank(design,rom_verilog): pin.width(), pin.height()) - def route_escape_pins(self): + def route_escape_pins(self, bbox): pins_to_route = [] for bit in range(self.col_bits): @@ -495,5 +499,6 @@ class rom_bank(design,rom_verilog): pins_to_route.append("cs") from openram.router import signal_escape_router as router rtr = router(layers=self.m3_stack, + bbox=bbox, design=self) rtr.route(pins_to_route) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 95ea9b87..843d13d8 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -243,7 +243,7 @@ class sram_1bank(design, verilog, lef): def create_modules(self): debug.error("Must override pure virtual function.", -1) - def route_supplies(self): + def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ # Copy the pins to the top level @@ -259,6 +259,7 @@ class sram_1bank(design, verilog, lef): from openram.router import supply_router as router rtr = router(layers=self.supply_stack, design=self, + bbox=bbox, pin_type=OPTS.supply_pin_type) rtr.route() @@ -322,7 +323,7 @@ class sram_1bank(design, verilog, lef): # Grid is left with many top level pins pass - def route_escape_pins(self): + def route_escape_pins(self, bbox=None): """ Add the top-level pins for a single bank SRAM with control. """ @@ -367,6 +368,7 @@ class sram_1bank(design, verilog, lef): from openram.router import signal_escape_router as router rtr = router(layers=self.m3_stack, + bbox=bbox, design=self) rtr.route(pins_to_route) @@ -1072,14 +1074,15 @@ class sram_1bank(design, verilog, lef): # Some technologies have an isolation self.add_dnwell(inflate=2.5) + init_bbox = self.get_bbox() # Route the supplies together and/or to the ring/stripes. # Route the pins to the perimeter if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - self.route_escape_pins() + self.route_escape_pins(init_bbox) - self.route_supplies() + self.route_supplies(init_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/router/router.py b/compiler/router/router.py index 917c49d9..39c5cdb1 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -20,7 +20,7 @@ class router(router_tech): This is the base class for routers that use the Hanan grid graph method. """ - def __init__(self, layers, design): + def __init__(self, layers, design, bbox=None): # `router_tech` contains tech constants for the router router_tech.__init__(self, layers, route_track_width=1) @@ -32,7 +32,13 @@ class router(router_tech): # Temporary GDSII file name to find pins and blockages self.gds_filename = OPTS.openram_temp + "temp.gds" # Calculate the bounding box for routing around the perimeter - self.bbox = self.design.get_bbox(margin=11 * self.track_width) + # FIXME: We wouldn't do this if `rom_bank` wasn't behaving weird + if bbox is None: + self.bbox = self.design.get_bbox(margin=11 * self.track_width) + else: + ll, ur = bbox + margin = vector([11 * self.track_width] * 2) + self.bbox = [ll - margin, ur + margin] # Dictionary for vdd and gnd pins self.pins = {} # Set of all the pins diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index d5b7c929..945b4b40 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -16,10 +16,10 @@ class signal_escape_router(router): This is the signal escape router that uses the Hanan grid graph method. """ - def __init__(self, layers, design): + def __init__(self, layers, design, bbox=None): # `router_tech` contains tech constants for the router - router.__init__(self, layers, design) + router.__init__(self, layers, design, bbox) # New pins are the side supply pins self.new_pins = {} @@ -53,7 +53,6 @@ class signal_escape_router(router): self.blockages.append(self.inflate_shape(pin)) # Route vdd and gnd - i = 0 for source, target, _ in self.get_route_pairs(pin_names): # Change fake pin's name so the graph will treat it as routable target.name = source.name diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index d33b9ad6..7105afd0 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -16,10 +16,10 @@ class supply_router(router): This is the supply router that uses the Hanan grid graph method. """ - def __init__(self, layers, design, pin_type=None): + def __init__(self, layers, design, bbox=None, pin_type=None): # `router_tech` contains tech constants for the router - router.__init__(self, layers, design) + router.__init__(self, layers, design, bbox) # Side supply pin type # (can be "top", "bottom", "right", "left", and "ring") @@ -52,7 +52,6 @@ class supply_router(router): self.convert_blockages() # Add side pins - self.calculate_ring_bbox() if self.pin_type in ["top", "bottom", "right", "left"]: self.add_side_pin(vdd_name) self.add_side_pin(gnd_name) @@ -84,7 +83,7 @@ class supply_router(router): # larger routing region if path is None: rll, rur = region - bll, bur = self.ring_bbox + bll, bur = self.bbox # Stop scaling the region and throw an error if rll.x < bll.x and rll.y < bll.y and \ rur.x > bur.x and rur.y > bur.y: @@ -103,38 +102,10 @@ class supply_router(router): break - def calculate_ring_bbox(self, num_vias=3): - """ Calculate the ring-safe bounding box of the layout. """ - - ll, ur = self.bbox - # Calculate the "wideness" of a side supply pin - wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) - # Total wideness is used to find it any pin overlaps in this region. If - # so, the bbox is shifted to prevent this overlap. - total_wideness = wideness * 4 - for blockage in self.blockages: - bll, bur = blockage.rect - if self.get_zindex(blockage.lpp) == 1: # Vertical - diff = ll.x + total_wideness - bll.x - if diff > 0: - ll = vector(ll.x - diff, ll.y) - diff = ur.x - total_wideness - bur.x - if diff < 0: - ur = vector(ur.x - diff, ur.y) - else: # Horizontal - diff = ll.y + total_wideness - bll.y - if diff > 0: - ll = vector(ll.x, ll.y - diff) - diff = ur.y - total_wideness - bur.y - if diff < 0: - ur = vector(ur.x, ur.y - diff) - self.ring_bbox = [ll, ur] - - def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): """ Add supply pin to one side of the layout. """ - ll, ur = self.ring_bbox + ll, ur = self.bbox vertical = side in ["left", "right"] inner = pin_name == self.gnd_name From ba8e80d2053ead94925de7627589d9b8c8ecd9f7 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 19:33:48 -0700 Subject: [PATCH 82/91] Replace layout pins in the new signal escape router --- compiler/router/router.py | 14 +++++++++----- compiler/router/signal_escape_router.py | 17 ++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/compiler/router/router.py b/compiler/router/router.py index 39c5cdb1..1fc83bef 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -227,7 +227,8 @@ class router(router_tech): """ Add the route path to the layout. """ nodes = self.prepare_path(path) - self.add_route(nodes) + shapes = self.add_route(nodes) + return shapes def prepare_path(self, path): @@ -263,6 +264,7 @@ class router(router_tech): working for this router. """ + new_shapes = [] for i in range(0, len(nodes) - 1): start = nodes[i].center end = nodes[i + 1].center @@ -276,10 +278,12 @@ class router(router_tech): self.design.add_via_center(layers=self.layers, offset=offset) else: # Wire - self.design.add_rect(layer=self.get_layer(start.z), - offset=offset, - width=abs(diff.x) + self.track_wire, - height=abs(diff.y) + self.track_wire) + shape = self.design.add_rect(layer=self.get_layer(start.z), + offset=offset, + width=abs(diff.x) + self.track_wire, + height=abs(diff.y) + self.track_wire) + new_shapes.append(shape) + return new_shapes def write_debug_gds(self, gds_name, g=None, source=None, target=None): diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 945b4b40..ffe47981 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -79,12 +79,14 @@ class signal_escape_router(router): debug.info(0, "Retry routing in larger routing region with scale {}".format(scale)) continue # Create the path shapes on layout - self.add_path(path) + new_shapes = self.add_path(path) + self.new_pins[source.name] = new_shapes[0] # Find the recently added shapes self.prepare_gds_reader() self.find_blockages(name) self.find_vias() break + self.replace_layout_pins() def add_perimeter_fake_pins(self): @@ -152,3 +154,16 @@ class signal_escape_router(router): fake = self.get_closest_perimeter_fake_pin(pin) to_route.append((pin, fake, pin.distance(fake))) return sorted(to_route, key=lambda x: x[2]) + + + def replace_layout_pins(self): + """ """ + + for name, pin in self.new_pins.items(): + pin = graph_shape(pin.name, pin.boundary, pin.lpp) + # Find the intersection of this pin on the perimeter + for fake in self.fake_pins: + edge = pin.intersection(fake) + if edge: + break + self.design.replace_layout_pin(name, edge) From e1d0902680a1a19ba21f49d830866882842395c0 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 21:26:24 -0700 Subject: [PATCH 83/91] Cleanup the new router --- compiler/router/router_tech.py | 2 +- compiler/router/signal_escape_router.py | 6 +++--- compiler/router/supply_router.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index 92366e3c..af889a1e 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -138,7 +138,7 @@ class router_tech: min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) return min_width - def get_layer_space(self, zindex, width): + def get_layer_space(self, zindex, width=None): """ Return the minimum spacing of a layer given wire width. """ if width is None: width = self.get_layer_width(zindex) diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index ffe47981..08b76be2 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -18,7 +18,7 @@ class signal_escape_router(router): def __init__(self, layers, design, bbox=None): - # `router_tech` contains tech constants for the router + # `router` is the base router class router.__init__(self, layers, design, bbox) # New pins are the side supply pins @@ -146,7 +146,7 @@ class signal_escape_router(router): def get_route_pairs(self, pin_names): - """ """ + """ Return the pairs to be routed. """ to_route = [] for name in pin_names: @@ -157,7 +157,7 @@ class signal_escape_router(router): def replace_layout_pins(self): - """ """ + """ Replace the old layout pins with new ones around the perimeter. """ for name, pin in self.new_pins.items(): pin = graph_shape(pin.name, pin.boundary, pin.lpp) diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index 7105afd0..5aabf7fb 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -18,7 +18,7 @@ class supply_router(router): def __init__(self, layers, design, bbox=None, pin_type=None): - # `router_tech` contains tech constants for the router + # `router` is the base router class router.__init__(self, layers, design, bbox) # Side supply pin type From 54fc34392dccd953eefa15e9d6758cf2a62e6d1b Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 21:28:21 -0700 Subject: [PATCH 84/91] Remove unnecessary imports --- compiler/modules/rom_bank.py | 1 - compiler/modules/sram_1bank.py | 1 - compiler/router/__init__.py | 1 - 3 files changed, 3 deletions(-) diff --git a/compiler/modules/rom_bank.py b/compiler/modules/rom_bank.py index 7cf30dc3..74d0678b 100644 --- a/compiler/modules/rom_bank.py +++ b/compiler/modules/rom_bank.py @@ -14,7 +14,6 @@ from openram.base import rom_verilog from openram import OPTS, print_time from openram.sram_factory import factory from openram.tech import drc, layer, parameter -from openram.router import router_tech class rom_bank(design,rom_verilog): diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 843d13d8..c4f8dc28 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -14,7 +14,6 @@ from openram.base import channel_route from openram.base import design from openram.base import verilog from openram.base import lef -from openram.router import router_tech from openram.sram_factory import factory from openram.tech import spice from openram import OPTS, print_time diff --git a/compiler/router/__init__.py b/compiler/router/__init__.py index cb521a12..ac2a64ec 100644 --- a/compiler/router/__init__.py +++ b/compiler/router/__init__.py @@ -3,6 +3,5 @@ # Copyright (c) 2016-2023 Regents of the University of California, Santa Cruz # All rights reserved. # -from .router_tech import * from .signal_escape_router import * from .supply_router import * From 8fff4e2635e6fd187b61aab9e423232d0b982c6a Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 21:35:13 -0700 Subject: [PATCH 85/91] Organize imports of the new router --- compiler/router/graph_node.py | 1 - compiler/router/router.py | 2 +- compiler/router/signal_escape_router.py | 2 +- compiler/router/supply_router.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/compiler/router/graph_node.py b/compiler/router/graph_node.py index acb40d6f..c0aa2ed2 100644 --- a/compiler/router/graph_node.py +++ b/compiler/router/graph_node.py @@ -3,7 +3,6 @@ # 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 from openram.tech import drc diff --git a/compiler/router/router.py b/compiler/router/router.py index 1fc83bef..fe445ae1 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -10,9 +10,9 @@ from openram.tech import GDS from openram.tech import drc from openram.tech import layer as tech_layer from openram import OPTS -from .router_tech import router_tech from .graph_shape import graph_shape from .graph_utils import snap +from .router_tech import router_tech class router(router_tech): diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index 08b76be2..5e4ae66e 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -7,8 +7,8 @@ from openram import debug from openram.base.vector import vector from openram import OPTS from .graph import graph -from .router import router from .graph_shape import graph_shape +from .router import router class signal_escape_router(router): diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index 5aabf7fb..f6acd24c 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -7,8 +7,8 @@ from openram import debug from openram.base.vector import vector from openram import OPTS from .graph import graph -from .router import router from .graph_shape import graph_shape +from .router import router class supply_router(router): From f8b2c1e9b9aa490175ce39208027ed82017e756d Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 21:48:29 -0700 Subject: [PATCH 86/91] Change OPTS.route_supplies option since there's only one router now --- compiler/modules/rom_bank.py | 9 +++------ compiler/modules/sram_1bank.py | 12 ++++-------- compiler/options.py | 2 +- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/compiler/modules/rom_bank.py b/compiler/modules/rom_bank.py index 74d0678b..244cbe16 100644 --- a/compiler/modules/rom_bank.py +++ b/compiler/modules/rom_bank.py @@ -113,7 +113,8 @@ class rom_bank(design,rom_verilog): # FIXME: Somehow ROM layout behaves weird and doesn't add all the pin # shapes before routing supplies init_bbox = self.get_bbox() - self.route_supplies(init_bbox) + if OPTS.route_supplies: + self.route_supplies(init_bbox) # Route the pins to the perimeter if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will @@ -449,11 +450,7 @@ class rom_bank(design,rom_verilog): for inst in self.insts: self.copy_power_pins(inst, pin_name) - if not OPTS.route_supplies: - # Do not route the power supply (leave as must-connect pins) - return - else: - from openram.router import supply_router as router + from openram.router import supply_router as router rtr = router(layers=self.supply_stack, design=self, bbox=bbox, diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index c4f8dc28..48e6cf89 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -251,11 +251,7 @@ class sram_1bank(design, verilog, lef): for inst in self.insts: self.copy_power_pins(inst, pin_name, self.ext_supply[pin_name]) - if not OPTS.route_supplies: - # Do not route the power supply (leave as must-connect pins) - return - else: - from openram.router import supply_router as router + from openram.router import supply_router as router rtr = router(layers=self.supply_stack, design=self, bbox=bbox, @@ -284,7 +280,7 @@ class sram_1bank(design, verilog, lef): pin.width(), pin.height()) - elif OPTS.route_supplies and OPTS.supply_pin_type == "single": + elif OPTS.supply_pin_type == "single": # Update these as we may have routed outside the region (perimeter pins) lowest_coord = self.find_lowest_coords() @@ -1080,8 +1076,8 @@ class sram_1bank(design, verilog, lef): # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides self.route_escape_pins(init_bbox) - - self.route_supplies(init_bbox) + if OPTS.route_supplies: + self.route_supplies(init_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/options.py b/compiler/options.py index 13649caf..38a38f26 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -115,7 +115,7 @@ class options(optparse.Values): # When enabled, layout is not generated (and no DRC or LVS are performed) netlist_only = False # Whether we should do the final power routing - route_supplies = "graph" + route_supplies = True supply_pin_type = "ring" # This determines whether LVS and DRC is checked at all. check_lvsdrc = False From fa1b2fc96e0225c07a2a0367b8b9b8d983f8d6b3 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 2 Aug 2023 21:54:45 -0700 Subject: [PATCH 87/91] Delete unused file --- compiler/router/direction.py | 75 ------------------------------------ compiler/router/graph.py | 1 - 2 files changed, 76 deletions(-) delete mode 100644 compiler/router/direction.py diff --git a/compiler/router/direction.py b/compiler/router/direction.py deleted file mode 100644 index bcca1e94..00000000 --- a/compiler/router/direction.py +++ /dev/null @@ -1,75 +0,0 @@ -# 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. -# -from enum import Enum -from openram import debug -from openram.base.vector3d import vector3d - - -class direction(Enum): - NORTH = 1 - SOUTH = 2 - EAST = 3 - WEST = 4 - UP = 5 - DOWN = 6 - NORTHEAST = 7 - NORTHWEST = 8 - SOUTHEAST = 9 - SOUTHWEST = 10 - - def get_offset(direct): - """ - Returns the vector offset for a given direction. - """ - if direct==direction.NORTH: - offset = vector3d(0, 1, 0) - elif direct==direction.SOUTH: - offset = vector3d(0, -1 ,0) - elif direct==direction.EAST: - offset = vector3d(1, 0, 0) - elif direct==direction.WEST: - offset = vector3d(-1, 0, 0) - elif direct==direction.UP: - offset = vector3d(0, 0, 1) - elif direct==direction.DOWN: - offset = vector3d(0, 0, -1) - elif direct==direction.NORTHEAST: - offset = vector3d(1, 1, 0) - elif direct==direction.NORTHWEST: - offset = vector3d(-1, 1, 0) - elif direct==direction.SOUTHEAST: - offset = vector3d(1, -1, 0) - elif direct==direction.SOUTHWEST: - offset = vector3d(-1, -1, 0) - else: - debug.error("Invalid direction {}".format(direct)) - - return offset - - def cardinal_directions(up_down_too=False): - temp_dirs = [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST] - if up_down_too: - temp_dirs.extend([direction.UP, direction.DOWN]) - return temp_dirs - - def cardinal_offsets(up_down_too=False): - return [direction.get_offset(d) for d in direction.cardinal_directions(up_down_too)] - - def all_directions(): - return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST, - direction.NORTHEAST, direction.NORTHWEST, direction.SOUTHEAST, direction.SOUTHWEST] - - def all_offsets(): - return [direction.get_offset(d) for d in direction.all_directions()] - - def all_neighbors(cell): - return [cell + x for x in direction.all_offsets()] - - def cardinal_neighbors(cell): - return [cell + x for x in direction.cardinal_offsets()] - diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 1d250b8b..1244a7d8 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -9,7 +9,6 @@ from openram import debug from openram.base.vector import vector from openram.base.vector3d import vector3d from openram.tech import drc -from .direction import direction from .graph_node import graph_node from .graph_probe import graph_probe from .graph_utils import snap From ad05fde099167cd0f256d01d5f945fc69f92c194 Mon Sep 17 00:00:00 2001 From: vlsida-bot Date: Fri, 4 Aug 2023 01:45:27 +0000 Subject: [PATCH 88/91] Bump version: 1.2.26 -> 1.2.27 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index cba64f43..3b781da9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.26 +1.2.27 From 9ac82060b9db4eb74fd71f360f7c255bd090243c Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Tue, 8 Aug 2023 11:54:12 -0700 Subject: [PATCH 89/91] Simplify 'remove' attribute of graph_node --- compiler/router/graph.py | 12 ++++++------ compiler/router/graph_node.py | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 1244a7d8..470dfe7d 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -301,12 +301,12 @@ class graph: def search(index, condition, shift): """ Search and connect neighbor nodes. """ base_nodes = self.nodes[index:index+2] - found = [hasattr(base_nodes[0], "remove"), - hasattr(base_nodes[1], "remove")] + found = [base_nodes[0].remove, + base_nodes[1].remove] while condition(index) and not all(found): nodes = self.nodes[index - shift:index - shift + 2] for k in range(2): - if not found[k] and not hasattr(nodes[k], "remove"): + if not found[k] and not nodes[k].remove: found[k] = True if not self.is_probe_blocked(base_nodes[k].center, nodes[k].center): base_nodes[k].add_neighbor(nodes[k]) @@ -315,8 +315,8 @@ class graph: for i in range(0, len(self.nodes), 2): search(i, lambda count: (count / 2) % y_len, 2) # Down search(i, lambda count: (count / 2) >= y_len, y_len * 2) # Left - if not hasattr(self.nodes[i], "remove") and \ - not hasattr(self.nodes[i + 1], "remove") and \ + if not self.nodes[i].remove and \ + not self.nodes[i + 1].remove and \ not self.is_via_blocked(self.nodes[i:i+2]): self.nodes[i].add_neighbor(self.nodes[i + 1]) @@ -338,7 +338,7 @@ class graph: for i in range(len(self.nodes) - 1, -1, -1): node = self.nodes[i] - if hasattr(node, "remove"): + if node.remove: node.remove_all_neighbors() self.nodes.remove(node) diff --git a/compiler/router/graph_node.py b/compiler/router/graph_node.py index c0aa2ed2..d849673b 100644 --- a/compiler/router/graph_node.py +++ b/compiler/router/graph_node.py @@ -22,6 +22,7 @@ class graph_node: else: self.center = vector3d(center) self.neighbors = [] + self.remove = False def add_neighbor(self, other): From ea02aae40fc135ab9becc91a967ee1606bfe0d2b Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Wed, 9 Aug 2023 17:45:49 -0700 Subject: [PATCH 90/91] Find blocked nodes and probes faster --- compiler/router/graph.py | 115 +++++++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 470dfe7d..132681c9 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -78,16 +78,23 @@ 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: - # Check if two shapes overlap - if blockage.overlaps(probe_shape): - # Probe is blocked if the shape isn't routable - if not self.is_routable(blockage): - return True - blockage = blockage.get_core() - if blockage.overlaps(probe_shape): - continue + 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 + # Probe is blocked if the shape isn't routable + if not self.is_routable(blockage): + return True + blockage = blockage.get_core() + 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: return True return False @@ -95,6 +102,11 @@ class graph: def is_node_blocked(self, node, pin_safe=True): """ Return if a node is blocked by a blockage. """ + p = node.center + x = p.x + y = p.y + z = p.z + def closest(value, checklist): """ Return the distance of the closest value in the checklist. """ diffs = [abs(value - other) for other in checklist] @@ -105,41 +117,47 @@ class graph: spacing = snap(self.router.track_space + half_wide + drc["grid"]) blocked = False for blockage in self.graph_blockages: - # Check if the node is inside the blockage - if self.inside_shape(node.center, blockage): - if not self.is_routable(blockage): - blocked = True - continue - blockage = blockage.get_core() - # Check if the node is inside the blockage's core - if self.inside_shape(node.center, blockage): - p = node.center - # Check if the node is too close to one edge of the shape - lengths = [blockage.width(), blockage.height()] - centers = blockage.center() - ll, ur = blockage.rect - safe = [True, True] - for i in range(2): - if lengths[i] >= wide: - min_diff = closest(p[i], [ll[i], ur[i]]) - if min_diff < half_wide: - safe[i] = False - elif centers[i] != p[i]: - safe[i] = False - if not all(safe): - blocked = True - continue - # Check if the node is in a safe region of the shape - xs, ys = self.get_safe_pin_values(blockage) - xdiff = closest(p.x, xs) - ydiff = closest(p.y, ys) - if xdiff == 0 and ydiff == 0: - if pin_safe and blockage in [self.source, self.target]: - return False - elif xdiff < spacing and ydiff < spacing: - blocked = True - else: - blocked = True + 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 + # Blocked if not routable + if not self.is_routable(blockage): + blocked = True + continue + blockage = blockage.get_core() + ll, ur = blockage.rect + # Not overlapping + if ll.x > x or x > ur.x or ll.y > y or y > ur.y: + blocked = True + continue + # Check if the node is too close to one edge of the shape + lengths = [blockage.width(), blockage.height()] + centers = blockage.center() + ll, ur = blockage.rect + safe = [True, True] + for i in range(2): + if lengths[i] >= wide: + min_diff = closest(p[i], [ll[i], ur[i]]) + if min_diff < half_wide: + safe[i] = False + elif centers[i] != p[i]: + safe[i] = False + if not all(safe): + blocked = True + continue + # Check if the node is in a safe region of the shape + xs, ys = self.get_safe_pin_values(blockage) + xdiff = closest(p.x, xs) + ydiff = closest(p.y, ys) + if xdiff == 0 and ydiff == 0: + if pin_safe and blockage in [self.source, self.target]: + return False + elif xdiff < spacing and ydiff < spacing: + blocked = True return blocked @@ -151,12 +169,17 @@ class graph: if self.is_node_blocked(node, pin_safe=False): return True # If the nodes are blocked by a via - point = node.center + x = node.center.x + y = node.center.y + z = node.center.z for via in self.graph_vias: ll, ur = via.rect + # Not overlapping + if ll.x > x or x > ur.x or ll.y > y or y > ur.y: + continue center = via.center() - if via.on_segment(ll, point, ur) and \ - (center.x != point.x or center.y != point.y): + # If not in the center + if center.x != x or center.y != y: return True return False From cc8161afc15b72ae224d0f403e1a2831a0a5a974 Mon Sep 17 00:00:00 2001 From: vlsida-bot Date: Thu, 10 Aug 2023 05:09:47 +0000 Subject: [PATCH 91/91] Bump version: 1.2.27 -> 1.2.28 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3b781da9..f6b839d3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.27 +1.2.28