[bazel] add DV sim target to opentitan_functest

To support transitioning SW builds to bazel, additional rules needed to
be added to build sim_dv-specific device artifacts. These included a
sim_dv-specific bootstrap image (#11640) rule, and a sim_dv-specific SW
logging database extraction rule (#11755).

While both of these rules have already been added, they were not yet
invoked by the main test macro. Specifically, at a high level, the
`opentitan_functest` generates a `test_suite` of `sh_test` rules. The
SW artifacts (ELFs, BINs, VMEMs, etc.) are marked as data runfiles of
each `sh_test` rule, so they get built when either `bazel build
<opentitan_functest name>` or `bazel test <opentitan_functest name>` is
executed. Since there was no DV sim specific `sh_test` rule in the
`opentitan_functest` `test_suite`, the sim_dv-specific artifacts were not
yet built when the prior commands.

To force bazel to build SW artifacts for the DV sim environment, an
additional `dv` target was added to the `opentitan_functest` macro.
Additionally, an associated `sh_test` target was added that runs a simple
shell script that echos the proper `dvsim.py` command a user should
execute to run the associated test in the DV simulation environment.

The reason the DV `sh_test` does not invoke `dvsim.py` directly, is
three fold:

1) `dvsim.py` has two components, an open-source component (that is
   included in this repository) and a closed source component (that is
   not included in this repository). The open-source component has the
   ability to launch DV simulations locally, provided a supported RTL
   simulator is installed locally on your machine. The closed-source
   component has the ability to launch DV simulations on
   cloud-infrastructure, that is often organization-specific. Teaching
   bazel how to handle the dependencies between these components is a
   topic for future investigation, outside the scope of this commit.

2) As written, `dvsim.py` invokes the SW build system as part of the
   process of launching simulations. If those simulations are launched
   on remote cloud infrastructure this will result in `dvsim.py`
   building SW artifacts twice, once locally when `bazel test
   <opentitan_functest test>` is invoked, and once on the remote machine
   when `dvsim.py` itself invokes `bazel build <opentitan_functest
   test>`. Resolving this inefficieny is outside the scope of this
   commit.

3) `dvsim.py` accepts a wide array of command line arguments that enable
   adjusting various simulation configurations and parameters (e.g.,
   whether to generate waves or not, whether to collect coverage or not,
   which random seeds to use, etc.). Integrating those into a bazel test
   target will require additional design / thought outside the scope of
   this commit.

This fixes #11684.

Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/rules/opentitan.bzl b/rules/opentitan.bzl
index f195c4a..5b8c0a5 100644
--- a/rules/opentitan.bzl
+++ b/rules/opentitan.bzl
@@ -711,6 +711,48 @@
         srcs = targets,
     )
 
+def dv_params(
+        rom = "//sw/device/lib/testing/test_rom:test_rom_sim_dv_scr_vmem",
+        otp = "//hw/ip/otp_ctrl/data:rma_image_verilator",
+        chip_sim_config = "//hw/top_earlgrey/dv:chip_sim_cfg.hjson",
+        bootstrap_sw = False,  # Default to backdoor loading.
+        tags = [],
+        timeout = "long",  # 15 minutes
+        local = True,
+        args = [
+            "$(location {chip_sim_config})",
+        ],
+        data = [],
+        **kwargs):
+    """A macro to create DV parameters for OpenTitan functional tests.
+
+    This macro emits a dictionary of parameters which are pasted into the DV
+    specific test rule.
+
+    Args:
+        @param rom: The ROM to use when running a DV simulation.
+        @param otp: The OTP image to use when running a DV simulation.
+        @param chip_sim_config: The dvsim.py HJSON chip_sim_config file for the toplevel.
+        @param bootstrap_sw: Whether to load flash via bootstrap.
+        @param tags: The test tags to apply to the test rule.
+        @param timeout: The timeout to apply to the test rule.
+        @param local: Whether the test should be run locally and without sandboxing.
+        @param args: Extra arguments to pass to `dvsim.py`.
+        @param data: Data dependencies of the test.
+    """
+    kwargs.update(
+        rom = rom,
+        otp = otp,
+        chip_sim_config = chip_sim_config,
+        bootstrap_sw = bootstrap_sw,
+        tags = tags + ["dv"],
+        timeout = timeout,
+        local = local,
+        args = args,
+        data = data,
+    )
+    return kwargs
+
 def verilator_params(
         rom = "//sw/device/lib/testing/test_rom:test_rom_sim_verilator_scr_vmem",
         otp = "//hw/ip/otp_ctrl/data:rma_image_verilator",
@@ -832,6 +874,7 @@
         test_in_rom = False,
         signed = False,
         key = "test_key_0",
+        dv = None,
         verilator = None,
         cw310 = None,
         **kwargs):
