diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index 8f9e81ee..44b005f6 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -441,10 +441,10 @@ class pin_layout: """ Given three co-linear points, determine if q lies on segment pr """ - if q[0] <= max(p[0], r[0]) and \ - q[0] >= min(p[0], r[0]) and \ - q[1] <= max(p[1], r[1]) and \ - q[1] >= min(p[1], r[1]): + if q.x <= max(p.x, r.x) and \ + q.x >= min(p.x, r.x) and \ + q.y <= max(p.y, r.y) and \ + q.y >= min(p.y, r.y): return True return False @@ -473,8 +473,8 @@ class pin_layout: x = (b2*c1 - b1*c2)/determinant y = (a1*c2 - a2*c1)/determinant - r = [x,y] + r = vector(x,y).snap_to_grid() if self.on_segment(a, r, b) and self.on_segment(c, r, d): - return [x, y] + return r return None diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index e50f94d9..13368d12 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -172,7 +172,6 @@ class pin_group: 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() @@ -183,7 +182,6 @@ class pin_group: 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() @@ -194,8 +192,10 @@ class pin_group: ymax = max(pc.y, ec.y) ll = vector(xmin, ymin) ur = vector(xmax, ymax) - p = pin_layout(pin.name, [ll, ur], pin.layer) + if ll.x==ur.x or ll.y==ur.y: + return None + p = pin_layout(pin.name, [ll, ur], pin.layer) return p def find_above_connector(self, pin, enclosures): @@ -226,7 +226,7 @@ class pin_group: # If it already overlaps, no connector needed if above_item.overlaps(pin): return None - + # Otherwise, make a connector to the item p = self.compute_connector(pin, above_item) return p @@ -485,7 +485,9 @@ class pin_group: for pin_list in self.pins: if not self.overlap_any_shape(pin_list, self.enclosures): connector = self.find_smallest_connector(pin_list, self.enclosures) - debug.check(connector!=None, "Could not find a connector for {} with {}".format(pin_list, self.enclosures)) + if connector==None: + debug.error("Could not find a connector for {} with {}".format(pin_list, self.enclosures)) + self.router.write_debug_gds("no_connector.gds") self.enclosures.append(connector) @@ -559,15 +561,13 @@ class pin_group: of any grid in the other set. """ # We could optimize this to just check the boundaries - g1_grids = set() - g2_grids = set() + adj_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) + adj_grids.add(g1) - return g1_grids,g2_grids + return adj_grids def convert_pin(self): """ @@ -576,14 +576,16 @@ class pin_group: should be either blocked or part of the pin. """ pin_set = set() + partial_set = set() blockage_set = set() for pin_list in self.pins: for pin in pin_list: debug.info(2," Converting {0}".format(pin)) # Determine which tracks the pin overlaps - pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin) - pin_set.update(pin_in_tracks) + (sufficient,insufficient)=self.router.convert_pin_to_tracks(self.name, pin) + pin_set.update(sufficient) + partial_set.update(insufficient) # Blockages will be a super-set of pins since it uses the inflated pin shape. blockage_in_tracks = self.router.convert_blockage(pin) @@ -595,89 +597,94 @@ class pin_group: if len(shared_set)>0: debug.info(2,"Removing pins {}".format(shared_set)) pin_set.difference_update(shared_set) + shared_set = partial_set & self.router.blocked_grids + if len(shared_set)>0: + debug.info(2,"Removing pins {}".format(shared_set)) + partial_set.difference_update(shared_set) shared_set = blockage_set & self.router.blocked_grids if len(shared_set)>0: debug.info(2,"Removing blocks {}".format(shared_set)) blockage_set.difference_update(shared_set) # At least one of the groups must have some valid tracks - if (len(pin_set)==0 and len(blockage_set)==0): + if (len(pin_set)==0 and len(partial_set)==0 and len(blockage_set)==0): #debug.warning("Pin is very close to metal blockage.\nAttempting to expand blocked pin {}".format(self.pins)) for pin_list in self.pins: for pin in pin_list: - debug.info(2," Converting {0}".format(pin)) + debug.warning(" Expanding conversion {0}".format(pin)) # Determine which tracks the pin overlaps - pin_in_tracks=self.router.convert_pin_to_tracks(self.name, pin, expansion=1) - pin_set.update(pin_in_tracks) + (sufficient,insufficient)=self.router.convert_pin_to_tracks(self.name, pin, expansion=1) + pin_set.update(sufficient) + partial_set.update(insufficient) - if len(pin_set)==0: + if len(pin_set)==0 and len(partial_set)==0: debug.error("Unable to find unblocked pin {} {}".format(self.name, self.pins)) self.router.write_debug_gds("blocked_pin.gds") - # 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 + # Consider all the grids that would be blocked + self.grids = pin_set | partial_set + # Remember the secondary grids for removing adjacent pins + self.secondary_grids = partial_set debug.info(2," pins {}".format(self.grids)) debug.info(2," secondary {}".format(self.secondary_grids)) - def recurse_simple_overlap_enclosure(self, 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. - """ - next_set = grid_utils.expand_border(start_set, direct) + # def recurse_simple_overlap_enclosure(self, 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. + # """ + # next_set = grid_utils.expand_border(start_set, direct) - supply_tracks = self.router.supply_rail_tracks[self.name] - supply_wire_tracks = self.router.supply_rail_wire_tracks[self.name] + # supply_tracks = self.router.supply_rail_tracks[self.name] + # supply_wire_tracks = self.router.supply_rail_wire_tracks[self.name] - supply_overlap = next_set & supply_tracks - wire_overlap = next_set & supply_wire_tracks + # supply_overlap = next_set & supply_tracks + # wire_overlap = next_set & supply_wire_tracks - # If the rail overlap is the same, we are done, since we connected to the actual wire - if len(wire_overlap)==len(start_set): - 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): - recurse_set = self.recurse_simple_overlap_enclosure(supply_overlap, direct) - new_set = start_set | supply_overlap | recurse_set - else: - # If we got no next set, we are done, can't expand! - new_set = set() + # # If the rail overlap is the same, we are done, since we connected to the actual wire + # if len(wire_overlap)==len(start_set): + # 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): + # recurse_set = self.recurse_simple_overlap_enclosure(supply_overlap, direct) + # new_set = start_set | supply_overlap | recurse_set + # else: + # # If we got no next set, we are done, can't expand! + # new_set = set() - return new_set + # return new_set - def create_simple_overlap_enclosure(self, 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. - """ - 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(start_set, direction.NORTH) - if not new_set: - new_set = self.recurse_simple_overlap_enclosure(start_set, direction.SOUTH) - else: - new_set = self.recurse_simple_overlap_enclosure(start_set, direction.EAST) - if not new_set: - new_set = self.recurse_simple_overlap_enclosure(start_set, direction.WEST) + # def create_simple_overlap_enclosure(self, 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. + # """ + # 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(start_set, direction.NORTH) + # if not new_set: + # new_set = self.recurse_simple_overlap_enclosure(start_set, direction.SOUTH) + # else: + # new_set = self.recurse_simple_overlap_enclosure(start_set, direction.EAST) + # if not new_set: + # new_set = self.recurse_simple_overlap_enclosure(start_set, direction.WEST) - # Expand the pin grid set to include some extra grids that connect the supply rail - self.grids.update(new_set) + # # Expand the pin grid set to include some extra grids that connect the supply rail + # self.grids.update(new_set) - # Add the inflated set so we don't get wide metal spacing issues (if it exists) - self.blockages.update(grid_utils.inflate_set(new_set,self.router.supply_rail_space_width)) + # # Block the grids + # self.blockages.update(new_set) - # Add the polygon enclosures and set this pin group as routed - self.set_routed() - self.enclosures = self.compute_enclosures() + # # Add the polygon enclosures and set this pin group as routed + # self.set_routed() + # self.enclosures = self.compute_enclosures() diff --git a/compiler/router/router.py b/compiler/router/router.py index bb6e1efc..49e74c7c 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -21,12 +21,12 @@ class router(router_tech): It populates blockages on a grid class. """ - def __init__(self, layers, design, gds_filename=None): + def __init__(self, layers, design, gds_filename=None, rail_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 considered. """ - router_tech.__init__(self, layers) + router_tech.__init__(self, layers, rail_track_width) self.cell = design @@ -177,15 +177,15 @@ class router(router_tech): #print_time("Convert pins",datetime.now(), start_time) #start_time = datetime.now() - for pin in pin_list: - self.combine_adjacent_pins(pin) + #for pin in pin_list: + # self.combine_adjacent_pins(pin) #print_time("Combine pins",datetime.now(), start_time) #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 + # Separate any adjacent grids of differing net names that overlap # Must be done before enclosing pins #start_time = datetime.now() - self.separate_adjacent_pins(self.supply_rail_space_width) + self.separate_adjacent_pins(0) #print_time("Separate pins",datetime.now(), start_time) # For debug #self.separate_adjacent_pins(1) @@ -201,7 +201,7 @@ class router(router_tech): """ Find pins that have adjacent routing tracks and merge them into a single pin_group. The pins themselves may not be touching, but - enclose_pis in the next step will ensure they are touching. + enclose_pins in the next step will ensure they are touching. """ debug.info(1,"Combining adjacent pins for {}.".format(pin_name)) # Find all adjacencies @@ -279,48 +279,50 @@ class router(router_tech): 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]): - # FIgXME: Use separation distance and edge grids only - grids_g1, grids_g2 = pg1.adjacent_grids(pg2, separation) + adj_grids = pg1.adjacent_grids(pg2, separation) # These should have the same length, so... - if len(grids_g1)>0: - debug.info(3,"Adjacent grids {0} {1} {2} {3}".format(index1,grids_g1,index2,grids_g2)) - self.remove_adjacent_grid(pg1, grids_g1, pg2, grids_g2) + if len(adj_grids)>0: + debug.info(2,"Adjacent grids {0} {1} adj={2}".format(index1,index2,adj_grids)) + self.remove_adjacent_grid(pg1, pg2, adj_grids) - def remove_adjacent_grid(self, pg1, grids1, pg2, grids2): + def remove_adjacent_grid(self, pg1, pg2, adj_grids): """ Remove one of the adjacent grids in a heuristic manner. + This will try to keep the groups similar sized by removing from the bigger group. """ - # 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(3,"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(3,"Removing {} from smaller {}".format(str(smaller_grids), smaller)) - smaller.grids.difference_update(smaller_grids) - self.blocked_grids.update(smaller_grids) - return + for adj in adj_grids: - # 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) + + # If the adjacent grids are a subset of the secondary grids (i.e. not necessary) + # remove them from each + if adj in bigger.secondary_grids: + debug.info(2,"Removing {} from bigger secondary {}".format(adj, bigger)) + bigger.grids.remove(adj) + bigger.secondary_grids.remove(adj) + self.blocked_grids.add(adj) + elif adj in smaller.secondary_grids: + debug.info(2,"Removing {} from smaller secondary {}".format(adj, smaller)) + smaller.grids.remove(adj) + smaller.secondary_grids.remove(adj) + self.blocked_grids.add(adj) + else: + # If we couldn't remove from a secondary grid, we must remove from the primary + # grid of at least one pin + if adj in bigger.grids: + debug.info(2,"Removing {} from bigger primary {}".format(adj, bigger)) + bigger.grids.remove(adj) + elif adj in smaller.grids: + debug.info(2,"Removing {} from smaller primary {}".format(adj, smaller)) + smaller.grids.remove(adj) + @@ -515,36 +517,24 @@ class router(router_tech): # scale the size bigger to include neaby tracks ll=ll.scale(self.track_factor).floor() ur=ur.scale(self.track_factor).ceil() - #print(pin) + # Keep tabs on tracks with sufficient and insufficient overlap sufficient_list = set() insufficient_list = set() - + zindex=self.get_zindex(pin.layer_num) for x in range(int(ll[0])+expansion,int(ur[0])+1+expansion): for y in range(int(ll[1]+expansion),int(ur[1])+1+expansion): - (full_overlap,partial_overlap) = self.convert_pin_coord_to_tracks(pin, vector3d(x,y,zindex)) + (full_overlap, partial_overlap) = self.convert_pin_coord_to_tracks(pin, vector3d(x,y,zindex)) if full_overlap: sufficient_list.update([full_overlap]) if partial_overlap: insufficient_list.update([partial_overlap]) - debug.info(4,"Converting [ {0} , {1} ] full={2} partial={3}".format(x,y, full_overlap, partial_overlap)) + debug.info(2,"Converting [ {0} , {1} ] full={2}".format(x,y, full_overlap)) - # Remove the blocked grids - sufficient_list.difference_update(self.blocked_grids) - insufficient_list.difference_update(self.blocked_grids) - - if len(sufficient_list)>0: - return sufficient_list - elif expansion==0 and len(insufficient_list)>0: - best_pin = self.get_all_offgrid_pin(pin, insufficient_list) - #print(best_pin) - return best_pin - elif expansion>0: - nearest_pin = self.get_furthest_offgrid_pin(pin, insufficient_list) - return nearest_pin - else: - return set() + # Return all grids with any potential overlap (sufficient or not) + return (sufficient_list,insufficient_list) + def get_all_offgrid_pin(self, pin, insufficient_list): """ @@ -622,26 +612,32 @@ class router(router_tech): def convert_pin_coord_to_tracks(self, pin, coord): """ - Given a pin and a track coordinate, determine if the pin overlaps enough. - If it does, add additional metal to make the pin "on grid". - If it doesn't, add it to the blocked grid list. + Return all tracks that an inflated pin overlaps """ - (width, spacing) = self.get_layer_width_space(coord.z) - - # This is the rectangle if we put a pin in the center of the track - track_pin = self.convert_track_to_pin(coord) - overlap_length = pin.overlap_length(track_pin) + # This is using the full track shape rather than a single track pin shape + # because we will later patch a connector if there isn't overlap. + track_pin = self.convert_track_to_shape_pin(coord) + + # This is the normal pin inflated by a minimum design rule + inflated_pin = pin_layout(pin.name, pin.inflate(0.5*self.track_space), pin.layer) - debug.info(3,"Check overlap: {0} {1} . {2} = {3}".format(coord, pin.rect, track_pin, overlap_length)) - # If it overlaps by more than the min width DRC, we can just use the track - if overlap_length==math.inf or snap_val_to_grid(overlap_length) >= snap_val_to_grid(width): - debug.info(3," Overlap: {0} >? {1}".format(overlap_length,spacing)) - return (coord, None) - # Otherwise, keep track of the partial overlap grids in case we need to patch it later. + overlap_length = pin.overlap_length(track_pin) + debug.info(2,"Check overlap: {0} {1} . {2} = {3}".format(coord, pin.rect, track_pin, overlap_length)) + inflated_overlap_length = inflated_pin.overlap_length(track_pin) + debug.info(2,"Check overlap: {0} {1} . {2} = {3}".format(coord, inflated_pin.rect, track_pin, inflated_overlap_length)) + + # If it overlaps with the pin, it is sufficient + if overlap_length==math.inf or overlap_length > 0: + debug.info(2," Overlap: {0} >? {1}".format(overlap_length,0)) + return (coord,None) + # If it overlaps with the inflated pin, it is partial + elif inflated_overlap_length==math.inf or inflated_overlap_length > 0: + debug.info(2," Partial overlap: {0} >? {1}".format(inflated_overlap_length,0)) + return (None,coord) else: - debug.info(3," Partial/no overlap: {0} >? {1}".format(overlap_length,spacing)) - return (None, coord) + debug.info(2," No overlap: {0} {1}".format(overlap_length,0)) + return (None,None) @@ -653,25 +649,34 @@ class router(router_tech): Convert a grid point into a rectangle shape that is centered track in the track and leaves half a DRC space in each direction. """ - # space depends on which layer it is - if self.get_layer(track[2])==self.horiz_layer_name: - space = 0.5*self.horiz_layer_spacing - else: - space = 0.5*self.vert_layer_spacing - # calculate lower left - x = track.x*self.track_width - 0.5*self.track_width + space - y = track.y*self.track_width - 0.5*self.track_width + space + x = track.x*self.track_width - 0.5*self.track_width + 0.5*self.track_space + y = track.y*self.track_width - 0.5*self.track_width + 0.5*self.track_space ll = snap_to_grid(vector(x,y)) # calculate upper right - x = track.x*self.track_width + 0.5*self.track_width - space - y = track.y*self.track_width + 0.5*self.track_width - space + x = track.x*self.track_width + 0.5*self.track_width - 0.5*self.track_space + y = track.y*self.track_width + 0.5*self.track_width - 0.5*self.track_space ur = snap_to_grid(vector(x,y)) p = pin_layout("", [ll, ur], self.get_layer(track[2])) return p + def convert_track_to_shape_pin(self, track): + """ + Convert a grid point into a rectangle shape that occupies the entire centered + track. + """ + # to scale coordinates to tracks + x = track[0]*self.track_width - 0.5*self.track_width + y = track[1]*self.track_width - 0.5*self.track_width + # offset lowest corner object to to (-track halo,-track halo) + ll = snap_to_grid(vector(x,y)) + ur = snap_to_grid(ll + vector(self.track_width,self.track_width)) + + p = pin_layout("", [ll, ur], self.get_layer(track[2])) + return p + def convert_track_to_shape(self, track): """ Convert a grid point into a rectangle shape that occupies the entire centered @@ -686,6 +691,23 @@ class router(router_tech): return [ll,ur] + def convert_track_to_inflated_pin(self, track): + """ + Convert a grid point into a rectangle shape that is inflated by a half DRC space. + """ + # calculate lower left + x = track.x*self.track_width - 0.5*self.track_width - 0.5*self.track_space + y = track.y*self.track_width - 0.5*self.track_width - 0.5*self.track_space + ll = snap_to_grid(vector(x,y)) + + # calculate upper right + x = track.x*self.track_width + 0.5*self.track_width + 0.5*self.track_space + y = track.y*self.track_width + 0.5*self.track_width + 0.5*self.track_space + ur = snap_to_grid(vector(x,y)) + + p = pin_layout("", [ll, ur], self.get_layer(track[2])) + return p + def analyze_pins(self, pin_name): """ Analyze the shapes of a pin and combine them into groups which are connected. @@ -877,8 +899,6 @@ class router(router_tech): Enclose the tracks from ll to ur in a single rectangle that meets the track DRC rules. """ - # 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 @@ -894,35 +914,35 @@ class router(router_tech): 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. - """ + # 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. + # """ - # Find the pin enclosure of the whole track shape (ignoring DRCs) - (abs_ll,unused) = self.convert_track_to_shape(ll) - (unused,abs_ur) = self.convert_track_to_shape(ur) + # # Find the pin enclosure of the whole track shape (ignoring DRCs) + # (abs_ll,unused) = self.convert_track_to_shape(ll) + # (unused,abs_ur) = self.convert_track_to_shape(ur) - # Get the layer information - x_distance = abs(abs_ll.x-abs_ur.x) - y_distance = abs(abs_ll.y-abs_ur.y) - shape_width = min(x_distance, y_distance) - shape_length = max(x_distance, y_distance) + # # Get the layer information + # x_distance = abs(abs_ll.x-abs_ur.x) + # y_distance = abs(abs_ll.y-abs_ur.y) + # shape_width = min(x_distance, y_distance) + # shape_length = max(x_distance, y_distance) - # 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) + # # Get the DRC rule for the grid dimensions + # (width, space) = self.get_supply_layer_width_space(zindex) + # 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 + spacing - new_ur = abs_ur - spacing - pin = pin_layout(name, [new_ll, new_ur], layer) + # 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 + spacing + # new_ur = abs_ur - spacing + # pin = pin_layout(name, [new_ll, new_ur], layer) - return pin + # return pin def contract_path(self,path): @@ -963,8 +983,7 @@ class router(router_tech): self.add_route(path) path_set = grid_utils.flatten_set(path) - inflated_path = grid_utils.inflate_set(path_set,self.supply_rail_space_width) - self.path_blockages.append(inflated_path) + self.path_blockages.append(path_set) else: self.write_debug_gds("failed_route.gds") # clean up so we can try a reroute diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index 94c4268a..a565201d 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -3,48 +3,62 @@ from contact import contact from pin_group import pin_group from vector import vector import debug +import math class router_tech: """ This is a class to hold the router tech constants. """ - def __init__(self, layers): + def __init__(self, layers, rail_track_width): """ 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.rail_track_width = rail_track_width + (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)) + max_via_size = max(via_connect.width,via_connect.height) + + self.horiz_layer_number = layer[self.horiz_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 + if self.rail_track_width>1: + (self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_supply_layer_width_space(1) + (self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_supply_layer_width_space(0) + # For supplies, we will make the wire wider than the vias + self.vert_layer_minwidth = max(self.vert_layer_minwidth, max_via_size) + self.horiz_layer_minwidth = max(self.horiz_layer_minwidth, max_via_size) + + self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing + self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing + + else: + (self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_layer_width_space(1) + (self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_layer_width_space(0) + + self.horiz_track_width = max_via_size + self.horiz_layer_spacing + self.vert_track_width = 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)) - + debug.info(1,"Track width: {:.3f}".format(self.track_width)) + self.track_space = max(self.horiz_layer_spacing,self.vert_layer_spacing) + debug.info(1,"Track spacing: {:.3f}".format(self.track_space)) + self.track_wire = self.track_width - self.track_space + debug.info(1,"Wire width: {:.3f}".format(self.track_wire)) + self.track_widths = vector([self.track_width] * 2) self.track_factor = vector([1/self.track_width] * 2) - debug.info(2,"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] - - + debug.info(2,"Track factor: {}".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_wire, 1, self.track_wire] + def get_zindex(self,layer_num): if layer_num==self.horiz_layer_number: return 0 @@ -76,4 +90,24 @@ class router_tech: return (min_width,min_spacing) + + def get_supply_layer_width_space(self, zindex): + """ + These are the width and spacing of a supply layer given a supply rail + of the given number of min wire widths. + """ + 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_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) + + min_width = drc("minwidth_{0}".format(layer_name), self.rail_track_width*min_wire_width, math.inf) + min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.rail_track_width*min_wire_width, math.inf) + + return (min_width,min_spacing) + + diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index a56b1a42..e5c8dd7d 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -24,17 +24,16 @@ class supply_router(router): 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) + # Power rail width in minimum wire widths + self.rail_track_width = 3 + + router.__init__(self, layers, design, gds_filename, self.rail_track_width) # The list of supply rails (grid sets) that may be routed self.supply_rails = {} - self.supply_rail_wires = {} # This is the same as above but as a sigle set for the all the rails self.supply_rail_tracks = {} - self.supply_rail_wire_tracks = {} - # Power rail width in grid units. - self.rail_track_width = 2 @@ -116,9 +115,7 @@ class supply_router(router): debug.info(1,"Routing simple overlap pins for {0}".format(pin_name)) # These are the wire tracks - wire_tracks = self.supply_rail_wire_tracks[pin_name] - # These are the wire and space tracks - supply_tracks = self.supply_rail_tracks[pin_name] + wire_tracks = self.supply_rail_tracks[pin_name] for pg in self.pin_groups[pin_name]: if pg.is_routed(): @@ -129,13 +126,10 @@ class supply_router(router): if len(overlap_grids)>0: pg.set_routed() continue - - # Else, if we overlap some of the space track, we can patch it with an enclosure - common_set = supply_tracks & pg.grids - if len(common_set)>0: - pg.create_simple_overlap_enclosure(common_set) - pg.add_enclosure(self.cell) + # Else, if we overlap some of the space track, we can patch it with an enclosure + #pg.create_simple_overlap_enclosure(pg.grids) + #pg.add_enclosure(self.cell) @@ -146,7 +140,7 @@ class supply_router(router): NOTE: It is still possible though unlikely that there are disconnected groups of rails. """ - all_rails = self.supply_rail_wires[name] + all_rails = self.supply_rails[name] connections = set() via_areas = [] @@ -186,8 +180,8 @@ class supply_router(router): # the indices to determine a rail is connected to another # the overlap area for placement of a via overlap = new_r1 & new_r2 - if len(overlap) >= self.supply_rail_wire_width**2: - debug.info(3,"Via overlap {0} {1} {2}".format(len(overlap),self.supply_rail_wire_width**2,overlap)) + if len(overlap) >= 1: + debug.info(3,"Via overlap {0} {1}".format(len(overlap),overlap)) connections.update([i1,i2]) via_areas.append(overlap) @@ -196,7 +190,7 @@ class supply_router(router): ll = grid_utils.get_lower_left(area) ur = grid_utils.get_upper_right(area) center = (ll + ur).scale(0.5,0.5,0) - self.add_via(center,self.rail_track_width) + self.add_via(center,1) # Determien which indices were not connected to anything above missing_indices = set([x for x in range(len(self.supply_rails[name]))]) @@ -209,7 +203,6 @@ class supply_router(router): ur = grid_utils.get_upper_right(all_rails[rail_index]) debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur)) self.supply_rails[name].pop(rail_index) - self.supply_rail_wires[name].pop(rail_index) # Make the supply rails into a big giant set of grids for easy blockages. # Must be done after we determine which ones are connected. @@ -226,7 +219,7 @@ class supply_router(router): ll = grid_utils.get_lower_left(rail) ur = grid_utils.get_upper_right(rail) z = ll.z - pin = self.compute_wide_enclosure(ll, ur, z, name) + pin = self.compute_pin_enclosure(ll, ur, z, name) debug.info(2,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin)) self.cell.add_layout_pin(text=name, layer=pin.layer, @@ -242,33 +235,30 @@ class supply_router(router): self.max_yoffset = self.rg.ur.y self.max_xoffset = self.rg.ur.x - # Longest length is conservative - rail_length = max(self.max_yoffset,self.max_xoffset) - # Convert the number of tracks to dimensions to get the design rule spacing - rail_width = self.track_width*self.rail_track_width + # # Longest length is conservative + # rail_length = max(self.max_yoffset,self.max_xoffset) + # # Convert the number of tracks to dimensions to get the design rule spacing + # rail_width = self.track_width*self.rail_track_width - # Get the conservative width and spacing of the top rails - (horizontal_width, horizontal_space) = self.get_layer_width_space(0, rail_width, rail_length) - (vertical_width, vertical_space) = self.get_layer_width_space(1, rail_width, rail_length) - width = max(horizontal_width, vertical_width) - space = max(horizontal_space, vertical_space) + # # Get the conservative width and spacing of the top rails + # (horizontal_width, horizontal_space) = self.get_supply_layer_width_space(0) + # (vertical_width, vertical_space) = self.get_supply_layer_width_space(1) + # width = max(horizontal_width, vertical_width) + # space = max(horizontal_space, vertical_space) - # This is the supply rail pitch in terms of routing grids - # i.e. a rail of self.rail_track_width needs this many tracks including - # space - track_pitch = self.rail_track_width*width + space + # track_pitch = 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)) + # # 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 - self.supply_rail_space_width = math.floor(0.5*total_space) - debug.info(1,"Rail space tracks: {} (on both sides)".format(self.supply_rail_space_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 + # self.supply_rail_space_width = math.floor(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): @@ -279,14 +269,13 @@ class supply_router(router): """ self.supply_rails[name]=[] - self.supply_rail_wires[name]=[] - start_offset = supply_number*self.supply_rail_width + start_offset = supply_number # Horizontal supply rails - for offset in range(start_offset, self.max_yoffset, 2*self.supply_rail_width): + for offset in range(start_offset, self.max_yoffset, 2): # Seed the function at the location with the given width - wave = [vector3d(0,offset+i,0) for i in range(self.supply_rail_width)] + wave = [vector3d(0,offset,0)] # While we can keep expanding east in this horizontal track while wave and wave[0].x < self.max_xoffset: added_rail = self.find_supply_rail(name, wave, direction.EAST) @@ -299,9 +288,9 @@ class supply_router(router): # Vertical supply rails max_offset = self.rg.ur.x - for offset in range(start_offset, self.max_xoffset, 2*self.supply_rail_width): + for offset in range(start_offset, self.max_xoffset, 2): # Seed the function at the location with the given width - wave = [vector3d(offset+i,0,1) for i in range(self.supply_rail_width)] + wave = [vector3d(offset,0,1)] # While we can keep expanding north in this vertical track while wave and wave[0].y < self.max_yoffset: added_rail = self.find_supply_rail(name, wave, direction.NORTH) @@ -378,11 +367,6 @@ class supply_router(router): if len(wave_path)>=4*self.rail_track_width: grid_set = wave_path.get_grids() self.supply_rails[name].append(grid_set) - - start_wire_index = self.supply_rail_space_width - end_wire_index = self.supply_rail_width - self.supply_rail_space_width - wire_set = wave_path.get_wire_grids(start_wire_index,end_wire_index) - self.supply_rail_wires[name].append(wire_set) return True return False @@ -417,10 +401,6 @@ class supply_router(router): rail_set.update(rail) self.supply_rail_tracks[pin_name] = rail_set - wire_set = set() - for rail in self.supply_rail_wires[pin_name]: - wire_set.update(rail) - self.supply_rail_wire_tracks[pin_name] = wire_set def route_pins_to_rails(self, pin_name): @@ -465,7 +445,7 @@ class supply_router(router): """ debug.info(4,"Add supply rail target {}".format(pin_name)) # Add the wire itself as the target - self.rg.set_target(self.supply_rail_wire_tracks[pin_name]) + self.rg.set_target(self.supply_rail_tracks[pin_name]) # But unblock all the rail tracks including the space self.rg.set_blocked(self.supply_rail_tracks[pin_name],False) diff --git a/compiler/router/tests/10_supply_grid_test.py b/compiler/router/tests/10_supply_grid_test.py index ef9a1be3..7258ab40 100755 --- a/compiler/router/tests/10_supply_grid_test.py +++ b/compiler/router/tests/10_supply_grid_test.py @@ -22,6 +22,9 @@ class no_blockages_test(openram_test): if False: from control_logic import control_logic cell = control_logic(16) + layer_stack =("metal3","via3","metal4") + rtr=router(layer_stack, cell) + self.assertTrue(rtr.route()) else: from sram import sram from sram_config import sram_config @@ -33,9 +36,6 @@ class no_blockages_test(openram_test): sram = sram(c, "sram1") cell = sram.s - layer_stack =("metal3","via3","metal4") - rtr=router(layer_stack, cell) - self.assertTrue(rtr.route()) self.local_check(cell,True) # fails if there are any DRC errors on any cells diff --git a/compiler/tests/20_psram_1bank_2mux_test.py b/compiler/tests/20_psram_1bank_2mux_test.py index bf5b9585..4f7bcd7b 100755 --- a/compiler/tests/20_psram_1bank_2mux_test.py +++ b/compiler/tests/20_psram_1bank_2mux_test.py @@ -11,7 +11,7 @@ import globals from globals import OPTS import debug -@unittest.skip("SKIPPING 20_psram_1bank_2mux_test, wide metal supply routing error") +#@unittest.skip("SKIPPING 20_psram_1bank_2mux_test, wide metal supply routing error") class psram_1bank_2mux_test(openram_test): def runTest(self): diff --git a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py index 69a8def5..27afb066 100755 --- a/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_2mux_1rw_1r_test.py @@ -29,13 +29,13 @@ class sram_1bank_2mux_1rw_1r_test(openram_test): num_banks=1) c.words_per_row=2 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True) diff --git a/compiler/tests/20_sram_1bank_2mux_test.py b/compiler/tests/20_sram_1bank_2mux_test.py index 769c7a51..89e55aa1 100755 --- a/compiler/tests/20_sram_1bank_2mux_test.py +++ b/compiler/tests/20_sram_1bank_2mux_test.py @@ -23,13 +23,13 @@ class sram_1bank_2mux_test(openram_test): num_banks=1) c.words_per_row=2 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True) diff --git a/compiler/tests/20_sram_1bank_4mux_test.py b/compiler/tests/20_sram_1bank_4mux_test.py index 2370b411..0f7ac4cb 100755 --- a/compiler/tests/20_sram_1bank_4mux_test.py +++ b/compiler/tests/20_sram_1bank_4mux_test.py @@ -23,13 +23,13 @@ class sram_1bank_4mux_test(openram_test): num_banks=1) c.words_per_row=4 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True) diff --git a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py index 75e14d3c..9e1a0d51 100755 --- a/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_8mux_1rw_1r_test.py @@ -29,13 +29,13 @@ class sram_1bank_8mux_1rw_1r_test(openram_test): num_banks=1) c.words_per_row=8 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True) diff --git a/compiler/tests/20_sram_1bank_8mux_test.py b/compiler/tests/20_sram_1bank_8mux_test.py index f2ad684a..a7525f1e 100755 --- a/compiler/tests/20_sram_1bank_8mux_test.py +++ b/compiler/tests/20_sram_1bank_8mux_test.py @@ -23,13 +23,13 @@ class sram_1bank_8mux_test(openram_test): num_banks=1) c.words_per_row=8 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True) diff --git a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py index 631e0309..6b878b91 100755 --- a/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py +++ b/compiler/tests/20_sram_1bank_nomux_1rw_1r_test.py @@ -29,13 +29,13 @@ class sram_1bank_nomux_1rw_1r_test(openram_test): num_banks=1) c.words_per_row=1 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True) diff --git a/compiler/tests/20_sram_1bank_nomux_test.py b/compiler/tests/20_sram_1bank_nomux_test.py index 2afa9ae8..d7683251 100755 --- a/compiler/tests/20_sram_1bank_nomux_test.py +++ b/compiler/tests/20_sram_1bank_nomux_test.py @@ -23,13 +23,13 @@ class sram_1bank_nomux_test(openram_test): num_banks=1) c.words_per_row=1 - debug.info(1, "Layout test for {}rw,{}r,{}w psram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, - OPTS.num_r_ports, - OPTS.num_w_ports, - c.word_size, - c.num_words, - c.words_per_row, - c.num_banks)) + debug.info(1, "Layout test for {}rw,{}r,{}w sram with {} bit words, {} words, {} words per row, {} banks".format(OPTS.num_rw_ports, + OPTS.num_r_ports, + OPTS.num_w_ports, + c.word_size, + c.num_words, + c.words_per_row, + c.num_banks)) a = sram(c, "sram") self.local_check(a, final_verification=True)