From db2a276077a072b485caee53b9bf404cf0dc6068 Mon Sep 17 00:00:00 2001 From: Eren Dogan Date: Mon, 31 Jul 2023 19:43:09 -0700 Subject: [PATCH] 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]