Renode Bazel rules for advanced co-simulation scenarios using Verilator as an example
Published:
Topics: Open source tools, Open simulation
The open source Bazel build system provides hermetic build environments, facilitating reproducible, controllable builds, with local and distributed cache. This approach significantly accelerates the build process and provides flexibility and extensibility across a wide range of projects, regardless of the codebase size. Bazel has become especially popular among hyperscalers such as Google who leverage their scale and existing infrastructure to build multiple targets in parallel and cache the artifacts to speed up future builds.
Antmicro has been using Bazel in many projects, including those involving large ASIC and FPGA designs that benefit the most from Bazel’s reusable rules and reproducible builds, such as Google’s XLS, or OpenTitan. In a recent customer project we improved and extended a set of Bazel rules for Renode, Antmicro’s open source simulation framework, which allow launching Renode interactively from Bazel and running tests in a comprehensive flow.
In this article we will describe how Bazel rules for Renode can be used to integrate Renode with existing development workflows for advanced simulation scenarios, using co-simulation over DPI (Direct Programming Interface) as an example.
Bazel rules for Renode
Bazel is quite complex and requires you to specify the process of running or building your software very precisely by creating rules - bundles of instructions simplifying specific tasks. In exchange, you get fully reproducible and hermetic builds with clear dependency chains, as no dubious requirements are pulled from the user’s environment at any time - every dependency, either within the project or external, has to be explicitly mentioned in the rules. This detailed information about dependency chains, in turn, enables fast, incremental builds, i.e. rebuilding only parts of the project that changed.
Renode-bazel-rules is a repository containing such rules for Renode, allowing you to run Renode interactively and then run Robot Framework tests. The current implementation works with Bazel 7.
To set up Renode in the project, you first need to define MODULE.bazel, where you can select the specific Renode version to be downloaded and unpacked in Bazel’s sandbox. Then, in BUILD.bazel, you define your build, and run or test your targets. This includes Antmicro’s custom renode_test and renode_interactive targets, with a non-interactive Robot test and an interactive session, respectively.
This allows using Renode as part of a more comprehensive flow, typically including building the software, testing it, and publishing the results.
Demo: co-simulation with Verilator over DPI
To showcase the usage of Renode Bazel rules, we prepared a co-simulation demo running a verilated peripheral, which you can execute in an interactive or automated manner, utilizing the renode_interactive and renode_test Bazel rules.
Running the demo is as simple as changing the current working directory to
./samples/verilator-cosimulation and executing one of the following commands, for the test and the interactive mode respectively:
bazel test //:dma --nozip_undeclared_test_outputs
or
bazel run //:dma-interactive
By default, all generated logs and artifacts are zipped and stored in the ./bazel-testlogs directory. To keep them unzipped (for easier access to files like sim.vcd, the DMA waveform from simulation), we use the --nozip_undeclared_test_outputs flag.
On the first run, Bazel will analyze the dependency graph, extracted from the MODULE.bazel and WORKSPACE files, download all required dependencies (i.e. Verilator, Python, Renode and the demo binaries), and build everything needed to execute the demo (e.g the Verilog sources).
The Renode rules and their dependencies are declared in the MODULE.bazel file:
[...]
# Renode
bazel_dep(name = "rules_renode", version = "0.0.0")
local_path_override(
module_name = "rules_renode",
path = "../..",
)
renode = use_extension("@rules_renode//renode:extensions.bzl", "renode")
renode.download_portable(
name = "renode_toolchain",
sha256 = "c1d6f50596a2fb410b0a4bd05a5a1e9cf5d83c49818d43a0c40b781812413170",
url = "https://builds.renode.io/renode-1.16.0+20251013gitab4b8f738.linux-portable-dotnet.tar.gz",
)
use_repo(renode, "renode_toolchains", renode_toolchain = "renode_toolchain")
register_toolchains("@renode_toolchains//:all")
[...]
The WORKSPACE file defines the rules_hdl dependency:
[...]
load("@rules_hdl//dependency_support:dependency_support.bzl", rules_hdl_dependency_support = "dependency_support")
rules_hdl_dependency_support()
load("@rules_hdl//:init.bzl", rules_hdl_init = "init")
rules_hdl_init()
The dma and dma-interactive targets are defined in the BUILD file:
common_variables = {
"bin": "@bin//file",
"rootfs": "@rootfs//file",
"dtb": "@dtb//file",
"dma_verilated": "//dpi-verilator-cosimulation:verilated",
}
renode_test(
name = "dma",
timeout = "moderate",
robot_test = "test_linux.robot",
variables_with_label = common_variables,
deps = [
"platform_linux.repl",
"platform_linux.resc",
],
)
renode_interactive(
name = "dma-interactive",
resc = "platform_linux.resc",
variables_with_label = common_variables,
deps = ["platform_linux.repl"],
)
The renode_test rule creates an automated testing environment. It uses a Robot Framework test file (test_linux.robot) that internally sets up Renode using the same .resc script as the interactive version. This allows Renode to be launched, the Linux system to boot, and the co-simulated DMA to be automatically tested through scripted transactions. It also generates a waveform which can later be inspected with GTKWave for debugging and signal analysis, as shown below.

The renode_interactive rule sets up an environment that the user can directly interact with. It uses only a .resc script to define the simulation but does not include any automated Robot tests. The renode_interactive rule lacks platform_linux.resc as its dependency, but this dependency is automatically added while providing the resc file (resc = "platform_linux.resc"). This is useful for interactive inspection of the emulation environment, and ease of debugging, before setting up automated tests.
Both Renode rules share the same set of variables (the common_variables dictionary) which is passed to either the .resc or the .robot script. The variables can be Bazel targets themselves, for example @bin//file refers to the file downloaded through the http_file rule defined in BUILD.bazel, while "dma_verilated": "//dpi-verilator-cosimulation:verilated" points to another Bazel target located in a different directory. That target defines how the Verilator simulation binary is built, which is later used by the .resc script to create the co-simulated DMA device. A partial snippet (demonstrating the rules building the Verilog source and the DPI library) is presented below:
verilog_library(
name = "sim_verilog",
srcs = ["sim.sv"],
deps = [
":dma_verilog",
":renode_dpi_verilog_library",
],
)
[...]
cc_binary(
name = "verilated",
srcs = ["sim-main-dpi.cpp"],
copts = [
"-std=c++20",
"-DVM_TRACE",
],
deps = [
":sim_verilator",
":renode_dpi_cc_library",
],
)
All these targets are built inside isolated Bazel sandboxes, meaning each target runs in a clean environment, with only its declared dependencies available. Additionally, only the resulting output files are passed between different stages of the build process, which ensures hermeticity and reproducibility.
Comprehensive testing with Renode and Bazel
With Bazel rules for Renode, you can run complex simulation scenarios to test your software in a deterministic, hermetic environment. Thanks to Bazel’s and Renode’s flexibility, these rules can be easily extended to target more use cases.
If you would like to learn more about Renode’s co-simulation capabilities or Antmicro’s engineering services for adapting specialized tools such as Bazel, don’t hesitate to contact us at contact@antmicro.com. You can also visit our offering page to see how we can help you improve your workflows with open source tools.