@@ -844,7 +887,8 @@
     Args:
       @param name: The name of this rule.
       @param targets: A list of hardware targets on which to dispatch tests.
-      @param args: Extra arguments to pass to the test runner (`opentitantool`).
+      @param args: Extra arguments to pass to the test runner (`opentitantool`
+                   or `dvsim.py`).
       @param data: Extra data dependencies needed while executing the test.
       @param ottf: Default dependencies for OTTF tests. Set to empty list if
                    your test doesn't use the OTTF.
@@ -852,12 +896,14 @@
                           default.
       @param signed: Whether to sign the test image. Unsigned by default.
       @param key: Which signed test image (by key) to use.
+      @param dv: DV test parameters.
       @param verilator: Verilator test parameters.
       @param cw310: CW310 test parameters.
       @param **kwargs: Arguments to forward to `opentitan_flash_binary`.
 
     This macro emits the following rules:
         opentitan_flash_binary named: {name}_prog (and all emitted rules).
+        sh_test                named: dv_{name}
         sh_test                named: verilator_{name}
         sh_test                named: cw310_{name}
         test_suite             named: {name}
@@ -880,6 +926,59 @@
 
     all_tests = []
 
+    if "dv" in targets:
+        # Set default DV sim parameters if none are provided.
+        if dv == None:
+            dv = dv_params()
+
+        test_name = "dv_{}".format(name)
+
+        # Regardless if the test is signed or not, DV sims backdoor load flash
+        # with the scrambled VMEM (unless the bootstrap path is used below).
+        test_bin = "{}_prog_sim_dv_scr_vmem64".format(name)
+
+        # If the (flash) test image is to be loaded via bootstrap, then we need
+        # to use the VMEM image that has been split into SPI flash frames.
+        if dv.pop("bootstrap_sw"):
+            if test_in_rom:
+                fail("A test runs in ROM cannot be bootstrapped.")
+            test_bin = "{}_prog_sim_dv_frames_vmem".format(name)
+
+        if "manual" not in dv.get("tags", []):
+            all_tests.append(test_name)
+
+        rom = dv.pop("rom")
+        if test_in_rom:
+            rom = name + "_rom_prog_sim_dv_scr_vmem"
+        otp = dv.pop("otp")
+        chip_sim_config = dv.pop("chip_sim_config")
+        dargs = _format_list("args", args, dv, chip_sim_config = chip_sim_config)
+        ddata = _format_list("data", data, dv)
+
+        # SW logs database files for backdoor message logging in sim.
+        rom_sw_logs_db = rom.replace("_scr_vmem", "_logs_db")
+        flash_sw_logs_db = "{}_prog_sim_dv_logs_db".format(name)
+
+        native.sh_test(
+            name = test_name,
+            srcs = ["//util:dvsim_test_runner.sh"],
+            args = [
+                "-i",
+                "chip_sw_{}".format(name),
+            ] + dargs,
+            data = [
+                test_bin,
+                rom,
+                otp,
+                chip_sim_config,
+                rom_sw_logs_db,
+                flash_sw_logs_db,
+                "//util/dvsim",
+                "//hw:all_files",
+            ] + ddata,
+            **dv
+        )
+
     if "verilator" in targets:
         # Set default Verilator sim parameters if none are provided.
         if verilator == None:
diff --git a/util/dvsim_test_runner.sh b/util/dvsim_test_runner.sh
new file mode 100755
index 0000000..fb38610
--- /dev/null
+++ b/util/dvsim_test_runner.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+#
+# A shell script for executing dvsim.py as the test harness for functional
+# tests.
+
+set -e
+
+readonly DVSIM="util/dvsim/dvsim.py"
+
+echo "At this time, dvsim.py must be run manually (after building SW) via:
+${DVSIM} $@"