Initial Xyce support.

This commit is contained in:
mrg 2021-05-14 11:28:29 -07:00
parent 3959cf73d1
commit 67a67111a6
4 changed files with 47 additions and 30 deletions

View File

@ -31,7 +31,7 @@ if not OPTS.analytical_delay:
if OPTS.spice_exe=="" or OPTS.spice_exe==None: if OPTS.spice_exe=="" or OPTS.spice_exe==None:
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1) debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1)
else: 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"])
# set the input dir for spice files if using ngspice # set the input dir for spice files if using ngspice
if OPTS.spice_name == "ngspice": if OPTS.spice_name == "ngspice":

View File

@ -11,21 +11,26 @@ import debug
from globals import OPTS 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. """ """ 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): def parse_spice_list(filename, key):
"""Parses a hspice output.lis file for a key value""" """Parses a hspice output.lis file for a key value"""
lower_key = key.lower()
if OPTS.spice_name == "xa" : if OPTS.spice_name == "xa" :
# customsim has a different output file name # customsim has a different output file name
full_filename="{0}xa.meas".format(OPTS.openram_temp) full_filename="{0}xa.meas".format(OPTS.openram_temp)
elif OPTS.spice_name == "spectre": elif OPTS.spice_name == "spectre":
full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure") full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure")
elif OPTS.spice_name == "Xyce":
full_filename = os.path.join(OPTS.openram_temp, "spice_stdout.log")
else: else:
# ngspice/hspice using a .lis file # 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: try:
f = open(full_filename, "r") 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.error("Unable to open spice output file: {0}".format(full_filename),1)
debug.archive() debug.archive()
contents = f.read() contents = f.read().lower()
f.close() 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*\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: 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)) return convert_to_float(val.group(1))
else: else:
return "Failed" 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 # times are in ns, so this is how many digits of precision
# 3 digits = 1ps # 3 digits = 1ps
# 4 digits = 0.1ps # 4 digits = 0.1ps
# etc. # 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 # voltages are in volts
# 3 digits = 1mv # 3 digits = 1mv
# 4 digits = 0.1mv # 4 digits = 0.1mv
# 5 digits = 0.01mv # 5 digits = 0.01mv
# 6 digits = 1uv # 6 digits = 1uv
# etc # etc
return round(voltage,voltage_precision) return round(voltage, voltage_precision)
def convert_to_float(number): def convert_to_float(number):
"""Converts a string into a (float) number; also converts units(m,u,n,p)""" """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 'n': lambda x: x * 0.000000001, # nano
'p': lambda x: x * 0.000000000001, # pico 'p': lambda x: x * 0.000000000001, # pico
'f': lambda x: x * 0.000000000000001 # femto '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 we weren't able to convert it to a float then error out
if not type(float_value)==float: if not type(float_value)==float:
@ -92,9 +100,10 @@ def convert_to_float(number):
return float_value return float_value
def check_dict_values_is_float(dict): def check_dict_values_is_float(dict):
"""Checks if all the values are floats. Useful for checking failed Spice measurements.""" """Checks if all the values are floats. Useful for checking failed Spice measurements."""
for key, value in dict.items(): for key, value in dict.items():
if type(value)!=float: if type(value)!=float:
return False return False
return True return True

View File

@ -191,7 +191,6 @@ class setup_hold():
setuphold_time, setuphold_time,
feasible_bound, feasible_bound,
2 * self.period)) 2 * self.period))
# raw_input("Press Enter to continue...")
while True: while True:
target_time = (feasible_bound + infeasible_bound) / 2 target_time = (feasible_bound + infeasible_bound) / 2

View File

@ -146,7 +146,7 @@ class stimuli():
edge. The first clk_time should be 0 and is the initial time that corresponds edge. The first clk_time should be 0 and is the initial time that corresponds
to the initial value. 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}" 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), debug.check(len(clk_times)==len(data_values),
str.format(len(clk_times), 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): 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 """ """ 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" 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_name,
trig_val, trig_val,
trig_dir, 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): 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 """ """ 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" 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, targ_name,
trig_name, trig_name,
trig_val, trig_val,
@ -204,7 +204,7 @@ class stimuli():
def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at): def gen_meas_find_voltage_at_time(self, meas_name, targ_name, time_at):
""" Creates the .meas statement for voltage at time""" """ Creates the .meas statement for voltage at time"""
measure_string=".meas tran {0} FIND v({1}) AT={2}n \n\n" 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, targ_name,
time_at)) time_at))
@ -215,13 +215,13 @@ class stimuli():
power_exp = "power" power_exp = "power"
else: else:
power_exp = "par('(-1*v(" + str(self.vdd_name) + ")*I(v" + str(self.vdd_name) + "))')" 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, power_exp,
t_initial, t_initial,
t_final)) t_final))
def gen_meas_value(self, meas_name, dout, 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) self.sf.write(measure_string)
def write_control(self, end_time, runlvl=4): def write_control(self, end_time, runlvl=4):
@ -238,8 +238,8 @@ class stimuli():
reltol = 0.001 # 0.1% reltol = 0.001 # 0.1%
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. 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": if OPTS.spice_name == "ngspice":
self.sf.write(".TEMP {}\n".format(self.temperature))
# UIC is needed for ngspice to converge # UIC is needed for ngspice to converge
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time)) self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
# ngspice sometimes has convergence problems if not using gear method # ngspice sometimes has convergence problems if not using gear method
@ -248,6 +248,7 @@ class stimuli():
# unless you figure out what these are. # unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol)) self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol))
elif OPTS.spice_name == "spectre": elif OPTS.spice_name == "spectre":
self.sf.write(".TEMP {}\n".format(self.temperature))
self.sf.write("simulator lang=spectre\n") self.sf.write("simulator lang=spectre\n")
if OPTS.use_pex: if OPTS.use_pex:
nestlvl = 1 nestlvl = 1
@ -255,8 +256,7 @@ class stimuli():
else: else:
nestlvl = 10 nestlvl = 10
spectre_save = "lvlpub" spectre_save = "lvlpub"
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format( self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(spectre_save, nestlvl))
spectre_save, nestlvl))
self.sf.write("simulatorOptions options reltol=1e-3 vabstol=1e-6 iabstol=1e-12 temp={0} try_fast_op=no " 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 " "rforce=10m maxnotes=10 maxwarns=10 "
" preservenode=all topcheck=fixall " " preservenode=all topcheck=fixall "
@ -265,12 +265,18 @@ class stimuli():
self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate ' self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate '
' annotate=status maxiters=5 \n'.format("5p", end_time)) ' annotate=status maxiters=5 \n'.format("5p", end_time))
self.sf.write("simulator lang=spice\n") 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(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl)) 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 PSF=1 \n") self.sf.write(".OPTIONS HIER_DELIM=1 \n")
self.sf.write(".OPTIONS HIER_DELIM=1 \n") elif OPTS.spice_name == "Xyce":
self.sf.write(".OPTIONS DEVICE TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS MEASURE MEASFAIL=1\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 # create plots for all signals
if not OPTS.use_pex: # Don't save all for extracted simulations if not OPTS.use_pex: # Don't save all for extracted simulations
@ -278,7 +284,7 @@ class stimuli():
if OPTS.verbose_level>0: if OPTS.verbose_level>0:
if OPTS.spice_name in ["hspice", "xa"]: if OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n") self.sf.write(".probe V(*)\n")
else: elif OPTS.spice_name != "Xyce":
self.sf.write(".plot V(*)\n") self.sf.write(".plot V(*)\n")
else: else:
self.sf.write("*.probe V(*)\n") self.sf.write("*.probe V(*)\n")
@ -312,7 +318,10 @@ class stimuli():
# Adding a commented out supply for simulators where gnd and 0 are not global grounds. # 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("\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 == "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): def run_sim(self, name):
""" Run hspice in batch mode and output rawfile to parse. """ """ Run hspice in batch mode and output rawfile to parse. """