diff --git a/.gitattributes b/.gitattributes index 2e6daefb..23fcc05d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ -*.sp linguist-vendored +*.sp linguist-language=Spice +*.tf linquist-language=Tech File diff --git a/README.md b/README.md index e459644a..48b15766 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,9 @@ +![](./images/OpenRAM_logo_yellow_transparent.svg) # OpenRAM [![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/) [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) - -Master: -[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip) - -Dev: +[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/stable.zip) [![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip) An open-source static random access memory (SRAM) compiler. @@ -32,10 +29,9 @@ things that need to be fixed. ## Dependencies The OpenRAM compiler has very few dependencies: -+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) ++ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later) or [Xyce] 7.2 (or later) + Python 3.5 or higher -+ Python numpy (pip3 install numpy to install) -+ Python scipy (pip3 install scipy to install) ++ Various Python packages (pip install -r requirements.txt) If you want to perform DRC and LVS, you will need either: + Calibre (for [FreePDK45]) @@ -218,6 +214,7 @@ If I forgot to add you, please let me know! [Netgen]: http://opencircuitdesign.com/netgen/ [Qflow]: http://opencircuitdesign.com/qflow/history.html [Ngspice]: http://ngspice.sourceforge.net/ +[Xyce]: http://xyce.sandia.gov/ [OSUPDK]: https://vlsiarch.ecen.okstate.edu/flow/ [FreePDK45]: https://www.eda.ncsu.edu/wiki/FreePDK45:Contents diff --git a/compiler/base/custom_layer_properties.py b/compiler/base/custom_layer_properties.py index 7f8e5993..eff24f82 100644 --- a/compiler/base/custom_layer_properties.py +++ b/compiler/base/custom_layer_properties.py @@ -123,6 +123,14 @@ class _wordline_driver: self.vertical_supply = vertical_supply +class _bitcell_array: + def __init__(self, + wordline_layer, + wordline_pitch_factor=2): + self.wordline_layer = wordline_layer + self.wordline_pitch_factor = wordline_pitch_factor + + class layer_properties(): """ This contains meta information about the module routing layers. These @@ -159,6 +167,10 @@ class layer_properties(): self._wordline_driver = _wordline_driver(vertical_supply=False) + self._local_bitcell_array = _bitcell_array(wordline_layer="m2") + + self._global_bitcell_array = _bitcell_array(wordline_layer="m3") + @property def bank(self): return self._bank @@ -191,3 +203,11 @@ class layer_properties(): def wordline_driver(self): return self._wordline_driver + @property + def global_bitcell_array(self): + return self._global_bitcell_array + + @property + def local_bitcell_array(self): + return self._local_bitcell_array + diff --git a/compiler/base/hierarchy_design.py b/compiler/base/hierarchy_design.py index d99c7363..fe295273 100644 --- a/compiler/base/hierarchy_design.py +++ b/compiler/base/hierarchy_design.py @@ -132,7 +132,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): for subinst, conns in zip(self.insts, self.conns): if subinst in self.graph_inst_exclude: continue - subinst_name = inst_name + '.X' + subinst.name + subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name subinst_ports = self.translate_nets(conns, port_dict, inst_name) subinst.mod.build_graph(graph, subinst_name, subinst_ports) @@ -148,7 +148,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): port_dict = {pin: port for pin, port in zip(self.pins, port_nets)} debug.info(3, "Instance name={}".format(inst_name)) for subinst, conns in zip(self.insts, self.conns): - subinst_name = inst_name + '.X' + subinst.name + subinst_name = inst_name + "{}x".format(OPTS.hier_seperator) + subinst.name subinst_ports = self.translate_nets(conns, port_dict, inst_name) for si_port, conn in zip(subinst_ports, conns): # Only add for first occurrence @@ -166,7 +166,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout): if conn in port_dict: converted_conns.append(port_dict[conn]) else: - converted_conns.append("{}.{}".format(inst_name, conn)) + converted_conns.append("{0}{2}{1}".format(inst_name, conn, OPTS.hier_seperator)) return converted_conns def add_graph_edges(self, graph, port_nets): diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 36a54937..1e2add8d 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -674,7 +674,8 @@ class layout(): directions=None, size=[1, 1], implant_type=None, - well_type=None): + well_type=None, + min_area=False): """ Punch a stack of vias from a start layer to a target layer by the center. """ @@ -708,7 +709,7 @@ class layout(): implant_type=implant_type, well_type=well_type) - if cur_layer != from_layer: + if cur_layer != from_layer or min_area: self.add_min_area_rect_center(cur_layer, offset, via.mod.first_layer_width, diff --git a/compiler/base/lef.py b/compiler/base/lef.py index a5c1910a..9db18ab1 100644 --- a/compiler/base/lef.py +++ b/compiler/base/lef.py @@ -10,6 +10,8 @@ from tech import layer_names import os import shutil from globals import OPTS +from vector import vector +from pin_layout import pin_layout class lef: @@ -67,23 +69,66 @@ class lef: def lef_write(self, lef_name): """ Write the entire lef of the object to the file. """ + # Can possibly use magic lef write to create the LEF + # if OPTS.drc_exe and OPTS.drc_exe[0] == "magic": + # self.magic_lef_write(lef_name) + # return - if OPTS.drc_exe and OPTS.drc_exe[0] == "magic": - self.magic_lef_write(lef_name) - return + # To maintain the indent level easily + self.indent = "" - debug.info(3, "Writing detailed LEF to {0}".format(lef_name)) - - self.indent = "" # To maintain the indent level easily + if OPTS.detailed_lef: + debug.info(3, "Writing detailed LEF to {0}".format(lef_name)) + else: + debug.info(3, "Writing abstract LEF to {0}".format(lef_name)) + self.compute_abstract_blockages() self.lef = open(lef_name, "w") self.lef_write_header() - for pin in self.pins: - self.lef_write_pin(pin) - self.lef_write_obstructions() + + for pin_name in self.pins: + self.lef_write_pin(pin_name) + + self.lef_write_obstructions(OPTS.detailed_lef) self.lef_write_footer() self.lef.close() + def compute_abstract_blockages(self): + # Start with blockages on all layers the size of the block + # minus the pin escape margin (hard coded to 4 x m3 pitch) + # These are a pin_layout to use their geometric functions + perimeter_margin = self.m3_pitch + self.blockages = {} + for layer_name in self.lef_layers: + self.blockages[layer_name]=[] + for layer_name in self.lef_layers: + ll = vector(perimeter_margin, perimeter_margin) + ur = vector(self.width - perimeter_margin, self.height - perimeter_margin) + self.blockages[layer_name].append(pin_layout("", + [ll, ur], + layer_name)) + + # For each pin, remove the blockage and add the pin + for pin_name in self.pins: + pin = self.get_pin(pin_name) + inflated_pin = pin.inflated_pin(multiple=1) + another_iteration_needed = True + while another_iteration_needed: + another_iteration_needed = False + old_blockages = list(self.blockages[pin.layer]) + for blockage in old_blockages: + if blockage.overlaps(inflated_pin): + intersection_shape = blockage.intersection(inflated_pin) + # If it is zero area, don't add the pin + if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]: + continue + another_iteration_needed = True + # Remove the old blockage and add the new ones + self.blockages[pin.layer].remove(blockage) + intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer) + new_blockages = blockage.cut(intersection_pin) + self.blockages[pin.layer].extend(new_blockages) + def lef_write_header(self): """ Header of LEF file """ self.lef.write("VERSION 5.4 ;\n") @@ -103,8 +148,8 @@ class lef: self.lef.write("{0}SYMMETRY X Y R90 ;\n".format(self.indent)) def lef_write_footer(self): - self.lef.write("{0}END {1}\n".format(self.indent, self.name)) self.indent = self.indent[:-3] + self.lef.write("{0}END {1}\n".format(self.indent, self.name)) self.lef.write("END LIBRARY\n") def lef_write_pin(self, name): @@ -136,24 +181,29 @@ class lef: self.indent = self.indent[:-3] self.lef.write("{0}END {1}\n".format(self.indent, name)) - def lef_write_obstructions(self): + def lef_write_obstructions(self, detailed=False): """ Write all the obstructions on each layer """ self.lef.write("{0}OBS\n".format(self.indent)) for layer in self.lef_layers: self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer])) self.indent += " " - blockages = self.get_blockages(layer, True) - for b in blockages: - self.lef_write_shape(b) + if detailed: + blockages = self.get_blockages(layer, True) + for b in blockages: + self.lef_write_shape(b) + else: + blockages = self.blockages[layer] + for b in blockages: + self.lef_write_shape(b.rect) self.indent = self.indent[:-3] self.lef.write("{0}END\n".format(self.indent)) - def lef_write_shape(self, rect): - if len(rect) == 2: + def lef_write_shape(self, obj): + if len(obj) == 2: """ Write a LEF rectangle """ self.lef.write("{0}RECT ".format(self.indent)) - for item in rect: - # print(rect) + for item in obj: + # print(obj) self.lef.write(" {0} {1}".format(round(item[0], self.round_grid), round(item[1], @@ -162,12 +212,10 @@ class lef: else: """ Write a LEF polygon """ self.lef.write("{0}POLYGON ".format(self.indent)) - for item in rect: + for item in obj: self.lef.write(" {0} {1}".format(round(item[0], self.round_grid), round(item[1], self.round_grid))) - # for i in range(0,len(rect)): - # self.lef.write(" {0} {1}".format(round(rect[i][0],self.round_grid), round(rect[i][1],self.round_grid))) self.lef.write(" ;\n") diff --git a/compiler/base/pin_layout.py b/compiler/base/pin_layout.py index e8c6f0a5..e6baa4fc 100644 --- a/compiler/base/pin_layout.py +++ b/compiler/base/pin_layout.py @@ -139,13 +139,13 @@ class pin_layout: min_area = drc("{}_minarea".format(self.layer)) pass - def inflate(self, spacing=None): + def inflate(self, spacing=None, multiple=0.5): """ Inflate the rectangle by the spacing (or other rule) and return the new rectangle. """ if not spacing: - spacing = 0.5*drc("{0}_to_{0}".format(self.layer)) + spacing = multiple*drc("{0}_to_{0}".format(self.layer)) (ll, ur) = self.rect spacing = vector(spacing, spacing) @@ -154,15 +154,23 @@ class pin_layout: return (newll, newur) + def inflated_pin(self, spacing=None, multiple=0.5): + """ + Inflate the rectangle by the spacing (or other rule) + and return the new rectangle. + """ + inflated_area = self.inflate(spacing, multiple) + return pin_layout(self.name, inflated_area, self.layer) + def intersection(self, other): """ Check if a shape overlaps with a rectangle """ (ll, ur) = self.rect (oll, our) = other.rect min_x = max(ll.x, oll.x) - max_x = min(ll.x, oll.x) + max_x = min(ur.x, our.x) min_y = max(ll.y, oll.y) - max_y = min(ll.y, oll.y) + max_y = min(ur.y, our.y) return [vector(min_x, min_y), vector(max_x, max_y)] @@ -578,6 +586,30 @@ class pin_layout: return None + def cut(self, shape): + """ + Return a set of shapes that are this shape minus the argument shape. + """ + # Make the unique coordinates in X and Y directions + x_offsets = sorted([self.lx(), self.rx(), shape.lx(), shape.rx()]) + y_offsets = sorted([self.by(), self.uy(), shape.by(), shape.uy()]) + + new_shapes = [] + # Create all of the shapes + for x1, x2 in zip(x_offsets[0:], x_offsets[1:]): + if x1==x2: + continue + for y1, y2 in zip(y_offsets[0:], y_offsets[1:]): + if y1==y2: + continue + new_shape = pin_layout("", [vector(x1, y1), vector(x2, y2)], self.lpp) + # Don't add the existing shape in if it overlaps the pin shape + if new_shape.contains(shape): + continue + new_shapes.append(new_shape) + + return new_shapes + def same_lpp(self, lpp1, lpp2): """ Check if the layers and purposes are the same. diff --git a/compiler/base/verilog.py b/compiler/base/verilog.py index 0405649d..7886615f 100644 --- a/compiler/base/verilog.py +++ b/compiler/base/verilog.py @@ -29,6 +29,11 @@ class verilog: self.vf.write("\n") self.vf.write("module {0}(\n".format(self.name)) + self.vf.write("`ifdef USE_POWER_PINS\n") + self.vf.write(" vdd,\n") + self.vf.write(" gnd,\n") + self.vf.write("`endif\n") + for port in self.all_ports: if port in self.readwrite_ports: self.vf.write("// Port {0}: RW\n".format(port)) @@ -65,6 +70,11 @@ class verilog: self.vf.write(" parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary\n") self.vf.write("\n") + self.vf.write("`ifdef USE_POWER_PINS\n") + self.vf.write(" inout vdd;\n") + self.vf.write(" inout gnd;\n") + self.vf.write("`endif\n") + for port in self.all_ports: self.add_inputs_outputs(port) diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 0d9fbe83..a092ac1e 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -24,14 +24,22 @@ debug.info(1, "Initializing characterizer...") OPTS.spice_exe = "" if not OPTS.analytical_delay: - debug.info(1, "Finding spice simulator.") - if OPTS.spice_name != "": + # Capitalize Xyce + if OPTS.spice_name == "xyce": + OPTS.spice_name = "Xyce" OPTS.spice_exe=find_exe(OPTS.spice_name) if OPTS.spice_exe=="" or OPTS.spice_exe==None: debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1) else: - (OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["ngspice", "ngspice.exe", "hspice", "xa"]) + (OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["Xyce", "ngspice", "ngspice.exe", "hspice", "xa"]) + + if OPTS.spice_name in ["Xyce", "xyce"]: + (OPTS.mpi_name, OPTS.mpi_exe) = get_tool("mpi", ["mpirun"]) + OPTS.hier_seperator = ":" + else: + OPTS.mpi_name = None + OPTS.mpi_exe = "" # set the input dir for spice files if using ngspice if OPTS.spice_name == "ngspice": @@ -39,6 +47,12 @@ if not OPTS.analytical_delay: if OPTS.spice_exe == "": debug.error("No recognizable spice version found. Unable to perform characterization.", 1) + else: + debug.info(1, "Finding spice simulator: {} ({})".format(OPTS.spice_name, OPTS.spice_exe)) + if OPTS.mpi_name: + debug.info(1, "MPI for spice simulator: {} ({})".format(OPTS.mpi_name, OPTS.mpi_exe)) + debug.info(1, "Simulation threads: {}".format(OPTS.num_sim_threads)) + else: debug.info(1, "Analytical model enabled.") diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 8f33c279..59ef3177 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -11,21 +11,26 @@ import debug from globals import OPTS -def relative_compare(value1,value2,error_tolerance=0.001): +def relative_compare(value1, value2, error_tolerance=0.001): """ This is used to compare relative values for convergence. """ - return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance) + return (abs(value1 - value2) / abs(max(value1, value2)) <= error_tolerance) def parse_spice_list(filename, key): """Parses a hspice output.lis file for a key value""" + + lower_key = key.lower() + if OPTS.spice_name == "xa" : # customsim has a different output file name full_filename="{0}xa.meas".format(OPTS.openram_temp) elif OPTS.spice_name == "spectre": full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure") + elif OPTS.spice_name in ["Xyce", "xyce"]: + full_filename = os.path.join(OPTS.openram_temp, "spice_stdout.log") else: # ngspice/hspice using a .lis file - full_filename="{0}{1}.lis".format(OPTS.openram_temp, filename) + full_filename = "{0}{1}.lis".format(OPTS.openram_temp, filename) try: f = open(full_filename, "r") @@ -33,31 +38,34 @@ def parse_spice_list(filename, key): debug.error("Unable to open spice output file: {0}".format(full_filename),1) debug.archive() - contents = f.read() + contents = f.read().lower() 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) + val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(lower_key), contents) if val != None: - debug.info(4, "Key = " + key + " Val = " + val.group(1)) + debug.info(4, "Key = " + lower_key + " Val = " + val.group(1)) return convert_to_float(val.group(1)) else: return "Failed" -def round_time(time,time_precision=3): + +def round_time(time, time_precision=3): # times are in ns, so this is how many digits of precision # 3 digits = 1ps # 4 digits = 0.1ps # etc. - return round(time,time_precision) + return round(time, time_precision) -def round_voltage(voltage,voltag_precision=5): + +def round_voltage(voltage, voltage_precision=5): # voltages are in volts # 3 digits = 1mv # 4 digits = 0.1mv # 5 digits = 0.01mv # 6 digits = 1uv # etc - return round(voltage,voltage_precision) + return round(voltage, voltage_precision) + def convert_to_float(number): """Converts a string into a (float) number; also converts units(m,u,n,p)""" @@ -84,7 +92,7 @@ def convert_to_float(number): 'n': lambda x: x * 0.000000001, # nano 'p': lambda x: x * 0.000000000001, # pico 'f': lambda x: x * 0.000000000000001 # femto - }[unit.group(2)](float(unit.group(1))) + }[unit.group(2)](float(unit.group(1))) # if we weren't able to convert it to a float then error out if not type(float_value)==float: @@ -92,9 +100,10 @@ def convert_to_float(number): return float_value + def check_dict_values_is_float(dict): """Checks if all the values are floats. Useful for checking failed Spice measurements.""" for key, value in dict.items(): - if type(value)!=float: - return False + if type(value)!=float: + return False return True diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 181df7c8..fb5174c6 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -69,7 +69,7 @@ class delay(simulation): for meas in meas_list: name = meas.name.lower() debug.check(name not in name_set, ("SPICE measurements must have unique names. " - "Duplicate name={}").format(name)) + "Duplicate name={0}").format(name)) name_set.add(name) def create_read_port_measurement_objects(self): @@ -77,7 +77,7 @@ class delay(simulation): self.read_lib_meas = [] self.clk_frmt = "clk{0}" # Unformatted clock name - targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit + targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit self.delay_meas = [] self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9)) self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file. @@ -166,7 +166,7 @@ class delay(simulation): self.dout_volt_meas = [] for meas in self.delay_meas: # Output voltage measures - self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name), + self.dout_volt_meas.append(voltage_at_measure("v_{0}".format(meas.name), meas.targ_name_no_port)) self.dout_volt_meas[-1].meta_str = meas.meta_str @@ -186,7 +186,7 @@ class delay(simulation): self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE) for cycle in meas_cycles: - meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) + meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) for polarity, meas in single_bit_meas.items(): meas.meta_str = cycle @@ -200,7 +200,7 @@ class delay(simulation): self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []} meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE) for cycle in meas_cycles: - meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name) + meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name) single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data) for polarity, meas in single_bit_meas.items(): meas.meta_str = cycle @@ -219,20 +219,20 @@ class delay(simulation): (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col) storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" - "supported for characterization. Storage nets={}").format(storage_names)) + "supported for characterization. Storage nets={0}").format(storage_names)) if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col) q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col) else: - q_name = cell_name + '.' + str(storage_names[0]) - qbar_name = cell_name + '.' + str(storage_names[1]) + q_name = cell_name + OPTS.hier_seperator + str(storage_names[0]) + qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1]) # Bit measures, measurements times to be defined later. The measurement names must be unique # but they is enforced externally. {} added to names to differentiate between ports allow the # measurements are independent of the ports - q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name) - qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name) + q_meas = voltage_at_measure("v_q_{0}".format(meas_tag), q_name) + qbar_meas = voltage_at_measure("v_qbar_{0}".format(meas_tag), qbar_name) return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas} @@ -242,15 +242,15 @@ class delay(simulation): # FIXME: There should be a default_read_port variable in this case, pathing is done with this # but is never mentioned otherwise port = self.read_ports[0] - sen_and_port = self.sen_name+str(port) + sen_and_port = self.sen_name + str(port) bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port # Isolate the s_en and bitline paths - debug.info(1, "self.bl_name = {}".format(self.bl_name)) - debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths)) + debug.info(1, "self.bl_name = {0}".format(self.bl_name)) + debug.info(1, "self.graph.all_paths = {0}".format(self.graph.all_paths)) sen_paths = [path for path in self.graph.all_paths if sen_and_port in path] bl_paths = [path for path in self.graph.all_paths if bl_and_port in path] - debug.check(len(sen_paths)==1, 'Found {} paths which contain the s_en net.'.format(len(sen_paths))) - debug.check(len(bl_paths)==1, 'Found {} paths which contain the bitline net.'.format(len(bl_paths))) + debug.check(len(sen_paths)==1, 'Found {0} paths which contain the s_en net.'.format(len(sen_paths))) + debug.check(len(bl_paths)==1, 'Found {0} paths which contain the bitline net.'.format(len(bl_paths))) sen_path = sen_paths[0] bitline_path = bl_paths[0] @@ -286,11 +286,11 @@ class delay(simulation): # Create the measurements path_meas = [] - for i in range(len(path)-1): - cur_net, next_net = path[i], path[i+1] - cur_dir, next_dir = path_dirs[i], path_dirs[i+1] - meas_name = "delay_{}_to_{}".format(cur_net, next_net) - if i+1 != len(path)-1: + for i in range(len(path) - 1): + cur_net, next_net = path[i], path[i + 1] + cur_dir, next_dir = path_dirs[i], path_dirs[i + 1] + meas_name = "delay_{0}_to_{1}".format(cur_net, next_net) + if i + 1 != len(path) - 1: path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False)) else: # Make the last measurement always measure on FALL because is a read 0 path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, "FALL", measure_scale=1e9, has_port=False)) @@ -309,13 +309,13 @@ class delay(simulation): # Convert to booleans based on function of modules (inverting/non-inverting) mod_type_bools = [mod.is_non_inverting() for mod in edge_mods] - #FIXME: obtuse hack to differentiate s_en input from bitline in sense amps + # FIXME: obtuse hack to differentiate s_en input from bitline in sense amps if self.sen_name in path: # Force the sense amp to be inverting for s_en->DOUT. # bitline->DOUT is non-inverting, but the module cannot differentiate inputs. s_en_index = path.index(self.sen_name) mod_type_bools[s_en_index] = False - debug.info(2,'Forcing sen->dout to be inverting.') + debug.info(2, 'Forcing sen->dout to be inverting.') # Use these to determine direction list assuming delay start on neg. edge of clock (FALL) # Also, use shorthand that 'FALL' == False, 'RISE' == True to simplify logic @@ -493,7 +493,7 @@ class delay(simulation): elif meas_type is voltage_at_measure: variant_tuple = self.get_volt_at_measure_variants(port, measure_obj) else: - debug.error("Input function not defined for measurement type={}".format(meas_type)) + debug.error("Input function not defined for measurement type={0}".format(meas_type)) # Removes port input from any object which does not use it. This shorthand only works if # the measurement has port as the last input. Could be implemented by measurement type or # remove entirely from measurement classes. @@ -515,7 +515,7 @@ class delay(simulation): elif delay_obj.meta_str == sram_op.READ_ONE: meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] else: - debug.error("Unrecognized delay Index={}".format(delay_obj.meta_str),1) + debug.error("Unrecognized delay Index={0}".format(delay_obj.meta_str), 1) # These measurements have there time further delayed to the neg. edge of the clock. if delay_obj.meta_add_delay: @@ -587,20 +587,20 @@ class delay(simulation): # Output some comments to aid where cycles start and # what is happening for comment in self.cycle_comments: - self.sf.write("* {}\n".format(comment)) + self.sf.write("* {0}\n".format(comment)) self.sf.write("\n") for read_port in self.targ_read_ports: - self.sf.write("* Read ports {}\n".format(read_port)) + self.sf.write("* Read ports {0}\n".format(read_port)) self.write_delay_measures_read_port(read_port) for write_port in self.targ_write_ports: - self.sf.write("* Write ports {}\n".format(write_port)) + self.sf.write("* Write ports {0}\n".format(write_port)) self.write_delay_measures_write_port(write_port) def load_pex_net(self, net: str): from subprocess import check_output, CalledProcessError - prefix = (self.sram_instance_name + ".").lower() + prefix = (self.sram_instance_name + OPTS.hier_seperator).lower() if not net.lower().startswith(prefix) or not OPTS.use_pex or not OPTS.calibre_pex: return net original_net = net @@ -640,26 +640,41 @@ class delay(simulation): col = self.bitline_column row = self.wordline_row for port in set(self.targ_read_ports + self.targ_write_ports): - probe_nets.add("WEB{}".format(port)) - probe_nets.add("{}.w_en{}".format(self.sram_instance_name, port)) - probe_nets.add("{0}.Xbank0.Xport_data{1}.Xwrite_driver_array{1}.Xwrite_driver{2}.en_bar".format( - self.sram_instance_name, port, self.bitline_column)) - probe_nets.add("{}.Xbank0.br_{}_{}".format(self.sram_instance_name, port, - self.bitline_column)) + probe_nets.add("WEB{0}".format(port)) + probe_nets.add("{0}{2}w_en{1}".format(self.sram_instance_name, port, OPTS.hier_seperator)) + probe_nets.add("{0}{3}Xbank0{3}Xport_data{1}{3}Xwrite_driver_array{1}{3}Xwrite_driver{2}{3}en_bar".format(self.sram_instance_name, + port, + self.bitline_column, + OPTS.hier_seperator)) + probe_nets.add("{0}{3}Xbank0{3}br_{1}_{2}".format(self.sram_instance_name, + port, + self.bitline_column, + OPTS.hier_seperator)) if not OPTS.use_pex: continue probe_nets.add( - "{0}.vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name, row, col - 1)) + "{0}{3}vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name, + row, + col - 1, + OPTS.hier_seperator)) probe_nets.add( - "{0}.p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name, port, col)) + "{0}{3}p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name, + port, + col, + OPTS.hier_seperator)) probe_nets.add( - "{0}.vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, port, col)) - probe_nets.add("{0}.vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name, - port, col)) + "{0}{3}vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, + port, + col, + OPTS.hier_seperator)) + probe_nets.add("{0}{3}vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name, + port, + col, + OPTS.hier_seperator)) probe_nets.update(self.measurement_nets) for net in probe_nets: - debug.info(2, "Probe: {}".format(net)) - self.sf.write(".plot V({}) \n".format(self.load_pex_net(net))) + debug.info(2, "Probe: {0}".format(net)) + self.sf.write(".plot V({0}) \n".format(self.load_pex_net(net))) def write_power_measures(self): """ @@ -778,7 +793,7 @@ class delay(simulation): if not self.check_bit_measures(self.write_bit_meas, port): return(False, {}) - debug.info(2, "Checking write values for port {}".format(port)) + debug.info(2, "Checking write values for port {0}".format(port)) write_port_dict = {} for measure in self.write_lib_meas: write_port_dict[measure.name] = measure.retrieve_measure(port=port) @@ -792,7 +807,7 @@ class delay(simulation): if not self.check_bit_measures(self.read_bit_meas, port): return(False, {}) - debug.info(2, "Checking read delay values for port {}".format(port)) + debug.info(2, "Checking read delay values for port {0}".format(port)) # Check sen timing, then bitlines, then general measurements. if not self.check_sen_measure(port): return (False, {}) @@ -821,7 +836,7 @@ class delay(simulation): """Checks that the sen occurred within a half-period""" sen_val = self.sen_meas.retrieve_measure(port=port) - debug.info(2, "s_en delay={}ns".format(sen_val)) + debug.info(2, "s_en delay={0}ns".format(sen_val)) if self.sen_meas.meta_add_delay: max_delay = self.period / 2 else: @@ -843,22 +858,22 @@ class delay(simulation): elif self.br_name == meas.targ_name_no_port: br_vals[meas.meta_str] = val - debug.info(2, "{}={}".format(meas.name, val)) + debug.info(2, "{0}={1}".format(meas.name, val)) dout_success = True bl_success = False for meas in self.dout_volt_meas: val = meas.retrieve_measure(port=port) - debug.info(2, "{}={}".format(meas.name, val)) + debug.info(2, "{0}={1}".format(meas.name, val)) debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name, val)) if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage * 0.1: dout_success = False - debug.info(1, "Debug measurement failed. Value {}V was read on read 1 cycle.".format(val)) + debug.info(1, "Debug measurement failed. Value {0}V was read on read 1 cycle.".format(val)) bl_success = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE]) elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage * 0.9: dout_success = False - debug.info(1, "Debug measurement failed. Value {}V was read on read 0 cycle.".format(val)) + debug.info(1, "Debug measurement failed. Value {0}V was read on read 0 cycle.".format(val)) bl_success = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE]) # If the bitlines have a correct value while the output does not then that is a @@ -877,7 +892,7 @@ class delay(simulation): for polarity, meas_list in bit_measures.items(): for meas in meas_list: val = meas.retrieve_measure(port=port) - debug.info(2, "{}={}".format(meas.name, val)) + debug.info(2, "{0}={1}".format(meas.name, val)) if type(val) != float: continue meas_cycle = meas.meta_str @@ -896,8 +911,8 @@ class delay(simulation): success = val < self.vdd_voltage / 2 if not success: debug.info(1, ("Wrong value detected on probe bit during read/write cycle. " - "Check writes and control logic for bugs.\n measure={}, op={}, " - "bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name, val)) + "Check writes and control logic for bugs.\n measure={0}, op={1}, " + "bit_storage={2}, V(bit)={3}").format(meas.name, meas_cycle.name, polarity.name, val)) return success @@ -912,7 +927,7 @@ class delay(simulation): min_dicharge = v_discharged_bl < self.vdd_voltage * 0.9 min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage * 0.1 - debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff)) + debug.info(1, "min_dicharge={0}, min_diff={1}".format(min_dicharge, min_diff)) return (min_dicharge and min_diff) def check_path_measures(self): @@ -921,11 +936,11 @@ class delay(simulation): # Get and set measurement, no error checking done other than prints. debug.info(2, "Checking measures in Delay Path") value_dict = {} - for meas in self.sen_path_meas+self.bl_path_meas: + for meas in self.sen_path_meas + self.bl_path_meas: val = meas.retrieve_measure() - debug.info(2, '{}={}'.format(meas.name, val)) - if type(val) != float or val > self.period/2: - debug.info(1,'Failed measurement:{}={}'.format(meas.name, val)) + debug.info(2, '{0}={1}'.format(meas.name, val)) + if type(val) != float or val > self.period / 2: + debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val)) value_dict[meas.name] = val #debug.info(0, "value_dict={}".format(value_dict)) return value_dict @@ -1100,14 +1115,14 @@ class delay(simulation): # Set up to trim the netlist here if that is enabled if OPTS.trim_netlist: - self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp) + self.trim_sp_file = "{0}trimmed.sp".format(OPTS.openram_temp) self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True) else: # The non-reduced netlist file when it is disabled - self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp) + self.trim_sp_file = "{0}sram.sp".format(OPTS.openram_temp) # The non-reduced netlist file for power simulation - self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp) + self.sim_sp_file = "{0}sram.sp".format(OPTS.openram_temp) # Make a copy in temp for debugging shutil.copy(self.sp_file, self.sim_sp_file) @@ -1194,6 +1209,7 @@ class delay(simulation): for mname, value in delay_results[port].items(): if "power" in mname: # Subtract partial array leakage and add full array leakage for the power measures + debug.info(1, "Adding leakage offset to {0} {1} + {2} = {3}".format(mname, value, leakage_offset, value + leakage_offset)) measure_data[port][mname].append(value + leakage_offset) else: measure_data[port][mname].append(value) @@ -1246,13 +1262,13 @@ class delay(simulation): if self.t_current == 0: self.add_noop_all_ports("Idle cycle (no positive clock edge)") - self.add_write("W data 1 address {}".format(inverse_address), + self.add_write("W data 1 address {0}".format(inverse_address), inverse_address, data_ones, wmask_ones, write_port) - self.add_write("W data 0 address {} to write value".format(self.probe_address), + self.add_write("W data 0 address {0} to write value".format(self.probe_address), self.probe_address, data_zeros, wmask_ones, @@ -1263,11 +1279,11 @@ class delay(simulation): self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times) - 1 # This also ensures we will have a H->L transition on the next read - self.add_read("R data 1 address {} to set dout caps".format(inverse_address), + self.add_read("R data 1 address {0} to set dout caps".format(inverse_address), inverse_address, read_port) - self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address), + self.add_read("R data 0 address {0} to check W0 worked".format(self.probe_address), self.probe_address, read_port) self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times) - 1 @@ -1277,7 +1293,7 @@ class delay(simulation): self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)") - self.add_write("W data 1 address {} to write value".format(self.probe_address), + self.add_write("W data 1 address {0} to write value".format(self.probe_address), self.probe_address, data_ones, wmask_ones, @@ -1287,7 +1303,7 @@ class delay(simulation): self.add_noop_clock_one_port(write_port) self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times) - 1 - self.add_write("W data 0 address {} to clear din caps".format(inverse_address), + self.add_write("W data 0 address {0} to clear din caps".format(inverse_address), inverse_address, data_zeros, wmask_ones, @@ -1297,11 +1313,11 @@ class delay(simulation): self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1 # This also ensures we will have a L->H transition on the next read - self.add_read("R data 0 address {} to clear dout caps".format(inverse_address), + self.add_read("R data 0 address {0} to clear dout caps".format(inverse_address), inverse_address, read_port) - self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address), + self.add_read("R data 1 address {0} to check W1 worked".format(self.probe_address), self.probe_address, read_port) self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1 diff --git a/compiler/characterizer/elmore.py b/compiler/characterizer/elmore.py index 549b3367..40035a6d 100644 --- a/compiler/characterizer/elmore.py +++ b/compiler/characterizer/elmore.py @@ -67,18 +67,15 @@ class elmore(simulation): load, total_delay.delay / 1e3, total_delay.slew / 1e3)) - - # Delay is only calculated on a single port and replicated for now. - for port in self.all_ports: - for mname in self.delay_meas_names + self.power_meas_names: - if "power" in mname: - port_data[port][mname].append(power.dynamic) - elif "delay" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.delay / 1e3) - elif "slew" in mname and port in self.read_ports: - port_data[port][mname].append(total_delay.slew / 1e3) - else: - debug.error("Measurement name not recognized: {}".format(mname), 1) + # Delay is only calculated on a single port and replicated for now. + for port in self.all_ports: + for mname in self.delay_meas_names + self.power_meas_names: + if "power" in mname: + port_data[port][mname].append(power.dynamic) + elif "delay" in mname and port in self.read_ports: + port_data[port][mname].append(total_delay.delay / 1e3) + elif "slew" in mname and port in self.read_ports: + port_data[port][mname].append(total_delay.slew / 1e3) # Margin for error in period. Calculated by averaging required margin for a small and large # memory. FIXME: margin is quite large, should be looked into. diff --git a/compiler/characterizer/functional.py b/compiler/characterizer/functional.py index f93de85c..db01708b 100644 --- a/compiler/characterizer/functional.py +++ b/compiler/characterizer/functional.py @@ -81,7 +81,7 @@ class functional(simulation): self.create_graph() self.set_internal_spice_names() self.q_name, self.qbar_name = self.get_bit_name() - debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name)) + debug.info(2, "q name={0}\nqbar name={1}".format(self.q_name, self.qbar_name)) # Number of checks can be changed self.num_cycles = cycles @@ -144,7 +144,7 @@ class functional(simulation): for port in self.write_ports: addr = self.gen_addr() (word, spare) = self.gen_data() - combined_word = "{}+{}".format(word, spare) + combined_word = "{0}+{1}".format(word, spare) comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current) self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port) self.stored_words[addr] = word @@ -167,7 +167,7 @@ class functional(simulation): self.add_noop_one_port(port) else: (addr, word, spare) = self.get_data() - combined_word = "{}+{}".format(word, spare) + combined_word = "{0}+{1}".format(word, spare) comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current) self.add_read_one_port(comment, addr, port) self.add_read_check(word, port) @@ -197,7 +197,7 @@ class functional(simulation): self.add_noop_one_port(port) else: (word, spare) = self.gen_data() - combined_word = "{}+{}".format(word, spare) + combined_word = "{0}+{1}".format(word, spare) comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current) self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port) self.stored_words[addr] = word @@ -213,7 +213,7 @@ class functional(simulation): (word, spare) = self.gen_data() wmask = self.gen_wmask() new_word = self.gen_masked_data(old_word, word, wmask) - combined_word = "{}+{}".format(word, spare) + combined_word = "{0}+{1}".format(word, spare) comment = self.gen_cycle_comment("partial_write", combined_word, addr, wmask, port, self.t_current) self.add_write_one_port(comment, addr, word + spare, wmask, port) self.stored_words[addr] = new_word @@ -222,7 +222,7 @@ class functional(simulation): else: (addr, word) = random.choice(list(self.stored_words.items())) spare = self.stored_spares[addr[:self.addr_spare_index]] - combined_word = "{}+{}".format(word, spare) + combined_word = "{0}+{1}".format(word, spare) # The write driver is not sized sufficiently to drive through the two # bitcell access transistors to the read port. So, for now, we do not allow # a simultaneous write and read to the same address on different ports. This @@ -363,7 +363,7 @@ class functional(simulation): self.stim_sp = "functional_stim.sp" temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp) self.sf = open(temp_stim, "w") - self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period)) + self.sf.write("* Functional test stimulus file for {0}ns period\n\n".format(self.period)) self.stim = stimuli(self.sf, self.corner) # Write include statements @@ -387,16 +387,16 @@ class functional(simulation): # Write important signals to stim file self.sf.write("\n\n* Important signals for debug\n") - self.sf.write("* bl: {}\n".format(self.bl_name.format(port))) - self.sf.write("* br: {}\n".format(self.br_name.format(port))) - self.sf.write("* s_en: {}\n".format(self.sen_name)) - self.sf.write("* q: {}\n".format(self.q_name)) - self.sf.write("* qbar: {}\n".format(self.qbar_name)) + self.sf.write("* bl: {0}\n".format(self.bl_name.format(port))) + self.sf.write("* br: {0}\n".format(self.br_name.format(port))) + self.sf.write("* s_en: {0}\n".format(self.sen_name)) + self.sf.write("* q: {0}\n".format(self.q_name)) + self.sf.write("* qbar: {0}\n".format(self.qbar_name)) # Write debug comments to stim file self.sf.write("\n\n* Sequence of operations\n") for comment in self.fn_cycle_comments: - self.sf.write("*{}\n".format(comment)) + self.sf.write("*{0}\n".format(comment)) # Generate data input bits self.sf.write("\n* Generation of data and address signals\n") @@ -414,10 +414,10 @@ class functional(simulation): # Generate control signals self.sf.write("\n * Generation of control signals\n") for port in self.all_ports: - self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05) for port in self.readwrite_ports: - self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) + self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05) # Generate wmask bits for port in self.write_ports: @@ -472,15 +472,15 @@ class functional(simulation): self.stim.write_control(self.cycle_times[-1] + self.period) self.sf.close() - #FIXME: Similar function to delay.py, refactor this + # FIXME: Similar function to delay.py, refactor this def get_bit_name(self): """ Get a bit cell name """ (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0) storage_names = cell_inst.mod.get_storage_net_names() debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" - "supported for characterization. Storage nets={}").format(storage_names)) - q_name = cell_name + '.' + str(storage_names[0]) - qbar_name = cell_name + '.' + str(storage_names[1]) + "supported for characterization. Storage nets={0}").format(storage_names)) + q_name = cell_name + OPTS.hier_seperator + str(storage_names[0]) + qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1]) return (q_name, qbar_name) diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index b1896880..448dee36 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -53,11 +53,20 @@ class spice_measurement(ABC): elif not self.has_port and port != None: debug.error("Unexpected port input received during measure retrieval.",1) + class delay_measure(spice_measurement): """Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" - def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str,\ - trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True): + def __init__(self, + measure_name, + trig_name, + targ_name, + trig_dir_str, + targ_dir_str, + trig_vdd=0.5, + targ_vdd=0.5, + measure_scale=None, + has_port=True): spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd) @@ -73,7 +82,7 @@ class delay_measure(spice_measurement): self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name - #Time delays and ports are variant and needed as inputs when writing the measurement + # Time delays and ports are variant and needed as inputs when writing the measurement def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" @@ -82,7 +91,7 @@ class delay_measure(spice_measurement): targ_val = self.targ_val_of_vdd * vdd_voltage if port != None: - #For dictionary indexing reasons, the name is formatted differently than the signals + # For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) trig_name = self.trig_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port) @@ -90,7 +99,8 @@ class delay_measure(spice_measurement): meas_name = self.name trig_name = self.trig_name_no_port targ_name = self.targ_name_no_port - return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td) + return (meas_name, trig_name, targ_name, trig_val, targ_val, self.trig_dir_str, self.targ_dir_str, trig_td, targ_td) + class slew_measure(delay_measure): @@ -114,7 +124,8 @@ class slew_measure(delay_measure): self.trig_name_no_port = signal_name self.targ_name_no_port = signal_name - #Time delays and ports are variant and needed as inputs when writing the measurement + # Time delays and ports are variant and needed as inputs when writing the measurement + class power_measure(spice_measurement): """Generates a spice measurement for the average power between two time points.""" @@ -128,8 +139,8 @@ class power_measure(spice_measurement): def set_meas_constants(self, power_type): """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" - #Not needed for power simulation - self.power_type = power_type #Expected to be "RISE"/"FALL" + # Not needed for power simulation + self.power_type = power_type # Expected to be "RISE"/"FALL" def get_measure_values(self, t_initial, t_final, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" @@ -138,7 +149,8 @@ class power_measure(spice_measurement): meas_name = "{}{}".format(self.name, port) else: meas_name = self.name - return (meas_name,t_initial,t_final) + return (meas_name, t_initial, t_final) + class voltage_when_measure(spice_measurement): """Generates a spice measurement to measure the voltage of a signal based on the voltage of another.""" @@ -161,7 +173,7 @@ class voltage_when_measure(spice_measurement): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" self.port_error_check(port) if port != None: - #For dictionary indexing reasons, the name is formatted differently than the signals + # For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) trig_name = self.trig_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port) @@ -169,9 +181,10 @@ class voltage_when_measure(spice_measurement): meas_name = self.name trig_name = self.trig_name_no_port targ_name = self.targ_name_no_port - trig_voltage = self.trig_val_of_vdd*vdd_voltage - return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td) + trig_voltage = self.trig_val_of_vdd * vdd_voltage + return (meas_name, trig_name, targ_name, trig_voltage, self.trig_dir_str, trig_td) + class voltage_at_measure(spice_measurement): """Generates a spice measurement to measure the voltage at a specific time. The time is considered variant with different periods.""" @@ -191,11 +204,11 @@ class voltage_at_measure(spice_measurement): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" self.port_error_check(port) if port != None: - #For dictionary indexing reasons, the name is formatted differently than the signals + # For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) targ_name = self.targ_name_no_port.format(port) else: meas_name = self.name targ_name = self.targ_name_no_port - return (meas_name,targ_name,time_at) + return (meas_name, targ_name, time_at) diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py index f72c3211..fcbc51c2 100644 --- a/compiler/characterizer/model_check.py +++ b/compiler/characterizer/model_check.py @@ -5,18 +5,16 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # -import sys,re,shutil import debug import tech -import math from .stimuli import * from .trim_spice import * from .charutils import * -import utils from globals import OPTS from .delay import delay from .measurements import * + class model_check(delay): """Functions to test for the worst case delay in a target SRAM @@ -39,43 +37,44 @@ class model_check(delay): self.power_name = "total_power" def create_measurement_names(self, port): - """Create measurement names. The names themselves currently define the type of measurement""" - #Create delay measurement names - wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] - wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] - sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + """ + Create measurement names. The names themselves currently define the type of measurement + """ + wl_en_driver_delay_names = ["delay_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_driver_stages())] + wl_driver_delay_names = ["delay_wl_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_driver_stages())] + sen_driver_delay_names = ["delay_sen_dvr_{0}".format(stage) for stage in range(1, self.get_num_sen_driver_stages())] if self.custom_delaychain: - dc_delay_names = ['delay_dc_out_final'] + dc_delay_names = ["delay_dc_out_final"] else: - dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] - self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"] + dc_delay_names = ["delay_delay_chain_stage_{0}".format(stage) for stage in range(1, self.get_num_delay_stages() + 1)] + self.wl_delay_meas_names = wl_en_driver_delay_names + ["delay_wl_en", "delay_wl_bar"] + wl_driver_delay_names + ["delay_wl"] if port not in self.sram.readonly_ports: - self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names + self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"] + dc_delay_names else: - self.rbl_delay_meas_names = ["delay_gated_clk_nand"]+dc_delay_names - self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"] + self.rbl_delay_meas_names = ["delay_gated_clk_nand"] + dc_delay_names + self.sae_delay_meas_names = ["delay_pre_sen"] + sen_driver_delay_names + ["delay_sen"] # if self.custom_delaychain: - # self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1) + # self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1) # else: - self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names)) - #Create slew measurement names - wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())] - wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())] - sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())] + self.delay_chain_indices = (len(self.rbl_delay_meas_names) - len(dc_delay_names), len(self.rbl_delay_meas_names)) + # Create slew measurement names + wl_en_driver_slew_names = ["slew_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_driver_stages())] + wl_driver_slew_names = ["slew_wl_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_driver_stages())] + sen_driver_slew_names = ["slew_sen_dvr_{0}".format(stage) for stage in range(1, self.get_num_sen_driver_stages())] if self.custom_delaychain: - dc_slew_names = ['slew_dc_out_final'] + dc_slew_names = ["slew_dc_out_final"] else: - dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)] - self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"] + dc_slew_names = ["slew_delay_chain_stage_{0}".format(stage) for stage in range(1, self.get_num_delay_stages() + 1)] + self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"] + wl_en_driver_slew_names + ["slew_wl_en", "slew_wl_bar"] + wl_driver_slew_names + ["slew_wl"] if port not in self.sram.readonly_ports: - self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar", "slew_gated_clk_nand", "slew_delay_chain_in"] + dc_slew_names else: - self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"]+dc_slew_names - self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"] + self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"] + dc_slew_names + self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"] + sen_driver_slew_names + ["slew_sen"] self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"] - self.power_meas_names = ['read0_power'] + self.power_meas_names = ["read0_power"] def create_signal_names(self, port): """Creates list of the signal names used in the spice file along the wl and sen paths. @@ -83,40 +82,45 @@ class model_check(delay): replicated here. """ delay.create_signal_names(self) - #Signal names are all hardcoded, need to update to make it work for probe address and different configurations. - wl_en_driver_signals = ["Xsram.Xcontrol{}.Xbuf_wl_en.Zb{}_int".format('{}', stage) for stage in range(1,self.get_num_wl_en_driver_stages())] - wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver{}.Xwl_driver_inv{}.Zb{}_int".format('{}', self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())] - sen_driver_signals = ["Xsram.Xcontrol{}.Xbuf_s_en.Zb{}_int".format('{}',stage) for stage in range(1,self.get_num_sen_driver_stages())] + + # Signal names are all hardcoded, need to update to make it work for probe address and different configurations. + wl_en_driver_signals = ["Xsram{1}Xcontrol{{}}.Xbuf_wl_en.Zb{0}_int".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_wl_en_driver_stages())] + wl_driver_signals = ["Xsram{2}Xbank0{2}Xwordline_driver{{}}{2}Xwl_driver_inv{0}{2}Zb{1}_int".format(self.wordline_row, stage, OPTS.hier_seperator) for stage in range(1, self.get_num_wl_driver_stages())] + sen_driver_signals = ["Xsram{1}Xcontrol{{}}{1}Xbuf_s_en{1}Zb{0}_int".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_sen_driver_stages())] if self.custom_delaychain: delay_chain_signal_names = [] else: - delay_chain_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())] + delay_chain_signal_names = ["Xsram{1}Xcontrol{{}}{1}Xreplica_bitline{1}Xdelay_chain{1}dout_{0}".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_delay_stages())] if len(self.sram.all_ports) > 1: port_format = '{}' else: port_format = '' - self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\ - wl_en_driver_signals+\ - ["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\ - wl_driver_signals+\ - ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)] - pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')] + self.wl_signal_names = ["Xsram{0}Xcontrol{{}}{0}gated_clk_bar".format(OPTS.hier_seperator)] + \ + wl_en_driver_signals + \ + ["Xsram{0}wl_en{{}}".format(OPTS.hier_seperator), + "Xsram{1}Xbank0{1}Xwordline_driver{{}}{1}wl_bar_{0}".format(self.wordline_row, + OPTS.hier_seperator)] + \ + wl_driver_signals + \ + ["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format, + self.wordline_row, + OPTS.hier_seperator)] + pre_delay_chain_names = ["Xsram.Xcontrol{{}}{0}gated_clk_bar".format(OPTS.hier_seperator)] if port not in self.sram.readonly_ports: - pre_delay_chain_names+= ["Xsram.Xcontrol{}.Xand2_rbl_in.zb_int".format('{}'), "Xsram.Xcontrol{}.rbl_in".format('{}')] + pre_delay_chain_names+= ["Xsram{0}Xcontrol{{}}{0}Xand2_rbl_in{0}zb_int".format(OPTS.hier_seperator), + "Xsram{0}Xcontrol{{}}{0}rbl_in".format(OPTS.hier_seperator)] - self.rbl_en_signal_names = pre_delay_chain_names+\ - delay_chain_signal_names+\ - ["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')] + self.rbl_en_signal_names = pre_delay_chain_names + \ + delay_chain_signal_names + \ + ["Xsram{0}Xcontrol{{}}{0}Xreplica_bitline{0}delayed_en".format(OPTS.hier_seperator)] + self.sae_signal_names = ["Xsram{0}Xcontrol{{}}{0}Xreplica_bitline{0}bl0_0".format(OPTS.hier_seperator), + "Xsram{0}Xcontrol{{}}{0}pre_s_en".format(OPTS.hier_seperator)] + \ + sen_driver_signals + \ + ["Xsram{0}s_en{{}}".format(OPTS.hier_seperator)] - self.sae_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.bl0_0".format('{}'), "Xsram.Xcontrol{}.pre_s_en".format('{}')]+\ - sen_driver_signals+\ - ["Xsram.s_en{}".format('{}')] - - dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - self.bl_signal_names = ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row),\ - "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\ - dout_name] + self.bl_signal_names = ["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format, self.wordline_row, OPTS.hier_seperator), + "Xsram{2}Xbank0{2}bl{0}_{1}".format(port_format, self.bitline_column, OPTS.hier_seperator), + "{0}{{}}_{1}".format(self.dout_name, self.probe_data)] # Empty values are the port and probe data bit def create_measurement_objects(self): """Create the measurements used for read and write ports""" @@ -124,7 +128,7 @@ class model_check(delay): self.create_sae_meas_objs() self.create_bl_meas_objs() self.create_power_meas_objs() - self.all_measures = self.wl_meas_objs+self.sae_meas_objs+self.bl_meas_objs+self.power_meas_objs + self.all_measures = self.wl_meas_objs + self.sae_meas_objs + self.bl_meas_objs + self.power_meas_objs def create_power_meas_objs(self): """Create power measurement object. Only one.""" @@ -138,14 +142,14 @@ class model_check(delay): targ_dir = "FALL" for i in range(1, len(self.wl_signal_names)): - self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1], - self.wl_signal_names[i-1], + self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i - 1], + self.wl_signal_names[i - 1], self.wl_signal_names[i], trig_dir, targ_dir, measure_scale=1e9)) - self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1], - self.wl_signal_names[i-1], + self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i - 1], + self.wl_signal_names[i - 1], trig_dir, measure_scale=1e9)) temp_dir = trig_dir @@ -155,9 +159,9 @@ class model_check(delay): def create_bl_meas_objs(self): """Create the measurements to measure the bitline to dout, static stages""" - #Bitline has slightly different measurements, objects appends hardcoded. + # Bitline has slightly different measurements, objects appends hardcoded. self.bl_meas_objs = [] - trig_dir, targ_dir = "RISE", "FALL" #Only check read 0 + trig_dir, targ_dir = "RISE", "FALL" # Only check read 0 self.bl_meas_objs.append(delay_measure(self.bitline_meas_names[0], self.bl_signal_names[0], self.bl_signal_names[-1], @@ -171,22 +175,22 @@ class model_check(delay): self.sae_meas_objs = [] trig_dir = "RISE" targ_dir = "FALL" - #Add measurements from gated_clk_bar to RBL + # Add measurements from gated_clk_bar to RBL for i in range(1, len(self.rbl_en_signal_names)): - self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1], - self.rbl_en_signal_names[i-1], + self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i - 1], + self.rbl_en_signal_names[i - 1], self.rbl_en_signal_names[i], trig_dir, targ_dir, measure_scale=1e9)) - self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1], - self.rbl_en_signal_names[i-1], + self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i - 1], + self.rbl_en_signal_names[i - 1], trig_dir, measure_scale=1e9)) temp_dir = trig_dir trig_dir = targ_dir targ_dir = temp_dir - if self.custom_delaychain: #Hack for custom delay chains + if self.custom_delaychain: # Hack for custom delay chains self.sae_meas_objs[-2] = delay_measure(self.rbl_delay_meas_names[-1], self.rbl_en_signal_names[-2], self.rbl_en_signal_names[-1], @@ -198,18 +202,18 @@ class model_check(delay): trig_dir, measure_scale=1e9)) - #Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL. + # Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL. trig_dir = "FALL" targ_dir = "RISE" for i in range(1, len(self.sae_signal_names)): - self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1], - self.sae_signal_names[i-1], + self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i - 1], + self.sae_signal_names[i - 1], self.sae_signal_names[i], trig_dir, targ_dir, measure_scale=1e9)) - self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1], - self.sae_signal_names[i-1], + self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i - 1], + self.sae_signal_names[i - 1], trig_dir, measure_scale=1e9)) temp_dir = trig_dir @@ -231,16 +235,16 @@ class model_check(delay): self.sf.write("* {}\n".format(comment)) for read_port in self.targ_read_ports: - self.write_measures_read_port(read_port) + self.write_measures_read_port(read_port) def get_delay_measure_variants(self, port, measure_obj): """Get the measurement values that can either vary from simulation to simulation (vdd, address) or port to port (time delays)""" - #Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port - #Assuming only read 0 for now - debug.info(3,"Power measurement={}".format(measure_obj)) + # Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port + # Assuming only read 0 for now + debug.info(3, "Power measurement={}".format(measure_obj)) if (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure): - meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2 + meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period / 2 return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) elif type(measure_obj) is power_measure: return self.get_power_measure_variants(port, measure_obj, "read") @@ -249,9 +253,9 @@ class model_check(delay): def get_power_measure_variants(self, port, power_obj, operation): """Get the measurement values that can either vary port to port (time delays)""" - #Return value is intended to match the power measure format: t_initial, t_final, port + # Return value is intended to match the power measure format: t_initial, t_final, port t_initial = self.cycle_times[self.measure_cycles[port]["read0"]] - t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1] + t_final = self.cycle_times[self.measure_cycles[port]["read0"] + 1] return (t_initial, t_final, port) @@ -280,8 +284,8 @@ class model_check(delay): elif type(measure)is power_measure: power_meas_list.append(measure_value) else: - debug.error("Measurement object not recognized.",1) - return delay_meas_list, slew_meas_list,power_meas_list + debug.error("Measurement object not recognized.", 1) + return delay_meas_list, slew_meas_list, power_meas_list def run_delay_simulation(self): """ @@ -290,7 +294,7 @@ class model_check(delay): works on the trimmed netlist by default, so powers do not include leakage of all cells. """ - #Sanity Check + # Sanity Check debug.check(self.period > 0, "Target simulation period non-positive") wl_delay_result = [[] for i in self.all_ports] @@ -303,16 +307,16 @@ class model_check(delay): # Checking from not data_value to data_value self.write_delay_stimulus() - self.stim.run_sim() #running sim prodoces spice output file. + self.stim.run_sim() # running sim prodoces spice output file. - #Retrieve the results from the output file + # Retrieve the results from the output file for port in self.targ_read_ports: - #Parse and check the voltage measurements - wl_delay_result[port], wl_slew_result[port],_ = self.get_measurement_values(self.wl_meas_objs, port) - sae_delay_result[port], sae_slew_result[port],_ = self.get_measurement_values(self.sae_meas_objs, port) - bl_delay_result[port], bl_slew_result[port],_ = self.get_measurement_values(self.bl_meas_objs, port) - _,__,power_result[port] = self.get_measurement_values(self.power_meas_objs, port) - return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result) + # Parse and check the voltage measurements + wl_delay_result[port], wl_slew_result[port], _ = self.get_measurement_values(self.wl_meas_objs, port) + sae_delay_result[port], sae_slew_result[port], _ = self.get_measurement_values(self.sae_meas_objs, port) + bl_delay_result[port], bl_slew_result[port], _ = self.get_measurement_values(self.bl_meas_objs, port) + _, __, power_result[port] = self.get_measurement_values(self.power_meas_objs, port) + return (True, wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result) def get_model_delays(self, port): """Get model delays based on port. Currently assumes single RW port.""" @@ -345,41 +349,41 @@ class model_check(delay): def scale_delays(self, delay_list): """Takes in a list of measured delays and convert it to simple units to easily compare to model values.""" converted_values = [] - #Calculate average + # Calculate average total = 0 for meas_value in delay_list: total+=meas_value - average = total/len(delay_list) + average = total / len(delay_list) - #Convert values + # Convert values for meas_value in delay_list: - converted_values.append(meas_value/average) + converted_values.append(meas_value / average) return converted_values def min_max_normalization(self, value_list): """Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1""" scaled_values = [] min_max_diff = max(value_list) - min(value_list) - average = sum(value_list)/len(value_list) + average = sum(value_list) / len(value_list) for value in value_list: - scaled_values.append((value-average)/(min_max_diff)) + scaled_values.append((value - average) / (min_max_diff)) return scaled_values def calculate_error_l2_norm(self, list_a, list_b): """Calculates error between two lists using the l2 norm""" error_list = [] for val_a, val_b in zip(list_a, list_b): - error_list.append((val_a-val_b)**2) + error_list.append((val_a - val_b)**2) return error_list def compare_measured_and_model(self, measured_vals, model_vals): """First scales both inputs into similar ranges and then compares the error between both.""" scaled_meas = self.min_max_normalization(measured_vals) - debug.info(1, "Scaled measurements:\n{}".format(scaled_meas)) + debug.info(1, "Scaled measurements:\n{0}".format(scaled_meas)) scaled_model = self.min_max_normalization(model_vals) - debug.info(1, "Scaled model:\n{}".format(scaled_model)) + debug.info(1, "Scaled model:\n{0}".format(scaled_model)) errors = self.calculate_error_l2_norm(scaled_meas, scaled_model) - debug.info(1, "Errors:\n{}\n".format(errors)) + debug.info(1, "Errors:\n{0}\n".format(errors)) def analyze(self, probe_address, probe_data, slews, loads, port): """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays.""" @@ -391,19 +395,19 @@ class model_check(delay): self.create_measurement_objects() data_dict = {} - read_port = self.read_ports[0] #only test the first read port + read_port = self.read_ports[0] # only test the first read port read_port = port self.targ_read_ports = [read_port] self.targ_write_ports = [self.write_ports[0]] - debug.info(1,"Model test: corner {}".format(self.corner)) + debug.info(1, "Model test: corner {0}".format(self.corner)) (success, wl_delays, sae_delays, wl_slews, sae_slews, bl_delays, bl_slews, powers)=self.run_delay_simulation() - debug.check(success, "Model measurements Failed: period={}".format(self.period)) + debug.check(success, "Model measurements Failed: period={0}".format(self.period)) - debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port])) - debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port])) - debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port])) - debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port])) - debug.info(1,"Measured Bitline delays (ns):\n\t {}".format(bl_delays[read_port])) + debug.info(1, "Measured Wordline delays (ns):\n\t {0}".format(wl_delays[read_port])) + debug.info(1, "Measured Wordline slews:\n\t {0}".format(wl_slews[read_port])) + debug.info(1, "Measured SAE delays (ns):\n\t {0}".format(sae_delays[read_port])) + debug.info(1, "Measured SAE slews:\n\t {0}".format(sae_slews[read_port])) + debug.info(1, "Measured Bitline delays (ns):\n\t {0}".format(bl_delays[read_port])) data_dict[self.wl_meas_name] = wl_delays[read_port] data_dict[self.sae_meas_name] = sae_delays[read_port] @@ -412,14 +416,14 @@ class model_check(delay): data_dict[self.bl_meas_name] = bl_delays[read_port] data_dict[self.power_name] = powers[read_port] - if OPTS.auto_delay_chain_sizing: #Model is not used in this case + if OPTS.auto_delay_chain_sizing: # Model is not used in this case wl_model_delays, sae_model_delays = self.get_model_delays(read_port) - debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays)) - debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays)) + debug.info(1, "Wordline model delays:\n\t {0}".format(wl_model_delays)) + debug.info(1, "SAE model delays:\n\t {0}".format(sae_model_delays)) data_dict[self.wl_model_name] = wl_model_delays data_dict[self.sae_model_name] = sae_model_delays - #Some evaluations of the model and measured values + # Some evaluations of the model and measured values # debug.info(1, "Comparing wordline measurements and model.") # self.compare_measured_and_model(wl_delays[read_port], wl_model_delays) # debug.info(1, "Comparing SAE measurements and model") @@ -430,17 +434,17 @@ class model_check(delay): def get_all_signal_names(self): """Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV.""" name_dict = {} - #Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names. + # Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names. name_dict[self.wl_meas_name] = self.wl_signal_names[1:] - name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:] + name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:] + self.sae_signal_names[1:] name_dict[self.wl_slew_name] = self.wl_slew_meas_names - name_dict[self.sae_slew_name] = self.rbl_slew_meas_names+self.sae_slew_meas_names + name_dict[self.sae_slew_name] = self.rbl_slew_meas_names + self.sae_slew_meas_names name_dict[self.bl_meas_name] = self.bitline_meas_names[0:1] name_dict[self.power_name] = self.power_meas_names - #name_dict[self.wl_slew_name] = self.wl_slew_meas_names + # pname_dict[self.wl_slew_name] = self.wl_slew_meas_names if OPTS.auto_delay_chain_sizing: - name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured. + name_dict[self.wl_model_name] = name_dict["wl_measures"] # model uses same names as measured. name_dict[self.sae_model_name] = name_dict["sae_measures"] return name_dict diff --git a/compiler/characterizer/setup_hold.py b/compiler/characterizer/setup_hold.py index b323078a..72e973d5 100644 --- a/compiler/characterizer/setup_hold.py +++ b/compiler/characterizer/setup_hold.py @@ -76,10 +76,10 @@ class setup_hold(): self.stim.write_supply() def write_data(self, mode, target_time, correct_value): - """Create the data signals for setup/hold analysis. First period is to + """ + Create the data signals for setup/hold analysis. First period is to initialize it to the opposite polarity. Second period is used for characterization. - """ self.sf.write("\n* Generation of the data and clk signals\n") if correct_value == 1: @@ -106,8 +106,11 @@ class setup_hold(): setup=0) def write_clock(self): - """ Create the clock signal for setup/hold analysis. First period initializes the FF - while the second is used for characterization.""" + """ + Create the clock signal for setup/hold analysis. + First period initializes the FF + while the second is used for characterization. + """ self.stim.gen_pwl(sig_name="clk", # initial clk edge is right after the 0 time to initialize a flop @@ -128,16 +131,6 @@ class setup_hold(): else: dout_rise_or_fall = "FALL" - # in SETUP mode, the input mirrors what the output should be - if mode == "SETUP": - din_rise_or_fall = dout_rise_or_fall - else: - # in HOLD mode, however, the input should be opposite of the output - if correct_value == 1: - din_rise_or_fall = "FALL" - else: - din_rise_or_fall = "RISE" - self.sf.write("\n* Measure statements for pass/fail verification\n") trig_name = "clk" targ_name = "Q" @@ -153,19 +146,6 @@ class setup_hold(): trig_td=1.9 * self.period, targ_td=1.9 * self.period) - targ_name = "D" - # Start triggers right after initialize value is returned to normal - # at one period - self.stim.gen_meas_delay(meas_name="setup_hold_time", - trig_name=trig_name, - targ_name=targ_name, - trig_val=trig_val, - targ_val=targ_val, - trig_dir="RISE", - targ_dir=din_rise_or_fall, - trig_td=1.2 * self.period, - targ_td=1.2 * self.period) - def bidir_search(self, correct_value, mode): """ This will perform a bidirectional search for either setup or hold times. It starts with the feasible priod and looks a half period beyond or before it @@ -189,26 +169,28 @@ class setup_hold(): correct_value=correct_value) self.stim.run_sim(self.stim_sp) 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)) + # We use a 1/2 speed clock for some reason... + setuphold_time = (feasible_bound - 2 * self.period) + if mode == "SETUP": # SETUP is clk-din, not din-clk + passing_setuphold_time = -1 * setuphold_time + else: + passing_setuphold_time = setuphold_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: - debug.error("Initial hold time fails for data value feasible bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound, - ideal_clk_to_q, - setuphold_time), + if type(ideal_clk_to_q)!=float: + debug.error("Initial hold time fails for data value feasible " + "bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound, + ideal_clk_to_q, + setuphold_time), 2) - if mode == "SETUP": # SETUP is clk-din, not din-clk - setuphold_time *= -1e9 - else: - setuphold_time *= 1e9 - - passing_setuphold_time = setuphold_time debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode, setuphold_time, feasible_bound, 2 * self.period)) - #raw_input("Press Enter to continue...") while True: target_time = (feasible_bound + infeasible_bound) / 2 @@ -224,15 +206,14 @@ class setup_hold(): self.stim.run_sim(self.stim_sp) 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 - else: - setuphold_time *= 1e9 - - debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time)) + # We use a 1/2 speed clock for some reason... + setuphold_time = (target_time - 2 * self.period) + if mode == "SETUP": # SETUP is clk-din, not din-clk + passing_setuphold_time = -1 * setuphold_time + else: passing_setuphold_time = setuphold_time + if type(clk_to_q) == float and (clk_to_q < 1.1 * ideal_clk_to_q): + debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time)) feasible_bound = target_time else: debug.info(2, "FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time)) @@ -242,7 +223,6 @@ class setup_hold(): debug.info(3, "CONVERGE {0} vs {1}".format(feasible_bound, infeasible_bound)) break - debug.info(2, "Converged on {0} time {1}.".format(mode, passing_setuphold_time)) return passing_setuphold_time diff --git a/compiler/characterizer/simulation.py b/compiler/characterizer/simulation.py index 5becbacf..e985e951 100644 --- a/compiler/characterizer/simulation.py +++ b/compiler/characterizer/simulation.py @@ -586,7 +586,7 @@ class simulation(): bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) if OPTS.use_pex and OPTS.pex_exe[0] != "calibre": for i in range(len(bl_names)): - bl_names[i] = bl_names[i].split('.')[-1] + bl_names[i] = bl_names[i].split(OPTS.hier_seperator)[-1] return bl_names[0], bl_names[1] def get_empty_measure_data_dict(self): diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index d60cab85..49fbc97c 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -146,7 +146,7 @@ class stimuli(): edge. The first clk_time should be 0 and is the initial time that corresponds to the initial value. """ - # the initial value is not a clock time + str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}" debug.check(len(clk_times)==len(data_values), str.format(len(clk_times), @@ -181,7 +181,7 @@ class stimuli(): def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): """ Creates the .meas statement for the measurement of delay """ measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n" - self.sf.write(measure_string.format(meas_name, + self.sf.write(measure_string.format(meas_name.lower(), trig_name, trig_val, trig_dir, @@ -194,7 +194,7 @@ class stimuli(): def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td): """ Creates the .meas statement for the measurement of delay """ measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n" - self.sf.write(measure_string.format(meas_name, + self.sf.write(measure_string.format(meas_name.lower(), targ_name, trig_name, trig_val, @@ -204,7 +204,7 @@ class stimuli(): def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at): """ Creates the .meas statement for voltage at time""" measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n" - self.sf.write(measure_string.format(meas_name, + self.sf.write(measure_string.format(meas_name.lower(), targ_name, time_at)) @@ -215,13 +215,13 @@ class stimuli(): power_exp = "power" else: power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')" - self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name, + self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(), power_exp, t_initial, t_final)) def gen_meas_value(self, meas_name, dout, t_initial, t_final): - measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_initial, t_final) + measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final) self.sf.write(measure_string) def write_control(self, end_time, runlvl=4): @@ -238,8 +238,8 @@ class stimuli(): reltol = 0.001 # 0.1% timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. - self.sf.write(".TEMP {}\n".format(self.temperature)) if OPTS.spice_name == "ngspice": + self.sf.write(".TEMP {}\n".format(self.temperature)) # UIC is needed for ngspice to converge self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) # ngspice sometimes has convergence problems if not using gear method @@ -248,6 +248,7 @@ class stimuli(): # unless you figure out what these are. self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol)) elif OPTS.spice_name == "spectre": + self.sf.write(".TEMP {}\n".format(self.temperature)) self.sf.write("simulator lang=spectre\n") if OPTS.use_pex: nestlvl = 1 @@ -255,8 +256,7 @@ class stimuli(): else: nestlvl = 10 spectre_save = "lvlpub" - self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format( - spectre_save, nestlvl)) + self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(spectre_save, nestlvl)) self.sf.write("simulatorOptions options reltol=1e-3 vabstol=1e-6 iabstol=1e-12 temp={0} try_fast_op=no " "rforce=10m maxnotes=10 maxwarns=10 " " preservenode=all topcheck=fixall " @@ -265,12 +265,19 @@ class stimuli(): self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate ' ' annotate=status maxiters=5 \n'.format("5p", end_time)) self.sf.write("simulator lang=spice\n") - else: + elif OPTS.spice_name in ["hspice", "xa"]: + self.sf.write(".TEMP {}\n".format(self.temperature)) self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl)) - if OPTS.spice_name == "hspice": # for cadence plots - self.sf.write(".OPTIONS PSF=1 \n") - self.sf.write(".OPTIONS HIER_DELIM=1 \n") + self.sf.write(".OPTIONS PSF=1 \n") + self.sf.write(".OPTIONS HIER_DELIM=1 \n") + elif OPTS.spice_name in ["Xyce", "xyce"]: + self.sf.write(".OPTIONS DEVICE TEMP={}\n".format(self.temperature)) + self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n") + self.sf.write(".OPTIONS LINSOL type=klu\n") + self.sf.write(".TRAN {0}p {1}n\n".format(timestep, end_time)) + else: + debug.error("Unkown spice simulator {}".format(OPTS.spice_name)) # create plots for all signals if not OPTS.use_pex: # Don't save all for extracted simulations @@ -278,7 +285,7 @@ class stimuli(): if OPTS.verbose_level>0: if OPTS.spice_name in ["hspice", "xa"]: self.sf.write(".probe V(*)\n") - else: + elif OPTS.spice_name != "Xyce": self.sf.write(".plot V(*)\n") else: self.sf.write("*.probe V(*)\n") @@ -312,7 +319,10 @@ class stimuli(): # Adding a commented out supply for simulators where gnd and 0 are not global grounds. self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n") - self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0)) + if OPTS.spice_name in ["Xyce", "xyce"]: + self.sf.write("V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0)) + else: + self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0)) def run_sim(self, name): """ Run hspice in batch mode and output rawfile to parse. """ @@ -349,6 +359,19 @@ class stimuli(): temp_stim, OPTS.openram_temp) valid_retcode=0 + elif OPTS.spice_name in ["Xyce", "xyce"]: + if OPTS.num_sim_threads > 1 and OPTS.mpi_name: + mpi_cmd = "{0} -np {1}".format(OPTS.mpi_exe, + OPTS.num_sim_threads) + else: + mpi_cmd = "" + + cmd = "{0} {1} -o {3}timing.lis {2}".format(mpi_cmd, + OPTS.spice_exe, + temp_stim, + OPTS.openram_temp) + + valid_retcode=0 else: # ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit # Measurements can't be made with a raw file set in ngspice diff --git a/compiler/custom/nand2_dec.py b/compiler/custom/nand2_dec.py index 98992f42..893bb34f 100644 --- a/compiler/custom/nand2_dec.py +++ b/compiler/custom/nand2_dec.py @@ -70,3 +70,7 @@ class nand2_dec(design.design): """ self.add_graph_edges(graph, port_nets) + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False diff --git a/compiler/custom/nand3_dec.py b/compiler/custom/nand3_dec.py index 34890a9c..a887e38f 100644 --- a/compiler/custom/nand3_dec.py +++ b/compiler/custom/nand3_dec.py @@ -70,3 +70,7 @@ class nand3_dec(design.design): """ self.add_graph_edges(graph, port_nets) + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False diff --git a/compiler/custom/nand4_dec.py b/compiler/custom/nand4_dec.py index d89dc926..d3b6491d 100644 --- a/compiler/custom/nand4_dec.py +++ b/compiler/custom/nand4_dec.py @@ -70,3 +70,7 @@ class nand4_dec(design.design): """ self.add_graph_edges(graph, port_nets) + def is_non_inverting(self): + """Return input to output polarity for module""" + + return False diff --git a/compiler/example_configs/example_config_scn4m_subm.py b/compiler/example_configs/example_config_scn4m_subm.py index d331c1fc..8fc92169 100644 --- a/compiler/example_configs/example_config_scn4m_subm.py +++ b/compiler/example_configs/example_config_scn4m_subm.py @@ -11,7 +11,7 @@ process_corners = ["TT"] supply_voltages = [5.0] temperatures = [25] -route_supplies = True +route_supplies = "side" check_lvsdrc = True output_name = "sram_{0}rw{1}r{2}w_{3}_{4}_{5}".format(num_rw_ports, diff --git a/compiler/example_configs/sky130_sram_1kbyte_1r1w_8x1024_8.py b/compiler/example_configs/sky130_sram_1kbyte_1r1w_8x1024_8.py new file mode 100644 index 00000000..5d86dff6 --- /dev/null +++ b/compiler/example_configs/sky130_sram_1kbyte_1r1w_8x1024_8.py @@ -0,0 +1,20 @@ +""" +Pseudo-dual port (independent read and write ports), 8bit word, 1 kbyte SRAM. + +Useful as a byte FIFO between two devices (the reader and the writer). +""" +word_size = 8 # Bits +num_words = 1024 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Dual port +num_rw_ports = 0 +num_r_ports = 1 +num_w_ports = 1 +ports_human = '1r1w' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_1kbyte_1rw1r_32x256_8.py b/compiler/example_configs/sky130_sram_1kbyte_1rw1r_32x256_8.py new file mode 100644 index 00000000..51d64589 --- /dev/null +++ b/compiler/example_configs/sky130_sram_1kbyte_1rw1r_32x256_8.py @@ -0,0 +1,21 @@ +""" +Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write. + +FIXME: What is this useful for? +FIXME: Why would you want byte write on this? +""" +word_size = 32 # Bits +num_words = 256 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Dual port +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 +ports_human = '1rw1r' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_1kbyte_1rw1r_8x1024_8.py b/compiler/example_configs/sky130_sram_1kbyte_1rw1r_8x1024_8.py new file mode 100644 index 00000000..ef62221c --- /dev/null +++ b/compiler/example_configs/sky130_sram_1kbyte_1rw1r_8x1024_8.py @@ -0,0 +1,21 @@ +""" +Dual port (1 read/write + 1 read only) 1 kbytes SRAM with byte write. + +FIXME: What is this useful for? +FIXME: Why would you want byte write on this? +""" +word_size = 8 # Bits +num_words = 1024 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Dual port +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 +ports_human = '1rw1r' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_1kbyte_1rw_32x256_8.py b/compiler/example_configs/sky130_sram_1kbyte_1rw_32x256_8.py new file mode 100644 index 00000000..955d3959 --- /dev/null +++ b/compiler/example_configs/sky130_sram_1kbyte_1rw_32x256_8.py @@ -0,0 +1,19 @@ +""" +Single port, 1 kbytes SRAM, with byte write, useful for RISC-V processor main +memory. +""" +word_size = 32 # Bits +num_words = 256 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Single port +num_rw_ports = 1 +num_r_ports = 0 +num_w_ports = 0 +ports_human = '1rw' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_2kbyte_1rw1r_32x512_8.py b/compiler/example_configs/sky130_sram_2kbyte_1rw1r_32x512_8.py new file mode 100644 index 00000000..0492902a --- /dev/null +++ b/compiler/example_configs/sky130_sram_2kbyte_1rw1r_32x512_8.py @@ -0,0 +1,21 @@ +""" +Dual port (1 read/write + 1 read only), 2 kbytes SRAM (with byte write). + +FIXME: What is this useful for? +FIXME: Why would you want byte write on this? +""" +word_size = 32 # Bits +num_words = 512 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Dual port +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 +ports_human = '1rw1r' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_2kbyte_1rw_32x512_8.py b/compiler/example_configs/sky130_sram_2kbyte_1rw_32x512_8.py new file mode 100644 index 00000000..7f64d18c --- /dev/null +++ b/compiler/example_configs/sky130_sram_2kbyte_1rw_32x512_8.py @@ -0,0 +1,19 @@ +""" +Single port, 2 kbytes SRAM, with byte write, useful for RISC-V processor main +memory. +""" +word_size = 32 # Bits +num_words = 512 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Single port +num_rw_ports = 1 +num_r_ports = 0 +num_w_ports = 0 +ports_human = '1rw' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_4kbyte_1rw1r_32x1024_8.py b/compiler/example_configs/sky130_sram_4kbyte_1rw1r_32x1024_8.py new file mode 100644 index 00000000..8d2f40e0 --- /dev/null +++ b/compiler/example_configs/sky130_sram_4kbyte_1rw1r_32x1024_8.py @@ -0,0 +1,22 @@ +""" +Dual port (1 read/write + 1 read only), 4 kbytes SRAM (with byte write). + +FIXME: What is this useful for? +FIXME: Why would you want byte write on this? +""" + +word_size = 32 # Bits +num_words = 1024 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Dual port +num_rw_ports = 1 +num_r_ports = 1 +num_w_ports = 0 +ports_human = '1rw1r' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_4kbyte_1rw_32x1024_8.py b/compiler/example_configs/sky130_sram_4kbyte_1rw_32x1024_8.py new file mode 100644 index 00000000..571ca030 --- /dev/null +++ b/compiler/example_configs/sky130_sram_4kbyte_1rw_32x1024_8.py @@ -0,0 +1,20 @@ +""" +Single port, 4 kbytes SRAM, with byte write, useful for RISC-V processor main +memory. +""" + +word_size = 32 # Bits +num_words = 1024 +human_byte_size = "{:.0f}kbytes".format((word_size * num_words)/1024/8) + +# Allow byte writes +write_size = 8 # Bits + +# Single port +num_rw_ports = 1 +num_r_ports = 0 +num_w_ports = 0 +ports_human = '1rw' + +import os +exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) diff --git a/compiler/example_configs/sky130_sram_common.py b/compiler/example_configs/sky130_sram_common.py new file mode 100644 index 00000000..0e3443b1 --- /dev/null +++ b/compiler/example_configs/sky130_sram_common.py @@ -0,0 +1,19 @@ +# Include with +# import os +# exec(open(os.path.join(os.path.dirname(__file__), 'sky130_sram_common.py')).read()) + + +tech_name = "sky130" +nominal_corner_only = True + +# Local wordlines have issues with met3 power routing for now +#local_array_size = 16 + +#route_supplies = False +check_lvsdrc = True +#perimeter_pins = False +#netlist_only = True +#analytical_delay = False + +output_name = "{tech_name}_sram_{human_byte_size}_{ports_human}_{word_size}x{num_words}_{write_size}".format(**locals()) +output_path = "macro/{output_name}".format(**locals()) diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index bd9968dc..71e5498c 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -422,7 +422,8 @@ class VlsiLayout: self.structures[self.rootStructureName].texts.append(textToAdd) def padText(self, text): - if(len(text)%2 == 1): + debug.check(len(text) > 0, "Cannot have zero length text string.") + if(len(text) % 2 == 1): return text + '\x00' else: return text @@ -696,7 +697,6 @@ class VlsiLayout: return max_pins - def getAllPinShapes(self, pin_name): """ Search for a pin label and return ALL the enclosing rectangles on the same layer diff --git a/compiler/globals.py b/compiler/globals.py index 0df210ca..d64c727f 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -22,7 +22,7 @@ import getpass import subprocess -VERSION = "1.1.14" +VERSION = "1.1.15" NAME = "OpenRAM v{}".format(VERSION) USAGE = "openram.py [options] \nUse -h for help.\n" @@ -66,7 +66,7 @@ def parse_args(): optparse.make_option("-m", "--sim_threads", action="store", type="int", - help="Specify the number of spice simulation threads (default: 2)", + help="Specify the number of spice simulation threads (default: 3)", dest="num_sim_threads"), optparse.make_option("-v", "--verbose", @@ -329,7 +329,7 @@ def read_config(config_file, is_unit_test=True): debug.info(1, "Configuration file is " + config_file + ".py") try: config = importlib.import_module(module_name) - except: + except ImportError: debug.error("Unable to read configuration file: {0}".format(config_file), 2) OPTS.overridden = {} diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 9c2b2add..a10a4924 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -75,6 +75,11 @@ class bank(design.design): self.bank_array_ll = self.offset_all_coordinates().scale(-1, -1) self.bank_array_ur = self.bitcell_array_inst.ur() self.bank_array_ul = self.bitcell_array_inst.ul() + + # These are used for other placements (e.g. address flops) + self.predecoder_top = self.port_address[0].predecoder_height + self.port_address_inst[0].by() + self.predecoder_bottom = self.port_address_inst[0].by() + self.DRC_LVS() def add_pins(self): @@ -227,7 +232,6 @@ class bank(design.design): x_offset = self.m2_gap + self.port_address[port].width self.port_address_offsets[port] = vector(-x_offset, self.main_bitcell_array_bottom) - self.predecoder_height = self.port_address[port].predecoder_height + self.port_address_offsets[port].y # LOWER LEFT QUADRANT # Place the col decoder left aligned with wordline driver @@ -1071,7 +1075,7 @@ class bank(design.design): """ Gets the spice name of the target bitcell. """ - return self.bitcell_array_inst.mod.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, + return self.bitcell_array_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col) diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index 9d1cc0de..238d499f 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -121,4 +121,4 @@ class bitcell_array(bitcell_base_array): def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col] + return inst_name + "{}x".format(OPTS.hier_seperator) + self.cell_inst[row, col].name, self.cell_inst[row, col] diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index eb9a21ed..52c1dd87 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -346,9 +346,12 @@ class control_logic(design.design): row += 1 self.place_wlen_row(row) row += 1 - self.place_delay(row) + + control_center_y = self.wl_en_inst.uy() + self.m3_pitch + + # Delay chain always gets placed at row 4 + self.place_delay(4) height = self.delay_inst.uy() - control_center_y = self.delay_inst.by() # This offset is used for placement of the control logic in the SRAM level. self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y) @@ -387,19 +390,22 @@ class control_logic(design.design): def place_delay(self, row): """ Place the replica bitline """ - y_off = row * self.and2.height + 2 * self.m1_pitch + debug.check(row % 2 == 0, "Must place delay chain at even row for supply alignment.") + + # It is flipped on X axis + y_off = row * self.and2.height + self.delay_chain.height # Add the RBL above the rows # Add to the right of the control rows and routing channel - offset = vector(self.delay_chain.width, y_off) - self.delay_inst.place(offset, mirror="MY") + offset = vector(0, y_off) + self.delay_inst.place(offset, mirror="MX") def route_delay(self): - out_pos = self.delay_inst.get_pin("out").bc() + out_pos = self.delay_inst.get_pin("out").center() # Connect to the rail level with the vdd rail - # Use pen since it is in every type of control logic - vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by() + # Use gated clock since it is in every type of control logic + vdd_ypos = self.gated_clk_buf_inst.get_pin("vdd").cy() + self.m1_pitch in_pos = vector(self.input_bus["rbl_bl_delay"].cx(), vdd_ypos) mid1 = vector(out_pos.x, in_pos.y) self.add_wire(self.m1_stack, [out_pos, mid1, in_pos]) @@ -676,7 +682,7 @@ class control_logic(design.design): # Connect the clock rail to the other clock rail # by routing in the supply rail track to avoid channel conflicts in_pos = self.ctrl_dff_inst.get_pin("clk").uc() - mid_pos = in_pos + vector(0, self.and2.height) + mid_pos = vector(in_pos.x, self.gated_clk_buf_inst.get_pin("vdd").cy() - self.m1_pitch) rail_pos = vector(self.input_bus["clk_buf"].cx(), mid_pos.y) self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos]) self.add_via_center(layers=self.m1_stack, @@ -794,3 +800,8 @@ class control_logic(design.design): to_layer="m2", offset=out_pos) + def get_left_pins(self, name): + """ + Return the left side supply pins to connect to a vertical stripe. + """ + return(self.cntrl_dff_inst.get_pins(name) + self.delay_inst.get_pins(name)) diff --git a/compiler/modules/delay_chain.py b/compiler/modules/delay_chain.py index d7e9c2d0..7e4830c9 100644 --- a/compiler/modules/delay_chain.py +++ b/compiler/modules/delay_chain.py @@ -31,6 +31,7 @@ class delay_chain(design.design): # number of inverters including any fanout loads. self.fanout_list = fanout_list + self.rows = len(self.fanout_list) self.create_netlist() if not OPTS.netlist_only: @@ -43,10 +44,10 @@ class delay_chain(design.design): def create_layout(self): # Each stage is a a row - self.height = len(self.fanout_list) * self.inv.height + self.height = self.rows * self.inv.height # The width is determined by the largest fanout plus the driver self.width = (max(self.fanout_list) + 1) * self.inv.width - + self.place_inverters() self.route_inverters() self.route_supplies() @@ -62,14 +63,19 @@ class delay_chain(design.design): self.add_pin("gnd", "GROUND") def add_modules(self): - self.inv = factory.create(module_type="pinv") + + self.dff = factory.create(module_type="dff_buf") + dff_height = self.dff.height + + self.inv = factory.create(module_type="pinv", + height=dff_height) self.add_mod(self.inv) def create_inverters(self): """ Create the inverters and connect them based on the stage list """ self.driver_inst_list = [] self.load_inst_map = {} - for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list): + for stage_num, fanout_size in zip(range(self.rows), self.fanout_list): # Add the inverter cur_driver=self.add_inst(name="dinv{}".format(stage_num), mod=self.inv) @@ -77,7 +83,7 @@ class delay_chain(design.design): self.driver_inst_list.append(cur_driver) # Hook up the driver - if stage_num + 1 == len(self.fanout_list): + if stage_num + 1 == self.rows: stageout_name = "out" else: stageout_name = "dout_{}".format(stage_num + 1) @@ -101,7 +107,7 @@ class delay_chain(design.design): def place_inverters(self): """ Place the inverters and connect them based on the stage list """ - for stage_num, fanout_size in zip(range(len(self.fanout_list)), self.fanout_list): + for stage_num, fanout_size in zip(range(self.rows), self.fanout_list): if stage_num % 2: inv_mirror = "MX" inv_offset = vector(0, (stage_num + 1) * self.inv.height) @@ -185,24 +191,26 @@ class delay_chain(design.design): def add_layout_pins(self): # input is A pin of first inverter + # It gets routed to the left a bit to prevent pin access errors + # due to the output pin when going up to M3 a_pin = self.driver_inst_list[0].get_pin("A") + mid_loc = vector(a_pin.cx() - self.m3_pitch, a_pin.cy()) self.add_via_stack_center(from_layer=a_pin.layer, - to_layer="m2", - offset=a_pin.center()) - self.add_layout_pin(text="in", - layer="m2", - offset=a_pin.ll().scale(1, 0), - height=a_pin.cy()) + to_layer="m2", + offset=mid_loc) + self.add_path(a_pin.layer, [a_pin.center(), mid_loc]) + + self.add_layout_pin_rect_center(text="in", + layer="m2", + offset=mid_loc) - # output is A pin of last load inverter + # output is A pin of last load/fanout inverter last_driver_inst = self.driver_inst_list[-1] a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A") self.add_via_stack_center(from_layer=a_pin.layer, - to_layer="m2", + to_layer="m1", offset=a_pin.center()) - mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy()) - self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)]) - self.add_layout_pin_segment_center(text="out", - layer="m2", - start=mid_point, - end=mid_point.scale(1, 0)) + self.add_layout_pin_rect_center(text="out", + layer="m1", + offset=a_pin.center()) + diff --git a/compiler/modules/global_bitcell_array.py b/compiler/modules/global_bitcell_array.py index 655cdbcf..dbd56e35 100644 --- a/compiler/modules/global_bitcell_array.py +++ b/compiler/modules/global_bitcell_array.py @@ -11,6 +11,7 @@ from sram_factory import factory from vector import vector import debug from numpy import cumsum +from tech import layer_properties as layer_props class global_bitcell_array(bitcell_base_array.bitcell_base_array): @@ -223,11 +224,20 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array): new_name = "{0}_{1}".format(base_name, col + col_value) self.copy_layout_pin(inst, pin_name, new_name) + # Add the global word lines + wl_layer = layer_props.global_bitcell_array.wordline_layer + for wl_name in self.local_mods[0].get_inputs(): + for local_inst in self.local_insts: + wl_pin = local_inst.get_pin(wl_name) + self.add_via_stack_center(from_layer=wl_pin.layer, + to_layer=wl_layer, + offset=wl_pin.center()) + left_pin = self.local_insts[0].get_pin(wl_name) right_pin = self.local_insts[-1].get_pin(wl_name) self.add_layout_pin_segment_center(text=wl_name, - layer=left_pin.layer, + layer=wl_layer, start=left_pin.lc(), end=right_pin.rc()) @@ -320,7 +330,7 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array): # We must also translate the global array column number to the local array column number local_col = col - self.col_offsets[i - 1] - return local_array.get_cell_name(inst_name + '.x' + local_inst.name, row, local_col) + return local_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + local_inst.name, row, local_col) def clear_exclude_bits(self): """ diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index c2ed5949..f83516d2 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -85,7 +85,6 @@ class hierarchical_predecode(design.design): self.bus_layer = layer_props.hierarchical_predecode.bus_layer self.bus_directions = layer_props.hierarchical_predecode.bus_directions - if self.column_decoder: # Column decoders may be routed on M2/M3 if there's a write mask self.bus_pitch = self.m3_pitch @@ -119,7 +118,8 @@ class hierarchical_predecode(design.design): self.input_rails = self.create_vertical_bus(layer=self.bus_layer, offset=offset, names=input_names, - length=self.height - 2 * self.bus_pitch) + length=self.height - 2 * self.bus_pitch, + pitch=self.bus_pitch) invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)] non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)] @@ -128,7 +128,8 @@ class hierarchical_predecode(design.design): self.decode_rails = self.create_vertical_bus(layer=self.bus_layer, offset=offset, names=decode_names, - length=self.height - 2 * self.bus_pitch) + length=self.height - 2 * self.bus_pitch, + pitch=self.bus_pitch) def create_input_inverters(self): """ Create the input inverters to invert input signals for the decode stage. """ @@ -180,10 +181,12 @@ class hierarchical_predecode(design.design): mirror=mirror) def route(self): + self.route_input_inverters() + self.route_output_inverters() self.route_inputs_to_rails() - self.route_and_to_rails() - self.route_output_and() + self.route_input_ands() + self.route_output_ands() self.route_vdd_gnd() def route_inputs_to_rails(self): @@ -215,7 +218,7 @@ class hierarchical_predecode(design.design): to_layer=self.bus_layer, offset=[self.decode_rails[a_pin].cx(), y_offset]) - def route_output_and(self): + def route_output_ands(self): """ Route all conections of the outputs and gates """ @@ -230,12 +233,40 @@ class hierarchical_predecode(design.design): def route_input_inverters(self): """ - Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd] + Route all conections of the inverter inputs + """ + for inv_num in range(self.number_of_inputs): + in_pin = "in_{}".format(inv_num) + + # route input + pin = self.inv_inst[inv_num].get_pin("A") + inv_in_pos = pin.center() + in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y) + self.add_path(self.input_layer, [in_pos, inv_in_pos]) + + # Inverter input pin + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.input_layer, + offset=inv_in_pos) + # Input rail pin position + via=self.add_via_stack_center(from_layer=self.input_layer, + to_layer=self.bus_layer, + offset=in_pos, + directions=self.bus_directions) + + # Create the input pin at this location on the rail + self.add_layout_pin_rect_center(text=in_pin, + layer=self.bus_layer, + offset=in_pos, + height=via.mod.second_layer_height, + width=via.mod.second_layer_width) + + def route_output_inverters(self): + """ + Route all conections of the inverter outputs """ for inv_num in range(self.number_of_inputs): out_pin = "Abar_{}".format(inv_num) - in_pin = "in_{}".format(inv_num) - inv_out_pin = self.inv_inst[inv_num].get_pin("Z") # add output so that it is just below the vdd or gnd rail @@ -255,31 +286,11 @@ class hierarchical_predecode(design.design): offset=rail_pos, directions=self.bus_directions) - # route input - pin = self.inv_inst[inv_num].get_pin("A") - inv_in_pos = pin.center() - in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y) - self.add_path(self.input_layer, [in_pos, inv_in_pos]) - self.add_via_stack_center(from_layer=pin.layer, - to_layer=self.input_layer, - offset=inv_in_pos) - via=self.add_via_stack_center(from_layer=self.input_layer, - to_layer=self.bus_layer, - offset=in_pos) - # Create the input pin at this location on the rail - self.add_layout_pin_rect_center(text=in_pin, - layer=self.bus_layer, - offset=in_pos, - height=via.mod.second_layer_height, - width=via.mod.second_layer_width) + def route_input_ands(self): + """ + Route the different permutations of the NAND/AND decocer cells. + """ - # This is a hack to fix via-to-via spacing issues, but it is currently - # causing its own DRC problems. - # if layer_props.hierarchical_predecode.vertical_supply: - # below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2)) - # self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2) - - def route_and_to_rails(self): # This 2D array defines the connection mapping and_input_line_combination = self.get_and_input_line_combination() for k in range(self.number_of_outputs): diff --git a/compiler/modules/local_bitcell_array.py b/compiler/modules/local_bitcell_array.py index f0427c51..e48cff5c 100644 --- a/compiler/modules/local_bitcell_array.py +++ b/compiler/modules/local_bitcell_array.py @@ -10,6 +10,7 @@ from globals import OPTS from sram_factory import factory from vector import vector import debug +from tech import layer_properties as layer_props class local_bitcell_array(bitcell_base_array.bitcell_base_array): @@ -190,6 +191,11 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array): def route(self): + global_wl_layer = layer_props.global_bitcell_array.wordline_layer + global_wl_pitch = getattr(self, "{}_pitch".format(global_wl_layer)) + global_wl_pitch_factor = layer_props.global_bitcell_array.wordline_pitch_factor + local_wl_layer = layer_props.local_bitcell_array.wordline_layer + # Route the global wordlines for port in self.all_ports: if port == 0: @@ -204,25 +210,33 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array): in_pin = self.wl_insts[port].get_pin(in_pin_name) y_offset = in_pin.cy() + if port == 0: - y_offset -= 2 * self.m3_pitch + y_offset -= global_wl_pitch_factor * global_wl_pitch else: - y_offset += 2 * self.m3_pitch - - self.add_layout_pin_segment_center(text=wl_name, - layer="m3", - start=vector(self.wl_insts[port].lx(), y_offset), - end=vector(self.wl_insts[port].lx() + self.wl_array.width, y_offset)) - + y_offset += global_wl_pitch_factor * global_wl_pitch mid = vector(in_pin.cx(), y_offset) - self.add_path("m2", [in_pin.center(), mid]) + self.add_layout_pin_rect_center(text=wl_name, + layer=global_wl_layer, + offset=mid) + + self.add_path(local_wl_layer, [in_pin.center(), mid]) + + # A short jog to the global line self.add_via_stack_center(from_layer=in_pin.layer, - to_layer="m2", - offset=in_pin.center()) - self.add_via_center(self.m2_stack, - offset=mid) - + to_layer=local_wl_layer, + offset=in_pin.center(), + min_area=True) + self.add_path(local_wl_layer, [in_pin.center(), mid]) + self.add_via_stack_center(from_layer=local_wl_layer, + to_layer=global_wl_layer, + offset=mid, + min_area=True) + # Add the global WL pin + self.add_layout_pin_rect_center(text=wl_name, + layer=global_wl_layer, + offset=mid) # Route the buffers for port in self.all_ports: driver_outputs = self.driver_wordline_outputs[port] @@ -281,7 +295,7 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array): def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) + return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col) def clear_exclude_bits(self): """ diff --git a/compiler/modules/orig_bitcell_array.py b/compiler/modules/orig_bitcell_array.py index 8bf498a4..42ebdc33 100644 --- a/compiler/modules/orig_bitcell_array.py +++ b/compiler/modules/orig_bitcell_array.py @@ -114,4 +114,4 @@ class bitcell_array(bitcell_base_array): def get_cell_name(self, inst_name, row, col): """Gets the spice name of the target bitcell.""" - return inst_name + '.x' + self.cell_inst[row, col].name, self.cell_inst[row, col] + return inst_name + "{}x".format(OPTS.hier_seperator) + self.cell_inst[row, col].name, self.cell_inst[row, col] diff --git a/compiler/modules/replica_bitcell_array.py b/compiler/modules/replica_bitcell_array.py index 828941ae..0173f1a0 100644 --- a/compiler/modules/replica_bitcell_array.py +++ b/compiler/modules/replica_bitcell_array.py @@ -553,7 +553,7 @@ class replica_bitcell_array(bitcell_base_array): """ Gets the spice name of the target bitcell. """ - return self.bitcell_array.get_cell_name(inst_name + '.x' + self.bitcell_array_inst.name, row, col) + return self.bitcell_array.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bitcell_array_inst.name, row, col) def clear_exclude_bits(self): """ diff --git a/compiler/modules/write_mask_and_array.py b/compiler/modules/write_mask_and_array.py index c0bd8b84..2ccf34a1 100644 --- a/compiler/modules/write_mask_and_array.py +++ b/compiler/modules/write_mask_and_array.py @@ -117,9 +117,10 @@ class write_mask_and_array(design.design): for i in range(self.num_wmasks): # Route the A pin over to the left so that it doesn't conflict with the sense # amp output which is usually in the center - a_pin = self.and2_insts[i].get_pin("A") + inst = self.and2_insts[i] + a_pin = inst.get_pin("A") a_pos = a_pin.center() - in_pos = vector(self.and2_insts[i].lx(), + in_pos = vector(inst.lx(), a_pos.y) self.add_via_stack_center(from_layer=a_pin.layer, to_layer="m2", @@ -130,21 +131,31 @@ class write_mask_and_array(design.design): self.add_path(a_pin.layer, [in_pos, a_pos]) # Copy remaining layout pins - self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i)) + self.copy_layout_pin(inst, "Z", "wmask_out_{0}".format(i)) # Add via connections to metal3 for AND array's B pin - en_pin = self.and2_insts[i].get_pin("B") + en_pin = inst.get_pin("B") en_pos = en_pin.center() self.add_via_stack_center(from_layer=en_pin.layer, to_layer="m3", offset=en_pos) + + # Add connection to the supply + for supply_name in ["gnd", "vdd"]: + supply_pin = inst.get_pin(supply_name) + self.add_via_stack_center(from_layer=supply_pin.layer, + to_layer="m1", + offset=supply_pin.center()) for supply in ["gnd", "vdd"]: supply_pin = self.and2_insts[0].get_pin(supply) supply_pin_yoffset = supply_pin.cy() left_loc = vector(0, supply_pin_yoffset) right_loc = vector(self.width, supply_pin_yoffset) - self.add_path(supply_pin.layer, [left_loc, right_loc]) - self.copy_power_pin(supply_pin, loc=left_loc) - self.copy_power_pin(supply_pin, loc=right_loc) + self.add_path("m1", [left_loc, right_loc]) + for loc in [left_loc, right_loc]: + self.add_via_stack_center(from_layer=supply_pin.layer, + to_layer="m1", + offset=loc) + self.copy_power_pin(supply_pin, loc=loc) diff --git a/compiler/options.py b/compiler/options.py index c163a491..214eecf7 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -6,9 +6,9 @@ # All rights reserved. # import optparse -import getpass import os + class options(optparse.Values): """ Class for holding all of the OpenRAM options. All @@ -137,8 +137,11 @@ class options(optparse.Values): # Number of threads to use num_threads = 1 # Number of threads to use in ngspice/hspice - num_sim_threads = 2 + num_sim_threads = 3 + # Some tools (e.g. Xyce) use other separators like ":" + hier_seperator = "." + # Should we print out the banner at startup print_banner = True @@ -155,6 +158,9 @@ class options(optparse.Values): # Route the input/output pins to the perimeter perimeter_pins = True + # Detailed or abstract LEF view + detailed_lef = False + keep_temp = False diff --git a/compiler/router/grid.py b/compiler/router/grid.py index 793e5e89..ea59c80f 100644 --- a/compiler/router/grid.py +++ b/compiler/router/grid.py @@ -122,35 +122,45 @@ class grid: self.set_target(n) # self.set_blocked(n, False) - def add_perimeter_target(self, side="all"): - debug.info(3, "Adding perimeter target") - + def get_perimeter_list(self, side="left", layers=[0, 1], width=1, margin=0, offset=0): + """ + Side specifies which side. + Layer specifies horizontal (0) or vertical (1) + Width specifies how wide the perimter "stripe" should be. + """ perimeter_list = [] # Add the left/right columns if side=="all" or side=="left": - x = self.ll.x - for y in range(self.ll.y, self.ur.y, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for x in range(self.ll.x + offset, self.ll.x + width + offset, 1): + for y in range(self.ll.y + margin, self.ur.y - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) if side=="all" or side=="right": - x = self.ur.x - for y in range(self.ll.y, self.ur.y, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for x in range(self.ur.x - width - offset, self.ur.x - offset, 1): + for y in range(self.ll.y + margin, self.ur.y - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) if side=="all" or side=="bottom": - y = self.ll.y - for x in range(self.ll.x, self.ur.x, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for y in range(self.ll.y + offset, self.ll.y + width + offset, 1): + for x in range(self.ll.x + margin, self.ur.x - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) if side=="all" or side=="top": - y = self.ur.y - for x in range(self.ll.x, self.ur.x, 1): - perimeter_list.append(vector3d(x, y, 0)) - perimeter_list.append(vector3d(x, y, 1)) + for y in range(self.ur.y - width - offset, self.ur.y - offset, 1): + for x in range(self.ll.x + margin, self.ur.x - margin, 1): + for layer in layers: + perimeter_list.append(vector3d(x, y, layer)) + return perimeter_list + + def add_perimeter_target(self, side="all", layers=[0, 1]): + debug.info(3, "Adding perimeter target") + + perimeter_list = self.get_perimeter_list(side, layers) + self.set_target(perimeter_list) def is_target(self, point): diff --git a/compiler/router/pin_group.py b/compiler/router/pin_group.py index 7a5d8817..5e6d6f89 100644 --- a/compiler/router/pin_group.py +++ b/compiler/router/pin_group.py @@ -155,6 +155,10 @@ class pin_group: # Now simplify the enclosure list new_pin_list = self.remove_redundant_shapes(pin_list) + # Now add the right name + for pin in new_pin_list: + pin.name = self.name + debug.check(len(new_pin_list) > 0, "Did not find any enclosures.") diff --git a/compiler/router/router.py b/compiler/router/router.py index 5213af4a..ca077572 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -28,7 +28,7 @@ class router(router_tech): route on a given layer. This is limited to two layer routes. It populates blockages on a grid class. """ - def __init__(self, layers, design, gds_filename=None, bbox=None, margin=0, route_track_width=1): + def __init__(self, layers, design, bbox=None, margin=0, route_track_width=1): """ This will instantiate a copy of the gds file or the module at (0,0) and route on top of this. The blockages from the gds/module will be @@ -39,19 +39,7 @@ class router(router_tech): self.cell = design - # If didn't specify a gds blockage file, write it out to read the gds - # This isn't efficient, but easy for now - # start_time = datetime.now() - if not gds_filename: - gds_filename = OPTS.openram_temp+"temp.gds" - self.cell.gds_write(gds_filename) - - # Load the gds file and read in all the shapes - self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) - self.reader = gdsMill.Gds2reader(self.layout) - self.reader.loadFromFile(gds_filename) - self.top_name = self.layout.rootStructureName - # print_time("GDS read",datetime.now(), start_time) + self.gds_filename = OPTS.openram_temp + "temp.gds" # The pin data structures # A map of pin names to a set of pin_layout structures @@ -91,6 +79,16 @@ class router(router_tech): """ Initialize the ll,ur values with the paramter or using the layout boundary. """ + + # If didn't specify a gds blockage file, write it out to read the gds + # This isn't efficient, but easy for now + # Load the gds file and read in all the shapes + self.cell.gds_write(self.gds_filename) + self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) + self.reader = gdsMill.Gds2reader(self.layout) + self.reader.loadFromFile(self.gds_filename) + self.top_name = self.layout.rootStructureName + if not bbox: # The boundary will determine the limits to the size # of the routing grid @@ -178,6 +176,17 @@ class router(router_tech): """ Find the pins and blockages in the design """ + + # If didn't specify a gds blockage file, write it out to read the gds + # This isn't efficient, but easy for now + # Load the gds file and read in all the shapes + self.cell.gds_write(self.gds_filename) + self.layout = gdsMill.VlsiLayout(units=GDS["unit"]) + self.reader = gdsMill.Gds2reader(self.layout) + self.reader.loadFromFile(self.gds_filename) + self.top_name = self.layout.rootStructureName + # print_time("GDS read",datetime.now(), start_time) + # This finds the pin shapes and sorts them into "groups" that # are connected. This must come before the blockages, so we # can not count the pins themselves @@ -881,12 +890,32 @@ class router(router_tech): # Clearing the blockage of this pin requires the inflated pins self.clear_blockages(pin_name) + def add_side_supply_pin(self, name, side="left", width=2): + """ + Adds a supply pin to the perimeter and resizes the bounding box. + """ + pg = pin_group(name, [], self) + if name == "vdd": + offset = width + else: + offset = 0 + + pg.grids = set(self.rg.get_perimeter_list(side=side, + width=width, + margin=self.margin, + offset=offset, + layers=[1])) + pg.enclosures = pg.compute_enclosures() + pg.pins = set(pg.enclosures) + self.cell.pin_map[name].update(pg.pins) + self.pin_groups[name].append(pg) + def add_perimeter_target(self, side="all"): """ This will mark all the cells on the perimeter of the original layout as a target. """ self.rg.add_perimeter_target(side=side) - + def num_pin_components(self, pin_name): """ This returns how many disconnected pin components there are. @@ -1214,12 +1243,12 @@ class router(router_tech): return self.convert_track_to_pin(v) return None - + def get_ll_pin(self, pin_name): """ Return the lowest, leftest pin group """ keep_pin = None - for index,pg in enumerate(self.pin_groups[pin_name]): + for index, pg in enumerate(self.pin_groups[pin_name]): for pin in pg.enclosures: if not keep_pin: keep_pin = pin @@ -1228,7 +1257,7 @@ class router(router_tech): keep_pin = pin return keep_pin - + def check_all_routed(self, pin_name): """ Check that all pin groups are routed. diff --git a/compiler/router/router_tech.py b/compiler/router/router_tech.py index b3e26f79..6cbbd422 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -123,7 +123,7 @@ class router_tech: min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) - min_width = drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) + min_width = self.route_track_width * drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf) min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf) return (min_width, min_spacing) diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index a9531290..7cc41d97 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -17,7 +17,7 @@ class signal_escape_router(router): A router that routes signals to perimeter and makes pins. """ - def __init__(self, layers, design, bbox=None, margin=0, gds_filename=None): + def __init__(self, layers, design, bbox=None, margin=0): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). @@ -25,7 +25,6 @@ class signal_escape_router(router): router.__init__(self, layers=layers, design=design, - gds_filename=gds_filename, bbox=bbox, margin=margin) diff --git a/compiler/router/signal_router.py b/compiler/router/signal_router.py index 89edcb01..9fbf871f 100644 --- a/compiler/router/signal_router.py +++ b/compiler/router/signal_router.py @@ -15,12 +15,12 @@ class signal_router(router): route on a given layer. This is limited to two layer routes. """ - def __init__(self, layers, design, gds_filename=None, bbox=None): + def __init__(self, layers, design, bbox=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). """ - router.__init__(self, layers, design, gds_filename, bbox) + router.__init__(self, layers, design, bbox) def route(self, src, dest, detour_scale=5): """ diff --git a/compiler/router/supply_grid_router.py b/compiler/router/supply_grid_router.py index 8a201474..f24498ab 100644 --- a/compiler/router/supply_grid_router.py +++ b/compiler/router/supply_grid_router.py @@ -21,7 +21,7 @@ class supply_grid_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, gds_filename=None, bbox=None): + def __init__(self, layers, design, margin=0, bbox=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). @@ -29,9 +29,9 @@ class supply_grid_router(router): start_time = datetime.now() # Power rail width in minimum wire widths - self.route_track_width = 2 + self.route_track_width = 1 - router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) + router.__init__(self, layers, design, bbox=bbox, margin=margin, route_track_width=self.route_track_width) # The list of supply rails (grid sets) that may be routed self.supply_rails = {} @@ -357,8 +357,9 @@ class supply_grid_router(router): # This is inefficient since it is non-incremental, but it was # easier to debug. - self.prepare_blockages(pin_name) - + self.prepare_blockages() + self.clear_blockages(self.vdd_name) + # Add the single component of the pin as the source # which unmarks it as a blockage too self.add_pin_component_source(pin_name, index) @@ -369,7 +370,7 @@ class supply_grid_router(router): # Actually run the A* router if not self.run_router(detour_scale=5): - self.write_debug_gds("debug_route.gds", False) + self.write_debug_gds("debug_route.gds") # if index==3 and pin_name=="vdd": # self.write_debug_gds("route.gds",False) diff --git a/compiler/router/supply_tree_router.py b/compiler/router/supply_tree_router.py index ba54ee39..97ba87d5 100644 --- a/compiler/router/supply_tree_router.py +++ b/compiler/router/supply_tree_router.py @@ -21,7 +21,7 @@ class supply_tree_router(router): routes a grid to connect the supply on the two layers. """ - def __init__(self, layers, design, gds_filename=None, bbox=None): + def __init__(self, layers, design, bbox=None, side_pin=None): """ This will route on layers in design. It will get the blockages from either the gds file name or the design itself (by saving to a gds file). @@ -31,11 +31,19 @@ class supply_tree_router(router): # for prettier routes. self.route_track_width = 1 - router.__init__(self, layers, design, gds_filename, bbox, self.route_track_width) + # The pin escape router already made the bounding box big enough, + # so we can use the regular bbox here. + self.side_pin = side_pin + router.__init__(self, + layers, + design, + bbox=bbox, + route_track_width=self.route_track_width) def route(self, vdd_name="vdd", gnd_name="gnd"): """ - Route the two nets in a single layer) + Route the two nets in a single layer. + Setting pin stripe will make a power rail on the left side. """ debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name)) self.vdd_name = vdd_name @@ -50,11 +58,17 @@ class supply_tree_router(router): # but this is simplest for now. self.create_routing_grid(signal_grid) - # Get the pin shapes start_time = datetime.now() + + # Get the pin shapes self.find_pins_and_blockages([self.vdd_name, self.gnd_name]) print_time("Finding pins and blockages", datetime.now(), start_time, 3) + # Add side pins if enabled + if self.side_pin: + self.add_side_supply_pin(self.vdd_name) + self.add_side_supply_pin(self.gnd_name) + # Route the supply pins to the supply rails # Route vdd first since we want it to be shorter start_time = datetime.now() @@ -87,16 +101,21 @@ class supply_tree_router(router): pin_size = len(self.pin_groups[pin_name]) adj_matrix = [[0] * pin_size for i in range(pin_size)] - for index1,pg1 in enumerate(self.pin_groups[pin_name]): - for index2,pg2 in enumerate(self.pin_groups[pin_name]): + for index1, pg1 in enumerate(self.pin_groups[pin_name]): + for index2, pg2 in enumerate(self.pin_groups[pin_name]): if index1>=index2: continue dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids)) adj_matrix[index1][index2] = dist # Find MST - debug.info(2, "Finding MinimumSpanning Tree") + debug.info(2, "Finding Minimum Spanning Tree") X = csr_matrix(adj_matrix) + from scipy.sparse import save_npz + #print("Saving {}.npz".format(self.cell.name)) + #save_npz("{}.npz".format(self.cell.name), X) + #exit(1) + Tcsr = minimum_spanning_tree(X) mst = Tcsr.toarray().astype(int) connections = [] diff --git a/compiler/sram/sram_1bank.py b/compiler/sram/sram_1bank.py index 6bc2cd43..a6eb9b71 100644 --- a/compiler/sram/sram_1bank.py +++ b/compiler/sram/sram_1bank.py @@ -120,8 +120,9 @@ class sram_1bank(sram_base): port = 0 # The row address bits are placed above the control logic aligned on the right. x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width - # It is above the control logic but below the top of the bitcell array - y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_height) + # It is above the control logic and the predecoder array + y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) + self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) @@ -130,7 +131,7 @@ class sram_1bank(sram_base): # The row address bits are placed above the control logic aligned on the left. x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.bank_array_ll.y + y_offset = self.bank.predecoder_bottom self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") @@ -419,11 +420,11 @@ class sram_1bank(sram_base): if len(route_map) > 0: # The write masks will have blockages on M1 - if self.num_wmasks > 0 and port in self.write_ports: - layer_stack = self.m3_stack - else: - layer_stack = self.m1_stack - + # if self.num_wmasks > 0 and port in self.write_ports: + # layer_stack = self.m3_stack + # else: + # layer_stack = self.m1_stack + layer_stack = self.m3_stack if port == 0: offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - self.data_bus_size[port] + 2 * self.m3_pitch) @@ -527,13 +528,13 @@ class sram_1bank(sram_base): # Only input (besides pins) is the replica bitline src_pin = self.control_logic_insts[port].get_pin("rbl_bl") dest_pin = self.bank_inst.get_pin("rbl_bl_{0}_{0}".format(port)) - self.add_wire(self.m2_stack[::-1], + self.add_wire(self.m3_stack, [src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()]) self.add_via_stack_center(from_layer=src_pin.layer, - to_layer="m2", + to_layer="m4", offset=src_pin.center()) self.add_via_stack_center(from_layer=dest_pin.layer, - to_layer="m2", + to_layer="m3", offset=dest_pin.center()) def route_row_addr_dff(self): @@ -612,7 +613,7 @@ class sram_1bank(sram_base): # Sanity check in case it was forgotten if inst_name.find("x") != 0: inst_name = "x" + inst_name - return self.bank_inst.mod.get_cell_name(inst_name + ".x" + self.bank_inst.name, row, col) + return self.bank_inst.mod.get_cell_name(inst_name + "{}x".format(OPTS.hier_seperator) + self.bank_inst.name, row, col) def get_bank_num(self, inst_name, row, col): return 0 diff --git a/compiler/sram/sram_base.py b/compiler/sram/sram_base.py index 6dacdd90..8b6d6a31 100644 --- a/compiler/sram/sram_base.py +++ b/compiler/sram/sram_base.py @@ -225,11 +225,7 @@ class sram_base(design, verilog, lef): for pin_name in ["vdd", "gnd"]: for inst in self.insts: self.copy_power_pins(inst, pin_name) - - if not OPTS.route_supplies: - # Do not route the power supply (leave as must-connect pins) - return - + try: from tech import power_grid grid_stack = power_grid @@ -238,38 +234,99 @@ class sram_base(design, verilog, lef): # Route a M3/M4 grid grid_stack = self.m3_stack - if OPTS.route_supplies == "grid": - from supply_grid_router import supply_grid_router as router - elif OPTS.route_supplies: - from supply_tree_router import supply_tree_router as router + # lowest_coord = self.find_lowest_coords() + # highest_coord = self.find_highest_coords() - rtr=router(grid_stack, self) + # # Add two rails to the side + # if OPTS.route_supplies == "side": + # supply_pins = {} + # # Find the lowest leftest pin for vdd and gnd + # for (pin_name, pin_index) in [("vdd", 0), ("gnd", 1)]: + # pin_width = 8 * getattr(self, "{}_width".format(grid_stack[2])) + # pin_space = 2 * getattr(self, "{}_space".format(grid_stack[2])) + # supply_pitch = pin_width + pin_space + + # # Add side power rails on left from bottom to top + # # These have a temporary name and will be connected later. + # # They are here to reserve space now and ensure other pins go beyond + # # their perimeter. + # supply_height = highest_coord.y - lowest_coord.y + + # supply_pins[pin_name] = self.add_layout_pin(text=pin_name, + # layer=grid_stack[2], + # offset=lowest_coord + vector(pin_index * supply_pitch, 0), + # width=pin_width, + # height=supply_height) + + if not OPTS.route_supplies: + # Do not route the power supply (leave as must-connect pins) + return + elif OPTS.route_supplies == "grid": + from supply_grid_router import supply_grid_router as router + else: + from supply_tree_router import supply_tree_router as router + + rtr=router(grid_stack, self, side_pin=(OPTS.route_supplies == "side")) rtr.route() - # Find the lowest leftest pin for vdd and gnd - for pin_name in ["vdd", "gnd"]: - # Copy the pin shape(s) to rectangles - for pin in self.get_pins(pin_name): + if OPTS.route_supplies == "side": + # Find the lowest leftest pin for vdd and gnd + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.get_pins(pin_name): + self.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.remove_layout_pin(pin_name) + + # Get the lowest, leftest pin + pin = rtr.get_ll_pin(pin_name) + self.add_layout_pin(pin_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + elif OPTS.route_supplies: + # Update these as we may have routed outside the region (perimeter pins) + lowest_coord = self.find_lowest_coords() + + # Find the lowest leftest pin for vdd and gnd + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.get_pins(pin_name): + self.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.remove_layout_pin(pin_name) + + # Get the lowest, leftest pin + pin = rtr.get_ll_pin(pin_name) + + pin_width = 2 * getattr(self, "{}_width".format(pin.layer)) + + # Add it as an IO pin to the perimeter + route_width = pin.rx() - lowest_coord.x + pin_offset = vector(lowest_coord.x, pin.by()) self.add_rect(pin.layer, - pin.ll(), - pin.width(), + pin_offset, + route_width, pin.height()) - # Remove the pin shape(s) - self.remove_layout_pin(pin_name) - - # Get the lowest, leftest pin - pin = rtr.get_ll_pin(pin_name) - - # Add it as an IO pin to the perimeter - lowest_coord = self.find_lowest_coords() - pin_width = pin.rx() - lowest_coord.x - pin_offset = vector(lowest_coord.x, pin.by()) - self.add_layout_pin(pin_name, - pin.layer, - pin_offset, - pin_width, - pin.height()) + self.add_layout_pin(pin_name, + pin.layer, + pin_offset, + pin_width, + pin.height()) + else: + # Grid is left with many top level pins + pass def route_escape_pins(self): """ @@ -314,7 +371,7 @@ class sram_base(design, verilog, lef): from signal_escape_router import signal_escape_router as router rtr=router(layers=self.m3_stack, design=self, - margin=4 * self.m3_pitch) + margin=8 * self.m3_pitch) rtr.escape_route(pins_to_route) def compute_bus_sizes(self): diff --git a/compiler/tests/21_xyce_delay_test.py b/compiler/tests/21_xyce_delay_test.py new file mode 100755 index 00000000..04a81886 --- /dev/null +++ b/compiler/tests/21_xyce_delay_test.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS +from sram_factory import factory +import debug + + +class timing_sram_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.spice_name="xyce" + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import delay + from sram_config import sram_config + c = sram_config(word_size=4, + num_words=16, + num_banks=1) + c.words_per_row=1 + c.recompute_sizes() + debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank") + s = factory.create(module_type="sram", sram_config=c) + + tempspice = OPTS.openram_temp + "temp.sp" + s.sp_write(tempspice) + + probe_address = "1" * s.s.addr_size + probe_data = s.s.word_size - 1 + debug.info(1, "Probe address {0} probe data bit {1}".format(probe_address, probe_data)) + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + d = delay(s.s, tempspice, corner) + import tech + loads = [tech.spice["dff_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + data, port_data = d.analyze(probe_address, probe_data, slews, loads) + # Combine info about port into all data + data.update(port_data[0]) + + if OPTS.tech_name == "freepdk45": + golden_data = {'delay_hl': [0.24042560000000002], + 'delay_lh': [0.24042560000000002], + 'disabled_read0_power': [0.8981647999999998], + 'disabled_read1_power': [0.9101543999999998], + 'disabled_write0_power': [0.9270382999999998], + 'disabled_write1_power': [0.9482969999999998], + 'leakage_power': 2.9792199999999998, + 'min_period': 0.938, + 'read0_power': [1.1107930999999998], + 'read1_power': [1.1143252999999997], + 'slew_hl': [0.2800772], + 'slew_lh': [0.2800772], + 'write0_power': [1.1667769], + 'write1_power': [1.0986076999999999]} + elif OPTS.tech_name == "scn4m_subm": + golden_data = {'delay_hl': [1.884186], + 'delay_lh': [1.884186], + 'disabled_read0_power': [20.86336], + 'disabled_read1_power': [22.10636], + 'disabled_write0_power': [22.62321], + 'disabled_write1_power': [23.316010000000002], + 'leakage_power': 13.351170000000002, + 'min_period': 7.188, + 'read0_power': [29.90159], + 'read1_power': [30.47858], + 'slew_hl': [2.042723], + 'slew_lh': [2.042723], + 'write0_power': [32.13199], + 'write1_power': [28.46703]} + else: + self.assertTrue(False) # other techs fail + # Check if no too many or too few results + self.assertTrue(len(data.keys())==len(golden_data.keys())) + + self.assertTrue(self.check_golden_data(data,golden_data,0.25)) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/21_xyce_setuphold_test.py b/compiler/tests/21_xyce_setuphold_test.py new file mode 100755 index 00000000..f53212f8 --- /dev/null +++ b/compiler/tests/21_xyce_setuphold_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2021 Regents of the University of California and The Board +# of Regents for the Oklahoma Agricultural and Mechanical College +# (acting for and on behalf of Oklahoma State University) +# All rights reserved. +# +import unittest +from testutils import * +import sys, os +sys.path.append(os.getenv("OPENRAM_HOME")) +import globals +from globals import OPTS + + +class timing_setup_test(openram_test): + + def runTest(self): + config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) + globals.init_openram(config_file) + OPTS.spice_name="Xyce" + OPTS.analytical_delay = False + OPTS.netlist_only = True + + # This is a hack to reload the characterizer __init__ with the spice version + from importlib import reload + import characterizer + reload(characterizer) + from characterizer import setup_hold + import tech + slews = [tech.spice["rise_time"]*2] + + corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) + sh = setup_hold(corner) + data = sh.analyze(slews,slews) + if OPTS.tech_name == "freepdk45": + golden_data = {'hold_times_HL': [-0.0158691], + 'hold_times_LH': [-0.0158691], + 'setup_times_HL': [0.026855499999999997], + 'setup_times_LH': [0.032959]} + elif OPTS.tech_name == "scn4m_subm": + golden_data = {'hold_times_HL': [-0.0805664], + 'hold_times_LH': [-0.11718749999999999], + 'setup_times_HL': [0.16357419999999998], + 'setup_times_LH': [0.1757812]} + elif OPTS.tech_name == "sky130": + golden_data = {'hold_times_HL': [-0.05615234], + 'hold_times_LH': [-0.03173828], + 'setup_times_HL': [0.078125], + 'setup_times_LH': [0.1025391]} + else: + self.assertTrue(False) # other techs fail + + # Check if no too many or too few results + self.assertTrue(len(data.keys())==len(golden_data.keys())) + + self.assertTrue(self.check_golden_data(data,golden_data,0.25)) + + globals.end_openram() + +# run the test from the command line +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main(testRunner=debugTestRunner()) diff --git a/compiler/tests/golden/sram_2_16_1_freepdk45.v b/compiler/tests/golden/sram_2_16_1_freepdk45.v index 46f89fa5..4441b717 100644 --- a/compiler/tests/golden/sram_2_16_1_freepdk45.v +++ b/compiler/tests/golden/sram_2_16_1_freepdk45.v @@ -3,6 +3,10 @@ // Word size: 2 module sram_2_16_1_freepdk45( +`ifdef USE_POWER_PINS + vdd, + gnd, +`endif // Port 0: RW clk0,csb0,web0,addr0,din0,dout0 ); @@ -15,6 +19,10 @@ module sram_2_16_1_freepdk45( parameter VERBOSE = 1 ; //Set to 0 to only display warnings parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary +`ifdef USE_POWER_PINS + inout vdd; + inout gnd; +`endif input clk0; // clock input csb0; // active low chip select input web0; // active low write control diff --git a/compiler/tests/golden/sram_2_16_1_scn4m_subm.v b/compiler/tests/golden/sram_2_16_1_scn4m_subm.v index cb95036b..fd77a66e 100644 --- a/compiler/tests/golden/sram_2_16_1_scn4m_subm.v +++ b/compiler/tests/golden/sram_2_16_1_scn4m_subm.v @@ -3,6 +3,10 @@ // Word size: 2 module sram_2_16_1_scn4m_subm( +`ifdef USE_POWER_PINS + vdd, + gnd, +`endif // Port 0: RW clk0,csb0,web0,addr0,din0,dout0 ); @@ -15,6 +19,10 @@ module sram_2_16_1_scn4m_subm( parameter VERBOSE = 1 ; //Set to 0 to only display warnings parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary +`ifdef USE_POWER_PINS + inout vdd; + inout gnd; +`endif input clk0; // clock input csb0; // active low chip select input web0; // active low write control diff --git a/compiler/verify/magic.py b/compiler/verify/magic.py index 3983de7c..e4f0b428 100644 --- a/compiler/verify/magic.py +++ b/compiler/verify/magic.py @@ -71,9 +71,12 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa global OPTS # Copy .magicrc file into the output directory - magic_file = OPTS.openram_tech + "tech/.magicrc" + magic_file = os.environ.get('OPENRAM_MAGICRC', None) + if not magic_file: + magic_file = OPTS.openram_tech + "tech/.magicrc" + if os.path.exists(magic_file): - shutil.copy(magic_file, output_path) + shutil.copy(magic_file, output_path + "/.magicrc") else: debug.warning("Could not locate .magicrc file: {}".format(magic_file)) diff --git a/images/OpenRAM_logo_yellow_transparent.png b/images/OpenRAM_logo_yellow_transparent.png new file mode 100644 index 00000000..425d406a Binary files /dev/null and b/images/OpenRAM_logo_yellow_transparent.png differ diff --git a/images/OpenRAM_logo_yellow_transparent.svg b/images/OpenRAM_logo_yellow_transparent.svg new file mode 100644 index 00000000..5c276be7 --- /dev/null +++ b/images/OpenRAM_logo_yellow_transparent.svg @@ -0,0 +1,236 @@ + + + + + + + + image/svg+xml + + + + + + OpenRAM + + + + + + + + + + + + + + + + + + + + + + + + + + +