Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Development environment

Before you can build a chip you need the tools that turn its source into something runnable. Pavona’s toolchain is larger than a typical firmware project’s because it spans two worlds at once, hardware and software…and the same repository builds both. Understanding what each tool does makes the setup feel less like wizardry and more like a normal down-to-earth toolbox.

At the center is Bazel, invoked through a wrapper script called bazelisk.sh that fetches the exact Bazel version the project expects. Bazel is the single entry point for almost everything. It builds the RISC-V software that runs on the chip, and it also drives the hardware build. When you ask Bazel for a sim_verilator target, it runs fusesoc to gather the RTL, invokes Verilator to generate and compile the C++ model, builds the device software, and finally launches opentitantool to load the memory images and stream the chip’s output back to your terminal. You rarely call Verilator, fusesoc, or the opentitantool yourself; Bazel orchestrates them, which is why nearly every command in this part starts with ./bazelisk.sh.

Note

Bazel is like make…but on steroids. You name a target and it works out what to rebuild just like make, except Bazel also goes and fetches its own tools (Verilator included), runs every step sandboxed so the result does not depend on what is lying around your machine, and caches test results and not just build outputs.

In the container we’re going to use, Verilator is built from source rather than installed from a package, because distribution packages tend to lag badly behind. Pavona pins the version in third_party/verilator/extensions.bzl (5.046 at the time of writing) and Bazel compiles it for you the first time you build a simulation target. That compile is the bulk of the first ten-minute wait, and it only happens once as long as Bazel’s cache survives, which is why the container instructions below bind-mount a directory for it. Second, the Python tooling (topgen, regtool, dvsim, and the scripts that generate register files and documentation) runs in a project-specific virtual environment, installed from python-requirements.txt with version/hash pinning, so its dependencies never collide with any system Python packages in the container.

There are two options to get your dev environment set up. Pavona’s getting started guide documents a native one running on a Ubuntu host. It works well if you already run a supported Ubuntu version. The container path below is the simpler option and works more broadly, thus it is what the rest of this book assumes.

Containerized build with Podman or Docker

Pavona provides a container definition at util/container/Dockerfile, built on Ubuntu 22.04, that installs every system dependency the getting started guide lists. Your locally checked out version of the Pavona repo stays on the host and is bind-mounted in, so your edits and your git history live in one place and outlive any container you recycle.

Bazel’s build cache does not, unless you mount a second directory for it. By default Bazel caches under ~/.cache/bazel, and inside the container that resolves to /home/dev/.cache/bazel, which is not part of the bind mount above. See the command below for instructions on how to mount this.

We use Podman here, but every invocation below is identical under Docker (substitute docker for podman, drop the --userns and --user flags explained below since Docker does not need them, and note that Pavona’s own instructions run docker build under sudo).

Build the image once from the top of your Pavona checkout:

podman build -t pavona -f util/container/Dockerfile .

Then start an interactive shell in it, mapping your checkout to /home/dev/src and a host directory to /home/dev/.cache so Bazel’s cache survives a recycled container. The DEV_UID and DEV_GID variables give the container’s dev user your own user and group IDs, so files it creates land back on the host owned by you rather than by root:

mkdir -p ~/.cache/pavona-bazel
podman run -it \
  --userns=keep-id \
  --user root:root \
  -v $(pwd):/home/dev/src \
  -v ~/.cache/pavona-bazel:/home/dev/.cache \
  --env DEV_UID=$(id -u) --env DEV_GID=$(id -g) \
  pavona:latest \
  bash

Note

Rootless Podman gives every container its own private user namespace, so by default a process the container thinks is UID 1000 is not the same as UID 1000 on your host, it is remapped. Using --userns=keep-id fixes that by mapping your real host UID into the container as itself, but it also makes the container start as that UID instead of root, which breaks the DEV_UID/DEV_GID remap the entrypoint script needs to run as root. --user root:root puts root back for the entrypoint.

Ready

You can now open a shell inside the container and run the Bazel wrapper:

cd ~/src && ./bazelisk.sh

With no arguments it fetches the pinned Bazel release and prints Bazel’s usage message. This is enough proof that the wrapper works and the workspace is is good to go. Onwards!