diff --git a/README.md b/README.md index b6fd98e1..05bb7772 100644 --- a/README.md +++ b/README.md @@ -246,4 +246,4 @@ If I forgot to add you, please let me know! [FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents [SCMOS]: https://www.mosis.com/files/scmos/scmos.pdf -[Slack]: https://join.slack.com/t/openram/shared_invite/enQtNDgxMjc3NzU5NTI1LTE4ODMyM2I0Mzk2ZmFiMjgwYTYyMTQ4NTgwMmUwMDhiM2E1MDViNDRjYzU1NjJhZTQxNWZjMzE3M2FlODBmZjA +[Slack]: https://join.slack.com/t/openram/shared_invite/enQtNDgxMjc3NzU5NTI1LWZiYWMwNjNkZThmYTdkODc3NDE1NDhjNzUxNDhmMDQ4ZTM3NDgwNWFlNjM5NWFiZDkyMzBlNzc1NTg3ZjllNTY diff --git a/compiler/base/contact.py b/compiler/base/contact.py index be3af1ce..30f6870b 100644 --- a/compiler/base/contact.py +++ b/compiler/base/contact.py @@ -7,39 +7,44 @@ # import hierarchy_design import debug -import utils -from tech import drc,layer +from tech import drc, layer from vector import vector class contact(hierarchy_design.hierarchy_design): """ - Object for a contact shape with its conductor enclosures. - Creates a contact array minimum active or poly enclosure and metal1 enclosure. - This class has enclosure on two or four sides of the contact. - The direction specifies whether the first and second layer have asymmetric extension in the H or V direction. + Object for a contact shape with its conductor enclosures. Creates + a contact array minimum active or poly enclosure and metal1 + enclosure. This class has enclosure on two or four sides of the + contact. The direction specifies whether the first and second + layer have asymmetric extension in the H or V direction. + + The well/implant_type is an option to add a select/implant layer + enclosing the contact. This is necessary to import layouts into + Magic which requires the select to be in the same GDS hierarchy as + the contact. - The well/implant_type is an option to add a select/implant layer enclosing the contact. This is - necessary to import layouts into Magic which requires the select to be in the same GDS - hierarchy as the contact. """ - def __init__(self, layer_stack, dimensions=(1,1), directions=("V","V"), implant_type=None, well_type=None, name=""): - # This will ignore the name parameter since we can guarantee a unique name here + def __init__(self, layer_stack, dimensions=(1, 1), directions=("V", "V"), + implant_type=None, well_type=None, name=""): + # This will ignore the name parameter since + # we can guarantee a unique name here hierarchy_design.hierarchy_design.__init__(self, name) debug.info(4, "create contact object {0}".format(name)) self.add_comment("layers: {0}".format(layer_stack)) self.add_comment("dimensions: {0}".format(dimensions)) if implant_type or well_type: - self.add_comment("implant type: {0}\nwell_type: {1}".format(implant_type,well_type)) + self.add_comment("implant type: {}\n".format(implant_type)) + self.add_comment("well_type: {}\n".format(well_type)) self.layer_stack = layer_stack self.dimensions = dimensions self.directions = directions - self.offset = vector(0,0) + self.offset = vector(0, 0) self.implant_type = implant_type - self.well_type = well_type + self.well_type = well_type # Module does not have pins, but has empty pin list. self.pins = [] self.create_layout() @@ -59,7 +64,7 @@ class contact(hierarchy_design.hierarchy_design): if self.implant_type and self.well_type: self.create_implant_well_enclosures() elif self.implant_type or self.well_type: - debug.error(-1,"Must define both implant and well type or none at all.") + debug.error(-1, "Must define both implant and well type or none at all.") def setup_layers(self): """ Locally assign the layer names. """ @@ -67,10 +72,11 @@ class contact(hierarchy_design.hierarchy_design): (first_layer, via_layer, second_layer) = self.layer_stack self.first_layer_name = first_layer self.via_layer_name = via_layer - # Some technologies have a separate active contact from the poly contact + # Some technologies have a separate active + # contact from the poly contact # We will use contact for DRC, but active_contact for output - if first_layer=="active" or second_layer=="active": - self.via_layer_name_expanded = "active_"+via_layer + if first_layer == "active" or second_layer == "active": + self.via_layer_name_expanded = "active_" + via_layer else: self.via_layer_name_expanded = via_layer self.second_layer_name = second_layer @@ -97,19 +103,19 @@ class contact(hierarchy_design.hierarchy_design): second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name)) second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)) - - # In some technologies, the minimum width may be larger than the overlap requirement around the via, so + # In some technologies, the minimum width may be larger + # than the overlap requirement around the via, so # check this for each dimension. if self.directions[0] == "V": self.first_layer_horizontal_enclosure = max(first_layer_enclosure, - (first_layer_minwidth - self.contact_array_width)/2) + (first_layer_minwidth - self.contact_array_width) / 2) self.first_layer_vertical_enclosure = max(first_layer_extend, - (first_layer_minwidth - self.contact_array_height)/2) + (first_layer_minwidth - self.contact_array_height) / 2) elif self.directions[0] == "H": self.first_layer_horizontal_enclosure = max(first_layer_extend, - (first_layer_minwidth - self.contact_array_width)/2) + (first_layer_minwidth - self.contact_array_width) / 2) self.first_layer_vertical_enclosure = max(first_layer_enclosure, - (first_layer_minwidth - self.contact_array_height)/2) + (first_layer_minwidth - self.contact_array_height) / 2) else: debug.error("Invalid first layer direction.", -1) @@ -117,23 +123,23 @@ class contact(hierarchy_design.hierarchy_design): # check this for each dimension. if self.directions[1] == "V": self.second_layer_horizontal_enclosure = max(second_layer_enclosure, - (second_layer_minwidth - self.contact_array_width)/2) + (second_layer_minwidth - self.contact_array_width) / 2) self.second_layer_vertical_enclosure = max(second_layer_extend, - (second_layer_minwidth - self.contact_array_height)/2) + (second_layer_minwidth - self.contact_array_height) / 2) elif self.directions[1] == "H": self.second_layer_horizontal_enclosure = max(second_layer_extend, - (second_layer_minwidth - self.contact_array_height)/2) + (second_layer_minwidth - self.contact_array_height) / 2) self.second_layer_vertical_enclosure = max(second_layer_enclosure, - (second_layer_minwidth - self.contact_array_width)/2) + (second_layer_minwidth - self.contact_array_width) / 2) else: debug.error("Invalid second layer direction.", -1) - def create_contact_array(self): """ Create the contact array at the origin""" # offset for the via array - self.via_layer_position =vector(max(self.first_layer_horizontal_enclosure,self.second_layer_horizontal_enclosure), - max(self.first_layer_vertical_enclosure,self.second_layer_vertical_enclosure)) + self.via_layer_position = vector( + max(self.first_layer_horizontal_enclosure, self.second_layer_horizontal_enclosure), + max(self.first_layer_vertical_enclosure, self.second_layer_vertical_enclosure)) for i in range(self.dimensions[1]): offset = self.via_layer_position + vector(0, self.contact_pitch * i) @@ -142,15 +148,16 @@ class contact(hierarchy_design.hierarchy_design): offset=offset, width=self.contact_width, height=self.contact_width) - offset = offset + vector(self.contact_pitch,0) + offset = offset + vector(self.contact_pitch, 0) def create_first_layer_enclosure(self): # this is if the first and second layers are different - self.first_layer_position = vector(max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure,0), - max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure,0)) + self.first_layer_position = vector( + max(self.second_layer_horizontal_enclosure - self.first_layer_horizontal_enclosure, 0), + max(self.second_layer_vertical_enclosure - self.first_layer_vertical_enclosure, 0)) - self.first_layer_width = self.contact_array_width + 2*self.first_layer_horizontal_enclosure - self.first_layer_height = self.contact_array_height + 2*self.first_layer_vertical_enclosure + self.first_layer_width = self.contact_array_width + 2 * self.first_layer_horizontal_enclosure + self.first_layer_height = self.contact_array_height + 2 * self.first_layer_vertical_enclosure self.add_rect(layer=self.first_layer_name, offset=self.first_layer_position, width=self.first_layer_width, @@ -158,27 +165,28 @@ class contact(hierarchy_design.hierarchy_design): def create_second_layer_enclosure(self): # this is if the first and second layers are different - self.second_layer_position = vector(max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure,0), - max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure,0)) + self.second_layer_position = vector( + max(self.first_layer_horizontal_enclosure - self.second_layer_horizontal_enclosure, 0), + max(self.first_layer_vertical_enclosure - self.second_layer_vertical_enclosure, 0)) - self.second_layer_width = self.contact_array_width + 2*self.second_layer_horizontal_enclosure - self.second_layer_height = self.contact_array_height + 2*self.second_layer_vertical_enclosure + self.second_layer_width = self.contact_array_width + 2 * self.second_layer_horizontal_enclosure + self.second_layer_height = self.contact_array_height + 2 * self.second_layer_vertical_enclosure self.add_rect(layer=self.second_layer_name, offset=self.second_layer_position, width=self.second_layer_width, height=self.second_layer_height) def create_implant_well_enclosures(self): - implant_position = self.first_layer_position - [drc("implant_enclosure_active")]*2 - implant_width = self.first_layer_width + 2*drc("implant_enclosure_active") - implant_height = self.first_layer_height + 2*drc("implant_enclosure_active") + implant_position = self.first_layer_position - [drc("implant_enclosure_active")] * 2 + implant_width = self.first_layer_width + 2 * drc("implant_enclosure_active") + implant_height = self.first_layer_height + 2 * drc("implant_enclosure_active") self.add_rect(layer="{}implant".format(self.implant_type), offset=implant_position, width=implant_width, height=implant_height) - well_position = self.first_layer_position - [drc("well_enclosure_active")]*2 - well_width = self.first_layer_width + 2*drc("well_enclosure_active") - well_height = self.first_layer_height + 2*drc("well_enclosure_active") + well_position = self.first_layer_position - [drc("well_enclosure_active")] * 2 + well_width = self.first_layer_width + 2 * drc("well_enclosure_active") + well_height = self.first_layer_height + 2 * drc("well_enclosure_active") self.add_rect(layer="{}well".format(self.well_type), offset=well_position, width=well_width, @@ -188,16 +196,30 @@ class contact(hierarchy_design.hierarchy_design): """ Get total power of a module """ return self.return_power() + from sram_factory import factory + # This is not instantiated and used for calculations only. # These are static 1x1 contacts to reuse in all the design modules. -well = factory.create(module_type="contact", layer_stack=("active", "contact", "metal1"), directions=("H","V")) -active = factory.create(module_type="contact", layer_stack=("active", "contact", "metal1"), directions=("H","V")) -poly = factory.create(module_type="contact", layer_stack=("poly", "contact", "metal1"), directions=("V","H")) -m1m2 = factory.create(module_type="contact", layer_stack=("metal1", "via1", "metal2"), directions=("H","V")) -m2m3 = factory.create(module_type="contact", layer_stack=("metal2", "via2", "metal3"), directions=("V","H")) +well = factory.create(module_type="contact", + layer_stack=("active", "contact", "metal1"), + directions=("H", "V")) +active = factory.create(module_type="contact", + layer_stack=("active", "contact", "metal1"), + directions=("H", "V")) +poly = factory.create(module_type="contact", + layer_stack=("poly", "contact", "metal1"), + directions=("V", "H")) +m1m2 = factory.create(module_type="contact", + layer_stack=("metal1", "via1", "metal2"), + directions=("H", "V")) +m2m3 = factory.create(module_type="contact", + layer_stack=("metal2", "via2", "metal3"), + directions=("V", "H")) if "metal4" in layer.keys(): - m3m4 = factory.create(module_type="contact", layer_stack=("metal3", "via3", "metal4"), directions=("H","V")) + m3m4 = factory.create(module_type="contact", + layer_stack=("metal3", "via3", "metal4"), + directions=("H", "V")) else: m3m4 = None diff --git a/compiler/base/delay_data.py b/compiler/base/delay_data.py index e3d5a8bc..97fe9867 100644 --- a/compiler/base/delay_data.py +++ b/compiler/base/delay_data.py @@ -6,6 +6,7 @@ # All rights reserved. # + class delay_data(): """ This is the delay class to represent the delay information @@ -20,13 +21,13 @@ class delay_data(): def __str__(self): """ override print function output """ - return "Delay Data: Delay "+str(self.delay)+", Slew "+str(self.slew)+"" + return "Delta Data: Delay {} Slew {}".format(self.delay, self.slew) def __add__(self, other): """ Override - function (left), for delay_data: a+b != b+a """ - assert isinstance(other,delay_data) + assert isinstance(other, delay_data) return delay_data(other.delay + self.delay, other.slew) @@ -34,7 +35,7 @@ class delay_data(): """ Override - function (right), for delay_data: a+b != b+a """ - assert isinstance(other,delay_data) + assert isinstance(other, delay_data) return delay_data(other.delay + self.delay, self.slew) diff --git a/compiler/base/design.py b/compiler/base/design.py index da23aa9e..33914358 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -7,12 +7,9 @@ # from hierarchy_design import hierarchy_design import contact -import globals -import verify -import debug -import os from globals import OPTS + class design(hierarchy_design): """ This is the same as the hierarchy_design class except it contains @@ -21,29 +18,29 @@ class design(hierarchy_design): """ def __init__(self, name): - hierarchy_design.__init__(self,name) + hierarchy_design.__init__(self, name) self.setup_drc_constants() self.setup_multiport_constants() from tech import layer - self.m1_pitch = max(contact.m1m2.width,contact.m1m2.height) + max(self.m1_space, self.m2_space) - self.m2_pitch = max(contact.m2m3.width,contact.m2m3.height) + max(self.m2_space, self.m3_space) + self.m1_pitch = max(contact.m1m2.width, contact.m1m2.height) + max(self.m1_space, self.m2_space) + self.m2_pitch = max(contact.m2m3.width, contact.m2m3.height) + max(self.m2_space, self.m3_space) if "metal4" in layer: - self.m3_pitch = max(contact.m3m4.width,contact.m3m4.height) + max(self.m3_space, self.m4_space) + self.m3_pitch = max(contact.m3m4.width, contact.m3m4.height) + max(self.m3_space, self.m4_space) else: self.m3_pitch = self.m2_pitch def setup_drc_constants(self): """ These are some DRC constants used in many places in the compiler.""" - from tech import drc,layer + from tech import drc, layer self.well_width = drc("minwidth_well") self.poly_width = drc("minwidth_poly") - self.poly_space = drc("poly_to_poly") + self.poly_space = drc("poly_to_poly") self.m1_width = drc("minwidth_metal1") self.m1_space = drc("metal1_to_metal1") self.m2_width = drc("minwidth_metal2") - self.m2_space = drc("metal2_to_metal2") + self.m2_space = drc("metal2_to_metal2") self.m3_width = drc("minwidth_metal3") self.m3_space = drc("metal3_to_metal3") if "metal4" in layer: @@ -93,12 +90,12 @@ class design(hierarchy_design): port_number += 1 for port in range(OPTS.num_w_ports): self.write_ports.append(port_number) - self.writeonly_ports.append(port_number) + self.writeonly_ports.append(port_number) port_number += 1 for port in range(OPTS.num_r_ports): self.read_ports.append(port_number) self.readonly_ports.append(port_number) - port_number += 1 + port_number += 1 def analytical_power(self, corner, load): """ Get total power of a module """ diff --git a/compiler/base/geometry.py b/compiler/base/geometry.py index 7063cf81..74b02f5f 100644 --- a/compiler/base/geometry.py +++ b/compiler/base/geometry.py @@ -6,7 +6,7 @@ # All rights reserved. # """ -This provides a set of useful generic types for the gdsMill interface. +This provides a set of useful generic types for the gdsMill interface. """ import debug from vector import vector @@ -15,6 +15,7 @@ import math from globals import OPTS from utils import round_to_grid + class geometry: """ A specific path, shape, or text geometry. Base class for shared @@ -27,11 +28,11 @@ class geometry: def __str__(self): """ override print function output """ - debug.error("__str__ must be overridden by all geometry types.",1) + debug.error("__str__ must be overridden by all geometry types.", 1) def __repr__(self): """ override print function output """ - debug.error("__repr__ must be overridden by all geometry types.",1) + debug.error("__repr__ must be overridden by all geometry types.", 1) # def translate_coords(self, coords, mirr, angle, xyShift): # """Calculate coordinates after flip, rotate, and shift""" @@ -46,50 +47,52 @@ class geometry: """Calculate coordinates after flip, rotate, and shift""" coordinate = [] for item in coords: - x = item[0]*math.cos(angle) - item[1]*mirr*math.sin(angle) + offset[0] - y = item[0]*math.sin(angle) + item[1]*mirr*math.cos(angle) + offset[1] + x = item[0] * math.cos(angle) - item[1] * mirr * math.sin(angle) + offset[0] + y = item[0] * math.sin(angle) + item[1] * mirr * math.cos(angle) + offset[1] coordinate += [[x, y]] return coordinate def normalize(self): """ Re-find the LL and UR points after a transform """ - (first,second)=self.boundary - ll = vector(min(first[0],second[0]),min(first[1],second[1])).snap_to_grid() - ur = vector(max(first[0],second[0]),max(first[1],second[1])).snap_to_grid() - self.boundary=[ll,ur] + (first, second) = self.boundary + ll = vector(min(first[0], second[0]), + min(first[1], second[1])).snap_to_grid() + ur = vector(max(first[0], second[0]), + max(first[1], second[1])).snap_to_grid() + self.boundary = [ll, ur] def update_boundary(self): """ Update the boundary with a new placement. """ - self.compute_boundary(self.offset,self.mirror,self.rotate) + self.compute_boundary(self.offset, self.mirror, self.rotate) - def compute_boundary(self,offset=vector(0,0),mirror="",rotate=0): - """ Transform with offset, mirror and rotation to get the absolute pin location. + def compute_boundary(self, offset=vector(0, 0), mirror="", rotate=0): + """ 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: return - (ll,ur) = [vector(0,0),vector(self.width,self.height)] + (ll, ur) = [vector(0, 0), vector(self.width, self.height)] - if mirror=="MX": - ll=ll.scale(1,-1) - ur=ur.scale(1,-1) - elif mirror=="MY": - ll=ll.scale(-1,1) - ur=ur.scale(-1,1) - elif mirror=="XY": - ll=ll.scale(-1,-1) - ur=ur.scale(-1,-1) + if mirror == "MX": + ll = ll.scale(1, -1) + ur = ur.scale(1, -1) + elif mirror == "MY": + ll = ll.scale(-1, 1) + ur = ur.scale(-1, 1) + elif mirror == "XY": + ll = ll.scale(-1, -1) + ur = ur.scale(-1, -1) - if rotate==90: - ll=ll.rotate_scale(-1,1) - ur=ur.rotate_scale(-1,1) - elif rotate==180: - ll=ll.scale(-1,-1) - ur=ur.scale(-1,-1) - elif rotate==270: - ll=ll.rotate_scale(1,-1) - ur=ur.rotate_scale(1,-1) + if rotate == 90: + ll = ll.rotate_scale(-1, 1) + ur = ur.rotate_scale(-1, 1) + elif rotate == 180: + ll = ll.scale(-1, -1) + ur = ur.scale(-1, -1) + elif rotate == 270: + ll = ll.rotate_scale(1, -1) + ur = ur.rotate_scale(1, -1) - self.boundary=[offset+ll,offset+ur] + self.boundary = [offset + ll, offset + ur] self.normalize() def ll(self): @@ -108,7 +111,6 @@ class geometry: """ Return the upper left corner """ return vector(self.boundary[0].x, self.boundary[1].y) - def uy(self): """ Return the upper edge """ return self.boundary[1].y @@ -127,11 +129,11 @@ class geometry: def cx(self): """ Return the center x """ - return 0.5*(self.boundary[0].x + self.boundary[1].x) + return 0.5 * (self.boundary[0].x + self.boundary[1].x) def cy(self): """ Return the center y """ - return 0.5*(self.boundary[0].y + self.boundary[1].y) + return 0.5 * (self.boundary[0].y + self.boundary[1].y) class instance(geometry): @@ -139,10 +141,11 @@ class instance(geometry): An instance of an instance/module with a specified location and rotation """ - def __init__(self, name, mod, offset=[0,0], mirror="R0", rotate=0): + def __init__(self, name, mod, offset=[0, 0], mirror="R0", rotate=0): """Initializes an instance to represent a module""" geometry.__init__(self) - debug.check(mirror not in ["R90","R180","R270"], "Please use rotation and not mirroring during instantiation.") + debug.check(mirror not in ["R90", "R180", "R270"], + "Please use rotation and not mirroring during instantiation.") self.name = name self.mod = mod @@ -154,13 +157,13 @@ class instance(geometry): self.width = 0 self.height = 0 else: - if mirror in ["R90","R270"] or rotate in [90,270]: + if mirror in ["R90", "R270"] or rotate in [90, 270]: self.width = round_to_grid(mod.height) self.height = round_to_grid(mod.width) else: self.width = round_to_grid(mod.width) self.height = round_to_grid(mod.height) - self.compute_boundary(offset,mirror,rotate) + self.compute_boundary(offset, mirror, rotate) debug.info(4, "creating instance: " + self.name) @@ -169,18 +172,18 @@ class instance(geometry): Apply the transform of the instance placement to give absolute blockages.""" angle = math.radians(float(self.rotate)) mirr = 1 - if self.mirror=="R90": + if self.mirror == "R90": angle += math.radians(90.0) - elif self.mirror=="R180": + elif self.mirror == "R180": angle += math.radians(180.0) - elif self.mirror=="R270": + elif self.mirror == "R270": angle += math.radians(270.0) - elif self.mirror=="MX": + elif self.mirror == "MX": mirr = -1 - elif self.mirror=="MY": + elif self.mirror == "MY": mirr = -1 angle += math.radians(180.0) - elif self.mirror=="XY": + elif self.mirror == "XY": mirr = 1 angle += math.radians(180.0) @@ -226,7 +229,7 @@ class instance(geometry): this instance location. Index will return one of several pins.""" import copy - if index==-1: + if index == -1: pin = copy.deepcopy(self.mod.get_pin(name)) pin.transform(self.offset,self.mirror,self.rotate) return pin @@ -339,6 +342,7 @@ class label(geometry): """ override print function output """ return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )" + class rectangle(geometry): """Represents a rectangular shape""" @@ -351,22 +355,23 @@ class rectangle(geometry): self.size = vector(width, height).snap_to_grid() self.width = round_to_grid(self.size.x) self.height = round_to_grid(self.size.y) - self.compute_boundary(offset,"",0) + self.compute_boundary(offset, "", 0) - debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): " + debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): " + str(self.width) + "x" + str(self.height) + " @ " + str(self.offset)) - def get_blockages(self, layer): """ Returns a list of one rectangle if it is on this layer""" if self.layerNumber == layer: - return [[self.offset, vector(self.offset.x+self.width,self.offset.y+self.height)]] + return [[self.offset, + vector(self.offset.x + self.width, + self.offset.y + self.height)]] else: return [] def gds_write_file(self, new_layout): """Writes the rectangular shape to GDS""" - debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):" + debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):" + str(self.width) + "x" + str(self.height) + " @ " + str(self.offset)) new_layout.addBox(layerNumber=self.layerNumber, purposeNumber=0, diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index ff13c4f4..04344738 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -122,6 +122,9 @@ class verilog: if self.write_size: self.vf.write(" wmask{0}_reg = wmask{0};\n".format(port)) self.vf.write(" addr{0}_reg = addr{0};\n".format(port)) + if port in self.read_ports: + self.add_write_read_checks(port) + if port in self.write_ports: self.vf.write(" din{0}_reg = din{0};\n".format(port)) if port in self.read_ports: @@ -211,4 +214,29 @@ class verilog: self.vf.write(" if (!csb{0}_reg)\n".format(port)) self.vf.write(" dout{0} <= #(DELAY) mem[addr{0}_reg];\n".format(port)) self.vf.write(" end\n") - + + def add_address_check(self, wport, rport): + """ Output a warning if the two addresses match """ + # If the rport is actually reading... and addresses match. + if rport in self.readwrite_ports: + rport_control = "!csb{0} && web{0}".format(rport) + else: + rport_control = "!csb{0}".format(rport) + if wport in self.readwrite_ports: + wport_control = "!csb{0} && !web{0}".format(wport) + else: + wport_control = "!csb{0}".format(wport) + + self.vf.write(" if ({1} && {3} && (addr{0} == addr{2}))\n".format(wport,wport_control,rport,rport_control)) + self.vf.write(" $display($time,\" WARNING: Writing and reading addr{0}=%b and addr{1}=%b simultaneously!\",addr{0},addr{1});\n".format(wport,rport)) + + def add_write_read_checks(self, rport): + """ + Add a warning if we read from an address that we are currently writing. + Can be fixed if we appropriately size the write drivers to do this . + """ + for wport in self.write_ports: + if wport == rport: + continue + else: + self.add_address_check(wport,rport) diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index cad069a5..b22c9a46 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -5,13 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design import debug import utils -from tech import GDS,layer,parameter,drc -import logical_effort +from tech import GDS, layer +import bitcell_base -class bitcell(design.design): + +class bitcell(bitcell_base.bitcell_base): """ A single bit cell (6T, 8T, etc.) This module implements the single memory cell used in the design. It is a hand-made cell, so @@ -21,13 +21,15 @@ class bitcell(design.design): pin_names = ["bl", "br", "wl", "vdd", "gnd"] storage_nets = ['Q', 'Qbar'] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) + type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] + (width, height) = utils.get_libcell_size("cell_6t", + GDS["unit"], + layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"]) def __init__(self, name=""): # Ignore the name argument - design.design.__init__(self, "cell_6t") + bitcell_base.bitcell_base.__init__(self, "cell_6t") debug.info(2, "Create bitcell") self.width = bitcell.width @@ -36,15 +38,9 @@ class bitcell(design.design): self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - def get_stage_effort(self, load): - parasitic_delay = 1 - size = 0.5 #This accounts for bitline being drained thought the access TX and internal node - cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file. - return logical_effort.logical_effort('bitline', size, cin, load, parasitic_delay, False) - def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl"] + row_pins = ["wl"] return row_pins def get_all_bitline_names(self): @@ -64,43 +60,22 @@ class bitcell(design.design): def get_bl_name(self, port=0): """Get bl name""" - debug.check(port==0,"One port for bitcell only.") + debug.check(port == 0, "One port for bitcell only.") return "bl" def get_br_name(self, port=0): """Get bl name""" - debug.check(port==0,"One port for bitcell only.") - return "br" + debug.check(port == 0, "One port for bitcell only.") + return "br" def get_wl_name(self, port=0): """Get wl name""" - debug.check(port==0,"One port for bitcell only.") - return "wl" + debug.check(port == 0, "One port for bitcell only.") + return "wl" - def analytical_power(self, corner, load): - """Bitcell power in nW. Only characterizes leakage.""" - from tech import spice - leakage = spice["bitcell_leakage"] - dynamic = 0 #temporary - total_power = self.return_power(dynamic, leakage) - return total_power - - def get_storage_net_names(self): - """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" - #Checks that they do exist - if self.nets_match: - return self.storage_nets - else: - debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) - return None - - def input_load(self): - """Return the relative capacitance of the access transistor gates""" - - # FIXME: This applies to bitline capacitances as well. - access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] - return 2*access_tx_cin - - def build_graph(self, graph, inst_name, port_nets): - """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) diff --git a/compiler/bitcells/bitcell_1rw_1r.py b/compiler/bitcells/bitcell_1rw_1r.py index 0d536b38..0280f3cb 100644 --- a/compiler/bitcells/bitcell_1rw_1r.py +++ b/compiler/bitcells/bitcell_1rw_1r.py @@ -5,13 +5,14 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design import debug import utils -from tech import GDS,layer,parameter,drc +from tech import GDS, layer, parameter, drc import logical_effort +import bitcell_base -class bitcell_1rw_1r(design.design): + +class bitcell_1rw_1r(bitcell_base.bitcell_base): """ A single bit cell (6T, 8T, etc.) This module implements the single memory cell used in the design. It is a hand-made cell, so @@ -20,14 +21,17 @@ class bitcell_1rw_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", + "INPUT", "INPUT", "POWER", "GROUND"] storage_nets = ['Q', 'Q_bar'] - (width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"]) + (width, height) = utils.get_libcell_size("cell_1rw_1r", + GDS["unit"], + layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"]) def __init__(self, name=""): # Ignore the name argument - design.design.__init__(self, "cell_1rw_1r") + bitcell_base.bitcell_base.__init__(self, "cell_1rw_1r") debug.info(2, "Create bitcell with 1RW and 1R Port") self.width = bitcell_1rw_1r.width @@ -36,15 +40,11 @@ class bitcell_1rw_1r(design.design): self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - def get_stage_effort(self, load): - parasitic_delay = 1 - size = 0.5 #This accounts for bitline being drained thought the access TX and internal node - cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file. - read_port_load = 0.5 #min size NMOS gate load - return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False) - def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ bitcell_pins = ["bl0_{0}".format(col), "br0_{0}".format(col), "bl1_{0}".format(col), @@ -57,7 +57,7 @@ class bitcell_1rw_1r(design.design): def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl0", "wl1"] + row_pins = ["wl0", "wl1"] return row_pins def get_all_bitline_names(self): @@ -97,52 +97,27 @@ class bitcell_1rw_1r(design.design): def get_bl_name(self, port=0): """Get bl name by port""" - debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") return "bl{}".format(port) def get_br_name(self, port=0): """Get bl name by port""" - debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") return "br{}".format(port) def get_wl_name(self, port=0): """Get wl name by port""" - debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") return "wl{}".format(port) - def analytical_power(self, corner, load): - """Bitcell power in nW. Only characterizes leakage.""" - from tech import spice - leakage = spice["bitcell_leakage"] - dynamic = 0 #temporary - total_power = self.return_power(dynamic, leakage) - return total_power - - def input_load(self): - """Return the relative capacitance of the access transistor gates""" - - # FIXME: This applies to bitline capacitances as well. - # FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. - access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] - return 2*access_tx_cin - - def get_storage_net_names(self): - """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" - #Checks that they do exist - if self.nets_match: - return self.storage_nets - else: - debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) - return None - - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} - #Edges hardcoded here. Essentially wl->bl/br for both ports. + pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + # Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) - graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) + graph.add_edge(pin_dict["wl0"], pin_dict["bl0"], self) + graph.add_edge(pin_dict["wl0"], pin_dict["br0"], self) # Port 1 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) diff --git a/compiler/bitcells/bitcell_1w_1r.py b/compiler/bitcells/bitcell_1w_1r.py index 7e8c9e75..a92dc75d 100644 --- a/compiler/bitcells/bitcell_1w_1r.py +++ b/compiler/bitcells/bitcell_1w_1r.py @@ -5,13 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design import debug import utils -from tech import GDS,layer,parameter,drc -import logical_effort +from tech import GDS, layer +import bitcell_base -class bitcell_1w_1r(design.design): + +class bitcell_1w_1r(bitcell_base.bitcell_base): """ A single bit cell (6T, 8T, etc.) This module implements the single memory cell used in the design. It is a hand-made cell, so @@ -20,14 +20,17 @@ class bitcell_1w_1r(design.design): """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] - storage_nets = ['Q', 'Q_bar'] - (width,height) = utils.get_libcell_size("cell_1w_1r", GDS["unit"], layer["boundary"]) + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", + "INPUT", "INPUT", "POWER", "GROUND"] + storage_nets = ['Q', 'Q_bar'] + (width, height) = utils.get_libcell_size("cell_1w_1r", + GDS["unit"], + layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_1w_1r", GDS["unit"]) def __init__(self, name=""): # Ignore the name argument - design.design.__init__(self, "cell_1w_1r") + bitcell_base.bitcell_base.__init__(self, "cell_1w_1r") debug.info(2, "Create bitcell with 1W and 1R Port") self.width = bitcell_1w_1r.width @@ -36,15 +39,11 @@ class bitcell_1w_1r(design.design): self.add_pin_types(self.type_list) self.nets_match = self.do_nets_exist(self.storage_nets) - def get_stage_effort(self, load): - parasitic_delay = 1 - size = 0.5 #This accounts for bitline being drained thought the access TX and internal node - cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file. - read_port_load = 0.5 #min size NMOS gate load - return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False) - def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, + indexed by column and row, for instance use in bitcell_array + """ bitcell_pins = ["bl0_{0}".format(col), "br0_{0}".format(col), "bl1_{0}".format(col), @@ -57,7 +56,7 @@ class bitcell_1w_1r(design.design): def get_all_wl_names(self): """ Creates a list of all wordline pin names """ - row_pins = ["wl0", "wl1"] + row_pins = ["wl0", "wl1"] return row_pins def get_all_bitline_names(self): @@ -105,40 +104,15 @@ class bitcell_1w_1r(design.design): def get_wl_name(self, port=0): """Get wl name by port""" - debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") return "wl{}".format(port) - def analytical_power(self, corner, load): - """Bitcell power in nW. Only characterizes leakage.""" - from tech import spice - leakage = spice["bitcell_leakage"] - dynamic = 0 #temporary - total_power = self.return_power(dynamic, leakage) - return total_power - - def input_load(self): - """Return the relative capacitance of the access transistor gates""" - - # FIXME: This applies to bitline capacitances as well. - # FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. - access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] - return 2*access_tx_cin - - def get_storage_net_names(self): - """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" - #Checks that they do exist - if self.nets_match: - return self.storage_nets - else: - debug.info(1,"Storage nodes={} not found in spice file.".format(self.storage_nets)) - return None - - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph. Multiport bitcell timing graph is too complex to use the add_graph_edges function.""" - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} - #Edges hardcoded here. Essentially wl->bl/br for both ports. + pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} + # Edges hardcoded here. Essentially wl->bl/br for both ports. # Port 0 edges - graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) - graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) - # Port 1 is a write port, so its timing is not considered here. + graph.add_edge(pin_dict["wl1"], pin_dict["bl1"], self) + graph.add_edge(pin_dict["wl1"], pin_dict["br1"], self) + # Port 1 is a write port, so its timing is not considered here. diff --git a/compiler/bitcells/bitcell_base.py b/compiler/bitcells/bitcell_base.py new file mode 100644 index 00000000..5265904b --- /dev/null +++ b/compiler/bitcells/bitcell_base.py @@ -0,0 +1,87 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2019 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# + +import debug +import design +import logical_effort +from tech import parameter, drc + + +class bitcell_base(design.design): + """ + Base bitcell parameters to be over-riden. + """ + def __init__(self, name): + design.design.__init__(self, name) + + def get_stage_effort(self, load): + parasitic_delay = 1 + # This accounts for bitline being drained + # thought the access TX and internal node + size = 0.5 + # Assumes always a minimum sizes inverter. + # Could be specified in the tech.py file. + cin = 3 + # min size NMOS gate load + read_port_load = 0.5 + + return logical_effort.logical_effort('bitline', + size, + cin, + load + read_port_load, + parasitic_delay, + False) + + def analytical_power(self, corner, load): + """Bitcell power in nW. Only characterizes leakage.""" + from tech import spice + leakage = spice["bitcell_leakage"] + # FIXME + dynamic = 0 + total_power = self.return_power(dynamic, leakage) + return total_power + + def input_load(self): + """ Return the relative capacitance of the access transistor gates """ + + # FIXME: This applies to bitline capacitances as well. + # FIXME: sizing is not accurate with the handmade cell. + # Change once cell widths are fixed. + access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"] + return 2 * access_tx_cin + + def get_wl_cin(self): + """Return the relative capacitance of the access transistor gates""" + # This is a handmade cell so the value must be entered + # in the tech.py file or estimated. + # Calculated in the tech file by summing the widths of all + # the related gates and dividing by the minimum width. + # FIXME: sizing is not accurate with the handmade cell. + # Change once cell widths are fixed. + access_tx_cin = parameter["6T_access_size"] / drc["minwidth_tx"] + return 2 * access_tx_cin + + def get_storage_net_names(self): + """ + Returns names of storage nodes in bitcell in + [non-inverting, inverting] format. + """ + # Checks that they do exist + if self.nets_match: + return self.storage_nets + else: + fmt_str = "Storage nodes={} not found in spice file." + debug.info(1, fmt_str.format(self.storage_nets)) + return None + + def build_graph(self, graph, inst_name, port_nets): + """ + By default, bitcells won't be part of the graph. + + """ + return diff --git a/compiler/bitcells/dummy_bitcell.py b/compiler/bitcells/dummy_bitcell.py index db748203..98da96e2 100644 --- a/compiler/bitcells/dummy_bitcell.py +++ b/compiler/bitcells/dummy_bitcell.py @@ -5,13 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design import debug import utils -from tech import GDS,layer,parameter,drc -import logical_effort +from tech import GDS, layer +import bitcell_base -class dummy_bitcell(design.design): + +class dummy_bitcell(bitcell_base.bitcell_base): """ A single bit cell (6T, 8T, etc.) This module implements the single memory cell used in the design. It is a hand-made cell, so @@ -20,29 +20,18 @@ class dummy_bitcell(design.design): """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] - (width,height) = utils.get_libcell_size("dummy_cell_6t", GDS["unit"], layer["boundary"]) + (width, height) = utils.get_libcell_size("dummy_cell_6t", + GDS["unit"], + layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "dummy_cell_6t", GDS["unit"]) def __init__(self, name=""): # Ignore the name argument - design.design.__init__(self, "dummy_cell_6t") + bitcell_base.bitcell_base.__init__(self, "dummy_cell_6t") debug.info(2, "Create dummy bitcell") self.width = dummy_bitcell.width self.height = dummy_bitcell.height self.pin_map = dummy_bitcell.pin_map - def analytical_power(self, corner, load): - """Bitcell power in nW. Only characterizes leakage.""" - from tech import spice - leakage = spice["bitcell_leakage"] - dynamic = 0 #temporary - total_power = self.return_power(dynamic, leakage) - return total_power - def get_wl_cin(self): - """Return the relative capacitance of the access transistor gates""" - #This is a handmade cell so the value must be entered in the tech.py file or estimated. - #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. - access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] - return 2*access_tx_cin diff --git a/compiler/bitcells/dummy_bitcell_1rw_1r.py b/compiler/bitcells/dummy_bitcell_1rw_1r.py index f8986f2d..401e9f85 100644 --- a/compiler/bitcells/dummy_bitcell_1rw_1r.py +++ b/compiler/bitcells/dummy_bitcell_1rw_1r.py @@ -5,12 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design import debug import utils -from tech import GDS,layer,drc,parameter +from tech import GDS, layer +import bitcell_base -class dummy_bitcell_1rw_1r(design.design): + +class dummy_bitcell_1rw_1r(bitcell_base.bitcell_base): """ A single bit cell which is forced to store a 0. This module implements the single memory cell used in the design. It @@ -18,13 +19,18 @@ class dummy_bitcell_1rw_1r(design.design): the technology library. """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", "INPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("dummy_cell_1rw_1r", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "dummy_cell_1rw_1r", GDS["unit"]) + type_list = ["OUTPUT", "OUTPUT", "OUTPUT", "OUTPUT", + "INPUT", "INPUT", "POWER", "GROUND"] + (width, height) = utils.get_libcell_size("dummy_cell_1rw_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "dummy_cell_1rw_1r", + GDS["unit"]) def __init__(self, name=""): - # Ignore the name argument - design.design.__init__(self, "dummy_cell_1rw_1r") + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "dummy_cell_1rw_1r") debug.info(2, "Create dummy bitcell 1rw+1r object") self.width = dummy_bitcell_1rw_1r.width @@ -32,14 +38,3 @@ class dummy_bitcell_1rw_1r(design.design): self.pin_map = dummy_bitcell_1rw_1r.pin_map self.add_pin_types(self.type_list) - def get_wl_cin(self): - """Return the relative capacitance of the access transistor gates""" - #This is a handmade cell so the value must be entered in the tech.py file or estimated. - #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. - #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. - access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] - return 2*access_tx_cin - - def build_graph(self, graph, inst_name, port_nets): - """Dummy bitcells are cannot form a path and be part of the timing graph""" - return diff --git a/compiler/bitcells/dummy_bitcell_1w_1r.py b/compiler/bitcells/dummy_bitcell_1w_1r.py index ef451b8c..54192f71 100644 --- a/compiler/bitcells/dummy_bitcell_1w_1r.py +++ b/compiler/bitcells/dummy_bitcell_1w_1r.py @@ -5,12 +5,13 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import design import debug import utils -from tech import GDS,layer,drc,parameter +from tech import GDS, layer +import bitcell_base -class dummy_bitcell_1w_1r(design.design): + +class dummy_bitcell_1w_1r(bitcell_base.bitcell_base): """ A single bit cell which is forced to store a 0. This module implements the single memory cell used in the design. It @@ -18,13 +19,18 @@ class dummy_bitcell_1w_1r(design.design): the technology library. """ pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"] - type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", "INPUT", "INPUT", "POWER", "GROUND"] - (width,height) = utils.get_libcell_size("dummy_cell_1w_1r", GDS["unit"], layer["boundary"]) - pin_map = utils.get_libcell_pins(pin_names, "dummy_cell_1w_1r", GDS["unit"]) + type_list = ["OUTPUT", "OUTPUT", "INPUT", "INPUT", + "INPUT", "INPUT", "POWER", "GROUND"] + (width, height) = utils.get_libcell_size("dummy_cell_1w_1r", + GDS["unit"], + layer["boundary"]) + pin_map = utils.get_libcell_pins(pin_names, + "dummy_cell_1w_1r", + GDS["unit"]) def __init__(self, name=""): - # Ignore the name argument - design.design.__init__(self, "dummy_cell_1w_1r") + # Ignore the name argument + bitcell_base.bitcell_base.__init__(self, "dummy_cell_1w_1r") debug.info(2, "Create dummy bitcell 1w+1r object") self.width = dummy_bitcell_1w_1r.width @@ -32,14 +38,4 @@ class dummy_bitcell_1w_1r(design.design): self.pin_map = dummy_bitcell_1w_1r.pin_map self.add_pin_types(self.type_list) - def get_wl_cin(self): - """Return the relative capacitance of the access transistor gates""" - #This is a handmade cell so the value must be entered in the tech.py file or estimated. - #Calculated in the tech file by summing the widths of all the related gates and dividing by the minimum width. - #FIXME: sizing is not accurate with the handmade cell. Change once cell widths are fixed. - access_tx_cin = parameter["6T_access_size"]/drc["minwidth_tx"] - return 2*access_tx_cin - def build_graph(self, graph, inst_name, port_nets): - """Dummy bitcells are cannot form a path and be part of the timing graph""" - return diff --git a/compiler/bitcells/pbitcell.py b/compiler/bitcells/pbitcell.py index 919a0316..bbfdf942 100644 --- a/compiler/bitcells/pbitcell.py +++ b/compiler/bitcells/pbitcell.py @@ -6,15 +6,16 @@ # All rights reserved. # import contact -import design import debug -from tech import drc, parameter, spice +from tech import drc, parameter from vector import vector from ptx import ptx from globals import OPTS import logical_effort +import bitcell_base -class pbitcell(design.design): + +class pbitcell(bitcell_base.bitcell_base): """ This module implements a parametrically sized multi-port bitcell, with a variable number of read/write, write, and read ports @@ -29,11 +30,13 @@ class pbitcell(design.design): self.replica_bitcell = replica_bitcell self.dummy_bitcell = dummy_bitcell - design.design.__init__(self, name) - info_string = "{0} rw ports, {1} w ports and {2} r ports".format(self.num_rw_ports, - self.num_w_ports, - self.num_r_ports) - debug.info(2, "create a multi-port bitcell with {}".format(info_string)) + bitcell_base.bitcell_base.__init__(self, name) + fmt_str = "{0} rw ports, {1} w ports and {2} r ports" + info_string = fmt_str.format(self.num_rw_ports, + self.num_w_ports, + self.num_r_ports) + debug.info(2, + "create a multi-port bitcell with {}".format(info_string)) self.add_comment(info_string) if self.dummy_bitcell: @@ -86,12 +89,13 @@ class pbitcell(design.design): if self.replica_bitcell: self.route_rbc_short() - # in netlist_only mode, calling offset_all_coordinates or translate_all will not be possible - # this function is not needed to calculate the dimensions of pbitcell in netlist_only mode though + # in netlist_only mode, calling offset_all_coordinates or + # translate_all will not be possible + # this function is not needed to calculate the dimensions + # of pbitcell in netlist_only mode though if not OPTS.netlist_only: self.translate_all(vector(self.leftmost_xpos, self.botmost_ypos)) - def add_pins(self): """ add pins and set names for bitlines and wordlines """ self.rw_bl_names = [] @@ -154,20 +158,20 @@ class pbitcell(design.design): # if there are any read/write ports, # then the inverter nmos is sized based the number of read/write ports if(self.num_rw_ports > 0): - inverter_nmos_width = self.num_rw_ports*parameter["6T_inv_nmos_size"] + inverter_nmos_width = self.num_rw_ports * parameter["6T_inv_nmos_size"] inverter_pmos_width = parameter["6T_inv_pmos_size"] readwrite_nmos_width = parameter["6T_access_size"] write_nmos_width = parameter["6T_access_size"] - read_nmos_width = 2*parameter["6T_inv_pmos_size"] + read_nmos_width = 2 * parameter["6T_inv_pmos_size"] # if there are no read/write ports, # then the inverter nmos is statically sized for the dual port case else: - inverter_nmos_width = 2*parameter["6T_inv_pmos_size"] + inverter_nmos_width = 2 * parameter["6T_inv_pmos_size"] inverter_pmos_width = parameter["6T_inv_pmos_size"] readwrite_nmos_width = parameter["6T_access_size"] write_nmos_width = parameter["6T_access_size"] - read_nmos_width = 2*parameter["6T_inv_pmos_size"] + read_nmos_width = 2 * parameter["6T_inv_pmos_size"] # create ptx for inverter transistors self.inverter_nmos = ptx(width=inverter_nmos_width, @@ -180,7 +184,7 @@ class pbitcell(design.design): # create ptx for readwrite transitors self.readwrite_nmos = ptx(width=readwrite_nmos_width, - tx_type="nmos") + tx_type="nmos") self.add_mod(self.readwrite_nmos) # create ptx for write transitors @@ -197,86 +201,108 @@ class pbitcell(design.design): """ Calculate transistor spacings """ # calculate metal contact extensions over transistor active - readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height \ - - self.readwrite_nmos.active_height) - write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height \ - - self.write_nmos.active_height) - read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height \ - - self.read_nmos.active_height) + readwrite_nmos_contact_extension = 0.5 * \ + (self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height) + write_nmos_contact_extension = 0.5 * \ + (self.write_nmos.active_contact.height - self.write_nmos.active_height) + read_nmos_contact_extension = 0.5 * \ + (self.read_nmos.active_contact.height - self.read_nmos.active_height) max_contact_extension = max(readwrite_nmos_contact_extension, write_nmos_contact_extension, read_nmos_contact_extension) # y-offset for the access transistor's gate contact self.gate_contact_yoffset = max_contact_extension + self.m2_space \ - + 0.5*max(contact.poly.height, contact.m1m2.height) + + 0.5 * max(contact.poly.height, contact.m1m2.height) # y-position of access transistors - self.port_ypos = self.m1_space + 0.5*contact.m1m2.height + self.gate_contact_yoffset + self.port_ypos = self.m1_space + 0.5 * contact.m1m2.height + self.gate_contact_yoffset # y-position of inverter nmos self.inverter_nmos_ypos = self.port_ypos # spacing between ports (same for read/write and write ports) - self.bitline_offset = -0.5*self.readwrite_nmos.active_width + 0.5*contact.m1m2.height \ + self.bitline_offset = -0.5 * self.readwrite_nmos.active_width \ + + 0.5 * contact.m1m2.height \ + self.m2_space + self.m2_width - m2_constraint = self.bitline_offset + self.m2_space + 0.5*contact.m1m2.height \ - - 0.5*self.readwrite_nmos.active_width - self.write_port_spacing = max(self.active_space, self.m1_space, m2_constraint) + m2_constraint = self.bitline_offset + self.m2_space \ + + 0.5 * contact.m1m2.height \ + - 0.5 * self.readwrite_nmos.active_width + self.write_port_spacing = max(self.active_space, + self.m1_space, + m2_constraint) self.read_port_spacing = self.bitline_offset + self.m2_space # spacing between cross coupled inverters self.inverter_to_inverter_spacing = contact.poly.width + self.m1_space # calculations related to inverter connections - inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) - inverter_nmos_contact_extension = 0.5*(self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height) - self.inverter_gap = max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + self.poly_to_polycontact + 2*contact.poly.width \ - + self.m1_space + inverter_pmos_contact_extension - self.cross_couple_lower_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ - + 0.5*contact.poly.width - self.cross_couple_upper_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + max(self.poly_to_active, self.m1_space + inverter_nmos_contact_extension) \ + inverter_pmos_contact_extension = 0.5 * \ + (self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) + inverter_nmos_contact_extension = 0.5 * \ + (self.inverter_nmos.active_contact.height - self.inverter_nmos.active_height) + self.inverter_gap = max(self.poly_to_active, + self.m1_space + inverter_nmos_contact_extension) \ + + self.poly_to_polycontact + 2 * contact.poly.width \ + + self.m1_space + inverter_pmos_contact_extension + self.cross_couple_lower_ypos = self.inverter_nmos_ypos \ + + self.inverter_nmos.active_height \ + + max(self.poly_to_active, + self.m1_space + inverter_nmos_contact_extension) \ + + 0.5 * contact.poly.width + self.cross_couple_upper_ypos = self.inverter_nmos_ypos \ + + self.inverter_nmos.active_height \ + + max(self.poly_to_active, + self.m1_space + inverter_nmos_contact_extension) \ + self.poly_to_polycontact \ - + 1.5*contact.poly.width + + 1.5 * contact.poly.width # spacing between wordlines (and gnd) - self.m1_offset = -0.5*self.m1_width + self.m1_offset = -0.5 * self.m1_width # spacing for vdd - implant_constraint = max(inverter_pmos_contact_extension, 0) + 2*self.implant_enclose_active \ - + 0.5*(contact.well.width - self.m1_width) + implant_constraint = max(inverter_pmos_contact_extension, 0) \ + + 2 * self.implant_enclose_active \ + + 0.5 * (contact.well.width - self.m1_width) metal1_constraint = max(inverter_pmos_contact_extension, 0) + self.m1_space self.vdd_offset = max(implant_constraint, metal1_constraint) + 0.5*self.m1_width # read port dimensions width_reduction = self.read_nmos.active_width - self.read_nmos.get_pin("D").cx() - self.read_port_width = 2*self.read_nmos.active_width - 2*width_reduction + self.read_port_width = 2 * self.read_nmos.active_width - 2 * width_reduction def calculate_postions(self): - """ Calculate positions that describe the edges and dimensions of the cell """ - self.botmost_ypos = self.m1_offset - self.total_ports*self.m1_pitch - self.topmost_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + self.inverter_gap + self.inverter_pmos.active_height \ + """ + Calculate positions that describe the edges + and dimensions of the cell + """ + self.botmost_ypos = self.m1_offset - self.total_ports * self.m1_pitch + self.topmost_ypos = self.inverter_nmos_ypos \ + + self.inverter_nmos.active_height \ + + self.inverter_gap \ + + self.inverter_pmos.active_height \ + self.vdd_offset - self.leftmost_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width \ - - self.num_rw_ports*(self.readwrite_nmos.active_width + self.write_port_spacing) \ - - self.num_w_ports*(self.write_nmos.active_width + self.write_port_spacing) \ - - self.num_r_ports*(self.read_port_width + self.read_port_spacing) \ - - self.bitline_offset - 0.5*contact.m1m2.width + self.leftmost_xpos = -0.5 * self.inverter_to_inverter_spacing \ + - self.inverter_nmos.active_width \ + - self.num_rw_ports * \ + (self.readwrite_nmos.active_width + self.write_port_spacing) \ + - self.num_w_ports * \ + (self.write_nmos.active_width + self.write_port_spacing) \ + - self.num_r_ports * \ + (self.read_port_width + self.read_port_spacing) \ + - self.bitline_offset - 0.5 * contact.m1m2.width - self.width = -2*self.leftmost_xpos + self.width = -2 * self.leftmost_xpos self.height = self.topmost_ypos - self.botmost_ypos - self.center_ypos = 0.5*(self.topmost_ypos + self.botmost_ypos) - + self.center_ypos = 0.5 * (self.topmost_ypos + self.botmost_ypos) def create_storage(self): """ - Creates the crossed coupled inverters that act as storage for the bitcell. - The stored value of the cell is denoted as "Q", and the inverted value as "Q_bar". + Creates the crossed coupled inverters that act + as storage for the bitcell. + The stored value of the cell is denoted as "Q", + and the inverted value as "Q_bar". """ # create active for nmos self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", @@ -297,94 +323,126 @@ class pbitcell(design.design): self.connect_inst(["vdd", self.Q, self.Q_bar, "vdd"]) def place_storage(self): - """ Places the transistors for the crossed coupled inverters in the bitcell """ + """ + Places the transistors for the crossed + coupled inverters in the bitcell + """ # calculate transistor offsets - left_inverter_xpos = -0.5*self.inverter_to_inverter_spacing - self.inverter_nmos.active_width - right_inverter_xpos = 0.5*self.inverter_to_inverter_spacing - inverter_pmos_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap + left_inverter_xpos = -0.5 * self.inverter_to_inverter_spacing \ + - self.inverter_nmos.active_width + right_inverter_xpos = 0.5 * self.inverter_to_inverter_spacing + inverter_pmos_ypos = self.inverter_nmos_ypos \ + + self.inverter_nmos.active_height \ + + self.inverter_gap # create active for nmos - self.inverter_nmos_left.place([left_inverter_xpos, self.inverter_nmos_ypos]) - self.inverter_nmos_right.place([right_inverter_xpos, self.inverter_nmos_ypos]) + self.inverter_nmos_left.place([left_inverter_xpos, + self.inverter_nmos_ypos]) + self.inverter_nmos_right.place([right_inverter_xpos, + self.inverter_nmos_ypos]) # create active for pmos - self.inverter_pmos_left.place([left_inverter_xpos, inverter_pmos_ypos]) - self.inverter_pmos_right.place([right_inverter_xpos, inverter_pmos_ypos]) + self.inverter_pmos_left.place([left_inverter_xpos, + inverter_pmos_ypos]) + self.inverter_pmos_right.place([right_inverter_xpos, + inverter_pmos_ypos]) - # update furthest left and right transistor edges (this will propagate to further transistor offset calculations) + # update furthest left and right transistor edges + # (this will propagate to further transistor offset calculations) self.left_building_edge = left_inverter_xpos - self.right_building_edge = right_inverter_xpos + self.inverter_nmos.active_width + self.right_building_edge = right_inverter_xpos \ + + self.inverter_nmos.active_width def route_storage(self): """ Routes inputs and outputs of inverters to cross couple them """ # connect input (gate) of inverters - self.add_path("poly", [self.inverter_nmos_left.get_pin("G").uc(), self.inverter_pmos_left.get_pin("G").bc()]) - self.add_path("poly", [self.inverter_nmos_right.get_pin("G").uc(), self.inverter_pmos_right.get_pin("G").bc()]) + self.add_path("poly", + [self.inverter_nmos_left.get_pin("G").uc(), + self.inverter_pmos_left.get_pin("G").bc()]) + self.add_path("poly", + [self.inverter_nmos_right.get_pin("G").uc(), + self.inverter_pmos_right.get_pin("G").bc()]) # connect output (drain/source) of inverters self.add_path("metal1", - [self.inverter_nmos_left.get_pin("D").uc(), self.inverter_pmos_left.get_pin("D").bc()], + [self.inverter_nmos_left.get_pin("D").uc(), + self.inverter_pmos_left.get_pin("D").bc()], width=contact.active.second_layer_width) self.add_path("metal1", - [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], + [self.inverter_nmos_right.get_pin("S").uc(), + self.inverter_pmos_right.get_pin("S").bc()], width=contact.active.second_layer_width) - # add contacts to connect gate poly to drain/source metal1 (to connect Q to Q_bar) - contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x + 0.5*contact.poly.height, + # add contacts to connect gate poly to drain/source + # metal1 (to connect Q to Q_bar) + contact_offset_left = vector(self.inverter_nmos_left.get_pin("D").rc().x \ + + 0.5 * contact.poly.height, self.cross_couple_upper_ypos) self.add_via_center(layers=("poly", "contact", "metal1"), offset=contact_offset_left, - directions=("H","H")) + directions=("H", "H")) - contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x - 0.5*contact.poly.height, + contact_offset_right = vector(self.inverter_nmos_right.get_pin("S").lc().x \ + - 0.5*contact.poly.height, self.cross_couple_lower_ypos) self.add_via_center(layers=("poly", "contact", "metal1"), offset=contact_offset_right, - directions=("H","H")) + directions=("H", "H")) # connect contacts to gate poly (cross couple connections) - gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, contact_offset_left.y) + gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").lc().x, + contact_offset_left.y) self.add_path("poly", [contact_offset_left, gate_offset_right]) - gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, contact_offset_right.y) + gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").rc().x, + contact_offset_right.y) self.add_path("poly", [contact_offset_right, gate_offset_left]) def route_rails(self): """ Adds gnd and vdd rails and connects them to the inverters """ # Add rails for vdd and gnd - gnd_ypos = self.m1_offset - self.total_ports*self.m1_pitch + gnd_ypos = self.m1_offset - self.total_ports * self.m1_pitch self.gnd_position = vector(0, gnd_ypos) self.add_rect_center(layer="metal1", offset=self.gnd_position, width=self.width) - self.add_power_pin("gnd", vector(0,gnd_ypos)) + self.add_power_pin("gnd", vector(0, gnd_ypos)) - vdd_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ - + self.inverter_gap + self.inverter_pmos.active_height \ + vdd_ypos = self.inverter_nmos_ypos \ + + self.inverter_nmos.active_height \ + + self.inverter_gap \ + + self.inverter_pmos.active_height \ + self.vdd_offset self.vdd_position = vector(0, vdd_ypos) self.add_rect_center(layer="metal1", offset=self.vdd_position, width=self.width) - self.add_power_pin("vdd", vector(0,vdd_ypos)) + self.add_power_pin("vdd", vector(0, vdd_ypos)) def create_readwrite_ports(self): """ - Creates read/write ports to the bit cell. A differential pair of transistor can both read and write, like in a 6T cell. - A read or write is enabled by setting a Read-Write-Wordline (RWWL) high, subsequently turning on the transistor. - The transistor is connected between a Read-Write-Bitline (RWBL) and the storage component of the cell (Q). - In a write operation, driving RWBL high or low sets the value of the cell. - In a read operation, RWBL is precharged, then is either remains high or is discharged depending on the value of the cell. - This is a differential design, so each write port has a mirrored port that connects RWBR to Q_bar. + Creates read/write ports to the bit cell. A differential + pair of transistor can both read and write, like in a 6T cell. + A read or write is enabled by setting a Read-Write-Wordline (RWWL) + high, subsequently turning on the transistor. + The transistor is connected between a Read-Write-Bitline (RWBL) + and the storage component of the cell (Q). + In a write operation, driving RWBL high or low sets the value + of the cell. + In a read operation, RWBL is precharged, then is either remains + high or is discharged depending on the value of the cell. + This is a differential design, so each write port has a mirrored + port that connects RWBR to Q_bar. """ - # define read/write transistor variables as empty arrays based on the number of read/write ports + # define read/write transistor variables as empty arrays based + # on the number of read/write ports self.readwrite_nmos_left = [None] * self.num_rw_ports self.readwrite_nmos_right = [None] * self.num_rw_ports # iterate over the number of read/write ports - for k in range(0,self.num_rw_ports): + for k in range(0, self.num_rw_ports): bl_name = self.rw_bl_names[k] br_name = self.rw_br_names[k] if self.dummy_bitcell: @@ -398,33 +456,37 @@ class pbitcell(design.design): self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k), mod=self.readwrite_nmos) - self.connect_inst([self.Q_bar, self.rw_wl_names[k], br_name, "gnd"]) + self.connect_inst([self.Q_bar, + self.rw_wl_names[k], br_name, "gnd"]) def place_readwrite_ports(self): """ Places read/write ports in the bit cell """ - # define read/write transistor variables as empty arrays based on the number of read/write ports + # define read/write transistor variables as empty arrays + # based on the number of read/write ports self.rwwl_positions = [None] * self.num_rw_ports self.rwbl_positions = [None] * self.num_rw_ports self.rwbr_positions = [None] * self.num_rw_ports # iterate over the number of read/write ports - for k in range(0,self.num_rw_ports): + for k in range(0, self.num_rw_ports): # calculate read/write transistor offsets left_readwrite_transistor_xpos = self.left_building_edge \ - - (k+1)*self.write_port_spacing \ - - (k+1)*self.readwrite_nmos.active_width + - (k + 1) * self.write_port_spacing \ + - (k + 1) * self.readwrite_nmos.active_width right_readwrite_transistor_xpos = self.right_building_edge \ - + (k+1)*self.write_port_spacing \ - + k*self.readwrite_nmos.active_width + + (k + 1) * self.write_port_spacing \ + + k * self.readwrite_nmos.active_width # place read/write transistors - self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, self.port_ypos]) + self.readwrite_nmos_left[k].place(offset=[left_readwrite_transistor_xpos, + self.port_ypos]) - self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, self.port_ypos]) + self.readwrite_nmos_right[k].place(offset=[right_readwrite_transistor_xpos, + self.port_ypos]) # add pin for RWWL - rwwl_ypos = self.m1_offset - k*self.m1_pitch + rwwl_ypos = self.m1_offset - k * self.m1_pitch self.rwwl_positions[k] = vector(0, rwwl_ypos) self.add_layout_pin_rect_center(text=self.rw_wl_names[k], layer="metal1", @@ -432,15 +494,19 @@ class pbitcell(design.design): width=self.width) # add pins for RWBL and RWBR - rwbl_xpos = left_readwrite_transistor_xpos - self.bitline_offset + 0.5*self.m2_width + rwbl_xpos = left_readwrite_transistor_xpos \ + - self.bitline_offset \ + + 0.5 * self.m2_width self.rwbl_positions[k] = vector(rwbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_bl_names[k], layer="metal2", offset=self.rwbl_positions[k], height=self.height) - rwbr_xpos = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width \ - + self.bitline_offset - 0.5*self.m2_width + rwbr_xpos = right_readwrite_transistor_xpos \ + + self.readwrite_nmos.active_width \ + + self.bitline_offset \ + - 0.5 * self.m2_width self.rwbr_positions[k] = vector(rwbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.rw_br_names[k], layer="metal2", @@ -449,22 +515,29 @@ class pbitcell(design.design): # update furthest left and right transistor edges self.left_building_edge = left_readwrite_transistor_xpos - self.right_building_edge = right_readwrite_transistor_xpos + self.readwrite_nmos.active_width + self.right_building_edge = right_readwrite_transistor_xpos \ + + self.readwrite_nmos.active_width def create_write_ports(self): """ - Creates write ports in the bit cell. A differential pair of transistors can write only. - A write is enabled by setting a Write-Rowline (WWL) high, subsequently turning on the transistor. - The transistor is connected between a Write-Bitline (WBL) and the storage component of the cell (Q). - In a write operation, driving WBL high or low sets the value of the cell. - This is a differential design, so each write port has a mirrored port that connects WBR to Q_bar. + Creates write ports in the bit cell. A differential pair of + transistors can write only. + A write is enabled by setting a Write-Rowline (WWL) high, + subsequently turning on the transistor. + The transistor is connected between a Write-Bitline (WBL) + and the storage component of the cell (Q). + In a write operation, driving WBL high or low sets the value + of the cell. + This is a differential design, so each write port has a + mirrored port that connects WBR to Q_bar. """ - # define write transistor variables as empty arrays based on the number of write ports + # define write transistor variables as empty arrays based + # on the number of write ports self.write_nmos_left = [None] * self.num_w_ports self.write_nmos_right = [None] * self.num_w_ports # iterate over the number of write ports - for k in range(0,self.num_w_ports): + for k in range(0, self.num_w_ports): bl_name = self.w_bl_names[k] br_name = self.w_br_names[k] if self.dummy_bitcell: @@ -482,31 +555,35 @@ class pbitcell(design.design): def place_write_ports(self): """ Places write ports in the bit cell """ - # define write transistor variables as empty arrays based on the number of write ports + # define write transistor variables as empty arrays based + # on the number of write ports self.wwl_positions = [None] * self.num_w_ports self.wbl_positions = [None] * self.num_w_ports self.wbr_positions = [None] * self.num_w_ports # iterate over the number of write ports - for k in range(0,self.num_w_ports): + for k in range(0, self.num_w_ports): # Add transistors # calculate write transistor offsets left_write_transistor_xpos = self.left_building_edge \ - - (k+1)*self.write_port_spacing \ - - (k+1)*self.write_nmos.active_width + - (k + 1) * self.write_port_spacing \ + - (k + 1) * self.write_nmos.active_width right_write_transistor_xpos = self.right_building_edge \ - + (k+1)*self.write_port_spacing \ - + k*self.write_nmos.active_width + + (k + 1) * self.write_port_spacing \ + + k * self.write_nmos.active_width # add write transistors - self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, self.port_ypos]) + self.write_nmos_left[k].place(offset=[left_write_transistor_xpos, + self.port_ypos]) - self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, self.port_ypos]) + self.write_nmos_right[k].place(offset=[right_write_transistor_xpos, + self.port_ypos]) # add pin for WWL - wwl_ypos = rwwl_ypos = self.m1_offset - self.num_rw_ports*self.m1_pitch \ - - k*self.m1_pitch + wwl_ypos = rwwl_ypos = self.m1_offset \ + - self.num_rw_ports * self.m1_pitch \ + - k * self.m1_pitch self.wwl_positions[k] = vector(0, wwl_ypos) self.add_layout_pin_rect_center(text=self.w_wl_names[k], layer="metal1", @@ -514,15 +591,19 @@ class pbitcell(design.design): width=self.width) # add pins for WBL and WBR - wbl_xpos = left_write_transistor_xpos - self.bitline_offset + 0.5*self.m2_width + wbl_xpos = left_write_transistor_xpos \ + - self.bitline_offset \ + + 0.5 * self.m2_width self.wbl_positions[k] = vector(wbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_bl_names[k], layer="metal2", offset=self.wbl_positions[k], height=self.height) - wbr_xpos = right_write_transistor_xpos + self.write_nmos.active_width + self.bitline_offset \ - - 0.5*self.m2_width + wbr_xpos = right_write_transistor_xpos \ + + self.write_nmos.active_width \ + + self.bitline_offset \ + - 0.5 * self.m2_width self.wbr_positions[k] = vector(wbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.w_br_names[k], layer="metal2", @@ -531,28 +612,38 @@ class pbitcell(design.design): # update furthest left and right transistor edges self.left_building_edge = left_write_transistor_xpos - self.right_building_edge = right_write_transistor_xpos + self.write_nmos.active_width + self.right_building_edge = right_write_transistor_xpos \ + + self.write_nmos.active_width def create_read_ports(self): """ - Creates read ports in the bit cell. A differential pair of ports can read only. - Two transistors function as a read port, denoted as the "read transistor" and the "read-access transistor". - The read transistor is connected to RWL (gate), RBL (drain), and the read-access transistor (source). - The read-access transistor is connected to Q_bar (gate), gnd (source), and the read transistor (drain). - A read is enabled by setting a Read-Rowline (RWL) high, subsequently turning on the read transistor. - The Read-Bitline (RBL) is precharged to high, and when the value of Q_bar is high, the read-access transistor - is turned on, creating a connection between RBL and gnd. RBL subsequently discharges allowing for a differential read - using sense amps. This is a differential design, so each read port has a mirrored port that connects RBL_bar to Q. + Creates read ports in the bit cell. A differential pair + of ports can read only. + Two transistors function as a read port, denoted as the + "read transistor" and the "read-access transistor". + The read transistor is connected to RWL (gate), RBL (drain), + and the read-access transistor (source). + The read-access transistor is connected to Q_bar (gate), + gnd (source), and the read transistor (drain). + A read is enabled by setting a Read-Rowline (RWL) high, + subsequently turning on the read transistor. + The Read-Bitline (RBL) is precharged to high, and when the + value of Q_bar is high, the read-access transistor + is turned on, creating a connection between RBL and gnd. + RBL subsequently discharges allowing for a differential read + using sense amps. This is a differential design, so each + read port has a mirrored port that connects RBL_bar to Q. """ - # define read transistor variables as empty arrays based on the number of read ports + # define read transistor variables as empty arrays based + # on the number of read ports self.read_nmos_left = [None] * self.num_r_ports self.read_nmos_right = [None] * self.num_r_ports self.read_access_nmos_left = [None] * self.num_r_ports self.read_access_nmos_right = [None] * self.num_r_ports # iterate over the number of read ports - for k in range(0,self.num_r_ports): + for k in range(0, self.num_r_ports): bl_name = self.r_bl_names[k] br_name = self.r_br_names[k] if self.dummy_bitcell: @@ -579,38 +670,47 @@ class pbitcell(design.design): def place_read_ports(self): """ Places the read ports in the bit cell """ - # define read transistor variables as empty arrays based on the number of read ports + # define read transistor variables as empty arrays based + # on the number of read ports self.rwl_positions = [None] * self.num_r_ports self.rbl_positions = [None] * self.num_r_ports self.rbr_positions = [None] * self.num_r_ports - # calculate offset to overlap the drain of the read-access transistor with the source of the read transistor - overlap_offset = self.read_nmos.get_pin("D").cx() - self.read_nmos.get_pin("S").cx() + # calculate offset to overlap the drain of the read-access transistor + # with the source of the read transistor + overlap_offset = self.read_nmos.get_pin("D").cx() \ + - self.read_nmos.get_pin("S").cx() # iterate over the number of read ports - for k in range(0,self.num_r_ports): + for k in range(0, self.num_r_ports): # calculate transistor offsets left_read_transistor_xpos = self.left_building_edge \ - - (k+1)*self.read_port_spacing \ - - (k+1)*self.read_port_width + - (k + 1) * self.read_port_spacing \ + - (k + 1) * self.read_port_width right_read_transistor_xpos = self.right_building_edge \ - + (k+1)*self.read_port_spacing \ - + k*self.read_port_width + + (k + 1) * self.read_port_spacing \ + + k * self.read_port_width # add read-access transistors - self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, self.port_ypos]) + self.read_access_nmos_left[k].place(offset=[left_read_transistor_xpos+overlap_offset, + self.port_ypos]) - self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, self.port_ypos]) + self.read_access_nmos_right[k].place(offset=[right_read_transistor_xpos, + self.port_ypos]) # add read transistors - self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, self.port_ypos]) + self.read_nmos_left[k].place(offset=[left_read_transistor_xpos, + self.port_ypos]) - self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, self.port_ypos]) + self.read_nmos_right[k].place(offset=[right_read_transistor_xpos+overlap_offset, + self.port_ypos]) # add pin for RWL - rwl_ypos = rwwl_ypos = self.m1_offset - self.num_rw_ports*self.m1_pitch \ - - self.num_w_ports*self.m1_pitch - k*self.m1_pitch + rwl_ypos = rwwl_ypos = self.m1_offset \ + - self.num_rw_ports * self.m1_pitch \ + - self.num_w_ports * self.m1_pitch \ + - k * self.m1_pitch self.rwl_positions[k] = vector(0, rwl_ypos) self.add_layout_pin_rect_center(text=self.r_wl_names[k], layer="metal1", @@ -618,15 +718,19 @@ class pbitcell(design.design): width=self.width) # add pins for RBL and RBR - rbl_xpos = left_read_transistor_xpos - self.bitline_offset + 0.5*self.m2_width + rbl_xpos = left_read_transistor_xpos \ + - self.bitline_offset \ + + 0.5 * self.m2_width self.rbl_positions[k] = vector(rbl_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_bl_names[k], layer="metal2", offset=self.rbl_positions[k], height=self.height) - rbr_xpos = right_read_transistor_xpos + self.read_port_width + self.bitline_offset \ - - 0.5*self.m2_width + rbr_xpos = right_read_transistor_xpos \ + + self.read_port_width \ + + self.bitline_offset \ + - 0.5 * self.m2_width self.rbr_positions[k] = vector(rbr_xpos, self.center_ypos) self.add_layout_pin_rect_center(text=self.r_br_names[k], layer="metal2", @@ -657,18 +761,22 @@ class pbitcell(design.design): wl_positions.append(self.rwl_positions[k]) wl_positions.append(self.rwl_positions[k]) - for k in range(2*self.total_ports): + for k in range(2 * self.total_ports): gate_offset = port_transistors[k].get_pin("G").bc() - port_contact_offset = gate_offset + vector(0, -self.gate_contact_yoffset + self.poly_extend_active) + port_contact_offset = gate_offset \ + + vector(0, + -self.gate_contact_yoffset + self.poly_extend_active) wl_contact_offset = vector(gate_offset.x, wl_positions[k].y) - # first transistor on either side of the cross coupled inverters does not need to route to wordline on metal2 + # first transistor on either side of the cross coupled inverters + # does not need to route to wordline on metal2 if (k == 0) or (k == 1): self.add_via_center(layers=("poly", "contact", "metal1"), offset=port_contact_offset) self.add_path("poly", [gate_offset, port_contact_offset]) - self.add_path("metal1", [port_contact_offset, wl_contact_offset]) + self.add_path("metal1", + [port_contact_offset, wl_contact_offset]) else: self.add_via_center(layers=("poly", "contact", "metal1"), @@ -678,10 +786,11 @@ class pbitcell(design.design): self.add_via_center(layers=("metal1", "via1", "metal2"), offset=wl_contact_offset, - directions=("H","H")) + directions=("H", "H")) self.add_path("poly", [gate_offset, port_contact_offset]) - self.add_path("metal2", [port_contact_offset, wl_contact_offset]) + self.add_path("metal2", + [port_contact_offset, wl_contact_offset]) def route_bitlines(self): """ Routes read/write transistors to their respective bitlines """ @@ -718,7 +827,8 @@ class pbitcell(design.design): self.add_via_center(layers=("metal1", "via1", "metal2"), offset=port_contact_offest) - self.add_path("metal2", [port_contact_offest, bl_offset], width=contact.m1m2.height) + self.add_path("metal2", + [port_contact_offest, bl_offset], width=contact.m1m2.height) for k in range(self.total_ports): port_contact_offest = right_port_transistors[k].get_pin("D").center() @@ -729,7 +839,8 @@ class pbitcell(design.design): self.add_via_center(layers=("metal1", "via1", "metal2"), offset=port_contact_offest) - self.add_path("metal2", [port_contact_offest, br_offset], width=contact.m1m2.height) + self.add_path("metal2", + [port_contact_offest, br_offset], width=contact.m1m2.height) def route_supply(self): """ Route inverter nmos and read-access nmos to gnd. Route inverter pmos to vdd. """ @@ -746,59 +857,87 @@ class pbitcell(design.design): offset=position) if position.x > 0: - contact_correct = 0.5*contact.m1m2.height + contact_correct = 0.5 * contact.m1m2.height else: - contact_correct = -0.5*contact.m1m2.height - supply_offset = vector(position.x + contact_correct, self.gnd_position.y) + contact_correct = -0.5 * contact.m1m2.height + supply_offset = vector(position.x + contact_correct, + self.gnd_position.y) self.add_via_center(layers=("metal1", "via1", "metal2"), offset=supply_offset, - directions=("H","H")) + directions=("H", "H")) self.add_path("metal2", [position, supply_offset]) # route inverter pmos to vdd - vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, self.vdd_position.y) - self.add_path("metal1", [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left]) + vdd_pos_left = vector(self.inverter_nmos_left.get_pin("S").uc().x, + self.vdd_position.y) + self.add_path("metal1", + [self.inverter_pmos_left.get_pin("S").uc(), vdd_pos_left]) - vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, self.vdd_position.y) - self.add_path("metal1", [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right]) + vdd_pos_right = vector(self.inverter_nmos_right.get_pin("D").uc().x, + self.vdd_position.y) + self.add_path("metal1", + [self.inverter_pmos_right.get_pin("D").uc(), vdd_pos_right]) def route_readwrite_access(self): - """ Routes read/write transistors to the storage component of the bitcell """ + """ + Routes read/write transistors to the storage + component of the bitcell + """ for k in range(self.num_rw_ports): - mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos) - Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) - self.add_path("metal1", [self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) + mid = vector(self.readwrite_nmos_left[k].get_pin("D").uc().x, + self.cross_couple_lower_ypos) + Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), + self.cross_couple_lower_ypos) + self.add_path("metal1", + [self.readwrite_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) - mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) - Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) - self.add_path("metal1", [self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) + mid = vector(self.readwrite_nmos_right[k].get_pin("S").uc().x, + self.cross_couple_lower_ypos) + Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), + self.cross_couple_lower_ypos) + self.add_path("metal1", + [self.readwrite_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) def route_write_access(self): - """ Routes read/write transistors to the storage component of the bitcell """ + """ + Routes read/write transistors to the storage + component of the bitcell + """ for k in range(self.num_w_ports): - mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, self.cross_couple_lower_ypos) - Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), self.cross_couple_lower_ypos) - self.add_path("metal1", [self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) + mid = vector(self.write_nmos_left[k].get_pin("D").uc().x, + self.cross_couple_lower_ypos) + Q_pos = vector(self.inverter_nmos_left.get_pin("D").lx(), + self.cross_couple_lower_ypos) + self.add_path("metal1", + [self.write_nmos_left[k].get_pin("D").uc(), mid, Q_pos]) - mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, self.cross_couple_lower_ypos) - Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), self.cross_couple_lower_ypos) - self.add_path("metal1", [self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) + mid = vector(self.write_nmos_right[k].get_pin("S").uc().x, + self.cross_couple_lower_ypos) + Q_bar_pos = vector(self.inverter_nmos_right.get_pin("S").rx(), + self.cross_couple_lower_ypos) + self.add_path("metal1", + [self.write_nmos_right[k].get_pin("S").uc(), mid, Q_bar_pos]) def route_read_access(self): - """ Routes read access transistors to the storage component of the bitcell """ + """ + Routes read access transistors to the storage + component of the bitcell + """ # add poly to metal1 contacts for gates of the inverters - left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - self.poly_to_polycontact - 0.5*contact.poly.width, + left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x \ + - self.poly_to_polycontact - 0.5*contact.poly.width, self.cross_couple_upper_ypos) self.add_via_center(layers=("poly", "contact", "metal1"), offset=left_storage_contact, - directions=("H","H")) + directions=("H", "H")) - right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + self.poly_to_polycontact + 0.5*contact.poly.width, + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x \ + + self.poly_to_polycontact + 0.5*contact.poly.width, self.cross_couple_upper_ypos) self.add_via_center(layers=("poly", "contact", "metal1"), offset=right_storage_contact, - directions=("H","H")) + directions=("H", "H")) inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_upper_ypos) self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) @@ -809,35 +948,48 @@ class pbitcell(design.design): # add poly to metal1 contacts for gates of read-access transistors # route from read-access contacts to inverter contacts on metal1 for k in range(self.num_r_ports): - port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active) + port_contact_offset = self.read_access_nmos_left[k].get_pin("G").uc() \ + + vector(0, + self.gate_contact_yoffset - self.poly_extend_active) self.add_via_center(layers=("poly", "contact", "metal1"), offset=port_contact_offset) - self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset]) + self.add_path("poly", + [self.read_access_nmos_left[k].get_pin("G").uc(), port_contact_offset]) - mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) - self.add_path("metal1", [port_contact_offset, mid, left_storage_contact]) + mid = vector(self.read_access_nmos_left[k].get_pin("G").uc().x, + self.cross_couple_upper_ypos) + self.add_path("metal1", + [port_contact_offset, mid, left_storage_contact]) - port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() + vector(0, self.gate_contact_yoffset - self.poly_extend_active) + port_contact_offset = self.read_access_nmos_right[k].get_pin("G").uc() \ + + vector(0, + self.gate_contact_yoffset - self.poly_extend_active) self.add_via_center(layers=("poly", "contact", "metal1"), offset=port_contact_offset) - self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset]) + self.add_path("poly", + [self.read_access_nmos_right[k].get_pin("G").uc(), port_contact_offset]) - mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, self.cross_couple_upper_ypos) - self.add_path("metal1", [port_contact_offset, mid, right_storage_contact]) + mid = vector(self.read_access_nmos_right[k].get_pin("G").uc().x, + self.cross_couple_upper_ypos) + self.add_path("metal1", + [port_contact_offset, mid, right_storage_contact]) def extend_well(self): """ - Connects wells between ptx modules and places well contacts""" - # extend pwell to encompass entire nmos region of the cell up to the height of the tallest nmos transistor + Connects wells between ptx modules and places well contacts + """ + # 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) - well_height = max_nmos_well_height + self.port_ypos - self.well_enclose_active - self.gnd_position.y + 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.add_rect(layer="pwell", offset=offset, @@ -846,15 +998,19 @@ class pbitcell(design.design): # 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 - inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height + self.inverter_gap - self.well_enclose_active + inverter_well_xpos = -(self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ + - self.well_enclose_active + inverter_well_ypos = self.inverter_nmos_ypos + self.inverter_nmos.active_height \ + + self.inverter_gap - self.well_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 - well_height = self.vdd_position.y - inverter_well_ypos + self.well_enclose_active + drc["minwidth_tx"] + well_width = 2 * (self.inverter_nmos.active_width + 0.5 * self.inverter_to_inverter_spacing) \ + + 2 * self.well_enclose_active + well_height = self.vdd_position.y - inverter_well_ypos \ + + self.well_enclose_active + drc["minwidth_tx"] - offset = [inverter_well_xpos,inverter_well_ypos] + offset = [inverter_well_xpos, inverter_well_ypos] self.add_rect(layer="nwell", offset=offset, width=well_width, @@ -865,7 +1021,7 @@ class pbitcell(design.design): offset = vector(0, self.gnd_position.y) self.add_via_center(layers=("active", "contact", "metal1"), offset=offset, - directions=("H","H"), + directions=("H", "H"), implant_type="p", well_type="p") @@ -873,18 +1029,21 @@ class pbitcell(design.design): offset = vector(0, self.vdd_position.y) self.add_via_center(layers=("active", "contact", "metal1"), offset=offset, - directions=("H","H"), + directions=("H", "H"), implant_type="n", well_type="n") def get_bitcell_pins(self, col, row): - """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ + """ + Creates a list of connections in the bitcell, indexed by column + and row, for instance use in bitcell_array + """ bitcell_pins = [] for port in range(self.total_ports): - bitcell_pins.append("bl{0}_{1}".format(port,col)) - bitcell_pins.append("br{0}_{1}".format(port,col)) + bitcell_pins.append("bl{0}_{1}".format(port, col)) + bitcell_pins.append("br{0}_{1}".format(port, col)) for port in range(self.total_ports): - bitcell_pins.append("wl{0}_{1}".format(port,row)) + bitcell_pins.append("wl{0}_{1}".format(port, row)) bitcell_pins.append("vdd") bitcell_pins.append("gnd") return bitcell_pins @@ -911,13 +1070,19 @@ class pbitcell(design.design): return self.rw_br_names + self.w_br_names + self.r_br_names def route_rbc_short(self): - """ route the short from Q_bar to gnd necessary for the replica bitcell """ + """ + Route the short from Q_bar to gnd necessary for + the replica bitcell + """ Q_bar_pos = self.inverter_pmos_right.get_pin("S").center() vdd_pos = self.inverter_pmos_right.get_pin("D").center() self.add_path("metal1", [Q_bar_pos, vdd_pos]) def get_storage_net_names(self): - """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + """ + Returns names of storage nodes in bitcell in + [non-inverting, inverting] format. + """ return self.storage_nets def get_bl_name(self, port=0): @@ -930,51 +1095,55 @@ class pbitcell(design.design): def get_wl_name(self, port=0): """Get wl name by port""" - debug.check(port<2,"Two ports for bitcell_1rw_1r only.") + debug.check(port < 2, "Two ports for bitcell_1rw_1r only.") return "wl{}".format(port) - def get_stage_effort(self, load): parasitic_delay = 1 - size = 0.5 #This accounts for bitline being drained thought the access TX and internal node - cin = 3 #Assumes always a minimum sizes inverter. Could be specified in the tech.py file. + # This accounts for bitline being drained thought the access + # TX and internal node + size = 0.5 + # Assumes always a minimum sizes inverter. Could be + # specified in the tech.py file. + cin = 3 - #Internal loads due to port configs are halved. This is to account for the size already being halved - #for stacked TXs, but internal loads do not see this size estimation. - write_port_load = self.num_w_ports*logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap'])/2 - read_port_load = self.num_r_ports/2 #min size NMOS gate load - total_load = load+read_port_load+write_port_load - return logical_effort.logical_effort('bitline', size, cin, load+read_port_load, parasitic_delay, False) - - def analytical_power(self, corner, load): - """Bitcell power in nW. Only characterizes leakage.""" - from tech import spice - leakage = spice["bitcell_leakage"] - dynamic = 0 #temporary - total_power = self.return_power(dynamic, leakage) - return total_power + # Internal loads due to port configs are halved. + # This is to account for the size already being halved + # for stacked TXs, but internal loads do not see this size + # estimation. + write_port_load = self.num_w_ports \ + * logical_effort.convert_farad_to_relative_c(parameter['bitcell_drain_cap']) / 2 + # min size NMOS gate load + read_port_load = self.num_r_ports / 2 + total_load = load + read_port_load + write_port_load + return logical_effort.logical_effort('bitline', + size, + cin, + load + read_port_load, + parasitic_delay, + False) def input_load(self): - """Return the relative capacitance of the access transistor gates""" + """ Return the relative capacitance of the access transistor gates """ # FIXME: This applies to bitline capacitances as well. # pbitcell uses the different sizing for the port access tx's. Not accounted for in this model. access_tx_cin = self.readwrite_nmos.get_cin() - return 2*access_tx_cin + return 2 * access_tx_cin - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges to graph for pbitcell. Only readwrite and read ports.""" if self.dummy_bitcell: return - pin_dict = {pin:port for pin,port in zip(self.pins, port_nets)} + pin_dict = {pin: port for pin, port in zip(self.pins, port_nets)} # Edges added wl->bl, wl->br for every port except write ports rw_pin_names = zip(self.r_wl_names, self.r_bl_names, self.r_br_names) r_pin_names = zip(self.rw_wl_names, self.rw_bl_names, self.rw_br_names) - for pin_zip in [rw_pin_names, r_pin_names]: - for wl,bl,br in pin_zip: - graph.add_edge(pin_dict[wl],pin_dict[bl], self) - graph.add_edge(pin_dict[wl],pin_dict[br], self) + for pin_zip in [rw_pin_names, r_pin_names]: + for wl, bl, br in pin_zip: + graph.add_edge(pin_dict[wl], pin_dict[bl], self) + graph.add_edge(pin_dict[wl], pin_dict[br], self) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 31dc898a..2a8d5293 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -1180,44 +1180,57 @@ class delay(simulation): wmask_zeroes = "0"*self.num_wmasks if self.t_current == 0: - self.add_noop_all_ports("Idle cycle (no positive clock edge)", - inverse_address, data_zeros,wmask_zeroes) + self.add_noop_all_ports("Idle cycle (no positive clock edge)") self.add_write("W data 1 address {}".format(inverse_address), - inverse_address,data_ones,wmask_ones,write_port) + inverse_address, + data_ones, + wmask_ones, + write_port) self.add_write("W data 0 address {} to write value".format(self.probe_address), - self.probe_address,data_zeros,wmask_ones,write_port) + self.probe_address, + data_zeros, + wmask_ones, + write_port) self.measure_cycles[write_port][sram_op.WRITE_ZERO] = len(self.cycle_times)-1 # This also ensures we will have a H->L transition on the next read self.add_read("R data 1 address {} to set dout caps".format(inverse_address), - inverse_address,data_zeros,wmask_ones,read_port) + inverse_address, + read_port) self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), - self.probe_address,data_zeros,wmask_ones,read_port) + self.probe_address, + read_port) self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times)-1 - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)", - inverse_address,data_zeros,wmask_zeroes) + self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)") self.add_write("W data 1 address {} to write value".format(self.probe_address), - self.probe_address,data_ones,wmask_ones,write_port) + self.probe_address, + data_ones, + wmask_ones, + write_port) self.measure_cycles[write_port][sram_op.WRITE_ONE] = len(self.cycle_times)-1 self.add_write("W data 0 address {} to clear din caps".format(inverse_address), - inverse_address,data_zeros,wmask_ones,write_port) + inverse_address, + data_zeros, + wmask_ones, + write_port) # This also ensures we will have a L->H transition on the next read self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), - inverse_address,data_zeros,wmask_ones,read_port) + inverse_address, + read_port) self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), - self.probe_address,data_zeros,wmask_ones,read_port) + self.probe_address, + read_port) self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times)-1 - self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))", - self.probe_address,data_zeros,wmask_zeroes) + self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))") def get_available_port(self,get_read_port): diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index 49907b4b..6d827aa3 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -6,6 +6,7 @@ # All rights reserved. # import sys,re,shutil +import copy import collections from design import design import debug @@ -49,28 +50,19 @@ class functional(simulation): self.create_graph() self.set_internal_spice_names() - self.initialize_wmask() - # Number of checks can be changed self.num_cycles = 15 # This is to have ordered keys for random selection self.stored_words = collections.OrderedDict() - self.write_check = [] self.read_check = [] + self.read_results = [] - def initialize_wmask(self): - self.wmask = "" - if self.write_size: - # initialize all wmask bits to 1 - for bit in range(self.num_wmasks): - self.wmask += "1" - def run(self, feasible_period=None): if feasible_period: #period defaults to tech.py feasible period otherwise. self.period = feasible_period # Generate a random sequence of reads and writes - self.write_random_memory_sequence() + self.create_random_memory_sequence() # Run SPICE simulation self.write_functional_stimulus() @@ -83,8 +75,26 @@ class functional(simulation): # Check read values with written values. If the values do not match, return an error. return self.check_stim_results() - - def write_random_memory_sequence(self): + + def check_lengths(self): + """ Do a bunch of assertions. """ + + for port in self.all_ports: + checks = [] + if port in self.read_ports: + checks.append((self.addr_value[port],"addr")) + if port in self.write_ports: + checks.append((self.data_value[port],"data")) + checks.append((self.wmask_value[port],"wmask")) + + for (val, name) in checks: + debug.check(len(self.cycle_times)==len(val), + "Port {2} lengths don't match. {0} clock values, {1} {3} values".format(len(self.cycle_times), + len(val), + port, + name)) + + def create_random_memory_sequence(self): if self.write_size: rw_ops = ["noop", "write", "partial_write", "read"] w_ops = ["noop", "write", "partial_write"] @@ -92,35 +102,45 @@ class functional(simulation): rw_ops = ["noop", "write", "read"] w_ops = ["noop", "write"] r_ops = ["noop", "read"] - rw_read_din_data = "0"*self.word_size - check = 0 - # First cycle idle - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, self.wmask, 0, self.t_current) - self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks) - - # Write at least once - addr = self.gen_addr() - word = self.gen_data() - comment = self.gen_cycle_comment("write", word, addr, self.wmask, 0, self.t_current) - self.add_write(comment, addr, word, self.wmask, 0) - self.stored_words[addr] = word + # First cycle idle is always an idle cycle + comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) + self.add_noop_all_ports(comment) - # Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously. - # This will test the viablilty of the transistor sizing in the bitcell. - for port in self.all_ports: - if port in self.write_ports: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) - else: - comment = self.gen_cycle_comment("read", word, addr, self.wmask, port, self.t_current) - self.add_read_one_port(comment, addr, rw_read_din_data, "0"*self.num_wmasks, port) - self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check]) - check += 1 + # 1. Write all the write ports first to seed a bunch of locations. + for port in self.write_ports: + addr = self.gen_addr() + word = self.gen_data() + comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) + self.stored_words[addr] = word + + # All other read-only ports are noops. + for port in self.read_ports: + if port not in self.write_ports: + self.add_noop_one_port(port) self.cycle_times.append(self.t_current) self.t_current += self.period + self.check_lengths() - # Perform a random sequence of writes and reads on random ports, using random addresses and random words - # and random write masks (if applicable) + # 2. Read at least once. For multiport, it is important that one + # read cycle uses all RW and R port to read from the same + # address simultaniously. This will test the viablilty of the + # transistor sizing in the bitcell. + for port in self.all_ports: + if port in self.write_ports: + self.add_noop_one_port(port) + else: + comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + self.add_read_one_port(comment, addr, port) + self.add_read_check(word, port) + self.cycle_times.append(self.t_current) + self.t_current += self.period + self.check_lengths() + + # 3. Perform a random sequence of writes and reads on random + # ports, using random addresses and random words and random + # write masks (if applicable) for i in range(self.num_cycles): w_addrs = [] for port in self.all_ports: @@ -132,63 +152,79 @@ class functional(simulation): op = random.choice(r_ops) if op == "noop": - addr = "0"*self.addr_size - word = "0"*self.word_size - wmask = "0" * self.num_wmasks - self.add_noop_one_port(addr, word, wmask, port) + self.add_noop_one_port(port) elif op == "write": addr = self.gen_addr() - word = self.gen_data() # two ports cannot write to the same address if addr in w_addrs: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) + self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("write", word, addr, self.wmask, port, self.t_current) - self.add_write_one_port(comment, addr, word, self.wmask, port) + word = self.gen_data() + comment = self.gen_cycle_comment("write", word, addr, "1"*self.num_wmasks, port, self.t_current) + self.add_write_one_port(comment, addr, word, "1"*self.num_wmasks, port) self.stored_words[addr] = word w_addrs.append(addr) elif op == "partial_write": # write only to a word that's been written to (addr,old_word) = self.get_data() - word = self.gen_data() - wmask = self.gen_wmask() - new_word = word - for bit in range(len(wmask)): - # When the write mask's bits are 0, the old data values should appear in the new word - # as to not overwrite the old values - if wmask[bit] == "0": - lower = bit * self.write_size - upper = lower + self.write_size - 1 - new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] # two ports cannot write to the same address if addr in w_addrs: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) + self.add_noop_one_port(port) else: + word = self.gen_data() + wmask = self.gen_wmask() + new_word = self.gen_masked_data(old_word, word, wmask) comment = self.gen_cycle_comment("partial_write", word, addr, wmask, port, self.t_current) self.add_write_one_port(comment, addr, word, wmask, port) self.stored_words[addr] = new_word w_addrs.append(addr) else: (addr,word) = random.choice(list(self.stored_words.items())) - # cannot read from an address that is currently being written to + # The write driver is not sized sufficiently to drive through the two + # bitcell access transistors to the read port. So, for now, we do not allow + # a simultaneous write and read to the same address on different ports. This + # could be even more difficult with multiple simultaneous read ports. if addr in w_addrs: - self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks, port) + self.add_noop_one_port(port) else: - comment = self.gen_cycle_comment("read", word, addr, self.wmask, port, self.t_current) - self.add_read_one_port(comment, addr, rw_read_din_data, "0"*self.num_wmasks, port) - self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check]) - check += 1 + comment = self.gen_cycle_comment("read", word, addr, "0"*self.num_wmasks, port, self.t_current) + self.add_read_one_port(comment, addr, port) + self.add_read_check(word, port) self.cycle_times.append(self.t_current) self.t_current += self.period # Last cycle idle needed to correctly measure the value on the second to last clock edge - comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, self.wmask, 0, self.t_current) - self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size, "0"*self.num_wmasks) - + comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, "0"*self.num_wmasks, 0, self.t_current) + self.add_noop_all_ports(comment) + + def gen_masked_data(self, old_word, word, wmask): + """ Create the masked data word """ + # Start with the new word + new_word = word + + # When the write mask's bits are 0, the old data values should appear in the new word + # as to not overwrite the old values + for bit in range(len(wmask)): + if wmask[bit] == "0": + lower = bit * self.write_size + upper = lower + self.write_size - 1 + new_word = new_word[:lower] + old_word[lower:upper+1] + new_word[upper + 1:] + + return new_word + + def add_read_check(self, word, port): + """ Add to the check array to ensure a read works. """ + try: + self.check + except: + self.check = 0 + self.read_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, self.check]) + self.check += 1 + def read_stim_results(self): - # Extrat dout values from spice timing.lis - for (word, dout_port, eo_period, check) in self.write_check: + # Extract dout values from spice timing.lis + for (word, dout_port, eo_period, check) in self.read_check: sp_read_value = "" for bit in range(self.word_size): value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check)) @@ -205,17 +241,17 @@ class functional(simulation): self.v_high) return (0, error) - self.read_check.append([sp_read_value, dout_port, eo_period, check]) + self.read_results.append([sp_read_value, dout_port, eo_period, check]) return (1, "SUCCESS") def check_stim_results(self): - for i in range(len(self.write_check)): - if self.write_check[i][0] != self.read_check[i][0]: - error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_check[i][1], + for i in range(len(self.read_check)): + if self.read_check[i][0] != self.read_results[i][0]: + error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_results[i][1], + self.read_results[i][0], self.read_check[i][0], - self.write_check[i][0], - int((self.read_check[i][2]-self.period)/self.period), - self.read_check[i][2]) + int((self.read_results[i][2]-self.period)/self.period), + self.read_results[i][2]) return(0, error) return(1, "SUCCESS") @@ -359,7 +395,7 @@ class functional(simulation): # Generate dout value measurements self.sf.write("\n * Generation of dout measurements\n") - for (word, dout_port, eo_period, check) in self.write_check: + for (word, dout_port, eo_period, check) in self.read_check: t_intital = eo_period - 0.01*self.period t_final = eo_period + 0.01*self.period for bit in range(self.word_size): diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 5203c732..adbe5f5f 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -60,8 +60,10 @@ class simulation(): port_info=(len(self.all_ports),self.write_ports,self.read_ports), abits=self.addr_size, dbits=self.word_size) - debug.check(len(self.sram.pins) == len(self.pins), "Number of pins generated for characterization \ - do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins,self.pins)) + debug.check(len(self.sram.pins) == len(self.pins), + "Number of pins generated for characterization \ + do match pins of SRAM\nsram.pins = {0}\npin_names = {1}".format(self.sram.pins, + self.pins)) #This is TODO once multiport control has been finalized. #self.control_name = "CSB" @@ -71,13 +73,18 @@ class simulation(): self.t_current = 0 # control signals: only one cs_b for entire multiported sram, one we_b for each write port - self.csb_values = [[] for port in self.all_ports] - self.web_values = [[] for port in self.readwrite_ports] - - # Three dimensional list to handle each addr and data bits for wach port over the number of checks - self.addr_values = [[[] for bit in range(self.addr_size)] for port in self.all_ports] - self.data_values = [[[] for bit in range(self.word_size)] for port in self.write_ports] - self.wmask_values = [[[] for bit in range(self.num_wmasks)] for port in self.write_ports] + self.csb_values = {port:[] for port in self.all_ports} + self.web_values = {port:[] for port in self.readwrite_ports} + + # Raw values added as a bit vector + self.addr_value = {port:[] for port in self.all_ports} + self.data_value = {port:[] for port in self.write_ports} + self.wmask_value = {port:[] for port in self.write_ports} + + # Three dimensional list to handle each addr and data bits for each port over the number of checks + self.addr_values = {port:[[] for bit in range(self.addr_size)] for port in self.all_ports} + self.data_values = {port:[[] for bit in range(self.word_size)] for port in self.write_ports} + self.wmask_values = {port:[[] for bit in range(self.num_wmasks)] for port in self.write_ports} # For generating comments in SPICE stimulus self.cycle_comments = [] @@ -105,7 +112,8 @@ class simulation(): def add_data(self, data, port): """ Add the array of data values """ debug.check(len(data)==self.word_size, "Invalid data word size.") - + + self.data_value[port].append(data) bit = self.word_size - 1 for c in data: if c=="0": @@ -116,10 +124,12 @@ class simulation(): debug.error("Non-binary data string",1) bit -= 1 + def add_address(self, address, port): """ Add the array of address values """ debug.check(len(address)==self.addr_size, "Invalid address size.") + self.addr_value[port].append(address) bit = self.addr_size - 1 for c in address: if c=="0": @@ -130,10 +140,12 @@ class simulation(): debug.error("Non-binary address string",1) bit -= 1 + def add_wmask(self, wmask, port): """ Add the array of address values """ debug.check(len(wmask) == self.num_wmasks, "Invalid wmask size.") + self.wmask_value[port].append(wmask) bit = self.num_wmasks - 1 for c in wmask: if c == "0": @@ -143,10 +155,13 @@ class simulation(): else: debug.error("Non-binary wmask string", 1) bit -= 1 - + + def add_write(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. """ - debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports)) + debug.check(port in self.write_ports, + "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, + self.write_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.append_cycle_comment(port, comment) @@ -159,16 +174,16 @@ class simulation(): self.add_address(address,port) self.add_wmask(wmask,port) - #This value is hard coded here. Possibly change to member variable or set in add_noop_one_port - noop_data = "0"*self.word_size #Add noops to all other ports. for unselected_port in self.all_ports: if unselected_port != port: - self.add_noop_one_port(address, noop_data, wmask, unselected_port) + self.add_noop_one_port(unselected_port) - def add_read(self, comment, address, din_data, wmask, port): + def add_read(self, comment, address, port): """ Add the control values for a read cycle. """ - debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports)) + debug.check(port in self.read_ports, + "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, + self.read_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.append_cycle_comment(port, comment) @@ -176,21 +191,26 @@ class simulation(): self.cycle_times.append(self.t_current) self.t_current += self.period self.add_control_one_port(port, "read") - - #If the port is also a readwrite then add data. - if port in self.write_ports: - self.add_data(din_data,port) - self.add_wmask(wmask,port) self.add_address(address, port) - #This value is hard coded here. Possibly change to member variable or set in add_noop_one_port - noop_data = "0"*self.word_size + # If the port is also a readwrite then add + # the same value as previous cycle + if port in self.write_ports: + try: + self.add_data(self.data_value[port][-1], port) + except: + self.add_data("0"*self.word_size, port) + try: + self.add_wmask(self.wmask_value[port][-1], port) + except: + self.add_wmask("0"*self.num_wmasks, port) + #Add noops to all other ports. for unselected_port in self.all_ports: if unselected_port != port: - self.add_noop_one_port(address, noop_data, wmask, unselected_port) + self.add_noop_one_port(unselected_port) - def add_noop_all_ports(self, comment, address, data, wmask): + def add_noop_all_ports(self, comment): """ Add the control values for a noop to all ports. """ debug.info(2, comment) self.fn_cycle_comments.append(comment) @@ -200,39 +220,64 @@ class simulation(): self.t_current += self.period for port in self.all_ports: - self.add_noop_one_port(address, data, wmask, port) + self.add_noop_one_port(port) def add_write_one_port(self, comment, address, data, wmask, port): """ Add the control values for a write cycle. Does not increment the period. """ - debug.check(port in self.write_ports, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_ports)) + debug.check(port in self.write_ports, + "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, + self.write_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.add_control_one_port(port, "write") - self.add_data(data,port) - self.add_address(address,port) - self.add_wmask(wmask,port) + self.add_data(data, port) + self.add_address(address, port) + self.add_wmask(wmask, port) - def add_read_one_port(self, comment, address, din_data, wmask, port): + def add_read_one_port(self, comment, address, port): """ Add the control values for a read cycle. Does not increment the period. """ - debug.check(port in self.read_ports, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_ports)) + debug.check(port in self.read_ports, + "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, + self.read_ports)) debug.info(2, comment) self.fn_cycle_comments.append(comment) self.add_control_one_port(port, "read") - #If the port is also a readwrite then add data. - if port in self.write_ports: - self.add_data(din_data,port) - self.add_wmask(wmask,port) self.add_address(address, port) + # If the port is also a readwrite then add + # the same value as previous cycle + if port in self.write_ports: + try: + self.add_data(self.data_value[port][-1], port) + except: + self.add_data("0"*self.word_size, port) + try: + self.add_wmask(self.wmask_value[port][-1], port) + except: + self.add_wmask("0"*self.num_wmasks, port) + - def add_noop_one_port(self, address, data, wmask, port): + def add_noop_one_port(self, port): """ Add the control values for a noop to a single port. Does not increment the period. """ self.add_control_one_port(port, "noop") + + try: + self.add_address(self.addr_value[port][-1], port) + except: + self.add_address("0"*self.addr_size, port) + + # If the port is also a readwrite then add + # the same value as previous cycle if port in self.write_ports: - self.add_data(data,port) - self.add_wmask(wmask,port) - self.add_address(address, port) + try: + self.add_data(self.data_value[port][-1], port) + except: + self.add_data("0"*self.word_size, port) + try: + self.add_wmask(self.wmask_value[port][-1], port) + except: + self.add_wmask("0"*self.num_wmasks, port) def append_cycle_comment(self, port, comment): """Add comment to list to be printed in stimulus file""" @@ -240,16 +285,16 @@ class simulation(): time = "{0:.2f} ns:".format(self.t_current) time_spacing = len(time)+6 self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times), - port, - time, - time_spacing, - comment)) + port, + time, + time_spacing, + comment)) def gen_cycle_comment(self, op, word, addr, wmask, port, t_current): if op == "noop": comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period), - t_current, - t_current+self.period) + t_current, + t_current+self.period) elif op == "write": comment = "\tWriting {0} to address {1} (from port {2}) during cycle {3} ({4}ns - {5}ns)".format(word, addr, diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 41a3428f..58a9e3ed 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -96,22 +96,6 @@ class stimuli(): self.sf.write(".ENDS test_{0}\n\n".format(buffer_name)) - def inst_buffer(self, buffer_name, signal_list): - """ Adds buffers to each top level signal that is in signal_list (only for sim purposes) """ - for signal in signal_list: - self.sf.write("X{0}_buffer {0} {0}_buf {1} {2} test_{3}\n".format(signal, - "test"+self.vdd_name, - "test"+self.gnd_name, - buffer_name)) - - - def inst_inverter(self, signal_list): - """ Adds inv for each signal that needs its inverted version (only for sim purposes) """ - for signal in signal_list: - self.sf.write("X{0}_inv {0} {0}_inv {1} {2} test_inv\n".format(signal, - "test"+self.vdd_name, - "test"+self.gnd_name)) - def gen_pulse(self, sig_name, v1, v2, offset, period, t_rise, t_fall): """ @@ -276,9 +260,6 @@ class stimuli(): """ Writes supply voltage statements """ gnd_node_name = "0" self.sf.write("V{0} {0} {1} {2}\n".format(self.vdd_name, gnd_node_name, self.voltage)) - # This is for the test power supply - self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.vdd_name, gnd_node_name, self.voltage)) - self.sf.write("V{0} {0} {1} {2}\n".format("test"+self.gnd_name, gnd_node_name, 0.0)) #Adding a commented out supply for simulators where gnd and 0 are not global grounds. self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n") diff --git a/compiler/debug.py b/compiler/debug.py index 02a28c22..15876f22 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -94,7 +94,7 @@ def info(lev, str): frm = inspect.stack()[1] mod = inspect.getmodule(frm[0]) # classname = frm.f_globals['__name__'] - if mod.__name__ == None: + if mod.__name__ is None: class_name = "" else: class_name = mod.__name__ diff --git a/compiler/example_configs/big_config_scn4m_subm.py b/compiler/example_configs/big_config_scn4m_subm.py index 4fa4de8d..279fa04a 100644 --- a/compiler/example_configs/big_config_scn4m_subm.py +++ b/compiler/example_configs/big_config_scn4m_subm.py @@ -3,11 +3,11 @@ num_words = 128 tech_name = "scn4m_subm" process_corners = ["TT"] -supply_voltages = [ 5.0 ] -temperatures = [ 25 ] +supply_voltages = [5.0] +temperatures = [25] output_path = "temp" -output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_{0}_{1}_{2}".format(word_size, num_words, tech_name) drc_name = "magic" lvs_name = "netgen" diff --git a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py index 5dad0207..ecb03395 100644 --- a/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py +++ b/compiler/example_configs/example_config_1rw_1r_scn4m_subm.py @@ -14,7 +14,9 @@ route_supplies = True check_lvsdrc = True output_path = "temp" -output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_1rw_1r_{0}_{1}_{2}".format(word_size, + num_words, + tech_name) drc_name = "magic" lvs_name = "netgen" diff --git a/compiler/example_configs/example_config_1w_1r_scn4m_subm.py b/compiler/example_configs/example_config_1w_1r_scn4m_subm.py index c698a035..4b6584d4 100644 --- a/compiler/example_configs/example_config_1w_1r_scn4m_subm.py +++ b/compiler/example_configs/example_config_1w_1r_scn4m_subm.py @@ -14,7 +14,9 @@ route_supplies = True check_lvsdrc = True output_path = "temp" -output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_1w_1r_{0}_{1}_{2}".format(word_size, + num_words, + tech_name) drc_name = "magic" lvs_name = "netgen" diff --git a/compiler/example_configs/example_config_freepdk45.py b/compiler/example_configs/example_config_freepdk45.py index 73e15b6d..eb5f6af3 100644 --- a/compiler/example_configs/example_config_freepdk45.py +++ b/compiler/example_configs/example_config_freepdk45.py @@ -10,5 +10,7 @@ route_supplies = True check_lvsdrc = True output_path = "temp" -output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_{0}_{1}_{2}".format(word_size, + num_words, + tech_name) diff --git a/compiler/example_configs/example_config_scn4m_subm.py b/compiler/example_configs/example_config_scn4m_subm.py index cf973225..500b4beb 100644 --- a/compiler/example_configs/example_config_scn4m_subm.py +++ b/compiler/example_configs/example_config_scn4m_subm.py @@ -10,7 +10,9 @@ route_supplies = True check_lvsdrc = True output_path = "temp" -output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_{0}_{1}_{2}".format(word_size, + num_words, + tech_name) drc_name = "magic" lvs_name = "netgen" diff --git a/compiler/example_configs/giant_config_scn4m_subm.py b/compiler/example_configs/giant_config_scn4m_subm.py index 74d52fe6..e91455da 100644 --- a/compiler/example_configs/giant_config_scn4m_subm.py +++ b/compiler/example_configs/giant_config_scn4m_subm.py @@ -7,7 +7,9 @@ supply_voltages = [ 5.0 ] temperatures = [ 25 ] output_path = "temp" -output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_{0}_{1}_{2}".format(word_size, + num_words, + tech_name) drc_name = "magic" lvs_name = "netgen" diff --git a/compiler/example_configs/medium_config_scn4m_subm.py b/compiler/example_configs/medium_config_scn4m_subm.py index 5faebb58..7c063c97 100644 --- a/compiler/example_configs/medium_config_scn4m_subm.py +++ b/compiler/example_configs/medium_config_scn4m_subm.py @@ -3,11 +3,13 @@ num_words = 256 tech_name = "scn4m_subm" process_corners = ["TT"] -supply_voltages = [ 3.3 ] -temperatures = [ 25 ] +supply_voltages = [3.3] +temperatures = [25] output_path = "temp" -output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name) +output_name = "sram_{0}_{1}_{2}".format(word_size, + num_words, + tech_name) drc_name = "magic" lvs_name = "netgen" diff --git a/compiler/gen_stimulus.py b/compiler/gen_stimulus.py index 6a6be1d1..0c5d988c 100755 --- a/compiler/gen_stimulus.py +++ b/compiler/gen_stimulus.py @@ -13,10 +13,7 @@ created without re-running the entire process. Right now, it assumes the nominal corner, but should probably be extended. """ -import sys,os -import datetime -import re -import importlib +import sys from globals import * (OPTS, args) = parse_args() diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 06883125..3256c9ac 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -42,7 +42,7 @@ class control_logic(design.design): self.enable_delay_chain_resizing = False self.inv_parasitic_delay = logical_effort.logical_effort.pinv - #Determines how much larger the sen delay should be. Accounts for possible error in model. + # Determines how much larger the sen delay should be. Accounts for possible error in model. self.wl_timing_tolerance = 1 self.wl_stage_efforts = None self.sen_stage_efforts = None @@ -201,7 +201,7 @@ class control_logic(design.design): def get_heuristic_delay_chain_size(self): """Use a basic heuristic to determine the size of the delay chain used for the Sense Amp Enable """ - #FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why? + # FIXME: The minimum was 2 fanout, now it will not pass DRC unless it is 3. Why? delay_fanout = 3 # This can be anything >=3 # Model poorly captures delay of the column mux. Be pessismistic for column mux if self.words_per_row >= 2: @@ -209,8 +209,8 @@ class control_logic(design.design): else: delay_stages = 2 - #Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference - #on certain sram configs. + # Read ports have a shorter s_en delay. The model is not accurate enough to catch this difference + # on certain sram configs. if self.port_type == "r": delay_stages+=2 @@ -226,7 +226,7 @@ class control_logic(design.design): def does_sen_rise_fall_timing_match(self): """Compare the relative rise/fall delays of the sense amp enable and wordline""" self.set_sen_wl_delays() - #This is not necessarily more reliable than total delay in some cases. + # This is not necessarily more reliable than total delay in some cases. if (self.wl_delay_rise*self.wl_timing_tolerance >= self.sen_delay_rise or self.wl_delay_fall*self.wl_timing_tolerance >= self.sen_delay_fall): return False @@ -236,8 +236,9 @@ class control_logic(design.design): def does_sen_total_timing_match(self): """Compare the total delays of the sense amp enable and wordline""" self.set_sen_wl_delays() - #The sen delay must always be bigger than than the wl delay. This decides how much larger the sen delay must be before - #a re-size is warranted. + # The sen delay must always be bigger than than the wl + # delay. This decides how much larger the sen delay must be + # before a re-size is warranted. if self.wl_delay*self.wl_timing_tolerance >= self.sen_delay: return False else: @@ -250,14 +251,14 @@ class control_logic(design.design): debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) delay_fanout = 3 # This can be anything >=2 - #The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each - #inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value + # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each + # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay) debug.check(required_delay > 0, "Cannot size delay chain to have negative delay") delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay)) if delay_stages%2 == 1: #force an even number of stages. delay_stages+=1 - #Fanout can be varied as well but is a little more complicated but potentially optimal. + # Fanout can be varied as well but is a little more complicated but potentially optimal. debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay)) return (delay_stages, delay_fanout) @@ -268,16 +269,16 @@ class control_logic(design.design): debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay)) fanout_rise = fanout_fall = 2 # This can be anything >=2 - #The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each - #inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value + # The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each + # inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value required_delay_fall = self.wl_delay_fall*self.wl_timing_tolerance - (self.sen_delay_fall-previous_delay_chain_delay/2) required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2) debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise)) - #If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. + # If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic. WARNING_FANOUT_DIFF = 5 stages_close = False - #The stages need to be equal (or at least a even number of stages with matching rise/fall delays) + # The stages need to be equal (or at least a even number of stages with matching rise/fall delays) while True: stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall) stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise) @@ -294,8 +295,8 @@ class control_logic(design.design): fanout_rise = safe_fanout_rise fanout_fall = safe_fanout_fall break - #There should also be a condition to make sure the fanout does not get too large. - #Otherwise, increase the fanout of delay with the most stages, calculate new stages + # There should also be a condition to make sure the fanout does not get too large. + # Otherwise, increase the fanout of delay with the most stages, calculate new stages elif stages_fall>stages_rise: fanout_fall+=1 else: @@ -304,13 +305,13 @@ class control_logic(design.design): total_stages = max(stages_fall,stages_rise)*2 debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall)) - #Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. + # Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage. stage_list = [fanout_fall if i%2==0 else fanout_rise for i in range(total_stages)] return stage_list def calculate_stages_with_fixed_fanout(self, required_delay, fanout): from math import ceil - #Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay + # Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0). return 1 delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay)) @@ -421,7 +422,7 @@ class control_logic(design.design): row += 1 if (self.port_type == "rw") or (self.port_type == "w"): self.place_rbl_delay_row(row) - row += 1 + row += 1 if (self.port_type == "rw") or (self.port_type == "r"): self.place_sen_row(row) row += 1 @@ -462,6 +463,7 @@ class control_logic(design.design): """ Create the replica bitline """ self.delay_inst=self.add_inst(name="delay_chain", mod=self.delay_chain) + # rbl_bl_delay is asserted (1) when the bitline has been discharged self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"]) def place_delay(self,row): @@ -612,6 +614,8 @@ class control_logic(design.design): def create_pen_row(self): self.p_en_bar_nand_inst=self.add_inst(name="nand_p_en_bar", mod=self.nand2) + # We use the rbl_bl_delay here to ensure that the p_en is only asserted when the + # bitlines have already been discharged. Otherwise, it is a combination loop. self.connect_inst(["gated_clk_buf", "rbl_bl_delay", "p_en_bar_unbuf", "vdd", "gnd"]) self.p_en_bar_driver_inst=self.add_inst(name="buf_p_en_bar", @@ -646,6 +650,9 @@ class control_logic(design.design): # GATE FOR S_EN self.s_en_gate_inst = self.add_inst(name="buf_s_en_and", mod=self.sen_and3) + # s_en is asserted in the second half of the cycle during a read. + # we also must wait until the bitline has been discharged enough for proper sensing + # hence we use rbl_bl_delay as well. self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"]) @@ -669,7 +676,6 @@ class control_logic(design.design): self.connect_output(self.s_en_gate_inst, "Z", "s_en") - def create_rbl_delay_row(self): self.rbl_bl_delay_inv_inst = self.add_inst(name="rbl_bl_delay_inv", @@ -696,7 +702,8 @@ class control_logic(design.design): rbl_map = zip(["A"], ["rbl_bl_delay"]) self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets) - + + def create_wen_row(self): # input: we (or cs) output: w_en @@ -709,6 +716,7 @@ class control_logic(design.design): # GATE THE W_EN self.w_en_gate_inst = self.add_inst(name="w_en_and", mod=self.wen_and) + # Only drive the writes in the second half of the clock cycle during a write operation. self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"]) diff --git a/compiler/openram.py b/compiler/openram.py index 97ada256..5fec7102 100755 --- a/compiler/openram.py +++ b/compiler/openram.py @@ -16,34 +16,32 @@ a LEF (.lef) file for preliminary P&R (real one should be from layout) a Liberty (.lib) file for timing analysis/optimization """ -import sys,os +import sys import datetime -import re -import importlib -from globals import * +import globals as g -(OPTS, args) = parse_args() +(OPTS, args) = g.parse_args() # Check that we are left with a single configuration file as argument. if len(args) != 1: - print(USAGE) + print(g.USAGE) sys.exit(2) # These depend on arguments, so don't load them until now. import debug -init_openram(config_file=args[0], is_unit_test=False) +g.init_openram(config_file=args[0], is_unit_test=False) # Only print banner here so it's not in unit tests -print_banner() +g.print_banner() # Keep track of running stats start_time = datetime.datetime.now() -print_time("Start",start_time) +g.print_time("Start", start_time) # Output info about this run -report_status() +g.report_status() from sram_config import sram_config @@ -54,15 +52,16 @@ c = sram_config(word_size=OPTS.word_size, write_size=OPTS.write_size) debug.print_raw("Words per row: {}".format(c.words_per_row)) -#from parser import * -output_extensions = ["sp","v","lib","py","html","log"] +output_extensions = ["sp", "v", "lib", "py", "html", "log"] # Only output lef/gds if back-end if not OPTS.netlist_only: - output_extensions.extend(["lef","gds"]) + output_extensions.extend(["lef", "gds"]) -output_files = ["{0}{1}.{2}".format(OPTS.output_path,OPTS.output_name,x) for x in output_extensions] +output_files = ["{0}{1}.{2}".format(OPTS.output_path, + OPTS.output_name, x) + for x in output_extensions] debug.print_raw("Output files are: ") -for path in output_files: +for path in output_files: debug.print_raw(path) @@ -74,7 +73,7 @@ s = sram(sram_config=c, s.save() # Delete temp files etc. -end_openram() -print_time("End",datetime.datetime.now(), start_time) +g.end_openram() +g.print_time("End", datetime.datetime.now(), start_time) diff --git a/compiler/options.py b/compiler/options.py index ce974d63..56bd1757 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -6,13 +6,13 @@ # All rights reserved. # import optparse -import getpass +import getpass import os -#import sram_config class options(optparse.Values): """ - Class for holding all of the OpenRAM options. All of these options can be over-riden in a configuration file + Class for holding all of the OpenRAM options. All + of these options can be over-riden in a configuration file that is the sole required command-line positional argument for openram.py. """ @@ -39,15 +39,16 @@ class options(optparse.Values): process_corners = "" # Size parameters must be specified by user in config file. - #num_words = 0 - #word_size = 0 + # num_words = 0 + # word_size = 0 # You can manually specify banks, but it is better to auto-detect it. num_banks = 1 ################### # Optimization options ################### - rbl_delay_percentage = 0.5 #Approximate percentage of delay compared to bitlines + # Approximate percentage of delay compared to bitlines + rbl_delay_percentage = 0.5 # Allow manual adjustment of the delay chain over automatic use_tech_delay_chain_size = False @@ -65,7 +66,8 @@ class options(optparse.Values): openram_temp = os.path.abspath(os.environ.get("OPENRAM_TMP")) except: # Else use a unique temporary directory - openram_temp = "/tmp/openram_{0}_{1}_temp/".format(getpass.getuser(),os.getpid()) + openram_temp = "/tmp/openram_{0}_{1}_temp/".format(getpass.getuser(), + os.getpid()) # This is the verbosity level to control debug information. 0 is none, 1 # is minimal, etc. debug_level = 0 @@ -100,7 +102,8 @@ class options(optparse.Values): drc_name = "" lvs_name = "" pex_name = "" - # The DRC/LVS/PEX executable being used which is derived from the user PATH. + # The DRC/LVS/PEX executable being used + # which is derived from the user PATH. drc_exe = None lvs_exe = None pex_exe = None @@ -113,15 +116,14 @@ class options(optparse.Values): output_path = "." # Define the output file base name output_name = "" - # Use analytical delay models by default rather than (slow) characterization + # Use analytical delay models by default + # rather than (slow) characterization analytical_delay = True - # Purge the temp directory after a successful run (doesn't purge on errors, anyhow) + # Purge the temp directory after a successful + # run (doesn't purge on errors, anyhow) purge_temp = True - - ################### # These are the default modules that can be over-riden - ################### bank_select = "bank_select" bitcell_array = "bitcell_array" bitcell = "bitcell" diff --git a/compiler/pgates/pand2.py b/compiler/pgates/pand2.py index d71b1e92..d410a8c7 100644 --- a/compiler/pgates/pand2.py +++ b/compiler/pgates/pand2.py @@ -6,16 +6,14 @@ # All rights reserved. # import debug -from tech import drc -from math import log from vector import vector -from globals import OPTS import pgate from sram_factory import factory + class pand2(pgate.pgate): """ - This is a simple buffer used for driving loads. + This is a simple buffer used for driving loads. """ def __init__(self, name, size=1, height=None): debug.info(1, "Creating pnand2 {}".format(name)) @@ -23,7 +21,7 @@ class pand2(pgate.pgate): self.size = size - # Creates the netlist and layout + # Creates the netlist and layout pgate.pgate.__init__(self, name, height) def create_netlist(self): @@ -33,10 +31,13 @@ class pand2(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 - self.nand = factory.create(module_type="pnand2",height=self.height) + self.nand = factory.create(module_type="pnand2", height=self.height) self.add_mod(self.nand) - self.inv = factory.create(module_type="pdriver", neg_polarity=True, fanout=3*self.size, height=self.height) + self.inv = factory.create(module_type="pdriver", + neg_polarity=True, + fanout=3*self.size, + height=self.height) self.add_mod(self.inv) def create_layout(self): @@ -54,44 +55,44 @@ class pand2(pgate.pgate): self.add_pin("gnd", "GROUND") def create_insts(self): - self.nand_inst=self.add_inst(name="pand2_nand", - mod=self.nand) - self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) + self.nand_inst = self.add_inst(name="pand2_nand", + mod=self.nand) + self.connect_inst(["A", "B", "zb_int", "vdd", "gnd"]) - self.inv_inst=self.add_inst(name="pand2_inv", - mod=self.inv) - self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + self.inv_inst = self.add_inst(name="pand2_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) def place_insts(self): - # Add NAND to the right - self.nand_inst.place(offset=vector(0,0)) + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(),0)) + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") a2_pin = self.inv_inst.get_pin("A") - mid1_point = vector(0.5*(z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) + mid1_point = vector(0.5 * (z1_pin.cx() + a2_pin.cx()), z1_pin.cy()) mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) - + self.add_path("metal1", + [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) def add_layout_pins(self): # Continous vdd rail along with label. - vdd_pin=self.inv_inst.get_pin("vdd") + vdd_pin = self.inv_inst.get_pin("vdd") self.add_layout_pin(text="vdd", layer="metal1", - offset=vdd_pin.ll().scale(0,1), + offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) # Continous gnd rail along with label. - gnd_pin=self.inv_inst.get_pin("gnd") + gnd_pin = self.inv_inst.get_pin("gnd") self.add_layout_pin(text="gnd", layer="metal1", - offset=gnd_pin.ll().scale(0,1), + offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -102,7 +103,7 @@ class pand2(pgate.pgate): width=pin.width(), height=pin.height()) - for pin_name in ["A","B"]: + for pin_name in ["A", "B"]: pin = self.nand_inst.get_pin(pin_name) self.add_layout_pin_rect_center(text=pin_name, layer=pin.layer, diff --git a/compiler/pgates/pand3.py b/compiler/pgates/pand3.py index 22864e5a..b02e18e4 100644 --- a/compiler/pgates/pand3.py +++ b/compiler/pgates/pand3.py @@ -6,16 +6,14 @@ # All rights reserved. # import debug -from tech import drc -from math import log from vector import vector -from globals import OPTS import pgate from sram_factory import factory + class pand3(pgate.pgate): """ - This is a simple buffer used for driving loads. + This is a simple buffer used for driving loads. """ def __init__(self, name, size=1, height=None): debug.info(1, "Creating pand3 {}".format(name)) @@ -23,7 +21,7 @@ class pand3(pgate.pgate): self.size = size - # Creates the netlist and layout + # Creates the netlist and layout pgate.pgate.__init__(self, name, height) def create_netlist(self): @@ -33,10 +31,12 @@ class pand3(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 - self.nand = factory.create(module_type="pnand3",height=self.height) + self.nand = factory.create(module_type="pnand3", height=self.height) self.add_mod(self.nand) - self.inv = factory.create(module_type="pinv", size=self.size, height=self.height) + self.inv = factory.create(module_type="pinv", + size=self.size, + height=self.height) self.add_mod(self.inv) def create_layout(self): @@ -55,44 +55,44 @@ class pand3(pgate.pgate): self.add_pin("gnd", "GROUND") def create_insts(self): - self.nand_inst=self.add_inst(name="pand3_nand", - mod=self.nand) - self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"]) + self.nand_inst = self.add_inst(name="pand3_nand", + mod=self.nand) + self.connect_inst(["A", "B", "C", "zb_int", "vdd", "gnd"]) - self.inv_inst=self.add_inst(name="pand3_inv", - mod=self.inv) - self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + self.inv_inst = self.add_inst(name="pand3_inv", + mod=self.inv) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) def place_insts(self): - # Add NAND to the right - self.nand_inst.place(offset=vector(0,0)) + # Add NAND to the right + self.nand_inst.place(offset=vector(0, 0)) # Add INV to the right - self.inv_inst.place(offset=vector(self.nand_inst.rx(),0)) + self.inv_inst.place(offset=vector(self.nand_inst.rx(), 0)) def add_wires(self): # nand Z to inv A z1_pin = self.nand_inst.get_pin("Z") a2_pin = self.inv_inst.get_pin("A") - mid1_point = vector(0.5*(z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) + mid1_point = vector(0.5 * (z1_pin.cx()+a2_pin.cx()), z1_pin.cy()) mid2_point = vector(mid1_point, a2_pin.cy()) - self.add_path("metal1", [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) - + self.add_path("metal1", + [z1_pin.center(), mid1_point, mid2_point, a2_pin.center()]) def add_layout_pins(self): # Continous vdd rail along with label. - vdd_pin=self.inv_inst.get_pin("vdd") + vdd_pin = self.inv_inst.get_pin("vdd") self.add_layout_pin(text="vdd", layer="metal1", - offset=vdd_pin.ll().scale(0,1), + offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) # Continous gnd rail along with label. - gnd_pin=self.inv_inst.get_pin("gnd") + gnd_pin = self.inv_inst.get_pin("gnd") self.add_layout_pin(text="gnd", layer="metal1", - offset=gnd_pin.ll().scale(0,1), + offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -103,20 +103,22 @@ class pand3(pgate.pgate): width=pin.width(), height=pin.height()) - for pin_name in ["A","B", "C"]: + for pin_name in ["A", "B", "C"]: pin = self.nand_inst.get_pin(pin_name) self.add_layout_pin_rect_center(text=pin_name, layer=pin.layer, offset=pin.center(), width=pin.width(), height=pin.height()) - - def analytical_delay(self, corner, slew, load=0.0): """ Calculate the analytical delay of DFF-> INV -> INV """ - nand_delay = self.nand.analytical_delay(corner, slew=slew, load=self.inv.input_load()) - inv_delay = self.inv.analytical_delay(corner, slew=nand_delay.slew, load=load) + nand_delay = self.nand.analytical_delay(corner, + slew=slew, + load=self.inv.input_load()) + inv_delay = self.inv.analytical_delay(corner, + slew=nand_delay.slew, + load=load) return nand_delay + inv_delay def get_stage_efforts(self, external_cout, inp_is_rise=False): diff --git a/compiler/pgates/pbuf.py b/compiler/pgates/pbuf.py index fcfc6586..0149f75f 100644 --- a/compiler/pgates/pbuf.py +++ b/compiler/pgates/pbuf.py @@ -6,20 +6,18 @@ # All rights reserved. # import debug -from tech import drc -from math import log from vector import vector -from globals import OPTS import pgate from sram_factory import factory + class pbuf(pgate.pgate): """ - This is a simple buffer used for driving loads. + This is a simple buffer used for driving loads. """ def __init__(self, name, size=4, height=None): - debug.info(1, "creating {0} with size of {1}".format(name,size)) + debug.info(1, "creating {0} with size of {1}".format(name, size)) self.add_comment("size: {}".format(size)) self.stage_effort = 4 @@ -29,7 +27,6 @@ class pbuf(pgate.pgate): # Creates the netlist and layout pgate.pgate.__init__(self, name, height) - def create_netlist(self): self.add_pins() self.create_modules() @@ -49,53 +46,54 @@ class pbuf(pgate.pgate): def create_modules(self): # Shield the cap, but have at least a stage effort of 4 - input_size = max(1,int(self.size/self.stage_effort)) - self.inv1 = factory.create(module_type="pinv", size=input_size, height=self.height) + input_size = max(1, int(self.size / self.stage_effort)) + self.inv1 = factory.create(module_type="pinv", + size=input_size, + height=self.height) self.add_mod(self.inv1) - self.inv2 = factory.create(module_type="pinv", size=self.size, height=self.height) + self.inv2 = factory.create(module_type="pinv", + size=self.size, + height=self.height) self.add_mod(self.inv2) def create_insts(self): - self.inv1_inst=self.add_inst(name="buf_inv1", - mod=self.inv1) - self.connect_inst(["A", "zb_int", "vdd", "gnd"]) + self.inv1_inst = self.add_inst(name="buf_inv1", + mod=self.inv1) + self.connect_inst(["A", "zb_int", "vdd", "gnd"]) - - self.inv2_inst=self.add_inst(name="buf_inv2", - mod=self.inv2) - self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + self.inv2_inst = self.add_inst(name="buf_inv2", + mod=self.inv2) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) def place_insts(self): - # Add INV1 to the right - self.inv1_inst.place(vector(0,0)) + # Add INV1 to the right + self.inv1_inst.place(vector(0, 0)) # Add INV2 to the right - self.inv2_inst.place(vector(self.inv1_inst.rx(),0)) - + self.inv2_inst.place(vector(self.inv1_inst.rx(), 0)) def add_wires(self): # inv1 Z to inv2 A z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") - mid_point = vector(z1_pin.cx(), a2_pin.cy()) + mid_point = vector(z1_pin.cx(), a2_pin.cy()) self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()]) - def add_layout_pins(self): # Continous vdd rail along with label. - vdd_pin=self.inv1_inst.get_pin("vdd") + vdd_pin = self.inv1_inst.get_pin("vdd") self.add_layout_pin(text="vdd", layer="metal1", - offset=vdd_pin.ll().scale(0,1), + offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) # Continous gnd rail along with label. - gnd_pin=self.inv1_inst.get_pin("gnd") + gnd_pin = self.inv1_inst.get_pin("gnd") self.add_layout_pin(text="gnd", layer="metal1", - offset=gnd_pin.ll().scale(0,1), + offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) diff --git a/compiler/pgates/pdriver.py b/compiler/pgates/pdriver.py index ec55f0c7..bb7739b7 100644 --- a/compiler/pgates/pdriver.py +++ b/compiler/pgates/pdriver.py @@ -7,28 +7,27 @@ # import debug import pgate -import math -from tech import drc -from math import log from vector import vector -from globals import OPTS from sram_factory import factory + class pdriver(pgate.pgate): """ - This instantiates an even or odd number of inverters sized for driving a load. + This instantiates an even or odd number of inverters + sized for driving a load. """ + def __init__(self, name, neg_polarity=False, fanout=0, size_list=None, height=None): debug.info(1, "creating pdriver {}".format(name)) self.stage_effort = 3 - self.height = height + self.height = height self.neg_polarity = neg_polarity self.size_list = size_list self.fanout = fanout - if size_list == None and self.fanout == 0: + if not size_list and self.fanout == 0: debug.error("Either fanout or size list must be specified.", -1) if self.size_list and self.fanout != 0: debug.error("Cannot specify both size_list and fanout.", -1) @@ -36,34 +35,33 @@ class pdriver(pgate.pgate): debug.error("Cannot specify both size_list and neg_polarity.", -1) # Creates the netlist and layout - pgate.pgate.__init__(self, name, height) + pgate.pgate.__init__(self, name, height) - def compute_sizes(self): # size_list specified if self.size_list: self.num_stages = len(self.size_list) else: # Find the optimal number of stages for the given effort - self.num_stages = max(1,int(round(self.fanout**(1/self.stage_effort)))) + self.num_stages = max(1, + int(round(self.fanout ** (1 / self.stage_effort)))) # Increase the number of stages if we need to fix polarity - if self.neg_polarity and (self.num_stages%2==0): + if self.neg_polarity and (self.num_stages % 2 == 0): self.num_stages += 1 - elif not self.neg_polarity and (self.num_stages%2): + elif not self.neg_polarity and (self.num_stages % 2): self.num_stages += 1 self.size_list = [] # compute sizes backwards from the fanout fanout_prev = self.fanout for x in range(self.num_stages): - fanout_prev = max(round(fanout_prev/self.stage_effort),1) + fanout_prev = max(round(fanout_prev / self.stage_effort), 1) self.size_list.append(fanout_prev) # reverse the sizes to be from input to output self.size_list.reverse() - def create_netlist(self): self.compute_sizes() self.add_comment("sizes: {}".format(str(self.size_list))) @@ -79,29 +77,30 @@ class pdriver(pgate.pgate): self.width = self.inv_inst_list[-1].rx() self.height = self.inv_inst_list[0].height - def add_pins(self): self.add_pin("A", "INPUT") self.add_pin("Z", "OUTPUT") self.add_pin("vdd", "POWER") self.add_pin("gnd", "GROUND") - def add_modules(self): + def add_modules(self): self.inv_list = [] for size in self.size_list: - temp_inv = factory.create(module_type="pinv", size=size, height=self.height) + temp_inv = factory.create(module_type="pinv", + size=size, + height=self.height) self.inv_list.append(temp_inv) self.add_mod(temp_inv) - def create_insts(self): self.inv_inst_list = [] - for x in range(1,self.num_stages+1): + for x in range(1, self.num_stages + 1): # Create first inverter if x == 1: - zbx_int = "Zb{}_int".format(x); - self.inv_inst_list.append(self.add_inst(name="buf_inv{}".format(x), - mod=self.inv_list[x-1])) + zbx_int = "Zb{}_int".format(x) + inst = self.add_inst(name="buf_inv{}".format(x), + mod=self.inv_list[x - 1]) + self.inv_inst_list.append(inst) if self.num_stages == 1: self.connect_inst(["A", "Z", "vdd", "gnd"]) else: @@ -109,70 +108,72 @@ class pdriver(pgate.pgate): # Create last inverter elif x == self.num_stages: - zbn_int = "Zb{}_int".format(x-1); - self.inv_inst_list.append(self.add_inst(name="buf_inv{}".format(x), - mod=self.inv_list[x-1])) + zbn_int = "Zb{}_int".format(x - 1) + inst = self.add_inst(name="buf_inv{}".format(x), + mod=self.inv_list[x - 1]) + self.inv_inst_list.append(inst) self.connect_inst([zbn_int, "Z", "vdd", "gnd"]) # Create middle inverters else: - zbx_int = "Zb{}_int".format(x-1); - zbn_int = "Zb{}_int".format(x); - self.inv_inst_list.append(self.add_inst(name="buf_inv{}".format(x), - mod=self.inv_list[x-1])) + zbx_int = "Zb{}_int".format(x - 1) + zbn_int = "Zb{}_int".format(x) + inst = self.add_inst(name="buf_inv{}".format(x), + mod=self.inv_list[x - 1]) + self.inv_inst_list.append(inst) self.connect_inst([zbx_int, zbn_int, "vdd", "gnd"]) - def place_modules(self): # Add the first inverter at the origin - self.inv_inst_list[0].place(vector(0,0)) + self.inv_inst_list[0].place(vector(0, 0)) # Add inverters to the right of the previous inverter - for x in range(1,len(self.inv_inst_list)): - self.inv_inst_list[x].place(vector(self.inv_inst_list[x-1].rx(),0)) + for x in range(1, len(self.inv_inst_list)): + loc = vector(self.inv_inst_list[x - 1].rx(), 0) + self.inv_inst_list[x].place(loc) - def route_wires(self): z_inst_list = [] a_inst_list = [] # inv_current Z to inv_next A - for x in range(0,len(self.inv_inst_list)-1): + for x in range(0, len(self.inv_inst_list) - 1): z_inst_list.append(self.inv_inst_list[x].get_pin("Z")) - a_inst_list.append(self.inv_inst_list[x+1].get_pin("A")) - mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy()) - self.add_path("metal1", [z_inst_list[x].center(), mid_point, a_inst_list[x].center()]) + a_inst_list.append(self.inv_inst_list[x + 1].get_pin("A")) + mid_point = vector(z_inst_list[x].cx(), a_inst_list[x].cy()) + self.add_path("metal1", + [z_inst_list[x].center(), mid_point, + a_inst_list[x].center()]) - def add_layout_pins(self): # Continous vdd rail along with label. - vdd_pin=self.inv_inst_list[0].get_pin("vdd") + vdd_pin = self.inv_inst_list[0].get_pin("vdd") self.add_layout_pin(text="vdd", layer="metal1", - offset=vdd_pin.ll().scale(0,1), + offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) # Continous gnd rail along with label. - gnd_pin=self.inv_inst_list[0].get_pin("gnd") + gnd_pin = self.inv_inst_list[0].get_pin("gnd") self.add_layout_pin(text="gnd", layer="metal1", - offset=gnd_pin.ll().scale(0,1), + offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) - z_pin = self.inv_inst_list[len(self.inv_inst_list)-1].get_pin("Z") + z_pin = self.inv_inst_list[len(self.inv_inst_list) - 1].get_pin("Z") self.add_layout_pin_rect_center(text="Z", layer=z_pin.layer, offset=z_pin.center(), - width = z_pin.width(), - height = z_pin.height()) + width=z_pin.width(), + height=z_pin.height()) a_pin = self.inv_inst_list[0].get_pin("A") self.add_layout_pin_rect_center(text="A", layer=a_pin.layer, offset=a_pin.center(), - width = a_pin.width(), - height = a_pin.height()) + width=a_pin.width(), + height=a_pin.height()) def get_sizes(self): """ Return the relative sizes of the buffers """ @@ -181,14 +182,14 @@ class pdriver(pgate.pgate): def get_stage_efforts(self, external_cout, inp_is_rise=False): """ Get the stage efforts of the A -> Z path """ cout_list = [] - for prev_inv,inv in zip(self.inv_list, self.inv_list[1:]): + for prev_inv, inv in zip(self.inv_list, self.inv_list[1:]): cout_list.append(inv.get_cin()) cout_list.append(external_cout) stage_effort_list = [] last_inp_is_rise = inp_is_rise - for inv,cout in zip(self.inv_list,cout_list): + for inv, cout in zip(self.inv_list, cout_list): stage = inv.get_stage_effort(cout, last_inp_is_rise) stage_effort_list.append(stage) last_inp_is_rise = stage.is_rise diff --git a/compiler/pgates/pgate.py b/compiler/pgates/pgate.py index f974f0c4..9113146b 100644 --- a/compiler/pgates/pgate.py +++ b/compiler/pgates/pgate.py @@ -8,14 +8,16 @@ import contact import design import debug -from tech import drc, parameter, spice +from tech import drc from vector import vector from globals import OPTS from sram_factory import factory + class pgate(design.design): """ - This is a module that implements some shared functions for parameterized gates. + This is a module that implements some shared + functions for parameterized gates. """ def __init__(self, name, height=None): @@ -29,78 +31,85 @@ class pgate(design.design): self.height = b.height self.create_netlist() - if not OPTS.netlist_only: + if not OPTS.netlist_only: self.create_layout() self.add_boundary() self.DRC_LVS() - def create_netlist(self): """ Pure virtual function """ - debug.error("Must over-ride create_netlist.",-1) + debug.error("Must over-ride create_netlist.", -1) def create_layout(self): """ Pure virtual function """ - debug.error("Must over-ride create_layout.",-1) + debug.error("Must over-ride create_layout.", -1) - def connect_pin_to_rail(self,inst,pin,supply): + def connect_pin_to_rail(self, inst, pin, supply): """ Connects a ptx pin to a supply rail. """ source_pin = inst.get_pin(pin) supply_pin = self.get_pin(supply) if supply_pin.overlaps(source_pin): return - if supply=="gnd": - height=supply_pin.by()-source_pin.by() - elif supply=="vdd": - height=supply_pin.uy()-source_pin.by() + if supply == "gnd": + height = supply_pin.by() - source_pin.by() + elif supply == "vdd": + height = supply_pin.uy() - source_pin.by() else: - debug.error("Invalid supply name.",-1) + debug.error("Invalid supply name.", -1) - if abs(height)>0: + if abs(height) > 0: self.add_rect(layer="metal1", offset=source_pin.ll(), height=height, width=source_pin.width()) def route_input_gate(self, pmos_inst, nmos_inst, ypos, name, position="left", rotate=False): - """ Route the input gate to the left side of the cell for access. - Position specifies to place the contact the left, center, or right of gate. """ + """ + Route the input gate to the left side of the cell for access. + Position specifies to place the contact the left, center, or + right of gate. + """ nmos_gate_pin = nmos_inst.get_pin("G") pmos_gate_pin = pmos_inst.get_pin("G") # Check if the gates are aligned and give an error if they aren't! - debug.check(nmos_gate_pin.ll().x==pmos_gate_pin.ll().x, "Connecting unaligned gates not supported.") + debug.check(nmos_gate_pin.ll().x == pmos_gate_pin.ll().x, + "Connecting unaligned gates not supported.") # Pick point on the left of NMOS and connect down to PMOS - nmos_gate_pos = nmos_gate_pin.ll() + vector(0.5*self.poly_width,0) - pmos_gate_pos = vector(nmos_gate_pos.x,pmos_gate_pin.bc().y) - self.add_path("poly",[nmos_gate_pos,pmos_gate_pos]) + nmos_gate_pos = nmos_gate_pin.ll() + vector(0.5 * self.poly_width, 0) + pmos_gate_pos = vector(nmos_gate_pos.x, pmos_gate_pin.bc().y) + self.add_path("poly", [nmos_gate_pos, pmos_gate_pos]) # Add the via to the cell midpoint along the gate - left_gate_offset = vector(nmos_gate_pin.lx(),ypos) + left_gate_offset = vector(nmos_gate_pin.lx(), ypos) - # Center is completely symmetric. + # Center is completely symmetric. if rotate: contact_width = contact.poly.height contact_m1_width = contact.poly.second_layer_height contact_m1_height = contact.poly.second_layer_width - directions = ("H","V") + directions = ("H", "V") else: contact_width = contact.poly.width contact_m1_width = contact.poly.second_layer_width contact_m1_height = contact.poly.second_layer_height - directions = ("V","H") + directions = ("V", "H") - if position=="center": - contact_offset = left_gate_offset + vector(0.5*self.poly_width, 0) - elif position=="farleft": - contact_offset = left_gate_offset - vector(0.5*contact.poly.width, 0) - elif position=="left": - contact_offset = left_gate_offset - vector(0.5*contact_width - 0.5*self.poly_width, 0) - elif position=="right": - contact_offset = left_gate_offset + vector(0.5*contact.width + 0.5*self.poly_width, 0) + if position == "center": + contact_offset = left_gate_offset \ + + vector(0.5 * self.poly_width, 0) + elif position == "farleft": + contact_offset = left_gate_offset \ + - vector(0.5 * contact.poly.width, 0) + elif position == "left": + contact_offset = left_gate_offset \ + - vector(0.5 * contact_width - 0.5 * self.poly_width, 0) + elif position == "right": + contact_offset = left_gate_offset \ + + vector(0.5 * contact.width + 0.5 * self.poly_width, 0) else: debug.error("Invalid contact placement option.", -1) @@ -110,29 +119,26 @@ class pgate(design.design): offset=contact_offset, directions=directions) - # self.add_layout_pin_segment_center(text=name, - # layer="metal1", - # start=left_gate_offset.scale(0,1), - # end=left_gate_offset) self.add_layout_pin_rect_center(text=name, layer="metal1", offset=contact_offset, width=contact_m1_width, height=contact_m1_height) - - # This is to ensure that the contact is connected to the gate - mid_point = contact_offset.scale(0.5,1)+left_gate_offset.scale(0.5,0) + # This is to ensure that the contact is + # connected to the gate + mid_point = contact_offset.scale(0.5, 1) \ + + left_gate_offset.scale(0.5, 0) self.add_rect_center(layer="poly", offset=mid_point, height=contact.poly.first_layer_width, - width=left_gate_offset.x-contact_offset.x) + width=left_gate_offset.x - contact_offset.x) def extend_wells(self, middle_position): """ Extend the n/p wells to cover whole cell """ # Add a rail width to extend the well to the top of the rail - max_y_offset = self.height + 0.5*self.m1_width + max_y_offset = self.height + 0.5 * self.m1_width self.nwell_position = middle_position nwell_height = max_y_offset - middle_position.y if drc("has_nwell"): @@ -145,8 +151,8 @@ class pgate(design.design): width=self.well_width, height=nwell_height) - pwell_position = vector(0,-0.5*self.m1_width) - pwell_height = middle_position.y-pwell_position.y + pwell_position = vector(0, -0.5 * self.m1_width) + pwell_height = middle_position.y - pwell_position.y if drc("has_pwell"): self.add_rect(layer="pwell", offset=pwell_position, @@ -163,38 +169,45 @@ class pgate(design.design): layer_stack = ("active", "contact", "metal1") # To the right a spacing away from the pmos right active edge - contact_xoffset = pmos_pos.x + pmos.active_width + drc("active_to_body_active") - # Must be at least an well enclosure of active down from the top of the well + contact_xoffset = pmos_pos.x + pmos.active_width \ + + drc("active_to_body_active") + + # Must be at least an well enclosure of active down + # from the top of the well # OR align the active with the top of PMOS active. - max_y_offset = self.height + 0.5*self.m1_width + 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.well_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, - 0.5*pmos.active_contact.first_layer_height) - self.nwell_contact=self.add_via_center(layers=layer_stack, - offset=contact_offset, - directions=("H","V"), - implant_type="n", - well_type="n") + contact_offset += vector(0.5 * pmos.active_contact.first_layer_width, + 0.5 * pmos.active_contact.first_layer_height) + self.nwell_contact = self.add_via_center(layers=layer_stack, + offset=contact_offset, + directions=("H", "V"), + implant_type="n", + well_type="n") self.add_rect_center(layer="metal1", - offset=contact_offset + vector(0,0.5*(self.height-contact_offset.y)), + offset=contact_offset + vector(0, 0.5 * (self.height-contact_offset.y)), width=self.nwell_contact.mod.second_layer_width, height=self.height - contact_offset.y) # Now add the full active and implant for the PMOS - #active_offset = pmos_pos + vector(pmos.active_width,0) - # This might be needed if the spacing between the actives is not satisifed + # active_offset = pmos_pos + vector(pmos.active_width,0) + # This might be needed if the spacing between the actives + # is not satisifed # self.add_rect(layer="active", # offset=active_offset, # width=pmos.active_contact.width, # height=pmos.active_height) - # we need to ensure implants don't overlap and are spaced far enough apart + # we need to ensure implants don't overlap and are + # spaced far enough apart # implant_spacing = self.implant_space+self.implant_enclose_active - # implant_offset = active_offset + vector(implant_spacing,0) - vector(0,self.implant_enclose_active) - # implant_width = pmos.active_contact.width + 2*self.implant_enclose_active + # implant_offset = active_offset + vector(implant_spacing,0) \ + # - vector(0,self.implant_enclose_active) + # implant_width = pmos.active_contact.width \ + # + 2*self.implant_enclose_active # implant_height = pmos.active_height + 2*self.implant_enclose_active # self.add_rect(layer="nimplant", # offset=implant_offset, @@ -208,39 +221,45 @@ class pgate(design.design): layer_stack = ("active", "contact", "metal1") - pwell_position = vector(0,-0.5*self.m1_width) + pwell_position = vector(0, -0.5 * self.m1_width) # To the right a spacing away from the nmos right active edge - contact_xoffset = nmos_pos.x + nmos.active_width + drc("active_to_body_active") - # Must be at least an well enclosure of active up from the bottom of the well + contact_xoffset = nmos_pos.x + nmos.active_width \ + + drc("active_to_body_active") + # 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 - nmos.active_contact.first_layer_height/2) + self.well_enclose_active \ + - nmos.active_contact.first_layer_height / 2) contact_offset = vector(contact_xoffset, contact_yoffset) # Offset by half a contact - contact_offset += vector(0.5*nmos.active_contact.first_layer_width, - 0.5*nmos.active_contact.first_layer_height) - self.pwell_contact=self.add_via_center(layers=layer_stack, - offset=contact_offset, - directions=("H","V"), - implant_type="p", - well_type="p") + contact_offset += vector(0.5 * nmos.active_contact.first_layer_width, + 0.5 * nmos.active_contact.first_layer_height) + self.pwell_contact= self.add_via_center(layers=layer_stack, + offset=contact_offset, + directions=("H", "V"), + implant_type="p", + well_type="p") self.add_rect_center(layer="metal1", offset=contact_offset.scale(1,0.5), width=self.pwell_contact.mod.second_layer_width, height=contact_offset.y) # Now add the full active and implant for the NMOS - # active_offset = nmos_pos + vector(nmos.active_width,0) - # This might be needed if the spacing between the actives is not satisifed + # active_offset = nmos_pos + vector(nmos.active_width,0) + # This might be needed if the spacing between the actives + # is not satisifed # self.add_rect(layer="active", # offset=active_offset, # width=nmos.active_contact.width, # height=nmos.active_height) # implant_spacing = self.implant_space+self.implant_enclose_active - # implant_offset = active_offset + vector(implant_spacing,0) - vector(0,self.implant_enclose_active) - # implant_width = nmos.active_contact.width + 2*self.implant_enclose_active + # implant_offset = active_offset + vector(implant_spacing,0) \ + # - vector(0,self.implant_enclose_active) + # implant_width = nmos.active_contact.width \ + # + 2*self.implant_enclose_active # implant_height = nmos.active_height + 2*self.implant_enclose_active # self.add_rect(layer="pimplant", # offset=implant_offset, diff --git a/compiler/pgates/pinv.py b/compiler/pgates/pinv.py index 275ccbe6..2b8ec7b7 100644 --- a/compiler/pgates/pinv.py +++ b/compiler/pgates/pinv.py @@ -16,6 +16,7 @@ from utils import round_to_grid import logical_effort from sram_factory import factory + class pinv(pgate.pgate): """ Pinv generates gds of a parametrically sized inverter. The @@ -28,12 +29,14 @@ class pinv(pgate.pgate): def __init__(self, name, size=1, beta=parameter["beta"], height=None, route_output=True): - debug.info(2, "creating pinv structure {0} with size of {1}".format(name, size)) + debug.info(2, + "creating pinv structure {0} with size of {1}".format(name, + size)) self.add_comment("size: {}".format(size)) self.size = size self.nmos_size = size - self.pmos_size = beta*size + self.pmos_size = beta * size self.beta = beta self.route_output = False @@ -44,7 +47,7 @@ class pinv(pgate.pgate): self.add_pins() self.determine_tx_mults() self.add_ptx() - self.create_ptx() + self.create_ptx() def create_layout(self): """ Calls all functions related to the generation of the layout """ @@ -54,7 +57,11 @@ class pinv(pgate.pgate): self.add_well_contacts() self.extend_wells(self.well_pos) self.connect_rails() - self.route_input_gate(self.pmos_inst, self.nmos_inst, self.output_pos.y, "A", position="farleft") + self.route_input_gate(self.pmos_inst, + self.nmos_inst, + self.output_pos.y, + "A", + position="farleft") self.route_outputs() def add_pins(self): @@ -63,7 +70,6 @@ class pinv(pgate.pgate): dir_list = ["INPUT", "OUTPUT", "POWER", "GROUND"] self.add_pin_list(pin_list, dir_list) - def determine_tx_mults(self): """ Determines the number of fingers needed to achieve the size within @@ -73,58 +79,71 @@ class pinv(pgate.pgate): # This may make the result differ when the layout is created... if OPTS.netlist_only: self.tx_mults = 1 - self.nmos_width = self.nmos_size*drc("minwidth_tx") - self.pmos_width = self.pmos_size*drc("minwidth_tx") + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") return # Do a quick sanity check and bail if unlikely feasible height - # Sanity check. can we make an inverter in the height with minimum tx sizes? - # Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain) + # Sanity check. can we make an inverter in the height + # with minimum tx sizes? + # Assume we need 3 metal 1 pitches (2 power rails, one + # between the tx for the drain) # plus the tx height nmos = factory.create(module_type="ptx", tx_type="nmos") - pmos = factory.create(module_type="ptx", width=drc("minwidth_tx"), tx_type="pmos") + pmos = factory.create(module_type="ptx", + width=drc("minwidth_tx"), + tx_type="pmos") tx_height = nmos.poly_height + pmos.poly_height # rotated m1 pitch or poly to active spacing min_channel = max(contact.poly.width + self.m1_space, - contact.poly.width + 2*drc("poly_to_active")) - # This is the extra space needed to ensure DRC rules to the active contacts - extra_contact_space = max(-nmos.get_pin("D").by(),0) + contact.poly.width + 2 * drc("poly_to_active")) + + # This is the extra space needed to ensure DRC rules + # to the active contacts + extra_contact_space = max(-nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, drc("poly_extend_active"), self.poly_space) - total_height = tx_height + min_channel + 2*self.top_bottom_space - debug.check(self.height> total_height,"Cell height {0} too small for simple min height {1}.".format(self.height,total_height)) + total_height = tx_height + min_channel + 2 * self.top_bottom_space + debug.check(self.height > total_height, + "Cell height {0} too small for simple min height {1}.".format(self.height, + total_height)) - # Determine the height left to the transistors to determine the number of fingers - tx_height_available = self.height - min_channel - 2*self.top_bottom_space - # Divide the height in half. Could divide proportional to beta, but this makes - # connecting wells of multiple cells easier. + # Determine the height left to the transistors to determine + # the number of fingers + tx_height_available = self.height - min_channel - 2 * self.top_bottom_space + # Divide the height in half. Could divide proportional to beta, + # but this makes connecting wells of multiple cells easier. # Subtract the poly space under the rail of the tx - nmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly") - pmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly") + nmos_height_available = 0.5 * tx_height_available - 0.5 * drc("poly_to_poly") + pmos_height_available = 0.5 * tx_height_available - 0.5 * drc("poly_to_poly") - debug.info(2,"Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available, - nmos_height_available, - pmos_height_available)) + debug.info(2, + "Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available, + nmos_height_available, + pmos_height_available)) - # Determine the number of mults for each to fit width into available space - self.nmos_width = self.nmos_size*drc("minwidth_tx") - self.pmos_width = self.pmos_size*drc("minwidth_tx") - nmos_required_mults = max(int(ceil(self.nmos_width/nmos_height_available)),1) - pmos_required_mults = max(int(ceil(self.pmos_width/pmos_height_available)),1) + # Determine the number of mults for each to fit width + # into available space + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") + nmos_required_mults = max(int(ceil(self.nmos_width / nmos_height_available)), 1) + pmos_required_mults = max(int(ceil(self.pmos_width / pmos_height_available)), 1) # The mults must be the same for easy connection of poly self.tx_mults = max(nmos_required_mults, pmos_required_mults) # Recompute each mult width and check it isn't too small # This could happen if the height is narrow and the size is small # User should pick a bigger size to fix it... - # We also need to round the width to the grid or we will end up with LVS property - # mismatch errors when fingers are not a grid length and get rounded in the offset geometry. + # We also need to round the width to the grid or we will end up + # with LVS property mismatch errors when fingers are not a grid + # length and get rounded in the offset geometry. self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults) - debug.check(self.nmos_width>=drc("minwidth_tx"),"Cannot finger NMOS transistors to fit cell height.") + debug.check(self.nmos_width >= drc("minwidth_tx"), + "Cannot finger NMOS transistors to fit cell height.") self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults) - debug.check(self.pmos_width>=drc("minwidth_tx"),"Cannot finger PMOS transistors to fit cell height.") - + debug.check(self.pmos_width >= drc("minwidth_tx"), + "Cannot finger PMOS transistors to fit cell height.") def setup_layout_constants(self): """ @@ -136,9 +155,7 @@ class pinv(pgate.pgate): self.well_width = self.pmos.active_width + self.pmos.active_contact.width \ + drc("active_to_body_active") + 2*drc("well_enclosure_active") self.width = self.well_width - # Height is an input parameter, so it is not recomputed. - - + # Height is an input parameter, so it is not recomputed. def add_ptx(self): """ Create the PMOS and NMOS transistors. """ @@ -162,58 +179,57 @@ class pinv(pgate.pgate): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", layer="metal1", - offset=vector(0.5*self.width,0), + offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", layer="metal1", - offset=vector(0.5*self.width,self.height), + offset=vector(0.5 * self.width, self.height), width=self.width) - - def create_ptx(self): - """ + """ Create the PMOS and NMOS netlist. """ - self.pmos_inst=self.add_inst(name="pinv_pmos", - mod=self.pmos) + self.pmos_inst = self.add_inst(name="pinv_pmos", + mod=self.pmos) self.connect_inst(["Z", "A", "vdd", "vdd"]) - self.nmos_inst=self.add_inst(name="pinv_nmos", - mod=self.nmos) + self.nmos_inst = self.add_inst(name="pinv_nmos", + mod=self.nmos) self.connect_inst(["Z", "A", "gnd", "gnd"]) - def place_ptx(self): - """ + """ Place PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ # place PMOS so it is half a poly spacing down from the top - self.pmos_pos = self.pmos.active_offset.scale(1,0) \ - + vector(0, self.height-self.pmos.active_height-self.top_bottom_space) + self.pmos_pos = self.pmos.active_offset.scale(1, 0) \ + + vector(0, + self.height - self.pmos.active_height - self.top_bottom_space) self.pmos_inst.place(self.pmos_pos) # place NMOS so that it is half a poly spacing up from the bottom - self.nmos_pos = self.nmos.active_offset.scale(1,0) + vector(0,self.top_bottom_space) + self.nmos_pos = self.nmos.active_offset.scale(1, 0) \ + + vector(0, self.top_bottom_space) self.nmos_inst.place(self.nmos_pos) - # Output position will be in between the PMOS and NMOS drains pmos_drain_pos = self.pmos_inst.get_pin("D").ll() nmos_drain_pos = self.nmos_inst.get_pin("D").ul() - 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.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()) def route_outputs(self): - """ Route the output (drains) together. Optionally, routes output to edge. """ + """ + Route the output (drains) together. + Optionally, routes output to edge. + """ # Get the drain pins nmos_drain_pin = self.nmos_inst.get_pin("D") @@ -222,14 +238,14 @@ class pinv(pgate.pgate): # Pick point at right most of NMOS and connect down to PMOS nmos_drain_pos = nmos_drain_pin.bc() pmos_drain_pos = vector(nmos_drain_pos.x, pmos_drain_pin.uc().y) - self.add_path("metal1",[nmos_drain_pos,pmos_drain_pos]) + self.add_path("metal1", [nmos_drain_pos, pmos_drain_pos]) # Remember the mid for the output - mid_drain_offset = vector(nmos_drain_pos.x,self.output_pos.y) + mid_drain_offset = vector(nmos_drain_pos.x, self.output_pos.y) - if self.route_output == True: + if self.route_output: # This extends the output to the edge of the cell - output_offset = mid_drain_offset.scale(0,1) + vector(self.width,0) + output_offset = mid_drain_offset.scale(0, 1) + vector(self.width, 0) self.add_layout_pin_segment_center(text="Z", layer="metal1", start=mid_drain_offset, @@ -238,8 +254,8 @@ class pinv(pgate.pgate): # This leaves the output as an internal pin (min sized) self.add_layout_pin_rect_center(text="Z", layer="metal1", - offset=mid_drain_offset + vector(0.5*self.m1_width,0)) - + offset=mid_drain_offset \ + + vector(0.5 * self.m1_width, 0)) def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -251,9 +267,9 @@ class pinv(pgate.pgate): def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ - self.connect_pin_to_rail(self.nmos_inst,"S","gnd") + self.connect_pin_to_rail(self.nmos_inst, "S", "gnd") - self.connect_pin_to_rail(self.pmos_inst,"S","vdd") + self.connect_pin_to_rail(self.pmos_inst, "S", "vdd") def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" @@ -268,27 +284,35 @@ class pinv(pgate.pgate): def calculate_effective_capacitance(self, load): """Computes effective capacitance. Results in fF""" c_load = load - c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) transition_prob = 0.5 - return transition_prob*(c_load + c_para) + return transition_prob * (c_load + c_para) def input_load(self): - """Return the capacitance of the gate connection in generic capacitive - units relative to the minimum width of a transistor""" + """ + Return the capacitance of the gate connection in generic capacitive + units relative to the minimum width of a transistor + """ return self.nmos_size + self.pmos_size def get_stage_effort(self, cout, inp_is_rise=True): - """Returns an object representing the parameters for delay in tau units. - Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ - parasitic_delay = 1 - return logical_effort.logical_effort(self.name, - self.size, - self.input_load(), - cout, - parasitic_delay, + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 1 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, not inp_is_rise) - def build_graph(self, graph, inst_name, port_nets): - """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/pinvbuf.py b/compiler/pgates/pinvbuf.py index a8d2c8b8..75b7ce6f 100644 --- a/compiler/pgates/pinvbuf.py +++ b/compiler/pgates/pinvbuf.py @@ -7,12 +7,10 @@ # import debug import pgate -from tech import drc -from math import log from vector import vector -from globals import OPTS from sram_factory import factory + class pinvbuf(pgate.pgate): """ This is a simple inverter/buffer used for driving loads. It is @@ -31,11 +29,10 @@ class pinvbuf(pgate.pgate): # The pinvbuf has a FO of 2 for the first stage, so the second stage # should be sized "half" to prevent loading of the first stage self.size = size - self.predriver_size = max(int(self.size/(self.stage_effort/2)),1) - - # Creates the netlist and layout - pgate.pgate.__init__(self, name) + self.predriver_size = max(int(self.size / (self.stage_effort / 2)), 1) + # Creates the netlist and layout + pgate.pgate.__init__(self, name) def create_netlist(self): self.add_pins() @@ -44,8 +41,8 @@ class pinvbuf(pgate.pgate): def create_layout(self): - self.width = 2*self.inv1.width + self.inv2.width - self.height = 2*self.inv1.height + self.width = 2 * self.inv1.width + self.inv2.width + self.height = 2 * self.inv1.height self.place_modules() self.route_wires() @@ -53,7 +50,6 @@ class pinvbuf(pgate.pgate): self.offset_all_coordinates() - def add_pins(self): self.add_pin("A") self.add_pin("Zb") @@ -64,96 +60,100 @@ class pinvbuf(pgate.pgate): def add_modules(self): # Shield the cap, but have at least a stage effort of 4 - input_size = max(1,int(self.predriver_size/self.stage_effort)) - self.inv = factory.create(module_type="pinv", size=input_size, height=self.row_height) + input_size = max(1, int(self.predriver_size / self.stage_effort)) + self.inv = factory.create(module_type="pinv", + size=input_size, + height=self.row_height) self.add_mod(self.inv) - self.inv1 = factory.create(module_type="pinv", size=self.predriver_size, height=self.row_height) + self.inv1 = factory.create(module_type="pinv", + size=self.predriver_size, + height=self.row_height) self.add_mod(self.inv1) - self.inv2 = factory.create(module_type="pinv", size=self.size, height=self.row_height) + self.inv2 = factory.create(module_type="pinv", + size=self.size, + height=self.row_height) self.add_mod(self.inv2) def create_insts(self): # Create INV1 (capacitance shield) - self.inv1_inst=self.add_inst(name="buf_inv1", - mod=self.inv) - self.connect_inst(["A", "zb_int", "vdd", "gnd"]) + self.inv1_inst = self.add_inst(name="buf_inv1", + mod=self.inv) + self.connect_inst(["A", "zb_int", "vdd", "gnd"]) - - self.inv2_inst=self.add_inst(name="buf_inv2", - mod=self.inv1) - self.connect_inst(["zb_int", "z_int", "vdd", "gnd"]) + self.inv2_inst = self.add_inst(name="buf_inv2", + mod=self.inv1) + self.connect_inst(["zb_int", "z_int", "vdd", "gnd"]) - self.inv3_inst=self.add_inst(name="buf_inv3", - mod=self.inv2) - self.connect_inst(["z_int", "Zb", "vdd", "gnd"]) + self.inv3_inst = self.add_inst(name="buf_inv3", + mod=self.inv2) + self.connect_inst(["z_int", "Zb", "vdd", "gnd"]) - self.inv4_inst=self.add_inst(name="buf_inv4", - mod=self.inv2) - self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) + self.inv4_inst = self.add_inst(name="buf_inv4", + mod=self.inv2) + self.connect_inst(["zb_int", "Z", "vdd", "gnd"]) def place_modules(self): # Add INV1 to the left (capacitance shield) - self.inv1_inst.place(vector(0,0)) + self.inv1_inst.place(vector(0, 0)) # Add INV2 to the right of INV1 - self.inv2_inst.place(vector(self.inv1_inst.rx(),0)) + self.inv2_inst.place(vector(self.inv1_inst.rx(), 0)) # Add INV3 to the right of INV2 - self.inv3_inst.place(vector(self.inv2_inst.rx(),0)) + self.inv3_inst.place(vector(self.inv2_inst.rx(), 0)) # Add INV4 flipped to the bottom aligned with INV2 - self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height), - mirror = "MX") - + self.inv4_inst.place(offset=vector(self.inv2_inst.rx(), + 2 * self.inv2.height), + mirror="MX") def route_wires(self): # inv1 Z to inv2 A z1_pin = self.inv1_inst.get_pin("Z") a2_pin = self.inv2_inst.get_pin("A") - mid_point = vector(z1_pin.cx(), a2_pin.cy()) + mid_point = vector(z1_pin.cx(), a2_pin.cy()) self.add_path("metal1", [z1_pin.center(), mid_point, a2_pin.center()]) # inv2 Z to inv3 A z2_pin = self.inv2_inst.get_pin("Z") a3_pin = self.inv3_inst.get_pin("A") - mid_point = vector(z2_pin.cx(), a3_pin.cy()) + mid_point = vector(z2_pin.cx(), a3_pin.cy()) self.add_path("metal1", [z2_pin.center(), mid_point, a3_pin.center()]) # inv1 Z to inv4 A (up and over) z1_pin = self.inv1_inst.get_pin("Z") a4_pin = self.inv4_inst.get_pin("A") - mid_point = vector(z1_pin.cx(), a4_pin.cy()) - self.add_wire(("metal1","via1","metal2"), [z1_pin.center(), mid_point, a4_pin.center()]) - self.add_via_center(layers=("metal1","via1","metal2"), + mid_point = vector(z1_pin.cx(), a4_pin.cy()) + self.add_wire(("metal1", "via1", "metal2"), + [z1_pin.center(), mid_point, a4_pin.center()]) + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=z1_pin.center()) - - def add_layout_pins(self): # Continous vdd rail along with label. - vdd_pin=self.inv1_inst.get_pin("vdd") + vdd_pin = self.inv1_inst.get_pin("vdd") self.add_layout_pin(text="vdd", layer="metal1", - offset=vdd_pin.ll().scale(0,1), + offset=vdd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) # Continous vdd rail along with label. - gnd_pin=self.inv4_inst.get_pin("gnd") + gnd_pin = self.inv4_inst.get_pin("gnd") self.add_layout_pin(text="gnd", layer="metal1", - offset=gnd_pin.ll().scale(0,1), + offset=gnd_pin.ll().scale(0, 1), width=self.width, height=gnd_pin.height()) # Continous gnd rail along with label. - gnd_pin=self.inv1_inst.get_pin("gnd") + gnd_pin = self.inv1_inst.get_pin("gnd") self.add_layout_pin(text="gnd", layer="metal1", - offset=gnd_pin.ll().scale(0,1), + offset=gnd_pin.ll().scale(0, 1), width=self.width, height=vdd_pin.height()) @@ -161,22 +161,21 @@ class pinvbuf(pgate.pgate): self.add_layout_pin_rect_center(text="Z", layer="metal2", offset=z_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=z_pin.center()) zb_pin = self.inv3_inst.get_pin("Z") self.add_layout_pin_rect_center(text="Zb", layer="metal2", offset=zb_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=zb_pin.center()) - a_pin = self.inv1_inst.get_pin("A") self.add_layout_pin_rect_center(text="A", layer="metal2", offset=a_pin.center()) - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=a_pin.center()) def determine_clk_buf_stage_efforts(self, external_cout, inp_is_rise=False): @@ -194,7 +193,8 @@ class pinvbuf(pgate.pgate): def determine_clk_buf_bar_stage_efforts(self, external_cout, inp_is_rise=False): """Get the stage efforts of the clk -> clk_buf path""" - #After (almost) every stage, the direction of the signal inverts. + + # After (almost) every stage, the direction of the signal inverts. stage_effort_list = [] stage1_cout = self.inv1.get_cin() + self.inv2.get_cin() stage1 = self.inv.get_stage_effort(stage1_cout, inp_is_rise) diff --git a/compiler/pgates/pnand2.py b/compiler/pgates/pnand2.py index 791097e5..c5c69fd4 100644 --- a/compiler/pgates/pnand2.py +++ b/compiler/pgates/pnand2.py @@ -10,10 +10,10 @@ import pgate import debug from tech import drc, parameter, spice from vector import vector -from globals import OPTS import logical_effort from sram_factory import factory + class pnand2(pgate.pgate): """ This module generates gds of a parametrically sized 2-input nand. @@ -22,17 +22,19 @@ class pnand2(pgate.pgate): def __init__(self, name, size=1, height=None): """ Creates a cell for a simple 2 input nand """ - debug.info(2, "creating pnand2 structure {0} with size of {1}".format(name, size)) + debug.info(2, + "creating pnand2 structure {0} with size of {1}".format(name, + size)) self.add_comment("size: {}".format(size)) self.size = size - self.nmos_size = 2*size - self.pmos_size = parameter["beta"]*size - self.nmos_width = self.nmos_size*drc("minwidth_tx") - self.pmos_width = self.pmos_size*drc("minwidth_tx") + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") # FIXME: Allow these to be sized - debug.check(size==1,"Size 1 pnand2 is only supported now.") + debug.check(size == 1, "Size 1 pnand2 is only supported now.") self.tx_mults = 1 # Creates the netlist and layout @@ -61,7 +63,6 @@ class pnand2(pgate.pgate): dir_list = ["INPUT", "INPUT", "OUTPUT", "POWER", "GROUND"] self.add_pin_list(pin_list, dir_list) - def add_ptx(self): """ Create the PMOS and NMOS transistors. """ self.nmos = factory.create(module_type="ptx", @@ -90,113 +91,126 @@ class pnand2(pgate.pgate): self.m3_space + contact.m2m3.second_layer_width) - # Compute the other pmos2 location, but determining offset to overlap the + # Compute the other pmos2 location, + # but determining offset to overlap the # source and drain pins self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2*self.pmos.active_width + contact.active.width \ - + 2*drc("active_to_body_active") + 2*drc("well_enclosure_active") + self.well_width = 2 * self.pmos.active_width + contact.active.width \ + + 2 * drc("active_to_body_active") \ + + 2 * drc("well_enclosure_active") self.width = self.well_width # Height is an input parameter, so it is not recomputed. - # This is the extra space needed to ensure DRC rules to the active contacts - extra_contact_space = max(-self.nmos.get_pin("D").by(),0) + # This is the extra space needed to ensure DRC rules + # to the active contacts + extra_contact_space = max(-self.nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, + self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, drc("poly_extend_active"), self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", layer="metal1", - offset=vector(0.5*self.width,0), + offset=vector(0.5*self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", layer="metal1", - offset=vector(0.5*self.width,self.height), + offset=vector(0.5 * self.width, self.height), width=self.width) def create_ptx(self): - """ + """ Add PMOS and NMOS to the netlist. """ - self.pmos1_inst=self.add_inst(name="pnand2_pmos1", - mod=self.pmos) + self.pmos1_inst = self.add_inst(name="pnand2_pmos1", + mod=self.pmos) self.connect_inst(["vdd", "A", "Z", "vdd"]) self.pmos2_inst = self.add_inst(name="pnand2_pmos2", mod=self.pmos) self.connect_inst(["Z", "B", "vdd", "vdd"]) - self.nmos1_inst=self.add_inst(name="pnand2_nmos1", - mod=self.nmos) + self.nmos1_inst = self.add_inst(name="pnand2_nmos1", + mod=self.nmos) self.connect_inst(["Z", "B", "net1", "gnd"]) - self.nmos2_inst=self.add_inst(name="pnand2_nmos2", - mod=self.nmos) + self.nmos2_inst = self.add_inst(name="pnand2_nmos2", + mod=self.nmos) self.connect_inst(["net1", "A", "gnd", "gnd"]) - def place_ptx(self): - """ + """ Place PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height - self.top_bottom_space) + self.height - self.pmos.active_height \ + - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) self.pmos2_pos = pmos1_pos + self.overlap_offset self.pmos2_inst.place(self.pmos2_pos) - - nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space) + nmos1_pos = vector(self.pmos.active_offset.x, + self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0,0.5*(pmos1_pos.y+nmos1_pos.y+self.nmos.active_height)) + # Output position will be in between the PMOS and NMOS + self.output_pos = vector(0, + 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()) + # This will help with the wells + self.well_pos = vector(0, self.nmos1_inst.uy()) def add_well_contacts(self): - """ Add n/p well taps to the layout and connect to supplies AFTER the wells are created """ + """ + Add n/p well taps to the layout and connect to supplies + AFTER the wells are created + """ self.add_nwell_contact(self.pmos, self.pmos2_pos) self.add_pwell_contact(self.nmos, self.nmos2_pos) - def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ - self.connect_pin_to_rail(self.nmos1_inst,"S","gnd") + self.connect_pin_to_rail(self.nmos1_inst, "S", "gnd") - self.connect_pin_to_rail(self.pmos1_inst,"S","vdd") + self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") - self.connect_pin_to_rail(self.pmos2_inst,"D","vdd") + self.connect_pin_to_rail(self.pmos2_inst, "D", "vdd") def route_inputs(self): """ Route the A and B inputs """ - inputB_yoffset = self.nmos2_pos.y + self.nmos.active_height + self.m2_space + 0.5*self.m2_width - self.route_input_gate(self.pmos2_inst, self.nmos2_inst, inputB_yoffset, "B", position="center") + inputB_yoffset = self.nmos2_pos.y + self.nmos.active_height \ + + self.m2_space + 0.5 * self.m2_width + self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + inputB_yoffset, + "B", + position="center") # This will help with the wells and the input/output placement self.inputA_yoffset = inputB_yoffset + self.input_spacing - self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, "A") + self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A") - def route_output(self): """ Route the Z output """ - # PMOS1 drain + # PMOS1 drain pmos_pin = self.pmos1_inst.get_pin("D") top_pin_offset = pmos_pin.center() # NMOS2 drain @@ -204,24 +218,26 @@ class pnand2(pgate.pgate): bottom_pin_offset = nmos_pin.center() # Output pin - out_offset = vector(nmos_pin.center().x + self.m1_pitch,self.inputA_yoffset) + out_offset = vector(nmos_pin.center().x + self.m1_pitch, + self.inputA_yoffset) # Midpoints of the L routes go horizontal first then vertical mid1_offset = vector(out_offset.x, top_pin_offset.y) - mid2_offset = vector(out_offset.x, bottom_pin_offset.y) + mid2_offset = vector(out_offset.x, bottom_pin_offset.y) self.add_via_center(layers=("metal1", "via1", "metal2"), offset=pmos_pin.center(), - directions=("V","H")) + directions=("V", "H")) self.add_via_center(layers=("metal1", "via1", "metal2"), offset=nmos_pin.center(), - directions=("V","H")) + directions=("V", "H")) self.add_via_center(layers=("metal1", "via1", "metal2"), offset=out_offset) - # PMOS1 to mid-drain to NMOS2 drain - self.add_path("metal2",[top_pin_offset, mid1_offset, out_offset, mid2_offset, bottom_pin_offset]) + self.add_path("metal2", + [top_pin_offset, mid1_offset, out_offset, + mid2_offset, bottom_pin_offset]) # This extends the output to the edge of the cell self.add_layout_pin_rect_center(text="Z", @@ -243,21 +259,32 @@ class pnand2(pgate.pgate): def calculate_effective_capacitance(self, load): """Computes effective capacitance. Results in fF""" c_load = load - c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) transition_prob = 0.1875 - return transition_prob*(c_load + c_para) + return transition_prob * (c_load + c_para) def input_load(self): """Return the relative input capacitance of a single input""" - return self.nmos_size+self.pmos_size + return self.nmos_size + self.pmos_size def get_stage_effort(self, cout, inp_is_rise=True): - """Returns an object representing the parameters for delay in tau units. - Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ - parasitic_delay = 2 - return logical_effort.logical_effort(self.name, self.size, self.input_load(), cout, parasitic_delay, not inp_is_rise) + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 2 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) - def build_graph(self, graph, inst_name, port_nets): - """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/pnand3.py b/compiler/pgates/pnand3.py index 508db024..621829f1 100644 --- a/compiler/pgates/pnand3.py +++ b/compiler/pgates/pnand3.py @@ -10,10 +10,10 @@ import pgate import debug from tech import drc, parameter, spice from vector import vector -from globals import OPTS import logical_effort from sram_factory import factory + class pnand3(pgate.pgate): """ This module generates gds of a parametrically sized 2-input nand. @@ -22,24 +22,25 @@ class pnand3(pgate.pgate): def __init__(self, name, size=1, height=None): """ Creates a cell for a simple 3 input nand """ - debug.info(2, "creating pnand3 structure {0} with size of {1}".format(name, size)) + debug.info(2, + "creating pnand3 structure {0} with size of {1}".format(name, + size)) self.add_comment("size: {}".format(size)) # We have trouble pitch matching a 3x sizes to the bitcell... # If we relax this, we could size this better. self.size = size - self.nmos_size = 2*size - self.pmos_size = parameter["beta"]*size - self.nmos_width = self.nmos_size*drc("minwidth_tx") - self.pmos_width = self.pmos_size*drc("minwidth_tx") + self.nmos_size = 2 * size + self.pmos_size = parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") # FIXME: Allow these to be sized - debug.check(size==1,"Size 1 pnand3 is only supported now.") + debug.check(size == 1,"Size 1 pnand3 is only supported now.") self.tx_mults = 1 # Creates the netlist and layout pgate.pgate.__init__(self, name, height) - def add_pins(self): """ Adds pins for spice netlist """ @@ -90,41 +91,44 @@ 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*drc("active_to_body_active") + 2*drc("well_enclosure_active") \ + self.well_width = 3 * self.pmos.active_width + self.pmos.active_contact.width \ + + 2 * drc("active_to_body_active") + 2 * drc("well_enclosure_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) + self.output_pos = vector(0, 0.5*self.height) - # This is the extra space needed to ensure DRC rules to the active contacts + # This is the extra space needed to ensure DRC rules + # to the active contacts nmos = factory.create(module_type="ptx", tx_type="nmos") - extra_contact_space = max(-nmos.get_pin("D").by(),0) + extra_contact_space = max(-nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), self.poly_space) + self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space \ + + extra_contact_space, + drc("poly_extend_active"), + self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", layer="metal1", - offset=vector(0.5*self.width,0), + offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", layer="metal1", - offset=vector(0.5*self.width,self.height), + offset=vector(0.5 * self.width, self.height), width=self.width) def create_ptx(self): - """ + """ Create the PMOS and NMOS in the netlist. """ - self.pmos1_inst=self.add_inst(name="pnand3_pmos1", - mod=self.pmos) + self.pmos1_inst = self.add_inst(name="pnand3_pmos1", + mod=self.pmos) self.connect_inst(["vdd", "A", "Z", "vdd"]) self.pmos2_inst = self.add_inst(name="pnand3_pmos2", @@ -135,27 +139,27 @@ class pnand3(pgate.pgate): mod=self.pmos) self.connect_inst(["Z", "C", "vdd", "vdd"]) - self.nmos1_inst=self.add_inst(name="pnand3_nmos1", - mod=self.nmos) + self.nmos1_inst = self.add_inst(name="pnand3_nmos1", + mod=self.nmos) self.connect_inst(["Z", "C", "net1", "gnd"]) - self.nmos2_inst=self.add_inst(name="pnand3_nmos2", - mod=self.nmos) + self.nmos2_inst = self.add_inst(name="pnand3_nmos2", + mod=self.nmos) self.connect_inst(["net1", "B", "net2", "gnd"]) - self.nmos3_inst=self.add_inst(name="pnand3_nmos3", - mod=self.nmos) + self.nmos3_inst = self.add_inst(name="pnand3_nmos3", + mod=self.nmos) self.connect_inst(["net2", "A", "gnd", "gnd"]) - def place_ptx(self): - """ - Place the PMOS and NMOS in the layout at the upper-most and lowest position - to provide maximum routing in channel + """ + Place the PMOS and NMOS in the layout at the upper-most + and lowest position to provide maximum routing in channel """ pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height - self.top_bottom_space) + self.height - self.pmos.active_height \ + - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) pmos2_pos = pmos1_pos + self.overlap_offset @@ -165,7 +169,8 @@ class pnand3(pgate.pgate): self.pmos3_inst.place(self.pmos3_pos) - nmos1_pos = vector(self.pmos.active_offset.x, self.top_bottom_space) + nmos1_pos = vector(self.pmos.active_offset.x, + self.top_bottom_space) self.nmos1_inst.place(nmos1_pos) nmos2_pos = nmos1_pos + self.overlap_offset @@ -175,7 +180,7 @@ class pnand3(pgate.pgate): self.nmos3_inst.place(self.nmos3_pos) # This should be placed at the top of the NMOS well - self.well_pos = vector(0,self.nmos1_inst.uy()) + self.well_pos = vector(0, self.nmos1_inst.uy()) def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ @@ -183,42 +188,53 @@ class pnand3(pgate.pgate): self.add_nwell_contact(self.pmos, self.pmos3_pos) self.add_pwell_contact(self.nmos, self.nmos3_pos) - def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ - self.connect_pin_to_rail(self.nmos1_inst,"S","gnd") + self.connect_pin_to_rail(self.nmos1_inst, "S", "gnd") - self.connect_pin_to_rail(self.pmos1_inst,"S","vdd") + self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") - self.connect_pin_to_rail(self.pmos2_inst,"D","vdd") + self.connect_pin_to_rail(self.pmos2_inst, "D", "vdd") def route_inputs(self): """ Route the A and B inputs """ # wire space or wire and one contact space - metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width, - self.m1_space + 0.5*contact.poly.width + 0.5*self.m1_width) + metal_spacing = max(self.m1_space + self.m1_width, + self.m2_space + self.m2_width, + self.m1_space + 0.5 *contact.poly.width + 0.5 * self.m1_width) - active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc("poly_to_active")) + active_spacing = max(self.m1_space, + 0.5 * contact.poly.first_layer_width + drc("poly_to_active")) inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing - self.route_input_gate(self.pmos3_inst, self.nmos3_inst, inputC_yoffset, "C", position="center") + self.route_input_gate(self.pmos3_inst, + self.nmos3_inst, + inputC_yoffset, + "C", + position="center") inputB_yoffset = inputC_yoffset + metal_spacing - self.route_input_gate(self.pmos2_inst, self.nmos2_inst, inputB_yoffset, "B", position="center") + self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + inputB_yoffset, + "B", + position="center") self.inputA_yoffset = inputB_yoffset + metal_spacing - self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, "A", position="center") - - + self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A", + position="center") def route_output(self): """ Route the Z output """ - # PMOS1 drain + # PMOS1 drain pmos1_pin = self.pmos1_inst.get_pin("D") - # PMOS3 drain + # PMOS3 drain pmos3_pin = self.pmos3_inst.get_pin("D") # NMOS3 drain - nmos3_pin = self.nmos3_inst.get_pin("D") + nmos3_pin = self.nmos3_inst.get_pin("D") # Go up to metal2 for ease on all output pins self.add_via_center(layers=("metal1", "via1", "metal2"), @@ -229,10 +245,10 @@ class pnand3(pgate.pgate): offset=nmos3_pin.center()) # PMOS3 and NMOS3 are drain aligned - self.add_path("metal2",[pmos3_pin.bc(), nmos3_pin.uc()]) + self.add_path("metal2", [pmos3_pin.bc(), nmos3_pin.uc()]) # Route in the A input track (top track) - mid_offset = vector(nmos3_pin.center().x,self.inputA_yoffset) - self.add_path("metal2",[pmos1_pin.bc(), mid_offset, nmos3_pin.uc()]) + mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset) + self.add_path("metal2", [pmos1_pin.bc(), mid_offset, nmos3_pin.uc()]) # This extends the output to the edge of the cell self.add_via_center(layers=("metal1", "via1", "metal2"), @@ -256,21 +272,32 @@ class pnand3(pgate.pgate): def calculate_effective_capacitance(self, load): """Computes effective capacitance. Results in fF""" c_load = load - c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) transition_prob = 0.1094 - return transition_prob*(c_load + c_para) + return transition_prob *(c_load + c_para) def input_load(self): """Return the relative input capacitance of a single input""" - return self.nmos_size+self.pmos_size + return self.nmos_size + self.pmos_size def get_stage_effort(self, cout, inp_is_rise=True): - """Returns an object representing the parameters for delay in tau units. - Optional is_rise refers to the input direction rise/fall. Input inverted by this stage. """ - parasitic_delay = 3 - return logical_effort.logical_effort(self.name, self.size, self.input_load(), cout, parasitic_delay, not inp_is_rise) + Returns an object representing the parameters for delay in tau units. + Optional is_rise refers to the input direction rise/fall. + Input inverted by this stage. + """ + parasitic_delay = 3 + return logical_effort.logical_effort(self.name, + self.size, + self.input_load(), + cout, + parasitic_delay, + not inp_is_rise) - def build_graph(self, graph, inst_name, port_nets): - """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/pnor2.py b/compiler/pgates/pnor2.py index ed7388e9..a99253cd 100644 --- a/compiler/pgates/pnor2.py +++ b/compiler/pgates/pnor2.py @@ -10,10 +10,9 @@ import pgate import debug from tech import drc, parameter, spice from vector import vector -from globals import OPTS -import logical_effort from sram_factory import factory + class pnor2(pgate.pgate): """ This module generates gds of a parametrically sized 2-input nor. @@ -22,22 +21,23 @@ class pnor2(pgate.pgate): def __init__(self, name, size=1, height=None): """ Creates a cell for a simple 2 input nor """ - debug.info(2, "creating pnor2 structure {0} with size of {1}".format(name, size)) + debug.info(2, + "creating pnor2 structure {0} with size of {1}".format(name, + size)) self.add_comment("size: {}".format(size)) self.nmos_size = size # We will just make this 1.5 times for now. NORs are not ideal anyhow. - self.pmos_size = 1.5*parameter["beta"]*size - self.nmos_width = self.nmos_size*drc("minwidth_tx") - self.pmos_width = self.pmos_size*drc("minwidth_tx") + self.pmos_size = 1.5 * parameter["beta"] * size + self.nmos_width = self.nmos_size * drc("minwidth_tx") + self.pmos_width = self.pmos_size * drc("minwidth_tx") # FIXME: Allow these to be sized - debug.check(size==1,"Size 1 pnor2 is only supported now.") + debug.check(size==1, "Size 1 pnor2 is only supported now.") self.tx_mults = 1 # Creates the netlist and layout pgate.pgate.__init__(self, name, height) - def create_netlist(self): self.add_pins() @@ -89,44 +89,48 @@ class pnor2(pgate.pgate): self.m2_space + contact.m2m3.first_layer_width, self.m3_space + contact.m2m3.second_layer_width) - # Compute the other pmos2 location, but determining offset to overlap the - # source and drain pins + # Compute the other pmos2 location, but determining + # offset to overlap the source and drain pins self.overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() # Two PMOS devices and a well contact. Separation between each. # Enclosure space on the sides. - self.well_width = 2*self.pmos.active_width + self.pmos.active_contact.width \ - + 2*drc("active_to_body_active") + 2*drc("well_enclosure_active") + self.well_width = 2 * self.pmos.active_width \ + + self.pmos.active_contact.width \ + + 2 * drc("active_to_body_active") \ + + 2 * drc("well_enclosure_active") self.width = self.well_width # Height is an input parameter, so it is not recomputed. - # This is the extra space needed to ensure DRC rules to the active contacts - extra_contact_space = max(-self.nmos.get_pin("D").by(),0) + # This is the extra space needed to ensure DRC rules + # to the active contacts + extra_contact_space = max(-self.nmos.get_pin("D").by(), 0) # This is a poly-to-poly of a flipped cell - self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space, - drc("poly_extend_active"), self.poly_space) + self.top_bottom_space = max(0.5 * self.m1_width + self.m1_space + extra_contact_space, + drc("poly_extend_active"), + self.poly_space) def route_supply_rails(self): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", layer="metal1", - offset=vector(0.5*self.width,0), + offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", layer="metal1", - offset=vector(0.5*self.width,self.height), + offset=vector(0.5 * self.width, self.height), width=self.width) def create_ptx(self): - """ + """ Add PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ - self.pmos1_inst=self.add_inst(name="pnor2_pmos1", - mod=self.pmos) + self.pmos1_inst = self.add_inst(name="pnor2_pmos1", + mod=self.pmos) self.connect_inst(["vdd", "A", "net1", "vdd"]) self.pmos2_inst = self.add_inst(name="pnor2_pmos2", @@ -134,23 +138,23 @@ class pnor2(pgate.pgate): self.connect_inst(["net1", "B", "Z", "vdd"]) - self.nmos1_inst=self.add_inst(name="pnor2_nmos1", - mod=self.nmos) + self.nmos1_inst = self.add_inst(name="pnor2_nmos1", + mod=self.nmos) self.connect_inst(["Z", "A", "gnd", "gnd"]) - self.nmos2_inst=self.add_inst(name="pnor2_nmos2", - mod=self.nmos) + self.nmos2_inst = self.add_inst(name="pnor2_nmos2", + mod=self.nmos) self.connect_inst(["Z", "B", "gnd", "gnd"]) - def place_ptx(self): - """ + """ Add PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ pmos1_pos = vector(self.pmos.active_offset.x, - self.height - self.pmos.active_height - self.top_bottom_space) + self.height - self.pmos.active_height \ + - self.top_bottom_space) self.pmos1_inst.place(pmos1_pos) self.pmos2_pos = pmos1_pos + self.overlap_offset @@ -162,42 +166,49 @@ class pnor2(pgate.pgate): self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0,0.5*(pmos1_pos.y+nmos1_pos.y+self.nmos.active_height)) + # Output position will be in between the PMOS and NMOS + self.output_pos = vector(0, + 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()) + # This will help with the wells + self.well_pos = vector(0, self.nmos1_inst.uy()) def add_well_contacts(self): """ Add n/p well taps to the layout and connect to supplies """ self.add_nwell_contact(self.pmos, self.pmos2_pos) self.add_pwell_contact(self.nmos, self.nmos2_pos) - def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ - self.connect_pin_to_rail(self.nmos1_inst,"S","gnd") + self.connect_pin_to_rail(self.nmos1_inst, "S", "gnd") - self.connect_pin_to_rail(self.nmos2_inst,"D","gnd") - - self.connect_pin_to_rail(self.pmos1_inst,"S","vdd") + self.connect_pin_to_rail(self.nmos2_inst, "D", "gnd") + self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") def route_inputs(self): """ Route the A and B inputs """ # Use M2 spaces so we can drop vias on the pins later! - inputB_yoffset = self.nmos2_pos.y + self.nmos.active_height + self.m2_space + self.m2_width - self.route_input_gate(self.pmos2_inst, self.nmos2_inst, inputB_yoffset, "B", position="center") + inputB_yoffset = self.nmos2_pos.y + self.nmos.active_height \ + + self.m2_space + self.m2_width + self.route_input_gate(self.pmos2_inst, + self.nmos2_inst, + inputB_yoffset, + "B", + position="center") # This will help with the wells and the input/output placement self.inputA_yoffset = inputB_yoffset + self.input_spacing - self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.inputA_yoffset, "A") + self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.inputA_yoffset, + "A") def route_output(self): """ Route the Z output """ - # PMOS2 drain + # PMOS2 drain pmos_pin = self.pmos2_inst.get_pin("D") # NMOS1 drain nmos_pin = self.nmos1_inst.get_pin("D") @@ -207,17 +218,18 @@ class pnor2(pgate.pgate): # Go up to metal2 for ease on all output pins self.add_via_center(layers=("metal1", "via1", "metal2"), offset=pmos_pin.center()) - m1m2_contact=self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=nmos_pin.center()) - + m1m2_contact = self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=nmos_pin.center()) - mid1_offset = vector(pmos_pin.center().x,nmos2_pin.center().y) - mid2_offset = vector(pmos_pin.center().x,self.inputA_yoffset) - mid3_offset = mid2_offset + vector(self.m2_width,0) + mid1_offset = vector(pmos_pin.center().x, nmos2_pin.center().y) + mid2_offset = vector(pmos_pin.center().x, self.inputA_yoffset) + mid3_offset = mid2_offset + vector(self.m2_width, 0) # PMOS1 to mid-drain to NMOS2 drain - self.add_path("metal2",[pmos_pin.bc(), mid2_offset, mid3_offset]) - self.add_path("metal2",[nmos_pin.rc(), mid1_offset, mid2_offset]) + self.add_path("metal2", + [pmos_pin.bc(), mid2_offset, mid3_offset]) + self.add_path("metal2", + [nmos_pin.rc(), mid1_offset, mid2_offset]) # This extends the output to the edge of the cell self.add_via_center(layers=("metal1", "via1", "metal2"), offset=mid3_offset) @@ -240,10 +252,11 @@ class pnor2(pgate.pgate): def calculate_effective_capacitance(self, load): """Computes effective capacitance. Results in fF""" c_load = load - c_para = spice["min_tx_drain_c"]*(self.nmos_size/parameter["min_tx_size"])#ff + # In fF + c_para = spice["min_tx_drain_c"] * (self.nmos_size / parameter["min_tx_size"]) transition_prob = 0.1875 - return transition_prob*(c_load + c_para) + return transition_prob * (c_load + c_para) - def build_graph(self, graph, inst_name, port_nets): + def build_graph(self, graph, inst_name, port_nets): """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) + self.add_graph_edges(graph, port_nets) diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index b4423bed..bd391ecc 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -13,6 +13,7 @@ from vector import vector from globals import OPTS from sram_factory import factory + class precharge(design.design): """ Creates a single precharge cell @@ -25,16 +26,15 @@ class precharge(design.design): self.bitcell = factory.create(module_type="bitcell") self.beta = parameter["beta"] - self.ptx_width = self.beta*parameter["min_tx_size"] + self.ptx_width = self.beta * parameter["min_tx_size"] self.width = self.bitcell.width self.bitcell_bl = bitcell_bl self.bitcell_br = bitcell_br - # 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() @@ -53,7 +53,8 @@ class precharge(design.design): self.connect_to_bitlines() def add_pins(self): - self.add_pin_list(["bl", "br", "en_bar", "vdd"], ["OUTPUT", "OUTPUT", "INPUT", "POWER"]) + self.add_pin_list(["bl", "br", "en_bar", "vdd"], + ["OUTPUT", "OUTPUT", "INPUT", "POWER"]) def add_ptx(self): """ @@ -64,16 +65,13 @@ class precharge(design.design): tx_type="pmos") self.add_mod(self.pmos) - - - def route_vdd_rail(self): """ Adds a vdd rail at the top of the cell """ # Adds the rail across the width of the cell - vdd_position = vector(0.5*self.width, self.height) + vdd_position = vector(0.5 * self.width, self.height) self.add_rect_center(layer="metal1", offset=vdd_position, width=self.width, @@ -87,44 +85,43 @@ class precharge(design.design): # Add vdd pin above the transistor self.add_power_pin("vdd", pmos_pin.center(), vertical=True) - def create_ptx(self): """ Create both the upper_pmos and lower_pmos to the module """ - self.lower_pmos_inst=self.add_inst(name="lower_pmos", - mod=self.pmos) + self.lower_pmos_inst = self.add_inst(name="lower_pmos", + mod=self.pmos) self.connect_inst(["bl", "en_bar", "br", "vdd"]) - self.upper_pmos1_inst=self.add_inst(name="upper_pmos1", - mod=self.pmos) + self.upper_pmos1_inst = self.add_inst(name="upper_pmos1", + mod=self.pmos) self.connect_inst(["bl", "en_bar", "vdd", "vdd"]) - self.upper_pmos2_inst=self.add_inst(name="upper_pmos2", - mod=self.pmos) + self.upper_pmos2_inst = self.add_inst(name="upper_pmos2", + mod=self.pmos) self.connect_inst(["br", "en_bar", "vdd", "vdd"]) - def place_ptx(self): """ Place both the upper_pmos and lower_pmos to the module """ - # Compute the other pmos2 location, but determining offset to overlap the - # source and drain pins + # Compute the other pmos2 location, + # but determining offset to overlap the source and drain pins overlap_offset = self.pmos.get_pin("D").ll() - self.pmos.get_pin("S").ll() # This is how much the contact is placed inside the ptx active contact_xdiff = self.pmos.get_pin("S").lx() # 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.lower_pmos_position = vector(max(bl_xoffset - contact_xdiff, + self.well_enclose_active), self.pmos.active_offset.y) self.lower_pmos_inst.place(self.lower_pmos_position) # adds the upper pmos(s) to layout - ydiff = self.pmos.height + 2*self.m1_space + contact.poly.width + ydiff = self.pmos.height + 2 * self.m1_space + contact.poly.width self.upper_pmos1_pos = self.lower_pmos_position + vector(0, ydiff) self.upper_pmos1_inst.place(self.upper_pmos1_pos) @@ -146,7 +143,9 @@ class precharge(design.design): # connects the two poly for the two upper pmos(s) offset = offset + vector(0, ylength - self.poly_width) - xlength = self.upper_pmos2_inst.get_pin("G").lx() - self.upper_pmos1_inst.get_pin("G").lx() + self.poly_width + xlength = self.upper_pmos2_inst.get_pin("G").lx() \ + - self.upper_pmos1_inst.get_pin("G").lx() \ + + self.poly_width self.add_rect(layer="poly", offset=offset, width=xlength, @@ -158,16 +157,16 @@ class precharge(design.design): """ # adds the en contact to connect the gates to the en rail on metal1 - offset = self.lower_pmos_inst.get_pin("G").ul() + vector(0,0.5*self.poly_space) + offset = self.lower_pmos_inst.get_pin("G").ul() \ + + vector(0, 0.5 * self.poly_space) self.add_via_center(layers=("poly", "contact", "metal1"), offset=offset) # adds the en rail on metal1 self.add_layout_pin_segment_center(text="en_bar", layer="metal1", - start=offset.scale(0,1), - end=offset.scale(0,1)+vector(self.width,0)) - + start=offset.scale(0, 1), + end=offset.scale(0, 1) + vector(self.width, 0)) def place_nwell_and_contact(self): """ @@ -175,8 +174,9 @@ 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.well.height/2 + drc("well_extend_active")) + well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1, 0) \ + + vector(0, self.upper_pmos1_inst.uy() + contact.well.height / 2 \ + + drc("well_extend_active")) self.add_via_center(layers=("active", "contact", "metal1"), offset=well_contact_pos, implant_type="n", @@ -187,18 +187,18 @@ class precharge(design.design): # nwell should span the whole design since it is pmos only self.add_rect(layer="nwell", - offset=vector(0,0), + offset=vector(0, 0), width=self.width, height=self.height) - def route_bitlines(self): """ Adds both bit-line and bit-line-bar to the module """ # adds the BL on metal 2 - offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(),0) - vector(0.5 * self.m2_width,0) + offset = vector(self.bitcell.get_pin(self.bitcell_bl).cx(), 0) \ + - vector(0.5 * self.m2_width, 0) self.bl_pin = self.add_layout_pin(text="bl", layer="metal2", offset=offset, @@ -206,7 +206,8 @@ class precharge(design.design): height=self.height) # adds the BR on metal 2 - offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(),0) - vector(0.5 * self.m2_width,0) + offset = vector(self.bitcell.get_pin(self.bitcell_br).cx(), 0) \ + - vector(0.5 * self.m2_width, 0) self.br_pin = self.add_layout_pin(text="br", layer="metal2", offset=offset, @@ -218,60 +219,64 @@ class precharge(design.design): Connect the bitlines to the devices """ self.add_bitline_contacts() - self.connect_pmos_m2(self.lower_pmos_inst.get_pin("S"),self.get_pin("bl")) - self.connect_pmos_m2(self.upper_pmos1_inst.get_pin("S"),self.get_pin("bl")) - self.connect_pmos_m1(self.lower_pmos_inst.get_pin("D"),self.get_pin("br")) - self.connect_pmos_m1(self.upper_pmos2_inst.get_pin("D"),self.get_pin("br")) - + self.connect_pmos_m2(self.lower_pmos_inst.get_pin("S"), + self.get_pin("bl")) + self.connect_pmos_m2(self.upper_pmos1_inst.get_pin("S"), + self.get_pin("bl")) + self.connect_pmos_m1(self.lower_pmos_inst.get_pin("D"), + self.get_pin("br")) + self.connect_pmos_m1(self.upper_pmos2_inst.get_pin("D"), + self.get_pin("br")) def add_bitline_contacts(self): """ Adds contacts/via from metal1 to metal2 for bit-lines """ - stack=("metal1", "via1", "metal2") + stack = ("metal1", "via1", "metal2") upper_pin = self.upper_pmos1_inst.get_pin("S") lower_pin = self.lower_pmos_inst.get_pin("S") # BL goes up to M2 at the transistor - self.bl_contact=self.add_via_center(layers=stack, - offset=upper_pin.center(), - directions=("V","V")) + self.bl_contact =self.add_via_center(layers=stack, + offset=upper_pin.center(), + directions=("V", "V")) self.add_via_center(layers=stack, offset=lower_pin.center(), - directions=("V","V")) + directions=("V", "V")) # BR routes over on M1 first self.add_via_center(layers=stack, - offset = vector(self.br_pin.cx(), upper_pin.cy()), - directions=("V","V")) + offset=vector(self.br_pin.cx(), upper_pin.cy()), + directions=("V", "V")) self.add_via_center(layers=stack, - offset = vector(self.br_pin.cx(), lower_pin.cy()), - directions=("V","V")) + offset=vector(self.br_pin.cx(), lower_pin.cy()), + directions=("V", "V")) def connect_pmos_m1(self, pmos_pin, bit_pin): - """ - Connect a pmos pin to bitline pin + """ + Connect a pmos pin to bitline pin """ - left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy()) - right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy()) + left_pos = vector(min(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) + right_pos = vector(max(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - self.add_path("metal1", [ left_pos, right_pos] ) + self.add_path("metal1", [left_pos, right_pos] ) def connect_pmos_m2(self, pmos_pin, bit_pin): - """ - Connect a pmos pin to bitline pin + """ + Connect a pmos pin to bitline pin """ - left_pos = vector(min(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy()) - right_pos = vector(max(pmos_pin.cx(),bit_pin.cx()), pmos_pin.cy()) + left_pos = vector(min(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) + right_pos = vector(max(pmos_pin.cx(), bit_pin.cx()), pmos_pin.cy()) - self.add_path("metal2", [ left_pos, right_pos], self.bl_contact.height) + self.add_path("metal2", [left_pos, right_pos], self.bl_contact.height) def get_en_cin(self): - """Get the relative capacitance of the enable in the precharge cell""" - #The enable connect to three pmos gates. They all use the same size pmos. + """Get the relative capacitance of the enable in the precharge cell""" + # The enable connect to three pmos gates + # They all use the same size pmos. pmos_cin = self.pmos.get_cin() - return 3*pmos_cin + return 3 * pmos_cin diff --git a/compiler/pgates/ptristate_inv.py b/compiler/pgates/ptristate_inv.py index 0564bf86..2a972407 100644 --- a/compiler/pgates/ptristate_inv.py +++ b/compiler/pgates/ptristate_inv.py @@ -10,16 +10,12 @@ import pgate import debug from tech import drc, parameter, spice from vector import vector -from math import ceil -from globals import OPTS -from utils import round_to_grid -import logical_effort from sram_factory import factory + class ptristate_inv(pgate.pgate): """ - ptristate generates gds of a parametrically sized tristate inverter. - + ptristate generates gds of a parametrically sized tristate inverter. There is some flexibility in the size, but we do not allow multiple fingers to fit in the cell height. @@ -27,14 +23,16 @@ class ptristate_inv(pgate.pgate): def __init__(self, name, size=1, height=None): - debug.info(2, "creating ptristate inv {0} with size of {1}".format(name, size)) + debug.info(2, + "creating ptristate inv {0} with size of {1}".format(name, + size)) self.add_comment("size: {}".format(size)) # We are 2x since there are two series devices - self.size = 2*size + self.size = 2 * size self.nmos_size = size self.beta = parameter["beta"] - self.pmos_size = self.beta*size + self.pmos_size = self.beta * size self.nmos_width = self.nmos_size * drc("minwidth_tx") self.pmos_width = self.pmos_size * drc("minwidth_tx") @@ -75,10 +73,10 @@ 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 + drc("well_enclosure_active") + self.well_width = 2 * self.pmos.active_width + drc("well_enclosure_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 + self.width = self.well_width + 0.5 * self.m1_space # Height is an input parameter, so it is not recomputed. # Make sure we can put a well above and below @@ -104,43 +102,44 @@ class ptristate_inv(pgate.pgate): """ Add vdd/gnd rails to the top and bottom. """ self.add_layout_pin_rect_center(text="gnd", layer="metal1", - offset=vector(0.5*self.width,0), + offset=vector(0.5 * self.width, 0), width=self.width) self.add_layout_pin_rect_center(text="vdd", layer="metal1", - offset=vector(0.5*self.width,self.height), + offset=vector(0.5 * self.width, self.height), width=self.width) - def create_ptx(self): - """ + """ Create the PMOS and NMOS netlist. """ # These are the inverter PMOS/NMOS - self.pmos1_inst=self.add_inst(name="ptri_pmos1", mod=self.pmos) + self.pmos1_inst = self.add_inst(name="ptri_pmos1", + mod=self.pmos) self.connect_inst(["vdd", "in", "n1", "vdd"]) - self.nmos1_inst=self.add_inst(name="ptri_nmos1", mod=self.nmos) + self.nmos1_inst = self.add_inst(name="ptri_nmos1", + mod=self.nmos) self.connect_inst(["gnd", "in", "n2", "gnd"]) # These are the tristate PMOS/NMOS self.pmos2_inst = self.add_inst(name="ptri_pmos2", mod=self.pmos) self.connect_inst(["out", "en_bar", "n1", "vdd"]) - self.nmos2_inst=self.add_inst(name="ptri_nmos2", mod=self.nmos) + self.nmos2_inst = self.add_inst(name="ptri_nmos2", + mod=self.nmos) self.connect_inst(["out", "en", "n2", "gnd"]) - - def place_ptx(self): - """ + """ Place PMOS and NMOS to the layout at the upper-most and lowest position to provide maximum routing in channel """ - pmos_yoff = self.height - self.pmos.active_height - self.top_bottom_space - 0.5*contact.well.height - nmos_yoff = self.top_bottom_space + 0.5*contact.well.height + pmos_yoff = self.height - self.pmos.active_height \ + - self.top_bottom_space - 0.5 * contact.well.height + nmos_yoff = self.top_bottom_space + 0.5 * contact.well.height # Tristate transistors pmos1_pos = vector(self.pmos.active_offset.x, pmos_yoff) @@ -154,21 +153,24 @@ class ptristate_inv(pgate.pgate): self.nmos2_pos = nmos1_pos + self.overlap_offset self.nmos2_inst.place(self.nmos2_pos) - # Output position will be in between the PMOS and NMOS - self.output_pos = vector(0, 0.5*(pmos_yoff + nmos_yoff + self.nmos.height)) + # Output position will be in between the PMOS and NMOS + self.output_pos = vector(0, + 0.5 * (pmos_yoff + nmos_yoff + self.nmos.height)) - # This will help with the wells - self.well_pos = vector(0,self.nmos1_inst.uy()) - + # This will help with the wells + self.well_pos = vector(0, self.nmos1_inst.uy()) def route_inputs(self): """ Route the gates """ - self.route_input_gate(self.pmos1_inst, self.nmos1_inst, self.output_pos.y, "in", position="farleft") + self.route_input_gate(self.pmos1_inst, + self.nmos1_inst, + self.output_pos.y, + "in", + position="farleft") self.route_single_gate(self.pmos2_inst, "en_bar", position="left") self.route_single_gate(self.nmos2_inst, "en", position="left") - def route_outputs(self): """ Route the output (drains) together. """ @@ -181,40 +183,41 @@ class ptristate_inv(pgate.pgate): self.add_layout_pin(text="out", layer="metal1", offset=nmos_drain_pos, - height=pmos_drain_pos.y-nmos_drain_pos.y) - + height=pmos_drain_pos.y - nmos_drain_pos.y) def add_well_contacts(self): - """ Add n/p well taps to the layout and connect to supplies AFTER the wells are created """ + """ + Add n/p well taps to the layout and connect to + supplies AFTER the wells are created + """ layer_stack = ("active", "contact", "metal1") drain_pos = self.nmos1_inst.get_pin("S").center() vdd_pos = self.get_pin("vdd").center() - self.nwell_contact=self.add_via_center(layers=layer_stack, - offset=vector(drain_pos.x,vdd_pos.y), - implant_type="n", - well_type="n") + self.nwell_contact = self.add_via_center(layers=layer_stack, + offset=vector(drain_pos.x, vdd_pos.y), + implant_type="n", + well_type="n") gnd_pos = self.get_pin("gnd").center() - self.pwell_contact=self.add_via_center(layers=layer_stack, - offset=vector(drain_pos.x,gnd_pos.y), - implant_type="p", - well_type="p") - - + self.pwell_contact = self.add_via_center(layers=layer_stack, + offset=vector(drain_pos.x, gnd_pos.y), + implant_type="p", + well_type="p") def connect_rails(self): """ Connect the nmos and pmos to its respective power rails """ - self.connect_pin_to_rail(self.nmos1_inst,"S","gnd") - self.connect_pin_to_rail(self.pmos1_inst,"S","vdd") + self.connect_pin_to_rail(self.nmos1_inst, "S", "gnd") + self.connect_pin_to_rail(self.pmos1_inst, "S", "vdd") def analytical_power(self, corner, load): """Returns dynamic and leakage power. Results in nW""" - #Power in this module currently not defined. Returns 0 nW (leakage and dynamic). - total_power = self.return_power() + # Power in this module currently not defined. + # Returns 0 nW (leakage and dynamic). + total_power = self.return_power() return total_power def get_cin(self): - return 9*spice["min_tx_gate_c"] + return 9 * spice["min_tx_gate_c"] diff --git a/compiler/pgates/ptx.py b/compiler/pgates/ptx.py index 79a29d07..3cc767d7 100644 --- a/compiler/pgates/ptx.py +++ b/compiler/pgates/ptx.py @@ -9,9 +9,9 @@ import design import debug from tech import drc, spice from vector import vector -from globals import OPTS from sram_factory import factory + class ptx(design.design): """ This module generates gds and spice of a parametrically NMOS or @@ -21,7 +21,14 @@ class ptx(design.design): you to connect the fingered gates and active for parallel devices. """ - def __init__(self, name="", width=drc("minwidth_tx"), mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None): + def __init__(self, + name="", + width=drc("minwidth_tx"), + mults=1, + tx_type="nmos", + connect_active=False, + connect_poly=False, + num_contacts=None): # We need to keep unique names because outputting to GDSII # will use the last record with a given name. I.e., you will # over-write a design in GDS if one has and the other doesn't @@ -34,7 +41,7 @@ class ptx(design.design): if num_contacts: name += "_c{}".format(num_contacts) # replace periods with underscore for newer spice compatibility - name=name.replace('.','_') + name = name.replace('.', '_') debug.info(3, "creating ptx {0}".format(name)) design.design.__init__(self, name) @@ -51,42 +58,43 @@ class ptx(design.design): # We must always create ptx layout for pbitcell # some transistor sizes in other netlist depend on pbitcell self.create_layout() - - def create_layout(self): """Calls all functions related to the generation of the layout""" self.setup_layout_constants() self.add_active() - self.add_well_implant() + self.add_well_implant() self.add_poly() self.add_active_contacts() self.translate_all(self.active_offset) # for run-time, we won't check every transitor DRC independently # but this may be uncommented for debug purposes - #self.DRC() + # self.DRC() def create_netlist(self): pin_list = ["D", "G", "S", "B"] - if self.tx_type=="nmos": + if self.tx_type == "nmos": body_dir = 'GROUND' - else: #Assumed that the check for either pmos or nmos is done elsewhere. + else: + # Assumed that the check for either pmos or nmos is done elsewhere. body_dir = 'POWER' dir_list = ['INOUT', 'INPUT', 'INOUT', body_dir] self.add_pin_list(pin_list, dir_list) # self.spice.append("\n.SUBCKT {0} {1}".format(self.name, # " ".join(self.pins))) - # Just make a guess since these will actually be decided in the layout later. - area_sd = 2.5*drc("minwidth_poly")*self.tx_width - perimeter_sd = 2*drc("minwidth_poly") + 2*self.tx_width - self.spice_device="M{{0}} {{1}} {0} m={1} w={2}u l={3}u pd={4:.2f}u ps={4:.2f}u as={5:.2f}p ad={5:.2f}p".format(spice[self.tx_type], - self.mults, - self.tx_width, - drc("minwidth_poly"), - perimeter_sd, - area_sd) + # Just make a guess since these will actually + # be decided in the layout later. + area_sd = 2.5 * drc("minwidth_poly") * self.tx_width + perimeter_sd = 2 * drc("minwidth_poly") + 2 * self.tx_width + main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type], + self.mults, + self.tx_width, + 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.append("\n* ptx " + self.spice_device) # self.spice.append(".ENDS {0}".format(self.name)) @@ -116,38 +124,39 @@ class ptx(design.design): # The contacted poly pitch (or uncontacted in an odd technology) - self.poly_pitch = max(2*self.contact_to_gate + self.contact_width + self.poly_width, + 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 + 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(drc("active_enclosure_contact"), - (self.active_width - self.contact_width)/2) + (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 # 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 # Poly height must include poly extension over active - self.poly_height = self.tx_width + 2*self.poly_extend_active + self.poly_height = self.tx_width + 2 * self.poly_extend_active # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active]*2) + self.active_offset = vector([self.well_enclose_active] * 2) # Well enclosure of active, ensure minwidth as well if drc("has_{}well".format(self.well_type)): - 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) + 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) # 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 @@ -157,17 +166,20 @@ class ptx(design.design): self.width = self.active_width # The active offset is due to the well extension - self.active_offset = vector([self.well_enclose_active]*2) + 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, - 0.5*self.active_height) + self.contact_offset = self.active_offset + vector(active_enclose_contact + 0.5 * self.contact_width, + 0.5 * self.active_height) # Min area results are just flagged for now. - debug.check(self.active_width*self.active_height>=drc("minarea_active"),"Minimum active area violated.") - # We do not want to increase the poly dimensions to fix an area problem as it would cause an LVS issue. - debug.check(self.poly_width*self.poly_height>=drc("minarea_poly"),"Minimum poly area violated.") + debug.check(self.active_width * self.active_height >= drc("minarea_active"), + "Minimum active area violated.") + # We do not want to increase the poly dimensions to fix + # an area problem as it would cause an LVS issue. + debug.check(self.poly_width * self.poly_height >= drc("minarea_poly"), + "Minimum poly area violated.") def connect_fingered_poly(self, poly_positions): """ @@ -182,13 +194,19 @@ class ptx(design.design): # The width of the poly is from the left-most to right-most poly gate poly_width = poly_positions[-1].x - poly_positions[0].x + self.poly_width if self.tx_type == "pmos": - # This can be limited by poly to active spacing or the poly extension - distance_below_active = self.poly_width + max(self.poly_to_active,0.5*self.poly_height) - poly_offset = poly_positions[0] - vector(0.5*self.poly_width, distance_below_active) + # This can be limited by poly to active spacing + # or the poly extension + distance_below_active = self.poly_width + max(self.poly_to_active, + 0.5 * self.poly_height) + poly_offset = poly_positions[0] - vector(0.5 * self.poly_width, + distance_below_active) else: - # This can be limited by poly to active spacing or the poly extension - distance_above_active = max(self.poly_to_active,0.5*self.poly_height) - poly_offset = poly_positions[0] + vector(-0.5*self.poly_width, distance_above_active) + # This can be limited by poly to active spacing + # or the poly extension + distance_above_active = max(self.poly_to_active, + 0.5 * self.poly_height) + 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 self.add_layout_pin(text="G", @@ -205,12 +223,13 @@ 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) - # drains always go to the MIDDLE of the cell, so top of NMOS, bottom of PMOS + # drains always go to the MIDDLE of the cell, + # so top of NMOS, bottom of PMOS # so reverse the directions for NMOS compared to PMOS. if self.tx_type == "pmos": drain_dir = -1 @@ -219,17 +238,19 @@ class ptx(design.design): drain_dir = 1 source_dir = -1 - if len(source_positions)>1: + if len(source_positions) > 1: source_offset = pin_offset.scale(source_dir,source_dir) self.remove_layout_pin("S") # remove the individual connections # Add each vertical segment for a in source_positions: - self.add_path(("metal1"), [a,a+pin_offset.scale(source_dir,source_dir)]) + self.add_path(("metal1"), + [a, a + pin_offset.scale(source_dir, + source_dir)]) # Add a single horizontal pin self.add_layout_pin_segment_center(text="S", layer="metal1", - start=source_positions[0]+source_offset-end_offset, - end=source_positions[-1]+source_offset+end_offset) + start=source_positions[0] + source_offset - end_offset, + end=source_positions[-1] + source_offset + end_offset) if len(drain_positions)>1: drain_offset = pin_offset.scale(drain_dir,drain_dir) @@ -240,24 +261,27 @@ class ptx(design.design): # Add a single horizontal pin self.add_layout_pin_segment_center(text="D", layer="metal1", - start=drain_positions[0]+drain_offset-end_offset, - end=drain_positions[-1]+drain_offset+end_offset) + start=drain_positions[0] + drain_offset - end_offset, + end=drain_positions[-1] + drain_offset + end_offset) def add_poly(self): """ Add the poly gates(s) and (optionally) connect them. """ # poly is one contacted spacing from the end and down an extension - poly_offset = self.active_offset + vector(self.poly_width,self.poly_height).scale(0.5,0.5) \ + poly_offset = self.active_offset \ + + vector(self.poly_width, self.poly_height).scale(0.5, 0.5) \ + vector(self.end_to_poly, -self.poly_extend_active) # poly_positions are the bottom center of the poly gates poly_positions = [] - # It is important that these are from left to right, so that the pins are in the right + # It is important that these are from left to right, + # so that the pins are in the right # order for the accessors for i in range(0, self.mults): - # Add this duplicate rectangle in case we remove the pin when joining fingers + # Add this duplicate rectangle in case we remove + # the pin when joining fingers self.add_rect_center(layer="poly", offset=poly_offset, height=self.poly_height, @@ -274,8 +298,8 @@ class ptx(design.design): self.connect_fingered_poly(poly_positions) def add_active(self): - """ - Adding the diffusion (active region = diffusion region) + """ + Adding the diffusion (active region = diffusion region) """ self.add_rect(layer="active", offset=self.active_offset, @@ -284,11 +308,11 @@ class ptx(design.design): # If the implant must enclose the active, shift offset # and increase width/height enclose_width = drc("implant_enclosure_active") - enclose_offset = [enclose_width]*2 + enclose_offset = [enclose_width] * 2 self.add_rect(layer="{}implant".format(self.implant_type), offset=self.active_offset - enclose_offset, - width=self.active_width + 2*enclose_width, - height=self.active_height + 2*enclose_width) + width=self.active_width + 2 * enclose_width, + height=self.active_height + 2 * enclose_width) def add_well_implant(self): """ @@ -320,15 +344,16 @@ class ptx(design.design): # The first one will always be a source source_positions = [self.contact_offset] drain_positions = [] - # It is important that these are from left to right, so that the pins are in the right + # It is important that these are from left to right, + # so that the pins are in the right # order for the accessors. 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_pitch, 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_pitch, 0)) return [source_positions,drain_positions] @@ -371,9 +396,12 @@ class ptx(design.design): def get_cin(self): """Returns the relative gate cin of the tx""" - return self.tx_width/drc("minwidth_tx") + return self.tx_width / drc("minwidth_tx") - def build_graph(self, graph, inst_name, port_nets): - """Adds edges based on inputs/outputs. Overrides base class function.""" - self.add_graph_edges(graph, port_nets) - \ No newline at end of file + def build_graph(self, graph, inst_name, port_nets): + """ + Adds edges based on inputs/outputs. + Overrides base class function. + """ + self.add_graph_edges(graph, port_nets) + diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index 999d3ccd..ffe313c8 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -9,17 +9,17 @@ import pgate import debug from tech import drc from vector import vector -import contact -from globals import OPTS from sram_factory import factory import logical_effort + class single_level_column_mux(pgate.pgate): """ This module implements the columnmux bitline cell used in the design. Creates a single columnmux cell with the given integer size relative to minimum size. Default is 8x. Per Samira and Hodges-Jackson book: - Column-mux transistors driven by the decoder must be sized for optimal speed + Column-mux transistors driven by the decoder must be sized + for optimal speed """ def __init__(self, name, tx_size=8, bitcell_bl="bl", bitcell_br="br"): @@ -38,7 +38,7 @@ class single_level_column_mux(pgate.pgate): def create_layout(self): - self.pin_height = 2*self.m2_width + self.pin_height = 2 * self.m2_width self.width = self.bitcell.width self.height = self.nmos_upper.uy() + self.pin_height self.connect_poly() @@ -50,11 +50,9 @@ class single_level_column_mux(pgate.pgate): self.bitcell = factory.create(module_type="bitcell") # Adds nmos_lower,nmos_upper to the module - self.ptx_width = self.tx_size*drc("minwidth_tx") + self.ptx_width = self.tx_size * drc("minwidth_tx") self.nmos = factory.create(module_type="ptx", width=self.ptx_width) self.add_mod(self.nmos) - - def add_pins(self): self.add_pin_list(["bl", "br", "bl_out", "br_out", "sel", "gnd"]) @@ -68,11 +66,11 @@ class single_level_column_mux(pgate.pgate): # bl and br self.add_layout_pin(text="bl", layer="metal2", - offset=bl_pos + vector(0,self.height - self.pin_height), + offset=bl_pos + vector(0, self.height - self.pin_height), height=self.pin_height) self.add_layout_pin(text="br", layer="metal2", - offset=br_pos + vector(0,self.height - self.pin_height), + offset=br_pos + vector(0, self.height - self.pin_height), height=self.pin_height) # bl_out and br_out @@ -85,35 +83,34 @@ class single_level_column_mux(pgate.pgate): offset=br_pos, height=self.pin_height) - def add_ptx(self): """ Create the two pass gate NMOS transistors to switch the bitlines""" # Space it in the center - nmos_lower_position = self.nmos.active_offset.scale(0,1) + vector(0.5*self.bitcell.width-0.5*self.nmos.active_width,0) - self.nmos_lower=self.add_inst(name="mux_tx1", - mod=self.nmos, - offset=nmos_lower_position) + nmos_lower_position = self.nmos.active_offset.scale(0,1) \ + + vector(0.5 * self.bitcell.width- 0.5 * self.nmos.active_width, 0) + self.nmos_lower = self.add_inst(name="mux_tx1", + mod=self.nmos, + offset=nmos_lower_position) self.connect_inst(["bl", "sel", "bl_out", "gnd"]) # This aligns it directly above the other tx with gates abutting - nmos_upper_position = nmos_lower_position + vector(0,self.nmos.active_height + self.poly_space) - self.nmos_upper=self.add_inst(name="mux_tx2", - mod=self.nmos, - offset=nmos_upper_position) + nmos_upper_position = nmos_lower_position \ + + vector(0, self.nmos.active_height + self.poly_space) + self.nmos_upper = self.add_inst(name="mux_tx2", + mod=self.nmos, + offset=nmos_upper_position) self.connect_inst(["br", "sel", "br_out", "gnd"]) - def connect_poly(self): """ Connect the poly gate of the two pass transistors """ - height=self.nmos_upper.get_pin("G").uy() - self.nmos_lower.get_pin("G").by() + height = self.nmos_upper.get_pin("G").uy() - self.nmos_lower.get_pin("G").by() self.add_layout_pin(text="sel", layer="poly", offset=self.nmos_lower.get_pin("G").ll(), height=height) - def connect_bitlines(self): """ Connect the bitlines to the mux transistors """ # These are on metal2 @@ -129,52 +126,61 @@ class single_level_column_mux(pgate.pgate): nmos_upper_d_pin = self.nmos_upper.get_pin("D") # Add vias to bl, br_out, nmos_upper/S, nmos_lower/D - self.add_via_center(layers=("metal1","via1","metal2"), + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=bl_pin.bc(), - directions=("V","V")) - self.add_via_center(layers=("metal1","via1","metal2"), + directions=("V", "V")) + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=br_out_pin.uc(), - directions=("V","V")) - self.add_via_center(layers=("metal1","via1","metal2"), + directions=("V", "V")) + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=nmos_upper_s_pin.center(), - directions=("V","V")) - self.add_via_center(layers=("metal1","via1","metal2"), + directions=("V", "V")) + self.add_via_center(layers=("metal1", "via1", "metal2"), offset=nmos_lower_d_pin.center(), - directions=("V","V")) - + directions=("V", "V")) # bl -> nmos_upper/D on metal1 # bl_out -> nmos_upper/S on metal2 - self.add_path("metal1",[bl_pin.ll(), vector(nmos_upper_d_pin.cx(),bl_pin.by()), nmos_upper_d_pin.center()]) + self.add_path("metal1", + [bl_pin.ll(), vector(nmos_upper_d_pin.cx(), bl_pin.by()), + nmos_upper_d_pin.center()]) # halfway up, move over - mid1 = bl_out_pin.uc().scale(1,0.4)+nmos_upper_s_pin.bc().scale(0,0.4) - mid2 = bl_out_pin.uc().scale(0,0.4)+nmos_upper_s_pin.bc().scale(1,0.4) - self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()]) + mid1 = bl_out_pin.uc().scale(1, 0.4) \ + + nmos_upper_s_pin.bc().scale(0, 0.4) + mid2 = bl_out_pin.uc().scale(0, 0.4) \ + + nmos_upper_s_pin.bc().scale(1, 0.4) + self.add_path("metal2", + [bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()]) # br -> nmos_lower/D on metal2 # br_out -> nmos_lower/S on metal1 - self.add_path("metal1",[br_out_pin.uc(), vector(nmos_lower_s_pin.cx(),br_out_pin.uy()), nmos_lower_s_pin.center()]) + self.add_path("metal1", + [br_out_pin.uc(), + vector(nmos_lower_s_pin.cx(), br_out_pin.uy()), + nmos_lower_s_pin.center()]) # halfway up, move over - mid1 = br_pin.bc().scale(1,0.5)+nmos_lower_d_pin.uc().scale(0,0.5) - mid2 = br_pin.bc().scale(0,0.5)+nmos_lower_d_pin.uc().scale(1,0.5) - self.add_path("metal2",[br_pin.bc(), mid1, mid2, nmos_lower_d_pin.uc()]) + mid1 = br_pin.bc().scale(1,0.5) \ + + nmos_lower_d_pin.uc().scale(0,0.5) + mid2 = br_pin.bc().scale(0,0.5) \ + + nmos_lower_d_pin.uc().scale(1,0.5) + self.add_path("metal2", + [br_pin.bc(), mid1, mid2, nmos_lower_d_pin.uc()]) - def add_wells(self): - """ + """ Add a well and implant over the whole cell. Also, add the - pwell contact (if it exists) + pwell contact (if it exists) """ # Add it to the right, aligned in between the two tx - active_pos = vector(self.bitcell.width,self.nmos_upper.by() - 0.5*self.poly_space) - active_via = self.add_via_center(layers=("active", "contact", "metal1"), - offset=active_pos, - implant_type="p", - well_type="p") + active_pos = vector(self.bitcell.width, + self.nmos_upper.by() - 0.5 * self.poly_space) + self.add_via_center(layers=("active", "contact", "metal1"), + offset=active_pos, + implant_type="p", + well_type="p") - - # Add the M1->M2->M3 stack + # Add the M1->M2->M3 stack self.add_via_center(layers=("metal1", "via1", "metal2"), offset=active_pos) self.add_via_center(layers=("metal2", "via2", "metal3"), @@ -185,13 +191,22 @@ class single_level_column_mux(pgate.pgate): # Add well enclosure over all the tx and contact self.add_rect(layer="pwell", - offset=vector(0,0), + offset=vector(0, 0), width=self.bitcell.width, height=self.height) def get_stage_effort(self, corner, slew, load): - """Returns relative delay that the column mux. Difficult to convert to LE model.""" + """ + Returns relative delay that the column mux. + Difficult to convert to LE model. + """ parasitic_delay = 1 - cin = 2*self.tx_size #This is not CMOS, so using this may be incorrect. - return logical_effort.logical_effort('column_mux', self.tx_size, cin, load, parasitic_delay, False) + # This is not CMOS, so using this may be incorrect. + cin = 2 * self.tx_size + return logical_effort.logical_effort("column_mux", + self.tx_size, + cin, + load, + parasitic_delay, + False) diff --git a/compiler/sram_factory.py b/compiler/sram_factory.py index 0083841d..bd602980 100644 --- a/compiler/sram_factory.py +++ b/compiler/sram_factory.py @@ -5,9 +5,9 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import debug from globals import OPTS + class sram_factory: """ This is a factory pattern to create modules for usage in an SRAM. @@ -19,7 +19,7 @@ class sram_factory: """ def __init__(self): - # A dictionary of modules indexed by module type + # A dictionary of modules indexed by module type self.modules = {} # These are the indices to append to name to make unique object names self.module_indices = {} @@ -34,8 +34,8 @@ class sram_factory: def create(self, module_type, **kwargs): """ - A generic function to create a module with a given module_type. The args - are passed directly to the module constructor. + A generic function to create a module with a given module_type. + The args are passed directly to the module constructor. """ # if name!="": # # This is a special case where the name and type don't match @@ -58,28 +58,30 @@ class sram_factory: self.objects[module_type] = [] # Either retreive a previous object or create a new one - #print("new",kwargs) for obj in self.objects[module_type]: (obj_kwargs, obj_item) = obj # Must have the same dictionary exactly (conservative) if obj_kwargs == kwargs: - #debug.info(0, "Existing module: type={0} name={1} kwargs={2}".format(module_type, obj_item.name, str(kwargs))) return obj_item - #else: - # print("obj",obj_kwargs) # Use the default name if there are default arguments - # This is especially for library cells so that the spice and gds files can be found. - if len(kwargs)>0: + # This is especially for library cells so that the + # spice and gds files can be found. + if len(kwargs) > 0: # Create a unique name and increment the index - module_name = "{0}_{1}".format(module_type, self.module_indices[module_type]) + module_name = "{0}_{1}".format(module_type, + self.module_indices[module_type]) self.module_indices[module_type] += 1 else: module_name = module_type - - #debug.info(0, "New module: type={0} name={1} kwargs={2}".format(module_type,module_name,str(kwargs))) - obj = mod(name=module_name,**kwargs) - self.objects[module_type].append((kwargs,obj)) + + # type_str = "type={}".format(module_type) + # name_str = "name={}".format(module_name) + # kwargs_str = "kwargs={}".format(str(kwargs)) + # import debug + # debug.info(0, "New module:" + type_str + name_str + kwargs_str) + obj = mod(name=module_name, **kwargs) + self.objects[module_type].append((kwargs, obj)) return obj def get_mods(self, module_type): @@ -90,11 +92,11 @@ class sram_factory: module_type = getattr(OPTS, module_type) try: mod_tuples = self.objects[module_type] - mods = [mod for kwargs,mod in mod_tuples] + mods = [mod for kwargs, mod in mod_tuples] except KeyError: mods = [] return mods - + + # Make a factory factory = sram_factory() - diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index e2222fcf..ebb424aa 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -61,27 +61,27 @@ class timing_sram_test(openram_test): data.update(port_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.2181231], - 'delay_lh': [0.2181231], - 'leakage_power': 0.0025453999999999997, - 'min_period': 0.781, - 'read0_power': [0.34664159999999994], - 'read1_power': [0.32656349999999995], - 'slew_hl': [0.21136519999999998], - 'slew_lh': [0.21136519999999998], - 'write0_power': [0.37980179999999997], - 'write1_power': [0.3532026]} + golden_data = {'delay_hl': [0.2383338], + 'delay_lh': [0.2383338], + 'leakage_power': 0.0014532999999999998, + 'min_period': 0.898, + 'read0_power': [0.30059800000000003], + 'read1_power': [0.30061810000000005], + 'slew_hl': [0.25358420000000004], + 'slew_lh': [0.25358420000000004], + 'write0_power': [0.34616749999999996], + 'write1_power': [0.2792924]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.4082], - 'delay_lh': [1.4082], - 'leakage_power': 0.0267388, - 'min_period': 4.688, - 'read0_power': [11.5255], - 'read1_power': [10.9406], - 'slew_hl': [1.2979], - 'slew_lh': [1.2979], - 'write0_power': [12.9458], - 'write1_power': [11.7444]} + golden_data = {'delay_hl': [1.7448], + 'delay_lh': [1.7448], + 'leakage_power': 0.0006356744000000001, + 'min_period': 6.25, + 'read0_power': [12.9846], + 'read1_power': [12.9722], + 'slew_hl': [1.7433], + 'slew_lh': [1.7433], + 'write0_power': [14.8772], + 'write1_power': [11.7217]} else: self.assertTrue(False) # other techs fail # Check if no too many or too few results diff --git a/compiler/tests/21_hspice_setuphold_test.py b/compiler/tests/21_hspice_setuphold_test.py index ef5cf1a8..83bd5509 100755 --- a/compiler/tests/21_hspice_setuphold_test.py +++ b/compiler/tests/21_hspice_setuphold_test.py @@ -42,10 +42,10 @@ class timing_setup_test(openram_test): 'setup_times_HL': [0.026855499999999997], 'setup_times_LH': [0.032959]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'hold_times_HL': [-0.0891113], - 'hold_times_LH': [-0.0769043], - 'setup_times_HL': [0.1184082], - 'setup_times_LH': [0.1733398]} + golden_data = {'hold_times_HL': [-0.0805664], + 'hold_times_LH': [-0.11718749999999999], + 'setup_times_HL': [0.16357419999999998], + 'setup_times_LH': [0.1757812]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index 0fe5dfdd..a5eb67fa 100755 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -65,16 +65,16 @@ class timing_sram_test(openram_test): 'write0_power': [0.36360849999999995], 'write1_power': [0.3486931]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'delay_hl': [1.7083549999999998], - 'delay_lh': [1.7083549999999998], - 'leakage_power': 0.001119657, - 'min_period': 7.812, - 'read0_power': [8.013845], - 'read1_power': [7.6889389999999995], - 'slew_hl': [1.31918], - 'slew_lh': [1.31918], - 'write0_power': [8.791557000000001], - 'write1_power': [8.70443]} + golden_data = {'delay_hl': [1.85985], + 'delay_lh': [1.85985], + 'leakage_power': 0.008613619, + 'min_period': 6.875, + 'read0_power': [12.656310000000001], + 'read1_power': [12.11682], + 'slew_hl': [1.868942], + 'slew_lh': [1.868942], + 'write0_power': [13.978110000000001], + 'write1_power': [11.437930000000001]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/21_ngspice_setuphold_test.py b/compiler/tests/21_ngspice_setuphold_test.py index 4a289812..0d160943 100755 --- a/compiler/tests/21_ngspice_setuphold_test.py +++ b/compiler/tests/21_ngspice_setuphold_test.py @@ -42,10 +42,10 @@ class timing_setup_test(openram_test): 'setup_times_HL': [0.02685547], 'setup_times_LH': [0.03295898]} elif OPTS.tech_name == "scn4m_subm": - golden_data = {'hold_times_HL': [-0.08911132999999999], - 'hold_times_LH': [-0.0769043], - 'setup_times_HL': [0.1184082], - 'setup_times_LH': [0.1672363]} + golden_data = {'hold_times_HL': [-0.08056640999999999], + 'hold_times_LH': [-0.1293945], + 'setup_times_HL': [0.1757812], + 'setup_times_LH': [0.1879883]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/view_profile.py b/compiler/view_profile.py index cad8e9ae..1d8e3f50 100755 --- a/compiler/view_profile.py +++ b/compiler/view_profile.py @@ -9,8 +9,8 @@ import pstats p = pstats.Stats("profile.dat") p.strip_dirs() -#p.sort_stats("cumulative") +# p.sort_stats("cumulative") p.sort_stats("tottime") -#p.print_stats(50) +# p.print_stats(50) p.print_stats() diff --git a/technology/scn4m_subm/models/ff/nmos.sp b/technology/scn4m_subm/models/ff/nmos.sp index 07ca8dba..02e8b528 100644 --- a/technology/scn4m_subm/models/ff/nmos.sp +++ b/technology/scn4m_subm/models/ff/nmos.sp @@ -5,6 +5,36 @@ * models from MOSIS or SCN4ME ********************************************* -.MODEL n NMOS (LEVEL=49 VTHO=0.669845 -+ NSUB=6E16 U0=461 K1=0.5705 TOX=13.9n VERSION=3.3.0) +.MODEL n NMOS ( LEVEL = 49 ++VERSION = 3.1 TNOM = 27 TOX = 7.6E-9 ++XJ = 1.5E-7 NCH = 1.7E17 VTH0 = 0.4964448 ++K1 = 0.5307769 K2 = 0.0199705 K3 = 0.2963637 ++K3B = 0.2012165 W0 = 2.836319E-6 NLX = 2.894802E-7 ++DVT0W = 0 DVT1W = 5.3E6 DVT2W = -0.032 ++DVT0 = 0.112017 DVT1 = 0.2453972 DVT2 = -0.171915 ++U0 = 444.9381976 UA = 2.921284E-10 UB = 1.773281E-18 ++UC = 7.067896E-11 VSAT = 1.130785E5 A0 = 1.1356246 ++AGS = 0.2810374 B0 = 2.844393E-7 B1 = 5E-6 ++KETA = -7.8181E-3 A1 = 0 A2 = 1 ++RDSW = 925.2701982 PRWG = -1E-3 PRWB = -1E-3 ++WR = 1 WINT = 7.186965E-8 LINT = 1.735515E-9 ++XL = -2E-8 XW = 0 DWG = -1.712973E-8 ++DWB = 5.851691E-9 VOFF = -0.132935 NFACTOR = 0.5710974 ++CIT = 0 CDSC = 8.607229E-4 CDSCD = 0 ++CDSCB = 0 ETA0 = 2.128321E-3 ETAB = 0 ++DSUB = 0.0257957 PCLM = 0.6766314 PDIBLC1 = 1 ++PDIBLC2 = 1.787424E-3 PDIBLCB = 0 DROUT = 0.7873539 ++PSCBE1 = 6.973485E9 PSCBE2 = 1.46235E-7 PVAG = 0.05 ++DELTA = 0.01 MOBMOD = 1 PRT = 0 ++UTE = -1.5 KT1 = -0.11 KT1L = 0 ++KT2 = 0.022 UA1 = 4.31E-9 UB1 = -7.61E-18 ++UC1 = -5.6E-11 AT = 3.3E4 WL = 0 ++WLN = 1 WW = 0 WWN = 1 ++WWL = 0 LL = 0 LLN = 1 ++LW = 0 LWN = 1 LWL = 0 ++CAPMOD = 2 CGDO = 1.96E-10 CGSO = 1.96E-10 ++CGBO = 0 CJ = 9.276962E-4 PB = 0.8157962 ++MJ = 0.3557696 CJSW = 3.181055E-10 PBSW = 0.6869149 ++MJSW = 0.1 PVTH0 = -0.0252481 PRDSW = -96.4502805 ++PK2 = -4.805372E-3 WKETA = -7.643187E-4 LKETA = -0.0129496 ) diff --git a/technology/scn4m_subm/models/ff/pmos.sp b/technology/scn4m_subm/models/ff/pmos.sp index b4dc9026..3a47539c 100644 --- a/technology/scn4m_subm/models/ff/pmos.sp +++ b/technology/scn4m_subm/models/ff/pmos.sp @@ -5,5 +5,35 @@ * models from MOSIS or SCN4ME ********************************************* -.MODEL p PMOS (LEVEL=49 VTHO=-0.322431 -+ NSUB=6E16 U0=212 K1=0.0821 TOX=13.9n VERSION=3.3.0) +.MODEL p PMOS ( LEVEL = 49 ++VERSION = 3.1 TNOM = 27 TOX = 7.6E-9 ++XJ = 1.5E-7 NCH = 1.7E17 VTH0 = -0.6636594 ++K1 = 0.4564781 K2 = -0.019447 K3 = 39.382919 ++K3B = -2.8930965 W0 = 2.655585E-6 NLX = 1.51028E-7 ++DVT0W = 0 DVT1W = 5.3E6 DVT2W = -0.032 ++DVT0 = 1.1744581 DVT1 = 0.7631128 DVT2 = -0.1035171 ++U0 = 151.3305606 UA = 2.061211E-10 UB = 1.823477E-18 ++UC = -8.97321E-12 VSAT = 9.915604E4 A0 = 1.1210053 ++AGS = 0.3961954 B0 = 6.493139E-7 B1 = 4.273215E-6 ++KETA = -9.27E-3 A1 = 0 A2 = 1 ++RDSW = 2.30725E3 PRWG = -1E-3 PRWB = 0 ++WR = 1 WINT = 5.962233E-8 LINT = 4.30928E-9 ++XL = -2E-8 XW = 0 DWG = -1.596201E-8 ++DWB = 1.378919E-8 VOFF = -0.15 NFACTOR = 2 ++CIT = 0 CDSC = 6.593084E-4 CDSCD = 0 ++CDSCB = 0 ETA0 = 0.0286461 ETAB = 0 ++DSUB = 0.2436027 PCLM = 4.3597508 PDIBLC1 = 7.447024E-4 ++PDIBLC2 = 4.256073E-3 PDIBLCB = 0 DROUT = 0.0120292 ++PSCBE1 = 1.347622E10 PSCBE2 = 5E-9 PVAG = 3.669793 ++DELTA = 0.01 MOBMOD = 1 PRT = 0 ++UTE = -1.5 KT1 = -0.11 KT1L = 0 ++KT2 = 0.022 UA1 = 4.31E-9 UB1 = -7.61E-18 ++UC1 = -5.6E-11 AT = 3.3E4 WL = 0 ++WLN = 1 WW = 0 WWN = 1 ++WWL = 0 LL = 0 LLN = 1 ++LW = 0 LWN = 1 LWL = 0 ++CAPMOD = 2 CGDO = 2.307E-10 CGSO = 2.307E-10 ++CGBO = 0 CJ = 1.420282E-3 PB = 0.99 ++MJ = 0.5490877 CJSW = 4.773605E-10 PBSW = 0.99 ++MJSW = 0.1997417 PVTH0 = 6.58707E-3 PRDSW = -93.5582228 ++PK2 = 1.011593E-3 WKETA = -0.0101398 LKETA = 6.027967E-3 ) diff --git a/technology/scn4m_subm/models/nom/nmos.sp b/technology/scn4m_subm/models/nom/nmos.sp index ad4db2b8..02e8b528 100644 --- a/technology/scn4m_subm/models/nom/nmos.sp +++ b/technology/scn4m_subm/models/nom/nmos.sp @@ -5,5 +5,36 @@ * models from MOSIS or SCN4ME ********************************************* -.MODEL n NMOS (LEVEL=49 VTHO=0.669845 -+ NSUB=6E16 U0=458 K1=0.5705 TOX=13.9n VERSION=3.3.0) +.MODEL n NMOS ( LEVEL = 49 ++VERSION = 3.1 TNOM = 27 TOX = 7.6E-9 ++XJ = 1.5E-7 NCH = 1.7E17 VTH0 = 0.4964448 ++K1 = 0.5307769 K2 = 0.0199705 K3 = 0.2963637 ++K3B = 0.2012165 W0 = 2.836319E-6 NLX = 2.894802E-7 ++DVT0W = 0 DVT1W = 5.3E6 DVT2W = -0.032 ++DVT0 = 0.112017 DVT1 = 0.2453972 DVT2 = -0.171915 ++U0 = 444.9381976 UA = 2.921284E-10 UB = 1.773281E-18 ++UC = 7.067896E-11 VSAT = 1.130785E5 A0 = 1.1356246 ++AGS = 0.2810374 B0 = 2.844393E-7 B1 = 5E-6 ++KETA = -7.8181E-3 A1 = 0 A2 = 1 ++RDSW = 925.2701982 PRWG = -1E-3 PRWB = -1E-3 ++WR = 1 WINT = 7.186965E-8 LINT = 1.735515E-9 ++XL = -2E-8 XW = 0 DWG = -1.712973E-8 ++DWB = 5.851691E-9 VOFF = -0.132935 NFACTOR = 0.5710974 ++CIT = 0 CDSC = 8.607229E-4 CDSCD = 0 ++CDSCB = 0 ETA0 = 2.128321E-3 ETAB = 0 ++DSUB = 0.0257957 PCLM = 0.6766314 PDIBLC1 = 1 ++PDIBLC2 = 1.787424E-3 PDIBLCB = 0 DROUT = 0.7873539 ++PSCBE1 = 6.973485E9 PSCBE2 = 1.46235E-7 PVAG = 0.05 ++DELTA = 0.01 MOBMOD = 1 PRT = 0 ++UTE = -1.5 KT1 = -0.11 KT1L = 0 ++KT2 = 0.022 UA1 = 4.31E-9 UB1 = -7.61E-18 ++UC1 = -5.6E-11 AT = 3.3E4 WL = 0 ++WLN = 1 WW = 0 WWN = 1 ++WWL = 0 LL = 0 LLN = 1 ++LW = 0 LWN = 1 LWL = 0 ++CAPMOD = 2 CGDO = 1.96E-10 CGSO = 1.96E-10 ++CGBO = 0 CJ = 9.276962E-4 PB = 0.8157962 ++MJ = 0.3557696 CJSW = 3.181055E-10 PBSW = 0.6869149 ++MJSW = 0.1 PVTH0 = -0.0252481 PRDSW = -96.4502805 ++PK2 = -4.805372E-3 WKETA = -7.643187E-4 LKETA = -0.0129496 ) + diff --git a/technology/scn4m_subm/models/nom/pmos.sp b/technology/scn4m_subm/models/nom/pmos.sp index 9ecb13e1..3a47539c 100644 --- a/technology/scn4m_subm/models/nom/pmos.sp +++ b/technology/scn4m_subm/models/nom/pmos.sp @@ -5,5 +5,35 @@ * models from MOSIS or SCN4ME ********************************************* -.MODEL p PMOS (LEVEL=49 VTHO=-0.322431 -+ NSUB=6E16 U0=212 K1=0.0821 TOX=13.9n VERSION=3.3.0) +.MODEL p PMOS ( LEVEL = 49 ++VERSION = 3.1 TNOM = 27 TOX = 7.6E-9 ++XJ = 1.5E-7 NCH = 1.7E17 VTH0 = -0.6636594 ++K1 = 0.4564781 K2 = -0.019447 K3 = 39.382919 ++K3B = -2.8930965 W0 = 2.655585E-6 NLX = 1.51028E-7 ++DVT0W = 0 DVT1W = 5.3E6 DVT2W = -0.032 ++DVT0 = 1.1744581 DVT1 = 0.7631128 DVT2 = -0.1035171 ++U0 = 151.3305606 UA = 2.061211E-10 UB = 1.823477E-18 ++UC = -8.97321E-12 VSAT = 9.915604E4 A0 = 1.1210053 ++AGS = 0.3961954 B0 = 6.493139E-7 B1 = 4.273215E-6 ++KETA = -9.27E-3 A1 = 0 A2 = 1 ++RDSW = 2.30725E3 PRWG = -1E-3 PRWB = 0 ++WR = 1 WINT = 5.962233E-8 LINT = 4.30928E-9 ++XL = -2E-8 XW = 0 DWG = -1.596201E-8 ++DWB = 1.378919E-8 VOFF = -0.15 NFACTOR = 2 ++CIT = 0 CDSC = 6.593084E-4 CDSCD = 0 ++CDSCB = 0 ETA0 = 0.0286461 ETAB = 0 ++DSUB = 0.2436027 PCLM = 4.3597508 PDIBLC1 = 7.447024E-4 ++PDIBLC2 = 4.256073E-3 PDIBLCB = 0 DROUT = 0.0120292 ++PSCBE1 = 1.347622E10 PSCBE2 = 5E-9 PVAG = 3.669793 ++DELTA = 0.01 MOBMOD = 1 PRT = 0 ++UTE = -1.5 KT1 = -0.11 KT1L = 0 ++KT2 = 0.022 UA1 = 4.31E-9 UB1 = -7.61E-18 ++UC1 = -5.6E-11 AT = 3.3E4 WL = 0 ++WLN = 1 WW = 0 WWN = 1 ++WWL = 0 LL = 0 LLN = 1 ++LW = 0 LWN = 1 LWL = 0 ++CAPMOD = 2 CGDO = 2.307E-10 CGSO = 2.307E-10 ++CGBO = 0 CJ = 1.420282E-3 PB = 0.99 ++MJ = 0.5490877 CJSW = 4.773605E-10 PBSW = 0.99 ++MJSW = 0.1997417 PVTH0 = 6.58707E-3 PRDSW = -93.5582228 ++PK2 = 1.011593E-3 WKETA = -0.0101398 LKETA = 6.027967E-3 ) diff --git a/technology/scn4m_subm/models/ss/nmos.sp b/technology/scn4m_subm/models/ss/nmos.sp index 3d9bda57..02e8b528 100644 --- a/technology/scn4m_subm/models/ss/nmos.sp +++ b/technology/scn4m_subm/models/ss/nmos.sp @@ -5,6 +5,36 @@ * models from MOSIS or SCN4ME ********************************************* -.MODEL n NMOS (LEVEL=49 VTHO=0.669845 -+ NSUB=6E16 U0=460 K1=0.5705 TOX=13.9n VERSION=3.3.0) +.MODEL n NMOS ( LEVEL = 49 ++VERSION = 3.1 TNOM = 27 TOX = 7.6E-9 ++XJ = 1.5E-7 NCH = 1.7E17 VTH0 = 0.4964448 ++K1 = 0.5307769 K2 = 0.0199705 K3 = 0.2963637 ++K3B = 0.2012165 W0 = 2.836319E-6 NLX = 2.894802E-7 ++DVT0W = 0 DVT1W = 5.3E6 DVT2W = -0.032 ++DVT0 = 0.112017 DVT1 = 0.2453972 DVT2 = -0.171915 ++U0 = 444.9381976 UA = 2.921284E-10 UB = 1.773281E-18 ++UC = 7.067896E-11 VSAT = 1.130785E5 A0 = 1.1356246 ++AGS = 0.2810374 B0 = 2.844393E-7 B1 = 5E-6 ++KETA = -7.8181E-3 A1 = 0 A2 = 1 ++RDSW = 925.2701982 PRWG = -1E-3 PRWB = -1E-3 ++WR = 1 WINT = 7.186965E-8 LINT = 1.735515E-9 ++XL = -2E-8 XW = 0 DWG = -1.712973E-8 ++DWB = 5.851691E-9 VOFF = -0.132935 NFACTOR = 0.5710974 ++CIT = 0 CDSC = 8.607229E-4 CDSCD = 0 ++CDSCB = 0 ETA0 = 2.128321E-3 ETAB = 0 ++DSUB = 0.0257957 PCLM = 0.6766314 PDIBLC1 = 1 ++PDIBLC2 = 1.787424E-3 PDIBLCB = 0 DROUT = 0.7873539 ++PSCBE1 = 6.973485E9 PSCBE2 = 1.46235E-7 PVAG = 0.05 ++DELTA = 0.01 MOBMOD = 1 PRT = 0 ++UTE = -1.5 KT1 = -0.11 KT1L = 0 ++KT2 = 0.022 UA1 = 4.31E-9 UB1 = -7.61E-18 ++UC1 = -5.6E-11 AT = 3.3E4 WL = 0 ++WLN = 1 WW = 0 WWN = 1 ++WWL = 0 LL = 0 LLN = 1 ++LW = 0 LWN = 1 LWL = 0 ++CAPMOD = 2 CGDO = 1.96E-10 CGSO = 1.96E-10 ++CGBO = 0 CJ = 9.276962E-4 PB = 0.8157962 ++MJ = 0.3557696 CJSW = 3.181055E-10 PBSW = 0.6869149 ++MJSW = 0.1 PVTH0 = -0.0252481 PRDSW = -96.4502805 ++PK2 = -4.805372E-3 WKETA = -7.643187E-4 LKETA = -0.0129496 ) diff --git a/technology/scn4m_subm/models/ss/pmos.sp b/technology/scn4m_subm/models/ss/pmos.sp index b4dc9026..3a47539c 100644 --- a/technology/scn4m_subm/models/ss/pmos.sp +++ b/technology/scn4m_subm/models/ss/pmos.sp @@ -5,5 +5,35 @@ * models from MOSIS or SCN4ME ********************************************* -.MODEL p PMOS (LEVEL=49 VTHO=-0.322431 -+ NSUB=6E16 U0=212 K1=0.0821 TOX=13.9n VERSION=3.3.0) +.MODEL p PMOS ( LEVEL = 49 ++VERSION = 3.1 TNOM = 27 TOX = 7.6E-9 ++XJ = 1.5E-7 NCH = 1.7E17 VTH0 = -0.6636594 ++K1 = 0.4564781 K2 = -0.019447 K3 = 39.382919 ++K3B = -2.8930965 W0 = 2.655585E-6 NLX = 1.51028E-7 ++DVT0W = 0 DVT1W = 5.3E6 DVT2W = -0.032 ++DVT0 = 1.1744581 DVT1 = 0.7631128 DVT2 = -0.1035171 ++U0 = 151.3305606 UA = 2.061211E-10 UB = 1.823477E-18 ++UC = -8.97321E-12 VSAT = 9.915604E4 A0 = 1.1210053 ++AGS = 0.3961954 B0 = 6.493139E-7 B1 = 4.273215E-6 ++KETA = -9.27E-3 A1 = 0 A2 = 1 ++RDSW = 2.30725E3 PRWG = -1E-3 PRWB = 0 ++WR = 1 WINT = 5.962233E-8 LINT = 4.30928E-9 ++XL = -2E-8 XW = 0 DWG = -1.596201E-8 ++DWB = 1.378919E-8 VOFF = -0.15 NFACTOR = 2 ++CIT = 0 CDSC = 6.593084E-4 CDSCD = 0 ++CDSCB = 0 ETA0 = 0.0286461 ETAB = 0 ++DSUB = 0.2436027 PCLM = 4.3597508 PDIBLC1 = 7.447024E-4 ++PDIBLC2 = 4.256073E-3 PDIBLCB = 0 DROUT = 0.0120292 ++PSCBE1 = 1.347622E10 PSCBE2 = 5E-9 PVAG = 3.669793 ++DELTA = 0.01 MOBMOD = 1 PRT = 0 ++UTE = -1.5 KT1 = -0.11 KT1L = 0 ++KT2 = 0.022 UA1 = 4.31E-9 UB1 = -7.61E-18 ++UC1 = -5.6E-11 AT = 3.3E4 WL = 0 ++WLN = 1 WW = 0 WWN = 1 ++WWL = 0 LL = 0 LLN = 1 ++LW = 0 LWN = 1 LWL = 0 ++CAPMOD = 2 CGDO = 2.307E-10 CGSO = 2.307E-10 ++CGBO = 0 CJ = 1.420282E-3 PB = 0.99 ++MJ = 0.5490877 CJSW = 4.773605E-10 PBSW = 0.99 ++MJSW = 0.1997417 PVTH0 = 6.58707E-3 PRDSW = -93.5582228 ++PK2 = 1.011593E-3 WKETA = -0.0101398 LKETA = 6.027967E-3 ) diff --git a/technology/scn4m_subm/tech/tech.py b/technology/scn4m_subm/tech/tech.py index 68066c09..37e55e21 100644 --- a/technology/scn4m_subm/tech/tech.py +++ b/technology/scn4m_subm/tech/tech.py @@ -291,8 +291,8 @@ spice["dff_leakage"] = 1 # Leakage power of flop in nW spice["default_event_frequency"] = 100 # Default event activity of every gate. MHz #Logical Effort relative values for the Handmade cells -parameter["le_tau"] = 23 #In pico-seconds. -parameter["min_inv_para_delay"] = .73 #In relative delay units +parameter["le_tau"] = 18.17 #In pico-seconds. +parameter["min_inv_para_delay"] = 2.07 #In relative delay units parameter["cap_relative_per_ff"] = .91 #Units of Relative Capacitance/ Femto-Farad parameter["dff_clk_cin"] = 27.5 #In relative capacitance units parameter["6tcell_wl_cin"] = 2 #In relative capacitance units