blob: e20d55bd6da2f774f6ed53278bf87412560a715e [file] [log] [blame]
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +01005"""dvsim is a command line tool to deploy ASIC tool flows such as regressions
6for design verification (DV), formal property verification (FPV), linting and
7synthesis.
8
9It uses hjson as the format for specifying what to build and run. It is an
10end-to-end regression manager that can deploy multiple builds (where some tests
11might need different set of compile time options requiring a uniquely build sim
12executable) in parallel followed by tests in parallel using the load balancer
13of your choice.
14
15dvsim is built to be tool-agnostic so that you can easily switch between the
16tools at your disposal. dvsim uses fusesoc as the starting step to resolve all
17inter-package dependencies and provide us with a filelist that will be consumed
18by the sim tool.
19
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080020"""
21
22import argparse
Srikrishna Iyer544da8d2020-01-14 23:51:41 -080023import datetime
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080024import logging as log
25import os
Eunchan Kim87e8f852021-01-05 09:03:01 -080026import shlex
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080027import subprocess
28import sys
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +010029import textwrap
Srikrishna Iyer2d151192021-02-10 16:56:16 -080030from pathlib import Path
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080031
Srikrishna Iyer9d9d86f2021-03-02 00:15:51 -080032import Launcher
33import LauncherFactory
Srikrishna Iyer206721c2021-04-15 15:23:08 -070034import LocalLauncher
Srikrishna Iyer2d151192021-02-10 16:56:16 -080035from CfgFactory import make_cfg
Srikrishna Iyer65783742021-02-10 21:42:12 -080036from Deploy import RunTest
Rupert Swarbrickc0e753d2020-06-04 13:27:58 +010037from Timer import Timer
Srikrishna Iyer2b944ad2021-03-01 13:05:40 -080038from utils import (TS_FORMAT, TS_FORMAT_LONG, VERBOSE, rm_path,
39 run_cmd_with_timeout)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080040
41# TODO: add dvsim_cfg.hjson to retrieve this info
42version = 0.1
43
Rupert Swarbricke83b55e2020-05-12 11:44:04 +010044# The different categories that can be passed to the --list argument.
45_LIST_CATEGORIES = ["build_modes", "run_modes", "tests", "regressions"]
46
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080047
48# Function to resolve the scratch root directory among the available options:
49# If set on the command line, then use that as a preference.
50# Else, check if $SCRATCH_ROOT env variable exists and is a directory.
Srikrishna Iyerf479b9e2021-04-08 16:03:11 -070051# Else use the default (<proj_root>/scratch)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080052# Try to create the directory if it does not already exist.
Srikrishna Iyerf479b9e2021-04-08 16:03:11 -070053def resolve_scratch_root(arg_scratch_root, proj_root):
54 default_scratch_root = proj_root + "/scratch"
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080055 scratch_root = os.environ.get('SCRATCH_ROOT')
Rupert Swarbrick06383682020-03-24 15:56:41 +000056 if not arg_scratch_root:
57 if scratch_root is None:
Srikrishna Iyerf479b9e2021-04-08 16:03:11 -070058 arg_scratch_root = default_scratch_root
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080059 else:
60 # Scratch space could be mounted in a filesystem (such as NFS) on a network drive.
61 # If the network is down, it could cause the access access check to hang. So run a
62 # simple ls command with a timeout to prevent the hang.
Srikrishna Iyer2d151192021-02-10 16:56:16 -080063 (out, status) = run_cmd_with_timeout(cmd="ls -d " + scratch_root,
64 timeout=1,
65 exit_on_failure=0)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080066 if status == 0 and out != "":
67 arg_scratch_root = scratch_root
68 else:
Srikrishna Iyerf479b9e2021-04-08 16:03:11 -070069 arg_scratch_root = default_scratch_root
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080070 log.warning(
Rupert Swarbrick06383682020-03-24 15:56:41 +000071 "Env variable $SCRATCH_ROOT=\"{}\" is not accessible.\n"
72 "Using \"{}\" instead.".format(scratch_root,
73 arg_scratch_root))
Srikrishna Iyer544da8d2020-01-14 23:51:41 -080074 else:
75 arg_scratch_root = os.path.realpath(arg_scratch_root)
76
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080077 try:
Srikrishna Iyer2d151192021-02-10 16:56:16 -080078 os.makedirs(arg_scratch_root, exist_ok=True)
79 except PermissionError as e:
80 log.fatal("Failed to create scratch root {}:\n{}.".format(
81 arg_scratch_root, e))
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080082 sys.exit(1)
Srikrishna Iyer2d151192021-02-10 16:56:16 -080083
84 if not os.access(arg_scratch_root, os.W_OK):
85 log.fatal("Scratch root {} is not writable!".format(arg_scratch_root))
86 sys.exit(1)
87
88 return arg_scratch_root
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080089
90
Rupert Swarbrick536ab202020-06-09 16:37:12 +010091def read_max_parallel(arg):
92 '''Take value for --max-parallel as an integer'''
93 try:
94 int_val = int(arg)
95 if int_val <= 0:
96 raise ValueError('bad value')
97 return int_val
98
99 except ValueError:
Weicai Yang813d6892020-10-19 16:20:31 -0700100 raise argparse.ArgumentTypeError(
101 'Bad argument for --max-parallel '
102 '({!r}): must be a positive integer.'.format(arg))
Rupert Swarbrick536ab202020-06-09 16:37:12 +0100103
104
105def resolve_max_parallel(arg):
106 '''Pick a value of max_parallel, defaulting to 16 or $DVSIM_MAX_PARALLEL'''
107 if arg is not None:
108 assert arg > 0
109 return arg
110
111 from_env = os.environ.get('DVSIM_MAX_PARALLEL')
112 if from_env is not None:
113 try:
114 return read_max_parallel(from_env)
115 except argparse.ArgumentTypeError:
116 log.warning('DVSIM_MAX_PARALLEL environment variable has value '
117 '{!r}, which is not a positive integer. Using default '
Weicai Yang813d6892020-10-19 16:20:31 -0700118 'value (16).'.format(from_env))
Rupert Swarbrick536ab202020-06-09 16:37:12 +0100119
120 return 16
121
122
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100123def resolve_branch(branch):
124 '''Choose a branch name for output files
125
126 If the --branch argument was passed on the command line, the branch
127 argument is the branch name to use. Otherwise it is None and we use git to
128 find the name of the current branch in the working directory.
129
Todd Broch3cb62ec2021-11-01 11:09:15 -0700130 Note, as this name will be used to generate output files any forward slashes
131 are replaced with single dashes to avoid being interpreted as directory hierarchy.
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100132 '''
133
134 if branch is not None:
Todd Broch3cb62ec2021-11-01 11:09:15 -0700135 return branch.replace("/", "-")
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100136
137 result = subprocess.run(["git", "rev-parse", "--abbrev-ref", "HEAD"],
138 stdout=subprocess.PIPE)
Todd Broch3cb62ec2021-11-01 11:09:15 -0700139 branch = result.stdout.decode("utf-8").strip().replace("/", "-")
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100140 if not branch:
141 log.warning("Failed to find current git branch. "
142 "Setting it to \"default\"")
143 branch = "default"
144
145 return branch
146
147
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800148# Get the project root directory path - this is used to construct the full paths
149def get_proj_root():
Rupert Swarbrick06383682020-03-24 15:56:41 +0000150 cmd = ["git", "rev-parse", "--show-toplevel"]
151 result = subprocess.run(cmd,
Rupert Swarbrick1dd327bd2020-03-24 15:14:00 +0000152 stdout=subprocess.PIPE,
153 stderr=subprocess.PIPE)
Udi Jonnalagaddaa122dda2020-03-13 16:56:45 -0700154 proj_root = result.stdout.decode("utf-8").strip()
Rupert Swarbrick06383682020-03-24 15:56:41 +0000155 if not proj_root:
Udi Jonnalagaddaa122dda2020-03-13 16:56:45 -0700156 log.error(
Rupert Swarbrick06383682020-03-24 15:56:41 +0000157 "Attempted to find the root of this GitHub repository by running:\n"
158 "{}\n"
159 "But this command has failed:\n"
160 "{}".format(' '.join(cmd), result.stderr.decode("utf-8")))
Udi Jonnalagaddaa122dda2020-03-13 16:56:45 -0700161 sys.exit(1)
Philipp Wagnercb186c72021-10-04 21:01:12 +0100162 return proj_root
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800163
164
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800165def resolve_proj_root(args):
166 '''Update proj_root based on how DVSim is invoked.
167
Srikrishna Iyere3af4cf2021-03-09 17:22:01 -0800168 If --remote switch is set, a location in the scratch area is chosen as the
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800169 new proj_root. The entire repo is copied over to this location. Else, the
170 proj_root is discovered using get_proj_root() method, unless the user
171 overrides it on the command line.
172
Cindy Chen13a5dde2020-12-21 09:31:56 -0800173 This function returns the updated proj_root src and destination path. If
Srikrishna Iyere3af4cf2021-03-09 17:22:01 -0800174 --remote switch is not set, the destination path is identical to the src
175 path. Likewise, if --dry-run is set.
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800176 '''
Cindy Chen13a5dde2020-12-21 09:31:56 -0800177 proj_root_src = args.proj_root or get_proj_root()
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800178
179 # Check if jobs are dispatched to external compute machines. If yes,
180 # then the repo needs to be copied over to the scratch area
181 # accessible to those machines.
Cindy Chenec6449e2020-12-17 11:55:38 -0800182 # If --purge arg is set, then purge the repo_top that was copied before.
Srikrishna Iyere3af4cf2021-03-09 17:22:01 -0800183 if args.remote and not args.dry_run:
Cindy Chen13a5dde2020-12-21 09:31:56 -0800184 proj_root_dest = os.path.join(args.scratch_root, args.branch,
185 "repo_top")
Srikrishna Iyerab4327e2020-12-29 12:49:07 -0800186 if args.purge:
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800187 rm_path(proj_root_dest)
Srikrishna Iyere3af4cf2021-03-09 17:22:01 -0800188 copy_repo(proj_root_src, proj_root_dest)
Cindy Chen13a5dde2020-12-21 09:31:56 -0800189 else:
190 proj_root_dest = proj_root_src
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800191
Cindy Chen13a5dde2020-12-21 09:31:56 -0800192 return proj_root_src, proj_root_dest
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800193
194
Srikrishna Iyere3af4cf2021-03-09 17:22:01 -0800195def copy_repo(src, dest):
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800196 '''Copy over the repo to a new location.
197
198 The repo is copied over from src to dest area. It tentatively uses the
199 rsync utility which provides the ability to specify a file containing some
200 exclude patterns to skip certain things from being copied over. With GitHub
201 repos, an existing `.gitignore` serves this purpose pretty well.
202 '''
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800203 rsync_cmd = [
204 "rsync", "--recursive", "--links", "--checksum", "--update",
205 "--inplace", "--no-group"
206 ]
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800207
208 # Supply `.gitignore` from the src area to skip temp files.
209 ignore_patterns_file = os.path.join(src, ".gitignore")
210 if os.path.exists(ignore_patterns_file):
211 # TODO: hack - include hw/foundry since it is excluded in .gitignore.
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800212 rsync_cmd += [
213 "--include=hw/foundry",
214 "--exclude-from={}".format(ignore_patterns_file), "--exclude=.*"
215 ]
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800216
Eunchan Kim425a0b82021-01-06 09:25:10 -0800217 rsync_cmd += [src + "/.", dest]
218 rsync_str = ' '.join([shlex.quote(w) for w in rsync_cmd])
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800219
Eunchan Kim425a0b82021-01-06 09:25:10 -0800220 cmd = ["flock", "--timeout", "600", dest, "--command", rsync_str]
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800221
222 log.info("[copy_repo] [dest]: %s", dest)
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800223 log.log(VERBOSE, "[copy_repo] [cmd]: \n%s", ' '.join(cmd))
Srikrishna Iyere3af4cf2021-03-09 17:22:01 -0800224
225 # Make sure the dest exists first.
226 os.makedirs(dest, exist_ok=True)
227 try:
228 subprocess.run(cmd,
229 check=True,
230 stdout=subprocess.PIPE,
231 stderr=subprocess.PIPE)
232 except subprocess.CalledProcessError as e:
233 log.error("Failed to copy over %s to %s: %s", src, dest,
234 e.stderr.decode("utf-8").strip())
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800235 log.info("Done.")
236
237
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100238def wrapped_docstring():
239 '''Return a text-wrapped version of the module docstring'''
240 paras = []
241 para = []
242 for line in __doc__.strip().split('\n'):
243 line = line.strip()
244 if not line:
245 if para:
246 paras.append('\n'.join(para))
247 para = []
248 else:
249 para.append(line)
250 if para:
251 paras.append('\n'.join(para))
252
253 return '\n\n'.join(textwrap.fill(p) for p in paras)
254
255
Rupert Swarbrickdcdfac32021-09-01 12:21:54 +0100256def parse_reseed_multiplier(as_str: str) -> float:
257 '''Parse the argument for --reseed-multiplier'''
258 try:
259 ret = float(as_str)
260 except ValueError:
261 raise argparse.ArgumentTypeError('Invalid reseed multiplier: {!r}. '
262 'Must be a float.'
263 .format(as_str))
264 if ret <= 0:
265 raise argparse.ArgumentTypeError('Reseed multiplier must be positive.')
266 return ret
267
268
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100269def parse_args():
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800270 parser = argparse.ArgumentParser(
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100271 description=wrapped_docstring(),
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800272 formatter_class=argparse.RawDescriptionHelpFormatter)
273
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800274 parser.add_argument("cfg",
275 metavar="<cfg-hjson-file>",
276 help="""Configuration hjson file.""")
277
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800278 parser.add_argument("--version",
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800279 action='store_true',
280 help="Print version and exit")
281
Weicai Yang813d6892020-10-19 16:20:31 -0700282 parser.add_argument(
283 "--tool",
284 "-t",
285 help=("Explicitly set the tool to use. This is "
286 "optional for running simulations (where it can "
287 "be set in an .hjson file), but is required for "
288 "other flows. Possible tools include: vcs, "
289 "xcelium, ascentlint, veriblelint, verilator, dc."))
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800290
Weicai Yang813d6892020-10-19 16:20:31 -0700291 parser.add_argument("--list",
292 "-l",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100293 nargs="*",
294 metavar='CAT',
295 choices=_LIST_CATEGORIES,
296 help=('Parse the the given .hjson config file, list '
297 'the things that can be run, then exit. The '
298 'list can be filtered with a space-separated '
Weicai Yang813d6892020-10-19 16:20:31 -0700299 'of categories from: {}.'.format(
300 ', '.join(_LIST_CATEGORIES))))
Srikrishna Iyer64009052020-01-13 11:27:39 -0800301
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100302 whatg = parser.add_argument_group('Choosing what to run')
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800303
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100304 whatg.add_argument("-i",
305 "--items",
306 nargs="*",
Cindy Chene513e362020-11-11 10:28:54 -0800307 default=["smoke"],
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100308 help=('Specify the regressions or tests to run. '
Cindy Chene513e362020-11-11 10:28:54 -0800309 'Defaults to "smoke", but can be a '
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100310 'space separated list of test or regression '
311 'names.'))
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800312
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100313 whatg.add_argument("--select-cfgs",
314 nargs="*",
315 metavar="CFG",
Scott Johnsonfe79c4b2020-07-08 10:31:08 -0700316 help=('The .hjson file is a primary config. Only run '
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100317 'the given configs from it. If this argument is '
318 'not used, dvsim will process all configs listed '
Scott Johnsonfe79c4b2020-07-08 10:31:08 -0700319 'in a primary config.'))
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800320
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100321 disg = parser.add_argument_group('Dispatch options')
322
323 disg.add_argument("--job-prefix",
324 default="",
325 metavar="PFX",
326 help=('Prepend this string when running each tool '
327 'command.'))
328
Srikrishna Iyer9d9d86f2021-03-02 00:15:51 -0800329 disg.add_argument("--local",
330 action='store_true',
331 help=('Force jobs to be dispatched locally onto user\'s '
332 'machine.'))
333
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800334 disg.add_argument("--remote",
335 action='store_true',
336 help=('Trigger copying of the repo to scratch area.'))
337
Weicai Yang813d6892020-10-19 16:20:31 -0700338 disg.add_argument("--max-parallel",
339 "-mp",
Rupert Swarbrick536ab202020-06-09 16:37:12 +0100340 type=read_max_parallel,
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100341 metavar="N",
342 help=('Run only up to N builds/tests at a time. '
Rupert Swarbrick536ab202020-06-09 16:37:12 +0100343 'Default value 16, unless the DVSIM_MAX_PARALLEL '
344 'environment variable is set, in which case that '
Srikrishna Iyer206721c2021-04-15 15:23:08 -0700345 'is used. Only applicable when launching jobs '
346 'locally.'))
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100347
348 pathg = parser.add_argument_group('File management')
349
Weicai Yang813d6892020-10-19 16:20:31 -0700350 pathg.add_argument("--scratch-root",
351 "-sr",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100352 metavar="PATH",
353 help=('Destination for build / run directories. If not '
354 'specified, uses the path in the SCRATCH_ROOT '
355 'environment variable, if set, or ./scratch '
356 'otherwise.'))
357
Weicai Yang813d6892020-10-19 16:20:31 -0700358 pathg.add_argument("--proj-root",
359 "-pr",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100360 metavar="PATH",
361 help=('The root directory of the project. If not '
362 'specified, dvsim will search for a git '
363 'repository containing the current directory.'))
364
Weicai Yang813d6892020-10-19 16:20:31 -0700365 pathg.add_argument("--branch",
366 "-br",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100367 metavar='B',
368 help=('By default, dvsim creates files below '
369 '{scratch-root}/{dut}.{flow}.{tool}/{branch}. '
370 'If --branch is not specified, dvsim assumes the '
371 'current directory is a git repository and uses '
372 'the name of the current branch.'))
373
Weicai Yang813d6892020-10-19 16:20:31 -0700374 pathg.add_argument("--max-odirs",
375 "-mo",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100376 type=int,
377 default=5,
378 metavar="N",
379 help=('When tests are run, older runs are backed '
380 'up. Discard all but the N most recent (defaults '
381 'to 5).'))
382
383 pathg.add_argument("--purge",
384 action='store_true',
385 help="Clean the scratch directory before running.")
386
387 buildg = parser.add_argument_group('Options for building')
388
389 buildg.add_argument("--build-only",
Weicai Yang528b3c02020-12-04 16:13:16 -0800390 "-bu",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100391 action='store_true',
392 help=('Stop after building executables for the given '
393 'items.'))
394
Weicai Yang813d6892020-10-19 16:20:31 -0700395 buildg.add_argument("--build-unique",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100396 action='store_true',
397 help=('Append a timestamp to the directory in which '
398 'files are built. This is suitable for the case '
399 'when another test is already running and you '
400 'want to run something else from a different '
401 'terminal without affecting it.'))
402
Weicai Yang813d6892020-10-19 16:20:31 -0700403 buildg.add_argument("--build-opts",
404 "-bo",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100405 nargs="+",
406 default=[],
407 metavar="OPT",
408 help=('Additional options passed on the command line '
409 'each time a build tool is run.'))
410
Weicai Yang813d6892020-10-19 16:20:31 -0700411 buildg.add_argument("--build-modes",
412 "-bm",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100413 nargs="+",
414 default=[],
415 metavar="MODE",
416 help=('The options for each build_mode in this list '
417 'are applied to all build and run targets.'))
418
Srikrishna Iyerd37d10d2021-03-25 18:35:36 -0700419 disg.add_argument("--gui",
420 action='store_true',
421 help=('Run the flow in interactive mode instead of the '
422 'batch mode.'))
423
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100424 rung = parser.add_argument_group('Options for running')
425
426 rung.add_argument("--run-only",
Weicai Yang528b3c02020-12-04 16:13:16 -0800427 "-ru",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100428 action='store_true',
429 help=('Skip the build step (assume that simulation '
430 'executables have already been built).'))
431
Weicai Yang813d6892020-10-19 16:20:31 -0700432 rung.add_argument("--run-opts",
433 "-ro",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100434 nargs="+",
435 default=[],
436 metavar="OPT",
437 help=('Additional options passed on the command line '
438 'each time a test is run.'))
439
Weicai Yang813d6892020-10-19 16:20:31 -0700440 rung.add_argument("--run-modes",
441 "-rm",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100442 nargs="+",
443 default=[],
444 metavar="MODE",
445 help=('The options for each run_mode in this list are '
446 'applied to each simulation run.'))
447
Weicai Yang813d6892020-10-19 16:20:31 -0700448 rung.add_argument("--profile",
449 "-p",
Srikrishna Iyerf807f9d2020-11-17 01:37:52 -0800450 nargs="?",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100451 choices=['time', 'mem'],
Srikrishna Iyerf807f9d2020-11-17 01:37:52 -0800452 const="time",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100453 metavar="P",
454 help=('Turn on simulation profiling (where P is time '
455 'or mem).'))
456
457 rung.add_argument("--xprop-off",
458 action='store_true',
459 help="Turn off X-propagation in simulation.")
460
461 rung.add_argument("--no-rerun",
462 action='store_true',
463 help=("Disable the default behaviour, where failing "
464 "tests are automatically rerun with waves "
465 "enabled."))
466
Weicai Yang813d6892020-10-19 16:20:31 -0700467 rung.add_argument("--verbosity",
468 "-v",
Srikrishna Iyera4f06642020-12-04 17:46:01 -0800469 choices=['n', 'l', 'm', 'h', 'f', 'd'],
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100470 metavar='V',
Rupert Swarbrickdcec9942020-09-04 08:15:07 +0100471 help=('Set tool/simulation verbosity to none (n), low '
Srikrishna Iyera4f06642020-12-04 17:46:01 -0800472 '(l), medium (m), high (h), full (f) or debug (d).'
473 ' The default value is set in config files.'))
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100474
475 seedg = parser.add_argument_group('Test seeds')
476
Weicai Yang813d6892020-10-19 16:20:31 -0700477 seedg.add_argument("--seeds",
478 "-s",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100479 nargs="+",
480 default=[],
481 metavar="S",
482 help=('A list of seeds for tests. Note that these '
483 'specific seeds are applied to items being run '
484 'in the order they are passed.'))
485
486 seedg.add_argument("--fixed-seed",
487 type=int,
488 metavar='S',
489 help=('Run all items with the seed S. This implies '
490 '--reseed 1.'))
491
Weicai Yang813d6892020-10-19 16:20:31 -0700492 seedg.add_argument("--reseed",
493 "-r",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100494 type=int,
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100495 metavar="N",
496 help=('Override any reseed value in the test '
497 'configuration and run each test N times, with '
498 'a new seed each time.'))
499
Weicai Yang813d6892020-10-19 16:20:31 -0700500 seedg.add_argument("--reseed-multiplier",
501 "-rx",
Rupert Swarbrickdcdfac32021-09-01 12:21:54 +0100502 type=parse_reseed_multiplier,
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100503 default=1,
504 metavar="N",
505 help=('Scale each reseed value in the test '
506 'configuration by N. This allows e.g. running '
507 'the tests 10 times as much as normal while '
508 'maintaining the ratio of numbers of runs '
509 'between different tests.'))
510
511 waveg = parser.add_argument_group('Dumping waves')
512
Weicai Yang813d6892020-10-19 16:20:31 -0700513 waveg.add_argument(
514 "--waves",
515 "-w",
516 nargs="?",
517 choices=["default", "fsdb", "shm", "vpd", "vcd", "evcd", "fst"],
518 const="default",
519 help=("Enable dumping of waves. It takes an optional "
520 "argument to pick the desired wave format. If "
521 "the optional argument is not supplied, it picks "
522 "whatever is the default for the chosen tool. "
523 "By default, dumping waves is not enabled."))
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100524
Weicai Yang813d6892020-10-19 16:20:31 -0700525 waveg.add_argument("--max-waves",
526 "-mw",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100527 type=int,
528 default=5,
529 metavar="N",
530 help=('Only dump waves for the first N tests run. This '
531 'includes both tests scheduled for run and those '
532 'that are automatically rerun.'))
533
534 covg = parser.add_argument_group('Generating simulation coverage')
535
Weicai Yang813d6892020-10-19 16:20:31 -0700536 covg.add_argument("--cov",
537 "-c",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100538 action='store_true',
539 help="Enable collection of coverage data.")
540
541 covg.add_argument("--cov-merge-previous",
542 action='store_true',
543 help=('Only applicable with --cov. Merge any previous '
544 'coverage database directory with the new '
545 'coverage database.'))
546
Weicai Yang080632d2020-10-16 17:52:14 -0700547 covg.add_argument("--cov-unr",
548 action='store_true',
549 help=('Run coverage UNR analysis and generate report. '
550 'This only supports VCS now.'))
551
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100552 covg.add_argument("--cov-analyze",
553 action='store_true',
554 help=('Rather than building or running any tests, '
555 'analyze the coverage from the last run.'))
556
557 pubg = parser.add_argument_group('Generating and publishing results')
558
559 pubg.add_argument("--map-full-testplan",
560 action='store_true',
561 help=("Show complete testplan annotated results "
562 "at the end."))
563
564 pubg.add_argument("--publish",
565 action='store_true',
566 help="Publish results to reports.opentitan.org.")
567
568 dvg = parser.add_argument_group('Controlling DVSim itself')
569
Weicai Yang813d6892020-10-19 16:20:31 -0700570 dvg.add_argument("--print-interval",
571 "-pi",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100572 type=int,
573 default=10,
574 metavar="N",
575 help="Print status every N seconds.")
576
577 dvg.add_argument("--verbose",
578 nargs="?",
579 choices=['default', 'debug'],
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100580 const="default",
581 metavar="D",
582 help=('With no argument, print verbose dvsim tool '
583 'messages. With --verbose=debug, the volume of '
584 'messages is even higher.'))
585
Weicai Yang813d6892020-10-19 16:20:31 -0700586 dvg.add_argument("--dry-run",
587 "-n",
Rupert Swarbrickb7aacc12020-06-02 11:04:55 +0100588 action='store_true',
589 help=("Print dvsim tool messages but don't actually "
590 "run any command"))
591
Rupert Swarbrick536ab202020-06-09 16:37:12 +0100592 args = parser.parse_args()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800593
594 if args.version:
595 print(version)
596 sys.exit()
597
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100598 # We want the --list argument to default to "all categories", but allow
599 # filtering. If args.list is None, then --list wasn't supplied. If it is
600 # [], then --list was supplied with no further arguments and we want to
601 # list all categories.
602 if args.list == []:
603 args.list = _LIST_CATEGORIES
604
Rupert Swarbrick536ab202020-06-09 16:37:12 +0100605 # Get max_parallel from environment if it wasn't specified on the command
606 # line.
607 args.max_parallel = resolve_max_parallel(args.max_parallel)
608 assert args.max_parallel > 0
609
610 return args
611
612
613def main():
614 args = parse_args()
615
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800616 # Add log level 'VERBOSE' between INFO and DEBUG
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800617 log.addLevelName(VERBOSE, 'VERBOSE')
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800618
619 log_format = '%(levelname)s: [%(module)s] %(message)s'
620 log_level = log.INFO
Rupert Swarbrick06383682020-03-24 15:56:41 +0000621 if args.verbose == "default":
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800622 log_level = VERBOSE
Rupert Swarbrick06383682020-03-24 15:56:41 +0000623 elif args.verbose == "debug":
624 log_level = log.DEBUG
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800625 log.basicConfig(format=log_format, level=log_level)
626
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800627 if not os.path.exists(args.cfg):
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800628 log.fatal("Path to config file %s appears to be invalid.", args.cfg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800629 sys.exit(1)
630
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800631 # If publishing results, then force full testplan mapping of results.
Rupert Swarbrick06383682020-03-24 15:56:41 +0000632 if args.publish:
633 args.map_full_testplan = True
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800634
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800635 args.branch = resolve_branch(args.branch)
Cindy Chen13a5dde2020-12-21 09:31:56 -0800636 proj_root_src, proj_root = resolve_proj_root(args)
Srikrishna Iyerf479b9e2021-04-08 16:03:11 -0700637 args.scratch_root = resolve_scratch_root(args.scratch_root, proj_root)
Srikrishna Iyer981c36b2020-12-12 12:20:35 -0800638 log.info("[proj_root]: %s", proj_root)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800639
Rupert Swarbrick62b3e762021-01-29 15:21:49 +0000640 # Create an empty FUSESOC_IGNORE file in scratch_root. This ensures that
641 # any fusesoc invocation from a job won't search within scratch_root for
642 # core files.
643 (Path(args.scratch_root) / 'FUSESOC_IGNORE').touch()
644
Cindy Chen13a5dde2020-12-21 09:31:56 -0800645 args.cfg = os.path.abspath(args.cfg)
646 if args.remote:
647 cfg_path = args.cfg.replace(proj_root_src + "/", "")
648 args.cfg = os.path.join(proj_root, cfg_path)
649
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800650 # Add timestamp to args that all downstream objects can use.
Eunchan Kim46125cd2020-04-09 09:26:52 -0700651 curr_ts = datetime.datetime.utcnow()
Srikrishna Iyer2b944ad2021-03-01 13:05:40 -0800652 setattr(args, "timestamp_long", curr_ts.strftime(TS_FORMAT_LONG))
653 setattr(args, "timestamp", curr_ts.strftime(TS_FORMAT))
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800654
655 # Register the seeds from command line with RunTest class.
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800656 RunTest.seeds = args.seeds
Srikrishna Iyer96e54102020-03-12 22:46:50 -0700657 # If we are fixing a seed value, no point in tests having multiple reseeds.
Rupert Swarbrick06383682020-03-24 15:56:41 +0000658 if args.fixed_seed:
659 args.reseed = 1
Srikrishna Iyer2d151192021-02-10 16:56:16 -0800660 RunTest.fixed_seed = args.fixed_seed
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800661
662 # Register the common deploy settings.
Rupert Swarbrickc0e753d2020-06-04 13:27:58 +0100663 Timer.print_interval = args.print_interval
Srikrishna Iyer206721c2021-04-15 15:23:08 -0700664 LocalLauncher.LocalLauncher.max_parallel = args.max_parallel
Srikrishna Iyer9d9d86f2021-03-02 00:15:51 -0800665 Launcher.Launcher.max_odirs = args.max_odirs
666 LauncherFactory.set_launcher_type(args.local)
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800667
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800668 # Build infrastructure from hjson file and create the list of items to
669 # be deployed.
Weicai Yangfc2ff3b2020-03-19 18:05:14 -0700670 global cfg
Rupert Swarbricka23dfec2020-09-07 10:01:28 +0100671 cfg = make_cfg(args.cfg, args, proj_root)
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100672
Srikrishna Iyer64009052020-01-13 11:27:39 -0800673 # List items available for run if --list switch is passed, and exit.
Rupert Swarbricke83b55e2020-05-12 11:44:04 +0100674 if args.list is not None:
Srikrishna Iyer64009052020-01-13 11:27:39 -0800675 cfg.print_list()
676 sys.exit(0)
677
Weicai Yang080632d2020-10-16 17:52:14 -0700678 # Purge the scratch path if --purge option is set.
679 if args.purge:
680 cfg.purge()
681
682 # If --cov-unr is passed, run UNR to generate report for unreachable
683 # exclusion file.
684 if args.cov_unr:
685 cfg.cov_unr()
686 cfg.deploy_objects()
687 sys.exit(0)
688
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800689 # In simulation mode: if --cov-analyze switch is passed, then run the GUI
690 # tool.
691 if args.cov_analyze:
692 cfg.cov_analyze()
Srikrishna Iyer39ffebd2020-03-30 11:53:12 -0700693 cfg.deploy_objects()
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800694 sys.exit(0)
695
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800696 # Deploy the builds and runs
Srikrishna Iyerbf7faa42021-10-19 22:22:19 -0700697 if args.items:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800698 # Create deploy objects.
699 cfg.create_deploy_objects()
Rupert Swarbricka2892a52020-10-12 08:50:08 +0100700 results = cfg.deploy_objects()
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800701
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800702 # Generate results.
Rupert Swarbricka2892a52020-10-12 08:50:08 +0100703 cfg.gen_results(results)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800704
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800705 # Publish results
Rupert Swarbrick06383682020-03-24 15:56:41 +0000706 if args.publish:
707 cfg.publish_results()
Weicai Yangfd2c22e2020-02-11 18:37:14 -0800708
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800709 else:
Srikrishna Iyerbf7faa42021-10-19 22:22:19 -0700710 log.error("Nothing to run!")
711 sys.exit(1)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800712
Srikrishna Iyer442d8db2020-03-05 15:17:17 -0800713 # Exit with non-zero status if there were errors or failures.
714 if cfg.has_errors():
715 log.error("Errors were encountered in this run.")
716 sys.exit(1)
717
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800718
719if __name__ == '__main__':
720 main()