Added data parsing to measurement objects and adding power measurements.

This commit is contained in:
Hunter Nichols 2018-12-20 15:54:56 -08:00
parent b10ef3fb7e
commit 66b2fcdc91
3 changed files with 135 additions and 114 deletions

View File

@ -80,3 +80,10 @@ def convert_to_float(number):
debug.error("Invalid number: {0}".format(number),1) debug.error("Invalid number: {0}".format(number),1)
return float_value 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

View File

@ -48,14 +48,30 @@ class delay(simulation):
self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"] self.power_meas_names = ["read0_power", "read1_power", "write0_power", "write1_power"]
def create_measurement_objects(self): 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}" 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 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.read_meas_objs.append(delay_measure("delay_lh", trig_delay_name, targ_name, "RISE", "RISE", 1e9))
self.meas_objs.append(delay_measure("delay_hl{}", trig_delay_name, targ_name, "FALL", "FALL")) 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.read_meas_objs.append(slew_measure("slew_lh", targ_name, "RISE", 1e9))
self.meas_objs.append(slew_measure("slew_hl{}", targ_name, "FALL")) 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): def create_signal_names(self):
self.addr_name = "A" self.addr_name = "A"
@ -209,99 +225,68 @@ class delay(simulation):
self.sf.close() self.sf.close()
def get_delay_meas_values(self, delay_name, port): def get_read_measure_variants(self, port, measure_obj):
"""Get the values needed to generate a Spice measurement statement based on the name of the measurement.""" """Checks the measurement object and calls respective function for related measurement inputs."""
debug.check('lh' in delay_name or 'hl' in delay_name, "Measure command {0} does not contain direction (lh/hl)") meas_type = type(measure_obj)
trig_clk_name = "clk{0}".format(port) if meas_type is delay_measure or meas_type is slew_measure:
meas_name="{0}{1}".format(delay_name, port) return self.get_delay_measure_variants(port, measure_obj)
targ_name = "{0}".format("{0}{1}_{2}".format(self.dout_name,port,self.probe_data)) elif meas_type is power_measure:
half_vdd = 0.5 * self.vdd_voltage return self.get_power_measure_variants(port, measure_obj, "read")
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"]]
else: else:
debug.error(1, "Measure command {0} not recognized".format(delay_name)) debug.error("Input function not defined for measurement type={}".format(meas_type))
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): 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)""" """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 #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. #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"]] 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"]] meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read1"]]
else: 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) 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): def write_delay_measures_read_port(self, port):
""" """
Write the measure statements to quantify the delay and power results for a read port. Write the measure statements to quantify the delay and power results for a read port.
""" """
# add measure statements for delays/slews # add measure statements for delays/slews
for measure in self.meas_objs: for measure in self.read_meas_objs:
measure_variant_tuple = self.get_measure_variants(port, measure) measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
measure.write_measure(self.stim, measure_variant_tuple) measure.write_measure(self.stim, measure_variant_inp_tuple)
# add measure statements for power def get_write_measure_variants(self, port, measure_obj):
for pname in self.power_meas_names: """Checks the measurement object and calls respective function for related measurement inputs."""
if "read" not in pname: meas_type = type(measure_obj)
continue if meas_type is power_measure:
#Different naming schemes are used for the measure cycle dict and measurement names. return self.get_power_measure_variants(port, measure_obj, "write")
#TODO: make them the same so they can be indexed the same. else:
if '1' in pname: debug.error("Input function not defined for measurement type={}".format(meas_type))
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): def write_delay_measures_write_port(self, port):
""" """
Write the measure statements to quantify the power results for a write port. Write the measure statements to quantify the power results for a write port.
""" """
# add measure statements for power # add measure statements for power
for pname in self.power_meas_names: for measure in self.write_meas_objs:
if "write" not in pname: measure_variant_inp_tuple = self.get_read_measure_variants(port, measure)
continue measure.write_measure(self.stim, measure_variant_inp_tuple)
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)
def write_delay_measures(self): def write_delay_measures(self):
""" """
@ -451,28 +436,27 @@ class delay(simulation):
#Too much duplicate code here. Try reducing #Too much duplicate code here. Try reducing
for port in self.targ_read_ports: for port in self.targ_read_ports:
debug.info(2, "Check delay values for port {}".format(port)) debug.info(2, "Check delay values for port {}".format(port))
delay_names = [mname for mname in self.delay_meas_names] read_port_dict = {}
delays = self.parse_values(delay_names, port, 1e9) # scale delays to ns #Get measurements from output file
if not self.check_valid_delays(delays): for measure in self.read_meas_objs:
return (False,{}) read_port_dict[measure.name] = measure.retrieve_measure(port=port)
result[port].update(delays)
power_names = [mname for mname in self.power_meas_names if 'read' in mname] #Check timing for read ports. Power is only checked if it was read correctly
powers = self.parse_values(power_names, port, 1e3) # scale power to mw if not self.check_valid_delays(read_port_dict):
#Check that power parsing worked. return (False,{})
for name, power in powers.items(): if not check_dict_values_is_float(read_port_dict):
if type(power)!=float: debug.error("Failed to Measure Read Port Values:\n\t\t{0}".format(read_port_dict),1) #Printing the entire dict looks bad.
debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad.
result[port].update(powers) result[port].update(read_port_dict)
for port in self.targ_write_ports: for port in self.targ_write_ports:
power_names = [mname for mname in self.power_meas_names if 'write' in mname] write_port_dict = {}
powers = self.parse_values(power_names, port, 1e3) # scale power to mw for measure in self.read_meas_objs:
#Check that power parsing worked. write_port_dict[measure.name] = measure.retrieve_measure(port=port)
for name, power in powers.items():
if type(power)!=float: if not check_dict_values_is_float(write_port_dict):
debug.error("Failed to Parse Power Values:\n\t\t{0}".format(powers),1) #Printing the entire dict looks bad. 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(powers) result[port].update(write_port_dict)
# The delay is from the negative edge for our SRAM # The delay is from the negative edge for our SRAM
return (True,result) return (True,result)
@ -502,13 +486,13 @@ class delay(simulation):
#key=raw_input("press return to continue") #key=raw_input("press return to continue")
return (leakage_power*1e3, trim_leakage_power*1e3) 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. """ """ Check if the measurements are defined and if they are valid. """
#Hard coded names currently #Hard coded names currently
delay_hl = delay_dict["delay_hl"] delay_hl = result_dict["delay_hl"]
delay_lh = delay_dict["delay_lh"] delay_lh = result_dict["delay_lh"]
slew_hl = delay_dict["slew_hl"] slew_hl = result_dict["slew_hl"]
slew_lh = delay_dict["slew_lh"] slew_lh = result_dict["slew_lh"]
period_load_slew_str = "period {0} load {1} slew {2}".format(self.period,self.load, self.slew) 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 # if it failed or the read was longer than a period

View File

@ -2,12 +2,14 @@ import debug
from tech import drc, parameter, spice from tech import drc, parameter, spice
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from .stimuli import * from .stimuli import *
from .charutils import *
class spice_measurement(ABC): class spice_measurement(ABC):
"""Base class for spice stimulus measurements.""" """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. #Names must be unique for correct spice simulation, but not enforced here.
self.name = measure_name self.name = measure_name
self.measure_scale = measure_scale
@abstractmethod @abstractmethod
def get_measure_function(self): def get_measure_function(self):
@ -24,12 +26,18 @@ class spice_measurement(ABC):
measure_vals = self.get_measure_values(*input_tuple) measure_vals = self.get_measure_values(*input_tuple)
measure_func(stim_obj, *measure_vals) 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): class delay_measure(spice_measurement):
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals.""" """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): def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str, measure_scale=None):
spice_measurement.__init__(self, measure_name) 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)
def get_measure_function(self): def get_measure_function(self):
@ -54,7 +62,8 @@ class delay_measure(spice_measurement):
targ_val = self.targ_val_of_vdd * vdd_voltage targ_val = self.targ_val_of_vdd * vdd_voltage
if port != None: 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) trig_name = self.trig_name_no_port.format(port)
targ_name = self.targ_name_no_port.format(port) targ_name = self.targ_name_no_port.format(port)
else: else:
@ -64,11 +73,10 @@ class delay_measure(spice_measurement):
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): class slew_measure(delay_measure):
def __init__(self, measure_name, signal_name, slew_dir_str): def __init__(self, measure_name, signal_name, slew_dir_str, measure_scale=None):
spice_measurement.__init__(self, measure_name) spice_measurement.__init__(self, measure_name, measure_scale)
self.set_meas_constants(signal_name, slew_dir_str) self.set_meas_constants(signal_name, slew_dir_str)
def set_meas_constants(self, signal_name, slew_dir_str): def set_meas_constants(self, signal_name, slew_dir_str):
@ -90,3 +98,25 @@ class slew_measure(delay_measure):
#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 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)