diff --git a/compiler/bank.py b/compiler/bank.py index dc57bd4f..19c1f77b 100644 --- a/compiler/bank.py +++ b/compiler/bank.py @@ -470,7 +470,7 @@ class bank(design.design): """ # 4x Inverter - self.inv4x = pinv(nmos_width=4*drc["minwidth_tx"]) + self.inv4x = pinv(4) self.add_mod(self.inv4x) self.nor2 = nor_2() diff --git a/compiler/control_logic.py b/compiler/control_logic.py index 099ecd06..eb5adb8f 100644 --- a/compiler/control_logic.py +++ b/compiler/control_logic.py @@ -50,13 +50,13 @@ class control_logic(design.design): # Special gates: inverters for buffering self.inv = self.inv1 = pinv() self.add_mod(self.inv1) - self.inv2 = pinv(nmos_width=2*drc["minwidth_tx"]) + self.inv2 = pinv(2) self.add_mod(self.inv2) - self.inv4 = pinv(nmos_width=4*drc["minwidth_tx"]) + self.inv4 = pinv(4) self.add_mod(self.inv4) - self.inv8 = pinv(nmos_width=8*drc["minwidth_tx"]) + self.inv8 = pinv(8) self.add_mod(self.inv8) - self.inv16 = pinv(nmos_width=16*drc["minwidth_tx"]) + self.inv16 = pinv(16) self.add_mod(self.inv16) c = reload(__import__(OPTS.config.ms_flop_array)) diff --git a/compiler/nand_2.py b/compiler/nand_2.py index a2590907..23ec3596 100644 --- a/compiler/nand_2.py +++ b/compiler/nand_2.py @@ -21,16 +21,16 @@ class nand_2(design.design): unique_id = 1 - def __init__(self, nmos_width=2*drc["minwidth_tx"], height=bitcell.height): + def __init__(self, size=2, height=bitcell.height): """Constructor : Creates a cell for a simple 2 input nand""" name = "nand2_{0}".format(nand_2.unique_id) nand_2.unique_id += 1 design.design.__init__(self, name) debug.info(2, "create nand_2 structure {0} with size of {1}".format(name, nmos_width)) - self.nmos_size = nmos_width + self.nmos_size = 2*size # FIXME why is this? - self.pmos_size = nmos_width + self.pmos_size = size self.tx_mults = 1 self.height = height diff --git a/compiler/nand_3.py b/compiler/nand_3.py index 25b35ffd..448f2a2a 100644 --- a/compiler/nand_3.py +++ b/compiler/nand_3.py @@ -21,16 +21,16 @@ class nand_3(design.design): unique_id = 1 - def __init__(self, nmos_width=3*drc["minwidth_tx"], height=bitcell.height): + def __init__(self, size=1, height=bitcell.height): """Constructor : Creates a cell for a simple 3 input nand""" name = "nand3_{0}".format(nand_3.unique_id) nand_3.unique_id += 1 design.design.__init__(self, name) debug.info(2, "create nand_3 structure {0} with size of {1}".format(name, nmos_width)) - self.nmos_size = nmos_width + self.nmos_size = 3*size # FIXME: Why is this?? - self.pmos_size = 2 * nmos_width / 3 + self.pmos_size = 2*size self.tx_mults = 1 self.height = height diff --git a/compiler/nor_2.py b/compiler/nor_2.py index a1c84637..e3f77daf 100644 --- a/compiler/nor_2.py +++ b/compiler/nor_2.py @@ -20,7 +20,7 @@ class nor_2(design.design): unique_id = 1 - def __init__(self, nmos_width=drc["minwidth_tx"], height=bitcell.height): + def __init__(self, size=1, height=bitcell.height): """Constructor : Creates a cell for a simple 2 input nor""" name = "nor2_{0}".format(nor_2.unique_id) nor_2.unique_id += 1 @@ -29,7 +29,7 @@ class nor_2(design.design): debug.check(nmos_width==drc["minwidth_tx"], "Need to rewrite nor2 for sizing.") - self.nmos_width = nmos_width + self.nmos_size = self.height = height self.add_pins() diff --git a/compiler/pinv.py b/compiler/pinv.py index 674961ea..2d16d08e 100644 --- a/compiler/pinv.py +++ b/compiler/pinv.py @@ -9,37 +9,47 @@ from globals import OPTS class pinv(design.design): """ - This module generates gds of a parametrically sized inverter. - This model use ptx to generate a inverter within a cetrain height. - The inverter's cell_height should be the same as the 6t library cell. + This module generates gds of a parametrically sized inverter. The + size is specified as the nmos size (relative to minimum) and a + beta value for choosing the pmos size. The inverter's cell_height + is usually the same as the 6t library cell. The route_output will + route the output to the right side of the cell for easier access. + """ c = reload(__import__(OPTS.config.bitcell)) bitcell = getattr(c, OPTS.config.bitcell) unique_id = 1 - def __init__(self, nmos_width=drc["minwidth_tx"], beta=parameter["pinv_beta"], height=bitcell.height, route_output=True): - """Constructor : Creates a cell for a simple inverter""" + def __init__(self, size=1, beta=parameter["pinv_beta"], height=bitcell.height, route_output=True): + # 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 + # have poly connected, for example. name = "pinv{0}".format(pinv.unique_id) pinv.unique_id += 1 design.design.__init__(self, name) - debug.info(2, "create pinv structure {0} with size of {1}".format(name, nmos_width)) + debug.info(2, "create pinv structure {0} with size of {1}".format(name, size)) - self.nmos_width = nmos_width + self.nmos_size = size + self.pmos_size = beta*size self.beta = beta self.height = height self.route_output = route_output self.add_pins() self.create_layout() + + # for run-time, we won't check every transitor DRC/LVS independently + # but this may be uncommented for debug purposes #self.DRC_LVS() def add_pins(self): - """Adds pins for spice netlist processing""" + """Adds pins for spice netlist""" self.add_pin_list(["A", "Z", "vdd", "gnd"]) def create_layout(self): - """Calls all functions related to the generation of the layout(gds)""" + """Calls all functions related to the generation of the layout""" # These aren't for instantiating, but we use them to get the dimensions self.poly_contact = contact.contact(("poly", "contact", "metal1")) @@ -66,45 +76,54 @@ class pinv(design.design): self.route_pins() def determine_tx_mults(self): - """Determines the number of fingers needed to achieve same size with a height constraint""" - # check minimum distance between well - minwidth_poly_contact = drc["minwidth_contact"] \ - + 2 * drc["poly_enclosure_contact"] + """ + Determines the number of fingers needed to achieve the size within + the height constraint. This may fail if the user has a tight height. + """ + # 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) + # plus the tx height + nmos = ptx(tx_type="nmos") + pmos = ptx(width=self.beta*drc["minwidth_tx"], tx_type="pmos") + tx_height = nmos.height + pmos.height + # rotated m1 pitch + m1_pitch = self.poly_contact.width + drc["metal1_to_metal1"] + metal_height = 4 * m1_pitch # This could be computed more accurately + debug.check(self.height>tx_height + metal_height,"Cell height too small for our simple design rules.") - # this should be 2*poly extension beyond active? - minwidth_box_poly = 2 * drc["minwidth_poly"] \ - + drc["poly_to_poly"] - well_to_well = max(drc["pwell_to_nwell"], - minwidth_poly_contact, - minwidth_box_poly) + # Determine the height left to the transistors to determine the number of fingers + tx_height_available = self.height - metal_height + # Divide the height according to beta + nmos_height_available = 1.0/(self.beta+1) * tx_height_available + pmos_height_available = tx_height_available - nmos_height_available + - # determine both mos enclosure sizes - bot_mos_enclosure = 2 * (drc["well_enclosure_active"]) - top_mos_enclosure = 2 * max(drc["well_enclosure_active"], - drc["metal1_to_metal1"] + 0.5 * drc["minwidth_metal1"]) - - self.nmos_size = parameter["min_tx_size"] - self.pmos_size = parameter["min_tx_size"] * self.beta - - # use multi finger if the cell is not big enough - if self.nmos_size <= self.nmos_width: - self.tx_mults = int(ceil(self.nmos_width / self.nmos_size)) - else: - self.tx_mults = 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... + self.nmos_width = self.nmos_width / self.tx_mults + debug.check(self.nmos_width>=drc["minwidth_tx"],"Cannot finger NMOS transistors to fit cell height.") + self.pmos_width = self.pmos_width / self.tx_mults + debug.check(self.pmos_width>=drc["minwidth_tx"],"Cannot finger PMOS transistors to fit cell height.") + def create_ptx(self): """Intiializes a ptx object""" - self.nmos = ptx(width=self.nmos_size, + self.nmos = ptx(width=self.nmos_width, mults=self.tx_mults, - tx_type="nmos", - connect_active=True, - connect_poly=True) + tx_type="nmos") self.add_mod(self.nmos) - self.pmos = ptx(width=self.pmos_size, + self.pmos = ptx(width=self.pmos_width, mults=self.tx_mults, - tx_type="pmos", - connect_active=True, - connect_poly=True) + tx_type="pmos") self.add_mod(self.pmos) def setup_layout_constants(self): diff --git a/compiler/ptx.py b/compiler/ptx.py index ce6febde..a870c8ff 100644 --- a/compiler/ptx.py +++ b/compiler/ptx.py @@ -40,17 +40,19 @@ class ptx(design.design): self.connect_poly = connect_poly self.num_contacts = num_contacts - self.add_pins() self.create_spice() self.create_layout() + # for run-time, we won't check every transitor DRC independently # but this may be uncommented for debug purposes #self.DRC() def add_pins(self): + """Adds pins for spice netlist""" self.add_pin_list(["D", "G", "S", "B"]) 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() @@ -58,6 +60,8 @@ class ptx(design.design): self.add_active_contacts() def create_spice(self): + self.add_pins() + self.spice.append("\n.SUBCKT {0} {1}".format(self.name, " ".join(self.pins))) self.spice.append("M{0} {1} {2} m={3} w={4}u l={5}u".format(self.tx_type, @@ -75,7 +79,18 @@ class ptx(design.design): if self.num_contacts==None: self.num_contacts=self.calculate_num_contacts() - + + # Determine layer types needed + if self.tx_type == "nmos": + self.implant_type = "n" + self.well_type = "p" + elif self.tx_type == "pmos": + self.implant_type = "p" + self.well_type = "n" + else: + self.error("Invalid transitor type.",-1) + + # This is not actually instantiated but used for calculations self.active_contact = contact(layer_stack=("active", "contact", "metal1"), dimensions=(1, self.num_contacts)) @@ -112,11 +127,19 @@ class ptx(design.design): self.poly_height = self.tx_width + 2*self.poly_extend_active # Well enclosure of active, ensure minwidth as well - self.well_width = max(self.active_width + 2*drc["well_enclosure_active"], - drc["minwidth_well"]) - self.well_height = max(self.tx_width + 2*drc["well_enclosure_active"], - drc["minwidth_well"]) + if info["has_{}well".format(self.well_type)]: + self.well_width = max(self.active_width + 2*drc["well_enclosure_active"], + drc["minwidth_well"]) + self.well_height = max(self.tx_width + 2*drc["well_enclosure_active"], + drc["minwidth_well"]) + self.height = self.well_height + self.width = self.well_width + else: + # If no well, use the boundary of the active and poly + self.height = self.poly_height + self.width = self.active_width + # The active offset is due to the well extension self.active_offset = vector([drc["well_enclosure_active"]]*2) @@ -231,17 +254,8 @@ class ptx(design.design): """ Add an (optional) well and implant for the type of transistor. """ - if self.tx_type == "nmos": - implant_type = "n" - well_type = "p" - elif self.tx_type == "pmos": - implant_type = "p" - well_type = "n" - else: - self.error("Invalid transitor type.",-1) - - if info["has_{}well".format(well_type)]: - self.add_rect(layer="{}well".format(well_type), + if info["has_{}well".format(self.well_type)]: + self.add_rect(layer="{}well".format(self.well_type), offset=(0,0), width=self.well_width, height=self.well_height) @@ -249,7 +263,7 @@ class ptx(design.design): offset=(0,0), width=self.well_width, height=self.well_height) - self.add_rect(layer="{}implant".format(implant_type), + self.add_rect(layer="{}implant".format(self.implant_type), offset=self.active_offset, width=self.active_width, height=self.active_height) diff --git a/compiler/tests/04_pinv_test.py b/compiler/tests/04_pinv_test.py index 256a6551..c8ad172a 100644 --- a/compiler/tests/04_pinv_test.py +++ b/compiler/tests/04_pinv_test.py @@ -25,18 +25,30 @@ class pinv_test(unittest.TestCase): import pinv import tech - # debug.info(2, "Checking min size inverter") - # tx = pinv.pinv(nmos_width=tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) - # self.local_check(tx) + debug.info(2, "Checking min size inverter") + tx = pinv.pinv(size=1, beta=tech.parameter["pinv_beta"]) + self.local_check(tx) debug.info(2, "Checking 2x min size inverter") - tx = pinv.pinv(nmos_width=2 * tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) + tx = pinv.pinv(size=2, beta=tech.parameter["pinv_beta"]) self.local_check(tx) debug.info(2, "Checking 5x min size inverter") - tx = pinv.pinv(nmos_width=5 * tech.drc["minwidth_tx"], beta=tech.parameter["pinv_beta"]) + tx = pinv.pinv(size=5, beta=tech.parameter["pinv_beta"]) self.local_check(tx) + debug.info(2, "Checking min size inverter with new beta") + tx = pinv.pinv(size=1, beta=3) + self.local_check(tx) + + debug.info(2, "Checking 2x size inverter with new beta") + tx = pinv.pinv(size=2, beta=3) + self.local_check(tx) + + debug.info(2, "Checking 5x size inverter with new beta") + tx = pinv.pinv(size=4, beta=3) + self.local_check(tx) + OPTS.check_lvsdrc = True globals.end_openram()