From d4f8d63442b61a0d6c1b28173597f8714ddf0aea Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 29 Nov 2017 09:44:40 -0800 Subject: [PATCH] Fix bug for even number of fingers. Add even finger tests. --- compiler/ptx.py | 137 +----------------- compiler/tests/03_ptx_4finger_nmos_test.py | 158 +++++++++++++++++++++ compiler/tests/03_ptx_4finger_pmos_test.py | 158 +++++++++++++++++++++ 3 files changed, 317 insertions(+), 136 deletions(-) create mode 100644 compiler/tests/03_ptx_4finger_nmos_test.py create mode 100644 compiler/tests/03_ptx_4finger_pmos_test.py diff --git a/compiler/ptx.py b/compiler/ptx.py index c91c2602..ce6febde 100644 --- a/compiler/ptx.py +++ b/compiler/ptx.py @@ -57,52 +57,6 @@ class ptx(design.design): self.add_poly() self.add_active_contacts() - # offset this BEFORE we add the active/poly connections - #self.offset_all_coordinates() - - def offset_attributes(self, coordinate): - """Translates all stored 2d cartesian coordinates within the - attr dictionary""" - # FIXME: This is dangerous. I think we should not do this, but explicitly - # offset the necessary coordinates. It is only used in ptx for now! - - for attr_key in dir(self): - attr_val = getattr(self,attr_key) - - # skip the list of things as these will be offset separately - if (attr_key in ['objs','insts','mods','pins','conns','name_map']): continue - - # if is a list - if isinstance(attr_val, list): - - for i in range(len(attr_val)): - # each unit in the list is a list coordinates - if isinstance(attr_val[i], (list,vector)): - attr_val[i] = vector(attr_val[i] - coordinate) - # the list itself is a coordinate - else: - if len(attr_val)!=2: continue - for val in attr_val: - if not isinstance(val, (int, long, float)): continue - setattr(self,attr_key, vector(attr_val - coordinate)) - break - - # if is a vector coordinate - if isinstance(attr_val, vector): - setattr(self, attr_key, vector(attr_val - coordinate)) - - # def offset_all_coordinates(self): - # offset = self.find_lowest_coords() - # self.offset_attributes(offset) - # self.translate_all(offset) - - # # We can do this in ptx because we have offset all modules it uses. - # # Is this really true considering the paths that connect the src/drain? - # self.height = max(max(obj.offset.y + obj.height for obj in self.objs), - # max(inst.offset.y + inst.mod.height for inst in self.insts)) - # self.width = max(max(obj.offset.x + obj.width for obj in self.objs), - # max(inst.offset.x + inst.mod.width for inst in self.insts)) - def create_spice(self): self.spice.append("\n.SUBCKT {0} {1}".format(self.name, " ".join(self.pins))) @@ -201,55 +155,6 @@ class ptx(design.design): height=drc["minwidth_poly"]) - - - def pairwise(self, iterable): - """ - Create an iterable set of pairs from a set: - "s -> (s0,s1), (s1,s2), (s2, s3), ..." - """ - from itertools import tee, izip - a, b = tee(iterable) - next(b, None) - return izip(a, b) - - - def determine_source_wire(self): - self.source_positions = [] - source_contact_pos = self.active_contact_positions[0:][::2] # even indexes - for a, b in self.pairwise(source_contact_pos): - correct=vector(0.5 * (self.active_contact.width - - drc["minwidth_metal1"] + drc["minwidth_contact"]), - 0.5 * (self.active_contact.height - drc["minwidth_contact"]) - - drc["metal1_extend_contact"]) - connected=vector(b.x + drc["minwidth_metal1"], - a.y + self.active_contact.height + drc["metal1_to_metal1"]) - self.source_positions.append(a + correct) - self.source_positions.append(vector(a.x + correct.x, connected.y)) - self.source_positions.append(vector(b.x + correct.x, - connected.y + 0.5 * drc["minwidth_metal2"])) - self.source_positions.append(b + correct) - - return source_positions - - def determine_drain_wire(self, contact_positions): - drain_positions = [] - drain_contact_pos = contact_positions[1:][::2] # odd indexes - for c, d in self.pairwise(drain_contact_pos): - correct = vector(0.5*(self.active_contact.width - - drc["minwidth_metal1"] + drc["minwidth_contact"]), - 0.5*(self.active_contact.height - drc["minwidth_contact"]) - - drc["metal1_extend_contact"]) - connected = vector(d.x + drc["minwidth_metal1"], c.y - drc["metal1_to_metal1"]) - self.drain_positions.append(vector(c + correct)) - self.drain_positions.append(vector(c.x + correct.x, connected.y)) - self.drain_positions.append(vector(d.x + correct.x, - connected.y - 0.5 * drc["minwidth_metal1"])) - self.drain_positions.append(vector(d + correct)) - - return drain_positions - - def connect_fingered_active(self, drain_positions, source_positions): """ Connect each contact up/down to a source or drain pin @@ -366,8 +271,7 @@ class ptx(design.design): source_positions = [self.contact_offset] drain_positions = [] - # For all but the last finger... - for i in range(self.mults-1): + 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)) @@ -375,9 +279,6 @@ class ptx(design.design): # It's a drain... so offset from previous source. drain_positions.append(source_positions[-1] + vector(self.contact_pitch,0)) - # The last one will always be a drain - drain_positions.append(source_positions[-1] + vector(self.contact_pitch,0)) - return [source_positions,drain_positions] def add_active_contacts(self): @@ -426,39 +327,3 @@ class ptx(design.design): self.connect_fingered_active(drain_positions, source_positions) - - # def remove_drain_connect(self): - # debug.info(3,"Removing drain on {}".format(self.name)) - # # FIXME: This is horrible exception handling! - # try: - # del self.insts[self.drain_connect_index] - # del self.drain_connect_index - # self.offset_all_coordinates() - # # change the name so it is unique - # self.name = self.name + "_rd" - # except: - # pass - - # def remove_source_connect(self): - # debug.info(3,"Removing source on {}".format(self.name)) - # # FIXME: This is horrible exception handling! - # try: - # del self.insts[self.source_connect_index] - # del self.source_connect_index - # if isinstance(self.drain_connect_index, int): - # self.drain_connect_index -= 1 - # self.offset_all_coordinates() - # # change the name so it is unique - # self.name = self.name + "_rs" - # except: - # pass - - # def remove_poly_connect(self): - # # FIXME: This is horrible exception handling! - # try: - # del self.objs[self.poly_connect_index] - # self.offset_all_coordinates() - # # change the name so it is unique - # self.name = self.name + "_rp" - # except: - # pass diff --git a/compiler/tests/03_ptx_4finger_nmos_test.py b/compiler/tests/03_ptx_4finger_nmos_test.py new file mode 100644 index 00000000..aeffd9a9 --- /dev/null +++ b/compiler/tests/03_ptx_4finger_nmos_test.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python2.7 +"Run a regresion test on a basic parameterized transistors" + +import unittest +from testutils import header +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug +import verify + +OPTS = globals.OPTS + +#@unittest.skip("SKIPPING 03_ptx_test") + + +class ptx_test(unittest.TestCase): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + # we will manually run lvs/drc + OPTS.check_lvsdrc = False + + import ptx + import tech + + debug.info(2, "Checking three fingers NMOS") + fet = ptx.ptx(width=tech.drc["minwidth_tx"], + mults=4, + tx_type="nmos", + connect_active=True, + connect_poly=True) + self.local_check(fet) + + OPTS.check_lvsdrc = True + globals.end_openram() + + def add_mods(self, fet): + self.create_contacts() + self.add_well_extension(fet) + self.add_wire_extension(fet) + self.add_well_tiedown(fet) + self.add_poly_tiedown(fet) + + def create_contacts(self): + layer_stack = ("active", "contact", "metal1") + self.well_contact = contact.contact(layer_stack) + + layer_stack = ("poly", "contact", "metal1") + self.poly_contact = contact.contact(layer_stack) + + def add_well_tiedown(self, fet): + offset = [fet.active_contact_positions[0][0], + fet.active_contact_positions[0][1] + fet.well_height] + fet.add_inst(name="well_tap", + mod=self.well_contact, + offset=offset, + mirror="R0", + rotate=0) + fet.well_contact = self.well_contact + fet.well_tiedown_location = offset + + def add_well_extension(self, fet): + well_define = {"pmos": "nwell", + "nmos": "pwell"} + well_type = well_define[fet.tx_type] + offset = getattr(fet,"{}_position".format(well_type)) + if tech.info["has_{0}".format(well_type)]: + fet.add_rect(layerNumber=tech.layer[well_type], + offset=offset, + width=fet.well_width, + height=2 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["{0}implant".format(fet.tx_type[0])], + offset=offset, + width=fet.well_width, + height=2 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["vtg"], + offset=offset, + width=fet.well_width, + height=2 * fet.well_height) + + well_type = "{0}well".format(fet.tx_type[0]) + offset[1] = offset[1] - 3 * fet.well_height + if tech.info["has_{0}".format(well_type)]: + fet.add_rect(layerNumber=tech.layer[well_type], + offset=offset, + width=fet.well_width, + height=3 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["{0}implant".format(well_define[fet.tx_type][ + 0])], + offset=offset, + width=fet.well_width, + height=3 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["vtg"], + offset=offset, + width=fet.well_width, + height=3 * fet.well_height) + + def add_wire_extension(self, fet): + xcorrect = (fet.active_contact.width / 2) - (tech.drc["minwidth_metal1"] / 2) + offset = [fet.active_contact_positions[0][0] + xcorrect, + fet.active_contact_positions[0][1]] + fet.add_rect(layerNumber=tech.layer["metal1"], + offset=offset, + width=tech.drc["minwidth_metal1"], + height=fet.well_height) + + offset = [fet.active_contact_positions[-1][0] + xcorrect, + fet.active_contact_positions[-1][1] - 2 * fet.well_height] + fet.add_rect(layerNumber=tech.layer["metal1"], + offset=offset, + width=tech.drc["minwidth_metal1"], + height=2 * fet.well_height) + + offset = [fet.poly_positions[-1][0], + fet.poly_positions[-1][1] - (fet.well_height)] + fet.add_rect(layerNumber=tech.layer["poly"], + offset=offset, + width=tech.drc["minwidth_poly"], + height=fet.well_height) + + def add_poly_tiedown(self, fet): + xcorrect = abs(self.poly_contact.upper_layer_vertical_enclosure - + self.poly_contact.lower_layer_vertical_enclosure) + offset = [fet.poly_positions[-1][0] - xcorrect, + fet.poly_positions[-1][1] - (fet.well_height)] + fet.add_inst(name="poly_contact", + mod=self.poly_contact, + offset=offset, + mirror="R270") + + + offset = [fet.active_contact_positions[-1][0], fet.active_contact_positions + [-1][1] - 2 * fet.well_height - self.well_contact.height] + fet.poly_tiedown_location = offset + fet.add_inst(name="n_tiedown", + mod=self.well_contact, + offset=offset) + tech.ptx_port.add_custom_layer(fet) + + def local_check(self, fet): + tempspice = OPTS.openram_temp + "temp.sp" + tempgds = OPTS.openram_temp + "temp.gds" + + fet.sp_write(tempspice) + fet.gds_write(tempgds) + + self.assertFalse(verify.run_drc(fet.name, tempgds)) + + os.remove(tempspice) + os.remove(tempgds) + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/tests/03_ptx_4finger_pmos_test.py b/compiler/tests/03_ptx_4finger_pmos_test.py new file mode 100644 index 00000000..4cc80f08 --- /dev/null +++ b/compiler/tests/03_ptx_4finger_pmos_test.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python2.7 +"Run a regresion test on a basic parameterized transistors" + +import unittest +from testutils import header +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug +import verify + +OPTS = globals.OPTS + +#@unittest.skip("SKIPPING 03_ptx_test") + + +class ptx_test(unittest.TestCase): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + # we will manually run lvs/drc + OPTS.check_lvsdrc = False + + import ptx + import tech + + debug.info(2, "Checking three fingers PMOS") + fet = ptx.ptx(width=tech.drc["minwidth_tx"], + mults=4, + tx_type="pmos", + connect_active=True, + connect_poly=True) + self.local_check(fet) + + OPTS.check_lvsdrc = True + globals.end_openram() + + def add_mods(self, fet): + self.create_contacts() + self.add_well_extension(fet) + self.add_wire_extension(fet) + self.add_well_tiedown(fet) + self.add_poly_tiedown(fet) + + def create_contacts(self): + layer_stack = ("active", "contact", "metal1") + self.well_contact = contact.contact(layer_stack) + + layer_stack = ("poly", "contact", "metal1") + self.poly_contact = contact.contact(layer_stack) + + def add_well_tiedown(self, fet): + offset = [fet.active_contact_positions[0][0], + fet.active_contact_positions[0][1] + fet.well_height] + fet.add_inst(name="well_tap", + mod=self.well_contact, + offset=offset, + mirror="R0", + rotate=0) + fet.well_contact = self.well_contact + fet.well_tiedown_location = offset + + def add_well_extension(self, fet): + well_define = {"pmos": "nwell", + "nmos": "pwell"} + well_type = well_define[fet.tx_type] + offset = getattr(fet,"{}_position".format(well_type)) + if tech.info["has_{0}".format(well_type)]: + fet.add_rect(layerNumber=tech.layer[well_type], + offset=offset, + width=fet.well_width, + height=2 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["{0}implant".format(fet.tx_type[0])], + offset=offset, + width=fet.well_width, + height=2 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["vtg"], + offset=offset, + width=fet.well_width, + height=2 * fet.well_height) + + well_type = "{0}well".format(fet.tx_type[0]) + offset[1] = offset[1] - 3 * fet.well_height + if tech.info["has_{0}".format(well_type)]: + fet.add_rect(layerNumber=tech.layer[well_type], + offset=offset, + width=fet.well_width, + height=3 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["{0}implant".format(well_define[fet.tx_type][ + 0])], + offset=offset, + width=fet.well_width, + height=3 * fet.well_height) + fet.add_rect(layerNumber=tech.layer["vtg"], + offset=offset, + width=fet.well_width, + height=3 * fet.well_height) + + def add_wire_extension(self, fet): + xcorrect = (fet.active_contact.width / 2) - (tech.drc["minwidth_metal1"] / 2) + offset = [fet.active_contact_positions[0][0] + xcorrect, + fet.active_contact_positions[0][1]] + fet.add_rect(layerNumber=tech.layer["metal1"], + offset=offset, + width=tech.drc["minwidth_metal1"], + height=fet.well_height) + + offset = [fet.active_contact_positions[-1][0] + xcorrect, + fet.active_contact_positions[-1][1] - 2 * fet.well_height] + fet.add_rect(layerNumber=tech.layer["metal1"], + offset=offset, + width=tech.drc["minwidth_metal1"], + height=2 * fet.well_height) + + offset = [fet.poly_positions[-1][0], + fet.poly_positions[-1][1] - (fet.well_height)] + fet.add_rect(layerNumber=tech.layer["poly"], + offset=offset, + width=tech.drc["minwidth_poly"], + height=fet.well_height) + + def add_poly_tiedown(self, fet): + xcorrect = abs(self.poly_contact.upper_layer_vertical_enclosure - + self.poly_contact.lower_layer_vertical_enclosure) + offset = [fet.poly_positions[-1][0] - xcorrect, + fet.poly_positions[-1][1] - (fet.well_height)] + fet.add_inst(name="poly_contact", + mod=self.poly_contact, + offset=offset, + mirror="R270") + + + offset = [fet.active_contact_positions[-1][0], fet.active_contact_positions + [-1][1] - 2 * fet.well_height - self.well_contact.height] + fet.poly_tiedown_location = offset + fet.add_inst(name="n_tiedown", + mod=self.well_contact, + offset=offset) + tech.ptx_port.add_custom_layer(fet) + + def local_check(self, fet): + tempspice = OPTS.openram_temp + "temp.sp" + tempgds = OPTS.openram_temp + "temp.gds" + + fet.sp_write(tempspice) + fet.gds_write(tempgds) + + self.assertFalse(verify.run_drc(fet.name, tempgds)) + + os.remove(tempspice) + os.remove(tempgds) + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main()