358 lines
12 KiB
Python
Executable File
358 lines
12 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
# Copyright (c) 2018-2019 Bluespec, Inc.
|
|
# See LICENSE for license details
|
|
|
|
usage_line = (
|
|
" Usage:\n"
|
|
" $ <this_prog> <simulation_executable> <repo_dir> <logs_dir> <arch> <opt verbosity>\n"
|
|
"\n"
|
|
" Runs the RISC-V <simulation_executable>\n"
|
|
" on ISA tests: ELF files taken from <repo-dir>/isa and its sub-directories.\n"
|
|
"\n"
|
|
" Runs it only on those ELF files that are relevant to architecture <arch>.\n"
|
|
"\n"
|
|
" For each ELF file FOO, saves simulation output in <logs_dir>/FOO.log. \n"
|
|
"\n"
|
|
" If <opt verbosity> is given, it must be one of the following:\n"
|
|
" v1: Print instruction trace during simulation\n"
|
|
" v2: Print pipeline stage state during simulation\n"
|
|
"\n"
|
|
" Example:\n"
|
|
" $ <this_prog> .exe_HW_sim ~somebody/GitHub/Piccolo ./Logs RV32IMU v1\n"
|
|
" will run the verilator simulation executable on the following RISC-V ISA tests:\n"
|
|
" ~somebody/GitHub/Tests/isa/rv32ui-p*\n"
|
|
" ~somebody/GitHub/Tests/isa/rv32mi-p*\n"
|
|
" ~somebody/GitHub/Tests/isa/rv32um-p*\n"
|
|
" which are relevant for architecture RV32IMU\n"
|
|
" and will leave a transcript of each test's simulation output in files like\n"
|
|
" ./Logs/rv32ui-p-add.log\n"
|
|
" Each log will contain an instruction trace (because of the 'v1' arg).\n"
|
|
)
|
|
|
|
import sys
|
|
import os
|
|
import stat
|
|
import subprocess
|
|
|
|
# ================================================================
|
|
|
|
num_executed = 0
|
|
num_passed = 0
|
|
|
|
# DEBUGGING ONLY: This exclude list allows skipping some specific test
|
|
# Tuba seems to hang on this test
|
|
exclude_list = ["rv64ud-p-move"]
|
|
|
|
# ================================================================
|
|
|
|
def main (argv = None):
|
|
print ("Use flag --help or --h for a help message")
|
|
if ((len (argv) <= 1) or
|
|
(argv [1] == '-h') or (argv [1] == '--help') or
|
|
(len (argv) < 5)):
|
|
|
|
sys.stdout.write (usage_line)
|
|
sys.stdout.write ("\n")
|
|
return 0
|
|
|
|
# Simulation executable
|
|
if not (os.path.exists (argv [1])):
|
|
sys.stderr.write ("ERROR: The given simulation path does not seem to exist?\n")
|
|
sys.stderr.write (" Simulation path: " + sim_path + "\n")
|
|
sys.exit (1)
|
|
args_dict = {'sim_path': os.path.abspath (os.path.normpath (argv [1]))}
|
|
|
|
# Repo in which to find ELFs and elf_to_hex executable
|
|
if (not os.path.exists (argv [2])):
|
|
sys.stderr.write ("ERROR: repo directory ({0}) does not exist?\n".format (argv [2]))
|
|
sys.stdout.write ("\n")
|
|
sys.stdout.write (usage_line)
|
|
sys.stdout.write ("\n")
|
|
return 1
|
|
repo = os.path.abspath (os.path.normpath (argv [2]))
|
|
|
|
elfs_path = os.path.join (repo, "Tests", "isa")
|
|
if (not os.path.exists (elfs_path)):
|
|
sys.stderr.write ("ERROR: ELFs directory ({0}) does not exist?\n".format (elfs_path))
|
|
sys.stdout.write ("\n")
|
|
sys.stdout.write (usage_line)
|
|
sys.stdout.write ("\n")
|
|
return 1
|
|
args_dict ['elfs_path'] = elfs_path
|
|
|
|
# Logs directory
|
|
logs_path = os.path.abspath (os.path.normpath (argv [3]))
|
|
if not (os.path.exists (logs_path) and os.path.isdir (logs_path)):
|
|
print ("Creating dir: " + logs_path)
|
|
os.mkdir (logs_path)
|
|
args_dict ['logs_path'] = logs_path
|
|
|
|
# Architecture string and implied ISA test families
|
|
arch_string = extract_arch_string (argv [4])
|
|
if (arch_string == None):
|
|
sys.stderr.write ("ERROR: no architecture specified?\n")
|
|
sys.stdout.write ("\n")
|
|
sys.stdout.write (usage_line)
|
|
sys.stdout.write ("\n")
|
|
return 1
|
|
args_dict ['arch_string'] = arch_string
|
|
|
|
test_families = select_test_families (arch_string)
|
|
print ("Testing the following families of ISA tests")
|
|
for tf in test_families:
|
|
print (" " + tf)
|
|
args_dict ['test_families'] = test_families
|
|
|
|
# Optional verbosity
|
|
verbosity = 0
|
|
for arg in argv [5:]:
|
|
if arg == "v1":
|
|
verbosity = 1
|
|
elif arg == "v2":
|
|
verbosity = 2
|
|
args_dict ['verbosity'] = verbosity
|
|
|
|
# elf_to_hex executable
|
|
elf_to_hex_exe = os.path.join (repo, "Tests", "elf_to_hex", "elf_to_hex")
|
|
if (not os.path.exists (elf_to_hex_exe)):
|
|
sys.stderr.write ("ERROR: elf_to_hex executable does not exist?\n")
|
|
sys.stderr.write (" at {0}\n".format (elf_to_hex_exe))
|
|
sys.stdout.write ("\n")
|
|
sys.stdout.write (usage_line)
|
|
sys.stdout.write ("\n")
|
|
return 1
|
|
args_dict ['elf_to_hex_exe'] = elf_to_hex_exe
|
|
|
|
sys.stdout.write ("Parameters:\n")
|
|
for key in iter (args_dict):
|
|
sys.stdout.write (" {0:<16}: {1}\n".format (key, args_dict [key]))
|
|
|
|
# Perform the recursive traversal
|
|
max_level = 20
|
|
traverse (max_level, 0, args_dict)
|
|
|
|
# Write final statistics
|
|
sys.stdout.write ("Executed: {0} tests\n".format (num_executed))
|
|
sys.stdout.write ("PASS: {0} tests\n".format (num_passed))
|
|
|
|
|
|
# ================================================================
|
|
# Extract the architecture string (e.g., RV64AIMSU) from the string s
|
|
|
|
def extract_arch_string (s):
|
|
s1 = s.upper()
|
|
j_rv32 = s1.find ("RV32")
|
|
j_rv64 = s1.find ("RV64")
|
|
|
|
if (j_rv32 >= 0):
|
|
j = j_rv32
|
|
elif (j_rv64 >= 0):
|
|
j = j_rv64
|
|
else:
|
|
sys.stderr.write ("ERROR: cannot find architecture string beginning with RV32 or RV64 in: \n")
|
|
sys.stderr.write (" '" + s + "'\n")
|
|
sys.exit (1)
|
|
|
|
k = j + 4
|
|
rv = s1 [j:k]
|
|
|
|
extns = ""
|
|
while (k < len (s)):
|
|
ch = s [k]
|
|
if (ch < "A") or (ch > "Z"): break
|
|
extns = extns + s [k]
|
|
k = k + 1
|
|
|
|
arch = rv + extns
|
|
return arch
|
|
|
|
# ================================================================
|
|
# Select ISA test families based on provided arch string
|
|
|
|
def select_test_families (arch):
|
|
arch = arch.lower ()
|
|
families = []
|
|
|
|
if arch.find ("32") != -1:
|
|
rv = 32
|
|
families = ["rv32ui-p", "rv32mi-p"]
|
|
else:
|
|
rv = 64
|
|
families = ["rv64ui-p", "rv64mi-p"]
|
|
|
|
if (arch.find ("s") != -1):
|
|
s = True
|
|
if rv == 32:
|
|
families.extend (["rv32ui-v", "rv32si-p"])
|
|
else:
|
|
families.extend (["rv64ui-v", "rv64si-p"])
|
|
else:
|
|
s = False
|
|
|
|
def add_family (extension):
|
|
if (arch.find (extension) != -1):
|
|
if rv == 32:
|
|
families.append ("rv32u" + extension + "-p")
|
|
if s:
|
|
families.append ("rv32u" + extension + "-v")
|
|
else:
|
|
families.append ("rv64u" + extension + "-p")
|
|
if s:
|
|
families.append ("rv64u" + extension + "-v")
|
|
|
|
add_family ("m")
|
|
add_family ("a")
|
|
add_family ("f")
|
|
add_family ("d")
|
|
add_family ("c")
|
|
|
|
return families
|
|
|
|
# ================================================================
|
|
# Recursively traverse the dir tree below elf_path and process each file
|
|
|
|
def traverse (max_level, level, args_dict):
|
|
elfs_path = args_dict ['elfs_path']
|
|
st = os.stat (elfs_path)
|
|
is_dir = stat.S_ISDIR (st.st_mode)
|
|
is_regular = stat.S_ISREG (st.st_mode)
|
|
do_foreachfile_function (level, is_dir, is_regular, args_dict)
|
|
if is_dir and level < max_level:
|
|
for entry in os.listdir (elfs_path):
|
|
elfs_path1 = os.path.join (elfs_path, entry)
|
|
args_dict1 = args_dict.copy ()
|
|
args_dict1 ['elfs_path'] = elfs_path1
|
|
traverse (max_level, level + 1, args_dict1)
|
|
return 0
|
|
|
|
# ================================================================
|
|
# This function is applied to every path in the
|
|
# recursive traversal
|
|
|
|
def do_foreachfile_function (level, is_dir, is_regular, args_dict):
|
|
prefix = ""
|
|
for j in range (level): prefix = " " + prefix
|
|
elfs_path = args_dict ['elfs_path']
|
|
|
|
# directories
|
|
if is_dir:
|
|
print ("%s%d dir %s" % (prefix, level, elfs_path))
|
|
|
|
# regular files
|
|
elif is_regular:
|
|
dirname = os.path.dirname (elfs_path)
|
|
basename = os.path.basename (elfs_path)
|
|
# print ("%s%d %s" % (prefix, level, elfs_path))
|
|
do_regular_file_function (level, dirname, basename, args_dict)
|
|
|
|
# other files
|
|
else:
|
|
print ("%s%d Unknown file type: %s" % (prefix, level, os.path.basename (elfs_path)))
|
|
|
|
# ================================================================
|
|
# For each ELF file, execute it in the RISC-V simulator
|
|
|
|
def do_regular_file_function (level, dirname, basename, args_dict):
|
|
global num_executed
|
|
global num_passed
|
|
|
|
full_filename = os.path.join (dirname, basename)
|
|
|
|
# Ignore filename if has any extension (heuristic that it's not an ELF file)
|
|
if "." in basename: return
|
|
|
|
# Ignore filename if does not match test_families
|
|
ignore = True
|
|
for x in args_dict ['test_families']:
|
|
if basename.find (x) != -1: ignore = False
|
|
if ignore:
|
|
# print ("Ignoring file: " + full_filename)
|
|
return
|
|
|
|
TEMPORARY FILTER WHILE DEBUGGING:
|
|
if basename in exclude_list:
|
|
sys.stdout.write ("WARNING: TEMPORARY FILTER IN EFFECT; REMOVE AFTER DEBUGGING\n")
|
|
sys.stdout.write (" This test is in exclude_list: {0}\n".format (basename))
|
|
return
|
|
|
|
# For debugging only
|
|
# prefix = ""
|
|
# for j in range (level): prefix = " " + prefix
|
|
# sys.stdout.write ("{0}{1} ACTION: {2}\n".format (prefix, level, full_filename))
|
|
|
|
# Construct the commands for sub-process execution
|
|
command1 = [args_dict ['elf_to_hex_exe'], full_filename, "Mem.hex"]
|
|
|
|
command2 = [args_dict ['sim_path'], "+tohost"]
|
|
if (args_dict ['verbosity'] == 1): command2.append ("+v1")
|
|
elif (args_dict ['verbosity'] == 2): command2.append ("+v2")
|
|
|
|
num_executed = num_executed + 1
|
|
sys.stdout.write ("Test {0}: {1}\n".format (num_executed, basename))
|
|
|
|
sys.stdout.write (" Exec:")
|
|
for x in command1:
|
|
sys.stdout.write (" {0}".format (x))
|
|
sys.stdout.write ("\n")
|
|
|
|
sys.stdout.write (" Exec:")
|
|
for x in command2:
|
|
sys.stdout.write (" {0}".format (x))
|
|
sys.stdout.write ("\n")
|
|
|
|
# Run command as a sub-process
|
|
completed_process1 = run_command (command1)
|
|
completed_process2 = run_command (command2)
|
|
passed = completed_process2.stdout.find ("PASS") != -1
|
|
if passed:
|
|
sys.stdout.write (" PASS")
|
|
num_passed = num_passed + 1
|
|
else:
|
|
sys.stdout.write (" FAIL")
|
|
|
|
# Save stdouts in log file
|
|
log_filename = os.path.join (args_dict ['logs_path'], basename + ".log")
|
|
sys.stdout.write (" Writing log: {0}\n".format (log_filename))
|
|
|
|
fd = open (log_filename, 'w')
|
|
fd.write (completed_process1.stdout)
|
|
fd.write (completed_process2.stdout)
|
|
fd.close ()
|
|
|
|
# If Tandem Verification trace file was created, save it as well
|
|
if os.path.exists ("./trace_out.dat"):
|
|
trace_filename = os.path.join (args_dict ['logs_path'], basename + ".trace_data")
|
|
os.rename ("./trace_out.dat", trace_filename)
|
|
sys.stdout.write (" Trace output saved in: {0}\n".format (trace_filename))
|
|
|
|
return
|
|
|
|
# ================================================================
|
|
# This is a wrapper around 'subprocess.run' because of an annoying
|
|
# incompatible change in moving from Python 3.5 to 3.6
|
|
|
|
def run_command (command):
|
|
python_minor_version = sys.version_info [1]
|
|
if python_minor_version < 6:
|
|
# Python 3.5 and earlier
|
|
result = subprocess.run (args = command,
|
|
bufsize = 0,
|
|
stdout = subprocess.PIPE,
|
|
stderr = subprocess.STDOUT,
|
|
universal_newlines = True)
|
|
else:
|
|
# Python 3.6 and later
|
|
result = subprocess.run (args = command,
|
|
bufsize = 0,
|
|
stdout = subprocess.PIPE,
|
|
stderr = subprocess.STDOUT,
|
|
encoding='utf-8')
|
|
return result
|
|
|
|
# ================================================================
|
|
# For non-interactive invocations, call main() and use its return value
|
|
# as the exit code.
|
|
if __name__ == '__main__':
|
|
sys.exit (main (sys.argv))
|