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:
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"])
# set the input dir for spice files if using ngspice
if OPTS.spice_name == "ngspice":

View File

@ -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 == "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

View File

@ -191,7 +191,6 @@ class setup_hold():
setuphold_time,
feasible_bound,
2 * self.period))
# raw_input("Press Enter to continue...")
while True:
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
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,18 @@ 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 == "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
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.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 +318,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 == "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. """