title: Building (and Testing) Software aliases: - /doc/ug/getting_started_build_sw

Before following this guide, make sure you have read the:

  • main [Getting Started]({{< relref “getting_started” >}}) instructions,
  • install Verilator section of the [Verilator guide]({{< relref “setup_verilator.md” >}}), and
  • [OpenTitan Software]({{< relref “sw/” >}}) documentation.

All OpenTitan software is built with Bazel. Additionally, most tests may be run with Bazel too.

TL;DR

To install the correct version of bazel, build, and run all on-host tests you can simply run:

$REPO_TOP/bazelisk.sh test //... --test_tag_filters=-cw310,-verilator --disk_cache=~/bazel_cache

Installing Bazel

There are two ways to install the correct version of Bazel:

  1. automatically, using the bazelisk.sh script provided in the repo, or
  2. manually.

Automatic Installation

To simplify the installation of Bazel, and provide a means to seamlessly update the Bazel version we use in the future, we provide a shell script that acts as a wrapper for invocations of “bazel ...”. To use it, you two options:

  1. use “./bazelisk.sh ...” instead of “bazel ...” to invoke of Bazel subcommands, or
  2. set the following alias (e.g., in your .bashrc file) to accomplish the same:
alias bazel="$REPO_TOP/bazelisk.sh"

Manual Installation

This section is optional and can be skipped if you completed the instructions above in Automatic Installation.

While the automatic installation is convenient, by installing Bazel directly, you can get some quality of life features like tab completion. If you haven't yet installed Bazel, and would like to, you can add it to apt and install it on Ubuntu systems with the following commands as described in the Bazel documentation:

sudo apt install apt-transport-https curl gnupg
curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor > bazel.gpg
sudo mv bazel.gpg /etc/apt/trusted.gpg.d/
echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" |
sudo tee /etc/apt/sources.list.d/bazel.list
sudo apt update && sudo apt install bazel-5.1.1
sudo ln -s /usr/bin/bazel-5.1.1 /usr/bin/bazel

or by following instructions for your system.

Building Software with Bazel

Running

bazel build //sw/...

will build all software in our repository. If you do not have Verilator installed yet, you can use the --define DISABLE_VERILATOR_BUILD=true flag to skip the jobs that depend on that.

