diff --git a/compiler/base/contact.py b/compiler/base/contact.py index 8def5bfd..1a5785aa 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -55,7 +55,7 @@ class contact(hierarchy_design.hierarchy_design): # Module does not have pins, but has empty pin list. self.pins = [] self.create_layout() - + def create_layout(self): self.setup_layers() @@ -65,8 +65,10 @@ class contact(hierarchy_design.hierarchy_design): self.create_second_layer_enclosure() self.create_nitride_cut_enclosure() - self.height = max(obj.offset.y + obj.height for obj in self.objs) - self.width = max(obj.offset.x + obj.width for obj in self.objs) + self.height = max(self.first_layer_position.y + self.first_layer_height, + self.second_layer_position.y + self.second_layer_height) + self.width = max(self.first_layer_position.x + self.first_layer_width, + self.second_layer_position.x + self.second_layer_width) # Do not include the select layer in the height/width if self.implant_type and self.well_type: @@ -83,7 +85,7 @@ class contact(hierarchy_design.hierarchy_design): self.second_layer_name = second_layer # Contacts will have unique per first layer - if via_layer in tech.layer.keys(): + if via_layer in tech.layer: self.via_layer_name = via_layer elif via_layer == "contact": if first_layer in ("active", "poly"): @@ -171,7 +173,7 @@ class contact(hierarchy_design.hierarchy_design): def create_nitride_cut_enclosure(self): """ Special layer that encloses poly contacts in some processes """ # Check if there is a special poly nitride cut layer - if "npc" not in tech.layer.keys(): + if "npc" not in tech.layer: return # Only add for poly layers @@ -224,13 +226,18 @@ class contact(hierarchy_design.hierarchy_design): offset=implant_position, width=implant_width, height=implant_height) - well_position = self.first_layer_position - [drc("well_enclose_active")] * 2 - well_width = self.first_layer_width + 2 * drc("well_enclose_active") - well_height = self.first_layer_height + 2 * drc("well_enclose_active") - self.add_rect(layer="{}well".format(self.well_type), - offset=well_position, - width=well_width, - height=well_height) + + # Optionally implant well if layer exists + well_layer = "{}well".format(self.well_type) + if well_layer in tech.layer: + well_enclose_active = drc(well_layer + "_enclose_active") + well_position = self.first_layer_position - [well_enclose_active] * 2 + well_width = self.first_layer_width + 2 * well_enclose_active + well_height = self.first_layer_height + 2 * well_enclose_active + self.add_rect(layer=well_layer, + offset=well_position, + width=well_width, + height=well_height) def analytical_power(self, corner, load): """ Get total power of a module """ diff --git a/compiler/base/design.py b/compiler/base/design.py index 59772a02..9b595b5f 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -63,8 +63,8 @@ class design(hierarchy_design): contact1 = getattr(contact, layer1 + layer2) max_contact = max(contact1.width, contact1.height) - layer1_space = getattr(self, layer1+"_space") - layer2_space = getattr(self, layer2+"_space") + layer1_space = getattr(self, layer1 + "_space") + layer2_space = getattr(self, layer2 + "_space") pitch = max_contact + max(layer1_space, layer2_space) return pitch @@ -83,7 +83,7 @@ class design(hierarchy_design): if match.group(1) == "active_contact": setattr(self, "contact_width", drc(match.group(0))) else: - setattr(self, match.group(1)+"_width", drc(match.group(0))) + setattr(self, match.group(1) + "_width", drc(match.group(0))) # Single layer area rules match = re.search(r"minarea_(.*)", rule) @@ -93,10 +93,10 @@ class design(hierarchy_design): # Single layer spacing rules match = re.search(r"(.*)_to_(.*)", rule) if match and match.group(1) == match.group(2): - setattr(self, match.group(1)+"_space", drc(match.group(0))) + setattr(self, match.group(1) + "_space", drc(match.group(0))) elif match and match.group(1) != match.group(2): if match.group(2) == "poly_active": - setattr(self, match.group(1)+"_to_contact", + setattr(self, match.group(1) + "_to_contact", drc(match.group(0))) else: setattr(self, match.group(0), drc(match.group(0))) diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 46cfe7c7..f354cf02 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -69,7 +69,9 @@ class geometry: """ Transform with offset, mirror and rotation to get the absolute pin location. We must then re-find the ll and ur. The master is the cell instance. """ if OPTS.netlist_only: + self.boundary = [vector(0,0), vector(0,0)] return + (ll, ur) = [vector(0, 0), vector(self.width, self.height)] if mirror == "MX": diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 27bf60eb..a1b18ef3 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -5,7 +5,6 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import itertools import collections import geometry import gdsMill @@ -16,7 +15,7 @@ import os from globals import OPTS from vector import vector from pin_layout import pin_layout -import lef + class layout(): """ @@ -32,7 +31,7 @@ class layout(): self.name = name self.width = None self.height = None - self.boundary = None + self.bounding_box = None self.insts = [] # Holds module/cell layout instances self.objs = [] # Holds all other objects (labels, geometries, etc) self.pin_map = {} # Holds name->pin_layout map for all pins @@ -57,64 +56,116 @@ class layout(): """ if (inv_num % 2 == 0): - base_offset=vector(x_offset, inv_num * height) + base_offset = vector(x_offset, inv_num * height) y_dir = 1 else: - # we lose a rail after every 2 gates - base_offset=vector(x_offset, (inv_num+1) * height - (inv_num%2)*drc["minwidth_m1"]) + # we lose a rail after every 2 gates + base_offset = vector(x_offset, + (inv_num + 1) * height - \ + (inv_num % 2) * drc["minwidth_m1"]) y_dir = -1 - return (base_offset,y_dir) - + return (base_offset, y_dir) def find_lowest_coords(self): - """Finds the lowest set of 2d cartesian coordinates within - this layout""" + """ + Finds the lowest set of 2d cartesian coordinates within + this layout + """ - if len(self.objs)>0: - lowestx1 = min(obj.lx() for obj in self.objs if obj.name!="label") - lowesty1 = min(obj.by() for obj in self.objs if obj.name!="label") + if len(self.objs) > 0: + lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label") + lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label") else: - lowestx1=lowesty1=None - if len(self.insts)>0: + lowestx1 = lowesty1 = None + if len(self.insts) > 0: lowestx2 = min(inst.lx() for inst in self.insts) lowesty2 = min(inst.by() for inst in self.insts) else: - lowestx2=lowesty2=None + lowestx2 = lowesty2 = None - if lowestx1==None and lowestx2==None: + if lowestx1 == None and lowestx2 == None: return None - elif lowestx1==None: - return vector(lowestx2,lowesty2) - elif lowestx2==None: - return vector(lowestx1,lowesty1) + elif lowestx1 == None: + return vector(lowestx2, lowesty2) + elif lowestx2 == None: + return vector(lowestx1, lowesty1) else: return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2)) def find_highest_coords(self): - """Finds the highest set of 2d cartesian coordinates within - this layout""" - - if len(self.objs)>0: - highestx1 = max(obj.rx() for obj in self.objs if obj.name!="label") - highesty1 = max(obj.uy() for obj in self.objs if obj.name!="label") + """ + Finds the highest set of 2d cartesian coordinates within + this layout + """ + if len(self.objs) > 0: + highestx1 = max(obj.rx() for obj in self.objs if obj.name != "label") + highesty1 = max(obj.uy() for obj in self.objs if obj.name != "label") else: - highestx1=highesty1=None - if len(self.insts)>0: + highestx1 = highesty1 = None + if len(self.insts) > 0: highestx2 = max(inst.rx() for inst in self.insts) highesty2 = max(inst.uy() for inst in self.insts) else: - highestx2=highesty2=None - if highestx1==None and highestx2==None: + highestx2 = highesty2 = None + if highestx1 == None and highestx2 == None: return None - elif highestx1==None: - return vector(highestx2,highesty2) - elif highestx2==None: - return vector(highestx1,highesty1) + elif highestx1 == None: + return vector(highestx2, highesty2) + elif highestx2 == None: + return vector(highestx1, highesty1) else: - return vector(max(highestx1, highestx2), max(highesty1, highesty2)) + return vector(max(highestx1, highestx2), + max(highesty1, highesty2)) + def find_highest_layer_coords(self, layer): + """ + Finds the highest set of 2d cartesian coordinates within + this layout on a layer + """ + # Only consider the layer not the purpose for now + layerNumber = techlayer[layer][0] + try: + highestx = max(obj.rx() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + highestx =0 + try: + highesty = max(obj.uy() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + highesty = 0 + for inst in self.insts: + # This really should be rotated/mirrored etc... + subcoord = inst.mod.find_highest_layer_coords(layer) + inst.offset + highestx = max(highestx, subcoord.x) + highesty = max(highesty, subcoord.y) + + return vector(highestx, highesty) + + def find_lowest_layer_coords(self, layer): + """ + Finds the highest set of 2d cartesian coordinates within + this layout on a layer + """ + # Only consider the layer not the purpose for now + layerNumber = techlayer[layer][0] + try: + lowestx = min(obj.lx() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + lowestx = 0 + try: + lowesty = min(obj.by() for obj in self.objs if obj.layerNumber == layerNumber) + except ValueError: + lowesty = 0 + + for inst in self.insts: + # This really should be rotated/mirrored etc... + subcoord = inst.mod.find_lowest_layer_coords(layer) + inst.offset + lowestx = min(lowestx, subcoord.x) + lowesty = min(lowesty, subcoord.y) + + return vector(lowestx, lowesty) + def translate_all(self, offset): """ Translates all objects, instances, and pins by the given (x,y) offset @@ -132,8 +183,8 @@ class layout(): for pin in pin_list: pin.rect = [pin.ll() - offset, pin.ur() - offset] - def add_inst(self, name, mod, offset=[0,0], mirror="R0",rotate=0): - """Adds an instance of a mod to this module""" + def add_inst(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): + """ Adds an instance of a mod to this module """ self.insts.append(geometry.instance(name, mod, offset, mirror, rotate)) debug.info(3, "adding instance {}".format(self.insts[-1])) # This is commented out for runtime reasons @@ -141,7 +192,7 @@ class layout(): return self.insts[-1] def get_inst(self, name): - """Retrieve an instance by name""" + """ Retrieve an instance by name """ for inst in self.insts: if inst.name == name: return inst @@ -152,9 +203,9 @@ class layout(): Adds a rectangle on a given layer,offset with width and height """ if not width: - width=drc["minwidth_{}".format(layer)] + width = drc["minwidth_{}".format(layer)] if not height: - height=drc["minwidth_{}".format(layer)] + height = drc["minwidth_{}".format(layer)] # negative layers indicate "unused" layers in a given technology lpp = techlayer[layer] if lpp[0] >= 0: @@ -164,58 +215,63 @@ class layout(): 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 + Adds a rectangle on a given layer at the center + point with width and height """ if not width: - width=drc["minwidth_{}".format(layer)] + width = drc["minwidth_{}".format(layer)] if not height: - height=drc["minwidth_{}".format(layer)] + height = drc["minwidth_{}".format(layer)] # negative layers indicate "unused" layers in a given technology lpp = techlayer[layer] - corrected_offset = offset - vector(0.5*width,0.5*height) + corrected_offset = offset - vector(0.5 * width, 0.5 * height) if lpp[0] >= 0: - self.objs.append(geometry.rectangle(lpp, corrected_offset, width, height)) + self.objs.append(geometry.rectangle(lpp, + corrected_offset, + width, + height)) return self.objs[-1] return None - def add_segment_center(self, layer, start, end): - """ - 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) - elif start.x!=end.x: - offset = vector(0,0.5*minwidth_layer) - return self.add_rect(layer,start-offset,end.x-start.x,minwidth_layer) + 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) + elif start.x != end.x: + offset = vector(0, 0.5 * minwidth_layer) + return self.add_rect(layer, + start-offset, + end.x-start.x, + minwidth_layer) else: - offset = vector(0.5*minwidth_layer,0) - return self.add_rect(layer,start-offset,minwidth_layer,end.y-start.y) - - + offset = vector(0.5 * minwidth_layer, 0) + return self.add_rect(layer, + start-offset, + minwidth_layer, + end.y-start.y) 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: + if len(self.pin_map[text]) > 1: debug.error("Should use a pin iterator since more than one pin {}".format(text),-1) # If we have one pin, return it and not the list. # Otherwise, should use get_pins() any_pin = next(iter(self.pin_map[text])) return any_pin - except Exception as e: - #print e + except Exception: self.gds_write("missing_pin.gds") debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1) - - 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] @@ -223,87 +279,97 @@ class layout(): return set() def get_pin_names(self): - """ + """ Return a pin list of all pins """ return self.pin_map.keys() def copy_layout_pin(self, instance, pin_name, new_name=""): - """ - Create a copied version of the layout pin at the current level. - You can optionally rename the pin to a new name. """ - pins=instance.get_pins(pin_name) + Create a copied version of the layout pin at the current level. + You can optionally rename the pin to a new name. + """ + pins = instance.get_pins(pin_name) - debug.check(len(pins)>0,"Could not find pin {}".format(pin_name)) + debug.check(len(pins) > 0, + "Could not find pin {}".format(pin_name)) for pin in pins: - if new_name=="": + if new_name == "": new_name = pin.name - self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height()) + self.add_layout_pin(new_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) def copy_layout_pins(self, instance, prefix=""): - """ + """ Create a copied version of the layout pin at the current level. - You can optionally rename the pin to a new name. + You can optionally rename the pin to a new name. """ for pin_name in self.pin_map.keys(): self.copy_layout_pin(instance, pin_name, prefix+pin_name) 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.") + debug.check(start.x == end.x or start.y == end.y, + "Cannot have a non-manhatten layout pin.") minwidth_layer = drc["minwidth_{}".format(layer)] # one of these will be zero - width = max(start.x,end.x) - min(start.x,end.x) - height = max(start.y,end.y) - min(start.y,end.y) - ll_offset = vector(min(start.x,end.x),min(start.y,end.y)) + width = max(start.x, end.x) - min(start.x, end.x) + height = max(start.y, end.y) - min(start.y, end.y) + ll_offset = vector(min(start.x, end.x), min(start.y, end.y)) # Shift it down 1/2 a width in the 0 dimension - if height==0: - ll_offset -= vector(0,0.5*minwidth_layer) - if width==0: - ll_offset -= vector(0.5*minwidth_layer,0) + if height == 0: + ll_offset -= vector(0, 0.5 * minwidth_layer) + if width == 0: + ll_offset -= vector(0.5 * minwidth_layer, 0) # This makes sure it is long enough, but also it is not 0 width! - height = max(minwidth_layer,height) - width = max(minwidth_layer,width) + height = max(minwidth_layer, height) + width = max(minwidth_layer, width) - - return self.add_layout_pin(text, layer, ll_offset, width, height) + return self.add_layout_pin(text, + layer, + ll_offset, + width, + height) def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None): """ Creates a path like pin with center-line convention """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] - ll_offset = offset - vector(0.5*width,0.5*height) + ll_offset = offset - vector(0.5 * width, 0.5 * height) return self.add_layout_pin(text, layer, ll_offset, width, height) - def remove_layout_pin(self, text): """ Delete a labeled pin (or all pins of the same name) """ - self.pin_map[text]=set() + self.pin_map[text] = set() def add_layout_pin(self, text, layer, offset, width=None, height=None): """ - Create a labeled pin + Create a labeled pin """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] - new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer) + new_pin = pin_layout(text, + [offset, offset+vector(width, height)], + layer) try: # Check if there's a duplicate! @@ -324,42 +390,41 @@ class layout(): in LVS. """ if not width: - width=drc["minwidth_{0}".format(layer)] + width = drc["minwidth_{0}".format(layer)] if not height: - height=drc["minwidth_{0}".format(layer)] + height = drc["minwidth_{0}".format(layer)] self.add_rect(layer=layer, offset=offset, width=width, height=height) self.add_label(text=text, layer=layer, - offset=offset+vector(0.5*width,0.5*height)) + offset=offset + vector(0.5 * width, + 0.5 * height)) - - def add_label(self, text, layer, offset=[0,0],zoom=-1): + def add_label(self, text, layer, offset=[0, 0], zoom=-1): """Adds a text label on the given layer,offset, and zoom level""" # negative layers indicate "unused" layers in a given technology - debug.info(5,"add label " + str(text) + " " + layer + " " + str(offset)) + debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset)) lpp = techlayer[layer] if lpp[0] >= 0: self.objs.append(geometry.label(text, lpp, offset, zoom)) return self.objs[-1] return None - def add_path(self, layer, coordinates, width=None): """Connects a routing path on given layer,coordinates,width.""" - debug.info(4,"add path " + str(layer) + " " + str(coordinates)) + debug.info(4, "add path " + str(layer) + " " + str(coordinates)) import wire_path # NOTE: (UNTESTED) add_path(...) is currently not used # negative layers indicate "unused" layers in a given technology - #lpp = techlayer[layer] - #if lpp[0] >= 0: - # self.objs.append(geometry.path(lpp, coordinates, width)) + # lpp = techlayer[layer] + # if lpp[0] >= 0: + # self.objs.append(geometry.path(lpp, coordinates, width)) wire_path.wire_path(obj=self, - layer=layer, - position_list=coordinates, + layer=layer, + position_list=coordinates, width=width) def add_route(self, layers, coordinates, layer_widths): @@ -369,21 +434,21 @@ class layout(): the coordinates. """ import route - debug.info(4,"add route " + str(layers) + " " + str(coordinates)) + debug.info(4, "add route " + str(layers) + " " + str(coordinates)) # add an instance of our path that breaks down into rectangles and contacts route.route(obj=self, - layer_stack=layers, + layer_stack=layers, path=coordinates, layer_widths=layer_widths) - def add_wire(self, layers, coordinates): """Connects a routing path on given layer,coordinates,width. The layers are the (horizontal, via, vertical). """ import wire - # add an instance of our path that breaks down into rectangles and contacts + # add an instance of our path that breaks down + # into rectangles and contacts wire.wire(obj=self, - layer_stack=layers, + layer_stack=layers, position_list=coordinates) def get_preferred_direction(self, layer): @@ -394,7 +459,7 @@ class layout(): def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None): """ Add a three layer via structure. """ - if directions==None: + if not directions: directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) @@ -406,17 +471,20 @@ class layout(): implant_type=implant_type, well_type=well_type) self.add_mod(via) - inst=self.add_inst(name=via.name, - mod=via, - offset=offset) + inst = self.add_inst(name=via.name, + mod=via, + offset=offset) # We don't model the logical connectivity of wires/paths self.connect_inst([]) return inst def add_via_center(self, layers, offset, directions=None, size=[1,1], implant_type=None, well_type=None): - """ Add a three layer via structure by the center coordinate accounting for mirroring and rotation. """ + """ + Add a three layer via structure by the center coordinate + accounting for mirroring and rotation. + """ - if directions==None: + if not directions: directions = (self.get_preferred_direction(layers[0]), self.get_preferred_direction(layers[2])) @@ -430,12 +498,13 @@ class layout(): height = via.height width = via.width - corrected_offset = offset + vector(-0.5*width,-0.5*height) + corrected_offset = offset + vector(-0.5 * width, + -0.5 * height) self.add_mod(via) - inst=self.add_inst(name=via.name, - mod=via, - offset=corrected_offset) + inst = self.add_inst(name=via.name, + mod=via, + offset=corrected_offset) # We don't model the logical connectivity of wires/paths self.connect_inst([]) return inst @@ -447,22 +516,20 @@ class layout(): mults=mults, tx_type=tx_type) self.add_mod(mos) - inst=self.add_inst(name=mos.name, - mod=mos, - offset=offset, - mirror=mirror, - rotate=rotate) + inst = self.add_inst(name=mos.name, + mod=mos, + offset=offset, + mirror=mirror, + rotate=rotate) return inst - - def gds_read(self): """Reads a GDSII file in the library and checks if it exists Otherwise, start a new layout for dynamic generation.""" # This must be done for netlist only mode too if os.path.isfile(self.gds_file): - self.is_library_cell=True + self.is_library_cell = True if OPTS.netlist_only: self.gds = None @@ -480,7 +547,7 @@ class layout(): def print_gds(self, gds_file=None): """Print the gds file (not the vlsi class) to the terminal """ - if gds_file == None: + if not gds_file: gds_file = self.gds_file debug.info(4, "Printing {}".format(gds_file)) arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"]) @@ -507,11 +574,12 @@ class layout(): # If it's not a premade cell # and we didn't add our own boundary, # we should add a boundary just for DRC in some technologies - if not self.is_library_cell and not self.boundary: + if not self.is_library_cell and not self.bounding_box: # If there is a boundary layer, and we didn't create one, add one. if "stdc" in techlayer.keys(): boundary_layer = "stdc" - boundary = [self.find_lowest_coords(), self.find_highest_coords()] + boundary = [self.find_lowest_coords(), + self.find_highest_coords()] height = boundary[1][1] - boundary[0][1] width = boundary[1][0] - boundary[0][0] (layer_number, layer_purpose) = techlayer[boundary_layer] @@ -522,7 +590,6 @@ class layout(): height=height, center=False) debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary)) - self.visited.append(self.name) @@ -549,20 +616,21 @@ class layout(): # populates the xyTree data structure for gds # self.gds.prepareForWrite() writer.writeToFile(gds_name) - debug.info(3, "Done writing to {}".format(gds_name)) + debug.info(3, "Done writing to {}".format(gds_name)) def get_boundary(self): """ Return the lower-left and upper-right coordinates of boundary """ # This assumes nothing spans outside of the width and height! - return [vector(0,0), vector(self.width, self.height)] + return [vector(0, 0), vector(self.width, self.height)] #return [self.find_lowest_coords(), self.find_highest_coords()] def get_blockages(self, layer, top_level=False): - """ - Write all of the obstacles in the current (and children) modules to the lef file + """ + Write all of the obstacles in the current (and children) + modules to the lef file. Do not write the pins since they aren't obstructions. """ - if type(layer)==str: + if type(layer) == str: lpp = techlayer[layer] else: lpp = layer @@ -634,22 +702,21 @@ class layout(): vertical=False, make_pins=False) - def create_bus(self, layer, pitch, offset, names, length, vertical, make_pins): """ Create a horizontal or vertical bus. It can be either just rectangles, or actual - layout pins. It returns an map of line center line positions indexed by name. + layout pins. It returns an map of line center line positions indexed by name. The other coordinate is a 0 since the bus provides a range. TODO: combine with channel router. """ # half minwidth so we can return the center line offsets - half_minwidth = 0.5*drc["minwidth_{}".format(layer)] + half_minwidth = 0.5 * drc["minwidth_{}".format(layer)] line_positions = {} if vertical: for i in range(len(names)): - line_offset = offset + vector(i*pitch,0) + line_offset = offset + vector(i * pitch, 0) if make_pins: self.add_layout_pin(text=names[i], layer=layer, @@ -659,11 +726,13 @@ class layout(): self.add_rect(layer=layer, offset=line_offset, height=length) - # Make this the center of the rail - line_positions[names[i]]=line_offset+vector(half_minwidth,0.5*length) + # Make this the center of the rail + line_positions[names[i]] = line_offset + vector(half_minwidth, + 0.5 * length) else: for i in range(len(names)): - line_offset = offset + vector(0,i*pitch + half_minwidth) + line_offset = offset + vector(0, + i * pitch + half_minwidth) if make_pins: self.add_layout_pin(text=names[i], layer=layer, @@ -674,7 +743,8 @@ class layout(): offset=line_offset, width=length) # Make this the center of the rail - line_positions[names[i]]=line_offset+vector(0.5*length,half_minwidth) + line_positions[names[i]] = line_offset + vector(0.5 * length, + half_minwidth) return line_positions @@ -684,17 +754,18 @@ class layout(): self.connect_bus(mapping, inst, bus_offsets, layer_stack, True) def connect_vertical_bus(self, mapping, inst, bus_offsets, - layer_stack=("m1", "via1", "m2")): + layer_stack=("m1", "via1", "m2")): """ Vertical version of connect_bus. """ self.connect_bus(mapping, inst, bus_offsets, layer_stack, False) def connect_bus(self, mapping, inst, bus_offsets, layer_stack, horizontal): - """ - Connect a mapping of pin -> name for a bus. This could be - replaced with a channel router in the future. - NOTE: This has only really been tested with point-to-point connections (not multiple pins on a net). """ - (horizontal_layer, via_layer, vertical_layer)=layer_stack + Connect a mapping of pin -> name for a bus. This could be + replaced with a channel router in the future. + NOTE: This has only really been tested with point-to-point + connections (not multiple pins on a net). + """ + (horizontal_layer, via_layer, vertical_layer) = layer_stack if horizontal: route_layer = vertical_layer else: @@ -712,37 +783,48 @@ class layout(): # left/right then up/down mid_pos = vector(bus_pos.x, pin_pos.y) - self.add_wire(layer_stack,[bus_pos, mid_pos, pin_pos]) + self.add_wire(layer_stack, + [bus_pos, mid_pos, pin_pos]) # Connect to the pin on the instances with a via if it is # not on the right layer if pin.layer != route_layer: self.add_via_center(layers=layer_stack, offset=pin_pos) - # FIXME: output pins tend to not be rotate, but supply pins are. Make consistent? + # FIXME: output pins tend to not be rotate, + # but supply pins are. Make consistent? - - # We only need a via if they happened to align perfectly # so the add_wire didn't add a via if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x): self.add_via_center(layers=layer_stack, offset=bus_pos, rotate=90) + def get_layer_pitch(self, layer): """ Return the track pitch on a given layer """ - if layer=="m1": - return (self.m1_pitch,self.m1_pitch-self.m1_space,self.m1_space) - elif layer=="m2": - return (self.m2_pitch,self.m2_pitch-self.m2_space,self.m2_space) - elif layer=="m3": - return (self.m3_pitch,self.m3_pitch-self.m3_space,self.m3_space) - elif layer=="m4": + if layer == "m1": + return (self.m1_pitch, + self.m1_pitch - self.m1_space, + self.m1_space) + elif layer == "m2": + return (self.m2_pitch, + self.m2_pitch - self.m2_space, + self.m2_space) + elif layer == "m3": + return (self.m3_pitch, + self.m3_pitch - self.m3_space, + self.m3_space) + elif layer == "m4": from tech import layer as tech_layer if "m4" in tech_layer: - return (self.m3_pitch,self.m3_pitch-self.m4_space,self.m4_space) + return (self.m3_pitch, + self.m3_pitch - self.m4_space, + self.m4_space) else: - return (self.m3_pitch,self.m3_pitch-self.m3_space,self.m3_space) + return (self.m3_pitch, + self.m3_pitch - self.m3_space, + self.m3_space) else: debug.error("Cannot find layer pitch.") @@ -752,18 +834,20 @@ class layout(): layer_stack, pitch): """ - Create a trunk route for all pins with the trunk located at the given y offset. + Create a trunk route for all pins with + the trunk located at the given y offset. """ max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog if max_x-min_x <= pitch: - - half_layer_width = 0.5*drc["minwidth_{0}".format(self.vertical_layer)] + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] # Add the horizontal trunk on the vertical layer! - self.add_path(self.vertical_layer,[vector(min_x-half_layer_width,trunk_offset.y), vector(max_x+half_layer_width,trunk_offset.y)]) + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) # Route each pin to the trunk for pin in pins: @@ -772,8 +856,9 @@ class layout(): self.add_path(self.vertical_layer, [pin.center(), mid]) else: # Add the horizontal trunk - self.add_path(self.horizontal_layer,[vector(min_x,trunk_offset.y), vector(max_x,trunk_offset.y)]) - trunk_mid = vector(0.5*(max_x+min_x),trunk_offset.y) + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) # Route each pin to the trunk for pin in pins: @@ -788,7 +873,8 @@ class layout(): layer_stack, pitch): """ - Create a trunk route for all pins with the trunk located at the given x offset. + Create a trunk route for all pins with the + trunk located at the given x offset. """ max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) @@ -796,10 +882,12 @@ class layout(): # if we are less than a pitch, just create a non-preferred layer jog if max_y-min_y <= pitch: - half_layer_width = 0.5*drc["minwidth_{0}".format(self.horizontal_layer)] + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] # Add the vertical trunk on the horizontal layer! - self.add_path(self.horizontal_layer,[vector(trunk_offset.x,min_y-half_layer_width), vector(trunk_offset.x,max_y+half_layer_width)]) + self.add_path(self.horizontal_layer, + [vector(trunk_offset.x, min_y - half_layer_width), + vector(trunk_offset.x,max_y + half_layer_width)]) # Route each pin to the trunk for pin in pins: @@ -808,8 +896,9 @@ class layout(): self.add_path(self.horizontal_layer, [pin.center(), mid]) else: # Add the vertical trunk - self.add_path(self.vertical_layer,[vector(trunk_offset.x,min_y), vector(trunk_offset.x,max_y)]) - trunk_mid = vector(trunk_offset.x,0.5*(max_y+min_y),) + self.add_path(self.vertical_layer, + [vector(trunk_offset.x, min_y), + vector(trunk_offset.x, max_y)]) # Route each pin to the trunk for pin in pins: @@ -817,10 +906,9 @@ class layout(): self.add_path(self.horizontal_layer, [pin.center(), mid]) self.add_via_center(layers=layer_stack, offset=mid) - def create_channel_route(self, netlist, - offset, + offset, layer_stack, vertical=False): """ @@ -835,7 +923,7 @@ class layout(): """ Remove the pin from the graph and all conflicts """ - g.pop(pin,None) + g.pop(pin, None) # Remove the pin from all conflicts # FIXME: This is O(n^2), so maybe optimize it. @@ -846,9 +934,9 @@ class layout(): return g def vcg_nets_overlap(net1, net2, vertical, pitch): - """ + """ Check all the pin pairs on two nets and return a pin - overlap if any pin overlaps + overlap if any pin overlaps. """ for pin1 in net1: @@ -861,7 +949,8 @@ class layout(): def vcg_pin_overlap(pin1, pin2, vertical, pitch): """ Check for vertical or horizontal overlap of the two pins """ # FIXME: If the pins are not in a row, this may break. - # However, a top pin shouldn't overlap another top pin, for example, so the + # However, a top pin shouldn't overlap another top pin, + # for example, so the # extra comparison *shouldn't* matter. # Pin 1 must be in the "BOTTOM" set @@ -872,80 +961,93 @@ class layout(): overlaps = (not vertical and x_overlap) or (vertical and y_overlap) return overlaps - if self.get_preferred_direction(layer_stack[0])=="V": + if self.get_preferred_direction(layer_stack[0]) == "V": self.vertical_layer = layer_stack[0] self.horizontal_layer = layer_stack[2] else: self.vertical_layer = layer_stack[2] self.horizontal_layer = layer_stack[0] - (self.vertical_pitch,self.vertical_width,self.vertical_space) = self.get_layer_pitch(self.vertical_layer) - (self.horizontal_pitch,self.horizontal_width,self.horizontal_space) = self.get_layer_pitch(self.horizontal_layer) + layer_stuff = self.get_layer_pitch(self.vertical_layer) + (self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff + layer_stuff = self.get_layer_pitch(self.horizontal_layer) + (self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff - - # FIXME: Must extend this to a horizontal conflict graph too if we want to minimize the + # FIXME: Must extend this to a horizontal conflict graph + # too if we want to minimize the # number of tracks! - #hcg = {} + # hcg = {} - # Initialize the vertical conflict graph (vcg) and make a list of all pins + # Initialize the vertical conflict graph (vcg) + # and make a list of all pins vcg = collections.OrderedDict() # Create names for the nets for the graphs nets = collections.OrderedDict() index = 0 - #print(netlist) + # print(netlist) for pin_list in netlist: - net_name = "n{}".format(index) - index += 1 - nets[net_name] = pin_list + net_name = "n{}".format(index) + index += 1 + nets[net_name] = pin_list # Find the vertical pin conflicts # FIXME: O(n^2) but who cares for now for net_name1 in nets: if net_name1 not in vcg.keys(): - vcg[net_name1]=[] + vcg[net_name1] = [] for net_name2 in nets: if net_name2 not in vcg.keys(): - vcg[net_name2]=[] + vcg[net_name2] = [] # Skip yourself if net_name1 == net_name2: continue - if vertical and vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical, self.vertical_pitch): + if vertical and vcg_nets_overlap(nets[net_name1], + nets[net_name2], + vertical, + self.vertical_pitch): vcg[net_name2].append(net_name1) - elif not vertical and vcg_nets_overlap(nets[net_name1], nets[net_name2], vertical, self.horizontal_pitch): + elif not vertical and vcg_nets_overlap(nets[net_name1], + nets[net_name2], + vertical, + self.horizontal_pitch): vcg[net_name2].append(net_name1) # list of routes to do while vcg: - #from pprint import pformat - #print("VCG:\n",pformat(vcg)) + # from pprint import pformat + # print("VCG:\n",pformat(vcg)) # get a route from conflict graph with empty fanout set - net_name=None - for net_name,conflicts in vcg.items(): - if len(conflicts)==0: - vcg=remove_net_from_graph(net_name,vcg) + net_name = None + for net_name, conflicts in vcg.items(): + if len(conflicts) == 0: + vcg = remove_net_from_graph(net_name, vcg) break else: # FIXME: We don't support cyclic VCGs right now. - debug.error("Cyclic VCG in channel router.",-1) - - + debug.error("Cyclic VCG in channel router.", -1) # These are the pins we'll have to connect pin_list = nets[net_name] - #print("Routing:",net_name,[x.name for x in pin_list]) + # print("Routing:", net_name, [x.name for x in pin_list]) # Remove the net from other constriants in the VCG - vcg=remove_net_from_graph(net_name, vcg) + vcg = remove_net_from_graph(net_name, vcg) - # Add the trunk routes from the bottom up for horizontal or the left to right for vertical + # Add the trunk routes from the bottom up for + # horizontal or the left to right for vertical if vertical: - self.add_vertical_trunk_route(pin_list, offset, layer_stack, self.vertical_pitch) - offset += vector(self.vertical_pitch,0) + self.add_vertical_trunk_route(pin_list, + offset, + layer_stack, + self.vertical_pitch) + offset += vector(self.vertical_pitch, 0) else: - self.add_horizontal_trunk_route(pin_list, offset, layer_stack, self.horizontal_pitch) - offset += vector(0,self.horizontal_pitch) - + self.add_horizontal_trunk_route(pin_list, + offset, + layer_stack, + self.horizontal_pitch) + offset += vector(0, self.horizontal_pitch) def create_vertical_channel_route(self, netlist, offset, layer_stack): """ @@ -959,32 +1061,35 @@ class layout(): """ self.create_channel_route(netlist, offset, layer_stack, vertical=False) - def add_boundary(self, ll=vector(0,0), ur=None): + def add_boundary(self, ll=vector(0, 0), ur=None): """ Add boundary for debugging dimensions """ + if OPTS.netlist_only: + return + if "stdc" in techlayer.keys(): boundary_layer = "stdc" else: boundary_layer = "boundary" - if ur == None: - self.boundary = self.add_rect(layer=boundary_layer, - offset=ll, - height=self.height, - width=self.width) + if not ur: + self.bounding_box = self.add_rect(layer=boundary_layer, + offset=ll, + height=self.height, + width=self.width) else: - self.boundary = self.add_rect(layer=boundary_layer, - offset=ll, - height=ur.y-ll.y, - width=ur.x-ll.x) + self.bounding_box = self.add_rect(layer=boundary_layer, + offset=ll, + height=ur.y-ll.y, + width=ur.x-ll.x) def add_enclosure(self, insts, layer="nwell"): """ Add a layer that surrounds the given instances. Useful for creating wells, for example. Doesn't check for minimum widths or spacings.""" - xmin=insts[0].lx() - ymin=insts[0].by() - xmax=insts[0].rx() - ymax=insts[0].uy() + xmin = insts[0].lx() + ymin = insts[0].by() + xmax = insts[0].rx() + ymax = insts[0].uy() for inst in insts: xmin = min(xmin, inst.lx()) ymin = min(ymin, inst.by()) @@ -992,50 +1097,52 @@ class layout(): ymax = max(ymax, inst.uy()) self.add_rect(layer=layer, - offset=vector(xmin,ymin), + offset=vector(xmin, ymin), width=xmax-xmin, height=ymax-ymin) - def copy_power_pins(self, inst, name): """ - This will copy a power pin if it is on M3. If it is on M1, it will add a power via too. + This will copy a power pin if it is on M3. + If it is on M1, it will add a power via too. """ - pins=inst.get_pins(name) + pins = inst.get_pins(name) for pin in pins: - if pin.layer=="m3": - self.add_layout_pin(name, pin.layer, pin.ll(), pin.width(), pin.height()) - elif pin.layer=="m1": + if pin.layer == "m3": + self.add_layout_pin(name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + elif pin.layer == "m1": self.add_power_pin(name, pin.center()) else: debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name)) - def add_power_pin(self, name, loc, size=[1,1], vertical=False, start_layer="m1"): - """ + def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"): + """ Add a single power pin from M3 down to M1 at the given center location. The starting layer is specified to determine which vias are needed. """ if vertical: - direction=("V","V") + direction = ("V", "V") else: - direction=("H","H") + direction = ("H", "H") - if start_layer=="m1": + if start_layer == "m1": self.add_via_center(layers=self.m1_stack, size=size, offset=loc, directions=direction) - - if start_layer=="m1" or start_layer=="m2": - via=self.add_via_center(layers=self.m2_stack, - size=size, - offset=loc, - directions=direction) - - if start_layer=="m3": + if start_layer == "m1" or start_layer == "m2": + via = self.add_via_center(layers=self.m2_stack, + size=size, + offset=loc, + directions=direction) + if start_layer == "m3": self.add_layout_pin_rect_center(text=name, layer="m3", offset=loc) @@ -1048,10 +1155,11 @@ class layout(): def add_power_ring(self, bbox): """ - Create vdd and gnd power rings around an area of the bounding box argument. Must - have a supply_rail_width and supply_rail_pitch defined as a member variable. - Defines local variables of the left/right/top/bottom vdd/gnd center offsets - for use in other modules.. + Create vdd and gnd power rings around an area of the bounding box + argument. Must have a supply_rail_width and supply_rail_pitch + defined as a member variable. Defines local variables of the + left/right/top/bottom vdd/gnd center offsets for use in other + modules.. """ [ll, ur] = bbox @@ -1061,65 +1169,71 @@ class layout(): width = (ur.x-ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing # LEFT vertical rails - offset = ll + vector(-2*self.supply_rail_pitch, -2*self.supply_rail_pitch) - left_gnd_pin=self.add_layout_pin(text="gnd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) + offset = ll + vector(-2 * self.supply_rail_pitch, + -2 * self.supply_rail_pitch) + left_gnd_pin = self.add_layout_pin(text="gnd", + layer="m2", + offset=offset, + width=self.supply_rail_width, + height=height) + offset = ll + vector(-1 * self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + left_vdd_pin = self.add_layout_pin(text="vdd", + layer="m2", + offset=offset, + width=self.supply_rail_width, + height=height) - offset = ll + vector(-1*self.supply_rail_pitch, -1*self.supply_rail_pitch) - left_vdd_pin=self.add_layout_pin(text="vdd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) - - # RIGHT vertical rails - offset = vector(ur.x,ll.y) + vector(0,-2*self.supply_rail_pitch) + # RIGHT vertical rails + offset = vector(ur.x, ll.y) + vector(0, -2 * self.supply_rail_pitch) right_gnd_pin = self.add_layout_pin(text="gnd", - layer="m2", + layer="m2", offset=offset, width=self.supply_rail_width, height=height) - offset = vector(ur.x,ll.y) + vector(self.supply_rail_pitch,-1*self.supply_rail_pitch) - right_vdd_pin=self.add_layout_pin(text="vdd", - layer="m2", - offset=offset, - width=self.supply_rail_width, - height=height) + offset = vector(ur.x, ll.y) + vector(self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + right_vdd_pin = self.add_layout_pin(text="vdd", + layer="m2", + offset=offset, + width=self.supply_rail_width, + height=height) # BOTTOM horizontal rails - offset = ll + vector(-2*self.supply_rail_pitch, -2*self.supply_rail_pitch) - bottom_gnd_pin=self.add_layout_pin(text="gnd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = ll + vector(-2 * self.supply_rail_pitch, + -2 * self.supply_rail_pitch) + bottom_gnd_pin = self.add_layout_pin(text="gnd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) - offset = ll + vector(-1*self.supply_rail_pitch, -1*self.supply_rail_pitch) - bottom_vdd_pin=self.add_layout_pin(text="vdd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = ll + vector(-1 * self.supply_rail_pitch, + -1 * self.supply_rail_pitch) + bottom_vdd_pin = self.add_layout_pin(text="vdd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) - # TOP horizontal rails - offset = vector(ll.x, ur.y) + vector(-2*self.supply_rail_pitch,0) - top_gnd_pin=self.add_layout_pin(text="gnd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + # TOP horizontal rails + offset = vector(ll.x, ur.y) + vector(-2 * self.supply_rail_pitch, + 0) + top_gnd_pin = self.add_layout_pin(text="gnd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) - offset = vector(ll.x, ur.y) + vector(-1*self.supply_rail_pitch, self.supply_rail_pitch) - top_vdd_pin=self.add_layout_pin(text="vdd", - layer="m1", - offset=offset, - width=width, - height=self.supply_rail_width) + offset = vector(ll.x, ur.y) + vector(-1 * self.supply_rail_pitch, + self.supply_rail_pitch) + top_vdd_pin = self.add_layout_pin(text="vdd", + layer="m1", + offset=offset, + width=width, + height=self.supply_rail_width) # Remember these for connecting things in the design self.left_gnd_x_center = left_gnd_pin.cx() @@ -1132,14 +1246,13 @@ class layout(): self.top_gnd_y_center = top_gnd_pin.cy() self.top_vdd_y_center = top_vdd_pin.cy() - # Find the number of vias for this pitch self.supply_vias = 1 from sram_factory import factory while True: - c=factory.create(module_type="contact", - layer_stack=self.m1_stack, - dimensions=(self.supply_vias, self.supply_vias)) + c = factory.create(module_type="contact", + layer_stack=self.m1_stack, + dimensions=(self.supply_vias, self.supply_vias)) if c.second_layer_width < self.supply_rail_width and c.second_layer_height < self.supply_rail_width: self.supply_vias += 1 else: @@ -1158,13 +1271,15 @@ class layout(): for pt in via_points: self.add_via_center(layers=self.m1_stack, offset=pt, - size = (self.supply_vias, self.supply_vias)) + size=(self.supply_vias, + self.supply_vias)) - - def pdf_write(self, pdf_name): - # NOTE: Currently does not work (Needs further research) - #self.pdf_name = self.name + ".pdf" + """ + Display the layout to a PDF file. + """ + debug.error("NOTE: Currently does not work (Needs further research)") + # self.pdf_name = self.name + ".pdf" debug.info(0, "Writing to {}".format(pdf_name)) pdf = gdsMill.pdfLayout(self.gds) @@ -1184,22 +1299,22 @@ class layout(): def print_attr(self): """Prints a list of attributes for the current layout object""" - debug.info(0, + debug.info(0, "|==============================================================================|") - debug.info(0, + debug.info(0, "|========= LIST OF OBJECTS (Rects) FOR: " + self.name) - debug.info(0, + debug.info(0, "|==============================================================================|") for obj in self.objs: debug.info(0, "layer={0} : offset={1} : size={2}".format(obj.layerNumber, obj.offset, obj.size)) - debug.info(0, + debug.info(0, "|==============================================================================|") - debug.info(0, + debug.info(0, "|========= LIST OF INSTANCES FOR: " + self.name) - debug.info(0, + debug.info(0, "|==============================================================================|") for inst in self.insts: debug.info(0, "name={0} : mod={1} : offset={2}".format(inst.name, diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index d7f6c49e..a057f3e0 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -128,7 +128,14 @@ class pin_layout: max_y = max(max_y, pin.ur().y) self.rect = [vector(min_x, min_y), vector(max_x, max_y)] - + + def fix_minarea(self): + """ + Try to fix minimum area rule. + """ + min_area = drc("{}_minarea".format(self.layer)) + pass + def inflate(self, spacing=None): """ Inflate the rectangle by the spacing (or other rule) diff --git a/compiler/base/vector.py b/compiler/base/vector.py index 6688462d..582b9391 100644 --- a/compiler/base/vector.py +++ b/compiler/base/vector.py @@ -50,7 +50,7 @@ class vector(): else: self.x=float(value[0]) self.y=float(value[1]) - + def __getitem__(self, index): """ override getitem function diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index d809c6f1..4f19c489 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -977,33 +977,36 @@ class pbitcell(bitcell_base.bitcell_base): """ # extend pwell to encompass entire nmos region of the cell up to the # height of the tallest nmos transistor - max_nmos_well_height = max(self.inverter_nmos.cell_well_height, - self.readwrite_nmos.cell_well_height, - self.write_nmos.cell_well_height, - self.read_nmos.cell_well_height) + max_nmos_well_height = max(self.inverter_nmos.well_height, + self.readwrite_nmos.well_height, + self.write_nmos.well_height, + self.read_nmos.well_height) well_height = max_nmos_well_height + self.port_ypos \ - - self.well_enclose_active - self.gnd_position.y - offset = vector(self.leftmost_xpos, self.botmost_ypos) + - self.nwell_enclose_active - self.gnd_position.y + # FIXME fudge factor xpos + well_width = self.width + 2*self.nwell_enclose_active + offset = vector(self.leftmost_xpos - self.nwell_enclose_active, self.botmost_ypos) self.add_rect(layer="pwell", offset=offset, - width=self.width, + width=well_width, height=well_height) # extend nwell to encompass inverter_pmos # calculate offset of the left pmos well inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ - - self.well_enclose_active + - self.nwell_enclose_active inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + self.inverter_gap - self.well_enclose_active + + self.inverter_gap - self.nwell_enclose_active # calculate width of the two combined nwells # calculate height to encompass nimplant connected to vdd well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ - + 2 * self.well_enclose_active + + 2 * self.nwell_enclose_active well_height = self.vdd_position.y - inverter_well_ypos \ - + self.well_enclose_active + drc["minwidth_tx"] + + self.nwell_enclose_active + drc["minwidth_tx"] - offset = [inverter_well_xpos, inverter_well_ypos] + # FIXME fudge factor xpos + offset = [inverter_well_xpos + 2*self.nwell_enclose_active, inverter_well_ypos] self.add_rect(layer="nwell", offset=offset, width=well_width, diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 4527044c..7ed0c9a3 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -333,7 +333,7 @@ class bank(design.design): self.col_addr_bus_width = self.m2_pitch*self.num_col_addr_lines # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclose_active"), + self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), 3*self.m2_pitch) diff --git a/compiler/modules/port_address.py b/compiler/modules/port_address.py index 7f1fdd49..44b0d5a5 100644 --- a/compiler/modules/port_address.py +++ b/compiler/modules/port_address.py @@ -149,7 +149,7 @@ class port_address(design.design): """ # A space for wells or jogging m2 - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclose_active"), + self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), 3*self.m2_pitch) row_decoder_offset = vector(0,0) diff --git a/compiler/modules/port_data.py b/compiler/modules/port_data.py index 33ecdd01..9e7f4048 100644 --- a/compiler/modules/port_data.py +++ b/compiler/modules/port_data.py @@ -212,7 +212,7 @@ class port_data(design.design): # A space for wells or jogging m2 between modules - self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclose_active"), + self.m2_gap = max(2*drc("pwell_to_nwell") + drc("nwell_enclose_active"), 3*self.m2_pitch) diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index 9cb73542..e009d1bb 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -8,7 +8,7 @@ import contact import design import debug -from tech import layer, drc +from tech import layer from vector import vector from globals import OPTS from sram_factory import factory @@ -126,29 +126,35 @@ class pgate(design.design): def extend_wells(self, middle_position): """ Extend the n/p wells to cover whole cell """ + # FIXME: float rounding problem + middle_position = middle_position.snap_to_grid() # Add a rail width to extend the well to the top of the rail - max_y_offset = self.height + 0.5 * self.m1_width - self.nwell_position = middle_position - nwell_height = max_y_offset - middle_position.y - if layer["nwell"]: + nwell_max_offset = max(self.find_highest_layer_coords("nwell").y, + self.height + 0.5 * self.m1_width) + nwell_position = middle_position + nwell_height = nwell_max_offset - middle_position.y + if "nwell" in layer: self.add_rect(layer="nwell", offset=middle_position, width=self.well_width, height=nwell_height) - if layer["vtg"]: + if "vtg" in layer: self.add_rect(layer="vtg", - offset=self.nwell_position, + offset=nwell_position, width=self.well_width, height=nwell_height) - pwell_position = vector(0, -0.5 * self.m1_width) + # Start this half a rail width below the cell + pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y, + -0.5 * self.m1_width) + pwell_position = vector(0, pwell_min_offset) pwell_height = middle_position.y - pwell_position.y - if layer["pwell"]: + if "pwell" in layer: self.add_rect(layer="pwell", offset=pwell_position, width=self.well_width, height=pwell_height) - if layer["vtg"]: + if "vtg" in layer: self.add_rect(layer="vtg", offset=pwell_position, width=self.well_width, @@ -168,7 +174,7 @@ class pgate(design.design): # OR align the active with the top of PMOS active. max_y_offset = self.height + 0.5 * self.m1_width contact_yoffset = min(pmos_pos.y + pmos.active_height - pmos.active_contact.first_layer_height, - max_y_offset - pmos.active_contact.first_layer_height / 2 - self.well_enclose_active) + max_y_offset - pmos.active_contact.first_layer_height / 2 - self.nwell_enclose_active) contact_offset = vector(contact_xoffset, contact_yoffset) # Offset by half a contact in x and y contact_offset += vector(0.5 * pmos.active_contact.first_layer_width, @@ -220,7 +226,7 @@ class pgate(design.design): # Must be at least an well enclosure of active up # from the bottom of the well contact_yoffset = max(nmos_pos.y, - self.well_enclose_active \ + self.nwell_enclose_active \ - nmos.active_contact.first_layer_height / 2) contact_offset = vector(contact_xoffset, contact_yoffset) diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index ce477a83..8a3a8326 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -153,7 +153,7 @@ class pinv(pgate.pgate): # the well width is determined the multi-finger PMOS device width plus # the well contact width and half well enclosure on both sides self.well_width = self.pmos.active_width + self.pmos.active_contact.width \ - + self.active_space + 2*self.well_enclose_active + + self.active_space + 2*self.nwell_enclose_active self.width = self.well_width # Height is an input parameter, so it is not recomputed. @@ -223,7 +223,7 @@ class pinv(pgate.pgate): self.output_pos = vector(0, 0.5 * (pmos_drain_pos.y + nmos_drain_pos.y)) # This will help with the wells - self.well_pos = vector(0, self.nmos_inst.uy()) + self.well_pos = self.output_pos def route_outputs(self): """ diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index dda736d2..8d3605a2 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -100,7 +100,7 @@ class pnand2(pgate.pgate): # Enclosure space on the sides. self.well_width = 2 * self.pmos.active_width + contact.activem1.width \ + 2 * self.active_space \ - + 2 * self.well_enclose_active + + 2 * self.nwell_enclose_active self.width = self.well_width # Height is an input parameter, so it is not recomputed. @@ -171,7 +171,7 @@ class pnand2(pgate.pgate): 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) # This will help with the wells - self.well_pos = vector(0, self.nmos1_inst.uy()) + self.well_pos = self.output_pos def add_well_contacts(self): """ diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 8ccbff2c..bb379db7 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -92,14 +92,11 @@ class pnand3(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. self.well_width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ - + 2 * self.active_space + 2 * self.well_enclose_active \ + + 2 * self.active_space + 2 * self.nwell_enclose_active \ - self.overlap_offset.x self.width = self.well_width # Height is an input parameter, so it is not recomputed. - # This will help with the wells and the input/output placement - self.output_pos = vector(0, 0.5*self.height) - # This is the extra space needed to ensure DRC rules # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") @@ -178,9 +175,12 @@ class pnand3(pgate.pgate): self.nmos3_pos = nmos2_pos + self.overlap_offset self.nmos3_inst.place(self.nmos3_pos) - + + # This will help with the wells and the input/output placement + self.output_pos = vector(0, 0.5*self.height) + # This should be placed at the top of the NMOS well - self.well_pos = vector(0, self.nmos1_inst.uy()) + self.well_pos = self.output_pos def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index d24189ac..9895e9d9 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -98,7 +98,7 @@ class pnor2(pgate.pgate): self.well_width = 2 * self.pmos.active_width \ + self.pmos.active_contact.width \ + 2 * self.active_space \ - + 2 * self.well_enclose_active + + 2 * self.nwell_enclose_active self.width = self.well_width # Height is an input parameter, so it is not recomputed. @@ -136,7 +136,6 @@ class pnor2(pgate.pgate): mod=self.pmos) self.connect_inst(["net1", "B", "Z", "vdd"]) - self.nmos1_inst = self.add_inst(name="pnor2_nmos1", mod=self.nmos) self.connect_inst(["Z", "A", "gnd", "gnd"]) @@ -170,7 +169,7 @@ class pnor2(pgate.pgate): 0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos.active_height)) # This will help with the wells - self.well_pos = vector(0, self.nmos1_inst.uy()) + self.well_pos = self.output_pos def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index f9e8ce87..d1e2f3af 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -116,7 +116,7 @@ class precharge(design.design): # adds the lower pmos to layout bl_xoffset = self.bitcell.get_pin(self.bitcell_bl).lx() self.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, - self.well_enclose_active), + self.nwell_enclose_active), self.pmos.active_offset.y) self.lower_pmos_inst.place(self.lower_pmos_position) @@ -176,7 +176,7 @@ class precharge(design.design): # adds the contact from active to metal1 well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) \ + vector(0, self.upper_pmos1_inst.uy() + contact.activem1.height / 2 \ - + self.well_extend_active) + + self.nwell_extend_active) self.add_via_center(layers=self.active_stack, offset=well_contact_pos, implant_type="n", diff --git a/compiler/pgates/ptristate_inv.py b/compiler/pgates/ptristate_inv.py index d2965cde..764b87c0 100644 --- a/compiler/pgates/ptristate_inv.py +++ b/compiler/pgates/ptristate_inv.py @@ -44,7 +44,7 @@ class ptristate_inv(pgate.pgate): """ Calls all functions related to the generation of the netlist """ self.add_pins() self.add_ptx() - self.create_ptx() + self.create_ptx() def create_layout(self): """ Calls all functions related to the generation of the layout """ @@ -61,7 +61,6 @@ class ptristate_inv(pgate.pgate): """ Adds pins for spice netlist """ self.add_pin_list(["in", "out", "en", "en_bar", "vdd", "gnd"]) - def setup_layout_constants(self): """ Pre-compute some handy layout parameters. @@ -73,7 +72,7 @@ class ptristate_inv(pgate.pgate): # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2 * self.pmos.active_width + self.well_enclose_active + self.well_width = 2 * self.pmos.active_width + self.nwell_enclose_active # Add an extra space because we route the output on the right of the S/D self.width = self.well_width + 0.5 * self.m1_space @@ -81,7 +80,6 @@ class ptristate_inv(pgate.pgate): # Make sure we can put a well above and below self.top_bottom_space = max(contact.activem1.width, contact.activem1.height) - def add_ptx(self): """ Create the PMOS and NMOS transistors. """ @@ -95,7 +93,6 @@ class ptristate_inv(pgate.pgate): width=self.pmos_width, mults=1, tx_type="pmos") - self.add_mod(self.pmos) def route_supply_rails(self): diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 5d4c015a..32bbeefe 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -58,11 +58,11 @@ class ptx(design.design): # some transistor sizes in other netlist depend on pbitcell self.create_layout() - #ll = self.find_lowest_coords() - #ur = self.find_highest_coords() - #self.add_boundary(ll, ur) + ll = self.find_lowest_coords() + ur = self.find_highest_coords() + self.add_boundary(ll, ur) - # (0,0) will be the corner ofthe active area (not the larger well) + # (0,0) will be the corner of the active area (not the larger well) self.translate_all(self.active_offset) def create_layout(self): @@ -99,7 +99,7 @@ class ptx(design.design): drc("minwidth_poly")) area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd, area_sd) - self.spice_device= main_str + area_str + self.spice_device = main_str + area_str self.spice.append("\n* ptx " + self.spice_device) # self.spice.append(".ENDS {0}".format(self.name)) @@ -108,8 +108,8 @@ class ptx(design.design): Pre-compute some handy layout parameters. """ - if self.num_contacts==None: - self.num_contacts=self.calculate_num_contacts() + if not self.num_contacts: + self.num_contacts = self.calculate_num_contacts() # Determine layer types needed if self.tx_type == "nmos": @@ -119,34 +119,35 @@ class ptx(design.design): self.implant_type = "p" self.well_type = "n" else: - self.error("Invalid transitor type.",-1) - + self.error("Invalid transitor type.", -1) # This is not actually instantiated but used for calculations self.active_contact = factory.create(module_type="contact", layer_stack=self.active_stack, - directions = ("V", "V"), + directions=("V", "V"), dimensions=(1, self.num_contacts)) - - # The contacted poly pitch (or uncontacted in an odd technology) + # The contacted poly pitch self.poly_pitch = max(2 * self.contact_to_gate + self.contact_width + self.poly_width, self.poly_space) - # The contacted poly pitch (or uncontacted in an odd technology) - self.contact_pitch = 2 * self.contact_to_gate + self.contact_width + self.poly_width - - # The enclosure of an active contact. Not sure about second term. - active_enclose_contact = max(self.active_enclose_contact, - (self.active_width - self.contact_width) / 2) - - # This is the distance from the edge of poly to the contacted end of active - self.end_to_poly = active_enclose_contact + self.contact_width + self.contact_to_gate - + # The contacted poly pitch + self.contact_spacing = 2 * self.contact_to_gate + \ + self.contact_width + self.poly_width + # This is measured because of asymmetric enclosure rules + active_enclose_contact = 0.5*(self.active_contact.width - self.contact_width) + + # This is the distance from the side of + # poly gate to the contacted end of active + # (i.e. the "outside" contacted diffusion sizes) + self.end_to_poly = self.active_contact.width - active_enclose_contact + \ + self.contact_to_gate + # Active width is determined by enclosure on both ends and contacted pitch, # at least one poly and n-1 poly pitches - self.active_width = 2 * self.end_to_poly + self.poly_width + (self.mults - 1) * self.poly_pitch + self.active_width = 2 * self.end_to_poly + self.poly_width + \ + (self.mults - 1) * self.poly_pitch # Active height is just the transistor width self.active_height = self.tx_width @@ -154,32 +155,35 @@ class ptx(design.design): # Poly height must include poly extension over active self.poly_height = self.tx_width + 2 * self.poly_extend_active + well_name = "{}well".format(self.well_type) + # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active] * 2) + if well_name in layer: + well_enclose_active = drc(well_name + "_enclose_active") + self.active_offset = vector([well_enclose_active] * 2) + else: + self.active_offset = vector(0, 0) # Well enclosure of active, ensure minwidth as well - well_name = "{}well".format(self.well_type) - if layer[well_name]: - self.cell_well_width = max(self.active_width + 2 * self.well_enclose_active, - self.well_width) - self.cell_well_height = max(self.tx_width + 2 * self.well_enclose_active, - self.well_width) + if well_name in layer: + well_width_rule = drc("minwidth_" + well_name) + well_enclose_active = drc(well_name + "_enclose_active") + self.well_width = max(self.active_width + 2 * well_enclose_active, + well_width_rule) + self.well_height = max(self.active_height + 2 * well_enclose_active, + well_width_rule) # We are going to shift the 0,0, so include that in the width and height - self.height = self.cell_well_height - self.active_offset.y - self.width = self.cell_well_width - self.active_offset.x + self.height = self.well_height - self.active_offset.y + self.width = self.well_width - self.active_offset.x else: - # If no well, use the boundary of the active and poly + # The well is not included in the height and width self.height = self.poly_height self.width = self.active_width - # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active] * 2) - # This is the center of the first active contact offset (centered vertically) - self.contact_offset = self.active_offset + vector(active_enclose_contact + 0.5 * self.contact_width, + self.contact_offset = self.active_offset + vector(0.5 * self.active_contact.width, 0.5 * self.active_height) - # Min area results are just flagged for now. debug.check(self.active_width * self.active_height >= self.minarea_active, "Minimum active area violated.") @@ -215,14 +219,14 @@ class ptx(design.design): poly_offset = poly_positions[0] + vector(-0.5 * self.poly_width, distance_above_active) # Remove the old pin and add the new one - self.remove_layout_pin("G") # only keep the main pin + # only keep the main pin + self.remove_layout_pin("G") self.add_layout_pin(text="G", layer="poly", offset=poly_offset, width=poly_width, height=self.poly_width) - def connect_fingered_active(self, drain_positions, source_positions): """ Connect each contact up/down to a source or drain pin @@ -230,10 +234,10 @@ class ptx(design.design): # This is the distance that we must route up or down from the center # of the contacts to avoid DRC violations to the other contacts - pin_offset = vector(0, 0.5 * self.active_contact.second_layer_height \ - + self.m1_space + 0.5 * self.m1_width) + pin_offset = vector(0, + 0.5 * self.active_contact.second_layer_height + self.m1_space + 0.5 * self.m1_width) # This is the width of a m1 extend the ends of the pin - end_offset = vector(self.m1_width/2,0) + end_offset = vector(self.m1_width / 2.0, 0) # drains always go to the MIDDLE of the cell, # so top of NMOS, bottom of PMOS @@ -246,8 +250,9 @@ class ptx(design.design): source_dir = -1 if len(source_positions) > 1: - source_offset = pin_offset.scale(source_dir,source_dir) - self.remove_layout_pin("S") # remove the individual connections + source_offset = pin_offset.scale(source_dir, source_dir) + # remove the individual connections + self.remove_layout_pin("S") # Add each vertical segment for a in source_positions: self.add_path(("m1"), @@ -326,26 +331,34 @@ class ptx(design.design): Add an (optional) well and implant for the type of transistor. """ well_name = "{}well".format(self.well_type) - if layer[well_name]: - self.add_rect(layer=well_name, - offset=(0,0), - width=self.cell_well_width, - height=self.cell_well_height) - if layer["vtg"]: - self.add_rect(layer="vtg", - offset=(0,0), - width=self.cell_well_width, - height=self.cell_well_height) + if not (well_name in layer or "vtg" in layer): + return + center_pos = self.active_offset + vector(self.width / 2.0, + self.height / 2.0) + well_ll = center_pos - vector(self.well_width / 2.0, + self.well_height / 2.0) + well_ll = well_ll - vector(0, + self.poly_extend_active) + + if well_name in layer: + self.add_rect(layer=well_name, + offset=well_ll, + width=self.well_width, + height=self.well_height) + if "vtg" in layer: + self.add_rect(layer="vtg", + offset=well_ll, + width=self.well_width, + height=self.well_height) def calculate_num_contacts(self): - """ + """ Calculates the possible number of source/drain contacts in a finger. For now, it is hard set as 1. """ return 1 - def get_contact_positions(self): """ Create a list of the centers of drain and source contact positions. @@ -359,10 +372,10 @@ class ptx(design.design): for i in range(self.mults): if i%2: # It's a source... so offset from previous drain. - source_positions.append(drain_positions[-1] + vector(self.contact_pitch, 0)) + source_positions.append(drain_positions[-1] + vector(self.contact_spacing, 0)) else: # It's a drain... so offset from previous source. - drain_positions.append(source_positions[-1] + vector(self.contact_pitch, 0)) + drain_positions.append(source_positions[-1] + vector(self.contact_spacing, 0)) return [source_positions,drain_positions] @@ -377,7 +390,7 @@ class ptx(design.design): contact=self.add_via_center(layers=self.active_stack, offset=pos, size=(1, self.num_contacts), - directions=("H","V"), + directions=("V","V"), implant_type=self.implant_type, well_type=self.well_type) self.add_layout_pin_rect_center(text="S", @@ -391,7 +404,7 @@ class ptx(design.design): contact=self.add_via_center(layers=self.active_stack, offset=pos, size=(1, self.num_contacts), - directions=("H","V"), + directions=("V","V"), implant_type=self.implant_type, well_type=self.well_type) self.add_layout_pin_rect_center(text="D", diff --git a/compiler/pgates/pwrite_driver.py b/compiler/pgates/pwrite_driver.py index 2dc356c3..ed970f9b 100644 --- a/compiler/pgates/pwrite_driver.py +++ b/compiler/pgates/pwrite_driver.py @@ -40,12 +40,10 @@ class pwrite_driver(design.design): # Creates the netlist and layout # Since it has variable height, it is not a pgate. self.create_netlist() - if not OPTS.netlist_only: + if not OPTS.netlist_only: self.create_layout() self.DRC_LVS() - - def create_netlist(self): self.add_pins() self.add_modules() @@ -55,8 +53,6 @@ class pwrite_driver(design.design): self.place_modules() self.route_wires() self.route_supplies() - - def add_pins(self): self.add_pin("din", "INPUT") @@ -66,17 +62,19 @@ class pwrite_driver(design.design): self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def add_modules(self): # Tristate inverter self.tri = factory.create(module_type="ptristate_inv", height="min") self.add_mod(self.tri) - debug.check(self.tri.width