[dvsim] Improve --help message

This should contain no functional changes, but improves the output
from dvsim.py --help.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/util/dvsim/dvsim.py b/util/dvsim/dvsim.py
index 0f3e40d..9c2d66f 100755
--- a/util/dvsim/dvsim.py
+++ b/util/dvsim/dvsim.py
@@ -2,14 +2,21 @@
 # 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.
+"""dvsim is a command line tool to deploy ASIC tool flows such as regressions
+for design verification (DV), formal property verification (FPV), linting and
+synthesis.
+
+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 the
+tools 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
@@ -18,6 +25,7 @@
 import os
 import subprocess
 import sys
+import textwrap
 from signal import SIGINT, signal
 
 import Deploy
@@ -138,296 +146,317 @@
     exit(1)
 
 
-def main():
+def wrapped_docstring():
+    '''Return a text-wrapped version of the module docstring'''
+    paras = []
+    para = []
+    for line in __doc__.strip().split('\n'):
+        line = line.strip()
+        if not line:
+            if para:
+                paras.append('\n'.join(para))
+                para = []
+        else:
+            para.append(line)
+    if para:
+        paras.append('\n'.join(para))
+
+    return '\n\n'.join(textwrap.fill(p) for p in paras)
+
+
+def parse_args():
     parser = argparse.ArgumentParser(
-        description=__doc__,
+        description=wrapped_docstring(),
         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="*",
-        choices=_LIST_CATEGORIES,
-        metavar="build_modes|run_modes|tests|regressions",
-        help=
-        """List the available build_modes / run_modes / tests / regressions for use."""
-    )
-
-    parser.add_argument("-t",
-                        "--tool",
-                        metavar="vcs|xcelium|ascentlint|veriblelint|dc|...",
-                        help="Override the tool that is set in hjson file")
-
-    parser.add_argument(
-        "--select-cfgs",
-        nargs="*",
-        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",
-        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",
-        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",
-        action='store_true',
-        help="Only build the simulation executables for the givem items.")
-
-    parser.add_argument(
-        "--run-only",
-        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,
-        help=
-        """Run all items with a fixed seed value. This option enforces --reseed 1."""
-    )
-
-    parser.add_argument(
-        "-r",
-        "--reseed",
-        type=int,
-        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",
-                        action='store_true',
-                        help="Enable dumping of waves")
-
-    parser.add_argument("-d",
-                        "--dump",
-                        choices=["fsdb", "shm", "vpd"],
-                        help=("Format to dump waves for simulation. If Verdi "
-                              "is installed (detected by searching PATH) this "
-                              "defaults to fsdb. Otherwise, defaults to shm "
-                              "for Xcelium or vpd for VCS."))
-
-    parser.add_argument("-mw",
-                        "--max-waves",
-                        type=int,
-                        default=5,
-                        metavar="N",
-                        help="""Enable dumping of waves for at most N tests;
-                                this includes tests scheduled for run AND automatic rerun"""
-                        )
-
-    parser.add_argument("-c",
-                        "--cov",
-                        action='store_true',
-                        help="turn on coverage collection")
-
-    parser.add_argument(
-        "--cov-merge-previous",
-        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",
-        action='store_true',
-        help="Analyze the coverage from the last regression result.")
-
-    parser.add_argument("-p",
-                        "--profile",
-                        nargs='?',
-                        const='time',
-                        choices=["time", "mem"],
-                        help="Turn on simulation profiling")
-
-    parser.add_argument("--xprop-off",
-                        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",
-                        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",
-        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",
-                        choices=["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(
-        "--verbose",
-        nargs="?",
-        choices=['default', 'debug'],
-        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",
                         action='store_true',
                         help="Print version and exit")
 
-    parser.add_argument(
-        "-n",
-        "--dry-run",
-        action='store_true',
-        help=
-        "Print dvsim tool messages only, without actually running any command")
+    parser.add_argument("--tool", "-t",
+                        default="",
+                        help=("Explicitly set the tool to use. This is "
+                              "optional for running simulations (where it can "
+                              "be set in an .hjson file), but is required for "
+                              "other flows. Possible tools include: vcs, "
+                              "xcelium, ascentlint, verible, dc."))
 
-    parser.add_argument(
-        "--map-full-testplan",
-        action='store_true',
-        help="Force complete testplan annotated results to be shown at the end."
-    )
+    parser.add_argument("--list", "-l",
+                        nargs="*",
+                        metavar='CAT',
+                        choices=_LIST_CATEGORIES,
+                        help=('Parse the the given .hjson config file, list '
+                              'the things that can be run, then exit. The '
+                              'list can be filtered with a space-separated '
+                              'of categories from: {}.'
+                              .format(', '.join(_LIST_CATEGORIES))))
 
-    parser.add_argument(
-        "--publish",
-        action='store_true',
-        help="Publish results to the reports.opentitan.org web server.")
+    whatg = parser.add_argument_group('Choosing what to run')
 
-    parser.add_argument(
-        "-pi",
-        "--print-interval",
-        type=int,
-        default=10,
-        metavar="N",
-        help="""Interval in seconds. Print status every N seconds.""")
+    whatg.add_argument("-i",
+                       "--items",
+                       nargs="*",
+                       default=["sanity"],
+                       help=('Specify the regressions or tests to run. '
+                             'Defaults to "sanity", but can be a '
+                             'space separated list of test or regression '
+                             'names.'))
 
-    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.""")
+    whatg.add_argument("--select-cfgs",
+                       nargs="*",
+                       metavar="CFG",
+                       help=('The .hjson file is a master config. Only run '
+                             'the given configs from it. If this argument is '
+                             'not used, dvsim will process all configs listed '
+                             'in a master config.'))
 
