rules_riviera
Bazel rules for Aldec Riviera-PRO, a mixed-language (Verilog / SystemVerilog / VHDL) HDL simulator.
The public surface is three rules:
riviera_library— compile averilog_library/vhdl_libraryDAG into ONE self-contained Riviera library.riviera_sim_test—vmapone or moreriviera_librarydeps, elaborate atop, and simulate undervsimsaas a Bazel test.riviera_toolchain— bind the Riviera executables to the toolchain type the rules resolve. See its generated reference for the full attr list.
Everything else — coverage collection under bazel coverage, //tools/acdb2lcov for ACDB -> lcov conversion — is designed around those rules.
Setup
Riviera-PRO is a commercial simulator; this repo does not distribute it. rules_riviera provides the Bazel rules and the toolchain type contract — how Riviera actually reaches your Bazel actions is deployment infrastructure and lives entirely in your own repo.
MODULE.bazel
bazel_dep(name = "rules_riviera", version = "0.0.1")
register_toolchains("//tools/riviera:my_riviera_toolchain")
Author a toolchain
vsimsa is the only mandatory attr: it is the one binary the rules invoke across the Bazel action boundary, from both riviera_library's compile action and riviera_sim_test at test time. Every other tool attr (acdb included — its coverage reports are generated by an acdb report TCL command inside that same vsimsa invocation) defaults to a PATH-resolving shim.
Bind it to anything Bazel can exec. The ideal is a hermetic toolchain that vendors the install into the action graph; how that install is provisioned is up to you. A wrapper's own data deps reach the action — the toolchain carries each tool's runfiles, not just its executable.
# tools/riviera/BUILD.bazel
load("@rules_riviera//riviera:riviera_toolchain.bzl", "riviera_toolchain")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
# Simplest possible binding: resolve vsimsa from PATH. Appropriate when
# actions already run inside an image with Riviera installed. Swap in an
# absolute install path, or a wrapper that sources a license env file.
sh_binary(
name = "vsimsa",
srcs = ["vsimsa.sh"], # #!/bin/sh + `exec vsimsa "$@"`
)
riviera_toolchain(
name = "my_riviera_toolchain_def",
vsimsa = ":vsimsa",
requires_network = True, # set True when the license server is remote
version = "2025.04",
)
toolchain(
name = "my_riviera_toolchain",
toolchain = ":my_riviera_toolchain_def",
toolchain_type = "@rules_riviera//riviera:toolchain_type",
)
A complete, buildable version of this lives in examples/simple.
Rules
Three public rules over the providers in //riviera:defs.bzl.
| Rule | Produces | Consumes |
|---|---|---|
riviera_library | RivieraLibraryInfo | VerilogInfo / VhdlInfo |
riviera_sim_test | (a bazel test) | RivieraLibraryInfo |
riviera_toolchain | RivieraToolchainInfo (one RivieraToolInfo per tool) | one executable per Riviera tool; only vsimsa is mandatory |
Anything that produces RivieraLibraryInfo (a pre-built vendor library, an imported UVM archive, etc.) is a drop-in for riviera_sim_test.deps — no sim-rule surgery required.
riviera_library— compile averilog_library/vhdl_libraryDAG into one Riviera library.riviera_sim_test—vmapone or moreriviera_librarydeps and elaborate + simulate atopundervsimsa. Automatic coverage underbazel coverage.riviera_toolchain— bind Riviera executables to the toolchain type consumed by the rules above.
Public entry point for riviera_library.
Rules
riviera_library
load("@rules_riviera//riviera:riviera_library.bzl", "riviera_library")
riviera_library(name, deps, srcs, acom_opts, alog_opts, exec_requirements, jobs, library_name,
link_libraries)
Compile the full transitive HDL DAG into ONE self-contained Aldec Riviera library.
deps takes verilog_library / vhdl_library targets (anything
providing VerilogInfo or VhdlInfo). The rule walks the whole
reachable source graph — own srcs + hdrs, transitive .deps, and
cross-language .vhdl_deps / .verilog_deps, through any number of
language boundaries — and compiles it in one alib/alog/acom
invocation via a generated vsimsa .do script. includes become
+incdir+ paths; data files are compile inputs and are forwarded as
runfiles to riviera_sim_test. Output is one TreeArtifact directory; riviera_sim_test
vmaps it by the logical library name.
srcs is an optional escape hatch for HDL files that only make sense
inside this Riviera library (a Riviera-specific stub, a compile-time
wrapper) and don't warrant their own HDL-library target.
Runs through //tools/riviera_wrapper, which isolates vsimsa's HOME,
maps link_libraries, cleans up the session library.cfg, and only
prints the transcript when the compile fails.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | verilog_library / vhdl_library targets; their full transitive source graph is compiled into this library. | List of labels | optional | [] |
| srcs | HDL files compiled directly into this library, after everything from deps. Prefer deps for anything another target might also want. | List of labels | optional | [] |
| acom_opts | Extra flags forwarded to VHDL compilation (acom). | List of strings | optional | [] |
| alog_opts | Extra flags forwarded to Verilog compilation (alog) | List of strings | optional | [] |
| exec_requirements | Extra execution_requirements merged over the toolchain's (resources:<name> for each of its resources, plus requires-network when it sets it). | Dictionary: String -> String | optional | {} |
| jobs | CPU slots to request from Bazel's scheduler. -1 inherits the toolchain's jobs; N > 0 requests N; anything else is no hint. Compiler parallelism flags go in alog_opts / acom_opts. | Integer | optional | -1 |
| library_name | Logical Riviera library name. Defaults to the target's label name. | String | optional | "" |
| link_libraries | Pre-compiled RivieraLibraryInfo targets vmapped before this compile, so library <name>; use <name>.<pkg>.all; clauses resolve. Unlike deps, their sources are not compiled into this library. | List of labels | optional | [] |
Public entry point for riviera_sim_test.
Rules
riviera_sim_test
load("@rules_riviera//riviera:riviera_sim_test.bzl", "riviera_sim_test")
riviera_sim_test(name, deps, data, asim_opts, env, exec_requirements, extra_tops, generics, jobs,
link_libraries, post_processor, pre_processor, top)
Elaborate + simulate a Riviera library under vsimsa as a Bazel test.
Takes one or more deps — each a riviera_library — and a fully-qualified
top (e.g. "my_lib.my_tb") that asim elaborates. Every library a
dep's library.cfg declares is vmapped by absolute path before
elaboration, so cross-library instantiation works out of the box.
asim_opts and env support Bazel's location templates
($(location ...), $(rlocationpath ...), execpath / rootpath and
their plural forms) against targets in data. Each template is
runfiles-resolved to an absolute path at test time, in place, so
-f $(location :f) or +define+HEX=$(rlocationpath :rom) work as
written.
Coverage kicks in automatically under bazel coverage: the launcher
adds Riviera's ACDB coverage flags, renders html + text reports into
$TEST_UNDECLARED_OUTPUTS_DIR, and converts the text report to lcov
via acdb2lcov for $COVERAGE_OUTPUT_FILE.
Seeding — RIVIERA_SEED carries the simulation seed in both
directions. Set it (--test_env=RIVIERA_SEED=42, or from a
pre_processor env fragment) to pin the run; leave it unset and the
launcher picks a fresh value per run. Either way the resolved seed
is passed to asim as -random_seed/-sv_seed, printed to the test
log as riviera_sim_test seed: <n>, and re-exported as
RIVIERA_SEED so the simulation and anything it spawns can read it.
No other seed variable is set — a harness wanting its framework's own
variable should set it alongside RIVIERA_SEED from a single value in
its pre_processor.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| deps | riviera_library targets holding the design under test, including top. | List of labels | required | |
| data | Runtime data (memory init files, .f lists, VPI shared libs, ...) staged into runfiles. Targets referenced from asim_opts / env templates must appear here. | List of labels | optional | [] |
| asim_opts | Extra arguments forwarded to asim. Location templates expand against data (see the rule doc). | List of strings | optional | [] |
| env | Extra environment variables for the test. Location templates expand against data (see the rule doc). | Dictionary: String -> String | optional | {} |
| exec_requirements | Extra execution_requirements merged over the toolchain's (resources:<name> for each of its resources, plus requires-network when it sets it). | Dictionary: String -> String | optional | {} |
| extra_tops | Additional fully-qualified top-level units appended to top on the asim command line. asim instantiates each at simulator root — use for co-elaborating helper modules like Xilinx xil_defaultlib.glbl whose internal signals (glbl.GSR, glbl.GTS) must exist for hierarchical references from user HDL (xpm_cdc, xpm_fifo, …) to resolve at elaboration. | List of strings | optional | [] |
| generics | Elaboration-time parameters / generics, bound by name at every level of the hierarchy (mapped to -G<name>=<value>). | Dictionary: String -> String | optional | {} |
| jobs | CPU slots to request from Bazel's scheduler. -1 inherits the toolchain's jobs; N > 0 adds cpu:N; anything else is no hint. Simulator parallelism flags go in asim_opts. | Integer | optional | -1 |
| link_libraries | RivieraLibraryInfo targets for pre-built libraries the design references but doesn't own (Xilinx simlibs, precompiled IP). Vmapped before deps, so a deps library wins any name collision. | List of labels | optional | [] |
| post_processor | Optional executable invoked after a successful simulation, before the test returns. Runs in the test's working directory — the runfiles root, like any other Bazel executable — so relative paths reach data deps the usual way. Everything the simulation produced is reachable through the environment rather than the CWD: vsimsa runs with its CWD set to $TEST_TMPDIR, so coverage.acdb, coverage.txt, coverage_html/, and anything the testbench itself wrote all land there. $COVERAGE_OUTPUT_FILE and $TEST_UNDECLARED_OUTPUTS_DIR are set as usual, alongside any env from the rule.A non-zero exit fails the test. | Label | optional | None |
| pre_processor | Optional executable target invoked before vsimsa, in $TEST_TMPDIR with the test's runfiles + env. Any executable label works (py_binary, sh_binary, py_venv_binary, custom rules setting DefaultInfo(executable = ...), etc.).Env-fragment contract: the launcher exports RIVIERA_PRE_PROCESSOR_ENV_FILE=<path> when invoking the pre_processor. If the pre_processor writes K=V\n-per-line contents to that file, the launcher parses each line and setenvs it before spawning vsimsa. Blank lines and lines starting with # are skipped; malformed lines fail the test loudly. This is the intended channel for pre_processor-computed values (venv paths, per-run tmpdirs, etc.) that the sim needs in env.A non-zero exit fails the test. | Label | optional | None |
| top | Fully-qualified HDL top-level unit passed to asim, e.g. "my_lib.my_tb". | String | required |
Public entry point for riviera_toolchain.
Rules
riviera_toolchain
load("@rules_riviera//riviera:riviera_toolchain.bzl", "riviera_toolchain")
riviera_toolchain(name, resources, acdb, acom, alib, alog, asim, env, jobs, requires_network,
riviera, version, vmap, vsim, vsimsa)
Declares an Aldec Riviera-PRO toolchain by binding one executable per Riviera tool.
Each tool attr takes an executable target that resolves to the
corresponding Riviera binary. In the ideal case that's a hermetic
toolchain that vendors the whole install into the Bazel action graph;
in practice it can be anything Bazel can exec — a sh_binary
wrapper that sources a license env file and execs a site install, a
container launcher, a PATH shim. The toolchain only cares that each
attr resolves to something that behaves like the named tool.
Tools bound to a wrapper keep their runfiles: each attr is exposed to
the rules as a RivieraToolInfo carrying the target's
files_to_run + default_runfiles, not just the bare executable
File.
vsimsa is the only mandatory attr. It is the one binary the rules
invoke across the Bazel action boundary — every riviera_library
compile action and every riviera_sim_test launcher call. Coverage
report generation (acdb report -txt/-html) runs as TCL inside that
same vsimsa invocation.
Everything else is optional and defaults to a shipped
exec <tool> "$@" PATH-resolving shim:
acdb,vsim,alib,alog,acom,asim,vmap— run inside vsimsa's TCL interpreter today and never cross the Bazel boundary.riviera— the interactive Riviera-PRO IDE. No rule here launches it; the attr exists so a rule of your own can.
They're exposed so downstream consumers have a place to bind if they ever need a Bazel-visible path to those binaries.
ATTRIBUTES
| Name | Description | Type | Mandatory | Default |
|---|---|---|---|---|
| name | A unique name for this target. | Name | required | |
| resources | --local_resources slots every Riviera action claims: resource name -> per-action count (as a string). Each entry becomes execution_requirements["resources:<name>"] = <count>, so locally executed actions can be held under a budget declared with --local_resources=<name>=<total>, typically license seats: {"riviera_license": "1"}. Every name used here needs a matching --local_resources registration or local actions fail to schedule. Empty by default, so actions schedule freely. | Dictionary: String -> String | optional | {} |
| acdb | Executable invoked as acdb. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:acdb" |
| acom | Executable invoked as acom. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:acom" |
| alib | Executable invoked as alib. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:alib" |
| alog | Executable invoked as alog. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:alog" |
| asim | Executable invoked as asim. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:asim" |
| env | Extra env vars applied to every action that resolves this toolchain. | Dictionary: String -> String | optional | {} |
| jobs | Default CPU-count hint for actions and tests that resolve this toolchain. -1 (default) means no hint — Bazel schedules however it likes; 0 also means no hint; N > 0 requests N CPUs. Per-target jobs = -1 (the rule default) inherits this value. | Integer | optional | -1 |
| requires_network | Set True when Riviera actions need network access (FlexLM license server). | Boolean | optional | False |
| riviera | Executable invoked as riviera. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:riviera" |
| version | Toolchain version identifier for diagnostics. | String | optional | "" |
| vmap | Executable invoked as vmap. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:vmap" |
| vsim | Executable invoked as vsim. Optional — no rule in this repo invokes it across the Bazel action boundary. Bind it if your own rules need a Bazel-visible path, or to override the default PATH-resolving shim. | Label | optional | "@rules_riviera//riviera/private/shims:vsim" |
| vsimsa | Executable invoked as vsimsa. MANDATORY — the rules call this tool directly. | Label | required |
acdb2lcov
A small Rust tool that converts Aldec's acdb report -txt output to lcov .info format. Same code powers riviera_sim_test's coverage post-processing.
Where it lives
- Binary:
//tools/acdb2lcov:acdb2lcov_cli— arust_binaryCLI wrapper around theacdb2lcovlibrary. - Library:
//tools/acdb2lcov:acdb2lcov— the parser + emitter, exposed as arust_libraryfor anyone who wants to embed it. - Tests:
//tools/acdb2lcov:acdb2lcov_test— arust_testover the library, runnable without a Riviera install.
CLI usage
bazel run @rules_riviera//tools/acdb2lcov:acdb2lcov_cli -- \
--input coverage.txt --output coverage.info
Both flags take filesystem paths. //tools/acdb2lcov:cli is an alias for the same binary; :acdb2lcov is the library and is not runnable.
Format notes
The launcher runs acdb report -txt -i coverage.acdb -o coverage.txt inside vsimsa after the sim ends, producing a human-readable table report. acdb2lcov walks that table:
- Statement Coverage tables carry
| <line> | <hits> | ... |rows keyed by a| Line | Hits | Source: /path/to/foo.sv |header. Rows with an emptyhitscell are treated as non-coverable and skipped so they don't count as missed lines. - Branch Coverage tables carry
IF branch#<line>#<idx>#parent rows followed by one arm-name row per branch arm; each arm becomes aBRDAentry.
The report states the same coverage twice — once under a DESIGN HIERARCHY banner (one block per elaborated instance) and again under DESIGN UNITS (one block per module / entity). Only the instance-based part is counted; it is the finer-grained of the two, since a module instantiated N times contributes N tables whose hits sum to the real execution count. Reports generated without it fall back to the design-unit part. Within the part that is counted, repeated tables for the same source are additive, and each branch arm stays a single BRDA entry.
Source paths in the report are absolute (/execroot/<workspace>/bazel-out/<config>/bin/<pkg>/<pkg>/<workspace-relative>); the parser normalizes them back to a workspace-relative form so Bazel's lcov merger matches them against the baseline report.
Coverage types beyond Statement and Branch (Covergroup, Expression, Condition, FSM, Toggle, Assertion, Path) are silently skipped — they're not representable in lcov and acdb report -txt doesn't include them by default.