Bazelizing Third-Party FPGA Designs: Two Case Studies

#FPGA#bazel#openxc7#Vivado#RISC-V#Verilog#tutorial#auto

Converting existing open-source FPGA projects to Bazel and rules_openxc7 makes hardware builds reproducible, eliminates machine-specific paths, and enables one-command bitstream generation without vendor licensing hurdles. This article examines two real conversions of external repositories: mmRISC-2, a silicon-proven RISC-V SoC with heavy SystemVerilog hierarchies, and Custom RISC CPU with Systolic Matrix Accelerator, a pipelined processor paired with a 2D systolic array coprocessor. Both examples illustrate how Bazel transforms fragile build scripts into reliable, sandboxed workflows.

The problem with traditional FPGA repositories

Most FPGA repositories published online share several recurring maintenance challenges:

  1. Vendor GUI project files: Repositories often check in proprietary IDE project files (such as Vivado .xpr files) that change on every save and do not track cleanly across tool versions.
  2. Machine-specific hardcoded paths: Simulation models and memory loaders often hardcode absolute paths pointing to private home directories on the author’s original workstation.
  3. Implicit host dependencies: Build instructions often require the reader to install dozens of gigabytes of vendor software or manually compile open-source EDA tools from source.
  4. Poor automation ergonomics: Running a build requires manual GUI clicking or invoking non-deterministic shell scripts that fail when run inside headless CI pipelines.

Bazel solves these friction points by providing sandboxed execution, explicit input tracking, and hermetic toolchains. With rules_openxc7, Bazel downloads prebuilt binaries for Yosys, nextpnr-xilinx, Project X-Ray, and openFPGALoader. Anyone with Bazel installed can clone a repository and produce a verified bitstream with one terminal command.

For an extensive architectural guide to authoring hardware rules in Bazel, consult the book Hardware Development with Bazel.

Case study 1: mmRISC-2 (Munetomo Maruyama)

The mmRISC-2 repository by Munetomo Maruyama is a complete, silicon-proven RISC-V system-on-chip. It features a pipelined CPU implementing RV32IMAC or RV64GC instruction sets, an AXI4 bus infrastructure, cache controllers, an integrated debug module, and a collection of peripheral interfaces.

Converting mmRISC-2 presented two engineering hurdles:

  • Large-scale SystemVerilog code: The design spans dozens of interconnected source files and package definitions with complex parameterization and forward references.
  • Target device support: The target evaluation board was the Digilent Arty A7-100T, which uses the Xilinx Artix-7 100T FPGA (xc7a100tcsg324-1).

What Bazel and rules_openxc7 accomplished

  1. Upstream toolchain enhancement: To support the Arty A7-100T board, we extended rules_openxc7 with definitions for the xc7a100tcsg324-1 part in rules_openxc7 PR #14.
  2. Yosys Slang frontend integration: Traditional Yosys Verilog frontends struggle with complex SystemVerilog package hierarchies. We configured the synthesis target to use the Slang plugin frontend with flags --allow-use-before-declare and --unroll-limit 100000.
  3. Clean boundary interfaces: In RTL/BUILD.bazel, all source files, include headers, and Xilinx primitive blackboxes (such as BUFG and MMCME2_BASE) were declared with fine-grained dependencies.
  4. Successful bitstream generation: Yosys synthesized 39,010 logic cells, and nextpnr-xilinx placed and routed the design with zero timing or routing errors, outputting a valid top_pnr.bit file.

The complete conversion is available in mmRISC-2 PR #1. Developers can now build and flash the SoC onto an Arty A7-100T board with:

# Synthesize the entire SoC
bazel build //:top_synth

# Place and route, generating top_pnr.bit
bazel build //:top_pnr

# Program the attached Arty A7 board via openFPGALoader
bazel run //:top_prog

Case study 2: Custom RISC CPU with Systolic Array (Mathew George)

The Custom RISC CPU with Systolic Matrix Accelerator by Mathew George couples a custom pipelined RISC core with a 2D 4x4 (16 Processing Element) systolic array coprocessor designed for matrix multiplication and 2D image filtering.

This project presented a classic hardware portability problem:

  • Hardcoded workstation paths: In instruction_memory.v and data_memory.v, the memory arrays were initialized using $readmemh calls pointing to absolute paths on the original developer’s computer: /home/mathew/Pixel_Processor_SystolicArray/program.hex and .../image_data2.hex.
  • Missing fixtures: Neither .hex file was included in the upstream git tree, causing synthesis and simulation to fail immediately on any other system.

What Bazel and rules_openxc7 accomplished

  1. Verilog parameterization: Instead of hardcoding paths, we refactored the memory modules to accept a parameter:

    module instruction_memory #(
        parameter MEM_FILE = ""
    ) (
        input  wire [31:0] address,
        output reg  [31:0] instruction
    );
        reg [31:0] memory [0:1023];
    
        initial begin
            if (MEM_FILE != "") begin
                $readmemh(MEM_FILE, memory);
            end
        end
        ...
    
  2. Decoupled data dependencies: We checked in clean, minimal test fixture files (fixtures/program.hex and fixtures/image_data2.hex). In Bazel, firmware files are passed as explicit data = [...] dependencies. Users can swap application firmware without modifying the underlying Verilog source code.

  3. Board integration wrapper: We added a top-level wrapper (arty_top.v) and pin constraints (arty_a7_100t.xdc) mapping CPU stall flags, coprocessor execution states, and RAM writes to user LEDs on the Arty A7-100T.

  4. High-speed execution: In the Bazel sandbox, the complete build from empty cache to finished bitstream takes only 14 seconds.

The full pull request is documented in Custom-RISC PR #1.

# Generate bitstream in 14 seconds
bazel build //:top_pnr

Why Bazel matters for hardware design

The lessons from these two conversions apply to both human hardware teams and autonomous coding agents:

  1. Hermeticity eliminates workstation divergence: When builds run in sandboxes with declared inputs and outputs, “works on my machine” bugs vanish.
  2. Open-source EDA becomes accessible: Requiring contributors to install proprietary vendor tools creates a high barrier to entry. Bazel automates the retrieval of open-source tools without manual compilation.
  3. Fast regression cycles: Action caching ensures that unchanged Verilog modules, constraint files, and test benches are never re-evaluated.
  4. Agent and CI ready: Headless, deterministic tools enable automated agents to verify pull requests, run formal proofs, and generate bitstreams without human intervention.

To learn how to structure your own hardware workspaces and author custom EDA rules, read Hardware Development with Bazel.