-    args = parser.parse_args()
+    disg = parser.add_argument_group('Dispatch options')
+
+    disg.add_argument("--job-prefix",
+                      default="",
+                      metavar="PFX",
+                      help=('Prepend this string when running each tool '
+                            'command.'))
+
+    disg.add_argument("--max-parallel", "-mp",
+                      type=int,
+                      default=16,
+                      metavar="N",
+                      help=('Run only up to N builds/tests at a time. '
+                            'Default value 16.'))
+
+    pathg = parser.add_argument_group('File management')
+
+    pathg.add_argument("--scratch-root", "-sr",
+                       metavar="PATH",
+                       help=('Destination for build / run directories. If not '
+                             'specified, uses the path in the SCRATCH_ROOT '
+                             'environment variable, if set, or ./scratch '
+                             'otherwise.'))
+
+    pathg.add_argument("--proj-root", "-pr",
+                       metavar="PATH",
+                       help=('The root directory of the project. If not '
+                             'specified, dvsim will search for a git '
+                             'repository containing the current directory.'))
+
+    pathg.add_argument("--branch", "-br",
+                       metavar='B',
+                       help=('By default, dvsim creates files below '
+                             '{scratch-root}/{dut}.{flow}.{tool}/{branch}. '
+                             'If --branch is not specified, dvsim assumes the '
+                             'current directory is a git repository and uses '
+                             'the name of the current branch.'))
+
+    pathg.add_argument("--max-odirs", "-mo",
+                       type=int,
+                       default=5,
+                       metavar="N",
+                       help=('When tests are run, older runs are backed '
+                             'up. Discard all but the N most recent (defaults '
+                             'to 5).'))
+
+    pathg.add_argument("--purge",
+                       action='store_true',
+                       help="Clean the scratch directory before running.")
+
+    buildg = parser.add_argument_group('Options for building')
+
+    buildg.add_argument("--build-only",
+                        action='store_true',
+                        help=('Stop after building executables for the given '
+                              'items.'))
+
+    buildg.add_argument("--build-unique", "-bu",
+                        action='store_true',
+                        help=('Append a timestamp to the directory in which '
+                              'files are built. This is suitable for the case '
+                              'when another test is already running and you '
+                              'want to run something else from a different '
+                              'terminal without affecting it.'))
+
+    buildg.add_argument("--build-opts", "-bo",
+                        nargs="+",
+                        default=[],
+                        metavar="OPT",
+                        help=('Additional options passed on the command line '
+                              'each time a build tool is run.'))
+
+    buildg.add_argument("--build-modes", "-bm",
+                        nargs="+",
+                        default=[],
+                        metavar="MODE",
+                        help=('The options for each build_mode in this list '
+                              'are applied to all build and run targets.'))
+
+    rung = parser.add_argument_group('Options for running')
+
+    rung.add_argument("--run-only",
+                      action='store_true',
+                      help=('Skip the build step (assume that simulation '
+                            'executables have already been built).'))
+
+    rung.add_argument("--run-opts", "-ro",
+                      nargs="+",
+                      default=[],
+                      metavar="OPT",
+                      help=('Additional options passed on the command line '
+                            'each time a test is run.'))
+
+    rung.add_argument("--run-modes", "-rm",
+                      nargs="+",
+                      default=[],
+                      metavar="MODE",
+                      help=('The options for each run_mode in this list are '
+                            'applied to each simulation run.'))
+
+    rung.add_argument("--profile", "-p",
+                      choices=['time', 'mem'],
+                      metavar="P",
+                      help=('Turn on simulation profiling (where P is time '
+                            'or mem).'))
+
+    rung.add_argument("--xprop-off",
+                      action='store_true',
+                      help="Turn off X-propagation in simulation.")
+
+    rung.add_argument("--no-rerun",
+                      action='store_true',
+                      help=("Disable the default behaviour, where failing "
+                            "tests are automatically rerun with waves "
+                            "enabled."))
+
+    rung.add_argument("--verbosity", "-v",
+                      default="l",
+                      choices=['n', 'l', 'm', 'h', 'd'],
+                      metavar='V',
+                      help=('Set UVM verbosity to none (n), low (l; the '
+                            'default), medium (m), high (h) or debug (d). '
+                            'This overrides any setting in the config files.'))
+
+    seedg = parser.add_argument_group('Test seeds')
+
+    seedg.add_argument("--seeds", "-s",
+                       nargs="+",
+                       default=[],
+                       metavar="S",
+                       help=('A list of seeds for tests. Note that these '
+                             'specific seeds are applied to items being run '
+                             'in the order they are passed.'))
+
+    seedg.add_argument("--fixed-seed",
+                       type=int,
+                       metavar='S',
+                       help=('Run all items with the seed S. This implies '
+                             '--reseed 1.'))
+
+    seedg.add_argument("--reseed", "-r",
+                       type=int,
+                       default=-1,
+                       metavar="N",
+                       help=('Override any reseed value in the test '
+                             'configuration and run each test N times, with '
+                             'a new seed each time.'))
+
+    seedg.add_argument("--reseed-multiplier", "-rx",
+                       type=int,
+                       default=1,
+                       metavar="N",
+                       help=('Scale each reseed value in the test '
+                             'configuration by N. This allows e.g. running '
+                             'the tests 10 times as much as normal while '
+                             'maintaining the ratio of numbers of runs '
+                             'between different tests.'))
+
+    waveg = parser.add_argument_group('Dumping waves')
+
+    waveg.add_argument("--waves", "-w",
+                       action='store_true',
+                       help="Enable dumping of waves")
+
+    waveg.add_argument("-d",
+                       "--dump",
+                       choices=["fsdb", "shm", "vpd"],
+                       help=("Format to dump waves for simulation. The default "
+                             "format depends on the tool. With VCS, this "
+                             "defaults to fsdb if Verdi is installed, else "
+                             "vpd. With Xcelium, defaults to shm."))
+
+    waveg.add_argument("--max-waves", "-mw",
+                       type=int,
+                       default=5,
+                       metavar="N",
+                       help=('Only dump waves for the first N tests run. This '
+                             'includes both tests scheduled for run and those '
+                             'that are automatically rerun.'))
+
+    covg = parser.add_argument_group('Generating simulation coverage')
+
+    covg.add_argument("--cov", "-c",
+                      action='store_true',
+                      help="Enable collection of coverage data.")
+
+    covg.add_argument("--cov-merge-previous",
+                      action='store_true',
+                      help=('Only applicable with --cov. Merge any previous '
+                            'coverage database directory with the new '
+                            'coverage database.'))
+
+    covg.add_argument("--cov-analyze",
+                      action='store_true',
+                      help=('Rather than building or running any tests, '
+                            'analyze the coverage from the last run.'))
+
+    pubg = parser.add_argument_group('Generating and publishing results')
+
+    pubg.add_argument("--map-full-testplan",
+                      action='store_true',
+                      help=("Show complete testplan annotated results "
+                            "at the end."))
+
+    pubg.add_argument("--publish",
+                      action='store_true',
+                      help="Publish results to reports.opentitan.org.")
+
+    dvg = parser.add_argument_group('Controlling DVSim itself')
+
+    dvg.add_argument("--print-interval", "-pi",
+                     type=int,
+                     default=10,
+                     metavar="N",
+                     help="Print status every N seconds.")
+
+    dvg.add_argument("--verbose",
+                     nargs="?",
+                     choices=['default', 'debug'],
+                     default=None,
+                     const="default",
+                     metavar="D",
+                     help=('With no argument, print verbose dvsim tool '
+                           'messages. With --verbose=debug, the volume of '
+                           'messages is even higher.'))
+
+    dvg.add_argument("--dry-run", "-n",
+                     action='store_true',
+                     help=("Print dvsim tool messages but don't actually "
+                           "run any command"))
+
+    return parser.parse_args()
+
+
+def main():
+    args = parse_args()
+
+    # We want the --list argument to default to "all categories", but allow
+    # filtering. If args.list is None, then --list wasn't supplied. If it is
+    # [], then --list was supplied with no further arguments and we want to
+    # list all categories.
+    if args.list == []:
+        args.list = _LIST_CATEGORIES
 
     if args.version:
         print(version)