| --- | |
| title: Bazel Notes | |
| --- | |
| Both OpenTitan hardware and software is built with Bazel. | |
| While our [Getting Started]({{< relref "getting_started" >}}) guides detail some of the Bazel commands that can be used to build both types of artifacts, below are detailed notes on: | |
| * how Bazel is configured for our project, and | |
| * brief examples of Bazel commands that are useful for: | |
| * querying, | |
| * building, and | |
| * running tests with Bazel. | |
| # OpenTitan Bazel Workspace | |
| The rules for Bazel are described in a language called Starlark, which looks a lot like Python. | |
| The `$REPO_TOP` directory is defined as a Bazel workspace by the `//WORKSPACE` file. | |
| `BUILD` files provide the information Bazel needs to build the targets in a directory. | |
| `BUILD` files also manage any subdirectories that don't have their own `BUILD` files. | |
| OpenTitan uses .bzl files to specify custom rules to build artifacts that require specific attention like on-device test rules and project specific binaries. | |
| ## WORKSPACE file | |
| The `WORKSPACE` file controls external dependencies such that builds can be made reproducible and hermetic. | |
| Bazel loads specific external dependencies, such as various language toolchains. | |
| It uses them to build OpenTitan targets (like it does with bazel\_embedded) or to satisfy dependencies (as it does with abseil). | |
| To produce increasingly stable releases the external dependencies loaded in `WORKSPACE` file attempts to fix a all external `http_archive`s to a specific SHA. | |
| As we add more dependencies to the workspace, builds and tests will become less sensitive to external updates, and we will vastly simplify the [Getting Started]({{< relref "getting_started" >}}) instructions. | |
| ## BUILD files | |
| Throughout the OpenTitan repository, `BUILD` files describe targets and dependencies in the same directory (and subdirectories that lack their own `BUILD` files). | |
| `BUILD` files are mostly hand-written. | |
| To maintain the invariant that hand-written files not be included in autogen directories, there are `BUILD` files that describe how to build and depend on auto-generated files in autogen subdirectories. | |
| # General Commands | |
| - Build everything (software and Verilator hardware): | |
| ```console | |
| bazel build //... | |
| ``` | |
| - Build and run all tests (on-host tests, and Verilator/FPGA on-device tests): | |
| ```console | |
| bazel test --test_tag_filters=-broken,-dv //sw/... | |
| ``` | |
| Note: this will take several hours. | |
| - Clean all build outputs and reclaim all disk and memory traces of a Bazel instance: | |
| ```console | |
| bazel clean --expunge | |
| ``` | |
| Note: you should rarely need to run this, see [below]({{< relref "#troubleshooting-builds" >}}) for when this may be necessary. | |
| # Locating Build Artifacts | |
| When Bazel builds a target, or the dependencies of a target, it places them in a Bazel-managed output directory. | |
| This can make them difficult to find manually, however, Bazel also provides a mechanism to query for built artifacts that is demonstrated below. | |
| Note, the `outquery-*` command shown below is a special command that is parsed via our `bazelisk.sh` script. | |
| Therefore, it cannot be run with a standalone `bazel ...` invocation. | |
| ## `opentitan_{rom,flash}_binary` Artifacts | |
| - Query the locations of all Bazel-built artifacts for all OpenTitan devices for an `opentitan_{rom,flash}_binary` macro: | |
| ```console | |
| ./bazelisk.sh outquery-all <target> | |
| ``` | |
| - Query the locations of all Bazel-built artifacts for a specific OpenTitan device for an `opentitan_{rom,flash}_binary` macro: | |
| ```console | |
| ./bazelisk.sh outquery-all <target>_<device> | |
| ``` | |
| Note: `<device>` will be in {`sim_dv`, `sim_verilator`, `fpga_cw310`}. | |
| See [Building (and Testing) Software]({{< relref "doc/getting_started/build_sw#device-artifacts" >}}), device software can be built for multiple OpenTitan devices and memories, using OpenTitan-specific Bazel macros. | |
| ## `opentitan_functest` Artifacts | |
| As described [Building (and Testing) Software]({{< relref "doc/getting_started/build_sw#device-artifacts" >}}), device software can be built for multiple OpenTitan devices and memories, using OpenTitan-specific Bazel macros. | |
| Since running tests on multiple OpenTitan devices (whether DV or Verilator simulation, or an FPGA) involves building several software images for multiple memories, we provide a Bazel macro for this. | |
| This macro is called `opentitan_functest`. | |
| - List all `sh_test` targets instantiated by a `opentitan_functest`, e.g. the UART smoketest: | |
| ```console | |
| bazel query 'labels(tests, //sw/device/tests:uart_smoketest)' | |
| ``` | |
| - Query the HW and SW dependencies of a specific `opentitan_functest` for the `fpga_cw310` device, e.g. the UART smoketest: | |
| ```console | |
| bazel query 'labels(data, //sw/device/tests:uart_smoketest_fpga_cw310)' | |
| ``` | |
| or for any `opentitan_functest` target and `<device>` in {`sim_dv`, `sim_verilator`, `fpga_cw310`} | |
| ```console | |
| bazel query 'labels(data, <target>_<device>)' | |
| ``` | |
| - Query the software artifacts built for the `opentitan_flash_binary` that is a dependency of an `opentitan_functest` for the `fpga_cw310` device, e.g. the UART smoketest: | |
| ```console | |
| bazel query 'labels(srcs, //sw/device/tests:uart_smoketest_prog_fpga_cw310)' | |
| ``` | |
| or for any `opentitan_functest` `<target>` and `<device>` in {`sim_dv`, `sim_verilator`, `fpga_cw310`} | |
| ```console | |
| bazel query 'labels(srcs, <target>_prog_<device>)' | |
| ``` | |
| Note: if an `opentitan_functest` target has the name `foo`, then the `opentitan_flash_binary` target that is instantiated by the `opentitan_functest` will be named `foo_prog_<device>`. | |
| # Building Software | |
| * To build OpenTitan software see [here]({{< relref "doc/getting_started/build_sw#building-software-with-bazel" >}}), or run | |
| ```console | |
| bazel build <target> | |
| ``` | |
| # Testing Software | |
| ## On-Host Tests | |
| * To query-for/run *on-host* software tests see [here]({{< relref "doc/getting_started/build_sw#running-tests-with-bazel" >}}). | |
| ## On-Device Tests | |
| All device software, regardless of the device, is built with Bazel. | |
| However, only Verilator simulation and FPGA device software tests can be run with Bazel. | |
| ### ROM Tests | |
| * Query for all ROM functional and E2E tests for FPGA: | |
| ```console | |
| bazel query 'filter(".*_fpga_cw310", kind(".*test rule", //sw/device/silicon_creator/...))' | |
| ``` | |
| and for Verilator: | |
| ```console | |
| bazel query 'filter(".*_sim_verilator", kind(".*test rule", //sw/device/silicon_creator/...))' | |
| ``` | |
| * Run all ROM functional and E2E tests on FPGA: | |
| ```console | |
| bazel test --test_tag_filters=cw310 //sw/device/silicon_creator/... | |
| ``` | |
| and for Verilator: | |
| ```console | |
| bazel test --test_tag_filters=verilator //sw/device/silicon_creator/... | |
| ``` | |
| * Run a single ROM functional or E2E test on FPGA and see the output in real time: | |
| ```console | |
| bazel test \ | |
| --define DISABLE_VERILATOR_BUILD=true \ | |
| --test_tag_filters=cw310 \ | |
| --test_output=streamed \ | |
| //sw/device/silicon_creator/lib:boot_data_functest | |
| ``` | |
| or, remove the define/filtering flags and just append the `<device>` name like: | |
| ```console | |
| bazel test --test_output=streamed //sw/device/silicon_creator/lib:boot_data_functest_fpga_cw310 | |
| ``` | |
| and similarly for Verilator: | |
| ```console | |
| bazel test --test_output=streamed //sw/device/silicon_creator/lib:boot_data_functest_sim_verilator | |
| ``` | |
| ### Chip-Level Tests | |
| * Query for all chip-level tests for FPGA: | |
| ```console | |
| bazel query 'filter(".*_fpga_cw310", kind(".*test rule", //sw/device/tests/...))' | |
| ``` | |
| and for Verilator: | |
| ```console | |
| bazel query 'filter(".*_sim_verilator", kind(".*test rule", //sw/device/tests/...))' | |
| ``` | |
| * Run all chip-level tests on FPGA: | |
| ```console | |
| bazel test --define DISABLE_VERILATOR_BUILD=true --test_tag_filters=cw310 //sw/device/tests/... | |
| ``` | |
| and for Verilator: | |
| ```console | |
| bazel test --test_tag_filters=verilator //sw/device/tests/... | |
| ``` | |
| * Run a single chip-level test on FPGA and see the output in real time: | |
| ```console | |
| bazel test \ | |
| --define DISABLE_VERILATOR_BUILD=true | |
| --test_tag_filters=cw310 \ | |
| --test_output=streamed \ | |
| //sw/device/tests:uart_smoketest | |
| ``` | |
| or, remove the define/filtering flags and just append the `<device>` name like: | |
| ```console | |
| bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310 | |
| ``` | |
| and similarly for Verilator: | |
| ```console | |
| bazel test --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator | |
| ``` | |
| # Linting Software | |
| There are several Bazel rules that enable running quality checks and fixers on code. | |
| The subsections below describe how to use them. | |
| All of the tools described below are run in CI on every pull request, so it is best to run them before committing code. | |
| ## Linting C/C++ Code | |
| The OpenTitan supported linter for C/C++ files is `clang-format`. | |
| It can be run with Bazel as shown below. | |
| Run the following to check if all C/C++ code as been formatted correctly: | |
| ```console | |
| bazel test //quality:clang_format_check --test_output=streamed | |
| ``` | |
| and run the following to fix it, if it is not formatted correctly. | |
| ```console | |
| bazel run //quality:clang_format_fix | |
| ``` | |
| ## Linting Starlark | |
| The OpenTitan supported linter for Bazel files is `buildifier`. | |
| It can be run with Bazel as shown below. | |
| Run the following to check if all `WORKSPACE`, `BUILD`, and `.bzl` files have been formatted correctly: | |
| ```console | |
| bazel test //quality:buildifier_check --test_output=streamed | |
| ``` | |
| and run the following to fix them, if they are not formatted correctly. | |
| ```console | |
| bazel run //quality:buildifier_fix | |
| ``` | |
| ## Checking License Headers | |
| Lastly, the OpenTitan supported linter for checking that every source code file contains a license header may be run with: | |
| ```console | |
| bazel run //quality:license_check --test_output=streamed | |
| ``` | |
| # Building Hardware | |
| Note, to run (software) tests on these OpenTitan hardware platforms **does not** require these Bazel commands be invoked before the test commands above. | |
| Bazel is aware of all dependency relationships, and knows what prerequisites to build to run a test. | |
| - Build FPGA bitstream with (test) ROM, see [here]({{< relref "doc/getting_started/setup_fpga#build-an-fpga-bitstream" >}}). | |
| - Build FPGA bitstream with (production) ROM, see [here]({{< relref "doc/getting_started/setup_fpga#build-an-fpga-bitstream" >}}). | |
| - Build Verilator simulation binary: | |
| ```console | |
| bazel build //hw:verilator | |
| ``` | |
| # Miscellaneous | |
| ## Troubleshooting Builds | |
| If you encounter an unexplained error building or running any `bazel` commands, you can issue a subsequent `bazel clean` command to erase any existing building directories to yield a clean build. | |
| Specifically, according to the Bazel [documentation](https://docs.bazel.build/versions/main/user-manual.html#clean), issuing a | |
| ```console | |
| bazel clean | |
| ``` | |
| deletes all the output build directories, while running a | |
| ```console | |
| bazel clean --expunge | |
| ``` | |
| will wipe all disk and memory traces (i.e., any cached intermediate files) produced by Bazel. | |
| The latter sledgehammer is only intended to be used as a last resort when the existing configuration is seriously broken. | |
| ## Create a `.bazelrc` File | |
| Create a `.bazelrc` file in your home directory to simplify executing bazel commands. | |
| For example, you can use a `.bazelrc` to: | |
| * set up a [disk cache]({{< relref "#disk-cache" >}}), or | |
| * skip running tests on the CW310 FPGA if you don not have one. | |
| A `.bazelrc` file that would accomplish this would look like: | |
| ``` | |
| # Make Bazel use a local directory as a remote cache. | |
| build --disk_cache=~/bazel_cache | |
| # Skip CW310 FPGA tests, since I do not have said FPGA. | |
| test --test_tag_filters=-cw310 | |
| ``` | |
| See the [`.bazelrc`](https://github.com/lowRISC/opentitan/blob/master/.bazelrc) file in the OpenTitan repository for more examples. | |
| Additionally, for more information see the Bazel [documentation](https://bazel.build/run/bazelrc). | |
| ### Site-specific `.bazelrc-site` options | |
| We use the term "compute site" to refer to a particular development host, or more broadly a _collection_ of compute nodes, that is operated by an organization with its own unique compute requirements or IT policies. | |
| The experience of working in such an environment may be different than using an off-the-shelf Linux distribution, and so team members working on some compute sites may require the use of slightly different Bazel options. | |
| In addition to each user's `$HOME/.bazelrc` file and the project-level configurations in [`$REPO_TOP/.bazelrc`](https://github.com/lowRISC/opentitan/blob/master/.bazelrc), there is the option to add site-specific configuration options, by adding them to the file `$REPO_TOP/.bazelrc-site`. | |
| The `.bazelrc-site` file can be useful for enforcing site-specific policies such as: | |
| - Default locations for build artifacts (e.g., new defaults for `--output_user_root` or perhaps `--output_base` options, outside the `$HOME` filesystem). | |
| - Site-specific, non-standard paths for build-tools, libraries, or package configuration data. | |
| Putting Bazel options in this file adds another layer of configuration to help groups of individuals working on a common compute site share the same site-specific default options. | |
| At a more fine-grained level, individual users can still place options in `$HOME/.bazelrc`, potentially overriding the options used in `.bazelrc-site` and `$REPO_TOP/.bazelrc`. | |
| However the "$HOME/.bazelrc" file is not specific to OpenTitan, which could create confusion for users working on a number of projects. | |
| The policies and paths for each contributing site could vary greatly. | |
| So in order to avoid accidental conflicts between sites, the `.bazelrc-site` file is explicitly _not_ included in the git repository and OpenTitan CI scripts will reject any commit that includes a `.bazelrc-site` file. | |
| ## Disk Cache | |
| Bazel can use a directory on the file system as a remote cache. | |
| This is useful for sharing build artifacts across multiple [`git` worktrees](https://git-scm.com/docs/git-worktree) or multiple workspaces of the same project, such as multiple checkouts. | |
| Use the `--disk_cache=<filename>` to specify a cache directory. | |
| For example, running | |
| ```console | |
| bazel build //... --disk_cache=~/.cache/bazel-disk-cache | |
| ``` | |
| will cache all built artifacts. | |
| Alternatively add the following to `$HOME/.bazelrc` to avoid having automatically use the disk cache on every Bazel invocation, as shown [above]({{< relref "#create-a-bazelrc-file" >}}). | |
| Note that Bazel does not perform any garbage collection on the disk cache. | |
| To clean out the disk cache, you can set a cron job to periodically delete all files that have not been accessed for a certain amount of time. | |
| For example add the following line with the path to your disk cache to your crontab (using `crontab -e`) to delete all files that were last accessed over 60 days ago. | |
| ```console | |
| 0 0 * * 0 /usr/bin/find /path/to/disk/cache -type f -atime +60 -delete | |
| ``` | |
| For more documentation on Bazel disk caches see the [official documentation](https://docs.bazel.build/versions/main/remote-caching.html#disk-cache). | |
| ## Excluding Verilator Simulation Binary Builds | |
| Many device software targets depend on the Verilator simulation binary, | |
| The Verilator simulation binary is slow to build. | |
| To avoid building it, use the | |
| `--define DISABLE_VERILATOR_BUILD=true` build option. | |
| For example, to build the UART smoke test artifacts but not the Verilator simulation binary run | |
| ```console | |
| bazel build --define DISABLE_VERILATOR_BUILD=true //sw/device/tests:uart_smoketest | |
| ``` | |
| ## Displaying Test Outputs in Real Time | |
| By default, Bazel does not write test outputs to STDOUT. | |
| To see a test's output in real time, use the `--test_output=streamed` flag, like: | |
| ```console | |
| bazel test --test_output=streamed //sw/device/tests:uart_smoketest_fpga_cw310 | |
| ``` | |
| ## Filtering Broken Tests | |
| Some tests are marked known to be broken (and are in the process of being triaged). | |
| To prevent running these tests when running a block of tests, use the `test_tag_filters=-broken` flag. | |
| For example, to run all chip-level tests except the broken ones on FPGA: | |
| ```console | |
| bazel test --test_tag_filters=cw310,-broken //sw/device/tests/... | |
| ``` | |
| ## Using Bazel with Git Worktrees | |
| Bazel was not optimized for the `git` worktree workflow, but using worktrees can help with branch management and provides the advantage of being able to run multiple Bazel jobs simultaneously. | |
| Here are some tips that can improve the developer experience when using worktrees. | |
| 1. Follow the [instructions above]({{< relref "#disk-cache" >}}) to enable the disk cache. | |
| Bazel uses the workspace's path when caching actions. | |
| Because each worktree is a separate workspace at a different path, different worktrees cannot share an action cache. | |
| They can, however, share a disk cache, which helps avoid rebuilding the same artifacts across different worktrees. | |
| Note that the repository cache is shared by default across all workspaces, so no additional configuration is needed there. | |
| 1. Before deleting a worktree, be sure to run `bazel clean --expunge` to remove Bazel's generated files. | |
| Otherwise, files from old worktrees can accumulate in your output user root (located at `~/.cache/bazel/_bazel_${USER}/` by default). | |
| ## Commonly Encountered Issues | |
| ### Cannot find ld | |
| One issue encountered while using bazel is the following error message when attempting to build: | |
| ```console | |
| = note: collect2: fatal error: cannot find 'ld' | |
| compilation terminated. | |
| ``` | |
| The reason this occurs is related to these issues: | |
| * [opentitan issue](https://github.com/lowRISC/opentitan/issues/12448) | |
| * [rust issue](https://github.com/bazelbuild/rules_rust/issues/1114) | |
| Specifically, when the rustc compiler is invoked, it uses the LLVM linker that is not managed by the bazel flow. | |
| This means bazel cannot ensure it is installed at a specific location, and instead just uses whatever is available on the host machine. | |
| The issue above points out that rustc expects the linker to be located in the same directory as `gcc`, so if on the host machine this statement is untrue, there can be build issues. | |
| To resolve this problem: | |
| 1. Install the LLVM linker with, e.g., `sudo apt install lld`. | |
| 2. Symlink `lld` to where `gcc` is installed. | |
| This workaround will be needed until the rust issue is resolved. |