From f48b0b8f41bd48e0e7f9d2b1aead603e46f0c13a Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 5 May 2021 13:45:12 -0700 Subject: [PATCH] Add left stripe power routes to tree router as option. --- compiler/router/grid.py | 48 +++++---- compiler/router/pin_group.py | 4 + compiler/router/router.py | 78 ++++++++------ compiler/router/router_tech.py | 2 +- compiler/router/signal_escape_router.py | 3 +- compiler/router/signal_router.py | 4 +- compiler/router/supply_grid_router.py | 13 +-- compiler/router/supply_tree_router.py | 28 +++-- compiler/sram/sram_base.py | 134 +++++++++++++----------- 9 files changed, 185 insertions(+), 129 deletions(-) diff --git a/compiler/router/grid.py b/compiler/router/grid.py index 793e5e89..ea59c80f 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -122,35 +122,45 @@ class grid: self.set_target(n) # self.set_blocked(n, False) - def add_perimeter_target(self, side="all"): - debug.info(3, "Adding perimeter target") - + 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 perimter "stripe" should be. + """ perimeter_list = [] # Add the left/right columns if side=="all" or side=="left": - x = self.ll.x - for y in range(self.ll.y, self.ur.y, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for x in range(self.ll.x + offset, self.ll.x + width + offset, 1): + for y in range(self.ll.y + margin, self.ur.y - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) if side=="all" or side=="right": - x = self.ur.x - for y in range(self.ll.y, self.ur.y, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for x in range(self.ur.x - width - offset, self.ur.x - offset, 1): + for y in range(self.ll.y + margin, self.ur.y - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) if side=="all" or side=="bottom": - y = self.ll.y - for x in range(self.ll.x, self.ur.x, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for y in range(self.ll.y + offset, self.ll.y + width + offset, 1): + for x in range(self.ll.x + margin, self.ur.x - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) if side=="all" or side=="top": - y = self.ur.y - for x in range(self.ll.x, self.ur.x, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for y in range(self.ur.y - width - offset, self.ur.y - offset, 1): + for x in range(self.ll.x + margin, self.ur.x - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) + 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): diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 7a5d8817..5e6d6f89 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -155,6 +155,10 @@ class pin_group: # Now simplify the enclosure list new_pin_list = self.remove_redundant_shapes(pin_list) + # Now add the right name + for pin in new_pin_list: + pin.name = self.name + debug.check(len(new_pin_list) > 0, "Did not find any enclosures.") diff --git a/compiler/router/router.py b/compiler/router/router.py index 8fe18765..ca077572 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -28,7 +28,7 @@ class router(router_tech): route on a given layer. This is limited to two layer routes. It populates blockages on a grid class. """ - def __init__(self, layers, design, gds_filename=None, bbox=None, margin=0, route_track_width=1): + 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 @@ -39,19 +39,7 @@ class router(router_tech): self.cell = design - # If didn't specify a gds blockage file, write it out to read the gds - # This isn't efficient, but easy for now - # start_time = datetime.now() - if not gds_filename: - gds_filename = OPTS.openram_temp+"temp.gds" - self.cell.gds_write(gds_filename) - - # Load the gds file and read in all the shapes - self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) - self.reader = gdsMill.Gds2reader(self.layout) - self.reader.loadFromFile(gds_filename) - self.top_name = self.layout.rootStructureName - # print_time("GDS read",datetime.now(), start_time) + self.gds_filename = OPTS.openram_temp + "temp.gds" # The pin data structures # A map of pin names to a set of pin_layout structures @@ -91,6 +79,16 @@ class router(router_tech): """ Initialize the ll,ur values with the paramter or using the layout boundary. """ + + # 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.layout = gdsMill.VlsiLayout(units=GDS["unit"]) + self.reader = gdsMill.Gds2reader(self.layout) + self.reader.loadFromFile(self.gds_filename) + self.top_name = self.layout.rootStructureName + if not bbox: # The boundary will determine the limits to the size # of the routing grid @@ -178,6 +176,17 @@ class router(router_tech): """ 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.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 @@ -881,12 +890,32 @@ class router(router_tech): # 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=2): + """ + Adds a supply pin to the perimeter and resizes the bounding box. + """ + pg = pin_group(name, [], self) + if name == "vdd": + offset = width + else: + offset = 0 + + pg.grids = set(self.rg.get_perimeter_list(side=side, + width=width, + margin=self.margin, + offset=offset, + layers=[1])) + pg.enclosures = pg.compute_enclosures() + pg.pins = set(pg.enclosures) + self.cell.pin_map[name].update(pg.pins) + self.pin_groups[name].append(pg) + def add_perimeter_target(self, side="all"): """ This will mark all the cells on the perimeter of the original layout as a target. """ self.rg.add_perimeter_target(side=side) - + def num_pin_components(self, pin_name): """ This returns how many disconnected pin components there are. @@ -1219,7 +1248,7 @@ class router(router_tech): """ Return the lowest, leftest pin group """ keep_pin = None - for index,pg in enumerate(self.pin_groups[pin_name]): + for index, pg in enumerate(self.pin_groups[pin_name]): for pin in pg.enclosures: if not keep_pin: keep_pin = pin @@ -1229,23 +1258,6 @@ class router(router_tech): return keep_pin - def get_left_pins(self, pin_name): - """ Return the leftest pin(s) group """ - - keep_pins = [] - for index, pg in enumerate(self.pin_groups[pin_name]): - for pin in pg.enclosures: - if not keep_pins: - keep_pins = [pin] - else: - # Only need to check first since they are all the same left value - if pin.lx() == keep_pins[0].lx(): - keep_pins.append(pin) - elif pin.lx() < keep_pins[0].lx(): - keep_pins = [pin] - - return keep_pins - def check_all_routed(self, pin_name): """ Check that all pin groups are routed. diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index b3e26f79..6cbbd422 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -123,7 +123,7 @@ class router_tech: min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) - min_width = drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, 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) return (min_width, min_spacing) diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index a9531290..7cc41d97 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -17,7 +17,7 @@ class signal_escape_router(router): A router that routes signals to perimeter and makes pins. """ - def __init__(self, layers, design, bbox=None, margin=0, gds_filename=None): + 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). @@ -25,7 +25,6 @@ class signal_escape_router(router): router.__init__(self, layers=layers, design=design, - gds_filename=gds_filename, bbox=bbox, margin=margin) diff --git a/compiler/router/signal_router.py b/compiler/router/signal_router.py index 89edcb01..9fbf871f 100644 --- a/compiler/router/signal_router.py +++ b/compiler/router/signal_router.py @@ -15,12 +15,12 @@ class signal_router(router): route on a given layer. This is limited to two layer routes. """ - def __init__(self, layers, design, gds_filename=None, bbox=None): + def __init__(self, layers, design, bbox=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). """ - router.__init__(self, layers, design, gds_filename, bbox) + router.__init__(self, layers, design, bbox) def route(self, src, dest, detour_scale=5): """ diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index 8a201474..f24498ab 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -21,7 +21,7 @@ class supply_grid_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, gds_filename=None, bbox=None): + def __init__(self, layers, design, margin=0, bbox=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). @@ -29,9 +29,9 @@ class supply_grid_router(router): start_time = datetime.now() # Power rail width in minimum wire widths - self.route_track_width = 2 + self.route_track_width = 1 - router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) + 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 = {} @@ -357,8 +357,9 @@ class supply_grid_router(router): # This is inefficient since it is non-incremental, but it was # easier to debug. - self.prepare_blockages(pin_name) - + 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) @@ -369,7 +370,7 @@ class supply_grid_router(router): # Actually run the A* router if not self.run_router(detour_scale=5): - self.write_debug_gds("debug_route.gds", False) + self.write_debug_gds("debug_route.gds") # if index==3 and pin_name=="vdd": # self.write_debug_gds("route.gds",False) diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index ba54ee39..a9f947ad 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -21,7 +21,7 @@ class supply_tree_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, gds_filename=None, bbox=None): + def __init__(self, layers, design, bbox=None, side_pin=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). @@ -31,11 +31,19 @@ class supply_tree_router(router): # for prettier routes. self.route_track_width = 1 - router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) + # The pin escape router already made the bounding box big enough, + # so we can use the regular bbox here. + self.side_pin = side_pin + 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) + 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 @@ -50,11 +58,17 @@ class supply_tree_router(router): # but this is simplest for now. self.create_routing_grid(signal_grid) - # Get the pin shapes 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.side_pin: + self.add_side_supply_pin(self.vdd_name) + self.add_side_supply_pin(self.gnd_name) + # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter start_time = datetime.now() @@ -87,15 +101,15 @@ class supply_tree_router(router): 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]): + 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 MinimumSpanning Tree") + debug.info(2, "Finding Minimum Spanning Tree") X = csr_matrix(adj_matrix) Tcsr = minimum_spanning_tree(X) mst = Tcsr.toarray().astype(int) diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index ce3f983c..1ea9d96b 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -225,6 +225,38 @@ class sram_base(design, verilog, lef): for pin_name in ["vdd", "gnd"]: for inst in self.insts: self.copy_power_pins(inst, pin_name) + + try: + from tech import power_grid + grid_stack = power_grid + except ImportError: + # if no power_grid is specified by tech we use sensible defaults + # Route a M3/M4 grid + grid_stack = self.m3_stack + + # lowest_coord = self.find_lowest_coords() + # highest_coord = self.find_highest_coords() + + # # Add two rails to the side + # if OPTS.route_supplies == "side": + # supply_pins = {} + # # Find the lowest leftest pin for vdd and gnd + # for (pin_name, pin_index) in [("vdd", 0), ("gnd", 1)]: + # pin_width = 8 * getattr(self, "{}_width".format(grid_stack[2])) + # pin_space = 2 * getattr(self, "{}_space".format(grid_stack[2])) + # supply_pitch = pin_width + pin_space + + # # Add side power rails on left from bottom to top + # # These have a temporary name and will be connected later. + # # They are here to reserve space now and ensure other pins go beyond + # # their perimeter. + # supply_height = highest_coord.y - lowest_coord.y + + # supply_pins[pin_name] = self.add_layout_pin(text=pin_name, + # layer=grid_stack[2], + # offset=lowest_coord + vector(pin_index * supply_pitch, 0), + # width=pin_width, + # height=supply_height) if not OPTS.route_supplies: # Do not route the power supply (leave as must-connect pins) @@ -233,71 +265,52 @@ class sram_base(design, verilog, lef): from supply_grid_router import supply_grid_router as router else: from supply_tree_router import supply_tree_router as router - - try: - from tech import power_grid - grid_stack = power_grid - except ImportError: - # if no power_grid is specified by tech we use sensible defaults - # Route a M3/M4 grid - grid_stack = self.m3_stack - rtr=router(grid_stack, self) + rtr=router(grid_stack, self, side_pin=(OPTS.route_supplies == "side")) rtr.route() - lowest_coord = self.find_lowest_coords() - highest_coord = self.find_highest_coords() - - # Find the lowest leftest pin for vdd and gnd - for (pin_name, pin_index) in [("vdd", 0), ("gnd", 1)]: - # Copy the pin shape(s) to rectangles - for pin in self.get_pins(pin_name): - self.add_rect(pin.layer, - pin.ll(), - pin.width(), - pin.height()) - - # Remove the pin shape(s) - self.remove_layout_pin(pin_name) - - # Either add two long rails or a simple pin - if OPTS.route_supplies == "side": - # Get the leftest pins - pins = rtr.get_left_pins(pin_name) - - pin_width = 2 * getattr(self, "{}_width".format(pins[0].layer)) - pin_space = 2 * getattr(self, "{}_space".format(pins[0].layer)) - supply_pitch = pin_width + pin_space - - # Add side power rails on left from bottom to top - # These have a temporary name and will be connected later. - # They are here to reserve space now and ensure other pins go beyond - # their perimeter. - supply_height = highest_coord.y - lowest_coord.y - supply_pin = self.add_layout_pin(text=pin_name, - layer="m4", - offset=lowest_coord + vector(pin_index * supply_pitch, 0), - width=pin_width, - height=supply_height) - - route_width = pins[0].rx() - lowest_coord.x - for pin in pins: - pin_offset = vector(lowest_coord.x, pin.by()) + if OPTS.route_supplies == "side": + # Find the lowest leftest pin for vdd and gnd + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.get_pins(pin_name): self.add_rect(pin.layer, - pin_offset, - route_width, + pin.ll(), + pin.width(), pin.height()) - center_offset = vector(supply_pin.cx(), - pin.cy()) - self.add_via_center(layers=self.m3_stack, - offset=center_offset) - else: + + # Remove the pin shape(s) + self.remove_layout_pin(pin_name) + # Get the lowest, leftest pin - pin = rtr.get_ll_pins(pin_name) - - pin_width = 2 * getattr(self, "{}_width".format(pin.layer)) - pin_space = 2 * getattr(self, "{}_space".format(pin.layer)) + pin = rtr.get_ll_pin(pin_name) + self.add_layout_pin(pin_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + elif OPTS.route_supplies == "tree": + # Update these as we may have routed outside the region (perimeter pins) + lowest_coord = self.find_lowest_coords() + + # Find the lowest leftest pin for vdd and gnd + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.get_pins(pin_name): + self.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.remove_layout_pin(pin_name) + + # Get the lowest, leftest pin + pin = rtr.get_ll_pin(pin_name) + + pin_width = 2 * getattr(self, "{}_width".format(pin.layer)) + # Add it as an IO pin to the perimeter route_width = pin.rx() - lowest_coord.x pin_offset = vector(lowest_coord.x, pin.by()) @@ -311,6 +324,9 @@ class sram_base(design, verilog, lef): pin_offset, pin_width, pin.height()) + else: + # Grid is left with many top level pins + pass def route_escape_pins(self): """ @@ -355,7 +371,7 @@ class sram_base(design, verilog, lef): from signal_escape_router import signal_escape_router as router rtr=router(layers=self.m3_stack, design=self, - margin=4 * self.m3_pitch) + margin=8 * self.m3_pitch) rtr.escape_route(pins_to_route) def compute_bus_sizes(self):