rules_fusesoc: FuseSoC Cores as Bazel Dependencies
rules_fusesoc integrates FuseSoC, the FPGA core package manager, into
hermetic Bazel builds.
Users declare desired core libraries and IP dependencies in Bazel build files.
The module resolves dependencies and exports Verilog and VHDL filesets as
standard Bazel filegroup targets for synthesis and simulation.
This post, part of the series on the build infrastructure behind
cocoapuffs-fpga, examines the division of responsibility between
package managers and build systems.
What a .core file hides
The open-source FPGA ecosystem maintains a substantial library of reusable IP
packaged as FuseSoC .core manifests.
At first glance, a .core file appears to be a static list of source files.
Parsing the manifest directly with custom scripts seems straightforward, but
that shortcut fails quickly.
A .core file defines conditional filesets per target and EDA tool.
It declares dependencies using version-ranged tuples
(vendor:library:name:version) requiring semantic resolution.
Crucially, cores often invoke dynamic generators: Python scripts and Mako
templates that synthesize register-transfer logic during environment setup.
The underlying source files do not exist until FuseSoC executes its generator
phase.
FuseSoC must therefore run to materialize sources. However, standard FuseSoC workflows download libraries from remote networks and rely on host Python interpreters. Neither behavior is acceptable in a hermetic build environment.
Drawing the boundary
rules_fusesoc runs the FuseSoC setup phase within isolated build actions with
pinned inputs:
- FuseSoC and its Python runtime dependencies are vendored and locked via pip. Bazel provides the pinned hermetic Python interpreter, eliminating host dependencies. Mako is bundled in vendored packages so generator scripts execute cleanly.
- A Bazel module extension,
fusesoc_cores, declares required inputs. Thefusesoc_cores.library(name, url, patches)rule registers core libraries as Git or HTTP archives fetched and verified by Bazel. Optional patches apply during repository extraction. Thefusesoc_cores.core(name, tool, target)rule materializes a specific core for a designated target. - The extension produces the
@fusesoc_coresexternal repository. Resolved cores export..._srcsand..._hdrsfilegroups that integrate directly into targetdeps. Verilator lint waivers (.vltfiles) emit into isolated filegroups to prevent unsupported tools from choking on waiver syntax.
The patches attribute allows modifying upstream manifests at fetch time.
Our build uses a patched fork of Muntjac rather than upstream sources.
Patching external repositories during fetch avoids maintaining long-lived
vendor forks.
Integrating foreign package managers involves resolving diverse corner cases. Package managers develop idiosyncratic behaviors over time, and hermetic wrappers must account for each edge case.
Selection criteria
Not every IP block justifies wrapping with FuseSoC.
The SERV bit-serial RISC-V core, used as the Cocoapuffs serial boot
loader, bypasses FuseSoC entirely.
Because SERV consists of a small set of static Verilog files without generators
or external dependencies, a standard git_repository rule with a handwritten
BUILD.bazel file is simpler to maintain.
FuseSoC is beneficial for complex, multi-file cores requiring dynamic dependency resolution. For small leaf cores with static filesets, the additional abstraction layer is unnecessary.
In cocoapuffs-fpga
In MODULE.bazel, our project registers the upstream fusesoc-cores
catalog alongside our patched Muntjac repository.
It instantiates several cores: blinky, uart16550, and the LowRISC Muntjac
processor for both Vivado and Verilator targets.
Downstream rules consume the generated uart16550 sources via standard labels
such as @fusesoc_cores//build/uart16550_...:..._srcs, identical in syntax to
handwritten filegroups.
Prior art and references
- FuseSoC provides the core packaging model, and Edalize provides the backend abstraction layer across EDA simulators and synthesis tools.
- The upstream fusesoc-cores repository provides standard open-source IP.
- bazel_rules_hdl represents another notable effort to manage HDL toolchains under Bazel.
Earlier posts in this series covered rules_vivado and grlib.
For full architecture context, consult the Cocoapuffs overview and the
inventory of programmable hardware tooling.
rules_fusesoc is published in my Bazel registry, with source code at
filmil/bazel_rules_fusesoc_2.