diff --git a/Makefile b/Makefile index 12c9eaa7..42e84f41 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ SRAM_LIB_GIT_REPO ?= https://github.com/vlsida/sky130_fd_bd_sram.git # Use this for development #SRAM_LIB_GIT_REPO ?= git@github.com:VLSIDA/sky130_fd_bd_sram.git #SRAM_LIB_GIT_REPO ?= https://github.com/google/skywater-pdk-libs-sky130_fd_bd_sram.git + SRAM_LIB_GIT_COMMIT ?= baa2b14282ee6c8498a9e480c88a5096fdce2b06 # Open PDKs diff --git a/VERSION b/VERSION index 550c9e96..37258517 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.29 +1.2.32 diff --git a/compiler/router/graph.py b/compiler/router/graph.py index 132681c9..dc9f5c40 100644 --- a/compiler/router/graph.py +++ b/compiler/router/graph.py @@ -184,7 +184,7 @@ class graph: return False - def create_graph(self, source, target, scale=1): + 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)) @@ -195,7 +195,6 @@ 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)) @@ -221,9 +220,6 @@ 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. """ @@ -422,6 +418,7 @@ class graph: path.append(current) current = came_from[current.id] path.append(current) + path.reverse() return path # Get the previous node to better calculate the next costs diff --git a/compiler/router/graph_shape.py b/compiler/router/graph_shape.py index 1221dabe..0f950ae4 100644 --- a/compiler/router/graph_shape.py +++ b/compiler/router/graph_shape.py @@ -72,17 +72,6 @@ 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 core_contained_by_any(self, shape_list): """ Return if the core of this shape is contained by any shape's core in the diff --git a/compiler/router/router.py b/compiler/router/router.py index fe445ae1..c498a815 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -59,6 +59,8 @@ class router(router_tech): def prepare_gds_reader(self): """ Write the current layout to a temporary file to read the layout. """ + # NOTE: Avoid using this function if possible since it is too slow to + # write/read these files self.design.gds_write(self.gds_filename) self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) self.reader = gdsMill.Gds2reader(self.layout) @@ -109,16 +111,26 @@ class router(router_tech): self.all_pins.update(pin_set) - def find_blockages(self, name="blockage"): + def find_blockages(self, name="blockage", shape_list=None): """ 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) + # If the list of shapes is given, don't get them from gdsMill + if shape_list is None: + shapes = self.layout.getAllShapes(lpp) + else: + shapes = shape_list 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]) + if shape_list is not None: + if boundary.lpp != lpp: + continue + ll = boundary.ll() + ur = boundary.ur() + else: + # 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) @@ -132,20 +144,28 @@ class router(router_tech): self.blockages.append(new_shape) - def find_vias(self): + def find_vias(self, shape_list=None): """ 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 + valid_lpp = self.horiz_lpp # Just a temporary lpp to prevent errors - shapes = self.layout.getAllShapes(via_lpp) + # If the list of shapes is given, don't get them from gdsMill + if shape_list is None: + shapes = self.layout.getAllShapes(via_lpp) + else: + shapes = shape_list 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]) + if shape_list is not None: + ll = boundary.ll() + ur = boundary.ur() + else: + # 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 @@ -264,7 +284,8 @@ class router(router_tech): working for this router. """ - new_shapes = [] + new_wires = [] + new_vias = [] for i in range(0, len(nodes) - 1): start = nodes[i].center end = nodes[i + 1].center @@ -275,15 +296,16 @@ class router(router_tech): 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) + via = self.design.add_via_center(layers=self.layers, + offset=offset) + new_vias.append(via) else: # 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 + 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) + new_wires.append(wire) + return new_wires, new_vias 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 5e4ae66e..21cae924 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -5,6 +5,7 @@ # from openram import debug from openram.base.vector import vector +from openram.base.vector3d import vector3d from openram import OPTS from .graph import graph from .graph_shape import graph_shape @@ -56,42 +57,82 @@ class signal_escape_router(router): 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 - 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 - 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 + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + 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) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + self.new_pins[source.name] = new_wires[-1] + # Find the recently added shapes + self.find_blockages(name, new_wires) + self.find_vias(new_vias) self.replace_layout_pins() + def get_closest_edge(self, point): + """ Return a point's the closest edge and the edge's axis direction. """ + + ll, ur = self.bbox + + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + return "left", True + if min_diff == ll_diff_y: + return "bottom", False + if min_diff == ur_diff_x: + return "right", True + return "top", False + + + def prepare_path(self, path): + """ + Override the `prepare_path` method from the `router` class to prevent + overflows from the SRAM layout area. + """ + + ll, ur = self.bbox + nodes = super().prepare_path(path) + new_nodes = [] + for i in range(len(nodes)): + node = nodes[i] + c = node.center + # Haven't overflown yet + if ll.x < c.x and c.x < ur.x and ll.y < c.y and c.y < ur.y: + new_nodes.append(node) + continue + # Snap the pin to the perimeter and break the iteration + edge, _ = self.get_closest_edge(c) + if edge == "left": + fake_center = vector3d(ll.x + self.half_wire, c.y, c.z) + if edge == "bottom": + fake_center = vector3d(c.x, ll.y + self.half_wire, c.z) + if edge == "right": + fake_center = vector3d(ur.x - self.half_wire, c.y, c.z) + if edge == "top": + fake_center = vector3d(c.x, ur.y - self.half_wire, c.z) + node.center = fake_center + new_nodes.append(node) + break + return new_nodes + + def add_perimeter_fake_pins(self): """ Add the fake pins on the perimeter to where the signals will be routed. + These perimeter fake pins are only used to replace layout pins at the + end of routing. """ ll, ur = self.bbox @@ -132,17 +173,36 @@ class signal_escape_router(router): self.fake_pins.append(pin) - def get_closest_perimeter_fake_pin(self, pin): - """ Return the closest fake pin for the given pin. """ + def create_fake_pin(self, pin): + """ Create a fake pin on the perimeter orthogonal to 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 + ll, ur = self.bbox + c = pin.center() + + # Find the closest edge + edge, vertical = self.get_closest_edge(c) + + # Keep the fake pin out of the SRAM layout are so that they won't be + # blocked by previous signals if they're on the same orthogonal line + if edge == "left": + fake_center = vector(ll.x - self.track_wire * 2, c.y) + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2) + if edge == "right": + fake_center = vector(ur.x + self.track_wire * 2, c.y) + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2) + + # Create the fake pin shape + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = fake_center - half_wire_vector + nur = fake_center + half_wire_vector + rect = [nll, nur] + pin = graph_shape(name="fake", + rect=rect, + layer_name_pp=layer) + return pin def get_route_pairs(self, pin_names): @@ -151,7 +211,7 @@ class signal_escape_router(router): to_route = [] for name in pin_names: pin = next(iter(self.pins[name])) - fake = self.get_closest_perimeter_fake_pin(pin) + fake = self.create_fake_pin(pin) to_route.append((pin, fake, pin.distance(fake))) return sorted(to_route, key=lambda x: x[2]) diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index f6acd24c..3cdcc4e6 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -71,35 +71,20 @@ class supply_router(router): 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.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 + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + 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) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + # Find the recently added shapes + self.find_blockages(pin_name, new_wires) + self.find_vias(new_vias) def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4):