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 likemake, 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-idfixes 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 theDEV_UID/DEV_GIDremap the entrypoint script needs to run as root.--user root:rootputs 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!