grlib: Gaisler’s GRLIB and the NOEL-V Core as a Bazel Module

#bazel#FPGA#RISC-V#GRLIB#NOEL-V#Cocoapuffs#auto

The grlib Bazel module packages GRLIB, Gaisler’s (Frontgrade’s) GPL VHDL IP library, so that the NOEL-V RV64 RISC-V core and the AMBA infrastructure around it can be consumed as ordinary Bazel dependencies. It is the module that puts the CPU into Cocoapuffs, the SoC that boots Fuchsia’s Zircon kernel on an Artix-7 FPGA. It is also the module where I learned the most about what it costs to move a large, make-era VHDL codebase into a modern build system, which is what this post is mostly about. It follows rules_vivado in my series on the modules behind cocoapuffs-fpga.

A library from a different build era

GRLIB is distributed as a large tree of VHDL organized into libraries (grlib, gaisler, techmap, …) and driven by a make-based flow. That flow encodes two decades of institutional knowledge, and two of its habits fight a hermetic build system directly.

The first habit: configuration through generated config.vhd files full of global CFG_* constants. Within Gaisler’s flow this works fine; one design, one config. But it does not compose. Two designs configured differently collide on the same global names, so you cannot build both from one workspace, and nothing about the configuration is visible to the build system as a dependency.

The second habit: compile order as tribal knowledge. VHDL requires packages to be compiled before their users, and the make flow computes a workable order for you. Bazel has no such global pass; every ordering constraint has to exist as an explicit dependency edge. Teasing the real package dependency graph out of the tree, and fixing the places where the generated build files got it wrong, was a long grind of elaboration failures, one missing edge at a time. The build files for the tree are generated by a maintained script rather than written by hand, because upstream GRLIB releases keep coming and the extraction has to be repeatable.

The module in three layers

  • MODULE.bazel downloads the exact upstream GPL release (grlib-gpl-2025.2-b4298) from Gaisler’s server, pinned by integrity hash, and re-exposes every VHDL source through the @grlib_srcs repository as tool-agnostic filegroup targets. (The module is named grlib and the sources repository grlib_srcs; the first attempt used the same name for both, and Bazel disagreed.)
  • Aggregate targets @grlib//:grlib, :gaisler, :techmap, and :NOELV_RV64 group the standard libraries and the NOEL-V core bundle.
  • A Kconfig layer (built on rules_kconfig) namespaces the configuration. Every symbol becomes a build setting prefixed by its GRLIB path, so IU_NWINDOWS from lib/gaisler/leon3/leon3.in turns into @grlib_config//:CONFIG_LIB_GAISLER_LEON3_LEON3_IU_NWINDOWS, and one flag (CONFIG_ACTIVE_DESIGN_PREFIX) promotes a chosen design’s configuration back to the generic CFG_* names the VHDL expects. Different designs stop stepping on one another, and a configuration change is now an input Bazel can see, cache against, and invalidate on.

Making 1993-era VHDL serve 2019 tools

GRLIB targets the common denominator of commercial simulators, which in practice means conservative VHDL. I wanted the same sources usable under modern standards, so the module has a --@grlib//:vhdl_standard flag covering 1987 through 2019. Getting the tree to actually compile under the newer standards took version-specific file swapping and narrow, range-based patches to the handful of files where the standards disagree; a blunt tree-wide patch breaks on every upstream release, and the surgical version survives them.

The other stress test was the simulator itself. The NOEL-V simulation in the module runs under NVC, and GRLIB’s heavy generic-and-record VHDL found real elaboration limits there: at one point noelvsys would not elaborate until the module folded some MSB-selection wrappers in cctrl5nv, and for a while the module shipped a workaround patch against the NVC rules that a later pinned NVC release made unnecessary. Packaging an IP library turns out to include being its simulators’ test suite.

In cocoapuffs-fpga

The SoC’s Vivado libraries pull NOEL-V core files (cpucorenv.vhd, cctrl5nv.vhd, and friends), the ACLINT, APLIC, JTAG, and misc peripherals directly from the fetched archive, while the repo vendors a thin noelvsys wrapper of its own. The //:grlib, //:gaisler, //:techmap, and //:NOELV_RV64 aliases show up in about forty BUILD files, from the board design in boards/noelv/ down to the interrupt controller in ip/plic/. The same sources feed Vivado for synthesis and NVC for simulation, which is the point of keeping the filegroups tool-agnostic.

Prior art and references

  • GRLIB itself, and the NOEL-V processor page; the module downloads from Gaisler’s release server.
  • FuseSoC is the non-Bazel approach to packaging exactly this kind of IP library (rules_fusesoc, next in this series, wraps it).
  • bazel_rules_hdl is the broader Bazel-for-hardware ecosystem this slots into.

The source is at filmil/bazel_grlib, published through my Bazel registry. For what all this wiring is for, see the Cocoapuffs post.