mirror of
https://github.com/VLSIDA/OpenRAM.git
synced 2026-08-29 09:30:38 +02:00
Fixed conflicts in delay and elmore modules on merge with dev.
This commit is contained in:
@@ -24,14 +24,22 @@ debug.info(1, "Initializing characterizer...")
|
||||
OPTS.spice_exe = ""
|
||||
|
||||
if not OPTS.analytical_delay:
|
||||
debug.info(1, "Finding spice simulator.")
|
||||
|
||||
if OPTS.spice_name != "":
|
||||
# Capitalize Xyce
|
||||
if OPTS.spice_name == "xyce":
|
||||
OPTS.spice_name = "Xyce"
|
||||
OPTS.spice_exe=find_exe(OPTS.spice_name)
|
||||
if OPTS.spice_exe=="" or OPTS.spice_exe==None:
|
||||
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1)
|
||||
else:
|
||||
(OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["ngspice", "ngspice.exe", "hspice", "xa"])
|
||||
(OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["Xyce", "ngspice", "ngspice.exe", "hspice", "xa"])
|
||||
|
||||
if OPTS.spice_name in ["Xyce", "xyce"]:
|
||||
(OPTS.mpi_name, OPTS.mpi_exe) = get_tool("mpi", ["mpirun"])
|
||||
OPTS.hier_seperator = ":"
|
||||
else:
|
||||
OPTS.mpi_name = None
|
||||
OPTS.mpi_exe = ""
|
||||
|
||||
# set the input dir for spice files if using ngspice
|
||||
if OPTS.spice_name == "ngspice":
|
||||
@@ -39,6 +47,12 @@ if not OPTS.analytical_delay:
|
||||
|
||||
if OPTS.spice_exe == "":
|
||||
debug.error("No recognizable spice version found. Unable to perform characterization.", 1)
|
||||
else:
|
||||
debug.info(1, "Finding spice simulator: {} ({})".format(OPTS.spice_name, OPTS.spice_exe))
|
||||
if OPTS.mpi_name:
|
||||
debug.info(1, "MPI for spice simulator: {} ({})".format(OPTS.mpi_name, OPTS.mpi_exe))
|
||||
debug.info(1, "Simulation threads: {}".format(OPTS.num_sim_threads))
|
||||
|
||||
else:
|
||||
debug.info(1, "Analytical model enabled.")
|
||||
|
||||
|
||||
@@ -11,21 +11,26 @@ import debug
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
def relative_compare(value1,value2,error_tolerance=0.001):
|
||||
def relative_compare(value1, value2, error_tolerance=0.001):
|
||||
""" This is used to compare relative values for convergence. """
|
||||
return (abs(value1 - value2) / abs(max(value1,value2)) <= error_tolerance)
|
||||
return (abs(value1 - value2) / abs(max(value1, value2)) <= error_tolerance)
|
||||
|
||||
|
||||
def parse_spice_list(filename, key):
|
||||
"""Parses a hspice output.lis file for a key value"""
|
||||
|
||||
lower_key = key.lower()
|
||||
|
||||
if OPTS.spice_name == "xa" :
|
||||
# customsim has a different output file name
|
||||
full_filename="{0}xa.meas".format(OPTS.openram_temp)
|
||||
elif OPTS.spice_name == "spectre":
|
||||
full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure")
|
||||
elif OPTS.spice_name in ["Xyce", "xyce"]:
|
||||
full_filename = os.path.join(OPTS.openram_temp, "spice_stdout.log")
|
||||
else:
|
||||
# ngspice/hspice using a .lis file
|
||||
full_filename="{0}{1}.lis".format(OPTS.openram_temp, filename)
|
||||
full_filename = "{0}{1}.lis".format(OPTS.openram_temp, filename)
|
||||
|
||||
try:
|
||||
f = open(full_filename, "r")
|
||||
@@ -33,31 +38,34 @@ def parse_spice_list(filename, key):
|
||||
debug.error("Unable to open spice output file: {0}".format(full_filename),1)
|
||||
debug.archive()
|
||||
|
||||
contents = f.read()
|
||||
contents = f.read().lower()
|
||||
f.close()
|
||||
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)
|
||||
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(key), contents)
|
||||
val = re.search(r"{0}\s*=\s*(-?\d+.?\d*[e]?[-+]?[0-9]*\S*)\s+.*".format(lower_key), contents)
|
||||
if val != None:
|
||||
debug.info(4, "Key = " + key + " Val = " + val.group(1))
|
||||
debug.info(4, "Key = " + lower_key + " Val = " + val.group(1))
|
||||
return convert_to_float(val.group(1))
|
||||
else:
|
||||
return "Failed"
|
||||
|
||||
def round_time(time,time_precision=3):
|
||||
|
||||
def round_time(time, time_precision=3):
|
||||
# times are in ns, so this is how many digits of precision
|
||||
# 3 digits = 1ps
|
||||
# 4 digits = 0.1ps
|
||||
# etc.
|
||||
return round(time,time_precision)
|
||||
return round(time, time_precision)
|
||||
|
||||
def round_voltage(voltage,voltag_precision=5):
|
||||
|
||||
def round_voltage(voltage, voltage_precision=5):
|
||||
# voltages are in volts
|
||||
# 3 digits = 1mv
|
||||
# 4 digits = 0.1mv
|
||||
# 5 digits = 0.01mv
|
||||
# 6 digits = 1uv
|
||||
# etc
|
||||
return round(voltage,voltage_precision)
|
||||
return round(voltage, voltage_precision)
|
||||
|
||||
|
||||
def convert_to_float(number):
|
||||
"""Converts a string into a (float) number; also converts units(m,u,n,p)"""
|
||||
@@ -84,7 +92,7 @@ def convert_to_float(number):
|
||||
'n': lambda x: x * 0.000000001, # nano
|
||||
'p': lambda x: x * 0.000000000001, # pico
|
||||
'f': lambda x: x * 0.000000000000001 # femto
|
||||
}[unit.group(2)](float(unit.group(1)))
|
||||
}[unit.group(2)](float(unit.group(1)))
|
||||
|
||||
# if we weren't able to convert it to a float then error out
|
||||
if not type(float_value)==float:
|
||||
@@ -92,9 +100,10 @@ def convert_to_float(number):
|
||||
|
||||
return float_value
|
||||
|
||||
|
||||
def check_dict_values_is_float(dict):
|
||||
"""Checks if all the values are floats. Useful for checking failed Spice measurements."""
|
||||
for key, value in dict.items():
|
||||
if type(value)!=float:
|
||||
return False
|
||||
if type(value)!=float:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -69,7 +69,7 @@ class delay(simulation):
|
||||
for meas in meas_list:
|
||||
name = meas.name.lower()
|
||||
debug.check(name not in name_set, ("SPICE measurements must have unique names. "
|
||||
"Duplicate name={}").format(name))
|
||||
"Duplicate name={0}").format(name))
|
||||
name_set.add(name)
|
||||
|
||||
def create_read_port_measurement_objects(self):
|
||||
@@ -77,7 +77,7 @@ class delay(simulation):
|
||||
|
||||
self.read_lib_meas = []
|
||||
self.clk_frmt = "clk{0}" # Unformatted clock name
|
||||
targ_name = "{0}{1}_{2}".format(self.dout_name, "{}", self.probe_data) # Empty values are the port and probe data bit
|
||||
targ_name = "{0}{{}}_{1}".format(self.dout_name, self.probe_data) # Empty values are the port and probe data bit
|
||||
self.delay_meas = []
|
||||
self.delay_meas.append(delay_measure("delay_lh", self.clk_frmt, targ_name, "RISE", "RISE", measure_scale=1e9))
|
||||
self.delay_meas[-1].meta_str = sram_op.READ_ONE # Used to index time delay values when measurements written to spice file.
|
||||
@@ -166,7 +166,7 @@ class delay(simulation):
|
||||
self.dout_volt_meas = []
|
||||
for meas in self.delay_meas:
|
||||
# Output voltage measures
|
||||
self.dout_volt_meas.append(voltage_at_measure("v_{}".format(meas.name),
|
||||
self.dout_volt_meas.append(voltage_at_measure("v_{0}".format(meas.name),
|
||||
meas.targ_name_no_port))
|
||||
self.dout_volt_meas[-1].meta_str = meas.meta_str
|
||||
|
||||
@@ -186,7 +186,7 @@ class delay(simulation):
|
||||
self.read_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
|
||||
meas_cycles = (sram_op.READ_ZERO, sram_op.READ_ONE)
|
||||
for cycle in meas_cycles:
|
||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||
for polarity, meas in single_bit_meas.items():
|
||||
meas.meta_str = cycle
|
||||
@@ -200,7 +200,7 @@ class delay(simulation):
|
||||
self.write_bit_meas = {bit_polarity.NONINVERTING: [], bit_polarity.INVERTING: []}
|
||||
meas_cycles = (sram_op.WRITE_ZERO, sram_op.WRITE_ONE)
|
||||
for cycle in meas_cycles:
|
||||
meas_tag = "a{}_b{}_{}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
meas_tag = "a{0}_b{1}_{2}".format(self.probe_address, self.probe_data, cycle.name)
|
||||
single_bit_meas = self.get_bit_measures(meas_tag, self.probe_address, self.probe_data)
|
||||
for polarity, meas in single_bit_meas.items():
|
||||
meas.meta_str = cycle
|
||||
@@ -219,20 +219,20 @@ class delay(simulation):
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, bit_row, bit_col)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={}").format(storage_names))
|
||||
"supported for characterization. Storage nets={0}").format(storage_names))
|
||||
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
||||
bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col)
|
||||
q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
|
||||
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
|
||||
else:
|
||||
q_name = cell_name + '.' + str(storage_names[0])
|
||||
qbar_name = cell_name + '.' + str(storage_names[1])
|
||||
q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
|
||||
qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
|
||||
|
||||
# Bit measures, measurements times to be defined later. The measurement names must be unique
|
||||
# but they is enforced externally. {} added to names to differentiate between ports allow the
|
||||
# measurements are independent of the ports
|
||||
q_meas = voltage_at_measure("v_q_{}".format(meas_tag), q_name)
|
||||
qbar_meas = voltage_at_measure("v_qbar_{}".format(meas_tag), qbar_name)
|
||||
q_meas = voltage_at_measure("v_q_{0}".format(meas_tag), q_name)
|
||||
qbar_meas = voltage_at_measure("v_qbar_{0}".format(meas_tag), qbar_name)
|
||||
|
||||
return {bit_polarity.NONINVERTING: q_meas, bit_polarity.INVERTING: qbar_meas}
|
||||
|
||||
@@ -242,15 +242,15 @@ class delay(simulation):
|
||||
# FIXME: There should be a default_read_port variable in this case, pathing is done with this
|
||||
# but is never mentioned otherwise
|
||||
port = self.read_ports[0]
|
||||
sen_and_port = self.sen_name+str(port)
|
||||
sen_and_port = self.sen_name + str(port)
|
||||
bl_and_port = self.bl_name.format(port) # bl_name contains a '{}' for the port
|
||||
# Isolate the s_en and bitline paths
|
||||
debug.info(1, "self.bl_name = {}".format(self.bl_name))
|
||||
debug.info(1, "self.graph.all_paths = {}".format(self.graph.all_paths))
|
||||
debug.info(1, "self.bl_name = {0}".format(self.bl_name))
|
||||
debug.info(1, "self.graph.all_paths = {0}".format(self.graph.all_paths))
|
||||
sen_paths = [path for path in self.graph.all_paths if sen_and_port in path]
|
||||
bl_paths = [path for path in self.graph.all_paths if bl_and_port in path]
|
||||
debug.check(len(sen_paths)==1, 'Found {} paths which contain the s_en net.'.format(len(sen_paths)))
|
||||
debug.check(len(bl_paths)==1, 'Found {} paths which contain the bitline net.'.format(len(bl_paths)))
|
||||
debug.check(len(sen_paths)==1, 'Found {0} paths which contain the s_en net.'.format(len(sen_paths)))
|
||||
debug.check(len(bl_paths)==1, 'Found {0} paths which contain the bitline net.'.format(len(bl_paths)))
|
||||
sen_path = sen_paths[0]
|
||||
bitline_path = bl_paths[0]
|
||||
|
||||
@@ -286,11 +286,11 @@ class delay(simulation):
|
||||
|
||||
# Create the measurements
|
||||
path_meas = []
|
||||
for i in range(len(path)-1):
|
||||
cur_net, next_net = path[i], path[i+1]
|
||||
cur_dir, next_dir = path_dirs[i], path_dirs[i+1]
|
||||
meas_name = "delay_{}_to_{}".format(cur_net, next_net)
|
||||
if i+1 != len(path)-1:
|
||||
for i in range(len(path) - 1):
|
||||
cur_net, next_net = path[i], path[i + 1]
|
||||
cur_dir, next_dir = path_dirs[i], path_dirs[i + 1]
|
||||
meas_name = "delay_{0}_to_{1}".format(cur_net, next_net)
|
||||
if i + 1 != len(path) - 1:
|
||||
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, next_dir, measure_scale=1e9, has_port=False))
|
||||
else: # Make the last measurement always measure on FALL because is a read 0
|
||||
path_meas.append(delay_measure(meas_name, cur_net, next_net, cur_dir, "FALL", measure_scale=1e9, has_port=False))
|
||||
@@ -309,13 +309,13 @@ class delay(simulation):
|
||||
# Convert to booleans based on function of modules (inverting/non-inverting)
|
||||
mod_type_bools = [mod.is_non_inverting() for mod in edge_mods]
|
||||
|
||||
#FIXME: obtuse hack to differentiate s_en input from bitline in sense amps
|
||||
# FIXME: obtuse hack to differentiate s_en input from bitline in sense amps
|
||||
if self.sen_name in path:
|
||||
# Force the sense amp to be inverting for s_en->DOUT.
|
||||
# bitline->DOUT is non-inverting, but the module cannot differentiate inputs.
|
||||
s_en_index = path.index(self.sen_name)
|
||||
mod_type_bools[s_en_index] = False
|
||||
debug.info(2,'Forcing sen->dout to be inverting.')
|
||||
debug.info(2, 'Forcing sen->dout to be inverting.')
|
||||
|
||||
# Use these to determine direction list assuming delay start on neg. edge of clock (FALL)
|
||||
# Also, use shorthand that 'FALL' == False, 'RISE' == True to simplify logic
|
||||
@@ -493,7 +493,7 @@ class delay(simulation):
|
||||
elif meas_type is voltage_at_measure:
|
||||
variant_tuple = self.get_volt_at_measure_variants(port, measure_obj)
|
||||
else:
|
||||
debug.error("Input function not defined for measurement type={}".format(meas_type))
|
||||
debug.error("Input function not defined for measurement type={0}".format(meas_type))
|
||||
# Removes port input from any object which does not use it. This shorthand only works if
|
||||
# the measurement has port as the last input. Could be implemented by measurement type or
|
||||
# remove entirely from measurement classes.
|
||||
@@ -515,7 +515,7 @@ class delay(simulation):
|
||||
elif delay_obj.meta_str == sram_op.READ_ONE:
|
||||
meas_cycle_delay = self.cycle_times[self.measure_cycles[port][delay_obj.meta_str]]
|
||||
else:
|
||||
debug.error("Unrecognized delay Index={}".format(delay_obj.meta_str),1)
|
||||
debug.error("Unrecognized delay Index={0}".format(delay_obj.meta_str), 1)
|
||||
|
||||
# These measurements have there time further delayed to the neg. edge of the clock.
|
||||
if delay_obj.meta_add_delay:
|
||||
@@ -587,20 +587,20 @@ class delay(simulation):
|
||||
# Output some comments to aid where cycles start and
|
||||
# what is happening
|
||||
for comment in self.cycle_comments:
|
||||
self.sf.write("* {}\n".format(comment))
|
||||
self.sf.write("* {0}\n".format(comment))
|
||||
|
||||
self.sf.write("\n")
|
||||
for read_port in self.targ_read_ports:
|
||||
self.sf.write("* Read ports {}\n".format(read_port))
|
||||
self.sf.write("* Read ports {0}\n".format(read_port))
|
||||
self.write_delay_measures_read_port(read_port)
|
||||
|
||||
for write_port in self.targ_write_ports:
|
||||
self.sf.write("* Write ports {}\n".format(write_port))
|
||||
self.sf.write("* Write ports {0}\n".format(write_port))
|
||||
self.write_delay_measures_write_port(write_port)
|
||||
|
||||
def load_pex_net(self, net: str):
|
||||
from subprocess import check_output, CalledProcessError
|
||||
prefix = (self.sram_instance_name + ".").lower()
|
||||
prefix = (self.sram_instance_name + OPTS.hier_seperator).lower()
|
||||
if not net.lower().startswith(prefix) or not OPTS.use_pex or not OPTS.calibre_pex:
|
||||
return net
|
||||
original_net = net
|
||||
@@ -640,26 +640,41 @@ class delay(simulation):
|
||||
col = self.bitline_column
|
||||
row = self.wordline_row
|
||||
for port in set(self.targ_read_ports + self.targ_write_ports):
|
||||
probe_nets.add("WEB{}".format(port))
|
||||
probe_nets.add("{}.w_en{}".format(self.sram_instance_name, port))
|
||||
probe_nets.add("{0}.Xbank0.Xport_data{1}.Xwrite_driver_array{1}.Xwrite_driver{2}.en_bar".format(
|
||||
self.sram_instance_name, port, self.bitline_column))
|
||||
probe_nets.add("{}.Xbank0.br_{}_{}".format(self.sram_instance_name, port,
|
||||
self.bitline_column))
|
||||
probe_nets.add("WEB{0}".format(port))
|
||||
probe_nets.add("{0}{2}w_en{1}".format(self.sram_instance_name, port, OPTS.hier_seperator))
|
||||
probe_nets.add("{0}{3}Xbank0{3}Xport_data{1}{3}Xwrite_driver_array{1}{3}Xwrite_driver{2}{3}en_bar".format(self.sram_instance_name,
|
||||
port,
|
||||
self.bitline_column,
|
||||
OPTS.hier_seperator))
|
||||
probe_nets.add("{0}{3}Xbank0{3}br_{1}_{2}".format(self.sram_instance_name,
|
||||
port,
|
||||
self.bitline_column,
|
||||
OPTS.hier_seperator))
|
||||
if not OPTS.use_pex:
|
||||
continue
|
||||
probe_nets.add(
|
||||
"{0}.vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name, row, col - 1))
|
||||
"{0}{3}vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name,
|
||||
row,
|
||||
col - 1,
|
||||
OPTS.hier_seperator))
|
||||
probe_nets.add(
|
||||
"{0}.p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name, port, col))
|
||||
"{0}{3}p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name,
|
||||
port,
|
||||
col,
|
||||
OPTS.hier_seperator))
|
||||
probe_nets.add(
|
||||
"{0}.vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, port, col))
|
||||
probe_nets.add("{0}.vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name,
|
||||
port, col))
|
||||
"{0}{3}vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name,
|
||||
port,
|
||||
col,
|
||||
OPTS.hier_seperator))
|
||||
probe_nets.add("{0}{3}vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name,
|
||||
port,
|
||||
col,
|
||||
OPTS.hier_seperator))
|
||||
probe_nets.update(self.measurement_nets)
|
||||
for net in probe_nets:
|
||||
debug.info(2, "Probe: {}".format(net))
|
||||
self.sf.write(".plot V({}) \n".format(self.load_pex_net(net)))
|
||||
debug.info(2, "Probe: {0}".format(net))
|
||||
self.sf.write(".plot V({0}) \n".format(self.load_pex_net(net)))
|
||||
|
||||
def write_power_measures(self):
|
||||
"""
|
||||
@@ -778,7 +793,7 @@ class delay(simulation):
|
||||
if not self.check_bit_measures(self.write_bit_meas, port):
|
||||
return(False, {})
|
||||
|
||||
debug.info(2, "Checking write values for port {}".format(port))
|
||||
debug.info(2, "Checking write values for port {0}".format(port))
|
||||
write_port_dict = {}
|
||||
for measure in self.write_lib_meas:
|
||||
write_port_dict[measure.name] = measure.retrieve_measure(port=port)
|
||||
@@ -792,7 +807,7 @@ class delay(simulation):
|
||||
if not self.check_bit_measures(self.read_bit_meas, port):
|
||||
return(False, {})
|
||||
|
||||
debug.info(2, "Checking read delay values for port {}".format(port))
|
||||
debug.info(2, "Checking read delay values for port {0}".format(port))
|
||||
# Check sen timing, then bitlines, then general measurements.
|
||||
if not self.check_sen_measure(port):
|
||||
return (False, {})
|
||||
@@ -821,7 +836,7 @@ class delay(simulation):
|
||||
"""Checks that the sen occurred within a half-period"""
|
||||
|
||||
sen_val = self.sen_meas.retrieve_measure(port=port)
|
||||
debug.info(2, "s_en delay={}ns".format(sen_val))
|
||||
debug.info(2, "s_en delay={0}ns".format(sen_val))
|
||||
if self.sen_meas.meta_add_delay:
|
||||
max_delay = self.period / 2
|
||||
else:
|
||||
@@ -843,22 +858,22 @@ class delay(simulation):
|
||||
elif self.br_name == meas.targ_name_no_port:
|
||||
br_vals[meas.meta_str] = val
|
||||
|
||||
debug.info(2, "{}={}".format(meas.name, val))
|
||||
debug.info(2, "{0}={1}".format(meas.name, val))
|
||||
|
||||
dout_success = True
|
||||
bl_success = False
|
||||
for meas in self.dout_volt_meas:
|
||||
val = meas.retrieve_measure(port=port)
|
||||
debug.info(2, "{}={}".format(meas.name, val))
|
||||
debug.info(2, "{0}={1}".format(meas.name, val))
|
||||
debug.check(type(val)==float, "Error retrieving numeric measurement: {0} {1}".format(meas.name, val))
|
||||
|
||||
if meas.meta_str == sram_op.READ_ONE and val < self.vdd_voltage * 0.1:
|
||||
dout_success = False
|
||||
debug.info(1, "Debug measurement failed. Value {}V was read on read 1 cycle.".format(val))
|
||||
debug.info(1, "Debug measurement failed. Value {0}V was read on read 1 cycle.".format(val))
|
||||
bl_success = self.check_bitline_meas(bl_vals[sram_op.READ_ONE], br_vals[sram_op.READ_ONE])
|
||||
elif meas.meta_str == sram_op.READ_ZERO and val > self.vdd_voltage * 0.9:
|
||||
dout_success = False
|
||||
debug.info(1, "Debug measurement failed. Value {}V was read on read 0 cycle.".format(val))
|
||||
debug.info(1, "Debug measurement failed. Value {0}V was read on read 0 cycle.".format(val))
|
||||
bl_success = self.check_bitline_meas(br_vals[sram_op.READ_ONE], bl_vals[sram_op.READ_ONE])
|
||||
|
||||
# If the bitlines have a correct value while the output does not then that is a
|
||||
@@ -877,7 +892,7 @@ class delay(simulation):
|
||||
for polarity, meas_list in bit_measures.items():
|
||||
for meas in meas_list:
|
||||
val = meas.retrieve_measure(port=port)
|
||||
debug.info(2, "{}={}".format(meas.name, val))
|
||||
debug.info(2, "{0}={1}".format(meas.name, val))
|
||||
if type(val) != float:
|
||||
continue
|
||||
meas_cycle = meas.meta_str
|
||||
@@ -896,8 +911,8 @@ class delay(simulation):
|
||||
success = val < self.vdd_voltage / 2
|
||||
if not success:
|
||||
debug.info(1, ("Wrong value detected on probe bit during read/write cycle. "
|
||||
"Check writes and control logic for bugs.\n measure={}, op={}, "
|
||||
"bit_storage={}, V(bit)={}").format(meas.name, meas_cycle.name, polarity.name, val))
|
||||
"Check writes and control logic for bugs.\n measure={0}, op={1}, "
|
||||
"bit_storage={2}, V(bit)={3}").format(meas.name, meas_cycle.name, polarity.name, val))
|
||||
|
||||
return success
|
||||
|
||||
@@ -912,7 +927,7 @@ class delay(simulation):
|
||||
min_dicharge = v_discharged_bl < self.vdd_voltage * 0.9
|
||||
min_diff = (v_charged_bl - v_discharged_bl) > self.vdd_voltage * 0.1
|
||||
|
||||
debug.info(1, "min_dicharge={}, min_diff={}".format(min_dicharge, min_diff))
|
||||
debug.info(1, "min_dicharge={0}, min_diff={1}".format(min_dicharge, min_diff))
|
||||
return (min_dicharge and min_diff)
|
||||
|
||||
def check_path_measures(self):
|
||||
@@ -921,11 +936,11 @@ class delay(simulation):
|
||||
# Get and set measurement, no error checking done other than prints.
|
||||
debug.info(2, "Checking measures in Delay Path")
|
||||
value_dict = {}
|
||||
for meas in self.sen_path_meas+self.bl_path_meas:
|
||||
for meas in self.sen_path_meas + self.bl_path_meas:
|
||||
val = meas.retrieve_measure()
|
||||
debug.info(2, '{}={}'.format(meas.name, val))
|
||||
if type(val) != float or val > self.period/2:
|
||||
debug.info(1,'Failed measurement:{}={}'.format(meas.name, val))
|
||||
debug.info(2, '{0}={1}'.format(meas.name, val))
|
||||
if type(val) != float or val > self.period / 2:
|
||||
debug.info(1, 'Failed measurement:{}={}'.format(meas.name, val))
|
||||
value_dict[meas.name] = val
|
||||
#debug.info(0, "value_dict={}".format(value_dict))
|
||||
return value_dict
|
||||
@@ -1100,14 +1115,14 @@ class delay(simulation):
|
||||
|
||||
# Set up to trim the netlist here if that is enabled
|
||||
if OPTS.trim_netlist:
|
||||
self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp)
|
||||
self.trim_sp_file = "{0}trimmed.sp".format(OPTS.openram_temp)
|
||||
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
|
||||
else:
|
||||
# The non-reduced netlist file when it is disabled
|
||||
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
self.trim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
|
||||
|
||||
# The non-reduced netlist file for power simulation
|
||||
self.sim_sp_file = "{}sram.sp".format(OPTS.openram_temp)
|
||||
self.sim_sp_file = "{0}sram.sp".format(OPTS.openram_temp)
|
||||
# Make a copy in temp for debugging
|
||||
shutil.copy(self.sp_file, self.sim_sp_file)
|
||||
|
||||
@@ -1194,6 +1209,7 @@ class delay(simulation):
|
||||
for mname, value in delay_results[port].items():
|
||||
if "power" in mname:
|
||||
# Subtract partial array leakage and add full array leakage for the power measures
|
||||
debug.info(1, "Adding leakage offset to {0} {1} + {2} = {3}".format(mname, value, leakage_offset, value + leakage_offset))
|
||||
measure_data[port][mname].append(value + leakage_offset)
|
||||
else:
|
||||
measure_data[port][mname].append(value)
|
||||
@@ -1246,13 +1262,13 @@ class delay(simulation):
|
||||
if self.t_current == 0:
|
||||
self.add_noop_all_ports("Idle cycle (no positive clock edge)")
|
||||
|
||||
self.add_write("W data 1 address {}".format(inverse_address),
|
||||
self.add_write("W data 1 address {0}".format(inverse_address),
|
||||
inverse_address,
|
||||
data_ones,
|
||||
wmask_ones,
|
||||
write_port)
|
||||
|
||||
self.add_write("W data 0 address {} to write value".format(self.probe_address),
|
||||
self.add_write("W data 0 address {0} to write value".format(self.probe_address),
|
||||
self.probe_address,
|
||||
data_zeros,
|
||||
wmask_ones,
|
||||
@@ -1263,11 +1279,11 @@ class delay(simulation):
|
||||
self.measure_cycles[write_port]["disabled_write0"] = len(self.cycle_times) - 1
|
||||
|
||||
# This also ensures we will have a H->L transition on the next read
|
||||
self.add_read("R data 1 address {} to set dout caps".format(inverse_address),
|
||||
self.add_read("R data 1 address {0} to set dout caps".format(inverse_address),
|
||||
inverse_address,
|
||||
read_port)
|
||||
|
||||
self.add_read("R data 0 address {} to check W0 worked".format(self.probe_address),
|
||||
self.add_read("R data 0 address {0} to check W0 worked".format(self.probe_address),
|
||||
self.probe_address,
|
||||
read_port)
|
||||
self.measure_cycles[read_port][sram_op.READ_ZERO] = len(self.cycle_times) - 1
|
||||
@@ -1277,7 +1293,7 @@ class delay(simulation):
|
||||
|
||||
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)")
|
||||
|
||||
self.add_write("W data 1 address {} to write value".format(self.probe_address),
|
||||
self.add_write("W data 1 address {0} to write value".format(self.probe_address),
|
||||
self.probe_address,
|
||||
data_ones,
|
||||
wmask_ones,
|
||||
@@ -1287,7 +1303,7 @@ class delay(simulation):
|
||||
self.add_noop_clock_one_port(write_port)
|
||||
self.measure_cycles[write_port]["disabled_write1"] = len(self.cycle_times) - 1
|
||||
|
||||
self.add_write("W data 0 address {} to clear din caps".format(inverse_address),
|
||||
self.add_write("W data 0 address {0} to clear din caps".format(inverse_address),
|
||||
inverse_address,
|
||||
data_zeros,
|
||||
wmask_ones,
|
||||
@@ -1297,11 +1313,11 @@ class delay(simulation):
|
||||
self.measure_cycles[read_port]["disabled_read1"] = len(self.cycle_times) - 1
|
||||
|
||||
# This also ensures we will have a L->H transition on the next read
|
||||
self.add_read("R data 0 address {} to clear dout caps".format(inverse_address),
|
||||
self.add_read("R data 0 address {0} to clear dout caps".format(inverse_address),
|
||||
inverse_address,
|
||||
read_port)
|
||||
|
||||
self.add_read("R data 1 address {} to check W1 worked".format(self.probe_address),
|
||||
self.add_read("R data 1 address {0} to check W1 worked".format(self.probe_address),
|
||||
self.probe_address,
|
||||
read_port)
|
||||
self.measure_cycles[read_port][sram_op.READ_ONE] = len(self.cycle_times) - 1
|
||||
|
||||
@@ -67,18 +67,15 @@ class elmore(simulation):
|
||||
load,
|
||||
total_delay.delay / 1e3,
|
||||
total_delay.slew / 1e3))
|
||||
|
||||
# Delay is only calculated on a single port and replicated for now.
|
||||
for port in self.all_ports:
|
||||
for mname in self.delay_meas_names + self.power_meas_names:
|
||||
if "power" in mname:
|
||||
port_data[port][mname].append(power.dynamic)
|
||||
elif "delay" in mname and port in self.read_ports:
|
||||
port_data[port][mname].append(total_delay.delay / 1e3)
|
||||
elif "slew" in mname and port in self.read_ports:
|
||||
port_data[port][mname].append(total_delay.slew / 1e3)
|
||||
else:
|
||||
debug.error("Measurement name not recognized: {}".format(mname), 1)
|
||||
# Delay is only calculated on a single port and replicated for now.
|
||||
for port in self.all_ports:
|
||||
for mname in self.delay_meas_names + self.power_meas_names:
|
||||
if "power" in mname:
|
||||
port_data[port][mname].append(power.dynamic)
|
||||
elif "delay" in mname and port in self.read_ports:
|
||||
port_data[port][mname].append(total_delay.delay / 1e3)
|
||||
elif "slew" in mname and port in self.read_ports:
|
||||
port_data[port][mname].append(total_delay.slew / 1e3)
|
||||
|
||||
# Margin for error in period. Calculated by averaging required margin for a small and large
|
||||
# memory. FIXME: margin is quite large, should be looked into.
|
||||
|
||||
@@ -81,7 +81,7 @@ class functional(simulation):
|
||||
self.create_graph()
|
||||
self.set_internal_spice_names()
|
||||
self.q_name, self.qbar_name = self.get_bit_name()
|
||||
debug.info(2, "q name={}\nqbar name={}".format(self.q_name, self.qbar_name))
|
||||
debug.info(2, "q name={0}\nqbar name={1}".format(self.q_name, self.qbar_name))
|
||||
|
||||
# Number of checks can be changed
|
||||
self.num_cycles = cycles
|
||||
@@ -144,7 +144,7 @@ class functional(simulation):
|
||||
for port in self.write_ports:
|
||||
addr = self.gen_addr()
|
||||
(word, spare) = self.gen_data()
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
combined_word = "{0}+{1}".format(word, spare)
|
||||
comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
|
||||
self.stored_words[addr] = word
|
||||
@@ -167,7 +167,7 @@ class functional(simulation):
|
||||
self.add_noop_one_port(port)
|
||||
else:
|
||||
(addr, word, spare) = self.get_data()
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
combined_word = "{0}+{1}".format(word, spare)
|
||||
comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current)
|
||||
self.add_read_one_port(comment, addr, port)
|
||||
self.add_read_check(word, port)
|
||||
@@ -197,7 +197,7 @@ class functional(simulation):
|
||||
self.add_noop_one_port(port)
|
||||
else:
|
||||
(word, spare) = self.gen_data()
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
combined_word = "{0}+{1}".format(word, spare)
|
||||
comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
|
||||
self.stored_words[addr] = word
|
||||
@@ -213,7 +213,7 @@ class functional(simulation):
|
||||
(word, spare) = self.gen_data()
|
||||
wmask = self.gen_wmask()
|
||||
new_word = self.gen_masked_data(old_word, word, wmask)
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
combined_word = "{0}+{1}".format(word, spare)
|
||||
comment = self.gen_cycle_comment("partial_write", combined_word, addr, wmask, port, self.t_current)
|
||||
self.add_write_one_port(comment, addr, word + spare, wmask, port)
|
||||
self.stored_words[addr] = new_word
|
||||
@@ -222,7 +222,7 @@ class functional(simulation):
|
||||
else:
|
||||
(addr, word) = random.choice(list(self.stored_words.items()))
|
||||
spare = self.stored_spares[addr[:self.addr_spare_index]]
|
||||
combined_word = "{}+{}".format(word, spare)
|
||||
combined_word = "{0}+{1}".format(word, spare)
|
||||
# The write driver is not sized sufficiently to drive through the two
|
||||
# bitcell access transistors to the read port. So, for now, we do not allow
|
||||
# a simultaneous write and read to the same address on different ports. This
|
||||
@@ -363,7 +363,7 @@ class functional(simulation):
|
||||
self.stim_sp = "functional_stim.sp"
|
||||
temp_stim = "{0}/{1}".format(self.output_path, self.stim_sp)
|
||||
self.sf = open(temp_stim, "w")
|
||||
self.sf.write("* Functional test stimulus file for {}ns period\n\n".format(self.period))
|
||||
self.sf.write("* Functional test stimulus file for {0}ns period\n\n".format(self.period))
|
||||
self.stim = stimuli(self.sf, self.corner)
|
||||
|
||||
# Write include statements
|
||||
@@ -387,16 +387,16 @@ class functional(simulation):
|
||||
|
||||
# Write important signals to stim file
|
||||
self.sf.write("\n\n* Important signals for debug\n")
|
||||
self.sf.write("* bl: {}\n".format(self.bl_name.format(port)))
|
||||
self.sf.write("* br: {}\n".format(self.br_name.format(port)))
|
||||
self.sf.write("* s_en: {}\n".format(self.sen_name))
|
||||
self.sf.write("* q: {}\n".format(self.q_name))
|
||||
self.sf.write("* qbar: {}\n".format(self.qbar_name))
|
||||
self.sf.write("* bl: {0}\n".format(self.bl_name.format(port)))
|
||||
self.sf.write("* br: {0}\n".format(self.br_name.format(port)))
|
||||
self.sf.write("* s_en: {0}\n".format(self.sen_name))
|
||||
self.sf.write("* q: {0}\n".format(self.q_name))
|
||||
self.sf.write("* qbar: {0}\n".format(self.qbar_name))
|
||||
|
||||
# Write debug comments to stim file
|
||||
self.sf.write("\n\n* Sequence of operations\n")
|
||||
for comment in self.fn_cycle_comments:
|
||||
self.sf.write("*{}\n".format(comment))
|
||||
self.sf.write("*{0}\n".format(comment))
|
||||
|
||||
# Generate data input bits
|
||||
self.sf.write("\n* Generation of data and address signals\n")
|
||||
@@ -414,10 +414,10 @@ class functional(simulation):
|
||||
# Generate control signals
|
||||
self.sf.write("\n * Generation of control signals\n")
|
||||
for port in self.all_ports:
|
||||
self.stim.gen_pwl("CSB{}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
||||
self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
|
||||
|
||||
for port in self.readwrite_ports:
|
||||
self.stim.gen_pwl("WEB{}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
||||
self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
|
||||
|
||||
# Generate wmask bits
|
||||
for port in self.write_ports:
|
||||
@@ -472,15 +472,15 @@ class functional(simulation):
|
||||
self.stim.write_control(self.cycle_times[-1] + self.period)
|
||||
self.sf.close()
|
||||
|
||||
#FIXME: Similar function to delay.py, refactor this
|
||||
# FIXME: Similar function to delay.py, refactor this
|
||||
def get_bit_name(self):
|
||||
""" Get a bit cell name """
|
||||
(cell_name, cell_inst) = self.sram.get_cell_name(self.sram.name, 0, 0)
|
||||
storage_names = cell_inst.mod.get_storage_net_names()
|
||||
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
|
||||
"supported for characterization. Storage nets={}").format(storage_names))
|
||||
q_name = cell_name + '.' + str(storage_names[0])
|
||||
qbar_name = cell_name + '.' + str(storage_names[1])
|
||||
"supported for characterization. Storage nets={0}").format(storage_names))
|
||||
q_name = cell_name + OPTS.hier_seperator + str(storage_names[0])
|
||||
qbar_name = cell_name + OPTS.hier_seperator + str(storage_names[1])
|
||||
|
||||
return (q_name, qbar_name)
|
||||
|
||||
|
||||
@@ -53,11 +53,20 @@ class spice_measurement(ABC):
|
||||
elif not self.has_port and port != None:
|
||||
debug.error("Unexpected port input received during measure retrieval.",1)
|
||||
|
||||
|
||||
class delay_measure(spice_measurement):
|
||||
"""Generates a spice measurement for the delay of 50%-to-50% points of two signals."""
|
||||
|
||||
def __init__(self, measure_name, trig_name, targ_name, trig_dir_str, targ_dir_str,\
|
||||
trig_vdd=0.5, targ_vdd=0.5, measure_scale=None, has_port=True):
|
||||
def __init__(self,
|
||||
measure_name,
|
||||
trig_name,
|
||||
targ_name,
|
||||
trig_dir_str,
|
||||
targ_dir_str,
|
||||
trig_vdd=0.5,
|
||||
targ_vdd=0.5,
|
||||
measure_scale=None,
|
||||
has_port=True):
|
||||
spice_measurement.__init__(self, measure_name, measure_scale, has_port)
|
||||
self.set_meas_constants(trig_name, targ_name, trig_dir_str, targ_dir_str, trig_vdd, targ_vdd)
|
||||
|
||||
@@ -73,7 +82,7 @@ class delay_measure(spice_measurement):
|
||||
self.trig_name_no_port = trig_name
|
||||
self.targ_name_no_port = targ_name
|
||||
|
||||
#Time delays and ports are variant and needed as inputs when writing the measurement
|
||||
# Time delays and ports are variant and needed as inputs when writing the measurement
|
||||
|
||||
def get_measure_values(self, trig_td, targ_td, vdd_voltage, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
@@ -82,7 +91,7 @@ class delay_measure(spice_measurement):
|
||||
targ_val = self.targ_val_of_vdd * vdd_voltage
|
||||
|
||||
if port != None:
|
||||
#For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
trig_name = self.trig_name_no_port.format(port)
|
||||
targ_name = self.targ_name_no_port.format(port)
|
||||
@@ -90,7 +99,8 @@ class delay_measure(spice_measurement):
|
||||
meas_name = self.name
|
||||
trig_name = self.trig_name_no_port
|
||||
targ_name = self.targ_name_no_port
|
||||
return (meas_name,trig_name,targ_name,trig_val,targ_val,self.trig_dir_str,self.targ_dir_str,trig_td,targ_td)
|
||||
return (meas_name, trig_name, targ_name, trig_val, targ_val, self.trig_dir_str, self.targ_dir_str, trig_td, targ_td)
|
||||
|
||||
|
||||
class slew_measure(delay_measure):
|
||||
|
||||
@@ -114,7 +124,8 @@ class slew_measure(delay_measure):
|
||||
self.trig_name_no_port = signal_name
|
||||
self.targ_name_no_port = signal_name
|
||||
|
||||
#Time delays and ports are variant and needed as inputs when writing the measurement
|
||||
# Time delays and ports are variant and needed as inputs when writing the measurement
|
||||
|
||||
|
||||
class power_measure(spice_measurement):
|
||||
"""Generates a spice measurement for the average power between two time points."""
|
||||
@@ -128,8 +139,8 @@ class power_measure(spice_measurement):
|
||||
|
||||
def set_meas_constants(self, power_type):
|
||||
"""Sets values useful for power simulations. This value is only meta related to the lib file (rise/fall)"""
|
||||
#Not needed for power simulation
|
||||
self.power_type = power_type #Expected to be "RISE"/"FALL"
|
||||
# Not needed for power simulation
|
||||
self.power_type = power_type # Expected to be "RISE"/"FALL"
|
||||
|
||||
def get_measure_values(self, t_initial, t_final, port=None):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
@@ -138,7 +149,8 @@ class power_measure(spice_measurement):
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
else:
|
||||
meas_name = self.name
|
||||
return (meas_name,t_initial,t_final)
|
||||
return (meas_name, t_initial, t_final)
|
||||
|
||||
|
||||
class voltage_when_measure(spice_measurement):
|
||||
"""Generates a spice measurement to measure the voltage of a signal based on the voltage of another."""
|
||||
@@ -161,7 +173,7 @@ class voltage_when_measure(spice_measurement):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
#For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
trig_name = self.trig_name_no_port.format(port)
|
||||
targ_name = self.targ_name_no_port.format(port)
|
||||
@@ -169,9 +181,10 @@ class voltage_when_measure(spice_measurement):
|
||||
meas_name = self.name
|
||||
trig_name = self.trig_name_no_port
|
||||
targ_name = self.targ_name_no_port
|
||||
trig_voltage = self.trig_val_of_vdd*vdd_voltage
|
||||
return (meas_name,trig_name,targ_name,trig_voltage,self.trig_dir_str,trig_td)
|
||||
trig_voltage = self.trig_val_of_vdd * vdd_voltage
|
||||
return (meas_name, trig_name, targ_name, trig_voltage, self.trig_dir_str, trig_td)
|
||||
|
||||
|
||||
class voltage_at_measure(spice_measurement):
|
||||
"""Generates a spice measurement to measure the voltage at a specific time.
|
||||
The time is considered variant with different periods."""
|
||||
@@ -191,11 +204,11 @@ class voltage_at_measure(spice_measurement):
|
||||
"""Constructs inputs to stimulus measurement function. Variant values are inputs here."""
|
||||
self.port_error_check(port)
|
||||
if port != None:
|
||||
#For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
# For dictionary indexing reasons, the name is formatted differently than the signals
|
||||
meas_name = "{}{}".format(self.name, port)
|
||||
targ_name = self.targ_name_no_port.format(port)
|
||||
else:
|
||||
meas_name = self.name
|
||||
targ_name = self.targ_name_no_port
|
||||
return (meas_name,targ_name,time_at)
|
||||
return (meas_name, targ_name, time_at)
|
||||
|
||||
|
||||
@@ -5,18 +5,16 @@
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import sys,re,shutil
|
||||
import debug
|
||||
import tech
|
||||
import math
|
||||
from .stimuli import *
|
||||
from .trim_spice import *
|
||||
from .charutils import *
|
||||
import utils
|
||||
from globals import OPTS
|
||||
from .delay import delay
|
||||
from .measurements import *
|
||||
|
||||
|
||||
class model_check(delay):
|
||||
"""Functions to test for the worst case delay in a target SRAM
|
||||
|
||||
@@ -39,43 +37,44 @@ class model_check(delay):
|
||||
self.power_name = "total_power"
|
||||
|
||||
def create_measurement_names(self, port):
|
||||
"""Create measurement names. The names themselves currently define the type of measurement"""
|
||||
#Create delay measurement names
|
||||
wl_en_driver_delay_names = ["delay_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_delay_names = ["delay_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
|
||||
sen_driver_delay_names = ["delay_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
|
||||
"""
|
||||
Create measurement names. The names themselves currently define the type of measurement
|
||||
"""
|
||||
wl_en_driver_delay_names = ["delay_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_delay_names = ["delay_wl_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_driver_stages())]
|
||||
sen_driver_delay_names = ["delay_sen_dvr_{0}".format(stage) for stage in range(1, self.get_num_sen_driver_stages())]
|
||||
if self.custom_delaychain:
|
||||
dc_delay_names = ['delay_dc_out_final']
|
||||
dc_delay_names = ["delay_dc_out_final"]
|
||||
else:
|
||||
dc_delay_names = ["delay_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
|
||||
self.wl_delay_meas_names = wl_en_driver_delay_names+["delay_wl_en", "delay_wl_bar"]+wl_driver_delay_names+["delay_wl"]
|
||||
dc_delay_names = ["delay_delay_chain_stage_{0}".format(stage) for stage in range(1, self.get_num_delay_stages() + 1)]
|
||||
self.wl_delay_meas_names = wl_en_driver_delay_names + ["delay_wl_en", "delay_wl_bar"] + wl_driver_delay_names + ["delay_wl"]
|
||||
if port not in self.sram.readonly_ports:
|
||||
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"]+dc_delay_names
|
||||
self.rbl_delay_meas_names = ["delay_gated_clk_nand", "delay_delay_chain_in"] + dc_delay_names
|
||||
else:
|
||||
self.rbl_delay_meas_names = ["delay_gated_clk_nand"]+dc_delay_names
|
||||
self.sae_delay_meas_names = ["delay_pre_sen"]+sen_driver_delay_names+["delay_sen"]
|
||||
self.rbl_delay_meas_names = ["delay_gated_clk_nand"] + dc_delay_names
|
||||
self.sae_delay_meas_names = ["delay_pre_sen"] + sen_driver_delay_names + ["delay_sen"]
|
||||
|
||||
# if self.custom_delaychain:
|
||||
# self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1)
|
||||
# self.delay_chain_indices = (len(self.rbl_delay_meas_names), len(self.rbl_delay_meas_names)+1)
|
||||
# else:
|
||||
self.delay_chain_indices = (len(self.rbl_delay_meas_names)-len(dc_delay_names), len(self.rbl_delay_meas_names))
|
||||
#Create slew measurement names
|
||||
wl_en_driver_slew_names = ["slew_wl_en_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_slew_names = ["slew_wl_dvr_{}".format(stage) for stage in range(1,self.get_num_wl_driver_stages())]
|
||||
sen_driver_slew_names = ["slew_sen_dvr_{}".format(stage) for stage in range(1,self.get_num_sen_driver_stages())]
|
||||
self.delay_chain_indices = (len(self.rbl_delay_meas_names) - len(dc_delay_names), len(self.rbl_delay_meas_names))
|
||||
# Create slew measurement names
|
||||
wl_en_driver_slew_names = ["slew_wl_en_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_slew_names = ["slew_wl_dvr_{0}".format(stage) for stage in range(1, self.get_num_wl_driver_stages())]
|
||||
sen_driver_slew_names = ["slew_sen_dvr_{0}".format(stage) for stage in range(1, self.get_num_sen_driver_stages())]
|
||||
if self.custom_delaychain:
|
||||
dc_slew_names = ['slew_dc_out_final']
|
||||
dc_slew_names = ["slew_dc_out_final"]
|
||||
else:
|
||||
dc_slew_names = ["slew_delay_chain_stage_{}".format(stage) for stage in range(1,self.get_num_delay_stages()+1)]
|
||||
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"]+wl_en_driver_slew_names+["slew_wl_en", "slew_wl_bar"]+wl_driver_slew_names+["slew_wl"]
|
||||
dc_slew_names = ["slew_delay_chain_stage_{0}".format(stage) for stage in range(1, self.get_num_delay_stages() + 1)]
|
||||
self.wl_slew_meas_names = ["slew_wl_gated_clk_bar"] + wl_en_driver_slew_names + ["slew_wl_en", "slew_wl_bar"] + wl_driver_slew_names + ["slew_wl"]
|
||||
if port not in self.sram.readonly_ports:
|
||||
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar","slew_gated_clk_nand", "slew_delay_chain_in"]+dc_slew_names
|
||||
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar", "slew_gated_clk_nand", "slew_delay_chain_in"] + dc_slew_names
|
||||
else:
|
||||
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"]+dc_slew_names
|
||||
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"]+sen_driver_slew_names+["slew_sen"]
|
||||
self.rbl_slew_meas_names = ["slew_rbl_gated_clk_bar"] + dc_slew_names
|
||||
self.sae_slew_meas_names = ["slew_replica_bl0", "slew_pre_sen"] + sen_driver_slew_names + ["slew_sen"]
|
||||
|
||||
self.bitline_meas_names = ["delay_wl_to_bl", "delay_bl_to_dout"]
|
||||
self.power_meas_names = ['read0_power']
|
||||
self.power_meas_names = ["read0_power"]
|
||||
|
||||
def create_signal_names(self, port):
|
||||
"""Creates list of the signal names used in the spice file along the wl and sen paths.
|
||||
@@ -83,40 +82,45 @@ class model_check(delay):
|
||||
replicated here.
|
||||
"""
|
||||
delay.create_signal_names(self)
|
||||
#Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
|
||||
wl_en_driver_signals = ["Xsram.Xcontrol{}.Xbuf_wl_en.Zb{}_int".format('{}', stage) for stage in range(1,self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_signals = ["Xsram.Xbank0.Xwordline_driver{}.Xwl_driver_inv{}.Zb{}_int".format('{}', self.wordline_row, stage) for stage in range(1,self.get_num_wl_driver_stages())]
|
||||
sen_driver_signals = ["Xsram.Xcontrol{}.Xbuf_s_en.Zb{}_int".format('{}',stage) for stage in range(1,self.get_num_sen_driver_stages())]
|
||||
|
||||
# Signal names are all hardcoded, need to update to make it work for probe address and different configurations.
|
||||
wl_en_driver_signals = ["Xsram{1}Xcontrol{{}}.Xbuf_wl_en.Zb{0}_int".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_wl_en_driver_stages())]
|
||||
wl_driver_signals = ["Xsram{2}Xbank0{2}Xwordline_driver{{}}{2}Xwl_driver_inv{0}{2}Zb{1}_int".format(self.wordline_row, stage, OPTS.hier_seperator) for stage in range(1, self.get_num_wl_driver_stages())]
|
||||
sen_driver_signals = ["Xsram{1}Xcontrol{{}}{1}Xbuf_s_en{1}Zb{0}_int".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_sen_driver_stages())]
|
||||
if self.custom_delaychain:
|
||||
delay_chain_signal_names = []
|
||||
else:
|
||||
delay_chain_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.Xdelay_chain.dout_{}".format('{}', stage) for stage in range(1,self.get_num_delay_stages())]
|
||||
delay_chain_signal_names = ["Xsram{1}Xcontrol{{}}{1}Xreplica_bitline{1}Xdelay_chain{1}dout_{0}".format(stage, OPTS.hier_seperator) for stage in range(1, self.get_num_delay_stages())]
|
||||
if len(self.sram.all_ports) > 1:
|
||||
port_format = '{}'
|
||||
else:
|
||||
port_format = ''
|
||||
self.wl_signal_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]+\
|
||||
wl_en_driver_signals+\
|
||||
["Xsram.wl_en{}".format('{}'), "Xsram.Xbank0.Xwordline_driver{}.wl_bar_{}".format('{}',self.wordline_row)]+\
|
||||
wl_driver_signals+\
|
||||
["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row)]
|
||||
pre_delay_chain_names = ["Xsram.Xcontrol{}.gated_clk_bar".format('{}')]
|
||||
self.wl_signal_names = ["Xsram{0}Xcontrol{{}}{0}gated_clk_bar".format(OPTS.hier_seperator)] + \
|
||||
wl_en_driver_signals + \
|
||||
["Xsram{0}wl_en{{}}".format(OPTS.hier_seperator),
|
||||
"Xsram{1}Xbank0{1}Xwordline_driver{{}}{1}wl_bar_{0}".format(self.wordline_row,
|
||||
OPTS.hier_seperator)] + \
|
||||
wl_driver_signals + \
|
||||
["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format,
|
||||
self.wordline_row,
|
||||
OPTS.hier_seperator)]
|
||||
pre_delay_chain_names = ["Xsram.Xcontrol{{}}{0}gated_clk_bar".format(OPTS.hier_seperator)]
|
||||
if port not in self.sram.readonly_ports:
|
||||
pre_delay_chain_names+= ["Xsram.Xcontrol{}.Xand2_rbl_in.zb_int".format('{}'), "Xsram.Xcontrol{}.rbl_in".format('{}')]
|
||||
pre_delay_chain_names+= ["Xsram{0}Xcontrol{{}}{0}Xand2_rbl_in{0}zb_int".format(OPTS.hier_seperator),
|
||||
"Xsram{0}Xcontrol{{}}{0}rbl_in".format(OPTS.hier_seperator)]
|
||||
|
||||
self.rbl_en_signal_names = pre_delay_chain_names+\
|
||||
delay_chain_signal_names+\
|
||||
["Xsram.Xcontrol{}.Xreplica_bitline.delayed_en".format('{}')]
|
||||
self.rbl_en_signal_names = pre_delay_chain_names + \
|
||||
delay_chain_signal_names + \
|
||||
["Xsram{0}Xcontrol{{}}{0}Xreplica_bitline{0}delayed_en".format(OPTS.hier_seperator)]
|
||||
|
||||
self.sae_signal_names = ["Xsram{0}Xcontrol{{}}{0}Xreplica_bitline{0}bl0_0".format(OPTS.hier_seperator),
|
||||
"Xsram{0}Xcontrol{{}}{0}pre_s_en".format(OPTS.hier_seperator)] + \
|
||||
sen_driver_signals + \
|
||||
["Xsram{0}s_en{{}}".format(OPTS.hier_seperator)]
|
||||
|
||||
self.sae_signal_names = ["Xsram.Xcontrol{}.Xreplica_bitline.bl0_0".format('{}'), "Xsram.Xcontrol{}.pre_s_en".format('{}')]+\
|
||||
sen_driver_signals+\
|
||||
["Xsram.s_en{}".format('{}')]
|
||||
|
||||
dout_name = "{0}{1}_{2}".format(self.dout_name,"{}",self.probe_data) #Empty values are the port and probe data bit
|
||||
self.bl_signal_names = ["Xsram.Xbank0.wl{}_{}".format(port_format, self.wordline_row),\
|
||||
"Xsram.Xbank0.bl{}_{}".format(port_format, self.bitline_column),\
|
||||
dout_name]
|
||||
self.bl_signal_names = ["Xsram{2}Xbank0{2}wl{0}_{1}".format(port_format, self.wordline_row, OPTS.hier_seperator),
|
||||
"Xsram{2}Xbank0{2}bl{0}_{1}".format(port_format, self.bitline_column, OPTS.hier_seperator),
|
||||
"{0}{{}}_{1}".format(self.dout_name, self.probe_data)] # Empty values are the port and probe data bit
|
||||
|
||||
def create_measurement_objects(self):
|
||||
"""Create the measurements used for read and write ports"""
|
||||
@@ -124,7 +128,7 @@ class model_check(delay):
|
||||
self.create_sae_meas_objs()
|
||||
self.create_bl_meas_objs()
|
||||
self.create_power_meas_objs()
|
||||
self.all_measures = self.wl_meas_objs+self.sae_meas_objs+self.bl_meas_objs+self.power_meas_objs
|
||||
self.all_measures = self.wl_meas_objs + self.sae_meas_objs + self.bl_meas_objs + self.power_meas_objs
|
||||
|
||||
def create_power_meas_objs(self):
|
||||
"""Create power measurement object. Only one."""
|
||||
@@ -138,14 +142,14 @@ class model_check(delay):
|
||||
targ_dir = "FALL"
|
||||
|
||||
for i in range(1, len(self.wl_signal_names)):
|
||||
self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i-1],
|
||||
self.wl_signal_names[i-1],
|
||||
self.wl_meas_objs.append(delay_measure(self.wl_delay_meas_names[i - 1],
|
||||
self.wl_signal_names[i - 1],
|
||||
self.wl_signal_names[i],
|
||||
trig_dir,
|
||||
targ_dir,
|
||||
measure_scale=1e9))
|
||||
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i-1],
|
||||
self.wl_signal_names[i-1],
|
||||
self.wl_meas_objs.append(slew_measure(self.wl_slew_meas_names[i - 1],
|
||||
self.wl_signal_names[i - 1],
|
||||
trig_dir,
|
||||
measure_scale=1e9))
|
||||
temp_dir = trig_dir
|
||||
@@ -155,9 +159,9 @@ class model_check(delay):
|
||||
|
||||
def create_bl_meas_objs(self):
|
||||
"""Create the measurements to measure the bitline to dout, static stages"""
|
||||
#Bitline has slightly different measurements, objects appends hardcoded.
|
||||
# Bitline has slightly different measurements, objects appends hardcoded.
|
||||
self.bl_meas_objs = []
|
||||
trig_dir, targ_dir = "RISE", "FALL" #Only check read 0
|
||||
trig_dir, targ_dir = "RISE", "FALL" # Only check read 0
|
||||
self.bl_meas_objs.append(delay_measure(self.bitline_meas_names[0],
|
||||
self.bl_signal_names[0],
|
||||
self.bl_signal_names[-1],
|
||||
@@ -171,22 +175,22 @@ class model_check(delay):
|
||||
self.sae_meas_objs = []
|
||||
trig_dir = "RISE"
|
||||
targ_dir = "FALL"
|
||||
#Add measurements from gated_clk_bar to RBL
|
||||
# Add measurements from gated_clk_bar to RBL
|
||||
for i in range(1, len(self.rbl_en_signal_names)):
|
||||
self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i-1],
|
||||
self.rbl_en_signal_names[i-1],
|
||||
self.sae_meas_objs.append(delay_measure(self.rbl_delay_meas_names[i - 1],
|
||||
self.rbl_en_signal_names[i - 1],
|
||||
self.rbl_en_signal_names[i],
|
||||
trig_dir,
|
||||
targ_dir,
|
||||
measure_scale=1e9))
|
||||
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i-1],
|
||||
self.rbl_en_signal_names[i-1],
|
||||
self.sae_meas_objs.append(slew_measure(self.rbl_slew_meas_names[i - 1],
|
||||
self.rbl_en_signal_names[i - 1],
|
||||
trig_dir,
|
||||
measure_scale=1e9))
|
||||
temp_dir = trig_dir
|
||||
trig_dir = targ_dir
|
||||
targ_dir = temp_dir
|
||||
if self.custom_delaychain: #Hack for custom delay chains
|
||||
if self.custom_delaychain: # Hack for custom delay chains
|
||||
self.sae_meas_objs[-2] = delay_measure(self.rbl_delay_meas_names[-1],
|
||||
self.rbl_en_signal_names[-2],
|
||||
self.rbl_en_signal_names[-1],
|
||||
@@ -198,18 +202,18 @@ class model_check(delay):
|
||||
trig_dir,
|
||||
measure_scale=1e9))
|
||||
|
||||
#Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
|
||||
# Add measurements from rbl_out to sae. Trigger directions do not invert from previous stage due to RBL.
|
||||
trig_dir = "FALL"
|
||||
targ_dir = "RISE"
|
||||
for i in range(1, len(self.sae_signal_names)):
|
||||
self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i-1],
|
||||
self.sae_signal_names[i-1],
|
||||
self.sae_meas_objs.append(delay_measure(self.sae_delay_meas_names[i - 1],
|
||||
self.sae_signal_names[i - 1],
|
||||
self.sae_signal_names[i],
|
||||
trig_dir,
|
||||
targ_dir,
|
||||
measure_scale=1e9))
|
||||
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i-1],
|
||||
self.sae_signal_names[i-1],
|
||||
self.sae_meas_objs.append(slew_measure(self.sae_slew_meas_names[i - 1],
|
||||
self.sae_signal_names[i - 1],
|
||||
trig_dir,
|
||||
measure_scale=1e9))
|
||||
temp_dir = trig_dir
|
||||
@@ -231,16 +235,16 @@ class model_check(delay):
|
||||
self.sf.write("* {}\n".format(comment))
|
||||
|
||||
for read_port in self.targ_read_ports:
|
||||
self.write_measures_read_port(read_port)
|
||||
self.write_measures_read_port(read_port)
|
||||
|
||||
def get_delay_measure_variants(self, port, measure_obj):
|
||||
"""Get the measurement values that can either vary from simulation to simulation (vdd, address)
|
||||
or port to port (time delays)"""
|
||||
#Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
|
||||
#Assuming only read 0 for now
|
||||
debug.info(3,"Power measurement={}".format(measure_obj))
|
||||
# Return value is intended to match the delay measure format: trig_td, targ_td, vdd, port
|
||||
# Assuming only read 0 for now
|
||||
debug.info(3, "Power measurement={}".format(measure_obj))
|
||||
if (type(measure_obj) is delay_measure or type(measure_obj) is slew_measure):
|
||||
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period/2
|
||||
meas_cycle_delay = self.cycle_times[self.measure_cycles[port]["read0"]] + self.period / 2
|
||||
return (meas_cycle_delay, meas_cycle_delay, self.vdd_voltage, port)
|
||||
elif type(measure_obj) is power_measure:
|
||||
return self.get_power_measure_variants(port, measure_obj, "read")
|
||||
@@ -249,9 +253,9 @@ class model_check(delay):
|
||||
|
||||
def get_power_measure_variants(self, port, power_obj, operation):
|
||||
"""Get the measurement values that can either vary port to port (time delays)"""
|
||||
#Return value is intended to match the power measure format: t_initial, t_final, port
|
||||
# Return value is intended to match the power measure format: t_initial, t_final, port
|
||||
t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
|
||||
t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1]
|
||||
t_final = self.cycle_times[self.measure_cycles[port]["read0"] + 1]
|
||||
|
||||
return (t_initial, t_final, port)
|
||||
|
||||
@@ -280,8 +284,8 @@ class model_check(delay):
|
||||
elif type(measure)is power_measure:
|
||||
power_meas_list.append(measure_value)
|
||||
else:
|
||||
debug.error("Measurement object not recognized.",1)
|
||||
return delay_meas_list, slew_meas_list,power_meas_list
|
||||
debug.error("Measurement object not recognized.", 1)
|
||||
return delay_meas_list, slew_meas_list, power_meas_list
|
||||
|
||||
def run_delay_simulation(self):
|
||||
"""
|
||||
@@ -290,7 +294,7 @@ class model_check(delay):
|
||||
works on the trimmed netlist by default, so powers do not
|
||||
include leakage of all cells.
|
||||
"""
|
||||
#Sanity Check
|
||||
# Sanity Check
|
||||
debug.check(self.period > 0, "Target simulation period non-positive")
|
||||
|
||||
wl_delay_result = [[] for i in self.all_ports]
|
||||
@@ -303,16 +307,16 @@ class model_check(delay):
|
||||
# Checking from not data_value to data_value
|
||||
self.write_delay_stimulus()
|
||||
|
||||
self.stim.run_sim() #running sim prodoces spice output file.
|
||||
self.stim.run_sim() # running sim prodoces spice output file.
|
||||
|
||||
#Retrieve the results from the output file
|
||||
# Retrieve the results from the output file
|
||||
for port in self.targ_read_ports:
|
||||
#Parse and check the voltage measurements
|
||||
wl_delay_result[port], wl_slew_result[port],_ = self.get_measurement_values(self.wl_meas_objs, port)
|
||||
sae_delay_result[port], sae_slew_result[port],_ = self.get_measurement_values(self.sae_meas_objs, port)
|
||||
bl_delay_result[port], bl_slew_result[port],_ = self.get_measurement_values(self.bl_meas_objs, port)
|
||||
_,__,power_result[port] = self.get_measurement_values(self.power_meas_objs, port)
|
||||
return (True,wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result)
|
||||
# Parse and check the voltage measurements
|
||||
wl_delay_result[port], wl_slew_result[port], _ = self.get_measurement_values(self.wl_meas_objs, port)
|
||||
sae_delay_result[port], sae_slew_result[port], _ = self.get_measurement_values(self.sae_meas_objs, port)
|
||||
bl_delay_result[port], bl_slew_result[port], _ = self.get_measurement_values(self.bl_meas_objs, port)
|
||||
_, __, power_result[port] = self.get_measurement_values(self.power_meas_objs, port)
|
||||
return (True, wl_delay_result, sae_delay_result, wl_slew_result, sae_slew_result, bl_delay_result, bl_slew_result, power_result)
|
||||
|
||||
def get_model_delays(self, port):
|
||||
"""Get model delays based on port. Currently assumes single RW port."""
|
||||
@@ -345,41 +349,41 @@ class model_check(delay):
|
||||
def scale_delays(self, delay_list):
|
||||
"""Takes in a list of measured delays and convert it to simple units to easily compare to model values."""
|
||||
converted_values = []
|
||||
#Calculate average
|
||||
# Calculate average
|
||||
total = 0
|
||||
for meas_value in delay_list:
|
||||
total+=meas_value
|
||||
average = total/len(delay_list)
|
||||
average = total / len(delay_list)
|
||||
|
||||
#Convert values
|
||||
# Convert values
|
||||
for meas_value in delay_list:
|
||||
converted_values.append(meas_value/average)
|
||||
converted_values.append(meas_value / average)
|
||||
return converted_values
|
||||
|
||||
def min_max_normalization(self, value_list):
|
||||
"""Re-scales input values on a range from 0-1 where min(list)=0, max(list)=1"""
|
||||
scaled_values = []
|
||||
min_max_diff = max(value_list) - min(value_list)
|
||||
average = sum(value_list)/len(value_list)
|
||||
average = sum(value_list) / len(value_list)
|
||||
for value in value_list:
|
||||
scaled_values.append((value-average)/(min_max_diff))
|
||||
scaled_values.append((value - average) / (min_max_diff))
|
||||
return scaled_values
|
||||
|
||||
def calculate_error_l2_norm(self, list_a, list_b):
|
||||
"""Calculates error between two lists using the l2 norm"""
|
||||
error_list = []
|
||||
for val_a, val_b in zip(list_a, list_b):
|
||||
error_list.append((val_a-val_b)**2)
|
||||
error_list.append((val_a - val_b)**2)
|
||||
return error_list
|
||||
|
||||
def compare_measured_and_model(self, measured_vals, model_vals):
|
||||
"""First scales both inputs into similar ranges and then compares the error between both."""
|
||||
scaled_meas = self.min_max_normalization(measured_vals)
|
||||
debug.info(1, "Scaled measurements:\n{}".format(scaled_meas))
|
||||
debug.info(1, "Scaled measurements:\n{0}".format(scaled_meas))
|
||||
scaled_model = self.min_max_normalization(model_vals)
|
||||
debug.info(1, "Scaled model:\n{}".format(scaled_model))
|
||||
debug.info(1, "Scaled model:\n{0}".format(scaled_model))
|
||||
errors = self.calculate_error_l2_norm(scaled_meas, scaled_model)
|
||||
debug.info(1, "Errors:\n{}\n".format(errors))
|
||||
debug.info(1, "Errors:\n{0}\n".format(errors))
|
||||
|
||||
def analyze(self, probe_address, probe_data, slews, loads, port):
|
||||
"""Measures entire delay path along the wordline and sense amp enable and compare it to the model delays."""
|
||||
@@ -391,19 +395,19 @@ class model_check(delay):
|
||||
self.create_measurement_objects()
|
||||
data_dict = {}
|
||||
|
||||
read_port = self.read_ports[0] #only test the first read port
|
||||
read_port = self.read_ports[0] # only test the first read port
|
||||
read_port = port
|
||||
self.targ_read_ports = [read_port]
|
||||
self.targ_write_ports = [self.write_ports[0]]
|
||||
debug.info(1,"Model test: corner {}".format(self.corner))
|
||||
debug.info(1, "Model test: corner {0}".format(self.corner))
|
||||
(success, wl_delays, sae_delays, wl_slews, sae_slews, bl_delays, bl_slews, powers)=self.run_delay_simulation()
|
||||
debug.check(success, "Model measurements Failed: period={}".format(self.period))
|
||||
debug.check(success, "Model measurements Failed: period={0}".format(self.period))
|
||||
|
||||
debug.info(1,"Measured Wordline delays (ns):\n\t {}".format(wl_delays[read_port]))
|
||||
debug.info(1,"Measured Wordline slews:\n\t {}".format(wl_slews[read_port]))
|
||||
debug.info(1,"Measured SAE delays (ns):\n\t {}".format(sae_delays[read_port]))
|
||||
debug.info(1,"Measured SAE slews:\n\t {}".format(sae_slews[read_port]))
|
||||
debug.info(1,"Measured Bitline delays (ns):\n\t {}".format(bl_delays[read_port]))
|
||||
debug.info(1, "Measured Wordline delays (ns):\n\t {0}".format(wl_delays[read_port]))
|
||||
debug.info(1, "Measured Wordline slews:\n\t {0}".format(wl_slews[read_port]))
|
||||
debug.info(1, "Measured SAE delays (ns):\n\t {0}".format(sae_delays[read_port]))
|
||||
debug.info(1, "Measured SAE slews:\n\t {0}".format(sae_slews[read_port]))
|
||||
debug.info(1, "Measured Bitline delays (ns):\n\t {0}".format(bl_delays[read_port]))
|
||||
|
||||
data_dict[self.wl_meas_name] = wl_delays[read_port]
|
||||
data_dict[self.sae_meas_name] = sae_delays[read_port]
|
||||
@@ -412,14 +416,14 @@ class model_check(delay):
|
||||
data_dict[self.bl_meas_name] = bl_delays[read_port]
|
||||
data_dict[self.power_name] = powers[read_port]
|
||||
|
||||
if OPTS.auto_delay_chain_sizing: #Model is not used in this case
|
||||
if OPTS.auto_delay_chain_sizing: # Model is not used in this case
|
||||
wl_model_delays, sae_model_delays = self.get_model_delays(read_port)
|
||||
debug.info(1,"Wordline model delays:\n\t {}".format(wl_model_delays))
|
||||
debug.info(1,"SAE model delays:\n\t {}".format(sae_model_delays))
|
||||
debug.info(1, "Wordline model delays:\n\t {0}".format(wl_model_delays))
|
||||
debug.info(1, "SAE model delays:\n\t {0}".format(sae_model_delays))
|
||||
data_dict[self.wl_model_name] = wl_model_delays
|
||||
data_dict[self.sae_model_name] = sae_model_delays
|
||||
|
||||
#Some evaluations of the model and measured values
|
||||
# Some evaluations of the model and measured values
|
||||
# debug.info(1, "Comparing wordline measurements and model.")
|
||||
# self.compare_measured_and_model(wl_delays[read_port], wl_model_delays)
|
||||
# debug.info(1, "Comparing SAE measurements and model")
|
||||
@@ -430,17 +434,17 @@ class model_check(delay):
|
||||
def get_all_signal_names(self):
|
||||
"""Returns all signals names as a dict indexed by hardcoded names. Useful for writing the head of the CSV."""
|
||||
name_dict = {}
|
||||
#Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
|
||||
# Signal names are more descriptive than the measurement names, first value trimmed to match size of measurements names.
|
||||
name_dict[self.wl_meas_name] = self.wl_signal_names[1:]
|
||||
name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:]+self.sae_signal_names[1:]
|
||||
name_dict[self.sae_meas_name] = self.rbl_en_signal_names[1:] + self.sae_signal_names[1:]
|
||||
name_dict[self.wl_slew_name] = self.wl_slew_meas_names
|
||||
name_dict[self.sae_slew_name] = self.rbl_slew_meas_names+self.sae_slew_meas_names
|
||||
name_dict[self.sae_slew_name] = self.rbl_slew_meas_names + self.sae_slew_meas_names
|
||||
name_dict[self.bl_meas_name] = self.bitline_meas_names[0:1]
|
||||
name_dict[self.power_name] = self.power_meas_names
|
||||
#name_dict[self.wl_slew_name] = self.wl_slew_meas_names
|
||||
# pname_dict[self.wl_slew_name] = self.wl_slew_meas_names
|
||||
|
||||
if OPTS.auto_delay_chain_sizing:
|
||||
name_dict[self.wl_model_name] = name_dict["wl_measures"] #model uses same names as measured.
|
||||
name_dict[self.wl_model_name] = name_dict["wl_measures"] # model uses same names as measured.
|
||||
name_dict[self.sae_model_name] = name_dict["sae_measures"]
|
||||
|
||||
return name_dict
|
||||
|
||||
@@ -76,10 +76,10 @@ class setup_hold():
|
||||
self.stim.write_supply()
|
||||
|
||||
def write_data(self, mode, target_time, correct_value):
|
||||
"""Create the data signals for setup/hold analysis. First period is to
|
||||
"""
|
||||
Create the data signals for setup/hold analysis. First period is to
|
||||
initialize it to the opposite polarity. Second period is used for
|
||||
characterization.
|
||||
|
||||
"""
|
||||
self.sf.write("\n* Generation of the data and clk signals\n")
|
||||
if correct_value == 1:
|
||||
@@ -106,8 +106,11 @@ class setup_hold():
|
||||
setup=0)
|
||||
|
||||
def write_clock(self):
|
||||
""" Create the clock signal for setup/hold analysis. First period initializes the FF
|
||||
while the second is used for characterization."""
|
||||
"""
|
||||
Create the clock signal for setup/hold analysis.
|
||||
First period initializes the FF
|
||||
while the second is used for characterization.
|
||||
"""
|
||||
|
||||
self.stim.gen_pwl(sig_name="clk",
|
||||
# initial clk edge is right after the 0 time to initialize a flop
|
||||
@@ -128,16 +131,6 @@ class setup_hold():
|
||||
else:
|
||||
dout_rise_or_fall = "FALL"
|
||||
|
||||
# in SETUP mode, the input mirrors what the output should be
|
||||
if mode == "SETUP":
|
||||
din_rise_or_fall = dout_rise_or_fall
|
||||
else:
|
||||
# in HOLD mode, however, the input should be opposite of the output
|
||||
if correct_value == 1:
|
||||
din_rise_or_fall = "FALL"
|
||||
else:
|
||||
din_rise_or_fall = "RISE"
|
||||
|
||||
self.sf.write("\n* Measure statements for pass/fail verification\n")
|
||||
trig_name = "clk"
|
||||
targ_name = "Q"
|
||||
@@ -153,19 +146,6 @@ class setup_hold():
|
||||
trig_td=1.9 * self.period,
|
||||
targ_td=1.9 * self.period)
|
||||
|
||||
targ_name = "D"
|
||||
# Start triggers right after initialize value is returned to normal
|
||||
# at one period
|
||||
self.stim.gen_meas_delay(meas_name="setup_hold_time",
|
||||
trig_name=trig_name,
|
||||
targ_name=targ_name,
|
||||
trig_val=trig_val,
|
||||
targ_val=targ_val,
|
||||
trig_dir="RISE",
|
||||
targ_dir=din_rise_or_fall,
|
||||
trig_td=1.2 * self.period,
|
||||
targ_td=1.2 * self.period)
|
||||
|
||||
def bidir_search(self, correct_value, mode):
|
||||
""" This will perform a bidirectional search for either setup or hold times.
|
||||
It starts with the feasible priod and looks a half period beyond or before it
|
||||
@@ -189,26 +169,28 @@ class setup_hold():
|
||||
correct_value=correct_value)
|
||||
self.stim.run_sim(self.stim_sp)
|
||||
ideal_clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
|
||||
setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time"))
|
||||
debug.info(2,"*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode, correct_value,ideal_clk_to_q,setuphold_time))
|
||||
# We use a 1/2 speed clock for some reason...
|
||||
setuphold_time = (feasible_bound - 2 * self.period)
|
||||
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
||||
passing_setuphold_time = -1 * setuphold_time
|
||||
else:
|
||||
passing_setuphold_time = setuphold_time
|
||||
debug.info(2, "*** {0} CHECK: {1} Ideal Clk-to-Q: {2} Setup/Hold: {3}".format(mode,
|
||||
correct_value,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time))
|
||||
|
||||
if type(ideal_clk_to_q)!=float or type(setuphold_time)!=float:
|
||||
debug.error("Initial hold time fails for data value feasible bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time),
|
||||
if type(ideal_clk_to_q)!=float:
|
||||
debug.error("Initial hold time fails for data value feasible "
|
||||
"bound {0} Clk-to-Q {1} Setup/Hold {2}".format(feasible_bound,
|
||||
ideal_clk_to_q,
|
||||
setuphold_time),
|
||||
2)
|
||||
|
||||
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
||||
setuphold_time *= -1e9
|
||||
else:
|
||||
setuphold_time *= 1e9
|
||||
|
||||
passing_setuphold_time = setuphold_time
|
||||
debug.info(2, "Checked initial {0} time {1}, data at {2}, clock at {3} ".format(mode,
|
||||
setuphold_time,
|
||||
feasible_bound,
|
||||
2 * self.period))
|
||||
#raw_input("Press Enter to continue...")
|
||||
|
||||
while True:
|
||||
target_time = (feasible_bound + infeasible_bound) / 2
|
||||
@@ -224,15 +206,14 @@ class setup_hold():
|
||||
|
||||
self.stim.run_sim(self.stim_sp)
|
||||
clk_to_q = convert_to_float(parse_spice_list("timing", "clk2q_delay"))
|
||||
setuphold_time = convert_to_float(parse_spice_list("timing", "setup_hold_time"))
|
||||
if type(clk_to_q) == float and (clk_to_q < 1.1 * ideal_clk_to_q) and type(setuphold_time)==float:
|
||||
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
||||
setuphold_time *= -1e9
|
||||
else:
|
||||
setuphold_time *= 1e9
|
||||
|
||||
debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
|
||||
# We use a 1/2 speed clock for some reason...
|
||||
setuphold_time = (target_time - 2 * self.period)
|
||||
if mode == "SETUP": # SETUP is clk-din, not din-clk
|
||||
passing_setuphold_time = -1 * setuphold_time
|
||||
else:
|
||||
passing_setuphold_time = setuphold_time
|
||||
if type(clk_to_q) == float and (clk_to_q < 1.1 * ideal_clk_to_q):
|
||||
debug.info(2, "PASS Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
|
||||
feasible_bound = target_time
|
||||
else:
|
||||
debug.info(2, "FAIL Clk-to-Q: {0} Setup/Hold: {1}".format(clk_to_q, setuphold_time))
|
||||
@@ -242,7 +223,6 @@ class setup_hold():
|
||||
debug.info(3, "CONVERGE {0} vs {1}".format(feasible_bound, infeasible_bound))
|
||||
break
|
||||
|
||||
|
||||
debug.info(2, "Converged on {0} time {1}.".format(mode, passing_setuphold_time))
|
||||
return passing_setuphold_time
|
||||
|
||||
|
||||
@@ -586,7 +586,7 @@ class simulation():
|
||||
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
|
||||
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
|
||||
for i in range(len(bl_names)):
|
||||
bl_names[i] = bl_names[i].split('.')[-1]
|
||||
bl_names[i] = bl_names[i].split(OPTS.hier_seperator)[-1]
|
||||
return bl_names[0], bl_names[1]
|
||||
|
||||
def get_empty_measure_data_dict(self):
|
||||
|
||||
@@ -146,7 +146,7 @@ class stimuli():
|
||||
edge. The first clk_time should be 0 and is the initial time that corresponds
|
||||
to the initial value.
|
||||
"""
|
||||
# the initial value is not a clock time
|
||||
|
||||
str = "Clock and data value lengths don't match. {0} clock values, {1} data values for {2}"
|
||||
debug.check(len(clk_times)==len(data_values),
|
||||
str.format(len(clk_times),
|
||||
@@ -181,7 +181,7 @@ class stimuli():
|
||||
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} TRIG v({1}) VAL={2} {3}=1 TD={4}n TARG v({5}) VAL={6} {7}=1 TD={8}n\n\n"
|
||||
self.sf.write(measure_string.format(meas_name,
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
trig_name,
|
||||
trig_val,
|
||||
trig_dir,
|
||||
@@ -194,7 +194,7 @@ class stimuli():
|
||||
def gen_meas_find_voltage(self, meas_name, trig_name, targ_name, trig_val, trig_dir, trig_td):
|
||||
""" Creates the .meas statement for the measurement of delay """
|
||||
measure_string=".meas tran {0} FIND v({1}) WHEN v({2})={3}v {4}=1 TD={5}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name,
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
trig_name,
|
||||
trig_val,
|
||||
@@ -204,7 +204,7 @@ class stimuli():
|
||||
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
|
||||
""" Creates the .meas statement for voltage at time"""
|
||||
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n"
|
||||
self.sf.write(measure_string.format(meas_name,
|
||||
self.sf.write(measure_string.format(meas_name.lower(),
|
||||
targ_name,
|
||||
time_at))
|
||||
|
||||
@@ -215,13 +215,13 @@ class stimuli():
|
||||
power_exp = "power"
|
||||
else:
|
||||
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')"
|
||||
self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name,
|
||||
self.sf.write(".meas tran {0} avg {1} from={2}n to={3}n\n\n".format(meas_name.lower(),
|
||||
power_exp,
|
||||
t_initial,
|
||||
t_final))
|
||||
|
||||
def gen_meas_value(self, meas_name, dout, t_initial, t_final):
|
||||
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_initial, t_final)
|
||||
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name.lower(), dout, t_initial, t_final)
|
||||
self.sf.write(measure_string)
|
||||
|
||||
def write_control(self, end_time, runlvl=4):
|
||||
@@ -238,8 +238,8 @@ class stimuli():
|
||||
reltol = 0.001 # 0.1%
|
||||
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests.
|
||||
|
||||
self.sf.write(".TEMP {}\n".format(self.temperature))
|
||||
if OPTS.spice_name == "ngspice":
|
||||
self.sf.write(".TEMP {}\n".format(self.temperature))
|
||||
# UIC is needed for ngspice to converge
|
||||
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
|
||||
# ngspice sometimes has convergence problems if not using gear method
|
||||
@@ -248,6 +248,7 @@ class stimuli():
|
||||
# unless you figure out what these are.
|
||||
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol))
|
||||
elif OPTS.spice_name == "spectre":
|
||||
self.sf.write(".TEMP {}\n".format(self.temperature))
|
||||
self.sf.write("simulator lang=spectre\n")
|
||||
if OPTS.use_pex:
|
||||
nestlvl = 1
|
||||
@@ -255,8 +256,7 @@ class stimuli():
|
||||
else:
|
||||
nestlvl = 10
|
||||
spectre_save = "lvlpub"
|
||||
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(
|
||||
spectre_save, nestlvl))
|
||||
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(spectre_save, nestlvl))
|
||||
self.sf.write("simulatorOptions options reltol=1e-3 vabstol=1e-6 iabstol=1e-12 temp={0} try_fast_op=no "
|
||||
"rforce=10m maxnotes=10 maxwarns=10 "
|
||||
" preservenode=all topcheck=fixall "
|
||||
@@ -265,12 +265,19 @@ class stimuli():
|
||||
self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate '
|
||||
' annotate=status maxiters=5 \n'.format("5p", end_time))
|
||||
self.sf.write("simulator lang=spice\n")
|
||||
else:
|
||||
elif OPTS.spice_name in ["hspice", "xa"]:
|
||||
self.sf.write(".TEMP {}\n".format(self.temperature))
|
||||
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
|
||||
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl))
|
||||
if OPTS.spice_name == "hspice": # for cadence plots
|
||||
self.sf.write(".OPTIONS PSF=1 \n")
|
||||
self.sf.write(".OPTIONS HIER_DELIM=1 \n")
|
||||
self.sf.write(".OPTIONS PSF=1 \n")
|
||||
self.sf.write(".OPTIONS HIER_DELIM=1 \n")
|
||||
elif OPTS.spice_name in ["Xyce", "xyce"]:
|
||||
self.sf.write(".OPTIONS DEVICE TEMP={}\n".format(self.temperature))
|
||||
self.sf.write(".OPTIONS MEASURE MEASFAIL=1\n")
|
||||
self.sf.write(".OPTIONS LINSOL type=klu\n")
|
||||
self.sf.write(".TRAN {0}p {1}n\n".format(timestep, end_time))
|
||||
else:
|
||||
debug.error("Unkown spice simulator {}".format(OPTS.spice_name))
|
||||
|
||||
# create plots for all signals
|
||||
if not OPTS.use_pex: # Don't save all for extracted simulations
|
||||
@@ -278,7 +285,7 @@ class stimuli():
|
||||
if OPTS.verbose_level>0:
|
||||
if OPTS.spice_name in ["hspice", "xa"]:
|
||||
self.sf.write(".probe V(*)\n")
|
||||
else:
|
||||
elif OPTS.spice_name != "Xyce":
|
||||
self.sf.write(".plot V(*)\n")
|
||||
else:
|
||||
self.sf.write("*.probe V(*)\n")
|
||||
@@ -312,7 +319,10 @@ class stimuli():
|
||||
|
||||
# Adding a commented out supply for simulators where gnd and 0 are not global grounds.
|
||||
self.sf.write("\n*Nodes gnd and 0 are the same global ground node in ngspice/hspice/xa. Otherwise, this source may be needed.\n")
|
||||
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
|
||||
if OPTS.spice_name in ["Xyce", "xyce"]:
|
||||
self.sf.write("V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
|
||||
else:
|
||||
self.sf.write("*V{0} {0} {1} {2}\n".format(self.gnd_name, gnd_node_name, 0.0))
|
||||
|
||||
def run_sim(self, name):
|
||||
""" Run hspice in batch mode and output rawfile to parse. """
|
||||
@@ -349,6 +359,19 @@ class stimuli():
|
||||
temp_stim,
|
||||
OPTS.openram_temp)
|
||||
valid_retcode=0
|
||||
elif OPTS.spice_name in ["Xyce", "xyce"]:
|
||||
if OPTS.num_sim_threads > 1 and OPTS.mpi_name:
|
||||
mpi_cmd = "{0} -np {1}".format(OPTS.mpi_exe,
|
||||
OPTS.num_sim_threads)
|
||||
else:
|
||||
mpi_cmd = ""
|
||||
|
||||
cmd = "{0} {1} -o {3}timing.lis {2}".format(mpi_cmd,
|
||||
OPTS.spice_exe,
|
||||
temp_stim,
|
||||
OPTS.openram_temp)
|
||||
|
||||
valid_retcode=0
|
||||
else:
|
||||
# ngspice 27+ supports threading with "set num_threads=4" in the stimulus file or a .spiceinit
|
||||
# Measurements can't be made with a raw file set in ngspice
|
||||
|
||||
Reference in New Issue
Block a user