| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| """ |
| dvsim is a command line tool to deploy regressions for design verification. It uses hjson as the |
| format for specifying what to build and run. It is an end-to-end regression manager that can deploy |
| multiple builds (where some tests might need different set of compile time options requiring a |
| uniquely build sim executable) in parallel followed by tests in parallel using the load balancer |
| of your choice. dvsim is built to be tool-agnostic so that you can easily switch between tools |
| available at your disposal. dvsim uses fusesoc as the starting step to resolve all inter-package |
| dependencies and provide us with a filelist that will be consumed by the sim tool. |
| """ |
| |
| import argparse |
| import datetime |
| import logging as log |
| import os |
| import subprocess |
| import sys |
| |
| import Deploy |
| import LintCfg |
| import SimCfg |
| import SynCfg |
| import utils |
| |
| # TODO: add dvsim_cfg.hjson to retrieve this info |
| version = 0.1 |
| |
| |
| # Function to resolve the scratch root directory among the available options: |
| # If set on the command line, then use that as a preference. |
| # Else, check if $SCRATCH_ROOT env variable exists and is a directory. |
| # Else use the default (<cwd>/scratch) |
| # Try to create the directory if it does not already exist. |
| def resolve_scratch_root(arg_scratch_root): |
| scratch_root = os.environ.get('SCRATCH_ROOT') |
| if not arg_scratch_root: |
| if scratch_root is None: |
| arg_scratch_root = os.getcwd() + "/scratch" |
| else: |
| # Scratch space could be mounted in a filesystem (such as NFS) on a network drive. |
| # If the network is down, it could cause the access access check to hang. So run a |
| # simple ls command with a timeout to prevent the hang. |
| (out, |
| status) = utils.run_cmd_with_timeout(cmd="ls -d " + scratch_root, |
| timeout=1, |
| exit_on_failure=0) |
| if status == 0 and out != "": |
| arg_scratch_root = scratch_root |
| else: |
| arg_scratch_root = os.getcwd() + "/scratch" |
| log.warning( |
| "Env variable $SCRATCH_ROOT=\"{}\" is not accessible.\n" |
| "Using \"{}\" instead.".format(scratch_root, |
| arg_scratch_root)) |
| else: |
| arg_scratch_root = os.path.realpath(arg_scratch_root) |
| |
| try: |
| os.system("mkdir -p " + arg_scratch_root) |
| except OSError: |
| log.fatal( |
| "Invalid --scratch-root=\"%s\" switch - failed to create directory!", |
| arg_scratch_root) |
| sys.exit(1) |
| return (arg_scratch_root) |
| |
| |
| # Set and return the current GitHub branch name, unless set on the command line. |
| # It runs "git branch --show-current". If the command fails, it throws a warning |
| # and sets the branch name to "default" |
| def resolve_branch(arg_branch): |
| if arg_branch is None or arg_branch == "": |
| result = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"], |
| stdout=subprocess.PIPE) |
| arg_branch = result.stdout.decode("utf-8").strip() |
| if arg_branch == "": |
| log.warning( |
| "Failed to find current git branch. Setting it to \"default\"") |
| arg_branch = "default" |
| return (arg_branch) |
| |
| |
| # Get the project root directory path - this is used to construct the full paths |
| def get_proj_root(): |
| cmd = ["git", "rev-parse", "--show-toplevel"] |
| result = subprocess.run(cmd, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| proj_root = result.stdout.decode("utf-8").strip() |
| if not proj_root: |
| log.error( |
| "Attempted to find the root of this GitHub repository by running:\n" |
| "{}\n" |
| "But this command has failed:\n" |
| "{}".format(' '.join(cmd), result.stderr.decode("utf-8"))) |
| sys.exit(1) |
| return (proj_root) |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.RawDescriptionHelpFormatter) |
| |
| parser.add_argument("cfg", |
| metavar="<cfg-hjson-file>", |
| help="""Configuration hjson file.""") |
| |
| parser.add_argument("-i", |
| "--items", |
| nargs="*", |
| default=["sanity"], |
| metavar="regr1, regr2, regr3|test1, test2, test3, ...", |
| help="""Indicate which regressions or tests to run.""") |
| |
| parser.add_argument( |
| "-l", |
| "--list", |
| nargs="+", |
| default=[], |
| metavar="build_modes|run_modes|tests|regressions", |
| help= |
| """List the available build_modes / run_modes / tests / regressions for use.""" |
| ) |
| |
| parser.add_argument("-t", |
| "--tool", |
| default="", |
| metavar="vcs|xcelium|ascentlint|dc|...", |
| help="Override the tool that is set in hjson file") |
| |
| parser.add_argument("-select_cfgs", |
| nargs="*", |
| default=[], |
| metavar="cfg1, cfg2, cfg3, ...", |
| help="""Specifies which cfg(s) of the master cfg shall be processed. |
| If this switch is not specified, dvsim will process all cfgs specified in |
| the master cfg list.""") |
| |
| parser.add_argument( |
| "-sr", |
| "--scratch-root", |
| metavar="path", |
| help="""root scratch directory path where all build / run drectories go; |
| by default, the tool will create the {scratch_path} = {scratch_root}/{dut} |
| directory if it doesn't already exist; under {scratch_path}, there will be |
| {compile_set} set of directories where all the build outputs go and |
| {test_name} set of directories where the test outputs go""" |
| ) |
| |
| parser.add_argument("-pr", |
| "--proj-root", |
| metavar="path", |
| help="""Specify the root directory of the project. |
| If this option is not passed, the tool will assume that this is |
| a local GitHub repository and will attempt to automatically find |
| the root directory.""") |
| |
| parser.add_argument( |
| "-br", |
| "--branch", |
| default="", |
| metavar="<branch-name>", |
| help= |
| """This variable is used to construct the scratch path directory name. If not |
| specified, it defaults to the GitHub branch name. The idea is to uniquefy the |
| scratch paths between different branches.""") |
| |
| parser.add_argument( |
| "-bo", |
| "--build-opts", |
| nargs="+", |
| default=[], |
| metavar="", |
| help="""Pass additional build options over the command line; |
| note that if there are multiple compile sets identified to be built, |
| these options will be passed on to all of them""") |
| |
| parser.add_argument( |
| "-bm", |
| "--build-modes", |
| nargs="+", |
| default=[], |
| metavar="", |
| help="""Set build modes on the command line for all tests run as a part |
| of the regression.""") |
| |
| parser.add_argument( |
| "-ro", |
| "--run-opts", |
| nargs="+", |
| default=[], |
| metavar="", |
| help="""Pass additional run time options over the command line; |
| these options will be passed on to all tests scheduled to be run""" |
| ) |
| |
| parser.add_argument( |
| "-rm", |
| "--run-modes", |
| nargs="+", |
| default=[], |
| metavar="", |
| help="""Set run modes on the command line for all tests run as a part |
| of the regression.""") |
| |
| parser.add_argument( |
| "-bu", |
| "--build-unique", |
| default=False, |
| action='store_true', |
| help= |
| """By default, under the {scratch} directory, there is a {compile_set} |
| directory created where the build output goes; this can be |
| uniquified by appending the current timestamp. This is suitable |
| for the case when a test / regression already running and you want |
| to run something else from a different terminal without affecting |
| the previous one""") |
| |
| parser.add_argument( |
| "--build-only", |
| default=False, |
| action='store_true', |
| help="Only build the simulation executables for the givem items.") |
| |
| parser.add_argument( |
| "--run-only", |
| default=False, |
| action='store_true', |
| help="Assume sim exec is available and proceed to run step") |
| |
| parser.add_argument( |
| "-s", |
| "--seeds", |
| nargs="+", |
| default=[], |
| metavar="seed0 seed1 ...", |
| help= |
| """Run tests with a specific seeds. Note that these specific seeds are applied to |
| items being run in the order they are passed.""") |
| |
| parser.add_argument( |
| "--fixed-seed", |
| type=int, |
| default=None, |
| help= |
| """Run all items with a fixed seed value. This option enforces --reseed 1.""" |
| ) |
| |
| parser.add_argument( |
| "-r", |
| "--reseed", |
| type=int, |
| default=-1, |
| metavar="N", |
| help="""Repeat tests with N iterations with different seeds""") |
| |
| parser.add_argument("-rx", |
| "--reseed-multiplier", |
| type=int, |
| default=1, |
| metavar="N", |
| help="""Multiplier for existing reseed values.""") |
| |
| parser.add_argument("-w", |
| "--waves", |
| default=False, |
| action='store_true', |
| help="Enable dumping of waves") |
| |
| parser.add_argument("-d", |
| "--dump", |
| default="fsdb", |
| metavar="fsdb|shm", |
| help="Dump waves in fsdb or shm.") |
| |
| parser.add_argument("-mw", |
| "--max-waves", |
| type=int, |
| default=5, |
| metavar="N", |
| help="""Enable dumpling of waves for at most N tests; |
| this includes tests scheduled for run AND automatic rerun""" |
| ) |
| |
| parser.add_argument("-c", |
| "--cov", |
| default=False, |
| action='store_true', |
| help="turn on coverage collection") |
| |
| parser.add_argument( |
| "--cov-merge-previous", |
| default=False, |
| action='store_true', |
| help="""Applicable when --cov switch is enabled. If a previous cov |
| database directory exists, this switch will cause it to be merged with |
| the current cov database.""") |
| |
| parser.add_argument( |
| "--cov-analyze", |
| default=False, |
| action='store_true', |
| help="Analyze the coverage from the last regression result.") |
| |
| parser.add_argument("-p", |
| "--profile", |
| default="none", |
| metavar="time|mem", |
| help="Turn on simulation profiling") |
| |
| parser.add_argument("--xprop-off", |
| default=False, |
| action='store_true', |
| help="Turn off Xpropagation") |
| |
| parser.add_argument("--job-prefix", |
| default="", |
| metavar="job-prefix", |
| help="Job prefix before deploying the tool commands.") |
| |
| parser.add_argument("--purge", |
| default=False, |
| action='store_true', |
| help="Clean the scratch directory before running.") |
| |
| parser.add_argument( |
| "-mo", |
| "--max-odirs", |
| type=int, |
| default=5, |
| metavar="N", |
| help="""When tests are run, the older runs are backed up. This switch |
| limits the number of backup directories being maintained.""") |
| |
| parser.add_argument( |
| "--no-rerun", |
| default=False, |
| action='store_true', |
| help= |
| """By default, failing tests will be automatically be rerun with waves; |
| this option will prevent the rerun from being triggered""") |
| |
| parser.add_argument("-v", |
| "--verbosity", |
| default="l", |
| metavar="n|l|m|h|d", |
| help="""Set verbosity to none/low/medium/high/debug; |
| This will override any setting added to any of the hjson files |
| used for config""") |
| |
| parser.add_argument("--email", |
| nargs="+", |
| default=[], |
| metavar="", |
| help="""email the report to specified addresses""") |
| |
| parser.add_argument( |
| "--verbose", |
| nargs="?", |
| default=None, |
| const="default", |
| metavar="debug", |
| help="""Print verbose dvsim tool messages. If 'debug' is passed, then the |
| volume of messages is ven higher.""") |
| |
| parser.add_argument("--version", |
| default=False, |
| action='store_true', |
| help="Print version and exit") |
| |
| parser.add_argument( |
| "-n", |
| "--dry-run", |
| default=False, |
| action='store_true', |
| help= |
| "Print dvsim tool messages only, without actually running any command") |
| |
| parser.add_argument( |
| "--map-full-testplan", |
| default=False, |
| action='store_true', |
| help="Force complete testplan annotated results to be shown at the end." |
| ) |
| |
| parser.add_argument( |
| "--publish", |
| default=False, |
| action='store_true', |
| help="Publish results to the reports.opentitan.org web server.") |
| |
| parser.add_argument( |
| "-pi", |
| "--print-interval", |
| type=int, |
| default=10, |
| metavar="N", |
| help="""Interval in seconds. Print status every N seconds.""") |
| |
| parser.add_argument( |
| "-mp", |
| "--max-parallel", |
| type=int, |
| default=16, |
| metavar="N", |
| help="""Run only upto a fixed number of builds/tests at a time.""") |
| |
| parser.add_argument( |
| "--local", |
| default=False, |
| action='store_true', |
| help= |
| """Deploy builds and runs on the local workstation instead of the compute farm. |
| Support for this has not been added yet.""") |
| |
| args = parser.parse_args() |
| |
| if args.version: |
| print(version) |
| sys.exit() |
| |
| # Add log level 'VERBOSE' between INFO and DEBUG |
| log.addLevelName(utils.VERBOSE, 'VERBOSE') |
| |
| log_format = '%(levelname)s: [%(module)s] %(message)s' |
| log_level = log.INFO |
| if args.verbose == "default": |
| log_level = utils.VERBOSE |
| elif args.verbose == "debug": |
| log_level = log.DEBUG |
| log.basicConfig(format=log_format, level=log_level) |
| |
| if not os.path.exists(args.cfg): |
| log.fatal("Path to config file %s appears to be invalid.", args.cfg) |
| sys.exit(1) |
| |
| # If publishing results, then force full testplan mapping of results. |
| if args.publish: |
| args.map_full_testplan = True |
| |
| args.scratch_root = resolve_scratch_root(args.scratch_root) |
| args.branch = resolve_branch(args.branch) |
| args.cfg = os.path.abspath(args.cfg) |
| |
| # Add timestamp to args that all downstream objects can use. |
| # Static variables - indicate timestamp. |
| ts_format_long = "%A %B %d %Y %I:%M:%S%p %Z" |
| ts_format = "%a.%m.%d.%y__%I.%M.%S%p" |
| curr_ts = datetime.datetime.now() |
| timestamp_long = curr_ts.strftime(ts_format_long) |
| timestamp = curr_ts.strftime(ts_format) |
| setattr(args, "ts_format_long", ts_format_long) |
| setattr(args, "ts_format", ts_format) |
| setattr(args, "timestamp_long", timestamp_long) |
| setattr(args, "timestamp", timestamp) |
| |
| # Register the seeds from command line with RunTest class. |
| Deploy.RunTest.seeds = args.seeds |
| # If we are fixing a seed value, no point in tests having multiple reseeds. |
| if args.fixed_seed: |
| args.reseed = 1 |
| Deploy.RunTest.fixed_seed = args.fixed_seed |
| |
| # Register the common deploy settings. |
| Deploy.Deploy.print_interval = args.print_interval |
| Deploy.Deploy.max_parallel = args.max_parallel |
| Deploy.Deploy.max_odirs = args.max_odirs |
| |
| # Build infrastructure from hjson file and create the list of items to |
| # be deployed. |
| |
| # Sets the project root directory: either specified from the command line |
| # or set by automatically assuming we are in a GitHub repository and |
| # automatically finding the root of this repository. |
| if args.proj_root: |
| proj_root = args.proj_root |
| else: |
| proj_root = get_proj_root() |
| |
| # TODO: SimCfg item below implies DV - need to solve this once we add FPV |
| # and other ASIC flow targets. |
| if args.tool == 'ascentlint': |
| cfg = LintCfg.LintCfg(args.cfg, proj_root, args) |
| elif args.tool == 'dc': |
| cfg = SynCfg.SynCfg(args.cfg, proj_root, args) |
| else: |
| cfg = SimCfg.SimCfg(args.cfg, proj_root, args) |
| |
| # List items available for run if --list switch is passed, and exit. |
| if args.list != []: |
| cfg.print_list() |
| sys.exit(0) |
| |
| # In simulation mode: if --cov-analyze switch is passed, then run the GUI |
| # tool. |
| if args.cov_analyze: |
| cfg.cov_analyze() |
| cfg.deploy_objects() |
| sys.exit(0) |
| |
| # Purge the scratch path if --purge option is set. |
| if args.purge: |
| cfg.purge() |
| |
| # Deploy the builds and runs |
| if args.items != []: |
| # Create deploy objects. |
| cfg.create_deploy_objects() |
| cfg.deploy_objects() |
| |
| # Generate results. |
| cfg.gen_results() |
| |
| # Publish results |
| if args.publish: |
| cfg.publish_results() |
| |
| else: |
| log.info("No items specified to be run.") |
| |
| # Exit with non-zero status if there were errors or failures. |
| if cfg.has_errors(): |
| log.error("Errors were encountered in this run.") |
| sys.exit(1) |
| |
| |
| if __name__ == '__main__': |
| main() |