diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index a2140e51..061972cf 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -80,3 +80,10 @@ def convert_to_float(number): debug.error("Invalid number: {0}".format(number),1) 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 + return True \ No newline at end of file diff --git a/compiler/characterizer/delay.py b/compiler/characterizer/delay.py index d04ccf50..7f25348b 100644 --- a/compiler/characterizer/delay.py +++ b/compiler/characterizer/delay.py @@ -48,15 +48,31 @@ class delay(simulation): self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"] def create_measurement_objects(self): - self.meas_objs = [] + """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.meas_objs.append(delay_measure("delay_lh{}", trig_delay_name, targ_name, "RISE", "RISE")) - self.meas_objs.append(delay_measure("delay_hl{}", trig_delay_name, targ_name, "FALL", "FALL")) + 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.meas_objs.append(slew_measure("slew_lh{}", targ_name, "RISE")) - self.meas_objs.append(slew_measure("slew_hl{}", targ_name, "FALL")) + 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_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)) + def create_signal_names(self): self.addr_name = "A" self.din_name = "DIN" @@ -209,99 +225,68 @@ class delay(simulation): self.sf.close() - def get_delay_meas_values(self, delay_name, port): - """Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" - debug.check('lh' in delay_name or 'hl' in delay_name, "Measure command {0} does not contain direction (lh/hl)") - trig_clk_name = "clk{0}".format(port) - meas_name="{0}{1}".format(delay_name, port) - targ_name = "{0}".format("{0}{1}_{2}".format(self.dout_name,port,self.probe_data)) - half_vdd = 0.5 * self.vdd_voltage - trig_slew_low = 0.1 * self.vdd_voltage - targ_slew_high = 0.9 * self.vdd_voltage - if 'delay' in delay_name: - trig_val = half_vdd - targ_val = half_vdd - trig_name = trig_clk_name - if 'lh' in delay_name: - trig_dir="RISE" - targ_dir="RISE" - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]] - else: - trig_dir="FALL" - targ_dir="FALL" - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] - - elif 'slew' in delay_name: - trig_name = targ_name - if 'lh' in delay_name: - trig_val = trig_slew_low - targ_val = targ_slew_high - targ_dir = trig_dir = "RISE" - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]] - else: - trig_val = targ_slew_high - targ_val = trig_slew_low - targ_dir = trig_dir = "FALL" - trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]] + def get_read_measure_variants(self, port, measure_obj): + """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) + elif meas_type is power_measure: + return self.get_power_measure_variants(port, measure_obj, "read") else: - debug.error(1, "Measure command {0} not recognized".format(delay_name)) - return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td) - - def get_measure_variants(self, port, measure_obj): + debug.error("Input function not defined for measurement type={}".format(meas_type)) + + 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 #vdd is arguably constant as that is true for a single lib file. - if measure_obj.targ_dir_str == "FALL": + if delay_obj.targ_dir_str == "FALL": meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] - elif measure_obj.targ_dir_str == "RISE": + elif delay_obj.targ_dir_str == "RISE": meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read1"]] else: - debug.error("Unrecognised measurement direction={}".format(measure_obj.targ_dir_str),1) + debug.error("Unrecognised measurement direction={}".format(delay_obj.targ_dir_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) + + return (t_initial, t_final, 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.meas_objs: - measure_variant_tuple = self.get_measure_variants(port, measure) - measure.write_measure(self.stim, measure_variant_tuple) + for measure in self.read_meas_objs: + measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) + + def get_write_measure_variants(self, port, measure_obj): + """Checks the measurement object and calls respective function for related measurement inputs.""" + meas_type = type(measure_obj) + if meas_type is power_measure: + return self.get_power_measure_variants(port, measure_obj, "write") + else: + debug.error("Input function not defined for measurement type={}".format(meas_type)) - # add measure statements for power - for pname in self.power_meas_names: - if "read" not in pname: - continue - #Different naming schemes are used for the measure cycle dict and measurement names. - #TODO: make them the same so they can be indexed the same. - if '1' in pname: - t_initial = self.cycle_times[self.measure_cycles[port]["read1"]] - t_final = self.cycle_times[self.measure_cycles[port]["read1"]+1] - elif '0' in pname: - t_initial = self.cycle_times[self.measure_cycles[port]["read0"]] - t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1] - self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port), - t_initial=t_initial, - t_final=t_final) - def write_delay_measures_write_port(self, port): """ Write the measure statements to quantify the power results for a write port. """ # add measure statements for power - for pname in self.power_meas_names: - if "write" not in pname: - continue - t_initial = self.cycle_times[self.measure_cycles[port]["write0"]] - t_final = self.cycle_times[self.measure_cycles[port]["write0"]+1] - if '1' in pname: - t_initial = self.cycle_times[self.measure_cycles[port]["write1"]] - t_final = self.cycle_times[self.measure_cycles[port]["write1"]+1] - - self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port), - t_initial=t_initial, - t_final=t_final) + for measure in self.write_meas_objs: + measure_variant_inp_tuple = self.get_read_measure_variants(port, measure) + measure.write_measure(self.stim, measure_variant_inp_tuple) def write_delay_measures(self): """ @@ -451,28 +436,27 @@ class delay(simulation): #Too much duplicate code here. Try reducing for port in self.targ_read_ports: debug.info(2, "Check delay values for port {}".format(port)) - delay_names = [mname for mname in self.delay_meas_names] - delays = self.parse_values(delay_names, port, 1e9) # scale delays to ns - if not self.check_valid_delays(delays): - return (False,{}) - result[port].update(delays) + read_port_dict = {} + #Get measurements from output file + for measure in self.read_meas_objs: + read_port_dict[measure.name] = measure.retrieve_measure(port=port) - power_names = [mname for mname in self.power_meas_names if 'read' in mname] - powers = self.parse_values(power_names, port, 1e3) # scale power to mw - #Check that power parsing worked. - for name, power in powers.items(): - if type(power)!=float: - debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad. - result[port].update(powers) - + #Check timing for read ports. Power is only checked if it was read correctly + if not self.check_valid_delays(read_port_dict): + 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. + + result[port].update(read_port_dict) + for port in self.targ_write_ports: - power_names = [mname for mname in self.power_meas_names if 'write' in mname] - powers = self.parse_values(power_names, port, 1e3) # scale power to mw - #Check that power parsing worked. - for name, power in powers.items(): - if type(power)!=float: - debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad. - result[port].update(powers) + write_port_dict = {} + for measure in self.read_meas_objs: + write_port_dict[measure.name] = measure.retrieve_measure(port=port) + + if not check_dict_values_is_float(write_port_dict): + debug.error("Failed to Measure Write Port Values:\n\t\t{0}".format(write_port_dict),1) #Printing the entire dict looks bad. + result[port].update(write_port_dict) # The delay is from the negative edge for our SRAM return (True,result) @@ -502,13 +486,13 @@ class delay(simulation): #key=raw_input("press return to continue") return (leakage_power*1e3, trim_leakage_power*1e3) - def check_valid_delays(self, delay_dict): + def check_valid_delays(self, result_dict): """ Check if the measurements are defined and if they are valid. """ #Hard coded names currently - delay_hl = delay_dict["delay_hl"] - delay_lh = delay_dict["delay_lh"] - slew_hl = delay_dict["slew_hl"] - slew_lh = delay_dict["slew_lh"] + delay_hl = result_dict["delay_hl"] + delay_lh = result_dict["delay_lh"] + slew_hl = result_dict["slew_hl"] + slew_lh = result_dict["slew_lh"] period_load_slew_str = "period {0} load {1} slew {2}".format(self.period,self.load, self.slew) # if it failed or the read was longer than a period diff --git a/compiler/characterizer/measurements.py b/compiler/characterizer/measurements.py index 2822183c..f065ac70 100644 --- a/compiler/characterizer/measurements.py +++ b/compiler/characterizer/measurements.py @@ -2,13 +2,15 @@ import debug from tech import drc, parameter, spice from abc import ABC, abstractmethod from .stimuli import * +from .charutils import * class spice_measurement(ABC): """Base class for spice stimulus measurements.""" - def __init__(self, measure_name): + def __init__(self, measure_name, measure_scale=None): #Names must be unique for correct spice simulation, but not enforced here. self.name = measure_name - + self.measure_scale = measure_scale + @abstractmethod def get_measure_function(self): return None @@ -23,13 +25,19 @@ class spice_measurement(ABC): debug.error("Did not set measure function",1) 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)) + if type(value)!=float or self.measure_scale == None: + return value + else: + return value*self.measure_scale 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): - spice_measurement.__init__(self, measure_name) + def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, 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) def get_measure_function(self): @@ -54,7 +62,8 @@ class delay_measure(spice_measurement): targ_val = self.targ_val_of_vdd * vdd_voltage if port != None: - meas_name = self.name.format(port) + #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: @@ -63,12 +72,11 @@ class delay_measure(spice_measurement): 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): - spice_measurement.__init__(self, measure_name) + def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) self.set_meas_constants(signal_name, slew_dir_str) def set_meas_constants(self, signal_name, slew_dir_str): @@ -89,4 +97,26 @@ class slew_measure(delay_measure): self.targ_name_no_port = signal_name #Time delays and ports are variant and needed as inputs when writing the measurement - \ No newline at end of file + +class power_measure(spice_measurement): + """Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" + + def __init__(self, measure_name, power_type="", measure_scale=None): + spice_measurement.__init__(self, measure_name, measure_scale) + self.set_meas_constants(power_type) + + def get_measure_function(self): + return stimuli.gen_meas_power + + 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" + + def get_measure_values(self, t_initial, t_final, port=None): + """Constructs inputs to stimulus measurement function. Variant values are inputs here.""" + if port != None: + meas_name = "{}{}".format(self.name, port) + else: + meas_name = self.name + return (meas_name,t_initial,t_final) \ No newline at end of file