From 0aad61892b397dc0bdb1ddb84e49bb18c35ca807 Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Fri, 19 Oct 2018 14:21:03 -0700 Subject: [PATCH] Supply router working except for off by one rail via error --- compiler/base/hierarchy_layout.py | 59 ++++++---- compiler/router/grid.py | 6 + compiler/router/router.py | 177 +++++++++++++++++++++--------- compiler/router/supply_router.py | 160 ++++++++++++++++++++++++--- 4 files changed, 315 insertions(+), 87 deletions(-) diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 3844c9a9..32af57c1 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -134,11 +134,13 @@ class layout(lef.lef): return inst return None - def add_rect(self, layer, offset, width=0, height=0): - """Adds a rectangle on a given layer,offset with width and height""" - if width==0: + def add_rect(self, layer, offset, width=None, height=None): + """ + Adds a rectangle on a given layer,offset with width and height + """ + if not width: width=drc["minwidth_{}".format(layer)] - if height==0: + if not height: height=drc["minwidth_{}".format(layer)] # negative layers indicate "unused" layers in a given technology layer_num = techlayer[layer] @@ -147,11 +149,13 @@ class layout(lef.lef): return self.objs[-1] return None - def add_rect_center(self, layer, offset, width=0, height=0): - """Adds a rectangle on a given layer at the center point with width and height""" - if width==0: + def add_rect_center(self, layer, offset, width=None, height=None): + """ + Adds a rectangle on a given layer at the center point with width and height + """ + if not width: width=drc["minwidth_{}".format(layer)] - if height==0: + if not height: height=drc["minwidth_{}".format(layer)] # negative layers indicate "unused" layers in a given technology layer_num = techlayer[layer] @@ -163,7 +167,9 @@ class layout(lef.lef): def add_segment_center(self, layer, start, end): - """ Add a min-width rectanglular segment using center line on the start to end point """ + """ + Add a min-width rectanglular segment using center line on the start to end point + """ minwidth_layer = drc["minwidth_{}".format(layer)] if start.x!=end.x and start.y!=end.y: debug.error("Nonrectilinear center rect!",-1) @@ -177,7 +183,9 @@ class layout(lef.lef): def get_pin(self, text): - """ Return the pin or list of pins """ + """ + Return the pin or list of pins + """ try: if len(self.pin_map[text])>1: debug.error("Should use a pin iterator since more than one pin {}".format(text),-1) @@ -192,7 +200,9 @@ class layout(lef.lef): def get_pins(self, text): - """ Return a pin list (instead of a single pin) """ + """ + Return a pin list (instead of a single pin) + """ if text in self.pin_map.keys(): return self.pin_map[text] else: @@ -210,7 +220,9 @@ class layout(lef.lef): self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height()) def add_layout_pin_segment_center(self, text, layer, start, end): - """ Creates a path like pin with center-line convention """ + """ + Creates a path like pin with center-line convention + """ debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.") @@ -235,9 +247,9 @@ class layout(lef.lef): def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): """ Creates a path like pin with center-line convention """ - if width==None: + if not width: width=drc["minwidth_{0}".format(layer)] - if height==None: + if not height: height=drc["minwidth_{0}".format(layer)] ll_offset = offset - vector(0.5*width,0.5*height) @@ -246,14 +258,18 @@ class layout(lef.lef): def remove_layout_pin(self, text): - """Delete a labeled pin (or all pins of the same name)""" + """ + Delete a labeled pin (or all pins of the same name) + """ self.pin_map[text]=[] def add_layout_pin(self, text, layer, offset, width=None, height=None): - """Create a labeled pin """ - if width==None: + """ + Create a labeled pin + """ + if not width: width=drc["minwidth_{0}".format(layer)] - if height==None: + if not height: height=drc["minwidth_{0}".format(layer)] new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer) @@ -273,13 +289,14 @@ class layout(lef.lef): return new_pin def add_label_pin(self, text, layer, offset, width=None, height=None): - """Create a labeled pin WITHOUT the pin data structure. This is not an + """ + Create a labeled pin WITHOUT the pin data structure. This is not an actual pin but a named net so that we can add a correspondence point in LVS. """ - if width==None: + if not width: width=drc["minwidth_{0}".format(layer)] - if height==None: + if not height: height=drc["minwidth_{0}".format(layer)] self.add_rect(layer=layer, offset=offset, diff --git a/compiler/router/grid.py b/compiler/router/grid.py index b7b1e91b..b43c36b2 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -36,6 +36,12 @@ class grid: # let's leave the map sparse, cells are created on demand to reduce memory self.map={} + def add_all_grids(self): + for x in range(self.ll.x, self.ur.x, 1): + for y in range(self.ll.y, self.ur.y, 1): + self.add_map(vector3d(x,y,0)) + self.add_map(vector3d(x,y,1)) + def set_blocked(self,n,value=True): if isinstance(n, (list,tuple,set,frozenset)): for item in n: diff --git a/compiler/router/router.py b/compiler/router/router.py index efcd09eb..3d0387a2 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -62,8 +62,6 @@ class router: ### The routed data structures # A list of paths that have been "routed" self.paths = [] - # The list of supply rails that may be routed - self.supply_rails = [] # The boundary will determine the limits to the size of the routing grid self.boundary = self.layout.measureBoundary(self.top_name) @@ -833,8 +831,31 @@ class router: # Add a shape from ll to ur ur = row[-1] - return self.add_enclosure(ll, ur, ll.z) + 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. + """ + print("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) + + print("FINAL :",new_pin_list) + return new_pin_list def compute_enclosures(self, tracks): """ @@ -844,19 +865,7 @@ class router: for seed in tracks: pin_list.append(self.enclose_pin_grids(tracks, seed)) - # Prune any enclosre that is contained in another - new_pin_list = pin_list - for pin1 in pin_list: - for pin2 in pin_list: - if pin1 == pin2: - continue - if pin2.contains(pin1): - try: - new_pin_list.remove(pin1) - except ValueError: - pass - - return new_pin_list + return self.remove_redundant_shapes(pin_list) def overlap_any_shape(self, pin_list, shape_list): """ @@ -900,19 +909,24 @@ class router: put a rectangle over it. It does not enclose grid squares that are blocked by other shapes. """ + # These are used for debugging + self.connector_enclosure = [] + self.enclosures = [] + for pin_name in self.pin_grids.keys(): debug.info(1,"Enclosing pins for {}".format(pin_name)) debug.check(len(self.pin_groups[pin_name])==len(self.pin_grids[pin_name]),"Unequal pin_group and pin_grid") - for pin_group,pin_set in zip(self.pin_groups[pin_name],self.pin_grids[pin_name]): + for pin_group,pin_grid_set in zip(self.pin_groups[pin_name],self.pin_grids[pin_name]): # Compute the enclosure pin_layout list of the set of tracks - enclosure_list = self.compute_enclosures(pin_set) + enclosure_list = self.compute_enclosures(pin_grid_set) for pin in enclosure_list: debug.info(2,"Adding enclosure {0} {1}".format(pin_name, pin)) self.cell.add_rect(layer=pin.layer, offset=pin.ll(), width=pin.width(), height=pin.height()) + self.enclosures.append(pin) # Check if a pin shape overlaps any enclosure. # If so, we are done. @@ -926,6 +940,7 @@ class router: offset=new_enclosure.ll(), width=new_enclosure.width(), height=new_enclosure.height()) + self.connector_enclosure.append(new_enclosure) @@ -956,7 +971,7 @@ class router: xmin = min(pbc.x,ebc.x) xmax = max(pbc.x,ebc.x) ll = vector(xmin, pbc.y) - ur = vetor(xmax, puc.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 @@ -1112,7 +1127,7 @@ class router: ll = path[0][0] ur = path[-1][-1] z = ll.z - pin = self.add_enclosure(ll, ur, z, name) + pin = self.compute_wide_enclosure(ll, ur, z, name) #print(ll, ur, ll.z, "->",pin) self.cell.add_layout_pin(text=name, layer=pin.layer, @@ -1122,7 +1137,28 @@ class router: return pin - def add_enclosure(self, ll, ur, zindex, name=""): + def compute_pin_enclosure(self, ll, ur, zindex, name=""): + """ + Enclose the tracks from ll to ur in a single rectangle that meets + the track DRC rules. If name is supplied, it is added as a pin and + not just a rectangle. + """ + # Get the layer information + (width, space) = self.get_layer_width_space(zindex) + layer = self.get_layer(zindex) + + # This finds the pin shape enclosed by the track with DRC spacing on the sides + (abs_ll,unused) = self.convert_track_to_pin(ll) + (unused,abs_ur) = self.convert_track_to_pin(ur) + #print("enclose ll={0} ur={1}".format(ll,ur)) + #print("enclose ll={0} ur={1}".format(abs_ll,abs_ur)) + + pin = pin_layout(name, [abs_ll, abs_ur], layer) + + return pin + + + def compute_wide_enclosure(self, ll, ur, zindex, name=""): """ Enclose the tracks from ll to ur in a single rectangle that meets the track DRC rules. If name is supplied, it is added as a pin and not just a rectangle. @@ -1142,14 +1178,18 @@ class router: # Get the DRC rule for the grid dimensions (width, space) = self.get_layer_width_space(zindex, shape_width, shape_length) layer = self.get_layer(zindex) - + + if zindex==0: + spacing = vector(0.5*self.track_width, 0.5*space) + else: + spacing = vector(0.5*space, 0.5*self.track_width) # Compute the shape offsets with correct spacing - new_ll = abs_ll + vector(0.5*space, 0.5*space) - new_ur = abs_ur - vector(0.5*space, 0.5*space) + new_ll = abs_ll + spacing + new_ur = abs_ur - spacing pin = pin_layout(name, [new_ll, new_ur], layer) return pin - + def get_inertia(self,p0,p1): """ @@ -1246,7 +1286,37 @@ class router: if stop_program: import sys sys.exit(1) - + + def annotate_grid(self, g): + """ + Display grid information in the GDS file for a single grid cell. + """ + shape = self.convert_track_to_shape(g) + partial_track=vector(0,self.track_width/6.0) + self.cell.add_rect(layer="text", + offset=shape[0], + width=shape[1].x-shape[0].x, + height=shape[1].y-shape[0].y) + t=self.rg.map[g].get_type() + + # midpoint offset + off=vector((shape[1].x+shape[0].x)/2, + (shape[1].y+shape[0].y)/2) + if g[2]==1: + # Upper layer is upper right label + type_off=off+partial_track + else: + # Lower layer is lower left label + type_off=off-partial_track + if t!=None: + self.cell.add_label(text=str(t), + layer="text", + offset=type_off) + self.cell.add_label(text="{0},{1}".format(g[0],g[1]), + layer="text", + offset=shape[0], + zoom=0.05) + def add_router_info(self): """ Write the routing grid and router cost, blockage, pins on @@ -1255,7 +1325,18 @@ class router: """ debug.info(0,"Adding router info") - if OPTS.debug_level>0: + show_blockages = False + show_blockage_grids = False + show_enclosures = False + show_connectors = False + show_all_grids = True + + if show_all_grids: + self.rg.add_all_grids() + for g in self.rg.map.keys(): + self.annotate_grid(g) + + if show_blockages: # Display the inflated blockage for blockage in self.blockages: debug.info(1,"Adding {}".format(blockage)) @@ -1264,36 +1345,26 @@ class router: offset=ll, width=ur.x-ll.x, height=ur.y-ll.y) - if OPTS.debug_level>1: + if show_blockage_grids: self.set_blockages(self.blocked_grids,True) grid_keys=self.rg.map.keys() - partial_track=vector(0,self.track_width/6.0) for g in grid_keys: - shape = self.convert_track_to_shape(g) - self.cell.add_rect(layer="text", - offset=shape[0], - width=shape[1].x-shape[0].x, - height=shape[1].y-shape[0].y) - t=self.rg.map[g].get_type() - - # midpoint offset - off=vector((shape[1].x+shape[0].x)/2, - (shape[1].y+shape[0].y)/2) - if g[2]==1: - # Upper layer is upper right label - type_off=off+partial_track - else: - # Lower layer is lower left label - type_off=off-partial_track - if t!=None: - self.cell.add_label(text=str(t), - layer="text", - offset=type_off) - self.cell.add_label(text="{0},{1}".format(g[0],g[1]), - layer="text", - offset=shape[0], - zoom=0.05) + self.annotate_grid(g) + if show_connectors: + for pin in self.connector_enclosure: + #print("connector: ",str(pin)) + self.cell.add_rect(layer="text", + offset=pin.ll(), + width=pin.width(), + height=pin.height()) + if show_enclosures: + for pin in self.enclosures: + #print("enclosure: ",pin.name,pin.ll(),pin.width(),pin.height()) + self.cell.add_rect(layer="text", + offset=pin.ll(), + width=pin.width(), + height=pin.height()) # FIXME: This should be replaced with vector.snap_to_grid at some point diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index ea9ef3f7..fd638d72 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -4,7 +4,6 @@ from contact import contact import math import debug from globals import OPTS -import grid from pin_layout import pin_layout from vector import vector from vector3d import vector3d @@ -24,6 +23,13 @@ class supply_router(router): """ router.__init__(self, layers, design, gds_filename) + + # The list of supply rails that may be routed + self.supply_rails = [] + # This is the same as above but the sets of pins + self.supply_rail_tracks = {} + self.supply_rail_wire_tracks = {} + # Power rail width in grid units. self.rail_track_width = 2 @@ -57,7 +63,9 @@ class supply_router(router): # 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) + # Add the supply rails in a mesh network and connect H/V with vias # Block everything self.prepare_blockages(self.gnd_name) @@ -70,17 +78,108 @@ class supply_router(router): self.route_supply_rails(self.vdd_name,1) #self.write_debug_gds("pre_pin_debug.gds",stop_program=True) + remaining_vdd_pin_indices = self.route_simple_overlaps(vdd_name) + remaining_gnd_pin_indices = self.route_simple_overlaps(gnd_name) # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter - self.route_pins_to_rails(vdd_name) - self.route_pins_to_rails(gnd_name) + self.route_pins_to_rails(vdd_name, remaining_vdd_pin_indices) + self.route_pins_to_rails(gnd_name, remaining_gnd_pin_indices) - #self.write_debug_gds("post_pin_debug.gds",stop_program=False) + self.write_debug_gds("post_pin_debug.gds",stop_program=False) return True + + + + + def route_simple_overlaps(self, pin_name): + """ + This checks for simple cases where a pin component already overlaps a supply rail. + It will add an enclosure to ensure the overlap in wide DRC rule cases. + """ + num_components = self.num_pin_components(pin_name) + remaining_pins = [] + supply_tracks = self.supply_rail_tracks[pin_name] + + for index in range(num_components): + pin_in_tracks = self.pin_grids[pin_name][index] + common_set = supply_tracks & pin_in_tracks + + if len(common_set)==0: + # if no overlap, add it to the complex route pins + remaining_pins.append(index) + else: + print("Overlap!",index) + self.create_simple_overlap_enclosure(pin_name, common_set) + + return remaining_pins + + def recurse_simple_overlap_enclosure(self, pin_name, start_set, direct): + """ + Recursive function to return set of tracks that connects to + the actual supply rail wire in a given direction (or terminating + when any track is no longer in the supply rail. + """ + import grid_utils + next_set = grid_utils.expand_border(start_set, direct) + + supply_tracks = self.supply_rail_tracks[pin_name] + supply_wire_tracks = self.supply_rail_wire_tracks[pin_name] + supply_overlap = next_set & supply_tracks + wire_overlap = next_set & supply_wire_tracks + + print("EXAMINING: ",start_set,len(start_set),len(supply_overlap),len(wire_overlap),direct) + # If the rail overlap is the same, we are done, since we connected to the actual wire + if len(wire_overlap)==len(start_set): + print("HIT RAIL", wire_overlap) + new_set = start_set | wire_overlap + # If the supply overlap is the same, keep expanding unti we hit the wire or move out of the rail region + elif len(supply_overlap)==len(start_set): + print("RECURSE", supply_overlap) + recurse_set = self.recurse_simple_overlap_enclosure(pin_name, supply_overlap, direct) + new_set = start_set | supply_overlap | recurse_set + else: + # If we got no next set, we are done, can't expand! + print("NO MORE OVERLAP", supply_overlap) + new_set = set() + + return new_set + + def create_simple_overlap_enclosure(self, pin_name, start_set): + """ + This takes a set of tracks that overlap a supply rail and creates an enclosure + that is ensured to overlap the supply rail wire. + It then adds rectangle(s) for the enclosure. + """ + import grid_utils + + additional_set = set() + # Check the layer of any element in the pin to determine which direction to route it + e = next(iter(start_set)) + new_set = start_set.copy() + if e.z==0: + new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.NORTH) + if not new_set: + new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.SOUTH) + else: + new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.EAST) + if not new_set: + new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.WEST) + + enclosure_list = self.compute_enclosures(new_set) + 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, + offset=pin.ll(), + width=pin.width(), + height=pin.height()) + + + + def connect_supply_rails(self, name): """ Determine which supply rails overlap and can accomodate a via. @@ -119,7 +218,7 @@ class supply_router(router): remove_hrails = [rail for flag,rail in zip(horizontal_flags,horizontal_rails) if not flag] remove_vrails = [rail for flag,rail in zip(vertical_flags,vertical_rails) if not flag] for rail in remove_hrails + remove_vrails: - debug.info(1,"Removing disconnected supply rail {}".format(rail)) + debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(rail[0][0],rail[-1][-1])) self.supply_rails.remove(rail) def add_supply_rails(self, name): @@ -155,9 +254,19 @@ class supply_router(router): # i.e. a rail of self.rail_track_width needs this many tracks including # space track_pitch = self.rail_track_width*width + space - + + # Determine the pitch (in tracks) of the rail wire + spacing self.supply_rail_width = math.ceil(track_pitch/self.track_width) debug.info(1,"Rail step: {}".format(self.supply_rail_width)) + + # Conservatively determine the number of tracks that the rail actually occupies + space_tracks = math.ceil(space/self.track_width) + self.supply_rail_wire_width = self.supply_rail_width - space_tracks + debug.info(1,"Rail wire tracks: {}".format(self.supply_rail_wire_width)) + total_space = self.supply_rail_width - self.supply_rail_wire_width + debug.check(total_space % 2 == 0, "Asymmetric wire track spacing...") + self.supply_rail_space_width = int(0.5*total_space) + debug.info(1,"Rail space tracks: {} (on both sides)".format(self.supply_rail_space_width)) def compute_supply_rails(self, name, supply_number): @@ -244,21 +353,46 @@ class supply_router(router): # Add the rails themselves self.add_supply_rails(name) + # Make the supply rails into a big giant set of grids + self.create_supply_track_set(name) + + + def create_supply_track_set(self, pin_name): + """ + Take the remaining supply rails and put the middle grids into a set. + The middle grids will be guaranteed to have the wire. + FIXME: Do this instead with the supply_rail_pitch and width + """ + rail_set = set() + wire_set = set() + for rail in self.supply_rails: + if rail.name != pin_name: + continue + # FIXME: Select based on self.supply_rail_wire_width and self.supply_rail_width + start_wire_index = self.supply_rail_space_width + end_wire_index = self.supply_rail_width - self.supply_rail_space_width + for wave_index in range(len(rail)): + rail_set.update(rail[wave_index]) + wire_set.update(rail[wave_index][start_wire_index:end_wire_index]) + + self.supply_rail_tracks[pin_name] = rail_set + self.supply_rail_wire_tracks[pin_name] = wire_set + - def route_pins_to_rails(self, pin_name): + def route_pins_to_rails(self, pin_name, remaining_component_indices): """ - This will route each of the pin components to the supply rails. + 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. """ - - num_components = self.num_pin_components(pin_name) - debug.info(1,"Pin {0} has {1} components to route.".format(pin_name, num_components)) + + debug.info(1,"Pin {0} has {1} remaining components to route.".format(pin_name, + len(remaining_component_indices))) recent_paths = [] # For every component - for index in range(num_components): + for index in remaining_component_indices: debug.info(2,"Routing component {0} {1}".format(pin_name, index)) self.rg.reinit()