From 099bc4e2584c9b2238e67d560dda2e4443ab02a0 Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Mon, 20 May 2019 18:35:52 -0700 Subject: [PATCH] Added bitcell check to storage nodes. --- compiler/base/hierarchy_spice.py | 17 +++++ compiler/bitcells/bitcell.py | 18 +++++ compiler/characterizer/charutils.py | 7 +- compiler/characterizer/delay.py | 100 ++++++++++++++++++++++--- compiler/characterizer/measurements.py | 59 ++++++++------- compiler/modules/bank.py | 6 +- compiler/modules/bitcell_array.py | 4 +- compiler/sram_1bank.py | 11 ++- compiler/tests/21_hspice_delay_test.py | 4 +- 9 files changed, 181 insertions(+), 45 deletions(-) diff --git a/compiler/base/hierarchy_spice.py b/compiler/base/hierarchy_spice.py index 2efed1da..eeabb61c 100644 --- a/compiler/base/hierarchy_spice.py +++ b/compiler/base/hierarchy_spice.py @@ -182,6 +182,23 @@ class spice(): else: self.spice = [] + def check_net_in_spice(self, net_name): + """Checks if a net name exists in the current. Intended to be check nets in hand-made cells.""" + #Remove spaces and lower case then add spaces. Nets are separated by spaces. + net_formatted = ' '+net_name.lstrip().rstrip().lower()+' ' + for line in self.spice: + #Lowercase the line and remove any part of the line that is a comment. + line = line.lower().split('*')[0] + + #Skip .subckt or .ENDS lines + if line.find('.') == 0: + continue + if net_formatted in line: + return True + return False + + + def contains(self, mod, modlist): for x in modlist: if x.name == mod.name: diff --git a/compiler/bitcells/bitcell.py b/compiler/bitcells/bitcell.py index 42b8112b..30766f06 100644 --- a/compiler/bitcells/bitcell.py +++ b/compiler/bitcells/bitcell.py @@ -20,6 +20,7 @@ class bitcell(design.design): """ pin_names = ["bl", "br", "wl", "vdd", "gnd"] + internal_nets = ['Q', 'Qbar'] type_list = ["OUTPUT", "OUTPUT", "INPUT", "POWER", "GROUND"] (width,height) = utils.get_libcell_size("cell_6t", GDS["unit"], layer["boundary"]) pin_map = utils.get_libcell_pins(pin_names, "cell_6t", GDS["unit"]) @@ -33,6 +34,7 @@ class bitcell(design.design): self.height = bitcell.height self.pin_map = bitcell.pin_map self.add_pin_types(self.type_list) + self.nets_match = self.check_internal_nets() def analytical_delay(self, corner, slew, load=0, swing = 0.5): parasitic_delay = 1 @@ -77,6 +79,22 @@ class bitcell(design.design): total_power = self.return_power(dynamic, leakage) return total_power + def check_internal_nets(self): + """For handmade cell, checks sp file contains the storage nodes.""" + nets_match = True + for net in self.internal_nets: + nets_match = nets_match and self.check_net_in_spice(net) + return nets_match + + def get_storage_net_names(self): + """Returns names of storage nodes in bitcell in [non-inverting, inverting] format.""" + #Checks that they do exist + if self.nets_match: + return self.internal_nets + else: + debug.info(1,"Storage nodes={} not found in spice file.".format(self.internal_nets)) + return None + def get_wl_cin(self): """Return the relative capacitance of the access transistor gates""" #This is a handmade cell so the value must be entered in the tech.py file or estimated. diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index 87e5adbe..6b965bc1 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -15,7 +15,11 @@ class sram_op(Enum): READ_ONE = 1 WRITE_ZERO = 2 WRITE_ONE = 3 - + +class bit_polarity(Enum): + NONINVERTING = 0 + INVERTING = 1 + 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) @@ -38,7 +42,6 @@ def parse_spice_list(filename, key): 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) - if val != None: debug.info(4, "Key = " + key + " Val = " + val.group(1)) return convert_to_float(val.group(1)) diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 01cb8948..e92663d9 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -60,7 +60,19 @@ class delay(simulation): """Create the measurements used for read and write ports""" self.read_meas_lists = self.create_read_port_measurement_objects() self.write_meas_lists = self.create_write_port_measurement_objects() - + self.check_meas_names(self.read_meas_lists+self.write_meas_lists) + + def check_meas_names(self, measures_lists): + """Given measurements (in 2d list), checks that their names are unique. + Spice sim will fail otherwise.""" + name_set = set() + for meas_list in measures_lists: + 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)) + name_set.add(name) + def create_read_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" @@ -104,6 +116,7 @@ class delay(simulation): #Other measurements associated with the read port not included in the liberty file read_measures.append(self.create_bitline_measurement_objects()) read_measures.append(self.create_debug_measurement_objects()) + read_measures.append(self.create_read_bit_measures()) return read_measures @@ -164,6 +177,37 @@ class delay(simulation): self.debug_volt_meas[-1].meta_str = debug_meas.meta_str return self.debug_delay_meas+self.debug_volt_meas + + def create_read_bit_measures(self): + """Adds bit measurements for read0 and read1 cycles""" + self.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) + 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 + self.bit_meas[polarity].append(meas) + #Dictionary values are lists, reduce to a single list of measurements + return [meas for meas_list in self.bit_meas.values() for meas in meas_list] + + def get_bit_measures(self, meas_tag, probe_address, probe_data): + """Creates measurements for the q/qbar of input bit position. + meas_tag is a unique identifier for the measurement.""" + bit_col = self.get_data_bit_column_number(probe_address, probe_data) + bit_row = self.get_address_row_number(probe_address) + (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)) + q_name = cell_name+'.'+str(storage_names[0]) + qbar_name = cell_name+'.'+str(storage_names[1]) + #Bit measures, measurements times to be defined later. The measurement names must be unique + # but they is enforced externally + q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name, has_port=False) + qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name, has_port=False) + + return {bit_polarity.NONINVERTING:q_meas, bit_polarity.INVERTING:qbar_meas} def set_load_slew(self,load,slew): """ Set the load and slew """ @@ -211,6 +255,11 @@ class delay(simulation): self.br_name = [bl for bl in preconv_names if 'br' in bl][0] debug.info(1,"bl_name={}".format(self.bl_name)) + (cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, self.wordline_row, self.bitline_column) + debug.info(1, "cell_name={}".format(cell_name)) + + + def check_arguments(self): """Checks if arguments given for write_stimulus() meets requirements""" try: @@ -350,16 +399,22 @@ class delay(simulation): """Checks the measurement object and calls respective function for related measurement inputs.""" meas_type = type(measure_obj) if meas_type is delay_measure or meas_type is slew_measure: - return self.get_delay_measure_variants(port, measure_obj) + variant_tuple = self.get_delay_measure_variants(port, measure_obj) elif meas_type is power_measure: - return self.get_power_measure_variants(port, measure_obj, "read") + variant_tuple = self.get_power_measure_variants(port, measure_obj, "read") elif meas_type is voltage_when_measure: - return self.get_volt_when_measure_variants(port, measure_obj) + variant_tuple = self.get_volt_when_measure_variants(port, measure_obj) elif meas_type is voltage_at_measure: - return self.get_volt_at_measure_variants(port, measure_obj) + variant_tuple = self.get_volt_at_measure_variants(port, measure_obj) else: debug.error("Input function not defined for measurement type={}".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. + if not measure_obj.has_port: + variant_tuple = variant_tuple[:-1] + return variant_tuple + def get_delay_measure_variants(self, port, delay_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 @@ -567,10 +622,10 @@ class delay(simulation): #Get measurements from output file for measure in self.read_lib_meas: read_port_dict[measure.name] = measure.retrieve_measure(port=port) - debug_passed = self.check_debug_measures(port, read_port_dict) - + success = self.check_debug_measures(port, read_port_dict) + success = success and self.check_bit_measures() #Check timing for read ports. Power is only checked if it was read correctly - if not self.check_valid_delays(read_port_dict) or not debug_passed: + if not self.check_valid_delays(read_port_dict) or not success: return (False,{}) if not check_dict_values_is_float(read_port_dict): debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad. @@ -648,7 +703,32 @@ class delay(simulation): return success - + + def check_bit_measures(self): + """Checks the measurements which represent the internal storage voltages + at the end of the read cycle.""" + success = True + for polarity, meas_list in self.bit_meas.items(): + for meas in meas_list: + val = meas.retrieve_measure() + debug.info(1,"{}={}".format(meas.name, val)) + if type(val) != float: + continue + meas_cycle = meas.meta_str + if (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.NONINVERTING) or\ + (meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.INVERTING): + success = val < self.vdd_voltage*.1 + elif (meas_cycle == sram_op.READ_ZERO and polarity == bit_polarity.INVERTING) or\ + (meas_cycle == sram_op.READ_ONE and polarity == bit_polarity.NONINVERTING): + success = val > self.vdd_voltage*.9 + if not success: + debug.info(1,("Wrong value detected on probe bit during read cycle. " + "Check writes and control logic for bugs.\n measure={}, op={}, " + "bit_storage={}").format(meas.name, meas_cycle.name, polarity.name)) + return success + + + def check_bitline_meas(self, v_discharged_bl, v_charged_bl): """Checks the value of the discharging bitline. Confirms s_en timing errors. Returns true if the bitlines are at there expected value.""" diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index 6cd03469..228f6839 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -13,10 +13,11 @@ from .charutils import * class spice_measurement(ABC): """Base class for spice stimulus measurements.""" - def __init__(self, measure_name, measure_scale=None): + def __init__(self, measure_name, measure_scale=None, has_port=True): #Names must be unique for correct spice simulation, but not enforced here. self.name = measure_name self.measure_scale = measure_scale + self.has_port = has_port #Needed for error checking #Some meta values used externally. variables are added here for consistency accross the objects self.meta_str = None self.meta_add_delay = False @@ -35,18 +36,29 @@ class spice_measurement(ABC): measure_vals = self.get_measure_values(*input_tuple) measure_func(stim_obj, *measure_vals) - def retrieve_measure(self, port=""): - value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + def retrieve_measure(self, port=None): + self.port_error_check(port) + if port != None: + value = parse_spice_list("timing", "{0}{1}".format(self.name.lower(), port)) + else: + value = parse_spice_list("timing", "{0}".format(self.name.lower())) if type(value)!=float or self.measure_scale == None: return value else: return value*self.measure_scale - + + def port_error_check(self, port): + if self.has_port and port == None: + debug.error("Cannot retrieve measurement, port input was expected.",1) + 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): - spice_measurement.__init__(self, measure_name, measure_scale) + 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) def get_measure_function(self): @@ -56,10 +68,8 @@ class delay_measure(spice_measurement): """Set the constants for this measurement: signal names, directions, and trigger scales""" self.trig_dir_str = trig_dir_str self.targ_dir_str = targ_dir_str - self.trig_val_of_vdd = trig_vdd self.targ_val_of_vdd = targ_vdd - self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name @@ -67,9 +77,10 @@ class delay_measure(spice_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.""" + self.port_error_check(port) trig_val = self.trig_val_of_vdd * vdd_voltage targ_val = self.targ_val_of_vdd * vdd_voltage - + if port != None: #For dictionary indexing reasons, the name is formatted differently than the signals meas_name = "{}{}".format(self.name, port) @@ -79,13 +90,12 @@ 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) class slew_measure(delay_measure): - def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(signal_name, slew_dir_str) def set_meas_constants(self, signal_name, slew_dir_str): @@ -101,7 +111,6 @@ class slew_measure(delay_measure): self.targ_val_of_vdd = 0.1 else: debug.error("Unrecognised slew measurement direction={}".format(slew_dir_str),1) - self.trig_name_no_port = signal_name self.targ_name_no_port = signal_name @@ -110,8 +119,8 @@ class slew_measure(delay_measure): class power_measure(spice_measurement): """Generates a spice measurement for the average power between two time points.""" - def __init__(self, measure_name, power_type="", measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, power_type="", measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(power_type) def get_measure_function(self): @@ -124,6 +133,7 @@ class power_measure(spice_measurement): def get_measure_values(self, t_initial, t_final, port=None): """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + self.port_error_check(port) if port != None: meas_name = "{}{}".format(self.name, port) else: @@ -133,8 +143,8 @@ class power_measure(spice_measurement): class voltage_when_measure(spice_measurement): """Generates a spice measurement to measure the voltage of a signal based on the voltage of another.""" - def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, trig_vdd, 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, trig_vdd) def get_measure_function(self): @@ -144,13 +154,12 @@ class voltage_when_measure(spice_measurement): """Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)""" self.trig_dir_str = trig_dir_str self.trig_val_of_vdd = trig_vdd - self.trig_name_no_port = trig_name self.targ_name_no_port = targ_name def get_measure_values(self, trig_td, vdd_voltage, port=None): """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 meas_name = "{}{}".format(self.name, port) @@ -160,17 +169,15 @@ 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 - + 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.""" - def __init__(self, measure_name, targ_name, measure_scale=None): - spice_measurement.__init__(self, measure_name, measure_scale) + def __init__(self, measure_name, targ_name, measure_scale=None, has_port=True): + spice_measurement.__init__(self, measure_name, measure_scale, has_port) self.set_meas_constants(targ_name) def get_measure_function(self): @@ -182,13 +189,13 @@ class voltage_at_measure(spice_measurement): def get_measure_values(self, time_at, port=None): """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 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 - + targ_name = self.targ_name_no_port return (meas_name,targ_name,time_at) \ No newline at end of file diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 72702069..efdf228d 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -1286,4 +1286,8 @@ class bank(design.design): """Precharge adds a loop between bitlines, can be excluded to reduce complexity""" for inst in self.precharge_array_inst: if inst != None: - self.graph_inst_exclude.add(inst) \ No newline at end of file + self.graph_inst_exclude.add(inst) + + def get_cell_name(self, inst_name, row, col): + """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, row, col) \ No newline at end of file diff --git a/compiler/modules/bitcell_array.py b/compiler/modules/bitcell_array.py index e19c7636..2380ee4b 100644 --- a/compiler/modules/bitcell_array.py +++ b/compiler/modules/bitcell_array.py @@ -210,4 +210,6 @@ class bitcell_array(design.design): continue self.graph_inst_exclude.add(self.cell_inst[row,col]) - \ No newline at end of file + 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] \ No newline at end of file diff --git a/compiler/sram_1bank.py b/compiler/sram_1bank.py index fdd41e8b..8d8ff65f 100644 --- a/compiler/sram_1bank.py +++ b/compiler/sram_1bank.py @@ -343,9 +343,12 @@ class sram_1bank(sram_base): debug.error("Signal={} not contained in control logic connections={}"\ .format(sen_name, control_conns)) if sen_name in self.pins: - debug.error("Internal signal={} contained in port list. Name defined by the parent.") - debug.info(1,"pins={}".format(self.pins)) - debug.info(1,"cl conns={}".format(control_conns)) + debug.error("Internal signal={} contained in port list. Name defined by the parent.") return "X{}.{}".format(sram_name, sen_name) - + def get_cell_name(self, inst_name, row, col): + """Gets the spice name of the target bitcell.""" + #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) \ No newline at end of file diff --git a/compiler/tests/21_hspice_delay_test.py b/compiler/tests/21_hspice_delay_test.py index 456dd915..d0fa88da 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -45,7 +45,9 @@ class timing_sram_test(openram_test): 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) - + #import sys + #sys.exit(1) + tempspice = OPTS.openram_temp + "temp.sp" s.sp_write(tempspice)