diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..6c8138fd --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,6 @@ +freepdk45: + script: "/home/gitlab-runner/regress_freepdk45.sh" + +scn3me_subm: + script: "/home/gitlab-runner/regress_scn3me_subm.sh" + diff --git a/README.md b/README.md index 58c8e505..d67155ca 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_presentation. The OpenRAM compiler has very few dependencies: * ngspice-26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) -* Python 2.7 and higher (currently excludes Python 3 and up) +* Python 3.5 and higher * Python numpy * a setup script for each technology * a technology directory for each technology with the base cells @@ -28,6 +28,10 @@ For example, in csh/tcsh, add to your .cshrc/.tcshrc: setenv OPENRAM_HOME "$HOME/OpenRAM/compiler" setenv OPENRAM_TECH "$HOME/OpenRAM/technology" ``` + +We include the tech files necessary for FreePDK and SCMOS. The SCMOS +spice models, however, are generic and should be replaced with foundry +models. If you are using FreePDK, you should also have that set up and have the environment variable point to the PDK. For example, in bash, add to your .bashrc: @@ -44,13 +48,16 @@ We do not distribute the PDK, but you may get it from: If you are using SCMOS, you should install Magic and netgen from: http://opencircuitdesign.com/magic/ http://opencircuitdesign.com/netgen/ -In addition, you will need to install the MOSIS SCMOS rules for scn3me_subm -that are part of QFlow: +We have included the SCN3ME design rules from QFlow: http://opencircuitdesign.com/qflow/ # DIRECTORY STRUCTURE * compiler - openram compiler itself (pointed to by OPENRAM_HOME) + * compiler/base - base data structure modules + * compiler/pgates - parameterized cells (e.g. logic gates) + * compiler/modules - high-level modules (e.g. decoders, etc.) + * compiler/verify - DRC and LVS verification wrappers * compiler/characterizer - timing characterization code * compiler/gdsMill - GDSII reader/writer * compiler/router - detailed router @@ -59,6 +66,8 @@ that are part of QFlow: * technology/freepdk45 - example configuration library for freepdk45 technology node * technology/scn3me_subm - example configuration library SCMOS technology node * technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies +* docs - LaTeX manual (likely outdated) +* lib - IP library of pregenerated memories # UNIT TESTS @@ -232,3 +241,4 @@ ui().importCds("default", When you import a gds file, make sure to attach the correct tech lib or you will get incorrect layers in the resulting library. +TEST diff --git a/compiler/base/design.py b/compiler/base/design.py index c1c976c5..794bb79a 100644 --- a/compiler/base/design.py +++ b/compiler/base/design.py @@ -40,10 +40,12 @@ class design(hierarchy_spice.spice, hierarchy_layout.layout): 'hierarchical_predecode3x8'] if name not in design.name_map: design.name_map.append(name) - elif self.__class__.__name__ in ok_list: - pass else: - debug.error("Duplicate layout reference name {0} of class {1}. GDS2 requires names be unique.".format(name,self.__class__),-1) + for ok_names in ok_list: + if ok_names in self.__class__.__name__: + break + else: + debug.error("Duplicate layout reference name {0} of class {1}. GDS2 requires names be unique.".format(name,self.__class__),-1) def setup_drc_constants(self): """ These are some DRC constants used in many places in the compiler.""" diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 38ed8957..bc4beb88 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -8,7 +8,7 @@ def relative_compare(value1,value2,error_tolerance=0.001): return (abs(value1 - value2) / max(value1,value2) <= error_tolerance) -def parse_output(filename, key): +def parse_spice_list(filename, key): """Parses a hspice output.lis file for a key value""" if OPTS.spice_name == "xa" : # customsim has a different output file name @@ -22,6 +22,7 @@ def parse_output(filename, key): except IOError: debug.error("Unable to open spice output file: {0}".format(full_filename),1) contents = f.read() + f.close() # val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents) val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 6b58d5d3..a7b2df7c 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -339,16 +339,16 @@ class delay(): # Checking from not data_value to data_value self.write_delay_stimulus() self.stim.run_sim() - delay_hl = parse_output("timing", "delay_hl") - delay_lh = parse_output("timing", "delay_lh") - slew_hl = parse_output("timing", "slew_hl") - slew_lh = parse_output("timing", "slew_lh") + delay_hl = parse_spice_list("timing", "delay_hl") + delay_lh = parse_spice_list("timing", "delay_lh") + slew_hl = parse_spice_list("timing", "slew_hl") + slew_lh = parse_spice_list("timing", "slew_lh") delays = (delay_hl, delay_lh, slew_hl, slew_lh) - read0_power=parse_output("timing", "read0_power") - write0_power=parse_output("timing", "write0_power") - read1_power=parse_output("timing", "read1_power") - write1_power=parse_output("timing", "write1_power") + read0_power=parse_spice_list("timing", "read0_power") + write0_power=parse_spice_list("timing", "write0_power") + read1_power=parse_spice_list("timing", "read1_power") + write1_power=parse_spice_list("timing", "write1_power") if not self.check_valid_delays(delays): return (False,{}) @@ -378,13 +378,13 @@ class delay(): self.write_power_stimulus(trim=False) self.stim.run_sim() - leakage_power=parse_output("timing", "leakage_power") + leakage_power=parse_spice_list("timing", "leakage_power") debug.check(leakage_power!="Failed","Could not measure leakage power.") self.write_power_stimulus(trim=True) self.stim.run_sim() - trim_leakage_power=parse_output("timing", "leakage_power") + trim_leakage_power=parse_spice_list("timing", "leakage_power") debug.check(trim_leakage_power!="Failed","Could not measure leakage power.") # For debug, you sometimes want to inspect each simulation. @@ -473,10 +473,10 @@ class delay(): # Checking from not data_value to data_value self.write_delay_stimulus() self.stim.run_sim() - delay_hl = parse_output("timing", "delay_hl") - delay_lh = parse_output("timing", "delay_lh") - slew_hl = parse_output("timing", "slew_hl") - slew_lh = parse_output("timing", "slew_lh") + delay_hl = parse_spice_list("timing", "delay_hl") + delay_lh = parse_spice_list("timing", "delay_lh") + slew_hl = parse_spice_list("timing", "slew_hl") + slew_lh = parse_spice_list("timing", "slew_lh") # if it failed or the read was longer than a period if type(delay_hl)!=float or type(delay_lh)!=float or type(slew_lh)!=float or type(slew_hl)!=float: debug.info(2,"Invalid measures: Period {0}, delay_hl={1}ns, delay_lh={2}ns slew_hl={3}ns slew_lh={4}ns".format(self.period, diff --git a/compiler/characterizer/setup_hold.py b/compiler/characterizer/setup_hold.py index 4f261b5a..aaeff0cd 100644 --- a/compiler/characterizer/setup_hold.py +++ b/compiler/characterizer/setup_hold.py @@ -186,8 +186,8 @@ class setup_hold(): target_time=feasible_bound, correct_value=correct_value) self.stim.run_sim() - ideal_clk_to_q = convert_to_float(parse_output("timing", "clk2q_delay")) - setuphold_time = convert_to_float(parse_output("timing", "setup_hold_time")) + ideal_clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay")) + setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time")) debug.info(2,"*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, correct_value,ideal_clk_to_q,setuphold_time)) if type(ideal_clk_to_q)!=float or type(setuphold_time)!=float: @@ -219,8 +219,8 @@ class setup_hold(): self.stim.run_sim() - clk_to_q = convert_to_float(parse_output("timing", "clk2q_delay")) - setuphold_time = convert_to_float(parse_output("timing", "setup_hold_time")) + clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay")) + setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time")) if type(clk_to_q)==float and (clk_to_q<1.1*ideal_clk_to_q) and type(setuphold_time)==float: if mode == "SETUP": # SETUP is clk-din, not din-clk setuphold_time *= -1e9 diff --git a/compiler/characterizer/trim_spice.py b/compiler/characterizer/trim_spice.py index ead653b9..d3be0dc8 100644 --- a/compiler/characterizer/trim_spice.py +++ b/compiler/characterizer/trim_spice.py @@ -17,6 +17,7 @@ class trim_spice(): # Load the file into a buffer for performance sp = open(self.sp_file, "r") self.spice = sp.readlines() + sp.close() for i in range(len(self.spice)): self.spice[i] = self.spice[i].rstrip(" \n") @@ -97,6 +98,7 @@ class trim_spice(): # Finally, write out the buffer as the new reduced file sp = open(self.reduced_spfile, "w") sp.write("\n".join(self.sp_buffer)) + sp.close() def remove_insts(self, subckt_name, keep_inst_list): diff --git a/compiler/globals.py b/compiler/globals.py index 58922e52..86d091b1 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -89,11 +89,13 @@ def print_banner(): def check_versions(): """ Run some checks of required software versions. """ - # Now require python >=3.6 + # Now require python >=3.5 major_python_version = sys.version_info.major minor_python_version = sys.version_info.minor - if not (major_python_version == 3 and minor_python_version >= 6): - debug.error("Python 3.6 or greater is required.",-1) + major_required = 3 + minor_required = 5 + if not (major_python_version == major_required and minor_python_version >= minor_required): + debug.error("Python {0}.{1} or greater is required.".format(major_required,minor_required),-1) # FIXME: Check versions of other tools here?? # or, this could be done in each module (e.g. verify, characterizer, etc.) diff --git a/compiler/modules/bitcell.py b/compiler/modules/bitcell.py index 0cb80635..33707b81 100644 --- a/compiler/modules/bitcell.py +++ b/compiler/modules/bitcell.py @@ -11,7 +11,7 @@ class bitcell(design.design): library. """ - pin_names = ["BL", "BR", "WL", "vdd", "gnd"] + pin_names = ["bl", "br", "wl", "vdd", "gnd"] (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"], layer["boundary"]) @@ -34,7 +34,59 @@ class bitcell(design.design): c_para = spice["min_tx_drain_c"] result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing) return result + + + def list_bitcell_pins(self, col, row): + """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ + bitcell_pins = ["bl[{0}]".format(col), + "br[{0}]".format(col), + "wl[{0}]".format(row), + "vdd", + "gnd"] + return bitcell_pins + + def list_row_pins(self): + """ Creates a list of all row pins (except for gnd and vdd) """ + row_pins = ["wl"] + return row_pins + + def list_read_row_pins(self): + """ Creates a list of row pins associated with read ports """ + row_pins = ["wl"] + return row_pins + + def list_write_row_pins(self): + """ Creates a list of row pins associated with write ports """ + row_pins = ["wl"] + return row_pins + + + def list_column_pins(self): + """ Creates a list of all column pins (except for gnd and vdd) """ + column_pins = ["bl", "br"] + return column_pins + + def list_read_column_pins(self): + """ Creates a list of column pins associated with read ports """ + column_pins = ["bl"] + return column_pins + + def list_read_bar_column_pins(self): + """ Creates a list of column pins associated with read_bar ports """ + column_pins = ["br"] + return column_pins + + def list_write_column_pins(self): + """ Creates a list of column pins associated with write ports """ + column_pins = ["bl"] + return column_pins + + def list_write_bar_column_pins(self): + """ Creates a list of column pins asscociated with write_bar ports""" + column_pins = ["br"] + return column_pins + def analytical_power(self, proc, vdd, temp, load): """Bitcell power in nW. Only characterizes leakage.""" from tech import spice @@ -42,3 +94,4 @@ class bitcell(design.design): dynamic = 0 #temporary total_power = self.return_power(dynamic, leakage) return total_power + diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index b609a384..d538388a 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -1,204 +1,202 @@ -import debug -import design -from tech import drc, spice -from vector import vector -from globals import OPTS - - - -class bitcell_array(design.design): - """ - Creates a rows x cols array of memory cells. Assumes bit-lines - and word line is connected by abutment. - Connects the word lines and bit lines. - """ - - def __init__(self, cols, rows, name="bitcell_array"): - design.design.__init__(self, name) - debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) - - - self.column_size = cols - self.row_size = rows - - from importlib import reload - c = reload(__import__(OPTS.bitcell)) - self.mod_bitcell = getattr(c, OPTS.bitcell) - self.cell = self.mod_bitcell() - self.add_mod(self.cell) - - # We increase it by a well enclosure so the precharges don't overlap our wells - self.height = self.row_size*self.cell.height + drc["well_enclosure_active"] + self.m1_width - self.width = self.column_size*self.cell.width + self.m1_width - - self.add_pins() - self.create_layout() - self.add_layout_pins() - - # We don't offset this because we need to align - # the replica bitcell in the control logic - #self.offset_all_coordinates() - - self.DRC_LVS() - - def add_pins(self): - for col in range(self.column_size): - self.add_pin("bl[{0}]".format(col)) - self.add_pin("br[{0}]".format(col)) - for row in range(self.row_size): - self.add_pin("wl[{0}]".format(row)) - self.add_pin("vdd") - self.add_pin("gnd") - - def create_layout(self): - xoffset = 0.0 - self.cell_inst = {} - for col in range(self.column_size): - yoffset = 0.0 - for row in range(self.row_size): - name = "bit_r{0}_c{1}".format(row, col) - - if row % 2: - tempy = yoffset + self.cell.height - dir_key = "MX" - else: - tempy = yoffset - dir_key = "" - - self.cell_inst[row,col]=self.add_inst(name=name, - mod=self.cell, - offset=[xoffset, tempy], - mirror=dir_key) - self.connect_inst(["bl[{0}]".format(col), - "br[{0}]".format(col), - "wl[{0}]".format(row), - "vdd", - "gnd"]) - yoffset += self.cell.height - xoffset += self.cell.width - - - def add_layout_pins(self): - """ Add the layout pins """ - - offset = vector(0.0, 0.0) - for col in range(self.column_size): - # get the pin of the lower row cell and make it the full width - bl_pin = self.cell_inst[0,col].get_pin("BL") - br_pin = self.cell_inst[0,col].get_pin("BR") - self.add_layout_pin(text="bl[{0}]".format(col), - layer="metal2", - offset=bl_pin.ll(), - width=bl_pin.width(), - height=self.height) - self.add_layout_pin(text="br[{0}]".format(col), - layer="metal2", - offset=br_pin.ll(), - width=br_pin.width(), - height=self.height) - - # increments to the next column width - offset.x += self.cell.width - - offset.x = 0.0 - for row in range(self.row_size): - wl_pin = self.cell_inst[row,0].get_pin("WL") - self.add_layout_pin(text="wl[{0}]".format(row), - layer="metal1", - offset=wl_pin.ll(), - width=self.width, - height=wl_pin.height()) - - # increments to the next row height - offset.y += self.cell.height - - # For every second row and column, add a via for vdd - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row,col] - for vdd_pin in inst.get_pins("vdd"): - # Drop to M1 if needed - if vdd_pin.layer == "metal1": - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=vdd_pin.center(), - rotate=90) - # Always drop to M2 - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=vdd_pin.center()) - self.add_layout_pin_rect_center(text="vdd", - layer="metal3", - offset=vdd_pin.center()) - - - # For every second row and column (+1), add a via for gnd - for row in range(self.row_size): - for col in range(self.column_size): - inst = self.cell_inst[row,col] - for gnd_pin in inst.get_pins("gnd"): - # Drop to M1 if needed - if gnd_pin.layer == "metal1": - self.add_via_center(layers=("metal1", "via1", "metal2"), - offset=gnd_pin.center(), - rotate=90) - # Always drop to M2 - self.add_via_center(layers=("metal2", "via2", "metal3"), - offset=gnd_pin.center()) - self.add_layout_pin_rect_center(text="gnd", - layer="metal3", - offset=gnd_pin.center()) - - def analytical_delay(self, slew, load=0): - from tech import drc - wl_wire = self.gen_wl_wire() - wl_wire.return_delay_over_wire(slew) - - wl_to_cell_delay = wl_wire.return_delay_over_wire(slew) - # hypothetical delay from cell to bl end without sense amp - bl_wire = self.gen_bl_wire() - cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r - # hence just use the whole c - bl_swing = 0.1 - cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing) - - #we do not consider the delay over the wire for now - return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay, - wl_to_cell_delay.slew) - - def analytical_power(self, proc, vdd, temp, load): - """Power of Bitcell array and bitline in nW.""" - from tech import drc - - # Dynamic Power from Bitline - bl_wire = self.gen_bl_wire() - cell_load = 2 * bl_wire.return_input_cap() - bl_swing = 0.1 #This should probably be defined in the tech file or input - freq = spice["default_event_rate"] - bitline_dynamic = bl_swing*cell_load*vdd*vdd*freq #not sure if calculation is correct - - #Calculate the bitcell power which currently only includes leakage - cell_power = self.cell.analytical_power(proc, vdd, temp, load) - - #Leakage power grows with entire array and bitlines. - total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, - cell_power.leakage * self.column_size * self.row_size) - return total_power - - def gen_wl_wire(self): - wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"]) - wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell - return wl_wire - - def gen_bl_wire(self): - bl_pos = 0 - bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"]) - bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell - return bl_wire - - def output_load(self, bl_pos=0): - bl_wire = self.gen_bl_wire() - return bl_wire.wire_c # sense amp only need to charge small portion of the bl - # set as one segment for now - - def input_load(self): - wl_wire = self.gen_wl_wire() - return wl_wire.return_input_cap() +import debug +import design +from tech import drc, spice +from vector import vector +from globals import OPTS + + + +class bitcell_array(design.design): + """ + Creates a rows x cols array of memory cells. Assumes bit-lines + and word line is connected by abutment. + Connects the word lines and bit lines. + """ + + def __init__(self, cols, rows, name="bitcell_array"): + design.design.__init__(self, name) + debug.info(1, "Creating {0} {1} x {2}".format(self.name, rows, cols)) + + + self.column_size = cols + self.row_size = rows + + from importlib import reload + c = reload(__import__(OPTS.bitcell)) + self.mod_bitcell = getattr(c, OPTS.bitcell) + self.cell = self.mod_bitcell() + self.add_mod(self.cell) + + # We increase it by a well enclosure so the precharges don't overlap our wells + self.height = self.row_size*self.cell.height + drc["well_enclosure_active"] + self.m1_width + self.width = self.column_size*self.cell.width + self.m1_width + + self.add_pins() + self.create_layout() + self.add_layout_pins() + + # We don't offset this because we need to align + # the replica bitcell in the control logic + #self.offset_all_coordinates() + + self.DRC_LVS() + + def add_pins(self): + row_list = self.cell.list_row_pins() + column_list = self.cell.list_column_pins() + for col in range(self.column_size): + for cell_column in column_list: + self.add_pin(cell_column+"[{0}]".format(col)) + for row in range(self.row_size): + for cell_row in row_list: + self.add_pin(cell_row+"[{0}]".format(row)) + self.add_pin("vdd") + self.add_pin("gnd") + + def create_layout(self): + xoffset = 0.0 + self.cell_inst = {} + for col in range(self.column_size): + yoffset = 0.0 + for row in range(self.row_size): + name = "bit_r{0}_c{1}".format(row, col) + + if row % 2: + tempy = yoffset + self.cell.height + dir_key = "MX" + else: + tempy = yoffset + dir_key = "" + + self.cell_inst[row,col]=self.add_inst(name=name, + mod=self.cell, + offset=[xoffset, tempy], + mirror=dir_key) + self.connect_inst(self.cell.list_bitcell_pins(col, row)) + + yoffset += self.cell.height + xoffset += self.cell.width + + + def add_layout_pins(self): + """ Add the layout pins """ + + row_list = self.cell.list_row_pins() + column_list = self.cell.list_column_pins() + + offset = vector(0.0, 0.0) + for col in range(self.column_size): + for cell_column in column_list: + bl_pin = self.cell_inst[0,col].get_pin(cell_column) + self.add_layout_pin(text=cell_column+"[{0}]".format(col), + layer="metal2", + offset=bl_pin.ll(), + width=bl_pin.width(), + height=self.height) + + # increments to the next column width + offset.x += self.cell.width + + offset.x = 0.0 + for row in range(self.row_size): + for cell_row in row_list: + wl_pin = self.cell_inst[row,0].get_pin(cell_row) + self.add_layout_pin(text=cell_row+"[{0}]".format(row), + layer="metal1", + offset=wl_pin.ll(), + width=self.width, + height=wl_pin.height()) + + # increments to the next row height + offset.y += self.cell.height + + # For every second row and column, add a via for vdd + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row,col] + for vdd_pin in inst.get_pins("vdd"): + # Drop to M1 if needed + if vdd_pin.layer == "metal1": + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=vdd_pin.center(), + rotate=90) + # Always drop to M2 + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=vdd_pin.center()) + self.add_layout_pin_rect_center(text="vdd", + layer="metal3", + offset=vdd_pin.center()) + + + # For every second row and column (+1), add a via for gnd + for row in range(self.row_size): + for col in range(self.column_size): + inst = self.cell_inst[row,col] + for gnd_pin in inst.get_pins("gnd"): + # Drop to M1 if needed + if gnd_pin.layer == "metal1": + self.add_via_center(layers=("metal1", "via1", "metal2"), + offset=gnd_pin.center(), + rotate=90) + # Always drop to M2 + self.add_via_center(layers=("metal2", "via2", "metal3"), + offset=gnd_pin.center()) + self.add_layout_pin_rect_center(text="gnd", + layer="metal3", + offset=gnd_pin.center()) + + def analytical_delay(self, slew, load=0): + from tech import drc + wl_wire = self.gen_wl_wire() + wl_wire.return_delay_over_wire(slew) + + wl_to_cell_delay = wl_wire.return_delay_over_wire(slew) + # hypothetical delay from cell to bl end without sense amp + bl_wire = self.gen_bl_wire() + cell_load = 2 * bl_wire.return_input_cap() # we ingore the wire r + # hence just use the whole c + bl_swing = 0.1 + cell_delay = self.cell.analytical_delay(wl_to_cell_delay.slew, cell_load, swing = bl_swing) + + #we do not consider the delay over the wire for now + return self.return_delay(cell_delay.delay+wl_to_cell_delay.delay, + wl_to_cell_delay.slew) + + def analytical_power(self, proc, vdd, temp, load): + """Power of Bitcell array and bitline in nW.""" + from tech import drc + + # Dynamic Power from Bitline + bl_wire = self.gen_bl_wire() + cell_load = 2 * bl_wire.return_input_cap() + bl_swing = 0.1 #This should probably be defined in the tech file or input + freq = spice["default_event_rate"] + bitline_dynamic = bl_swing*cell_load*vdd*vdd*freq #not sure if calculation is correct + + #Calculate the bitcell power which currently only includes leakage + cell_power = self.cell.analytical_power(proc, vdd, temp, load) + + #Leakage power grows with entire array and bitlines. + total_power = self.return_power(cell_power.dynamic + bitline_dynamic * self.column_size, + cell_power.leakage * self.column_size * self.row_size) + return total_power + + def gen_wl_wire(self): + wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"]) + wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell + return wl_wire + + def gen_bl_wire(self): + bl_pos = 0 + bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"]) + bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell + return bl_wire + + def output_load(self, bl_pos=0): + bl_wire = self.gen_bl_wire() + return bl_wire.wire_c # sense amp only need to charge small portion of the bl + # set as one segment for now + + def input_load(self): + wl_wire = self.gen_wl_wire() + return wl_wire.return_input_cap() diff --git a/compiler/modules/sense_amp_array.py b/compiler/modules/sense_amp_array.py index c56fc047..5d16b323 100644 --- a/compiler/modules/sense_amp_array.py +++ b/compiler/modules/sense_amp_array.py @@ -33,9 +33,8 @@ class sense_amp_array(design.design): def add_pins(self): - for i in range(0,self.row_size,self.words_per_row): - index = int(i/self.words_per_row) - self.add_pin("data[{0}]".format(index)) + for i in range(0,self.word_size): + self.add_pin("data[{0}]".format(i)) self.add_pin("bl[{0}]".format(i)) self.add_pin("br[{0}]".format(i)) @@ -55,23 +54,22 @@ class sense_amp_array(design.design): br_pin = self.amp.get_pin("br") dout_pin = self.amp.get_pin("dout") - for i in range(0,self.row_size,self.words_per_row): + amp_spacing = self.amp.width * self.words_per_row + for i in range(0,self.word_size): name = "sa_d{0}".format(i) - amp_position = vector(self.amp.width * i, 0) + amp_position = vector(amp_spacing * i, 0) bl_offset = amp_position + bl_pin.ll().scale(1,0) br_offset = amp_position + br_pin.ll().scale(1,0) dout_offset = amp_position + dout_pin.ll() - index = int(i/self.words_per_row) - inst = self.add_inst(name=name, mod=self.amp, offset=amp_position) self.connect_inst(["bl[{0}]".format(i), "br[{0}]".format(i), - "data[{0}]".format(index), + "data[{0}]".format(i), "en", "vdd", "gnd"]) @@ -100,7 +98,7 @@ class sense_amp_array(design.design): width=br_pin.width(), height=br_pin.height()) - self.add_layout_pin(text="data[{0}]".format(index), + self.add_layout_pin(text="data[{0}]".format(i), layer="metal2", offset=dout_offset, width=dout_pin.width(), diff --git a/compiler/options.py b/compiler/options.py index c68a6537..bc0368ce 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -48,6 +48,7 @@ class options(optparse.Values): # These are the configuration parameters rw_ports = 1 r_ports = 0 + w_ports = 0 # These will get initialized by the the file supply_voltages = "" temperatures = "" diff --git a/compiler/pgates/pbitcell.py b/compiler/pgates/pbitcell.py new file mode 100644 index 00000000..3d090432 --- /dev/null +++ b/compiler/pgates/pbitcell.py @@ -0,0 +1,1138 @@ +import contact +import pgate +import design +import debug +from tech import drc, parameter, spice +from vector import vector +from ptx import ptx +from globals import OPTS + +class pbitcell(pgate.pgate): + """ + This module implements a parametrically sized multi-port bitcell + """ + + width = None + height = None + + def __init__(self, num_readwrite=OPTS.rw_ports, num_write=OPTS.w_ports, num_read=OPTS.r_ports): + name = "pbitcell_{0}RW_{1}W_{2}R".format(num_readwrite, num_write, num_read) + pgate.pgate.__init__(self, name) + debug.info(2, "create a multi-port bitcell with {0} write ports and {1} read ports".format(num_write, num_read)) + + self.num_readwrite = num_readwrite + self.num_write = num_write + self.num_read = num_read + + self.add_pins() + self.create_layout() + self.DRC_LVS() + + pbitcell.width = self.width + pbitcell.height = self.height + + + def add_pins(self): + """ + Adding pins for pbitcell module + """ + for k in range(self.num_readwrite): + self.add_pin("rwbl{}".format(k)) + self.add_pin("rwbl_bar{}".format(k)) + for k in range(self.num_write): + self.add_pin("wbl{}".format(k)) + self.add_pin("wbl_bar{}".format(k)) + for k in range(self.num_read): + self.add_pin("rbl{}".format(k)) + self.add_pin("rbl_bar{}".format(k)) + + for k in range(self.num_readwrite): + self.add_pin("rwwl{}".format(k)) + for k in range(self.num_write): + self.add_pin("wwl{}".format(k)) + for k in range(self.num_read): + self.add_pin("rwl{}".format(k)) + + self.add_pin("vdd") + self.add_pin("gnd") + + + def create_layout(self): + self.create_ptx() + self.calculate_spacing() + self.calculate_postions() + self.add_storage() + self.add_rails() + if(self.num_readwrite > 0): + self.add_readwrite_ports() + if(self.num_write > 0): + self.add_write_ports() + if(self.num_read > 0): + self.add_read_ports() + self.extend_well() + self.offset_all_coordinates() + + + def create_ptx(self): + """ + Calculate transistor sizes and create ptx for read/write, write, and read ports + """ + + """ calculate transistor sizes """ + # if there are any read/write ports, then the inverter nmos is sized based the number of them + if(self.num_readwrite > 0): + inverter_nmos_width = self.num_readwrite*3*parameter["min_tx_size"] + inverter_pmos_width = parameter["min_tx_size"] + readwrite_nmos_width = 1.5*parameter["min_tx_size"] + write_nmos_width = parameter["min_tx_size"] + read_nmos_width = 2*parameter["min_tx_size"] + + # if there are no read/write ports, then the inverter nmos is sized for the dual port case + else: + inverter_nmos_width = 2*parameter["min_tx_size"] + inverter_pmos_width = parameter["min_tx_size"] + readwrite_nmos_width = 1.5*parameter["min_tx_size"] + write_nmos_width = parameter["min_tx_size"] + read_nmos_width = 2*parameter["min_tx_size"] + + """ Create ptx for all transistors """ + # create ptx for inverter transistors + self.inverter_nmos = ptx(width=inverter_nmos_width, + tx_type="nmos") + self.add_mod(self.inverter_nmos) + + self.inverter_pmos = ptx(width=inverter_pmos_width, + tx_type="pmos") + self.add_mod(self.inverter_pmos) + + # create ptx for readwrite transitors + self.readwrite_nmos = ptx(width=readwrite_nmos_width, + tx_type="nmos") + self.add_mod(self.readwrite_nmos) + + # create ptx for write transitors + self.write_nmos = ptx(width=write_nmos_width, + tx_type="nmos") + self.add_mod(self.write_nmos) + + # create ptx for read transistors + self.read_nmos = ptx(width=read_nmos_width, + tx_type="nmos") + self.add_mod(self.read_nmos) + + + def calculate_spacing(self): + """ + Calculate transistor spacings + """ + + """ calculate metal contact extensions over transistor active """ + self.inverter_pmos_contact_extension = 0.5*(self.inverter_pmos.active_contact.height - self.inverter_pmos.active_height) + self.readwrite_nmos_contact_extension = 0.5*(self.readwrite_nmos.active_contact.height - self.readwrite_nmos.active_height) + self.write_nmos_contact_extension = 0.5*(self.write_nmos.active_contact.height - self.write_nmos.active_height) + self.read_nmos_contact_extension = 0.5*(self.read_nmos.active_contact.height - self.read_nmos.active_height) + + # calculate the distance threshold for different gate contact spacings + self.gate_contact_thres = drc["poly_to_active"] - drc["minwidth_metal2"] + + """ calculations for horizontal transistor to tansistor spacing """ + # inverter spacings + self.inverter_to_inverter_spacing = contact.poly.height + drc["minwidth_metal1"] + self.inverter_to_write_spacing = drc["pwell_to_nwell"] + 2*drc["well_enclosure_active"] + + # readwrite to readwrite transistor spacing (also acts as readwrite to write transistor spacing) + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + self.readwrite_to_readwrite_spacing = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] + else: + self.readwrite_to_readwrite_spacing = drc["poly_to_active"] + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] + + # write to write transistor spacing + if(self.write_nmos_contact_extension > self.gate_contact_thres): + self.write_to_write_spacing = drc["minwidth_metal2"] + self.write_nmos_contact_extension + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] + else: + self.write_to_write_spacing = drc["poly_to_active"] + contact.poly.width + drc["poly_to_field_poly"] + drc["poly_extend_active"] + + # read to read transistor spacing + if(self.read_nmos_contact_extension > self.gate_contact_thres): + self.read_to_read_spacing = 2*(drc["minwidth_metal2"] + self.read_nmos_contact_extension) + drc["minwidth_metal1"] + 2*contact.poly.width + else: + self.read_to_read_spacing = 2*drc["poly_to_active"] + drc["minwidth_metal1"] + 2*contact.poly.width + + # write to read transistor spacing (also acts as readwrite to read transistor spacing) + # calculation is dependent on whether the read transistor is adjacent to a write transistor or a readwrite transistor + if(self.num_write > 0): + if(self.write_nmos_contact_extension > self.gate_contact_thres): + write_portion = drc["minwidth_metal2"] + self.write_nmos_contact_extension + else: + write_portion = drc["poly_to_active"] + else: + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + write_portion = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + else: + write_portion = drc["poly_to_active"] + + if(self.read_nmos_contact_extension > self.gate_contact_thres): + read_portion = drc["minwidth_metal2"] + self.read_nmos_contact_extension + else: + read_portion = drc["poly_to_active"] + + self.write_to_read_spacing = write_portion + read_portion + 2*contact.poly.width + drc["poly_to_field_poly"] + + """ calculations for transistor tiling (transistor + spacing) """ + self.inverter_tile_width = self.inverter_nmos.active_width + 0.5*self.inverter_to_inverter_spacing + self.readwrite_tile_width = self.readwrite_to_readwrite_spacing + self.readwrite_nmos.active_height + self.write_tile_width = self.write_to_write_spacing + self.write_nmos.active_height + self.read_tile_width = self.read_to_read_spacing + self.read_nmos.active_height + + """ calculation for row line tiling """ + self.rail_tile_height = drc["active_to_body_active"] + 0.5*(drc["minwidth_tx"] - drc["minwidth_metal1"]) + drc["minwidth_metal1"] + self.rowline_tile_height = drc["minwidth_metal1"] + contact.m1m2.width + + """ calculations related to inverter connections """ + self.inverter_gap = drc["poly_to_active"] + drc["poly_to_field_poly"] + 2*contact.poly.width + drc["minwidth_metal1"] + self.inverter_pmos_contact_extension + self.cross_couple_lower_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + 0.5*contact.poly.width + self.cross_couple_upper_ypos = self.inverter_nmos.active_height + drc["poly_to_active"] + drc["poly_to_field_poly"] + 1.5*contact.poly.width + + + def calculate_postions(self): + """ + Calculate positions that describe the edges of the cell + """ + # create flags for excluding readwrite, write, or read port calculations if they are not included in the bitcell + if(self.num_readwrite > 0): + self.readwrite_port_flag = True + else: + self.readwrite_port_flag = False + + if(self.num_write > 0): + self.write_port_flag = True + else: + self.write_port_flag = False + + if(self.num_read > 0): + self.read_port_flag = True + else: + self.read_port_flag = False + + # determine the distance of the leftmost/rightmost transistor gate connection + if (self.num_read > 0): + if(self.read_nmos_contact_extension > self.gate_contact_thres): + end_connection = drc["minwidth_metal2"] + self.read_nmos_contact_extension + contact.m1m2.height + else: + end_connection = drc["poly_to_active"] + contact.m1m2.height + else: + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + end_connection = drc["minwidth_metal2"] + self.readwrite_nmos_contact_extension + contact.m1m2.height + else: + end_connection = drc["poly_to_active"] + contact.m1m2.height + + # leftmost position = storage width + read/write ports width + write ports width + read ports width + end transistor gate connections + metal spacing necessary for tiling the bitcell + self.leftmost_xpos = -self.inverter_tile_width \ + - self.inverter_to_write_spacing \ + - self.readwrite_port_flag*(self.readwrite_nmos.active_height + (self.num_readwrite-1)*self.readwrite_tile_width) \ + - self.write_port_flag*self.readwrite_port_flag*self.write_to_write_spacing \ + - self.write_port_flag*(self.write_nmos.active_height + (self.num_write-1)*self.write_tile_width) \ + - self.read_port_flag*self.write_to_read_spacing \ + - self.read_port_flag*(self.read_nmos.active_height + (self.num_read-1)*self.read_tile_width) \ + - end_connection \ + - 0.5*drc["minwidth_metal2"] + + self.rightmost_xpos = -self.leftmost_xpos + + # bottommost position = gnd height + rwwl height + wwl height + rwl height + space needed between tiled bitcells + array_tiling_offset = 0.5*drc["minwidth_metal2"] + self.botmost_ypos = -self.rail_tile_height \ + - self.num_readwrite*self.rowline_tile_height \ + - self.num_write*self.rowline_tile_height \ + - self.num_read*self.rowline_tile_height \ + - array_tiling_offset + + # topmost position = height of the inverter + height of vdd + self.topmost_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \ + + self.rail_tile_height + + # calculations for the cell dimensions + array_vdd_overlap = 0.5*drc["minwidth_metal1"] + self.width = -2*self.leftmost_xpos + self.height = self.topmost_ypos - self.botmost_ypos - array_vdd_overlap + + + def add_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". + """ + + # 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.active_height + self.inverter_gap + + # create active for nmos + self.inverter_nmos_left = self.add_inst(name="inverter_nmos_left", + mod=self.inverter_nmos, + offset=[left_inverter_xpos,0]) + self.connect_inst(["Q_bar", "Q", "gnd", "gnd"]) + + self.inverter_nmos_right = self.add_inst(name="inverter_nmos_right", + mod=self.inverter_nmos, + offset=[right_inverter_xpos,0]) + self.connect_inst(["gnd", "Q_bar", "Q", "gnd"]) + + # create active for pmos + self.inverter_pmos_left = self.add_inst(name="inverter_pmos_left", + mod=self.inverter_pmos, + offset=[left_inverter_xpos, inverter_pmos_ypos]) + self.connect_inst(["Q_bar", "Q", "vdd", "vdd"]) + + self.inverter_pmos_right = self.add_inst(name="inverter_pmos_right", + mod=self.inverter_pmos, + offset=[right_inverter_xpos, inverter_pmos_ypos]) + self.connect_inst(["vdd", "Q_bar", "Q", "vdd"]) + + # 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()]) + + # 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()], width=contact.well.second_layer_width) + self.add_path("metal1", [self.inverter_nmos_right.get_pin("S").uc(), self.inverter_pmos_right.get_pin("S").bc()], width=contact.well.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, self.cross_couple_upper_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=contact_offset_left, + rotate=90) + + 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_contact_center(layers=("poly", "contact", "metal1"), + offset=contact_offset_right, + rotate=90) + + # 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) + 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) + self.add_path("poly", [contact_offset_right, gate_offset_left]) + + # update furthest left and right transistor edges (this will propagate to further transistor offset calculations) + self.left_building_edge = -self.inverter_tile_width + self.right_building_edge = self.inverter_tile_width + + + def add_rails(self): + """ + Add gnd and vdd rails and connects them to the inverters + """ + + """ Add rails for vdd and gnd """ + self.gnd_position = vector(self.leftmost_xpos, -self.rail_tile_height) + self.gnd = self.add_layout_pin(text="gnd", + layer="metal1", + offset=self.gnd_position, + width=self.width, + height=contact.well.second_layer_width) + + vdd_ypos = self.inverter_nmos.active_height + self.inverter_gap + self.inverter_pmos.active_height \ + + drc["active_to_body_active"] + 0.5*(drc["minwidth_tx"] - drc["minwidth_metal1"]) + self.vdd_position = vector(self.leftmost_xpos, vdd_ypos) + self.vdd = self.add_layout_pin(text="vdd", + layer="metal1", + offset=self.vdd_position, + width=self.width, + height=drc["minwidth_metal1"]) + + """ Connect inverters to rails """ + # connect inverter nmos to gnd + gnd_pos_left = vector(self.inverter_nmos_left.get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.inverter_nmos_left.get_pin("S").bc(), gnd_pos_left]) + + gnd_pos_right = vector(self.inverter_nmos_right.get_pin("D").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.inverter_nmos_right.get_pin("D").bc(), gnd_pos_right]) + + # connect 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_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 add_readwrite_ports(self): + """ + Adds 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 RWBL_bar to Q_bar. + """ + + """ Define variables relevant to write transistors """ + # define offset correction due to rotation of the ptx module + readwrite_rotation_correct = self.readwrite_nmos.active_height + + # define write transistor variables as empty arrays based on the number of write ports + self.readwrite_nmos_left = [None] * self.num_readwrite + self.readwrite_nmos_right = [None] * self.num_readwrite + self.rwwl_positions = [None] * self.num_readwrite + self.rwbl_positions = [None] * self.num_readwrite + self.rwbl_bar_positions = [None] * self.num_readwrite + + # iterate over the number of read/write ports + for k in range(0,self.num_readwrite): + """ Add transistors """ + # calculate read/write transistor offsets + left_readwrite_transistor_xpos = self.left_building_edge \ + - self.inverter_to_write_spacing \ + - self.readwrite_nmos.active_height - k*self.readwrite_tile_width \ + + readwrite_rotation_correct + + right_readwrite_transistor_xpos = self.right_building_edge \ + + self.inverter_to_write_spacing \ + + k*self.readwrite_tile_width \ + + readwrite_rotation_correct + + # add read/write transistors + self.readwrite_nmos_left[k] = self.add_inst(name="readwrite_nmos_left{}".format(k), + mod=self.readwrite_nmos, + offset=[left_readwrite_transistor_xpos,0], + rotate=90) + self.connect_inst(["Q", "rwwl{}".format(k), "rwbl{}".format(k), "gnd"]) + + self.readwrite_nmos_right[k] = self.add_inst(name="readwrite_nmos_right{}".format(k), + mod=self.readwrite_nmos, + offset=[right_readwrite_transistor_xpos,0], + rotate=90) + self.connect_inst(["Q_bar", "rwwl{}".format(k), "rwbl_bar{}".format(k), "gnd"]) + + """ Add RWWL lines """ + # calculate RWWL position + rwwl_ypos = self.gnd_position.y - (k+1)*self.rowline_tile_height + self.rwwl_positions[k] = vector(self.leftmost_xpos, rwwl_ypos) + + # add pin for RWWL + self.add_layout_pin(text="rwwl{}".format(k), + layer="metal1", + offset=self.rwwl_positions[k], + width=self.width, + height=contact.m1m2.width) + + """ Source/RWBL/RWBL_bar connections """ + # add metal1-to-metal2 contacts on top of read/write transistor source pins for connection to WBL and WBL_bar + offset_left = self.readwrite_nmos_left[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_left, + rotate=90) + + offset_right = self.readwrite_nmos_right[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_right, + rotate=90) + + # add pins for RWBL and RWBL_bar, overlaid on source contacts + self.rwbl_positions[k] = vector(self.readwrite_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="rwbl{}".format(k), + layer="metal2", + offset=self.rwbl_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + self.rwbl_bar_positions[k] = vector(self.readwrite_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="rwbl_bar{}".format(k), + layer="metal2", + offset=self.rwbl_bar_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + """ Gate/RWWL connections """ + # add poly-to-meltal2 contacts to connect gate of read/write transistors to RWWL (contact next to gate) + # contact must be placed a metal1 width below the source pin to avoid drc from source pin routings + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.readwrite_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = left_readwrite_transistor_xpos - self.readwrite_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width + contact_ypos = self.readwrite_nmos_left[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + left_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_gate_contact) + + if(self.readwrite_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.readwrite_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = right_readwrite_transistor_xpos + drc["poly_to_active"] + 0.5*contact.poly.width + contact_ypos = self.readwrite_nmos_right[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + right_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_gate_contact) + + # connect gate of read/write transistor to contact (poly path) + midL = vector(left_gate_contact.x, self.readwrite_nmos_left[k].get_pin("G").lc().y) + self.add_path("poly", [self.readwrite_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact], width=contact.poly.width) + + midR = vector(right_gate_contact.x, self.readwrite_nmos_right[k].get_pin("G").rc().y) + self.add_path("poly", [self.readwrite_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact], width=contact.poly.width) + + # add metal1-to-metal2 contacts to RWWL lines + left_rwwl_contact = vector(left_gate_contact.x, self.rwwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_rwwl_contact, + rotate=90) + + right_rwwl_contact = vector(right_gate_contact.x, self.rwwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_rwwl_contact, + rotate=90) + + # connect read/write transistor gate contacts to RWWL contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_rwwl_contact]) + self.add_path("metal2", [right_gate_contact, right_rwwl_contact]) + + """ Drain/Storage connections """ + # this path only needs to be drawn once on the last iteration of the loop + if(k == self.num_readwrite-1): + # add contacts to connect gate of inverters to drain of read/write transistors + left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_storage_contact, + rotate=90) + + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_storage_contact, + rotate=90) + + # connect gate of inverters to contacts (poly path) + inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) + + inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [right_storage_contact, inverter_gate_offset_right]) + + # connect contacts to drains of read/write transistors (metal1 path) + midL0 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], left_storage_contact.y) + midL1 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], self.readwrite_nmos_left[k].get_pin("D").lc().y) + self.add_path("metal1", [left_storage_contact, midL0, midL1, self.readwrite_nmos_left[k].get_pin("D").lc()]) + + midR0 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], right_storage_contact.y) + midR1 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], self.readwrite_nmos_right[k].get_pin("D").rc().y) + self.add_path("metal1", [right_storage_contact, midR0, midR1, self.readwrite_nmos_right[k].get_pin("D").rc()]) + # end if + # end for + + """ update furthest left and right transistor edges """ + self.left_building_edge = left_readwrite_transistor_xpos - self.readwrite_nmos.active_height + self.right_building_edge = right_readwrite_transistor_xpos + + + def add_write_ports(self): + """ + Adds write ports to 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 WBL_bar to Q_bar. + """ + + """ Define variables relevant to write transistors """ + # define offset correction due to rotation of the ptx module + write_rotation_correct = self.write_nmos.active_height + + # define write transistor variables as empty arrays based on the number of write ports + self.write_nmos_left = [None] * self.num_write + self.write_nmos_right = [None] * self.num_write + self.wwl_positions = [None] * self.num_write + self.wbl_positions = [None] * self.num_write + self.wbl_bar_positions = [None] * self.num_write + + # iterate over the number of write ports + for k in range(0,self.num_write): + """ Add transistors """ + # calculate write transistor offsets + left_write_transistor_xpos = self.left_building_edge \ + - (not self.readwrite_port_flag)*self.inverter_to_write_spacing \ + - (self.readwrite_port_flag)*self.readwrite_to_readwrite_spacing \ + - self.write_nmos.active_height - k*self.write_tile_width \ + + write_rotation_correct + + right_write_transistor_xpos = self.right_building_edge \ + + (not self.readwrite_port_flag)*self.inverter_to_write_spacing \ + + (self.readwrite_port_flag)*self.readwrite_to_readwrite_spacing \ + + k*self.write_tile_width \ + + write_rotation_correct + + # add write transistors + self.write_nmos_left[k] = self.add_inst(name="write_nmos_left{}".format(k), + mod=self.write_nmos, + offset=[left_write_transistor_xpos,0], + rotate=90) + self.connect_inst(["Q", "wwl{}".format(k), "wbl{}".format(k), "gnd"]) + + self.write_nmos_right[k] = self.add_inst(name="write_nmos_right{}".format(k), + mod=self.write_nmos, + offset=[right_write_transistor_xpos,0], + rotate=90) + self.connect_inst(["Q_bar", "wwl{}".format(k), "wbl_bar{}".format(k), "gnd"]) + + """ Add WWL lines """ + # calculate WWL position + wwl_ypos = self.gnd_position.y \ + - self.num_readwrite*self.rowline_tile_height \ + - (k+1)*self.rowline_tile_height + self.wwl_positions[k] = vector(self.leftmost_xpos, wwl_ypos) + + # add pin for WWL + self.add_layout_pin(text="wwl{}".format(k), + layer="metal1", + offset=self.wwl_positions[k], + width=self.width, + height=contact.m1m2.width) + + """ Source/WBL/WBL_bar connections """ + # add metal1-to-metal2 contacts on top of write transistor source pins for connection to WBL and WBL_bar + offset_left = self.write_nmos_left[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_left, + rotate=90) + + offset_right = self.write_nmos_right[k].get_pin("S").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_right, + rotate=90) + + # add pins for WBL and WBL_bar, overlaid on source contacts + self.wbl_positions[k] = vector(self.write_nmos_left[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="wbl{}".format(k), + layer="metal2", + offset=self.wbl_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + self.wbl_bar_positions[k] = vector(self.write_nmos_right[k].get_pin("S").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="wbl_bar{}".format(k), + layer="metal2", + offset=self.wbl_bar_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + """ Gate/WWL connections """ + # add poly-to-meltal2 contacts to connect gate of write transistors to WWL (contact next to gate) + # contact must be placed a metal width below the source pin to avoid drc from source pin routings + if(self.write_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.write_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = left_write_transistor_xpos - self.write_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width + contact_ypos = self.write_nmos_left[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + left_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_gate_contact) + + if(self.write_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.write_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = right_write_transistor_xpos + drc["poly_to_active"] + 0.5*contact.poly.width + contact_ypos = self.write_nmos_right[k].get_pin("D").bc().y - drc["minwidth_metal1"] - 0.5*contact.m1m2.height + right_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_gate_contact) + + # connect gate of write transistor to contact (poly path) + midL = vector(left_gate_contact.x, self.write_nmos_left[k].get_pin("G").lc().y) + self.add_path("poly", [self.write_nmos_left[k].get_pin("G").lc(), midL, left_gate_contact], width=contact.poly.width) + + midR = vector(right_gate_contact.x, self.write_nmos_right[k].get_pin("G").rc().y) + self.add_path("poly", [self.write_nmos_right[k].get_pin("G").rc(), midR, right_gate_contact], width=contact.poly.width) + + # add metal1-to-metal2 contacts to WWL lines + left_wwl_contact = vector(left_gate_contact.x, self.wwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_wwl_contact, + rotate=90) + + right_wwl_contact = vector(right_gate_contact.x, self.wwl_positions[k].y + 0.5*contact.m1m2.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_wwl_contact, + rotate=90) + + # connect write transistor gate contacts to WWL contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_wwl_contact]) + self.add_path("metal2", [right_gate_contact, right_wwl_contact]) + + """ Drain/Storage connections """ + # this path only needs to be drawn once on the last iteration of the loop + if(k == self.num_write-1): + # add contacts to connect gate of inverters to drain of write transistors + left_storage_contact = vector(self.inverter_nmos_left.get_pin("G").lc().x - drc["poly_to_field_poly"] - 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_storage_contact, + rotate=90) + + right_storage_contact = vector(self.inverter_nmos_right.get_pin("G").rc().x + drc["poly_to_field_poly"] + 0.5*contact.poly.width, self.cross_couple_lower_ypos) + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_storage_contact, + rotate=90) + + # connect gate of inverters to contacts (poly path) + inverter_gate_offset_left = vector(self.inverter_nmos_left.get_pin("G").lc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [left_storage_contact, inverter_gate_offset_left]) + + inverter_gate_offset_right = vector(self.inverter_nmos_right.get_pin("G").rc().x, self.cross_couple_lower_ypos) + self.add_path("poly", [right_storage_contact, inverter_gate_offset_right]) + + # connect contacts to drains of write transistors (metal1 path) + midL0 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], left_storage_contact.y) + midL1 = vector(left_storage_contact.x - 0.5*contact.poly.height - 1.5*drc["minwidth_metal1"], self.write_nmos_left[k].get_pin("D").lc().y) + self.add_path("metal1", [left_storage_contact, midL0, midL1, self.write_nmos_left[k].get_pin("D").lc()]) + + midR0 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], right_storage_contact.y) + midR1 = vector(right_storage_contact.x + 0.5*contact.poly.height + 1.5*drc["minwidth_metal1"], self.write_nmos_right[k].get_pin("D").rc().y) + self.add_path("metal1", [right_storage_contact, midR0, midR1, self.write_nmos_right[k].get_pin("D").rc()]) + # end if + # end for + + """ update furthest left and right transistor edges """ + self.left_building_edge = left_write_transistor_xpos - self.write_nmos.active_height + self.right_building_edge = right_write_transistor_xpos + + + def add_read_ports(self): + """ + Adds read ports to 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 variables relevant to read transistors """ + # define offset correction due to rotation of the ptx module + read_rotation_correct = self.read_nmos.active_height + + # 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").ll() - self.read_nmos.get_pin("S").ll() + + # define read transistor variables as empty arrays based on the number of read ports + self.read_nmos_left = [None] * self.num_read + self.read_nmos_right = [None] * self.num_read + self.read_access_nmos_left = [None] * self.num_read + self.read_access_nmos_right = [None] * self.num_read + self.rwl_positions = [None] * self.num_read + self.rbl_positions = [None] * self.num_read + self.rbl_bar_positions = [None] * self.num_read + + # iterate over the number of read ports + for k in range(0,self.num_read): + """ Add transistors """ + # calculate transistor offsets + left_read_transistor_xpos = self.left_building_edge \ + - self.write_to_read_spacing \ + - self.read_nmos.active_height - k*self.read_tile_width \ + + read_rotation_correct + + right_read_transistor_xpos = self.right_building_edge \ + + self.write_to_read_spacing \ + + k*self.read_tile_width \ + + read_rotation_correct + + # add read-access transistors + self.read_access_nmos_left[k] = self.add_inst(name="read_access_nmos_left{}".format(k), + mod=self.read_nmos, + offset=[left_read_transistor_xpos,0], + rotate=90) + self.connect_inst(["RA_to_R_left{}".format(k), " Q_bar", "gnd", "gnd"]) + + self.read_access_nmos_right[k] = self.add_inst(name="read_access_nmos_right{}".format(k), + mod=self.read_nmos, + offset=[right_read_transistor_xpos,0], + rotate=90) + self.connect_inst(["RA_to_R_right{}".format(k), "Q", "gnd", "gnd"]) + + # add read transistors + self.read_nmos_left[k] = self.add_inst(name="read_nmos_left{}".format(k), + mod=self.read_nmos, + offset=[left_read_transistor_xpos,overlap_offset.x], + rotate=90) + self.connect_inst(["rbl{}".format(k), "rwl{}".format(k), "RA_to_R_left{}".format(k), "gnd"]) + + self.read_nmos_right[k] = self.add_inst(name="read_nmos_right{}".format(k), + mod=self.read_nmos, + offset=[right_read_transistor_xpos,overlap_offset.x], + rotate=90) + self.connect_inst(["rbl_bar{}".format(k), "rwl{}".format(k), "RA_to_R_right{}".format(k), "gnd"]) + + """ Add RWL lines """ + # calculate RWL position + rwl_ypos = self.gnd_position.y \ + - self.num_readwrite*self.rowline_tile_height \ + - self.num_write*self.rowline_tile_height \ + - (k+1)*self.rowline_tile_height + self.rwl_positions[k] = vector(self.leftmost_xpos, rwl_ypos) + + # add pin for RWL + self.add_layout_pin(text="rwl{}".format(k), + layer="metal1", + offset=self.rwl_positions[k], + width=self.width, + height=contact.m1m2.width) + + """ Drain of read transistor / RBL & RBL_bar connection """ + # add metal1-to-metal2 contacts on top of read transistor drain pins for connection to RBL and RBL_bar + offset_left = self.read_nmos_left[k].get_pin("D").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_left, + rotate=90) + + offset_right = self.read_nmos_right[k].get_pin("D").center() + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=offset_right, + rotate=90) + + # add pins for RBL and RBL_bar, overlaid on drain contacts + self.rbl_positions[k] = vector(self.read_nmos_left[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="rbl{}".format(k), + layer="metal2", + offset=self.rbl_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + self.rbl_bar_positions[k] = vector(self.read_nmos_right[k].get_pin("D").center().x - 0.5*drc["minwidth_metal2"], self.botmost_ypos) + self.add_layout_pin(text="rbl_bar{}".format(k), + layer="metal2", + offset=self.rbl_bar_positions[k], + width=drc["minwidth_metal2"], + height=self.height) + + """ Gate of read transistor / RWL connection """ + # add poly-to-meltal2 contacts to connect gate of read transistors to RWL (contact next to gate) + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_left[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = left_read_transistor_xpos - self.read_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width + contact_ypos = self.read_nmos_left[k].get_pin("G").lc().y + left_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_gate_contact) + + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_right[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = right_read_transistor_xpos + drc["poly_to_active"] + 0.5*contact.poly.width + contact_ypos = self.read_nmos_right[k].get_pin("G").rc().y + right_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_gate_contact) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_gate_contact) + + # connect gate of read transistor to contact (poly path) + self.add_path("poly", [self.read_nmos_left[k].get_pin("G").lc(), left_gate_contact]) + self.add_path("poly", [self.read_nmos_right[k].get_pin("G").rc(), right_gate_contact]) + + # add metal1-to-metal2 contacts to RWL lines + left_rwl_contact = vector(left_gate_contact.x, self.rwl_positions[k].y + 0.5*contact.poly.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=left_rwl_contact, + rotate=90) + + right_rwl_contact = vector(right_gate_contact.x, self.rwl_positions[k].y + 0.5*contact.poly.width) + self.add_contact_center(layers=("metal1", "via1", "metal2"), + offset=right_rwl_contact, + rotate=90) + + # connect read transistor gate contacts to RWL contacts (metal2 path) + self.add_path("metal2", [left_gate_contact, left_rwl_contact]) + self.add_path("metal2", [right_gate_contact, right_rwl_contact]) + + """ Source of read-access transistor / GND connection """ + # connect source of read-access transistor to GND (metal1 path) + gnd_offset_left = vector(self.read_access_nmos_left[k].get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.read_access_nmos_left[k].get_pin("S").bc(), gnd_offset_left]) + + gnd_offset_right = vector(self.read_access_nmos_right[k].get_pin("S").bc().x, self.gnd_position.y) + self.add_path("metal1", [self.read_access_nmos_right[k].get_pin("S").bc(), gnd_offset_right]) + + """ Gate of read-access transistor / storage connection """ + # add poly-to-metal1 contacts to connect gate of read-access transistors to output of inverters (contact next to gate) + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_left[k].get_pin("S").rc().x + drc["minwidth_metal2"] + 0.5*contact.m1m2.width + else: + contact_xpos = left_read_transistor_xpos + drc["poly_to_active"] + 0.5*contact.poly.width + contact_ypos = self.read_access_nmos_left[k].get_pin("G").rc().y + left_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=left_gate_contact) + + if(self.read_nmos_contact_extension > self.gate_contact_thres): + contact_xpos = self.read_nmos_right[k].get_pin("S").lc().x - drc["minwidth_metal2"] - 0.5*contact.m1m2.width + else: + contact_xpos = right_read_transistor_xpos - self.read_nmos.active_height - drc["poly_to_active"] - 0.5*contact.poly.width + contact_ypos = self.read_access_nmos_right[k].get_pin("G").lc().y + right_gate_contact = vector(contact_xpos, contact_ypos) + + self.add_contact_center(layers=("poly", "contact", "metal1"), + offset=right_gate_contact) + + # connect gate of read-access transistor to contact (poly path) + self.add_path("poly", [self.read_access_nmos_left[k].get_pin("G").rc(), left_gate_contact]) + self.add_path("poly", [self.read_access_nmos_right[k].get_pin("G").lc(), right_gate_contact]) + + # save the positions of the first gate contacts for use in later iterations + if(k == 0): + left_gate_contact0 = left_gate_contact + right_gate_contact0 = right_gate_contact + + # connect contact to output of inverters (metal1 path) + # mid0: metal1 path must route over the read transistors (above drain of read transistor) + # mid1: continue metal1 path horizontally until at first read access gate contact + # mid2: route up or down to be level with inverter output + # endpoint at drain/source of inverter + midL0 = vector(left_gate_contact.x, self.read_nmos_left[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midL1 = vector(left_gate_contact0.x, self.read_nmos_left[0].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midL2 = vector(left_gate_contact0.x, self.cross_couple_upper_ypos) + left_inverter_offset = vector(self.inverter_nmos_left.get_pin("D").center().x, self.cross_couple_upper_ypos) + self.add_path("metal1", [left_gate_contact, midL0, midL1, midL2, left_inverter_offset]) + + midR0 = vector(right_gate_contact.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midR1 = vector(right_gate_contact0.x, self.read_nmos_right[k].get_pin("D").uc().y + 1.5*drc["minwidth_metal1"]) + midR2 = vector(right_gate_contact0.x, self.cross_couple_upper_ypos) + right_inverter_offset = vector(self.inverter_nmos_right.get_pin("S").center().x, self.cross_couple_upper_ypos) + self.add_path("metal1", [right_gate_contact, midR0, midR1, midR2, right_inverter_offset]) + # end for + + + def extend_well(self): + """ + Connects wells between ptx modules to avoid drc spacing issues. + Since the pwell of the read ports rise higher than the nwell of the inverters, + the well connections must be done piecewise to avoid pwell and nwell overlap. + """ + + """ extend pwell to encompass entire nmos region of the cell up to the height of the inverter nmos well """ + offset = vector(self.leftmost_xpos, self.botmost_ypos) + well_height = -self.botmost_ypos + self.inverter_nmos.cell_well_height - drc["well_enclosure_active"] + self.add_rect(layer="pwell", + offset=offset, + width=self.width, + height=well_height) + + """ extend pwell over read/write and write transistors to the height of the write transistor well (read/write and write transistors are the same height) """ + if(self.num_write > 0): + # calculate the edge of the write transistor well closest to the center + left_write_well_xpos = self.write_nmos_left[0].offset.x + drc["well_enclosure_active"] + right_write_well_xpos = self.write_nmos_right[0].offset.x - self.write_nmos.active_height - drc["well_enclosure_active"] + else: + # calculate the edge of the read/write transistor well closest to the center + left_write_well_xpos = self.readwrite_nmos_left[0].offset.x + drc["well_enclosure_active"] + right_write_well_xpos = self.readwrite_nmos_right[0].offset.x - self.readwrite_nmos.active_height - drc["well_enclosure_active"] + + # calculate a width that will halt at the edge of the write transistors + write_well_width = -(self.leftmost_xpos - left_write_well_xpos) + write_well_height = self.write_nmos.cell_well_width - drc["well_enclosure_active"] + + offset = vector(left_write_well_xpos - write_well_width, 0) + self.add_rect(layer="pwell", + offset=offset, + width=write_well_width, + height=write_well_height) + + offset = vector(right_write_well_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=write_well_width, + height=write_well_height) + + """ extend pwell over the read transistors to the height of the bitcell """ + if(self.num_read > 0): + # calculate the edge of the read transistor well clostest to the center + left_read_well_xpos = self.read_nmos_left[0].offset.x + drc["well_enclosure_active"] + right_read_well_xpos = self.read_nmos_right[0].offset.x - self.read_nmos.active_height - drc["well_enclosure_active"] + + # calculate a width that will halt at the edge of the read transistors + read_well_width = -(self.leftmost_xpos - left_read_well_xpos) + read_well_height = self.topmost_ypos + + offset = vector(self.leftmost_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=read_well_width, + height=read_well_height) + + offset = vector(right_read_well_xpos, 0) + self.add_rect(layer="pwell", + offset=offset, + width=read_well_width, + height=read_well_height) + + """ extend nwell to encompass inverter_pmos """ + # calculate offset of the left pmos well + inverter_well_xpos = -self.inverter_tile_width - drc["well_enclosure_active"] + inverter_well_ypos = self.inverter_nmos.active_height + self.inverter_gap - drc["well_enclosure_active"] + + # calculate width of the two combined nwells + # calculate height to encompass nimplant connected to vdd + well_width = 2*self.inverter_tile_width + 2*drc["well_enclosure_active"] + well_height = self.vdd_position.y - inverter_well_ypos + drc["well_enclosure_active"] + drc["minwidth_tx"] + + offset = [inverter_well_xpos,inverter_well_ypos] + self.add_rect(layer="nwell", + offset=offset, + width=well_width, + height=well_height) + + + """ add well contacts """ + # connect pimplants to gnd + offset = vector(0, self.gnd_position.y + 0.5*contact.well.second_layer_width) + self.add_contact_center(layers=("active", "contact", "metal1"), + offset=offset, + rotate=90) + + self.add_rect_center(layer="pimplant", + offset=offset, + width=drc["minwidth_tx"], + height=drc["minwidth_tx"]) + + # connect nimplants to vdd + offset = vector(0, self.vdd_position.y + 0.5*drc["minwidth_metal1"]) + self.add_contact_center(layers=("active", "contact", "metal1"), + offset=offset, + rotate=90) + + self.add_rect_center(layer="nimplant", + offset=offset, + width=drc["minwidth_tx"], + height=drc["minwidth_tx"]) + + + def list_bitcell_pins(self, col, row): + """ Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """ + bitcell_pins = [] + for k in range(self.num_readwrite): + bitcell_pins.append("rwbl{0}[{1}]".format(k,col)) + bitcell_pins.append("rwbl_bar{0}[{1}]".format(k,col)) + for k in range(self.num_write): + bitcell_pins.append("wbl{0}[{1}]".format(k,col)) + bitcell_pins.append("wbl_bar{0}[{1}]".format(k,col)) + for k in range(self.num_read): + bitcell_pins.append("rbl{0}[{1}]".format(k,col)) + bitcell_pins.append("rbl_bar{0}[{1}]".format(k,col)) + for k in range(self.num_readwrite): + bitcell_pins.append("rwwl{0}[{1}]".format(k,row)) + for k in range(self.num_write): + bitcell_pins.append("wwl{0}[{1}]".format(k,row)) + for k in range(self.num_read): + bitcell_pins.append("rwl{0}[{1}]".format(k,row)) + bitcell_pins.append("vdd") + bitcell_pins.append("gnd") + + return bitcell_pins + + + def list_row_pins(self): + """ Creates a list of all row pins (except for gnd and vdd) """ + row_pins = [] + for k in range(self.num_readwrite): + row_pins.append("rwwl{0}".format(k)) + for k in range(self.num_write): + row_pins.append("wwl{0}".format(k)) + for k in range(self.num_read): + row_pins.append("rwl{0}".format(k)) + + return row_pins + + def list_read_row_pins(self): + """ Creates a list of row pins associated with read ports """ + row_pins = [] + for k in range(self.num_readwrite): + row_pins.append("rwwl{0}".format(k)) + for k in range(self.num_read): + row_pins.append("rwl{0}".format(k)) + + return row_pins + + def list_write_row_pins(self): + """ Creates a list of row pins associated with write ports """ + row_pins = [] + for k in range(self.num_readwrite): + row_pins.append("rwwl{0}".format(k)) + for k in range(self.num_write): + row_pins.append("wwl{0}".format(k)) + + return row_pins + + + def list_column_pins(self): + """ Creates a list of all column pins """ + column_pins = [] + for k in range(self.num_readwrite): + column_pins.append("rwbl{0}".format(k)) + column_pins.append("rwbl_bar{0}".format(k)) + for k in range(self.num_write): + column_pins.append("wbl{0}".format(k)) + column_pins.append("wbl_bar{0}".format(k)) + for k in range(self.num_read): + column_pins.append("rbl{0}".format(k)) + column_pins.append("rbl_bar{0}".format(k)) + + return column_pins + + def list_read_column_pins(self): + """ Creates a list of column pins associated with read ports """ + column_pins = [] + for k in range(self.num_readwrite): + column_pins.append("rwbl{0}".format(k)) + for k in range(self.num_read): + column_pins.append("rbl{0}".format(k)) + + return column_pins + + def list_read_bar_column_pins(self): + """ Creates a list of column pins associated with read_bar ports """ + column_pins = [] + for k in range(self.num_readwrite): + column_pins.append("rwbl_bar{0}".format(k)) + for k in range(self.num_read): + column_pins.append("rbl_bar{0}".format(k)) + + return column_pins + + def list_write_column_pins(self): + """ Creates a list of column pins associated with write ports """ + column_pins = [] + for k in range(self.num_readwrite): + column_pins.append("rwbl{0}".format(k)) + for k in range(self.num_write): + column_pins.append("wbl{0}".format(k)) + + return column_pins + + def list_write_bar_column_pins(self): + """ Creates a list of column pins asscociated with write_bar ports""" + column_pins = [] + for k in range(self.num_readwrite): + column_pins.append("rwbl_bar{0}".format(k)) + for k in range(self.num_write): + column_pins.append("wbl_bar{0}".format(k)) + + return column_pins diff --git a/compiler/pgates/precharge.py b/compiler/pgates/precharge.py index f1808c08..e474be92 100644 --- a/compiler/pgates/precharge.py +++ b/compiler/pgates/precharge.py @@ -82,12 +82,12 @@ class precharge(pgate.pgate): """Adds both the upper_pmos and lower_pmos to the module""" # adds the lower pmos to layout #base = vector(self.width - 2*self.pmos.width + self.overlap_offset.x, 0) - self.lower_pmos_position = vector(self.bitcell.get_pin("BL").lx(), + self.lower_pmos_position = vector(self.bitcell.get_pin("bl").lx(), self.pmos.active_offset.y) self.lower_pmos_inst=self.add_inst(name="lower_pmos", mod=self.pmos, offset=self.lower_pmos_position) - self.connect_inst(["bl", "en", "BR", "vdd"]) + self.connect_inst(["bl", "en", "br", "vdd"]) # adds the upper pmos(s) to layout ydiff = self.pmos.height + 2*self.m1_space + contact.poly.width @@ -159,7 +159,7 @@ class precharge(pgate.pgate): def add_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("BL").cx(),0) - vector(0.5 * self.m2_width,0) + offset = vector(self.bitcell.get_pin("bl").cx(),0) - vector(0.5 * self.m2_width,0) self.add_layout_pin(text="bl", layer="metal2", offset=offset, @@ -167,7 +167,7 @@ class precharge(pgate.pgate): height=self.height) # adds the BR on metal 2 - offset = vector(self.bitcell.get_pin("BR").cx(),0) - vector(0.5 * self.m2_width,0) + offset = vector(self.bitcell.get_pin("br").cx(),0) - vector(0.5 * self.m2_width,0) self.add_layout_pin(text="br", layer="metal2", offset=offset, diff --git a/compiler/pgates/single_level_column_mux.py b/compiler/pgates/single_level_column_mux.py index 9cb59840..6b7d7988 100644 --- a/compiler/pgates/single_level_column_mux.py +++ b/compiler/pgates/single_level_column_mux.py @@ -31,7 +31,7 @@ class single_level_column_mux(design.design): self.add_ptx() self.pin_height = 2*self.m2_width self.width = self.bitcell.width - self.height = self.nmos2.uy() + self.pin_height + self.height = self.nmos_upper.uy() + self.pin_height self.connect_poly() self.add_bitline_pins() self.connect_bitlines() @@ -40,8 +40,8 @@ class single_level_column_mux(design.design): def add_bitline_pins(self): """ Add the top and bottom pins to this cell """ - bl_pos = vector(self.bitcell.get_pin("BL").lx(), 0) - br_pos = vector(self.bitcell.get_pin("BR").lx(), 0) + bl_pos = vector(self.bitcell.get_pin("bl").lx(), 0) + br_pos = vector(self.bitcell.get_pin("br").lx(), 0) # bl and br self.add_layout_pin(text="bl", @@ -67,32 +67,32 @@ class single_level_column_mux(design.design): def add_ptx(self): """ Create the two pass gate NMOS transistors to switch the bitlines""" - # Adds nmos1,nmos2 to the module + # Adds nmos_lower,nmos_upper to the module self.nmos = ptx(width=self.ptx_width) self.add_mod(self.nmos) # Space it in the center - nmos1_position = self.nmos.active_offset.scale(0,1) + vector(0.5*self.bitcell.width-0.5*self.nmos.active_width,0) - self.nmos1=self.add_inst(name="mux_tx1", + 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=nmos1_position) + offset=nmos_lower_position) self.connect_inst(["bl", "sel", "bl_out", "gnd"]) # This aligns it directly above the other tx with gates abutting - nmos2_position = nmos1_position + vector(0,self.nmos.active_height + self.poly_space) - self.nmos2=self.add_inst(name="mux_tx2", + 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=nmos2_position) + 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.nmos2.get_pin("G").uy() - self.nmos1.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.nmos1.get_pin("G").ll(), + offset=self.nmos_lower.get_pin("G").ll(), height=height) @@ -105,36 +105,36 @@ class single_level_column_mux(design.design): br_out_pin = self.get_pin("br_out") # These are on metal1 - nmos1_s_pin = self.nmos1.get_pin("S") - nmos1_d_pin = self.nmos1.get_pin("D") - nmos2_s_pin = self.nmos2.get_pin("S") - nmos2_d_pin = self.nmos2.get_pin("D") + nmos_lower_s_pin = self.nmos_lower.get_pin("S") + nmos_lower_d_pin = self.nmos_lower.get_pin("D") + nmos_upper_s_pin = self.nmos_upper.get_pin("S") + nmos_upper_d_pin = self.nmos_upper.get_pin("D") - # Add vias to bl, br_out, nmos2/S, nmos1/D + # Add vias to bl, br_out, nmos_upper/S, nmos_lower/D self.add_via_center(layers=("metal1","via1","metal2"), offset=bl_pin.bc()) self.add_via_center(layers=("metal1","via1","metal2"), offset=br_out_pin.uc()) self.add_via_center(layers=("metal1","via1","metal2"), - offset=nmos2_s_pin.center()) + offset=nmos_upper_s_pin.center()) self.add_via_center(layers=("metal1","via1","metal2"), - offset=nmos1_d_pin.center()) + offset=nmos_lower_d_pin.center()) - # bl -> nmos2/D on metal1 - # bl_out -> nmos2/S on metal2 - self.add_path("metal1",[bl_pin.ll(), vector(nmos2_d_pin.cx(),bl_pin.by()), nmos2_d_pin.center()]) + # 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()]) # halfway up, move over - mid1 = bl_out_pin.uc().scale(1,0.5)+nmos2_s_pin.bc().scale(0,0.5) - mid2 = bl_out_pin.uc().scale(0,0.5)+nmos2_s_pin.bc().scale(1,0.5) - self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos2_s_pin.bc()]) + mid1 = bl_out_pin.uc().scale(1,0.5)+nmos_upper_s_pin.bc().scale(0,0.5) + mid2 = bl_out_pin.uc().scale(0,0.5)+nmos_upper_s_pin.bc().scale(1,0.5) + self.add_path("metal2",[bl_out_pin.uc(), mid1, mid2, nmos_upper_s_pin.bc()]) - # br -> nmos1/D on metal2 - # br_out -> nmos1/S on metal1 - self.add_path("metal1",[br_out_pin.uc(), vector(nmos1_s_pin.cx(),br_out_pin.uy()), nmos1_s_pin.center()]) + # 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()]) # halfway up, move over - mid1 = br_pin.bc().scale(1,0.5)+nmos1_d_pin.uc().scale(0,0.5) - mid2 = br_pin.bc().scale(0,0.5)+nmos1_d_pin.uc().scale(1,0.5) - self.add_path("metal2",[br_pin.bc(), mid1, mid2, nmos1_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): @@ -144,11 +144,11 @@ class single_level_column_mux(design.design): """ # Add it to the right, aligned in between the two tx - active_pos = self.nmos2.lr().scale(0,0.5) + self.nmos1.ur().scale(1,0.5) - 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()) + active_via = self.add_via_center(layers=("active", "contact", "metal1"), + offset=active_pos, + implant_type="p", + well_type="p") # Add the M1->M2->M3 stack @@ -159,6 +159,12 @@ class single_level_column_mux(design.design): self.add_layout_pin_rect_center(text="gnd", layer="metal3", offset=active_pos) + + # Add well enclosure over all the tx and contact + self.add_rect(layer="pwell", + offset=vector(0,0), + width=self.bitcell.width, + height=self.height) diff --git a/compiler/tests/#03_ptx_4finger_pmos_test.py# b/compiler/tests/#03_ptx_4finger_pmos_test.py# deleted file mode 100644 index 43931cc3..00000000 --- a/compiler/tests/#03_ptx_4finger_pmos_test.py# +++ /dev/null @@ -1,154 +0,0 @@ -#!/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 -from globals import OPTS -import debug - -class ptx_test(unittest.TestCase): - - def runTest(self): - globals.init_openram("config_20_{0}".format(OPTS.tech_name)) - global verify - import verify - 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() diff --git a/compiler/tests/00_code_format_check_test.py b/compiler/tests/00_code_format_check_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/01_library_drc_test.py b/compiler/tests/01_library_drc_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/02_library_lvs_test.py b/compiler/tests/02_library_lvs_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_contact_test.py b/compiler/tests/03_contact_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_path_test.py b/compiler/tests/03_path_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_ptx_1finger_nmos_test.py b/compiler/tests/03_ptx_1finger_nmos_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_ptx_1finger_pmos_test.py b/compiler/tests/03_ptx_1finger_pmos_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_ptx_3finger_nmos_test.py b/compiler/tests/03_ptx_3finger_nmos_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_ptx_3finger_pmos_test.py b/compiler/tests/03_ptx_3finger_pmos_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_ptx_4finger_nmos_test.py b/compiler/tests/03_ptx_4finger_nmos_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_ptx_4finger_pmos_test.py b/compiler/tests/03_ptx_4finger_pmos_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/03_wire_test.py b/compiler/tests/03_wire_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pbitcell_test.py b/compiler/tests/04_pbitcell_test.py new file mode 100755 index 00000000..8a684839 --- /dev/null +++ b/compiler/tests/04_pbitcell_test.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python2.7 +""" +Run regresion tests on a parameterized bitcell +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +OPTS = globals.OPTS + +#@unittest.skip("SKIPPING 04_pbitcell_test") + + +class pbitcell_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + global verify + import verify + OPTS.check_lvsdrc = False + + import pbitcell + import tech + + debug.info(2, "Bitcell with 1 of each port: read/write, write, and read") + tx = pbitcell.pbitcell(num_readwrite=1,num_write=1,num_read=1) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 read/write ports") + tx = pbitcell.pbitcell(num_readwrite=0,num_write=1,num_read=1) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 write ports") + tx = pbitcell.pbitcell(num_readwrite=1,num_write=0,num_read=1) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 read ports") + tx = pbitcell.pbitcell(num_readwrite=1,num_write=1,num_read=0) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 read ports and 0 write ports") + tx = pbitcell.pbitcell(num_readwrite=1,num_write=0,num_read=0) + self.local_check(tx) + + debug.info(2, "Bitcell with 2 of each port: read/write, write, and read") + tx = pbitcell.pbitcell(num_readwrite=2,num_write=2,num_read=2) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 read/write ports") + tx = pbitcell.pbitcell(num_readwrite=0,num_write=2,num_read=2) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 write ports") + tx = pbitcell.pbitcell(num_readwrite=2,num_write=0,num_read=2) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 read ports") + tx = pbitcell.pbitcell(num_readwrite=2,num_write=2,num_read=0) + self.local_check(tx) + + debug.info(2, "Bitcell with 0 read ports and 0 write ports") + tx = pbitcell.pbitcell(num_readwrite=2,num_write=0,num_read=0) + self.local_check(tx) + + OPTS.check_lvsdrc = True + globals.end_openram() + OPTS.bitcell = "bitcell" + + +# 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/04_pinv_10x_test.py b/compiler/tests/04_pinv_10x_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pinv_1x_beta_test.py b/compiler/tests/04_pinv_1x_beta_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pinv_1x_test.py b/compiler/tests/04_pinv_1x_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pinv_2x_test.py b/compiler/tests/04_pinv_2x_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pinvbuf_test.py b/compiler/tests/04_pinvbuf_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pnand2_test.py b/compiler/tests/04_pnand2_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pnand3_test.py b/compiler/tests/04_pnand3_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_pnor2_test.py b/compiler/tests/04_pnor2_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_precharge_test.py b/compiler/tests/04_precharge_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/04_single_level_column_mux_test.py b/compiler/tests/04_single_level_column_mux_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/05_bitcell_array_test.py b/compiler/tests/05_bitcell_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/05_pbitcell_array_test.py b/compiler/tests/05_pbitcell_array_test.py new file mode 100755 index 00000000..2dfed383 --- /dev/null +++ b/compiler/tests/05_pbitcell_array_test.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +Run a regression test on a basic array +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +#@unittest.skip("SKIPPING 05_array_multiport_test") + +class array_multiport_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + global verify + import verify + OPTS.check_lvsdrc = False + + import bitcell_array + + OPTS.bitcell = "pbitcell" + OPTS.rw_ports = 2 + OPTS.r_ports = 2 + OPTS.w_ports = 2 + + debug.info(2, "Testing 4x4 array for multiport bitcell, with read ports at the edge of the bit cell") + a = bitcell_array.bitcell_array(name="pbitcell_array", cols=4, rows=4) + self.local_check(a) + + OPTS.rw_ports = 2 + OPTS.r_ports = 0 + OPTS.w_ports = 2 + + debug.info(2, "Testing 4x4 array for multiport bitcell, with read/write ports at the edge of the bit cell") + a = bitcell_array.bitcell_array(name="pbitcell_array", cols=4, rows=4) + self.local_check(a) + + + OPTS.bitcell = "bitcell" + OPTS.check_lvsdrc = True + globals.end_openram() + +# 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/06_hierarchical_decoder_test.py b/compiler/tests/06_hierarchical_decoder_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/06_hierarchical_predecode2x4_test.py b/compiler/tests/06_hierarchical_predecode2x4_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/06_hierarchical_predecode3x8_test.py b/compiler/tests/06_hierarchical_predecode3x8_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/07_single_level_column_mux_array_test.py b/compiler/tests/07_single_level_column_mux_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/08_precharge_array_test.py b/compiler/tests/08_precharge_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/08_wordline_driver_test.py b/compiler/tests/08_wordline_driver_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/09_sense_amp_array_test.py b/compiler/tests/09_sense_amp_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/10_write_driver_array_test.py b/compiler/tests/10_write_driver_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/11_dff_array_test.py b/compiler/tests/11_dff_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/11_dff_buf_array_test.py b/compiler/tests/11_dff_buf_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/11_dff_buf_test.py b/compiler/tests/11_dff_buf_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/11_dff_inv_array_test.py b/compiler/tests/11_dff_inv_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/11_dff_inv_test.py b/compiler/tests/11_dff_inv_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/11_ms_flop_array_test.py b/compiler/tests/11_ms_flop_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/12_tri_gate_array_test.py b/compiler/tests/12_tri_gate_array_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/13_delay_chain_test.py b/compiler/tests/13_delay_chain_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/14_replica_bitline_test.py b/compiler/tests/14_replica_bitline_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/16_control_logic_test.py b/compiler/tests/16_control_logic_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/19_bank_select_test.py b/compiler/tests/19_bank_select_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/19_multi_bank_test.py b/compiler/tests/19_multi_bank_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/19_single_bank_test.py b/compiler/tests/19_single_bank_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/20_sram_1bank_test.py b/compiler/tests/20_sram_1bank_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/20_sram_2bank_test.py b/compiler/tests/20_sram_2bank_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/20_sram_4bank_test.py b/compiler/tests/20_sram_4bank_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py old mode 100644 new mode 100755 index 731c0d90..9ac3db2e --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -45,7 +45,7 @@ class timing_sram_test(openram_test): debug.info(1, "Probe address {0} probe data {1}".format(probe_address, probe_data)) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - d = delay.delay(s,tempspice,corner) + d = delay(s,tempspice,corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] diff --git a/compiler/tests/21_hspice_setuphold_test.py b/compiler/tests/21_hspice_setuphold_test.py old mode 100644 new mode 100755 index 4d469ac3..78403a56 --- a/compiler/tests/21_hspice_setuphold_test.py +++ b/compiler/tests/21_hspice_setuphold_test.py @@ -33,7 +33,7 @@ class timing_setup_test(openram_test): slews = [tech.spice["rise_time"]*2] corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - sh = setup_hold.setup_hold(corner) + sh = setup_hold(corner) data = sh.analyze(slews,slews) #print data if OPTS.tech_name == "freepdk45": diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py old mode 100644 new mode 100755 index 6ebf4dd2..7f7bfd39 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -43,7 +43,7 @@ class timing_sram_test(openram_test): debug.info(1, "Probe address {0} probe data {1}".format(probe_address, probe_data)) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - d = delay.delay(s,tempspice,corner) + d = delay(s,tempspice,corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] diff --git a/compiler/tests/21_ngspice_setuphold_test.py b/compiler/tests/21_ngspice_setuphold_test.py old mode 100644 new mode 100755 index 9bf0fd4c..9a8ff67c --- a/compiler/tests/21_ngspice_setuphold_test.py +++ b/compiler/tests/21_ngspice_setuphold_test.py @@ -32,7 +32,7 @@ class timing_setup_test(openram_test): slews = [tech.spice["rise_time"]*2] corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) - sh = setup_hold.setup_hold(corner) + sh = setup_hold(corner) data = sh.analyze(slews,slews) #print data if OPTS.tech_name == "freepdk45": diff --git a/compiler/tests/22_pex_func_test_with_pinv.py b/compiler/tests/22_pex_test.py similarity index 100% rename from compiler/tests/22_pex_func_test_with_pinv.py rename to compiler/tests/22_pex_test.py diff --git a/compiler/tests/22_sram_func_test.py b/compiler/tests/22_sram_func_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/23_lib_sram_model_test.py b/compiler/tests/23_lib_sram_model_test.py old mode 100644 new mode 100755 index 86ab2124..9eb8ac5b --- a/compiler/tests/23_lib_sram_model_test.py +++ b/compiler/tests/23_lib_sram_model_test.py @@ -30,7 +30,7 @@ class lib_test(openram_test): tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) - lib.lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=True) + lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=True) # get all of the .lib files generated files = os.listdir(OPTS.openram_temp) diff --git a/compiler/tests/23_lib_sram_prune_test.py b/compiler/tests/23_lib_sram_prune_test.py old mode 100644 new mode 100755 index 9d26c1ba..4227bc44 --- a/compiler/tests/23_lib_sram_prune_test.py +++ b/compiler/tests/23_lib_sram_prune_test.py @@ -40,7 +40,7 @@ class lib_test(openram_test): tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) - lib.lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=False) + lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=False) # get all of the .lib files generated files = os.listdir(OPTS.openram_temp) diff --git a/compiler/tests/23_lib_sram_test.py b/compiler/tests/23_lib_sram_test.py old mode 100644 new mode 100755 index 0bbd63c4..46750250 --- a/compiler/tests/23_lib_sram_test.py +++ b/compiler/tests/23_lib_sram_test.py @@ -40,7 +40,7 @@ class lib_test(openram_test): tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice) - lib.lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=False) + lib(out_dir=OPTS.openram_temp, sram=s, sp_file=tempspice, use_model=False) # get all of the .lib files generated files = os.listdir(OPTS.openram_temp) diff --git a/compiler/tests/24_lef_sram_test.py b/compiler/tests/24_lef_sram_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/25_verilog_sram_test.py b/compiler/tests/25_verilog_sram_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/30_openram_test.py b/compiler/tests/30_openram_test.py old mode 100644 new mode 100755 diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index 3948bf38..caef7211 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -91,25 +91,25 @@ class openram_test(unittest.TestCase): import re import debug + numeric_const_pattern = r""" + [-+]? # optional sign + (?: + (?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc + | + (?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc + ) + # followed by optional exponent part if desired + (?: [Ee] [+-]? \d+ ) ? + """ + rx = re.compile(numeric_const_pattern, re.VERBOSE) with open(f1, 'rb') as fp1, open(f2, 'rb') as fp2: while True: - b1 = fp1.readline() - b2 = fp2.readline() + b1 = fp1.readline().decode('utf-8') + b2 = fp2.readline().decode('utf-8') #print "b1:",b1, #print "b2:",b2, # 1. Find all of the floats using a regex - numeric_const_pattern = r""" - [-+]? # optional sign - (?: - (?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc - | - (?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc - ) - # followed by optional exponent part if desired - (?: [Ee] [+-]? \d+ ) ? - """ - rx = re.compile(numeric_const_pattern, re.VERBOSE) b1_floats=rx.findall(b1) b2_floats=rx.findall(b2) debug.info(3,"b1_floats: "+str(b1_floats)) @@ -117,9 +117,9 @@ class openram_test(unittest.TestCase): # 2. Remove the floats from the string for f in b1_floats: - b1=b1.replace(str(f),"",1) + b1=b1.replace(f,"",1) for f in b2_floats: - b2=b2.replace(str(f),"",1) + b2=b2.replace(f,"",1) #print "b1:",b1, #print "b2:",b2, diff --git a/compiler/verify/calibre.py b/compiler/verify/calibre.py index e947c3ad..38e92a2c 100644 --- a/compiler/verify/calibre.py +++ b/compiler/verify/calibre.py @@ -89,7 +89,7 @@ def run_drc(cell_name, gds_name): # write the runset file f = open(OPTS.openram_temp + "drc_runset", "w") - for k in sorted(drc_runset.iterkeys()): + for k in sorted(iter(drc_runset.keys())): f.write("*{0}: {1}\n".format(k, drc_runset[k])) f.close() @@ -177,7 +177,7 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False): # write the runset file f = open(OPTS.openram_temp + "lvs_runset", "w") - for k in sorted(lvs_runset.iterkeys()): + for k in sorted(iter(lvs_runset.keys())): f.write("*{0}: {1}\n".format(k, lvs_runset[k])) f.close() @@ -286,7 +286,7 @@ def run_pex(cell_name, gds_name, sp_name, output=None): # write the runset file f = open(OPTS.openram_temp + "pex_runset", "w") - for k in sorted(pex_runset.iterkeys()): + for k in sorted(iter(pex_runset.keys())): f.write("*{0}: {1}\n".format(k, pex_runset[k])) f.close() diff --git a/technology/freepdk45/gds_lib/cell_6t.gds b/technology/freepdk45/gds_lib/cell_6t.gds index e48beb23..bde8742a 100644 Binary files a/technology/freepdk45/gds_lib/cell_6t.gds and b/technology/freepdk45/gds_lib/cell_6t.gds differ diff --git a/technology/scn3me_subm/gds_lib/cell_6t.gds b/technology/scn3me_subm/gds_lib/cell_6t.gds index c6fdb0e8..ec926456 100644 Binary files a/technology/scn3me_subm/gds_lib/cell_6t.gds and b/technology/scn3me_subm/gds_lib/cell_6t.gds differ