326 lines
13 KiB
Python
326 lines
13 KiB
Python
from string import Template
|
|
import sys
|
|
import os
|
|
import re
|
|
import csv
|
|
import glob
|
|
import time
|
|
import threading
|
|
from datetime import timedelta
|
|
import argparse
|
|
import subprocess
|
|
import logging
|
|
from configparser import ConfigParser, ExtendedInterpolation
|
|
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
# Configure logging system
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
FILE_LOG_FORMAT = "%(levelname)s (%(threadName)10s) - %(message)s"
|
|
logging.basicConfig(
|
|
level=logging.INFO, stream=sys.stdout, format="%(levelname)s (%(threadName)10s) - %(message)s"
|
|
)
|
|
logger = logging.getLogger("Modelsim_run_log")
|
|
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
# Parse commandline arguments
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"files",
|
|
nargs="+",
|
|
help="Pass SimulationDeckInfo generated by OpenFPGA flow"
|
|
+ " or pass taskname <taskname> <run_number[optional]>",
|
|
)
|
|
parser.add_argument(
|
|
"--maxthreads",
|
|
type=int,
|
|
default=2,
|
|
help="Number of fpga_flow threads to run default = 2,"
|
|
+ "Typically <= Number of processors on the system",
|
|
)
|
|
parser.add_argument("--debug", action="store_true", help="Run script in debug mode")
|
|
parser.add_argument("--modelsim_proc_tmpl", type=str, help="Modelsim proc template file")
|
|
parser.add_argument("--modelsim_runsim_tmpl", type=str, help="Modelsim runsim template file")
|
|
parser.add_argument("--run_sim", action="store_true", help="Execute generated script in formality")
|
|
parser.add_argument("--modelsim_proj_name", help="Provide modelsim project name")
|
|
parser.add_argument(
|
|
"--modelsim_ini",
|
|
type=str,
|
|
default="/uusoc/facility/cad_tools/Mentor/modelsim10.7b/modeltech/modelsim.ini",
|
|
help="Skip any confirmation",
|
|
)
|
|
parser.add_argument("--skip_prompt", action="store_true", help="Skip any confirmation")
|
|
parser.add_argument(
|
|
"--ini_filename",
|
|
type=str,
|
|
default="simulation_deck_info.ini",
|
|
help="default INI filename in in fun dir",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
# Read script configuration file
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
task_script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
script_env_vars = {
|
|
"PATH": {
|
|
"OPENFPGA_FLOW_PATH": task_script_dir,
|
|
"ARCH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "arch"),
|
|
"BENCH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "benchmarks"),
|
|
"TECH_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "tech"),
|
|
"SPICENETLIST_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "SpiceNetlists"),
|
|
"VERILOG_PATH": os.path.join("${PATH:OPENFPGA_PATH}", "VerilogNetlists"),
|
|
"OPENFPGA_PATH": os.path.abspath(os.path.join(task_script_dir, os.pardir, os.pardir)),
|
|
}
|
|
}
|
|
config = ConfigParser(interpolation=ExtendedInterpolation())
|
|
config.read_dict(script_env_vars)
|
|
config.read_file(open(os.path.join(task_script_dir, "run_fpga_task.conf")))
|
|
gc = config["GENERAL CONFIGURATION"]
|
|
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
# Load default templates for modelsim
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
task_script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
if not args.modelsim_proc_tmpl:
|
|
args.modelsim_proc_tmpl = os.path.join(task_script_dir, os.pardir, "misc", "modelsim_proc.tcl")
|
|
if not args.modelsim_runsim_tmpl:
|
|
args.modelsim_runsim_tmpl = os.path.join(
|
|
task_script_dir, os.pardir, "misc", "modelsim_runsim.tcl"
|
|
)
|
|
|
|
args.modelsim_proc_tmpl = os.path.abspath(args.modelsim_proc_tmpl)
|
|
args.modelsim_runsim_tmpl = os.path.abspath(args.modelsim_runsim_tmpl)
|
|
|
|
|
|
def main():
|
|
if os.path.isfile(args.files[0]):
|
|
create_tcl_script(args.files)
|
|
else:
|
|
# Check if task directory exists and consistent
|
|
taskname = args.files[0]
|
|
task_run = "latest"
|
|
if len(args.files) > 1:
|
|
task_run = f"run{int(args.files[1]):03}"
|
|
|
|
temp_dir = os.path.join(gc["task_dir"], taskname)
|
|
if not os.path.isdir(temp_dir):
|
|
clean_up_and_exit("Task directory [%s] not found" % temp_dir)
|
|
temp_dir = os.path.join(gc["task_dir"], taskname, task_run)
|
|
if not os.path.isdir(temp_dir):
|
|
clean_up_and_exit("Task run directory [%s] not found" % temp_dir)
|
|
|
|
# = = = = = = = Create a current script log file handler = = = =
|
|
logfile_path = os.path.join(gc["task_dir"], taskname, task_run, "modelsim_run.log")
|
|
resultfile_path = os.path.join(gc["task_dir"], taskname, task_run, "modelsim_result.csv")
|
|
logfilefh = logging.FileHandler(logfile_path, "w")
|
|
logfilefh.setFormatter(logging.Formatter(FILE_LOG_FORMAT))
|
|
logger.addHandler(logfilefh)
|
|
logger.info("Created log file at %s" % logfile_path)
|
|
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
|
|
|
|
# = = = = Read Task log file and extract run directory = = =
|
|
logfile = os.path.join(gc["task_dir"], taskname, task_run, "*_out.log")
|
|
logfiles = glob.glob(logfile)
|
|
if not len(logfiles):
|
|
clean_up_and_exit("No successful run found in [%s]" % temp_dir)
|
|
|
|
task_ini_files = []
|
|
for eachfile in logfiles:
|
|
with open(eachfile) as fp:
|
|
run_dir = [
|
|
re.findall(r"^INFO.*Run directory : (.*)$", line) for line in open(eachfile)
|
|
]
|
|
run_dir = filter(bool, run_dir)
|
|
for each_run in run_dir:
|
|
INIfile = os.path.join(each_run[0], args.ini_filename)
|
|
if os.path.isfile(INIfile):
|
|
task_ini_files.append(INIfile)
|
|
logger.info(f"Found {len(task_ini_files)} INI files")
|
|
results = create_tcl_script(task_ini_files)
|
|
if args.run_sim:
|
|
collect_result(resultfile_path, results)
|
|
|
|
|
|
def clean_up_and_exit(msg):
|
|
logger.error(msg)
|
|
logger.error("Exiting . . . . . .")
|
|
exit(1)
|
|
|
|
|
|
def create_tcl_script(files):
|
|
runsim_files = []
|
|
for eachFile in files:
|
|
eachFile = os.path.abspath(eachFile)
|
|
pDir = os.path.dirname(eachFile)
|
|
os.chdir(pDir)
|
|
|
|
config = ConfigParser()
|
|
config.read(eachFile)
|
|
config = config["SIMULATION_DECK"]
|
|
|
|
# Resolve project Modelsim project path
|
|
args.modelsim_run_dir = os.path.dirname(os.path.abspath(eachFile))
|
|
modelsim_proj_dir = os.path.join(args.modelsim_run_dir, "MMSIM2")
|
|
logger.info(
|
|
f"Modelsim project dir not provide " + f"using default {modelsim_proj_dir} directory"
|
|
)
|
|
|
|
modelsim_proj_dir = os.path.abspath(modelsim_proj_dir)
|
|
config["MODELSIM_PROJ_DIR"] = modelsim_proj_dir
|
|
if not os.path.exists(modelsim_proj_dir):
|
|
os.makedirs(modelsim_proj_dir)
|
|
|
|
# Resolve Modelsim Project name
|
|
args.modelsim_proj_name = config["BENCHMARK"] + "_MMSIM"
|
|
logger.info(
|
|
f"Modelsim project name not provide "
|
|
+ f"using default {args.modelsim_proj_name} directory"
|
|
)
|
|
|
|
config["MODELSIM_PROJ_NAME"] = args.modelsim_proj_name
|
|
config["MODELSIM_INI"] = args.modelsim_ini
|
|
config["VERILOG_PATH"] = os.path.join(os.getcwd(), config["VERILOG_PATH"])
|
|
IncludeFile = os.path.join(os.getcwd(), config["VERILOG_PATH"], config["VERILOG_FILE2"])
|
|
IncludeFileResolved = os.path.join(
|
|
os.getcwd(),
|
|
config["VERILOG_PATH"],
|
|
config["VERILOG_FILE2"].replace(".v", "_resolved.v"),
|
|
)
|
|
with open(IncludeFileResolved, "w") as fpw:
|
|
with open(IncludeFile, "r") as fp:
|
|
for eachline in fp.readlines():
|
|
eachline = eachline.replace('"./', '"../../../')
|
|
fpw.write(eachline)
|
|
# Modify the variables in config file here
|
|
config["TOP_TB"] = os.path.splitext(config["TOP_TB"])[0]
|
|
|
|
# Write final template file
|
|
# Write runsim file
|
|
tmpl = Template(open(args.modelsim_runsim_tmpl, encoding="utf-8").read())
|
|
runsim_filename = os.path.join(modelsim_proj_dir, "%s_runsim.tcl" % config["BENCHMARK"])
|
|
logger.info(f"Creating tcl script at : {runsim_filename}")
|
|
with open(runsim_filename, "w", encoding="utf-8") as tclout:
|
|
tclout.write(tmpl.substitute(config))
|
|
|
|
# Write proc file
|
|
proc_filename = os.path.join(
|
|
modelsim_proj_dir, "%s_autocheck_proc.tcl" % config["BENCHMARK"]
|
|
)
|
|
logger.info(f"Creating tcl script at : {proc_filename}")
|
|
with open(proc_filename, "w", encoding="utf-8") as tclout:
|
|
tclout.write(open(args.modelsim_proc_tmpl, encoding="utf-8").read())
|
|
runsim_files.append(
|
|
{
|
|
"ini_file": eachFile,
|
|
"modelsim_run_dir": args.modelsim_run_dir,
|
|
"runsim_filename": runsim_filename,
|
|
"run_complete": False,
|
|
"status": False,
|
|
"finished": True,
|
|
"starttime": 0,
|
|
"endtime": 0,
|
|
"Errors": 0,
|
|
"Warnings": 0,
|
|
}
|
|
)
|
|
# Execute modelsim
|
|
if args.run_sim:
|
|
thread_sema = threading.Semaphore(args.maxthreads)
|
|
logger.info("Launching %d parallel threads" % args.maxthreads)
|
|
thread_list = []
|
|
for thread_no, eachjob in enumerate(runsim_files):
|
|
t = threading.Thread(
|
|
target=run_modelsim_thread,
|
|
name=f"Thread_{thread_no:d}",
|
|
args=(thread_sema, eachjob, runsim_files),
|
|
)
|
|
t.start()
|
|
thread_list.append(t)
|
|
for eachthread in thread_list:
|
|
eachthread.join()
|
|
return runsim_files
|
|
else:
|
|
logger.info("Created runsim and proc files")
|
|
logger.info(f"runsim_filename {runsim_filename}")
|
|
logger.info(f"proc_filename {proc_filename}")
|
|
from pprint import pprint
|
|
|
|
pprint(runsim_files)
|
|
|
|
|
|
def run_modelsim_thread(s, eachJob, job_list):
|
|
os.chdir(eachJob["modelsim_run_dir"])
|
|
with s:
|
|
thread_name = threading.currentThread().getName()
|
|
eachJob["starttime"] = time.time()
|
|
eachJob["Errors"] = 0
|
|
eachJob["Warnings"] = 0
|
|
try:
|
|
logfile = "%s_modelsim.log" % thread_name
|
|
eachJob["logfile"] = "<task_dir>" + os.path.relpath(logfile, gc["task_dir"])
|
|
with open(logfile, "w+") as output:
|
|
output.write("* " * 20 + "\n")
|
|
output.write("RunDirectory : %s\n" % os.getcwd())
|
|
command = ["vsim", "-c", "-do", eachJob["runsim_filename"]]
|
|
output.write(" ".join(command) + "\n")
|
|
output.write("* " * 20 + "\n")
|
|
logger.info("Running modelsim with [%s]" % " ".join(command))
|
|
process = subprocess.Popen(
|
|
command,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
universal_newlines=True,
|
|
)
|
|
for line in process.stdout:
|
|
if "Errors" in line:
|
|
logger.info(line.strip())
|
|
e, w = re.match("# .*: ([0-9].*), .*: ([0-9].*)", line).groups()
|
|
eachJob["Errors"] += int(e)
|
|
eachJob["Warnings"] += int(w)
|
|
sys.stdout.buffer.flush()
|
|
output.write(line)
|
|
process.wait()
|
|
if process.returncode:
|
|
raise subprocess.CalledProcessError(0, " ".join(command))
|
|
eachJob["run_complete"] = True
|
|
if not eachJob["Errors"]:
|
|
eachJob["status"] = True
|
|
except:
|
|
logger.exception("Failed to execute openfpga flow - " + eachJob["name"])
|
|
if not args.continue_on_fail:
|
|
os._exit(1)
|
|
eachJob["endtime"] = time.time()
|
|
timediff = timedelta(seconds=(eachJob["endtime"] - eachJob["starttime"]))
|
|
timestr = humanize.naturaldelta(timediff) if "humanize" in sys.modules else str(timediff)
|
|
eachJob["exectime"] = timestr
|
|
logger.info(
|
|
"%s Finished with returncode %d, Time Taken %s "
|
|
% (thread_name, process.returncode, timestr)
|
|
)
|
|
eachJob["finished"] = True
|
|
no_of_finished_job = sum([not eachJ["finished"] for eachJ in job_list])
|
|
logger.info("***** %d runs pending *****" % (no_of_finished_job))
|
|
|
|
|
|
def collect_result(result_file, result_obj):
|
|
colnames = ["status", "Errors", "Warnings", "run_complete", "exectime", "finished", "logfile"]
|
|
if len(result_obj):
|
|
with open(result_file, "w", newline="") as csvfile:
|
|
writer = csv.DictWriter(csvfile, extrasaction="ignore", fieldnames=colnames)
|
|
writer.writeheader()
|
|
for eachResult in result_obj:
|
|
writer.writerow(eachResult)
|
|
logger.info("= = = =" * 10)
|
|
passed_jobs = [each["status"] for each in result_obj]
|
|
logger.info(f"Passed Jobs %d/%d", len(passed_jobs), len(result_obj))
|
|
logger.info(f"Result file stored at {result_file}")
|
|
logger.info("= = = =" * 10)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if args.debug:
|
|
logger.info("Setting loggger in debug mode")
|
|
logger.setLevel(logging.DEBUG)
|
|
main()
|