From 272267358f16eac7ffc689718814251a32f6306f Mon Sep 17 00:00:00 2001 From: Hunter Nichols Date: Thu, 3 Jan 2019 05:51:28 -0800 Subject: [PATCH] Moved all bitline delay measurements to delay class. Added measurements to check delay model. --- compiler/characterizer/__init__.py | 1 + compiler/characterizer/bitline_delay.py | 19 +++ compiler/characterizer/delay.py | 156 +++++++++++++------- compiler/characterizer/logical_effort.py | 4 + compiler/characterizer/measurements.py | 52 +++++-- compiler/characterizer/model_check.py | 174 +++++++++++++++++++++++ compiler/modules/control_logic.py | 22 ++- compiler/tests/21_hspice_delay_test.py | 29 ++-- compiler/tests/21_ngspice_delay_test.py | 39 +++-- compiler/tests/28_delay_model_test.py | 61 ++++++++ 10 files changed, 456 insertions(+), 101 deletions(-) create mode 100644 compiler/characterizer/model_check.py create mode 100755 compiler/tests/28_delay_model_test.py diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 009459b3..cbcf0a1a 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -10,6 +10,7 @@ from .worst_case import * from .simulation import * from .bitline_delay import * from .measurements import * +from .model_check import * debug.info(1,"Initializing characterizer...") OPTS.spice_exe = "" diff --git a/compiler/characterizer/bitline_delay.py b/compiler/characterizer/bitline_delay.py index 6368675c..2af290f1 100644 --- a/compiler/characterizer/bitline_delay.py +++ b/compiler/characterizer/bitline_delay.py @@ -26,7 +26,26 @@ class bitline_delay(delay): delay.create_signal_names(self) self.bl_signal_names = ["Xsram.Xbank0.bl", "Xsram.Xbank0.br"] self.sen_name = "Xsram.s_en" + + def create_measurement_objects(self): + """Create the measurements used for read and write ports""" + self.meas_objs = [] + self.create_bitline_find_measurement_objects() + self.create_bitline_delay_measurement_objects() + + def create_bitline_delay_measurement_objects(self): + self.find_meas_objs = [] + trig_delay_name = "clk{0}" + targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit + self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9)) + self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", 1e9)) + self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9)) + self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", 1e9)) + + self.read_meas_objs.append(power_measure("read1_power", "RISE", 1e3)) + self.read_meas_objs.append(power_measure("read0_power", "FALL", 1e3)) + def create_measurement_names(self): """Create measurement names. The names themselves currently define the type of measurement""" #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index 7f25348b..a327c548 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -46,32 +46,77 @@ class delay(simulation): #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. self.delay_meas_names = ["delay_lh", "delay_hl", "slew_lh", "slew_hl"] self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"] + self.voltage_when_names = ["volt_bl", "volt_br"] + self.bitline_delay_names = ["delay_bl", "delay_br"] def create_measurement_objects(self): """Create the measurements used for read and write ports""" self.create_read_port_measurement_objects() self.create_write_port_measurement_objects() - + def create_read_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" + self.read_meas_objs = [] trig_delay_name = "clk{0}" targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit - self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9)) - self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", 1e9)) + self.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read1" #Used to index time delay values when measurements written to spice file. + self.read_meas_objs.append(delay_measure("delay_hl", trig_delay_name, targ_name, "FALL", "FALL", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read0" - self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9)) - self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", 1e9)) + self.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read1" + self.read_meas_objs.append(slew_measure("slew_hl", targ_name, "FALL", measure_scale=1e9)) + self.read_meas_objs[-1].meta_str = "read0" - self.read_meas_objs.append(power_measure("read1_power", "RISE", 1e3)) - self.read_meas_objs.append(power_measure("read0_power", "FALL", 1e3)) - + self.read_meas_objs.append(power_measure("read1_power", "RISE", measure_scale=1e3)) + self.read_meas_objs[-1].meta_str = "read1" + self.read_meas_objs.append(power_measure("read0_power", "FALL", measure_scale=1e3)) + self.read_meas_objs[-1].meta_str = "read0" + + trig_name = "Xsram.s_en{}" #Sense amp enable + if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name + port_format = "" + else: + port_format = "{}" + + bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) + br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) + self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[0], trig_name, bl_name, "RISE", .5)) + self.read_meas_objs.append(voltage_when_measure(self.voltage_when_names[1], trig_name, br_name, "RISE", .5)) + + #These are read values but need to be separated for unique error checking. + self.create_bitline_delay_measurement_objects() + + def create_bitline_delay_measurement_objects(self): + """Create the measurements used for bitline delay values. Due to unique error checking, these are separated from other measurements. + These measurements are only associated with read values + """ + self.bitline_delay_objs = [] + trig_name = "clk{0}" + if len(self.all_ports) == 1: #special naming case for single port sram bitlines which does not include the port in name + port_format = "" + else: + port_format = "{}" + bl_name = "Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column) + br_name = "Xsram.Xbank0.br{}_{}".format(port_format, self.bitline_column) + targ_val = (self.vdd_voltage - tech.spice["v_threshold_typical"])/self.vdd_voltage #Calculate as a percentage of vdd + + targ_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit + self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[0], trig_name, bl_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) + self.bitline_delay_objs[-1].meta_str = "read0" + self.bitline_delay_objs.append(delay_measure(self.bitline_delay_names[1], trig_name, br_name, "FALL", "FALL", targ_vdd=targ_val, measure_scale=1e9)) + self.bitline_delay_objs[-1].meta_str = "read1" + def create_write_port_measurement_objects(self): """Create the measurements used for read ports: delays, slews, powers""" self.write_meas_objs = [] - self.write_meas_objs.append(power_measure("write1_power", "RISE", 1e3)) - self.write_meas_objs.append(power_measure("write0_power", "FALL", 1e3)) + self.write_meas_objs.append(power_measure("write1_power", "RISE", measure_scale=1e3)) + self.write_meas_objs[-1].meta_str = "read1" + self.write_meas_objs.append(power_measure("write0_power", "FALL", measure_scale=1e3)) + self.write_meas_objs[-1].meta_str = "write0" def create_signal_names(self): self.addr_name = "A" @@ -232,6 +277,8 @@ class delay(simulation): return self.get_delay_measure_variants(port, measure_obj) elif meas_type is power_measure: return 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) else: debug.error("Input function not defined for measurement type={}".format(meas_type)) @@ -239,35 +286,37 @@ class delay(simulation): """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 #vdd is arguably constant as that is true for a single lib file. - if delay_obj.targ_dir_str == "FALL": - meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] - elif delay_obj.targ_dir_str == "RISE": - meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read1"]] + if delay_obj.meta_str == "read0": + #Falling delay are measured starting from neg. clk edge. Delay adjusted to that. + meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] + self.period/2 + elif delay_obj.meta_str == "read1": + meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]] else: - debug.error("Unrecognised measurement direction={}".format(delay_obj.targ_dir_str),1) + debug.error("Unrecognised delay Index={}".format(delay_obj.meta_str),1) return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port) 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 - if power_obj.power_type == "FALL": - t_initial = self.cycle_times[self.measure_cycles[port]["{}0".format(operation)]] - t_final = self.cycle_times[self.measure_cycles[port]["{}0".format(operation)]+1] - elif power_obj.power_type == "RISE": - t_initial = self.cycle_times[self.measure_cycles[port]["{}1".format(operation)]] - t_final = self.cycle_times[self.measure_cycles[port]["{}1".format(operation)]+1] - else: - debug.error("Unrecognised power measurement type={}".format(power_obj.power_type),1) - + t_initial = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]] + t_final = self.cycle_times[self.measure_cycles[port][power_obj.meta_str]+1] + return (t_initial, t_final, port) + def get_volt_when_measure_variants(self, port, power_obj): + """Get the measurement values that can either vary port to port (time delays)""" + #Only checking 0 value reads for now. + t_trig = meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + + return (t_trig, self.vdd_voltage, port) + def write_delay_measures_read_port(self, port): """ Write the measure statements to quantify the delay and power results for a read port. """ # add measure statements for delays/slews - for measure in self.read_meas_objs: + for measure in self.read_meas_objs+self.bitline_delay_objs: measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) measure.write_measure(self.stim, measure_variant_inp_tuple) @@ -285,7 +334,7 @@ class delay(simulation): """ # add measure statements for power for measure in self.write_meas_objs: - measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) + measure_variant_inp_tuple = self.get_write_measure_variants(port, measure) measure.write_measure(self.stim, measure_variant_inp_tuple) def write_delay_measures(self): @@ -394,28 +443,7 @@ class delay(simulation): previous_period = self.period debug.info(1, "Found feasible_period: {0}ns".format(self.period)) return feasible_delays - - - def parse_values(self, values_names, port, mult = 1.0): - """Parse multiple values in the timing output file. Optional multiplier. - Return a dict of the input names and values. Port used for parsing file. - """ - values = [] - all_values_floats = True - for vname in values_names: - #ngspice converts all measure characters to lowercase, not tested on other sims - value = parse_spice_list("timing", "{0}{1}".format(vname.lower(), port)) - #Check if any of the values fail to parse - if type(value)!=float: - all_values_floats = False - values.append(value) - - #Apply Multiplier only if all values are floats. Let other check functions handle this error. - if all_values_floats: - return {values_names[i]:values[i]*mult for i in range(len(values))} - else: - return {values_names[i]:values[i] for i in range(len(values))} - + def run_delay_simulation(self): """ This tries to simulate a period and checks if the result works. If @@ -448,10 +476,13 @@ class delay(simulation): debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad. result[port].update(read_port_dict) - + + bitline_delay_dict = self.evaluate_bitline_delay(port) + result[port].update(bitline_delay_dict) + for port in self.targ_write_ports: write_port_dict = {} - for measure in self.read_meas_objs: + for measure in self.write_meas_objs: write_port_dict[measure.name] = measure.retrieve_measure(port=port) if not check_dict_values_is_float(write_port_dict): @@ -461,7 +492,17 @@ class delay(simulation): # The delay is from the negative edge for our SRAM return (True,result) - + def evaluate_bitline_delay(self, port): + """Parse and check the bitline delay. One of the measurements is expected to fail which warrants its own function.""" + bl_delay_meas_dict = {} + values_added = 0 #For error checking + for measure in self.bitline_delay_objs: + bl_delay_val = measure.retrieve_measure(port=port) + if type(bl_delay_val) != float or 0 > bl_delay_val or bl_delay_val > self.period/2: #Only add if value is valid, do not error. + debug.error("Bitline delay measurement failed: half-period={}, {}={}".format(self.period/2, measure.name, bl_delay_val),1) + bl_delay_meas_dict[measure.name] = bl_delay_val + return bl_delay_meas_dict + def run_power_simulation(self): """ This simulates a disabled SRAM to get the leakage power when it is off. @@ -618,9 +659,18 @@ class delay(simulation): functions in this characterizer besides analyze.""" self.probe_address = probe_address self.probe_data = probe_data - + self.bitline_column = self.get_data_bit_column_number(probe_address, probe_data) + self.prepare_netlist() + def get_data_bit_column_number(self, probe_address, probe_data): + """Calculates bitline column number of data bit under test using bit position and mux size""" + if self.sram.col_addr_size>0: + col_address = int(probe_address[0:self.sram.col_addr_size],2) + else: + col_address = 0 + bl_column = int(self.sram.words_per_row*probe_data + col_address) + return bl_column def prepare_netlist(self): """ Prepare a trimmed netlist and regular netlist. """ @@ -899,7 +949,7 @@ class delay(simulation): def get_empty_measure_data_dict(self): """Make a dict of lists for each type of delay and power measurement to append results to""" - measure_names = self.delay_meas_names + self.power_meas_names + measure_names = self.delay_meas_names + self.power_meas_names + self.voltage_when_names + self.bitline_delay_names #Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists. measure_data = [{mname:[] for mname in measure_names} for i in self.all_ports] return measure_data diff --git a/compiler/characterizer/logical_effort.py b/compiler/characterizer/logical_effort.py index bbb0bd77..c80e69a2 100644 --- a/compiler/characterizer/logical_effort.py +++ b/compiler/characterizer/logical_effort.py @@ -35,6 +35,10 @@ class logical_effort(): def get_stage_delay(self, pinv): return self.get_stage_effort()+self.get_parasitic_delay(pinv) + +def calculate_delays(stage_effort_list, pinv): + """Convert stage effort objects to list of delay values""" + return [stage.get_stage_delay(pinv) for stage in stage_effort_list] def calculate_relative_delay(stage_effort_list, pinv=parameter["min_inv_para_delay"]): """Calculates the total delay of a given delay path made of a list of logical effort objects.""" diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index f065ac70..aec4d769 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -10,6 +10,7 @@ class spice_measurement(ABC): #Names must be unique for correct spice simulation, but not enforced here. self.name = measure_name self.measure_scale = measure_scale + self.meta_str = None #Some measurements set this, set here to be clear on existence @abstractmethod def get_measure_function(self): @@ -36,20 +37,20 @@ class spice_measurement(ABC): 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, measure_scale=None): + 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) - self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str) + self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd) def get_measure_function(self): return stimuli.gen_meas_delay - def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str): - """Set the values needed to generate a Spice measurement statement based on the name of the measurement.""" + def set_meas_constants(self, trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd): + """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 = 0.5 - self.targ_val_of_vdd = 0.5 + 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 @@ -99,7 +100,7 @@ class slew_measure(delay_measure): #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 delay of 50%-to-50% points of two signals.""" + """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) @@ -119,4 +120,39 @@ class power_measure(spice_measurement): meas_name = "{}{}".format(self.name, port) else: meas_name = self.name - return (meas_name,t_initial,t_final) \ No newline at end of file + 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.""" + + 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) + self.set_meas_constants(trig_name, targ_name, trig_dir_str, trig_vdd) + + def get_measure_function(self): + return stimuli.gen_meas_find_voltage + + def set_meas_constants(self, trig_name, targ_name, trig_dir_str, trig_vdd): + """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.""" + + if port != None: + #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) + else: + 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) \ No newline at end of file diff --git a/compiler/characterizer/model_check.py b/compiler/characterizer/model_check.py new file mode 100644 index 00000000..7a06ab7a --- /dev/null +++ b/compiler/characterizer/model_check.py @@ -0,0 +1,174 @@ +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 + + The current worst case determines a feasible period for the SRAM then tests + several bits and record the delay and differences between the bits. + + """ + + def __init__(self, sram, spfile, corner): + delay.__init__(self,sram,spfile,corner) + self.period = tech.spice["feasible_period"] + + def create_measurement_names(self): + """Create measurement names. The names themselves currently define the type of measurement""" + #Altering the names will crash the characterizer. TODO: object orientated approach to the measurements. + self.wl_delay_meas_names = ["delay_wl_en_bar", "delay_wl_en", "delay_dvr_en_bar", "delay_wl"] + self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in", "delay_delay_chain_stage_1", "delay_delay_chain_stage_2"] + self.sae_delay_meas_names = ["delay_pre_sen", "delay_sen_bar", "delay_sen"] + + def create_signal_names(self): + delay.create_signal_names(self) + #Signal names are all hardcoded, need to update to make it work for probe address and different configurations. + self.wl_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xbuf_wl_en.zb_int", "Xsram.wl_en0", "Xsram.Xbank0.Xwordline_driver0.wl_bar_15", "Xsram.Xbank0.wl_15"] + self.rbl_en_signal_names = ["Xsram.Xcontrol0.gated_clk_bar", "Xsram.Xcontrol0.Xand2_rbl_in.zb_int", "Xsram.Xcontrol0.rbl_in", "Xsram.Xcontrol0.Xreplica_bitline.Xdelay_chain.dout_1", "Xsram.Xcontrol0.Xreplica_bitline.delayed_en"] + self.sae_signal_names = ["Xsram.Xcontrol0.Xreplica_bitline.bl0_0", "Xsram.Xcontrol0.pre_s_en", "Xsram.Xcontrol0.Xbuf_s_en.zb_int", "Xsram.s_en0"] + + def create_measurement_objects(self): + """Create the measurements used for read and write ports""" + self.create_wordline_measurement_objects() + self.create_sae_measurement_objects() + self.all_measures = self.wl_meas_objs+self.sae_meas_objs + + def create_wordline_measurement_objects(self): + """Create the measurements to measure the wordline path from the gated_clk_bar signal""" + self.wl_meas_objs = [] + trig_dir = "RISE" + 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_signal_names[i], trig_dir, targ_dir, measure_scale=1e9)) + temp_dir = trig_dir + trig_dir = targ_dir + targ_dir = temp_dir + + def create_sae_measurement_objects(self): + """Create the measurements to measure the sense amp enable path from the gated_clk_bar signal. The RBL splits this path into two.""" + + self.sae_meas_objs = [] + trig_dir = "RISE" + targ_dir = "FALL" + #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.rbl_en_signal_names[i], trig_dir, targ_dir, measure_scale=1e9)) + temp_dir = trig_dir + trig_dir = targ_dir + targ_dir = temp_dir + + #Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL. + trig_dir = "FALL" + targ_dir = "RISE" + #Add measurements from gated_clk_bar to RBL + 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_signal_names[i], trig_dir, targ_dir, measure_scale=1e9)) + temp_dir = trig_dir + trig_dir = targ_dir + targ_dir = temp_dir + + def write_delay_measures(self): + """ + Write the measure statements to quantify the delay and power results for all targeted ports. + """ + self.sf.write("\n* Measure statements for delay and power\n") + + # Output some comments to aid where cycles start and what is happening + for comment in self.cycle_comments: + self.sf.write("* {}\n".format(comment)) + + for read_port in self.targ_read_ports: + self.write_measures_read_port(read_port) + + 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 + #Assuming only read 0 for now + 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) + + def write_measures_read_port(self, port): + """ + Write the measure statements for all nodes along the wordline path. + """ + # add measure statements for delays/slews + for measure in self.all_measures: + measure_variant_inp_tuple = self.get_delay_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) + + + def run_delay_simulation(self): + """ + This tries to simulate a period and checks if the result works. If + so, it returns True and the delays, slews, and powers. It + works on the trimmed netlist by default, so powers do not + include leakage of all cells. + """ + #Sanity Check + debug.check(self.period > 0, "Target simulation period non-positive") + + wl_result = [[] for i in self.all_ports] + sae_result = [[] for i in self.all_ports] + # Checking from not data_value to data_value + self.write_delay_stimulus() + + self.stim.run_sim() #running sim prodoces spice output file. + + for port in self.targ_read_ports: + #Parse and check the voltage measurements + wl_meas_list = [] + for measure in self.wl_meas_objs: + wl_meas_list.append(measure.retrieve_measure(port=port)) + if type(wl_meas_list[-1]) != float: + debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, wl_meas_list[-1]),1) #Printing the entire dict looks bad. + wl_result[port] = wl_meas_list + + sae_meas_list = [] + for measure in self.sae_meas_objs: + sae_meas_list.append(measure.retrieve_measure(port=port)) + if type(sae_meas_list[-1]) != float: + debug.error("Failed to Measure Value:\n\t\t{}={}".format(measure.name, sae_meas_list[-1]),1) #Printing the entire dict looks bad. + sae_result[port] = sae_meas_list + + # The delay is from the negative edge for our SRAM + return (True,wl_result, sae_result) + + def get_model_delays(self, port): + """Get model delays based on port. Currently assumes single RW port.""" + return self.sram.control_logic_rw.get_wl_sen_delays() + + def analyze(self, probe_address, probe_data, slews, loads): + """Measures entire delay path along the wordline and sense amp enable and compare it to the model delays.""" + self.set_probe(probe_address, probe_data) + self.load=max(loads) + self.slew=max(slews) + self.create_measurement_objects() + + read_port = self.read_ports[0] #only test the first read port + self.targ_read_ports = [read_port] + self.targ_write_ports = [self.write_ports[0]] + debug.info(1,"Bitline swing test: corner {}".format(self.corner)) + (success, wl_delays, sae_delays)=self.run_delay_simulation() + debug.check(success, "Model measurements Failed: period={}".format(self.period)) + wl_model_delays, sae_model_delays = self.get_model_delays(read_port) + + debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port])) + debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays)) + debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port])) + debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays)) + + return wl_delays, sae_delays + + + + \ No newline at end of file diff --git a/compiler/modules/control_logic.py b/compiler/modules/control_logic.py index 8545480c..9a945383 100644 --- a/compiler/modules/control_logic.py +++ b/compiler/modules/control_logic.py @@ -37,6 +37,8 @@ class control_logic(design.design): #self.sram=None #disable re-sizing for debugging, FIXME: resizing is not working, needs to be adjusted for new control logic. self.wl_timing_tolerance = 1 #Determines how much larger the sen delay should be. Accounts for possible error in model. self.parasitic_inv_delay = parameter["min_inv_para_delay"] #Keeping 0 for now until further testing. + self.wl_stage_efforts = None + self.sen_stage_efforts = None if self.port_type == "rw": self.num_control_signals = 2 @@ -157,7 +159,7 @@ class control_logic(design.design): elif self.words_per_row == 2: delay_stages = 6 else: - delay_stages = 4 + delay_stages = 2 return (delay_stages, delay_fanout) @@ -808,8 +810,8 @@ class control_logic(design.design): def get_delays_to_wl(self): """Get the delay (in delay units) of the clk to a wordline in the bitcell array""" debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") - stage_efforts = self.determine_wordline_stage_efforts() - clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay) + self.wl_stage_efforts = self.determine_wordline_stage_efforts() + clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts, self.parasitic_inv_delay) total_delay = clk_to_wl_rise + clk_to_wl_fall debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay)) return clk_to_wl_rise,clk_to_wl_fall @@ -838,8 +840,8 @@ class control_logic(design.design): This does not incorporate the delay of the replica bitline. """ debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.") - stage_efforts = self.determine_sa_enable_stage_efforts() - clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(stage_efforts, self.parasitic_inv_delay) + self.sen_stage_efforts = self.determine_sa_enable_stage_efforts() + clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts, self.parasitic_inv_delay) total_delay = clk_to_sen_rise + clk_to_sen_fall debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay)) return clk_to_sen_rise, clk_to_sen_fall @@ -870,4 +872,12 @@ class control_logic(design.design): last_stage_rise = stage_effort_list[-1].is_rise return stage_effort_list - \ No newline at end of file + + def get_wl_sen_delays(self): + """Gets a list of the stages and delays in order of their path.""" + if self.sen_stage_efforts == None or self.wl_stage_efforts == None: + debug.error("Model delays not calculated for SRAM.", 1) + wl_delays = logical_effort.calculate_delays(self.wl_stage_efforts, self.parasitic_inv_delay) + sen_delays = logical_effort.calculate_delays(self.sen_stage_efforts, self.parasitic_inv_delay) + return wl_delays, sen_delays + \ 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 c847210b..26735870 100755 --- a/compiler/tests/21_hspice_delay_test.py +++ b/compiler/tests/21_hspice_delay_test.py @@ -23,7 +23,7 @@ class timing_sram_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import delay, bitline_delay + from characterizer import delay from sram import sram from sram_config import sram_config c = sram_config(word_size=1, @@ -43,15 +43,14 @@ class timing_sram_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) d = delay(s.s, tempspice, corner) - bl = bitline_delay(s.s, tempspice, corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] data, port_data = d.analyze(probe_address, probe_data, slews, loads) - bitline_data = bl.analyze(probe_address, probe_data, slews, loads) #Combine info about port into all data data.update(port_data[0]) - data.update(bitline_data[0]) + + print(data) if OPTS.tech_name == "freepdk45": golden_data = {'delay_hl': [0.2011], @@ -60,13 +59,14 @@ class timing_sram_test(openram_test): 'min_period': 0.41, 'read0_power': [0.63604], 'read1_power': [0.6120599999999999], - 'slew_hl': [0.10853], - 'slew_lh': [0.10853], + 'slew_hl': [0.07078999999999999], + 'slew_lh': [0.07078999999999999], 'write0_power': [0.51742], 'write1_power': [0.51095], - 'volt_bl': 0.045626, - 'volt_br': 1.0709, - 'delay_bl_vth': 0.1813} + 'volt_bl': [0.2017], + 'volt_br': [1.0765], + 'delay_bl': [0.18114999999999998], + 'delay_br': [0.17763]} elif OPTS.tech_name == "scn4m_subm": golden_data = {'delay_hl': [1.3911], 'delay_lh': [1.3911], @@ -74,13 +74,14 @@ class timing_sram_test(openram_test): 'min_period': 2.812, 'read0_power': [22.1183], 'read1_power': [21.4388], - 'slew_hl': [0.7397553], - 'slew_lh': [0.7397553], + 'slew_hl': [0.6], + 'slew_lh': [0.6], 'write0_power': [19.4103], 'write1_power': [20.1167], - 'volt_bl': 1.8329, - 'volt_br': 5.081, - 'delay_bl_vth': 1.1141} + 'volt_bl': [3.1763], + 'volt_br': [5.5731], + 'delay_bl': [1.1133000000000002], + 'delay_br': [0.9958395]} else: self.assertTrue(False) # other techs fail # Check if no too many or too few results diff --git a/compiler/tests/21_ngspice_delay_test.py b/compiler/tests/21_ngspice_delay_test.py index 16c3fb3a..98f01e53 100755 --- a/compiler/tests/21_ngspice_delay_test.py +++ b/compiler/tests/21_ngspice_delay_test.py @@ -23,7 +23,7 @@ class timing_sram_test(openram_test): from importlib import reload import characterizer reload(characterizer) - from characterizer import delay, bitline_delay + from characterizer import delay from sram import sram from sram_config import sram_config c = sram_config(word_size=1, @@ -43,30 +43,28 @@ class timing_sram_test(openram_test): corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) d = delay(s.s, tempspice, corner) - bl = bitline_delay(s.s, tempspice, corner) import tech loads = [tech.spice["msflop_in_cap"]*4] slews = [tech.spice["rise_time"]*2] data, port_data = d.analyze(probe_address, probe_data, slews, loads) - bitline_data = bl.analyze(probe_address, probe_data, slews, loads) #Combine info about port into all data data.update(port_data[0]) - data.update(bitline_data[0]) if OPTS.tech_name == "freepdk45": - golden_data = {'delay_hl': [0.20443139999999999], - 'delay_lh': [0.20443139999999999], - 'leakage_power': 0.0017840640000000001, - 'min_period': 0.41, - 'read0_power': [0.6435831], - 'read1_power': [0.6233463], - 'slew_hl': [0.1138734], - 'slew_lh': [0.1138734], - 'write0_power': [0.5205761], - 'write1_power': [0.5213689], - 'volt_bl': 0.03667602, - 'volt_br': 1.056013, - 'delay_bl_vth': 0.184373} + golden_data = {'delay_bl': [0.1840938], + 'delay_br': [0.1804373], + 'delay_hl': [0.2130831], + 'delay_lh': [0.2130831], + 'leakage_power': 0.001595639, + 'min_period': 0.527, + 'read0_power': [0.4852456], + 'read1_power': [0.46341889999999997], + 'slew_hl': [0.07351041999999999], + 'slew_lh': [0.07351041999999999], + 'volt_bl': [0.1954744], + 'volt_br': [1.058266], + 'write0_power': [0.4065201], + 'write1_power': [0.46341889999999997]} elif OPTS.tech_name == "scn4m_subm": golden_data = {'delay_hl': [1.610911], 'delay_lh': [1.610911], @@ -78,9 +76,10 @@ class timing_sram_test(openram_test): 'slew_lh': [0.7986348999999999], 'write0_power': [17.58272], 'write1_power': [18.523419999999998], - 'volt_bl': 1.639692, - 'volt_br': 5.06107, - 'delay_bl_vth': 1.322235} + 'volt_bl': [3.1763], + 'volt_br': [5.5731], + 'delay_bl': [1.1133000000000002], + 'delay_br': [0.9958395]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/28_delay_model_test.py b/compiler/tests/28_delay_model_test.py new file mode 100755 index 00000000..74ad22f3 --- /dev/null +++ b/compiler/tests/28_delay_model_test.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +""" +Run a regression test on various srams +""" + +import unittest +from testutils import header,openram_test +import sys,os +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +from globals import OPTS +import debug + +class delay_model_test(openram_test): + + def runTest(self): + globals.init_openram("config_20_{0}".format(OPTS.tech_name)) + OPTS.spice_name="hspice" + 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 model_check + from sram import sram + from sram_config import sram_config + c = sram_config(word_size=1, + 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 = sram(c, name="sram1") + + 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]) + mc = model_check(s.s, tempspice, corner) + import tech + loads = [tech.spice["msflop_in_cap"]*4] + slews = [tech.spice["rise_time"]*2] + wl_data, sae_data = mc.analyze(probe_address, probe_data, slews, loads) + #Combine info about port into all data + + #debug.info(1,"Data:\n{}".format(wl_data)) + + 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()