From 1344a8f7f1a7140a17798859773594d568b59bd2 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Tue, 30 Oct 2018 12:24:13 -0700 Subject: [PATCH] Add remove adjacent feature for wide metal spacing --- compiler/router/direction.py | 7 ++ compiler/router/grid_path.py | 15 +--- compiler/router/pin_group.py | 140 +++++++++++++++++++++++-------- compiler/router/router.py | 87 ++++++++++++++++--- compiler/router/supply_router.py | 9 +- compiler/router/vector3d.py | 4 + 6 files changed, 197 insertions(+), 65 deletions(-) diff --git a/compiler/router/direction.py b/compiler/router/direction.py index c3de59a2..ab5873b4 100644 --- a/compiler/router/direction.py +++ b/compiler/router/direction.py @@ -30,3 +30,10 @@ class direction(Enum): debug.error("Invalid direction {}".format(dirct)) return offset + + def cardinal_directions(): + return [direction.NORTH, direction.EAST, direction.SOUTH, direction.WEST] + + def cardinal_offsets(): + return [direction.get_offset(d) for d in direction.cardinal_directions()] + diff --git a/compiler/router/grid_path.py b/compiler/router/grid_path.py index 437b6acb..250e485d 100644 --- a/compiler/router/grid_path.py +++ b/compiler/router/grid_path.py @@ -172,20 +172,7 @@ class grid_path: return neighbors def neighbor(self, d): - if d==direction.EAST: - offset = vector3d(1,0,0) - elif d==direction.WEST: - offset = vector3d(-1,0,0) - elif d==direction.NORTH: - offset = vector3d(0,1,0) - elif d==direction.SOUTH: - offset = vector3d(0,-1,0) - elif d==direction.UP: - offset = vector3d(0,0,1) - elif d==direction.DOWN: - offset = vector3d(0,0,-1) - else: - debug.error("Invalid direction {}".format(d),-1) + offset = direction.get_offset(d) newwave = [point + offset for point in self.pathlist[-1]] diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index b742d035..6b2e925f 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -15,6 +15,9 @@ class pin_group: self.name = name # Flag for when it is routed self.routed = False + # Flag for when it is enclosed + self.enclosed = False + # This is a list because we can have a pin group of disconnected sets of pins # and these are represented by separate lists if pin_shapes: @@ -25,10 +28,40 @@ class pin_group: self.router = router # These are the corresponding pin grids for each pin group. self.grids = set() + # These are the secondary grids that could or could not be part of the pin + self.secondary_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 __str__(self): + """ override print function output """ + total_string = "(pg {} ".format(self.name) + + pin_string = "\n pins={}".format(self.pins) + total_string += pin_string + + grids_string = "\n grids={}".format(self.grids) + total_string += grids_string + + grids_string = "\n secondary={}".format(self.secondary_grids) + total_string += grids_string + + if self.enclosed: + enlosure_string = "\n enclose={}".format(self.enclosures) + total_string += enclosure_string + + total_string += ")" + return total_string + + def __repr__(self): + """ override repr function output """ + return str(self) + + def size(self): + return len(self.grids) + def set_routed(self, value=True): self.routed = value @@ -277,22 +310,41 @@ class pin_group: 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() # A single set of connected pins is easy, so use the optimized set - if len(self.pins)==1: - enclosure_list = self.enclosures - smallest = self.find_smallest_overlapping(self.pins[0],enclosure_list) - if smallest: - self.enclosures=[smallest] - - debug.info(2,"Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name, + # if len(self.pins)==1: + # enclosure_list = self.enclosures + # smallest = self.find_smallest_overlapping(self.pins[0],enclosure_list) + # if smallest: + # self.enclosures=[smallest] + + # Save the list of all grids + #self.all_grids = self.grids.copy() + + # Remove the grids that are not covered by the enclosures + # FIXME: We could probably just store what grids each enclosure overlaps when + # it was created. + #for enclosure in self.enclosures: + # enclosure_in_tracks=router.convert_pin_to_tracks(self.name, enclosure) + # self.grids.difference_update(enclosure_in_tracks) + + debug.info(3,"Computed enclosure(s) {0}\n {1}\n {2}\n {3}".format(self.name, self.pins, self.grids, self.enclosures)) - + def combine_pins(self, pg1, pg2): + """ + Combine two pin groups into one. + """ + self.pins = [*pg1.pins, *pg2.pins] # Join the two lists of pins + self.grids = pg1.grids | pg2.grids # OR the set of grid locations + self.secondary_grids = pg1.secondary_grids | pg2.secondary_grids + def add_enclosure(self, cell): """ Add the enclosure shape to the given cell. @@ -305,23 +357,57 @@ class pin_group: height=enclosure.height()) - + def perimeter_grids(self): + """ + Return a list of the grids on the perimeter. + This assumes that we have a single contiguous shape. + """ + perimeter_set = set() + cardinal_offsets = direction.cardinal_offsets() + for g1 in self.grids: + neighbor_grids = [g1 + offset for offset in cardinal_offsets] + neighbor_count = sum([x in self.grids for x in neighbor_grids]) + # If we aren't completely enclosed, we are on the perimeter + if neighbor_count < 4: + perimeter_set.add(g1) + + return perimeter_set def adjacent(self, other): """ Chck if the two pin groups have at least one adjacent pin grid. """ # We could optimize this to just check the boundaries - for g1 in self.grids: - for g2 in other.grids: + for g1 in self.perimeter_grids(): + for g2 in other.perimeter_grids(): if g1.adjacent(g2): return True return False + + def adjacent_grids(self, other, separation): + """ + Determine the sets of grids that are within a separation distance + of any grid in the other set. + """ + # We could optimize this to just check the boundaries + g1_grids = set() + g2_grids = set() + for g1 in self.grids: + for g2 in other.grids: + if g1.distance(g2) <= separation: + g1_grids.add(g1) + g2_grids.add(g2) + + return g1_grids,g2_grids + def convert_pin(self, router): - #print("PG ",pg) - # Keep the same groups for each pin + """ + Convert the list of pin shapes into sets of routing grids. + The secondary set of grids are "optional" pin shapes that could be + should be either blocked or part of the pin. + """ pin_set = set() blockage_set = set() @@ -334,19 +420,6 @@ class pin_group: # Blockages will be a super-set of pins since it uses the inflated pin shape. blockage_in_tracks = router.convert_blockage(pin) blockage_set.update(blockage_in_tracks) - - # If we have a blockage, we must remove the grids - # Remember, this excludes the pin blockages already - shared_set = pin_set & router.blocked_grids - if len(shared_set)>0: - debug.info(2,"Removing pins {}".format(shared_set)) - shared_set = blockage_set & router.blocked_grids - if len(shared_set)>0: - debug.info(2,"Removing blocks {}".format(shared_set)) - pin_set.difference_update(router.blocked_grids) - blockage_set.difference_update(router.blocked_grids) - debug.info(2," pins {}".format(pin_set)) - debug.info(2," blocks {}".format(blockage_set)) # At least one of the groups must have some valid tracks if (len(pin_set)==0 and len(blockage_set)==0): @@ -355,14 +428,9 @@ class pin_group: # We need to route each of the components, so don't combine the groups self.grids = pin_set | blockage_set + # Remember the secondary grids for removing adjacent pins in wide metal spacing + self.secondary_grids = blockage_set - pin_set - # Add all of the partial blocked grids to the set for the design - # if they are not blocked by other metal - #partial_set = blockage_set - pin_set - #self.blockages = partial_set - - # We should not have added the pins to the blockages, - # but remove them just in case - # Partial set may still be in the blockages if there were - # other shapes disconnected from the pins that were also overlapping - #route.blocked_grids.difference_update(pin_set) + debug.info(2," pins {}".format(self.grids)) + debug.info(2," secondary {}".format(self.secondary_grids)) + diff --git a/compiler/router/router.py b/compiler/router/router.py index 861da04c..ee6a9300 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -48,7 +48,6 @@ class router(router_tech): self.all_pins = set() # 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 = {} ### The blockage data structures @@ -157,10 +156,14 @@ class router(router_tech): #self.write_debug_gds("debug_combine_pins.gds",stop_program=True) # Separate any adjacent grids of differing net names to prevent wide metal DRC violations - self.separate_adjacent_pins(pin) - + # Must be done before enclosing pins + self.separate_adjacent_pins(self.supply_rail_space_width) + # For debug + #self.separate_adjacent_pins(1) + # Enclose the continguous grid units in a metal rectangle to fix some DRCs self.enclose_pins() + #self.write_debug_gds("debug_enclose_pins.gds",stop_program=True) def combine_adjacent_pins_pass(self, pin_name): @@ -191,13 +194,13 @@ class router(router_tech): # Combine if at least 1 grid cell is adjacent if pg1.adjacent(pg2): combined = pin_group(pin_name, [], self) - combined.pins = [*pg1.pins, *pg2.pins] # Join the two lists of pins - combined.grids = pg1.grids | pg2.grids # OR the set of grid locations - debug.info(2,"Combining {0}:\n {1}\n {2}".format(pin_name, pg1.pins, pg2.pins)) - debug.info(2," --> {0}\n {1}\n".format(combined.pins,combined.grids)) + combined.combine_pins(pg1, pg2) + debug.info(2,"Combining {0} {1} {2}:".format(pin_name, index1, index2)) + debug.info(2, " {0}\n {1}".format(pg1.pins, pg2.pins)) + debug.info(2," --> {0}\n {1}".format(combined.pins,combined.grids)) remove_indices.update([index1,index2]) pin_groups.append(combined) - + break # Remove them in decreasing order to not invalidate the indices debug.info(2,"Removing {}".format(sorted(remove_indices))) @@ -207,7 +210,7 @@ class router(router_tech): # Use the new pin group! self.pin_groups[pin_name] = pin_groups - removed_pairs = len(remove_indices)/2 + removed_pairs = int(len(remove_indices)/2) debug.info(1, "Combined {0} pin pairs for {1}".format(removed_pairs,pin_name)) return removed_pairs @@ -229,17 +232,77 @@ class router(router_tech): else: debug.warning("Did not converge combining adjacent pins in supply router.") - def separate_adjacent_pins(self, pin_name, separation=1): + def separate_adjacent_pins(self, separation): """ This will try to separate all grid pins by the supplied number of separation tracks (default is to prevent adjacency). + """ + # Commented out to debug with SCMOS + #if separation==0: + # return + + pin_names = self.pin_groups.keys() + for pin_name1 in pin_names: + for pin_name2 in pin_names: + if pin_name1==pin_name2: + continue + 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. """ - pass + debug.info(1,"Comparing {0} and {1} adjacency".format(pin_name1, pin_name2)) + for index1,pg1 in enumerate(self.pin_groups[pin_name1]): + for index2,pg2 in enumerate(self.pin_groups[pin_name2]): + # FIXME: Use separation distance and edge grids only + grids_g1, grids_g2 = pg1.adjacent_grids(pg2, separation) + # These should have the same length, so... + if len(grids_g1)>0: + debug.info(1,"Adjacent grids {0} {1} {2} {3}".format(index1,grids_g1,index2,grids_g2)) + self.remove_adjacent_grid(pg1, grids_g1, pg2, grids_g2) + def remove_adjacent_grid(self, pg1, grids1, pg2, grids2): + """ + Remove one of the adjacent grids in a heuristic manner. + """ + # Determine the bigger and smaller group + if pg1.size()>pg2.size(): + bigger = pg1 + bigger_grids = grids1 + smaller = pg2 + smaller_grids = grids2 + else: + bigger = pg2 + bigger_grids = grids2 + smaller = pg1 + smaller_grids = grids1 + + # First, see if we can remove grids that are in the secondary grids + # i.e. they aren't necessary to the pin grids + if bigger_grids.issubset(bigger.secondary_grids): + debug.info(1,"Removing {} from bigger {}".format(str(bigger_grids), bigger)) + bigger.grids.difference_update(bigger_grids) + self.blocked_grids.update(bigger_grids) + return + elif smaller_grids.issubset(smaller.secondary_grids): + debug.info(1,"Removing {} from smaller {}".format(str(smaller_grids), smaller)) + smaller.grids.difference_update(smaller_grids) + self.blocked_grids.update(smaller_grids) + return + + # If that fails, just randomly remove from the bigger one and give a warning. + # This might fail later. + debug.warning("Removing arbitrary grids from a pin group {} {}".format(bigger, bigger_grids)) + debug.check(len(bigger.grids)>len(bigger_grids),"Zero size pin group after adjacency removal {} {}".format(bigger, bigger_grids)) + bigger.grids.difference_update(bigger_grids) + self.blocked_grids.update(bigger_grids) + + + def prepare_blockages(self, pin_name): """ Reset and add all of the blockages in the design. @@ -975,6 +1038,8 @@ class router(router_tech): if show_enclosures: for key in self.pin_groups.keys(): 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", diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index 4f12e16a..6c40c788 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -41,6 +41,7 @@ class supply_router(router): # Power rail width in grid units. self.rail_track_width = 2 + def create_routing_grid(self): """ @@ -69,6 +70,9 @@ class supply_router(router): # but this is simplest for now. self.create_routing_grid() + # Compute the grid dimensions + self.compute_supply_rail_dimensions() + # Get the pin shapes self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) #self.write_debug_gds("pin_enclosures.gds",stop_program=True) @@ -87,7 +91,7 @@ class supply_router(router): self.route_simple_overlaps(vdd_name) self.route_simple_overlaps(gnd_name) - #self.write_debug_gds("debug_simple_route.gds",stop_program=True) + self.write_debug_gds("debug_simple_route.gds",stop_program=False) # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter @@ -436,9 +440,6 @@ class supply_router(router): Must be done with lower left at 0,0 """ - # Compute the grid dimensions - self.compute_supply_rail_dimensions() - # Compute the grid locations of the supply rails self.compute_supply_rails(name, supply_number) diff --git a/compiler/router/vector3d.py b/compiler/router/vector3d.py index e7008036..a6e61078 100644 --- a/compiler/router/vector3d.py +++ b/compiler/router/vector3d.py @@ -163,6 +163,10 @@ class vector3d(): """ Min of both values """ return vector3d(min(self.x,other.x),min(self.y,other.y),min(self.z,other.z)) + def distance(self, other): + """ Return the planar distance between two values """ + return abs(self.x-other.x)+abs(self.y-other.y) + def adjacent(self, other): """ Is the one grid adjacent in any planar direction to the other """