[bazel,dvsim] remove gen-binaries.py's reliance on meson_init.sh

Meson will soon be removed from our project and replaced entirely with
bazel (#12449). This updates the the OTBN `gen-binaries.py` script to
not rely on the presence of the `.env` file that was produced by the
`meson_init.sh` script to provide locations to the RV32 toolchain.

Instead, the `gen-binaries.py` script now uses environment variables to
get the locations of the RV32 toolchain tools, which are populated via
queries to bazel.

This fixes #12447.

Signed-off-by: Timothy Trippel <ttrippel@google.com>
diff --git a/hw/ip/otbn/dv/uvm/gen-binaries.py b/hw/ip/otbn/dv/uvm/gen-binaries.py
index 7960fa4..47eb8ec 100755
--- a/hw/ip/otbn/dv/uvm/gen-binaries.py
+++ b/hw/ip/otbn/dv/uvm/gen-binaries.py
@@ -38,6 +38,10 @@
     return read_positive(val)
 
 
+def is_exe(path: str) -> bool:
+    return os.path.isfile(path) and os.access(path, os.X_OK)
+
+
 class Toolchain:
 
     def __init__(self, env_data: Dict[str, str]) -> None:
@@ -50,7 +54,7 @@
     def get_tool(env_data: Dict[str, str], tool: str) -> str:
         path = env_data.get(tool)
         if path is None:
-            raise RuntimeError('No entry for {} in .env file'.format(tool))
+            raise RuntimeError('Unable to find tool: {}.'.format(tool))
         return path
 
     def run(self, cmd: List[str]) -> None:
@@ -61,60 +65,29 @@
         subprocess.run(cmd, env=env)
 
 
-def take_env_line(dst: Dict[str, str], path: str, line_number: int,
-                  line: str) -> None:
-    '''Read one line from a .env file, updating dst.'''
-    line = line.split('#', 1)[0].strip()
-    if not line:
-        return
+def get_toolchain(otbn_dir: str) -> Toolchain:
+    '''Reads environment variables to get toolchain info.'''
+    env_dict = {}  # type: Dict[str, str]
 
-    parts = line.split('=', 1)
-    if len(parts) != 2:
-        raise RuntimeError('{}:{}: No equals sign in line {!r}.'.format(
-            path, line_number, line))
+    # OTBN assembler and linker
+    env_dict['OTBN_AS'] = f"{otbn_dir}/util/otbn_as.py"
+    env_dict['OTBN_LD'] = f"{otbn_dir}/util/otbn_ld.py"
 
-    dst[parts[0]] = parts[1]
+    # RV32 assembler and linker
+    env_dict['RV32_TOOL_AS'] = os.getenv('RV32_TOOL_AS')
+    rv32_tool_as_default = "tools/riscv/bin/riscv32-unknown-elf-as"
+    if env_dict['RV32_TOOL_AS'] is None and is_exe(rv32_tool_as_default):
+        env_dict['RV32_TOOL_AS'] = rv32_tool_as_default
+    env_dict['RV32_TOOL_LD'] = os.getenv('RV32_TOOL_LD')
+    rv32_tool_ld_default = "tools/riscv/bin/riscv32-unknown-elf-ld"
+    if env_dict['RV32_TOOL_LD'] is None and is_exe(rv32_tool_ld_default):
+        env_dict['RV32_TOOL_LD'] = rv32_tool_ld_default
 
-
-def read_toolchain(obj_dir_arg: Optional[str], otbn_dir: str) -> Toolchain:
-    '''Read Meson's dumped .env file to get toolchain info'''
-    if obj_dir_arg is not None:
-        obj_dir = obj_dir_arg
-        source = 'specified by an --obj-dir argument'
-    else:
-        obj_dir_env = os.getenv('OBJ_DIR')
-        if obj_dir_env is not None:
-            obj_dir = obj_dir_env
-            source = ('inferred from OBJ_DIR environment variable; '
-                      'have you run meson_init.sh?')
-        else:
-            git_dir = os.path.normpath(os.path.join(otbn_dir, '../' * 3))
-            obj_dir = os.path.normpath(os.path.join(git_dir, 'build-out'))
-            source = ('inferred from script location; '
-                      'have you run meson_init.sh?')
-
-    env_path = os.path.join(obj_dir, '.env')
-    try:
-        with open(env_path) as env_file:
-            env_dict = {}  # type: Dict[str, str]
-            for idx, line in enumerate(env_file):
-                take_env_line(env_dict, env_path, idx + 1, line)
-
-            return Toolchain(env_dict)
-    except OSError as ose:
-        raise RuntimeError('Failed to read .env file at {!r} '
-                           '(at a path {}): {}.'.format(env_path, source,
-                                                        ose)) from None
+    return Toolchain(env_dict)
 
 
 def main() -> int:
     parser = argparse.ArgumentParser()
-    parser.add_argument('--obj-dir',
-                        help=('Object directory configured with Meson (used '
-                              'to find tool configuration). If not supplied, '
-                              'defaults to the OBJ_DIR environment variable '
-                              'if set, or build-out at the top of the '
-                              'repository if not.'))
     parser.add_argument('--count',
                         type=read_positive,
                         help='Number of binaries to generate (default: 10)')
@@ -164,7 +137,7 @@
     otbn_dir = os.path.normpath(os.path.join(script_dir, '../' * 2))
 
     try:
-        toolchain = read_toolchain(args.obj_dir, otbn_dir)
+        toolchain = get_toolchain(otbn_dir)
     except RuntimeError as err:
         print(err, file=sys.stderr)
         return 1
@@ -204,7 +177,7 @@
 
 def write_ninja_rnd(handle: TextIO, toolchain: Toolchain, otbn_dir: str,
                     count: int, start_seed: int, size: int) -> None:
-    '''Write a build.ninja to build random binaries
+    '''Write a build.ninja to build random binaries.
 
     The rules build everything in the same directory as the build.ninja file.
     OTBN tooling is found through the toolchain argument.
diff --git a/hw/ip/otbn/dv/uvm/get-toolchain-paths.sh b/hw/ip/otbn/dv/uvm/get-toolchain-paths.sh
new file mode 100755
index 0000000..00b941c
--- /dev/null
+++ b/hw/ip/otbn/dv/uvm/get-toolchain-paths.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# Copyright lowRISC contributors.
+# Licensed under the Apache License, Version 2.0, see LICENSE for details.
+# SPDX-License-Identifier: Apache-2.0
+
+set -o errexit
+set -o pipefail
+
+# If we are on an air-gapped machine, we need to first ensure the toolchain has
+# been unpacked from the repository cache.
+if [[ -n ${BAZEL_CACHE} ]]; then
+  BAZEL_CMD="bazel"
+  ${BAZEL_CMD} fetch \
+    --distdir=${BAZEL_DISTDIR} \
+    --repository_cache=${BAZEL_CACHE} \
+    @com_lowrisc_toolchain_rv32imc_compiler//...
+else
+  BAZEL_CMD="./bazelisk.sh"
+  ${BAZEL_CMD} fetch @com_lowrisc_toolchain_rv32imc_compiler//...
+fi
+
+# Set environment variables for the RV32 linker and assembler.
+export RV32_TOOL_LD=$(${BAZEL_CMD} query \
+  'deps(@com_lowrisc_toolchain_rv32imc_compiler//:bin/riscv32-unknown-elf-ld)' \
+  --output location | cut -f1 -d:)
+export RV32_TOOL_AS=$(${BAZEL_CMD} query \
+  'deps(@com_lowrisc_toolchain_rv32imc_compiler//:bin/riscv32-unknown-elf-as)' \
+  --output location | cut -f1 -d:)
diff --git a/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson b/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson
index bf2cfc6..01d5378 100644
--- a/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson
+++ b/hw/ip/otbn/dv/uvm/otbn_sim_cfg.hjson
@@ -70,18 +70,12 @@
 
   run_opts: ["+otbn_elf_dir={otbn_elf_dir}"]
 
-  // A default build mode, used for the tests explicitly listed below. This
-  // runs meson to set up a .env file, needed by gen-binaries.py to find a
-  // toolchain. This isn't needed for things like the automated CSR tests, and
-  // they won't use it because they build in a different mode anyway.
-  //
-  // This step could run either as a pre-build command or a post-build command.
-  // Since it's much quicker than the SV build, we do it first so that you get
-  // a quick result if something has gone wrong.
+  // The default build mode, used for the tests explicitly listed below does not
+  // require any pre-build steps.
   build_modes: [
     {
       name: default
-      pre_build_cmds: ["cd {proj_root} && BUILD_ROOT={build_dir} ./meson_init.sh"]
+      pre_build_cmds: []
     }
   ]
 
@@ -89,12 +83,13 @@
   // controls the number of instructions that are run before ECALL or error.
   binary_size: 2000
 
-  otbn_obj_dir: "{build_dir}/build-out"
-  gen_binaries_py: "{otbn_dir}/dv/uvm/gen-binaries.py"
+  // This runs bazel to locate the RV32 toolchain, needed by gen-binaries.py.
+  setup_env: "pushd {proj_root}; source hw/ip/otbn/dv/uvm/get-toolchain-paths.sh; popd;"
+  gen_binaries_py: "{setup_env} {otbn_dir}/dv/uvm/gen-binaries.py"
 
   rnd_args: "--seed {seed} --size {binary_size}"
-  gen_fixed: "{gen_binaries_py} --obj-dir {otbn_obj_dir}"
-  gen_rnd: "{gen_binaries_py} --obj-dir {otbn_obj_dir} {rnd_args}"
+  gen_fixed: "{gen_binaries_py}"
+  gen_rnd: "{gen_binaries_py} {rnd_args}"
 
   smoke_dir: "{otbn_dir}/dv/smoke"
   multi_err_dir: "{otbn_dir}/dv/otbnsim/test/simple/multi"