From 0544d02ca25e8ea9ed928931bb486638e6a1ecf8 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Thu, 25 Oct 2018 13:36:35 -0700 Subject: [PATCH] Refactor router to have pin_groups for pins and router_tech file --- compiler/router/pin_group.py | 238 +++++++++++++++++++++ compiler/router/router.py | 354 +++---------------------------- compiler/router/router_tech.py | 78 +++++++ compiler/router/supply_router.py | 65 +++--- 4 files changed, 378 insertions(+), 357 deletions(-) create mode 100644 compiler/router/pin_group.py create mode 100644 compiler/router/router_tech.py diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py new file mode 100644 index 00000000..b1599a8e --- /dev/null +++ b/compiler/router/pin_group.py @@ -0,0 +1,238 @@ +from vector3d import vector3d +from tech import drc +import debug + +class pin_group: + """ + A class to represent a group of touching rectangular design pin. + It requires a router to define the track widths and blockages which + determine how pin shapes get mapped to tracks. + """ + def __init__(self, name, pin_shapes, router): + self.name = name + # Flag for when it is routed + self.routed = False + self.shapes = pin_shapes + self.router = router + # These are the corresponding pin grids for each pin group. + self.grids = set() + # The corresponding set of partially blocked grids for each pin group. + # These are blockages for other nets but unblocked for routing this group. + self.blockages = set() + + def set_routed(self, value=True): + self.routed = value + + def is_routed(self): + return self.routed + + def remove_redundant_shapes(self, pin_list): + """ + Remove any pin layout that is contained within another. + """ + local_debug = False + if local_debug: + debug.info(0,"INITIAL:",pin_list) + + # Make a copy of the list to start + new_pin_list = pin_list.copy() + + # This is n^2, but the number is small + for pin1 in pin_list: + for pin2 in pin_list: + # Can't contain yourself + if pin1 == pin2: + continue + if pin2.contains(pin1): + # It may have already been removed by being enclosed in another pin + if pin1 in new_pin_list: + new_pin_list.remove(pin1) + + if local_debug: + debug.info(0,"FINAL :",new_pin_list) + return new_pin_list + + # FIXME: This relies on some technology parameters from router which is not clearn. + def compute_enclosures(self): + """ + Find the minimum rectangle enclosures of the given tracks. + """ + # Enumerate every possible enclosure + pin_list = [] + for seed in self.grids: + (ll, ur) = self.enclose_pin_grids(seed) + enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z) + pin_list.append(enclosure) + + #return pin_list + # We used to do this, but smaller enclosures can be + return self.remove_redundant_shapes(pin_list) + + def compute_enclosure(self, pin, enclosure): + """ + Compute an enclosure 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) + p = pin_layout(pin.name, [ll, ur], pin.layer) + 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) + p = pin_layout(pin.name, [ll, ur], pin.layer) + 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) + p = pin_layout(pin.name, [ll, ur], pin.layer) + + return p + + def find_smallest_connector(self, enclosure_list): + """ + Compute all of the connectors between non-overlapping pins and enclosures. + Return the smallest. + """ + smallest = None + for pin in self.shapes: + for enclosure in enclosure_list: + new_enclosure = self.compute_enclosure(pin, enclosure) + if smallest == None or new_enclosure.area() min_width: + if smallest_shape == None or other.area() biggest.area(): + biggest = pin + + return pin + + def enclose_pin_grids(self, seed): + """ + This encloses a single pin component with a rectangle + starting with the seed and expanding right until blocked + and then up until blocked. + """ + + # We may have started with an empty set + if not self.grids: + return None + + # Start with the seed + ll = seed + + # Start with the ll and make the widest row + row = [ll] + # Move right while we can + while True: + right = row[-1] + vector3d(1,0,0) + # Can't move if not in the pin shape + if right in self.grids and right not in self.router.blocked_grids: + row.append(right) + else: + break + # Move up while we can + while True: + next_row = [x+vector3d(0,1,0) 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 self.router.blocked_grids: + 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): + """ + 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. + """ + # Compute the enclosure pin_layout list of the set of tracks + enclosure_list = self.compute_enclosures() + self.enclosure = self.find_smallest_overlapping(enclosure_list) + if not self.enclosure: + self.enclosure = self.find_smallest_connector(enclosure_list) + debug.info(2,"Computed enclosure {0} {1}".format(self.name, self.enclosure)) + + def add_enclosure(self, cell): + """ + Add the enclosure shape to the given cell. + """ + debug.info(2,"Adding enclosure {0} {1}".format(self.name, self.enclosure)) + self.router.cell.add_rect(layer=self.enclosure.layer, + offset=self.enclosure.ll(), + width=self.enclosure.width(), + height=self.enclosure.height()) + + + + diff --git a/compiler/router/router.py b/compiler/router/router.py index a3878b5e..7269e07f 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -1,17 +1,18 @@ import sys import gdsMill -from tech import drc,GDS,layer -from contact import contact +from tech import drc,GDS import math import debug +from router_tech import router_tech from pin_layout import pin_layout +from pin_group import pin_group from vector import vector from vector3d import vector3d from globals import OPTS from pprint import pformat import grid_utils -class router: +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. @@ -23,6 +24,8 @@ class router: 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) + self.cell = design # If didn't specify a gds blockage file, write it out to read the gds @@ -37,22 +40,16 @@ class router: self.reader.loadFromFile(gds_filename) self.top_name = self.layout.rootStructureName - # Set up layers and track sizes - self.set_layers(layers) - ### The pin data structures - # A map of pin names to pin structures + # A map of pin names to a set of pin_layout structures self.pins = {} - # This is a set of all pins so that we don't create blockages for these shapes. + # This is a set of all pins (ignoring names) so that can quickly not create blockages for pins + # (They will be blocked based on the names we are routing) self.all_pins = set() - # This is a set of pin groups. Each group consists of overlapping pin shapes on the same layer. + # A map of pin names to a list of pin groups + # A pin group is a set overlapping pin shapes on the same layer. self.pin_groups = {} - # These are the corresponding pin grids for each pin group. - self.pin_grids = {} - # The corresponding set of partially blocked grids for each pin group. - # These are blockages for other nets but unblocked for this component. - self.pin_blockages = {} ### The blockage data structures # A list of metal shapes (using the same pin_layout structure) that are not pins but blockages. @@ -78,8 +75,6 @@ class router: self.pins = {} self.all_pins = set() self.pin_groups = {} - self.pin_grids = {} - self.pin_blockages = {} # DO NOT clear the blockages as these don't change self.rg.reinit() @@ -88,19 +83,6 @@ class router: """ If we want to route something besides the top-level cell.""" self.top_name = top_name - def get_zindex(self,layer_num): - if layer_num==self.horiz_layer_number: - return 0 - else: - return 1 - - def get_layer(self, zindex): - if zindex==1: - return self.vert_layer_name - elif zindex==0: - return self.horiz_layer_name - else: - debug.error("Invalid zindex {}".format(zindex),-1) def is_wave(self,path): """ @@ -108,40 +90,6 @@ class router: """ return len(path[0])>1 - def set_layers(self, layers): - """ - Allows us to change the layers that we are routing on. First layer - is always horizontal, middle is via, and last is always - vertical. - """ - self.layers = layers - (self.horiz_layer_name, self.via_layer_name, self.vert_layer_name) = self.layers - - # This is the minimum routed track spacing - via_connect = contact(self.layers, (1, 1)) - self.max_via_size = max(via_connect.width,via_connect.height) - - self.vert_layer_minwidth = drc("minwidth_{0}".format(self.vert_layer_name)) - self.vert_layer_spacing = drc(str(self.vert_layer_name)+"_to_"+str(self.vert_layer_name)) - self.vert_layer_number = layer[self.vert_layer_name] - - self.horiz_layer_minwidth = drc("minwidth_{0}".format(self.horiz_layer_name)) - self.horiz_layer_spacing = drc(str(self.horiz_layer_name)+"_to_"+str(self.horiz_layer_name)) - self.horiz_layer_number = layer[self.horiz_layer_name] - - self.horiz_track_width = self.max_via_size + self.horiz_layer_spacing - self.vert_track_width = self.max_via_size + self.vert_layer_spacing - - # We'll keep horizontal and vertical tracks the same for simplicity. - self.track_width = max(self.horiz_track_width,self.vert_track_width) - debug.info(1,"Track width: "+str(self.track_width)) - - self.track_widths = [self.track_width] * 2 - self.track_factor = [1/self.track_width] * 2 - debug.info(1,"Track factor: {0}".format(self.track_factor)) - - # When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side) - self.layer_widths = [self.track_width - self.horiz_layer_spacing, 1, self.track_width - self.vert_layer_spacing] def retrieve_pins(self,pin_name): """ @@ -224,14 +172,16 @@ class router: self.set_supply_rail_blocked(True) # Block all of the pin components (some will be unblocked if they're a source/target) - for name in self.pin_grids.keys(): - self.set_blockages(self.pin_grids[name],True) + for name in self.pin_groups.keys(): + blockage_grids = {y for x in self.pin_groups[name] for y in x.grids} + self.set_blockages(blockage_grids,True) # Don't mark the other components as targets since we want to route # directly to a rail, but unblock all the source components so we can # route over them - self.set_blockages(self.pin_grids[pin_name],False) + blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} + self.set_blockages(blockage_grids,False) # These are the paths that have already been routed. self.set_path_blockages() @@ -458,23 +408,6 @@ class router: return set([best_coord]) - def get_layer_width_space(self, zindex, width=0, length=0): - """ - Return the width and spacing of a given layer - and wire of a given width and length. - """ - 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_width = drc("minwidth_{0}".format(layer_name), width, length) - min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), width, length) - - return (min_width,min_spacing) - def convert_pin_coord_to_tracks(self, pin, coord): """ Given a pin and a track coordinate, determine if the pin overlaps enough. @@ -599,24 +532,18 @@ class router: reduced_classes = combine_classes(equiv_classes) if local_debug: debug.info(0,"FINAL ",reduced_classes) - self.pin_groups[pin_name]=reduced_classes + self.pin_groups[pin_name] = [pin_group(name=pin_name, pin_shapes=x, router=self) for x in reduced_classes] def convert_pins(self, pin_name): """ Convert the pin groups into pin tracks and blockage tracks. """ - try: - self.pin_grids[pin_name] - except: - self.pin_grids[pin_name] = [] - - found_pin = False for pg in self.pin_groups[pin_name]: #print("PG ",pg) # Keep the same groups for each pin pin_set = set() blockage_set = set() - for pin in pg: + for pin in pg.shapes: debug.info(2," Converting {0}".format(pin)) # Determine which tracks the pin overlaps pin_in_tracks=self.convert_pin_to_tracks(pin_name, pin) @@ -644,7 +571,7 @@ class router: debug.error("Unable to find unblocked pin on grid.") # We need to route each of the components, so don't combine the groups - self.pin_grids[pin_name].append(pin_set | blockage_set) + pg.grids = pin_set | blockage_set # Add all of the partial blocked grids to the set for the design # if they are not blocked by other metal @@ -658,143 +585,6 @@ class router: #self.blocked_grids.difference_update(pin_set) - def enclose_pin_grids(self, grids, seed): - """ - This encloses a single pin component with a rectangle - starting with the seed and expanding right until blocked - and then up until blocked. - """ - - # We may have started with an empty set - if not grids: - return None - - # Start with the seed - ll = seed - - # Start with the ll and make the widest row - row = [ll] - # Move right while we can - while True: - right = row[-1] + vector3d(1,0,0) - # Can't move if not in the pin shape - if right in grids and right not in self.blocked_grids: - row.append(right) - else: - break - # Move up while we can - while True: - next_row = [x+vector3d(0,1,0) 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 grids or cell in self.blocked_grids: - 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 self.compute_pin_enclosure(ll, ur, ll.z) - - def remove_redundant_shapes(self, pin_list): - """ - Remove any pin layout that is contained within another. - """ - local_debug = False - if local_debug: - debug.info(0,"INITIAL:",pin_list) - - # Make a copy of the list to start - new_pin_list = pin_list.copy() - - # This is n^2, but the number is small - for pin1 in pin_list: - for pin2 in pin_list: - # Can't contain yourself - if pin1 == pin2: - continue - if pin2.contains(pin1): - # It may have already been removed by being enclosed in another pin - if pin1 in new_pin_list: - new_pin_list.remove(pin1) - - if local_debug: - debug.info(0,"FINAL :",new_pin_list) - return new_pin_list - - def compute_enclosures(self, tracks): - """ - Find the minimum rectangle enclosures of the given tracks. - """ - # Enumerate every possible enclosure - pin_list = [] - for seed in tracks: - pin_list.append(self.enclose_pin_grids(tracks, seed)) - - #return pin_list - # We used to do this, but smaller enclosures can be - return self.remove_redundant_shapes(pin_list) - - 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 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: - # They may not be all on the same layer... in the future. - zindex=self.get_zindex(pin.layer_num) - (min_width,min_space) = self.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 smallest_shape == None or other.area() biggest.area(): - biggest = pin - - return pin - - def find_smallest_connector(self, pin_list, enclosure_list): - """ - Compute all of the connectors between non-overlapping pins and enclosures. - Return the smallest. - """ - smallest = None - for pin in pin_list: - for enclosure in enclosure_list: - new_enclosure = self.compute_enclosure(pin, enclosure) - if smallest == None or new_enclosure.area()0: + self.create_simple_overlap_enclosure(pin_name, common_set) + pg.set_routed() + + def recurse_simple_overlap_enclosure(self, pin_name, start_set, direct): """ @@ -167,7 +165,9 @@ class supply_router(router): if not new_set: new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.WEST) - enclosure_list = self.compute_enclosures(new_set) + pg = pin_group(name=pin_name, pin_shapes=[], router=self) + pg.grids=new_set + enclosure_list = pg.compute_enclosures() for pin in enclosure_list: debug.info(2,"Adding simple overlap enclosure {0} {1}".format(pin_name, pin)) self.cell.add_rect(layer=pin.layer, @@ -464,23 +464,26 @@ class supply_router(router): self.supply_rail_wire_tracks[pin_name] = wire_set - def route_pins_to_rails(self, pin_name, remaining_component_indices): + 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,"Pin {0} has {1} remaining components to route.".format(pin_name, - len(remaining_component_indices))) + remaining_components)) - recent_paths = [] - # For every component - for index in remaining_component_indices: + for index,pg in enumerate(self.pin_groups[pin_name]): + if pg.is_routed(): + continue debug.info(2,"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(pin_name) # Add the single component of the pin as the source @@ -491,16 +494,10 @@ class supply_router(router): # Don't add the other pins, but we could? self.add_supply_rail_target(pin_name) - # Add the previous paths as targets too - #self.add_path_target(recent_paths) - - #print(self.rg.target) - # Actually run the A* router if not self.run_router(detour_scale=5): self.write_debug_gds() - recent_paths.append(self.paths[-1]) def add_supply_rail_target(self, pin_name):