import sys import gdsMill from tech import drc,GDS from tech import layer as techlayer 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,print_time from pprint import pformat import grid_utils from datetime import datetime 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. It populates blockages on a grid class. """ def __init__(self, layers, design, gds_filename=None): """ 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 # This isn't efficient, but easy for now #start_time = datetime.now() if not gds_filename: gds_filename = OPTS.openram_temp+"temp.gds" self.cell.gds_write(gds_filename) # Load the gds file and read in all the shapes self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) self.reader = gdsMill.Gds2reader(self.layout) self.reader.loadFromFile(gds_filename) self.top_name = self.layout.rootStructureName #print_time("GDS read",datetime.now(), start_time) ### The pin data structures # A map of pin names to a set of pin_layout structures self.pins = {} # 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() # A map of pin names to a list of pin groups self.pin_groups = {} ### The blockage data structures # A list of metal shapes (using the same pin_layout structure) that are not pins but blockages. self.blockages=[] # The corresponding set of blocked grids for above pin shapes self.blocked_grids = set() ### The routed data structures # A list of paths that have been "routed" self.paths = [] # A list of path blockages (they might be expanded for wide metal DRC) self.path_blockages = [] # The boundary will determine the limits to the size of the routing grid self.boundary = self.layout.measureBoundary(self.top_name) # These must be un-indexed to get rid of the matrix type self.ll = vector(self.boundary[0][0], self.boundary[0][1]) # Pad the UR by a few tracks to add an extra rail possibly self.ur = vector(self.boundary[1][0], self.boundary[1][1]) + self.track_widths.scale(5,5) def clear_pins(self): """ Convert the routed path to blockages. Keep the other blockages unchanged. """ self.pins = {} self.all_pins = set() self.pin_groups = {} # DO NOT clear the blockages as these don't change self.rg.reinit() def set_top(self,top_name): """ If we want to route something besides the top-level cell.""" self.top_name = top_name def is_wave(self,path): """ Determines if this is a multi-track width wave (True) or a normal route (False) """ return len(path[0])>1 def retrieve_pins(self,pin_name): """ Retrieve the pin shapes on metal 3 from the layout. """ debug.info(2,"Retrieving pins for {}.".format(pin_name)) shape_list=self.layout.getAllPinShapes(str(pin_name)) pin_set = set() for shape in shape_list: (layer,boundary)=shape # GDSMill boundaries are in (left, bottom, right, top) order # so repack and snap to the grid ll = vector(boundary[0],boundary[1]).snap_to_grid() ur = vector(boundary[2],boundary[3]).snap_to_grid() rect = [ll,ur] pin = pin_layout(pin_name, rect, layer) pin_set.add(pin) debug.check(len(pin_set)>0,"Did not find any pin shapes for {0}.".format(str(pin_name))) self.pins[pin_name] = pin_set self.all_pins.update(pin_set) for pin in self.pins[pin_name]: debug.info(3,"Retrieved pin {}".format(str(pin))) def find_pins(self,pin_name): """ Finds the pin shapes and converts to tracks. Pin can either be a label or a location,layer pair: [[x,y],layer]. """ debug.info(1,"Finding pins for {}.".format(pin_name)) #start_time = datetime.now() self.retrieve_pins(pin_name) #print_time("Retrieved pins",datetime.now(), start_time) #start_time = datetime.now() self.analyze_pins(pin_name) #print_time("Analyzed pins",datetime.now(), start_time) def find_blockages(self): """ Iterate through all the layers and write the obstacles to the routing grid. This doesn't consider whether the obstacles will be pins or not. They get reset later if they are not actually a blockage. """ debug.info(1,"Finding blockages.") for layer in [self.vert_layer_number,self.horiz_layer_number]: self.retrieve_blockages(layer) def find_pins_and_blockages(self, pin_list): """ Find the pins and blockages in the design """ # This finds the pin shapes and sorts them into "groups" that are connected # This must come before the blockages, so we can not count the pins themselves # as blockages. for pin in pin_list: self.find_pins(pin) # This will get all shapes as blockages and convert to grid units # This ignores shapes that were pins #start_time = datetime.now() self.find_blockages() #print_time("Find blockags",datetime.now(), start_time) # Convert the blockages to grid units #start_time = datetime.now() self.convert_blockages() #print_time("Find blockags",datetime.now(), start_time) # This will convert the pins to grid units # It must be done after blockages to ensure no DRCs between expanded pins and blocked grids #start_time = datetime.now() for pin in pin_list: self.convert_pins(pin) #print_time("Convert pins",datetime.now(), start_time) #start_time = datetime.now() 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 # Must be done before enclosing pins #start_time = datetime.now() self.separate_adjacent_pins(self.supply_rail_space_width) #print_time("Separate pins",datetime.now(), start_time) # For debug #self.separate_adjacent_pins(1) # Enclose the continguous grid units in a metal rectangle to fix some DRCs #start_time = datetime.now() self.enclose_pins() #print_time("Enclose pins",datetime.now(), start_time) #self.write_debug_gds("debug_enclose_pins.gds",stop_program=True) def combine_adjacent_pins(self, pin_name): """ 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. """ debug.info(1,"Combining adjacent pins for {}.".format(pin_name)) # Find all adjacencies adjacent_pins = {} for index1,pg1 in enumerate(self.pin_groups[pin_name]): for index2,pg2 in enumerate(self.pin_groups[pin_name]): # Cannot combine with yourself, also don't repeat if index1<=index2: continue # Combine if at least 1 grid cell is adjacent if pg1.adjacent(pg2): if not index1 in adjacent_pins.keys(): adjacent_pins[index1] = set([index2]) else: adjacent_pins[index1].add(index2) # Make a list of indices to ensure every group gets in the new set all_indices = set([x for x in range(len(self.pin_groups[pin_name]))]) # Now reconstruct the new groups new_pin_groups = [] for index1,index2_set in adjacent_pins.items(): # Remove the indices if they are added to the new set all_indices.discard(index1) all_indices.difference_update(index2_set) # Create the combined group starting with the first item combined = self.pin_groups[pin_name][index1] # Add all of the other items that overlapped for index2 in index2_set: pg = self.pin_groups[pin_name][index2] combined.add_group(pg) debug.info(3,"Combining {0} {1}:".format(pin_name, index2)) debug.info(3, " {0}\n {1}".format(combined.pins, pg.pins)) debug.info(3," --> {0}\n {1}".format(combined.pins,combined.grids)) new_pin_groups.append(combined) # Add the pin groups that weren't added to the new set for index in all_indices: new_pin_groups.append(self.pin_groups[pin_name][index]) old_size = len(self.pin_groups[pin_name]) # Use the new pin group! self.pin_groups[pin_name] = new_pin_groups removed_pairs = old_size - len(new_pin_groups) debug.info(1, "Combined {0} pin groups for {1}".format(removed_pairs,pin_name)) return removed_pairs 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). """ debug.info(1,"Separating adjacent pins.") # 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. """ 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) # 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) 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(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 # 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. Names is a list of pins to add as a blockage. """ debug.info(3,"Preparing blockages.") # Start fresh. Not the best for run-time, but simpler. self.clear_blockages() # This adds the initial blockges of the design #print("BLOCKING:",self.blocked_grids) self.set_blockages(self.blocked_grids,True) # Block all of the supply rails (some will be unblocked if they're a target) self.set_supply_rail_blocked(True) # Block all of the pin components (some will be unblocked if they're a source/target) # Also block the previous routes 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) blockage_grids = {y for x in self.pin_groups[name] for y in x.blockages} self.set_blockages(blockage_grids,True) # FIXME: These duplicate a bit of work # These are the paths that have already been routed. self.set_blockages(self.path_blockages) # 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 blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids} self.set_blockages(blockage_grids,False) # def translate_coordinates(self, coord, mirr, angle, xyShift): # """ # Calculate coordinates after flip, rotate, and shift # """ # coordinate = [] # for item in coord: # x = (item[0]*math.cos(angle)-item[1]*mirr*math.sin(angle)+xyShift[0]) # y = (item[0]*math.sin(angle)+item[1]*mirr*math.cos(angle)+xyShift[1]) # coordinate += [(x, y)] # return coordinate def convert_shape_to_units(self, shape): """ Scale a shape (two vector list) to user units """ unit_factor = [GDS["unit"][0]] * 2 ll=shape[0].scale(unit_factor) ur=shape[1].scale(unit_factor) return [ll,ur] def min_max_coord(self, coord): """ Find the lowest and highest corner of a Rectangle """ coordinate = [] minx = min(coord[0][0], coord[1][0], coord[2][0], coord[3][0]) maxx = max(coord[0][0], coord[1][0], coord[2][0], coord[3][0]) miny = min(coord[0][1], coord[1][1], coord[2][1], coord[3][1]) maxy = max(coord[0][1], coord[1][1], coord[2][1], coord[3][1]) coordinate += [vector(minx, miny)] coordinate += [vector(maxx, maxy)] return coordinate def get_inertia(self,p0,p1): """ Sets the direction based on the previous direction we came from. """ # direction (index) of movement if p0.x!=p1.x: return 0 elif p0.y!=p1.y: return 1 else: # z direction return 2 def clear_blockages(self): """ Clear all blockages on the grid. """ debug.info(3,"Clearing all blockages") self.rg.clear_blockages() def set_blockages(self, blockages, value=True): """ Flag the blockages in the grid """ self.rg.set_blocked(blockages, value) def get_blockage_tracks(self, ll, ur, z): debug.info(3,"Converting blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) block_list = [] for x in range(int(ll[0]),int(ur[0])+1): for y in range(int(ll[1]),int(ur[1])+1): block_list.append(vector3d(x,y,z)) return set(block_list) def convert_blockage(self, blockage): """ Convert a pin layout blockage shape to routing grid tracks. """ # Inflate the blockage by half a spacing rule [ll,ur]=self.convert_blockage_to_tracks(blockage.inflate()) zlayer = self.get_zindex(blockage.layer_num) blockage_tracks = self.get_blockage_tracks(ll, ur, zlayer) return blockage_tracks def convert_blockages(self): """ Convert blockages to grid tracks. """ debug.info(1,"Converting blockages.") for blockage in self.blockages: debug.info(3,"Converting blockage {}".format(str(blockage))) blockage_list = self.convert_blockage(blockage) self.blocked_grids.update(blockage_list) def retrieve_blockages(self, layer_num): """ Recursive find boundaries as blockages to the routing grid. """ shapes = self.layout.getAllShapes(layer_num) for boundary in shapes: ll = vector(boundary[0],boundary[1]) ur = vector(boundary[2],boundary[3]) rect = [ll,ur] new_pin = pin_layout("blockage{}".format(len(self.blockages)),rect,layer_num) # If there is a rectangle that is the same in the pins, it isn't a blockage! if new_pin not in self.all_pins: self.blockages.append(new_pin) def convert_point_to_units(self, p): """ Convert a path set of tracks to center line path. """ pt = vector3d(p) pt = pt.scale(self.track_widths[0],self.track_widths[1],1) return pt def convert_wave_to_units(self, wave): """ Convert a wave to a set of center points """ return [self.convert_point_to_units(i) for i in wave] def convert_blockage_to_tracks(self, shape): """ Convert a rectangular blockage shape into track units. """ (ll,ur) = shape ll = snap_to_grid(ll) ur = snap_to_grid(ur) # to scale coordinates to tracks debug.info(3,"Converting [ {0} , {1} ]".format(ll,ur)) old_ll = ll old_ur = ur ll=ll.scale(self.track_factor) ur=ur.scale(self.track_factor) # We can round since we are using inflated shapes # and the track points are at the center ll = ll.round() ur = ur.round() # if ll[0]<45 and ll[0]>35 and ll[1]<5 and ll[1]>-5: # debug.info(0,"Converting [ {0} , {1} ]".format(old_ll,old_ur)) # debug.info(0,"Converted [ {0} , {1} ]".format(ll,ur)) # pin=self.convert_track_to_shape(ll) # debug.info(0,"Pin {}".format(pin)) return [ll,ur] def convert_pin_to_tracks(self, pin_name, pin, expansion=0): """ Convert a rectangular pin shape into a list of track locations,layers. If no pins are "on-grid" (i.e. sufficient overlap) it makes the one with most overlap if it is not blocked. If expansion>0, expamine areas beyond the current pin when it is blocked. """ (ll,ur) = pin.rect debug.info(3,"Converting pin [ {0} , {1} ]".format(ll,ur)) # 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)) 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)) # 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() def get_all_offgrid_pin(self, pin, insufficient_list): """ Find a list of all pins with some overlap. """ #print("INSUFFICIENT LIST",insufficient_list) # Find the coordinate with the most overlap any_overlap = set() for coord in insufficient_list: full_pin = self.convert_track_to_pin(coord) # Compute the overlap with that rectangle overlap_rect=pin.compute_overlap(full_pin) # Determine the max x or y overlap max_overlap = max(overlap_rect) if max_overlap>0: any_overlap.update([coord]) return any_overlap def get_best_offgrid_pin(self, pin, insufficient_list): """ Find a list of the single pin with the most overlap. """ #print("INSUFFICIENT LIST",insufficient_list) # Find the coordinate with the most overlap best_coord = None best_overlap = -math.inf for coord in insufficient_list: full_pin = self.convert_track_to_pin(coord) # Compute the overlap with that rectangle overlap_rect=pin.compute_overlap(full_pin) # Determine the min x or y overlap min_overlap = min(overlap_rect) if min_overlap>best_overlap: best_overlap=min_overlap best_coord=coord return set([best_coord]) def get_furthest_offgrid_pin(self, pin, insufficient_list): """ Get a grid cell that is the furthest from the blocked grids. """ #print("INSUFFICIENT LIST",insufficient_list) # Find the coordinate with the most overlap best_coord = None best_dist = math.inf for coord in insufficient_list: min_dist = grid_utils.distance_set(coord, self.blocked_grids) if min_dist= 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. else: debug.info(3," Partial/no overlap: {0} >? {1}".format(overlap_length,spacing)) return (None, coord) def convert_track_to_pin(self, track): """ 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 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 ur = snap_to_grid(vector(x,y)) 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 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)) return [ll,ur] def analyze_pins(self, pin_name): """ Analyze the shapes of a pin and combine them into groups which are connected. """ debug.info(2,"Analyzing pin groups for {}.".format(pin_name)) pin_set = self.pins[pin_name] # Put each pin in an equivalence class of it's own equiv_classes = [set([x]) for x in pin_set] def combine_classes(equiv_classes): for class1 in equiv_classes: for class2 in equiv_classes: if class1 == class2: continue # Compare each pin in each class, # and if any overlap, update equiv_classes to include the combined the class for p1 in class1: for p2 in class2: if p1.overlaps(p2): combined_class = class1 | class2 equiv_classes.remove(class1) equiv_classes.remove(class2) equiv_classes.append(combined_class) return(equiv_classes) return(equiv_classes) old_length = math.inf while (len(equiv_classes)1: newpath.append(path[-1]) return newpath def run_router(self, detour_scale): """ This assumes the blockages, source, and target are all set up. """ # returns the path in tracks (path,cost) = self.rg.route(detour_scale) if path: debug.info(2,"Found path: cost={0} ".format(cost)) debug.info(3,str(path)) self.paths.append(path) 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) else: self.write_debug_gds("failed_route.gds") # clean up so we can try a reroute self.rg.reinit() return False return True def annotate_pin_and_tracks(self, pin, tracks): """" Annotate some shapes for debug purposes """ debug.info(0,"Annotating\n pin {0}\n tracks {1}".format(pin,tracks)) for coord in tracks: (ll,ur) = self.convert_track_to_shape(coord) self.cell.add_rect(layer="text", offset=ll, width=ur[0]-ll[0], height=ur[1]-ll[1]) (ll,ur) = self.convert_track_to_pin(coord).rect self.cell.add_rect(layer="boundary", offset=ll, width=ur[0]-ll[0], height=ur[1]-ll[1]) (ll,ur) = pin.rect self.cell.add_rect(layer="text", offset=ll, width=ur[0]-ll[0], height=ur[1]-ll[1]) def write_debug_gds(self, gds_name="debug_route.gds", stop_program=True): """ Write out a GDS file with the routing grid and search information annotated on it. """ debug.info(0,"Writing annotated router gds file to {}".format(gds_name)) self.add_router_info() self.cell.gds_write(gds_name) 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 t!=None: 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 self.cell.add_label(text=str(t), layer="text", offset=type_off) t=self.rg.map[g].get_cost() partial_track=vector(self.track_width/6.0,0) if t!=None: if g[2]==1: # Upper layer is right label type_off=off+partial_track else: # Lower layer is left label type_off=off-partial_track 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 the boundary layer for debugging purposes. This can only be called once or the labels will overlap. """ debug.info(0,"Adding router info") show_blockages = False show_blockage_grids = False show_enclosures = 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)) (ll,ur) = blockage.inflate() self.cell.add_rect(layer="text", offset=ll, width=ur.x-ll.x, height=ur.y-ll.y) if show_blockage_grids: self.set_blockages(self.blocked_grids,True) grid_keys=self.rg.map.keys() for g in grid_keys: self.annotate_grid(g) 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", offset=pin.ll(), width=pin.width(), height=pin.height()) # FIXME: This should be replaced with vector.snap_to_grid at some point def snap_to_grid(offset): """ Changes the coodrinate to match the grid settings """ xoff = snap_val_to_grid(offset[0]) yoff = snap_val_to_grid(offset[1]) return vector(xoff, yoff) def snap_val_to_grid(x): grid = drc("grid") xgrid = int(round(round((x / grid), 2), 0)) xoff = xgrid * grid return xoff