In general, you can build any software target (and all of it's dependencies) using the following syntax:

bazel build @<repository>//<package>:<target>

Since most targets are within the main Bazel repository (lowrisc_opentitan), you can often drop the “@<repository>” prefix. For example, to build the boot ROM we use for testing (also referred to as the test ROM), you can use

bazel build //sw/device/lib/testing/test_rom:test_rom

Additionally, some Bazel syntactic sugar enables dropping the target name when the target name matches the last subcomponent of the package name. For example, the following is equivalent to the above

bazel build //sw/device/lib/testing/test_rom

For more information on Bazel repositories, packages, and targets, please refer to the Bazel documentation.

Running Tests with Bazel

In addition to building software, Bazel is also used to build and run tests. There are two categories of OpenTitan tests Bazel can build and run:

  1. on-host, and
  2. on-device.

On-host tests are compiled and run on the host machine, while on-device tests are compiled and run on (simulated/emulated) OpenTitan hardware.

Examples of on-host tests are:

  • unit tests for device software, such as [DIF]({{< relref “/sw/device/lib/dif” >}}) and [ROM]({{< relref “/sw/device/silicon_creator/rom/docs/” >}}) unit tests.
  • any test for host software, such as opentitan{lib,tool}.

Examples of on-device tests are:

  • [chip-level tests]({{< relref “/sw/device/tests/index.md” >}}).
  • [ROM functional tests]({{< relref “/sw/device/silicon_creator/rom/docs/” >}})

The remainder of this document will focus on building and running on-host tests with Bazel. To learn about running on-device tests with Bazel, please continue back to the main [Getting Started]({{< relref “getting_started” >}}) instructions, and proceed with the [Verilator]({{< relref “setup_verilator” >}}) and/or [FPGA]({{< relref “setup_fpga” >}}) setup instructions.

Running on-host DIF Tests

The Device Interface Function or [DIF]({{< relref “/sw/device/lib/dif” >}}) libraries contain unit tests that run on the host and are built and run with Bazel. As shown below, you may use Bazel to query which tests are available, build and run all tests, or build and run only one test.

Querying which tests are available

bazel query 'tests(//sw/device/lib/dif:all)'

Building and running all tests

bazel test //sw/device/lib/dif:all

Building and running a single test

For example, building and testing the UART DIF library's unit tests:

bazel test //sw/device/lib/dif:uart_unittest

Running on-host ROM Tests

Similar to the DIF libraries, you can query, build, and run all the [ROM]({{< relref “/sw/device/silicon_creator/rom/docs/” >}}) unit tests (which also run on the host) with Bazel.

Querying which (on-host) tests are available

Note, the ROM has both on-host and on-device tests. This query filters tests by their kind, i.e., only on-host tests.

bazel query 'kind(cc_.*, tests(//sw/device/silicon_creator/lib/...))'

Building and running all (on-host) tests

bazel test --test_tag_filters=-cw310,-dv,-verilator //sw/device/silicon_creator/lib/...

Building and running a single (on-host) test

For example, building and testing the ROM UART driver unit tests:

bazel test //sw/device/silicon_creator/lib/drivers:uart_unittest

Miscellaneous

Bazel-built Software Artifacts

As described in the [OpenTitan Software]({{< relref “sw/” >}}) documentation, there are three categories of OpenTitan software, all of which are built with Bazel. These include:

  1. device software,
  2. OTBN software,
  3. host software,

Bazel produces various artifacts depending on the category of software that is built.

Device Artifacts

Device software is built and run on OpenTitan hardware. There are three OpenTitan “devices” for simulating/emulating OpenTitan hardware:

  1. DV simulation (i.e., RTL simulation with commercial simulators),
  2. Verilator simulation (i.e., RTL simulation with the open source Verilator simulator),
  3. FPGA.

Additionally, for each device, there are two types of software images that can be built, depending on the memory type the software is destined for, i.e.:

  1. ROM,
  2. flash,

To facilitate instantiating all build rules required to build the same artifacts across several devices and memories, we implement two OpenTitan-specific Bazel macros. These macros include:

  • opentitan_rom_binary
  • opentitan_flash_binary

Both macros instantiate build rules to produce software artifacts for each OpenTitan device above. Specifically, building either an opentitan_rom_binary or opentitan_flash_binary named <target>, destined to run on the OpenTitan device <device>, will output the following files under bazel-out/:

  • <target>_<device>.elf: the linked program, in ELF format.
  • <target>_<device>.bin: the linked program, as a plain binary with ELF debug information removed.
  • <target>_<device>.dis: the disassembled program with inline source code.
  • <target>_<device>.logs.txt: a textual database of addresses where LOG_* macros are invoked (for DV backdoor logging interface).
  • <target>_<device>.rodata.txt: same as above, but contains the strings that are logged.
  • <target>_<device>.*.vmem: a Verilog memory file which can be read by $readmemh() in Verilog code. Note, <device> will be in {sim_dv, sim_verilator, fpga_cw310}.

Additionally, if the opentitan_flash_binary is signed, then these files will also be under bazel-out/:

  • <target>_<device>.<signing key name>.signed.bin: the same .bin file above, but with a valid signature field in the manifest.
  • <target>_<device>.<signing key name>.signed.*.vmem: the same *.vmem file above, but with a valid signature field in the manifest.

OTBN Artifacts

OTBN programs use a specialized build flow (defined in rules/otbn.bzl). OTBN programs produce the following artifacts:

  • <target>.o: unlinked object file usually representing a single assembly file
  • <target>.elf: standalone executable binary representing one or more assembly/object files
  • <target>.rv32embed.{a,o}: artifacts representing an OTBN binary, set up to be linked into a RISC-V program

In terms of Bazel rules:

  • the otbn_library rule runs the assembler to create <target>.o artifacts, and
  • the otbn_binary and otbn_sim_test rules run the linker on one or more .o files to create the .elf and .rv32embed.{a,o} artifacts.

Since OTBN has limited instruction memory, the best practice is to list each file individually as an otbn_library. This way, binary targets can easily include only the files they need.

OTBN programs run on the OTBN coprocessor, unlike standard “on-device” programs that run on the main processor (Ibex). There are two ways to run an OTBN program:

  1. Run a standalone binary (.elf) on the specialized OTBN simulator.
  2. Include a .rv32embed artifact in a C program that runs on Ibex, and create an on-device target as described in the previous section.

You can run .elf artifacts directly using the simulator as described in the OTBN README. The otbn_sim_test rule is a thin wrapper around otbn_binary. If you use it, bazel test will run the OTBN simulator for you and check the test result.

To include an OTBN program in a C program, you need to add the desired OTBN otbn_binary Bazel target to the deps list of the C program's Bazel target. No #include is necessary, but you will likely need to initialize the symbols from the OTBN program as required by the OTBN driver you are using.

Host Artifacts

Host software is built and run on the host hardware (e.g., an x64 Linux machine). A final linked program in the ELF format is all that is produced for host software builds. Note, the file will not have an extension.

Disassembling Device Code

A disassembly of all executable sections is produced by the build system by default. It can be found by looking for files with the .dis extension next to the corresponding ELF file.

./bazelisk.sh build //sw/device/tests:uart_smoketest_prog_sim_verilator_dis

less "$(./bazelisk.sh outquery //sw/device/tests:uart_smoketest_prog_sim_verilator_dis)"

To get a different type of disassembly, e.g. one which includes data sections in addition to executable sections, objdump can be called manually. For example the following command shows how to disassemble all sections of the UART DIF smoke test interleaved with the actual source code:

./bazelisk.sh build --config=riscv32 //sw/device/tests:uart_smoketest_prog_sim_verilator.elf

riscv32-unknown-elf-objdump --disassemble-all --headers --line-numbers --source \
  "$(./bazelisk.sh outquery --config=riscv32 //sw/device/tests:uart_smoketest_prog_sim_verilator.elf)"

Refer to the output of riscv32-unknown-elf-objdump --help for a full list of options.