rules_vivado

Bazel rules for Xilinx Vivado FPGA synthesis, placement, routing, and bitstream generation.

Overview

rules_vivado wires Xilinx Vivado into Bazel as a set of ordinary build and test rules. HDL sources flow in through rules_verilog (VerilogInfo) and rules_vhdl (VhdlInfo); the same *_library targets can be reused for simulation and synthesis. The build phases are each their own rule (vivado_synthesize, vivado_placement, vivado_routing, vivado_write_bitstream, …) so checkpoints are cached between phases, or you can chain the whole flow with the vivado_flow macro.

The Xilinx install itself is resolved via a registered vivado_toolchain — there is no per-target install path to configure once a toolchain is in place.

Quick start

The walkthrough below takes a Verilog top module from source to bitstream with the vivado_flow macro.

MODULE.bazel

bazel_dep(name = "rules_verilog", version = "1.1.1")
bazel_dep(name = "rules_vhdl", version = "0.1.1")
bazel_dep(name = "rules_vivado", version = "{version}")

register_toolchains("//tools/vivado:vivado_toolchain")

A vivado_toolchain is mandatory — every vivado_* rule resolves the Xilinx install through it. See Toolchains for how to author one.

tools/vivado/vivado.sh

#!/usr/bin/env bash
exec /opt/Xilinx/Vivado/2024.2/bin/vivado "$@"

Mark it executable: chmod +x tools/vivado/vivado.sh.

tools/vivado/BUILD.bazel

load("@rules_vivado//vivado:toolchain.bzl", "vivado_toolchain")

vivado_toolchain(
    name = "vivado_local",
    vivado = "vivado.sh",
    env = {
        "XILINXD_LICENSE_FILE": "2100@license.example.com",
        "HOME": "/tmp",
    },
)

toolchain(
    name = "vivado_toolchain",
    toolchain = ":vivado_local",
    toolchain_type = "@rules_vivado//vivado:toolchain_type",
)

See Toolchains for license-server and multi-version setup.

hello/hello.sv

module hello (
    input  wire clk,
    input  wire rst,
    output reg  led
);
  always_ff @(posedge clk) begin
    if (rst) led <= 1'b0;
    else     led <= ~led;
  end
endmodule

hello/BUILD.bazel

load("@rules_verilog//verilog:defs.bzl", "verilog_library")
load("@rules_vivado//vivado:defs.bzl", "vivado_flow")

verilog_library(
    name = "hello",
    srcs = ["hello.sv"],
    data = ["hello.xdc"],
)

vivado_flow(
    name = "hello_bitstream",
    module = ":hello",
    module_top = "hello",
    part_number = "xczu28dr-ffvg1517-2-e",
)

Build it

$ bazel build //hello:hello_bitstream
$ ls bazel-bin/hello/
hello_bitstream.bit  hello_bitstream_route.dcp  ...

vivado_flow is a convenience macro — it expands to the per-phase rules below so each checkpoint is cached on its own:

  • hello_bitstream_synth — synthesis (.dcp)
  • hello_bitstream_synth_opt — post-synthesis optimization
  • hello_bitstream_placement — placement
  • hello_bitstream_place_opt — post-placement optimization
  • hello_bitstream_route — routing
  • hello_bitstream — final .bit

Build any one of them directly to stop the flow early or to inspect intermediate reports.

Going further

  • Toolchains — author a vivado_toolchain, register multiple versions, gate them with constraints and platforms.
  • Rules — every public rule, indexed by build phase.

Toolchains

rules_vivado resolves the Xilinx Vivado install through Bazel toolchain resolution. You declare a vivado_toolchain, wrap it in toolchain(...), and register it from MODULE.bazel. Every vivado_* rule then picks it up automatically — there is no per-target xilinx_env to thread through.

Registering a toolchain is required.

Implementing a toolchain

See vivado_toolchain — the rule's docstring has the worked quickstart, attribute reference, and the env vs xilinx_env framing.

Network vs. node-locked licenses

vivado_toolchain.requires_network defaults to True, which is correct for a floating/network license server (XILINXD_LICENSE_FILE=PORT@HOST). It sets the requires-network execution requirement on every vivado_* action.

Set it to False for license-free editions (Vivado ML Standard / WebPACK) or for node-locked .lic files read from disk. Sandboxed and remote-execution builds need network disabled to be reproducible without the license server, so be deliberate here.

Constraining toolchains

To run multiple Vivado versions side-by-side, gate each vivado_toolchain with one of the per-version constraint_values in //vivado/constraints/BUILD.bazel. Each constraint corresponds to one entry in VIVADO_VERSIONS (defined in //vivado/private:versions.bzl). The vivado_toolchain docstring has the full multi-version walkthrough — platform(...) setup, register_execution_platforms, and the --platforms switch.

For per-target switching without a global flag, use a wrapper rule with cfg = transition(...); see tests/transition.bzl for a with_vivado_version wrapper that takes a list of targets and pins the version for the whole group.

Constraints are the only mechanism — there is no parallel build-setting / flag-driven path. This keeps per-version metadata (constraints, exec_properties like container-image) all on the platform object where it belongs and avoids the two-sources-of-truth problem.

Reference

See vivado_toolchain for the full attribute set and VivadoToolchainInfo for the resolved provider that downstream rules consume.

Toolchain for the Xilinx Vivado tool.

Defines VivadoToolchainInfo and the vivado_toolchain rule. Users register a vivado_toolchain instance via register_toolchains(...) against the //vivado:toolchain_type toolchain type so every vivado_* rule automatically resolves the Xilinx environment.

Quickstart

  1. Author a small bash shim that execs your Vivado install. The shim is the file Bazel tracks; it hard-codes the install path (typically a fixed location baked into a container image):

    #!/usr/bin/env bash
    # tools/vivado/vivado.sh
    exec /opt/Xilinx/Vivado/2024.2/bin/vivado "$@"
    

    Mark it executable: chmod +x tools/vivado/vivado.sh.

  2. Declare a vivado_toolchain and a toolchain() wrapper in BUILD, pointing at the shim. Put your license server and any extra env in env:

    load("@rules_vivado//vivado:toolchain.bzl", "vivado_toolchain")
    
    vivado_toolchain(
        name = "vivado_local",
        vivado = "vivado.sh",
        env = {
            "XILINXD_LICENSE_FILE": "2100@license.example.com",
            "HOME": "/tmp",
        },
    )
    
    toolchain(
        name = "vivado_toolchain",
        toolchain = ":vivado_local",
        toolchain_type = "@rules_vivado//vivado:toolchain_type",
    )
    
  3. Register it from MODULE.bazel:

    register_toolchains("//tools/vivado:vivado_toolchain")
    

Every vivado_* rule resolves this toolchain automatically.

xilinx_env is an optional escape hatch — a shell script sourced inside the action immediately before vivado runs — for shell-side env composition neither env nor the shim itself covers. Prefer env and the shim's own preamble first.

Constraining toolchains

Register multiple vivado_toolchain instances side-by-side and let Bazel pick one per action via exec_compatible_with against the per-version constraint_values in //vivado/constraints/BUILD.bazel. Each constraint corresponds to one entry in //vivado/private:versions.bzl VIVADO_VERSIONS.

load("@rules_vivado//vivado:toolchain.bzl", "vivado_toolchain")

vivado_toolchain(
    name = "vivado_2024_2",
    vivado = "vivado_2024_2.sh",
)

toolchain(
    name = "vivado_toolchain_2024_2",
    exec_compatible_with = ["@rules_vivado//vivado/constraints/version:2024.2"],
    toolchain = ":vivado_2024_2",
    toolchain_type = "@rules_vivado//vivado:toolchain_type",
)

platform(
    name = "vivado_2024_2_platform",
    constraint_values = ["@rules_vivado//vivado/constraints/version:2024.2"],
    exec_properties = {
        "container-image": "docker://your.registry/vivado:2024.2",
    },
    parents = ["@platforms//host"],
)

Register both the toolchain and the platform from MODULE.bazel:

register_toolchains("//tools/vivado:vivado_toolchain_2024_2")
register_execution_platforms("//tools/vivado:vivado_2024_2_platform")

The first registered exec platform becomes the default. Switch versions per build with --platforms=//tools/vivado:vivado_2024_2_platform (which also lets target_compatible_with = ["@rules_vivado//vivado/constraints/version:2024.2"] on a target evaluate against the right constraint), or use a wrapper rule with cfg = transition(...) to switch per target. See //tests/transition.bzl for an example with_vivado_version wrapper.

Rules

Providers

vivado_toolchain

load("@rules_vivado//vivado:toolchain.bzl", "vivado_toolchain")

vivado_toolchain(name, env, requires_network, version, vivado, xilinx_env)

Declares a Vivado toolchain.

Wrap with toolchain(...) and register via register_toolchains(...) in MODULE.bazel so every vivado_* rule resolves it automatically. Multiple instances can be registered side-by-side and selected via target_settings (flag-driven) or exec_compatible_with (platform-driven). See the //vivado:toolchain.bzl module docstring and the rules_vivado README for full walkthroughs.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
envEnvironment variables passed to every Vivado action.Dictionary: String -> Stringoptional{}
requires_networkWhether Vivado actions need network access. True (the default) is correct for a floating/network license server (XILINXD_LICENSE_FILE=PORT@HOST). Set to False for license-free editions (Vivado ML Standard / WebPACK) or node-locked .lic files read from disk. Controls whether the requires-network execution requirement is set on every vivado_* action.BooleanoptionalTrue
versionThe version of Vivado associated with this toolchain.Stringoptional""
vivadoThe Vivado executable. Typically a small bash shim that execs the real vivado out of a known install path (e.g. baked into a container image), but any *_binary rule works too — runfiles travel along. Defaults to a stock shim that calls vivado from the exec platform's PATH as a migration aid; production toolchains should pin the install path with their own shim.Labeloptional"@rules_vivado//vivado/private:vivado.sh"
xilinx_envOptional escape hatch — a shell script sourced inside the action shell immediately before vivado runs, for shell-side env composition env cannot express. Prefer env.LabeloptionalNone

VivadoToolchainInfo

load("@rules_vivado//vivado:toolchain.bzl", "VivadoToolchainInfo")

VivadoToolchainInfo(env, requires_network, version, vivado, xilinx_env)

Toolchain info for the Xilinx Vivado tool.

FIELDS

NameDescription
envdict[str, str]: environment variables passed to every Vivado action.
requires_networkbool: whether Vivado actions need network access (typically for a network license server).
versionstr: The version of Vivado associated with this toolchain.
vivadoFilesToRunProvider: the executable Bazel invokes for every Vivado action. Passed via tools= so runfiles travel along. Typically a small shim that execs the real vivado binary out of the install location (often a fixed path inside a container image), but any *_binary rule works too.
xilinx_envFile or None: optional shell script sourced inside the action shell immediately before vivado runs. Escape hatch for shell-side env composition env cannot express.

Rules

Every public rule in rules_vivado, grouped by the build phase it belongs to. All of them resolve their Xilinx install through a registered vivado_toolchain.

Project setup

Synthesis

Implementation

Bitstream

End-to-end flow

  • vivado_flow — convenience macro (loaded from @rules_vivado//vivado:defs.bzl) that chains synthesis → opt → placement → place-opt → routing → bitstream into one target name. See the Quick start for a worked example.

IP packaging

Simulation

  • xsim_test — run a Vivado XSim simulation as a Bazel test target.

Toolchain

Providers

  • VivadoToolchainInfo and friends — the providers passed between phases (VivadoSynthCheckpointInfo, VivadoPlacementCheckpointInfo, VivadoRoutingCheckpointInfo, VivadoIPBlockInfo, VivadoInterfaceInfo).

vivado_create_project rule: build a Vivado project without synthesizing.

Rules

vivado_create_project

load("@rules_vivado//vivado:project.bzl", "vivado_create_project")

vivado_create_project(name, create_project_tcl_template, ip_blocks, jobs, module, module_top,
                      part_number)

Create a Vivado project from a verilog_library without running synthesis.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
create_project_tcl_templateThe create project tcl templateLabeloptional"@rules_vivado//vivado/private:create_project.tcl.template"
ip_blocksIp blocks to include in this design.List of labelsoptional[]
jobsJobs to pass to vivado which defines the amount of parallelism.Integeroptional4
moduleThe top level build.Labelrequired
module_topThe name of the top level verilog module.Stringrequired
part_numberThe targeted xilinx part.Stringrequired

Synthesis-phase rules: vivado_synthesize and vivado_synthesis_optimize.

Rules

vivado_synthesis_optimize

load("@rules_vivado//vivado:synthesis.bzl", "vivado_synthesis_optimize")

vivado_synthesis_optimize(name, checkpoint, opt_directive, synthesis_optimize_template, threads,
                          with_probes)

Run post-synthesis optimization on a synthesis checkpoint.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
checkpointSynthesis checkpoint.Labelrequired
opt_directiveThe optimization directive.Stringoptional"Explore"
synthesis_optimize_templateThe synthesis optimization tcl templateLabeloptional"@rules_vivado//vivado/private:synth_optimize.tcl.template"
threadsThreads to pass to vivado which defines the amount of parallelism.Integeroptional8
with_probesCreate debug probes.BooleanoptionalFalse

vivado_synthesize

load("@rules_vivado//vivado:synthesis.bzl", "vivado_synthesize")

vivado_synthesize(name, create_project_tcl_template, ip_blocks, jobs, module, module_top,
                  part_number, synth_strategy)

Create a Vivado project and run synthesis on it.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
create_project_tcl_templateThe create project tcl templateLabeloptional"@rules_vivado//vivado/private:create_project.tcl.template"
ip_blocksIp blocks to include in this design.List of labelsoptional[]
jobsJobs to pass to vivado which defines the amount of parallelism.Integeroptional4
moduleThe top level build.Labelrequired
module_topThe name of the top level verilog module.Stringrequired
part_numberThe targeted xilinx part.Stringrequired
synth_strategyThe synthesis strategy to use.Stringoptional"Vivado Synthesis Defaults"

Implementation-phase rules: placement, physical optimization, routing.

Rules

vivado_place_optimize

load("@rules_vivado//vivado:implementation.bzl", "vivado_place_optimize")

vivado_place_optimize(name, checkpoint, phys_opt_directive, place_optimize_template, threads)

Run post-placement physical optimization.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
checkpointPlacement checkpoint.Labelrequired
phys_opt_directiveThe optimization directive.Stringoptional"AggressiveExplore"
place_optimize_templateThe placement tcl templateLabeloptional"@rules_vivado//vivado/private:place_optimize.tcl.template"
threadsThreads to pass to vivado which defines the amount of parallelism.Integeroptional8

vivado_placement

load("@rules_vivado//vivado:implementation.bzl", "vivado_placement")

vivado_placement(name, checkpoint, placement_directive, placement_template, threads)

Run placement on a (synthesis-optimized) checkpoint.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
checkpointSynthesis checkpoint.Labelrequired
placement_directiveThe optimization directive.Stringoptional"Explore"
placement_templateThe placement tcl templateLabeloptional"@rules_vivado//vivado/private:placement.tcl.template"
threadsThreads to pass to vivado which defines the amount of parallelism.Integeroptional8

vivado_routing

load("@rules_vivado//vivado:implementation.bzl", "vivado_routing")

vivado_routing(name, checkpoint, route_directive, route_template, threads)

Run routing on a placement checkpoint.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
checkpointPlacement checkpoint.Labelrequired
route_directiveThe routing directive.Stringoptional"Explore"
route_templateThe routing tcl templateLabeloptional"@rules_vivado//vivado/private:route.tcl.template"
threadsThreads to pass to vivado which defines the amount of parallelism.Integeroptional8

Bitstream-phase rule: vivado_write_bitstream.

Rules

vivado_write_bitstream

load("@rules_vivado//vivado:bitstream.bzl", "vivado_write_bitstream")

vivado_write_bitstream(name, checkpoint, threads, with_xsa, write_bitstream_template)

Write a Vivado bitstream (.bit) from a routed checkpoint, optionally including a .xsa.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
checkpointRouted checkpoint.Labelrequired
threadsThreads to pass to vivado which defines the amount of parallelism.Integeroptional8
with_xsaGenerate xsa tooBooleanoptionalFalse
write_bitstream_templateThe write bitstream tcl templateLabeloptional"@rules_vivado//vivado/private:write_bitstream.tcl.template"

IP packaging rules

Rules

vivado_create_interface_ip

load("@rules_vivado//vivado:ip.bzl", "vivado_create_interface_ip")

vivado_create_interface_ip(name, create_interface_ip_template, description, interface, module,
                           part_number, vendor_display_name)

Package a Vivado interface definition as an IP block. Unlike vivado_create_ip, this does not require a top module.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
create_interface_ip_templateThe TCL template for creating interface IP.Labeloptional"@rules_vivado//vivado/private:create_interface_ip.tcl.template"
descriptionDescription for the IP block.Stringoptional""
interfaceThe interface definition to package.Labelrequired
moduleThe verilog_library containing the interface source file(s).LabeloptionalNone
part_numberThe targeted xilinx part.Stringrequired
vendor_display_nameDisplay name for the vendor.Stringoptional""

vivado_create_ip

load("@rules_vivado//vivado:ip.bzl", "vivado_create_ip")

vivado_create_ip(name, create_ip_block_template, encrypt, ip_blocks, ip_library, ip_vendor,
                 ip_version, jobs, keyfile, module, module_top, part_number)

Use vivado to package a module into an IP core

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
create_ip_block_templateThe create project tcl templateLabeloptional"@rules_vivado//vivado/private:create_ip_block.tcl.template"
encryptEncrypt the sources. Note: This requires a license. See: https://support.xilinx.com/s/article/68071?language=en_USBooleanoptionalFalse
ip_blocksIp blocks to include in this design.List of labelsoptional[]
ip_libraryThe version of this ip core.Stringrequired
ip_vendorThe version of this ip core.Stringrequired
ip_versionThe version of this ip core.Stringrequired
jobsJobs to pass to vivado which defines the amount of parallelism.Integeroptional4
keyfileThe keyfile to use when optionally encryptingLabeloptional"@rules_vivado//vivado/private:xilinx_keyfile.txt"
moduleThe top level build.Labelrequired
module_topThe name of the top level verilog module.Stringrequired
part_numberThe targeted xilinx part.Stringrequired

vivado_interface_definition

load("@rules_vivado//vivado:ip.bzl", "vivado_interface_definition")

vivado_interface_definition(name, src, abstraction_definition_template, bus_definition_template,
                            description, direct_connection, interface_name, interface_setup_template,
                            is_addressable, library, max_masters, max_slaves, parser, vendor, version)

Generate Vivado IP-XACT interface definition files (bus definition and abstraction definition XML).

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
srcThe SystemVerilog interface source file to parse.Labelrequired
abstraction_definition_templateThe abstraction definition XML template.Labeloptional"@rules_vivado//vivado/private:abstraction_definition.xml.template"
bus_definition_templateThe bus definition XML template.Labeloptional"@rules_vivado//vivado/private:bus_definition.xml.template"
descriptionDescription for the interface.Stringoptional""
direct_connectionWhether direct connections are allowed.BooleanoptionalTrue
interface_nameThe name of the interface (e.g., 'hbm_reader').Stringrequired
interface_setup_templateThe interface setup TCL template.Labeloptional"@rules_vivado//vivado/private:interface_setup.tcl.template"
is_addressableWhether the interface is addressable.BooleanoptionalTrue
libraryThe library VLNV component (e.g., 'interface').Stringoptional"interface"
max_mastersMaximum number of masters.Integeroptional1
max_slavesMaximum number of slaves.Integeroptional1
parserPython parser script (SV -> JSON). Override to customize SV parsing.Labeloptional"@rules_vivado//vivado/private:parse_sv_interface"
vendorThe vendor VLNV component (e.g., 'mycompany.com').Stringrequired
versionThe version VLNV component (e.g., '1.0').Stringoptional"1.0"

Simulation rule: xsim_test.

Rules

xsim_test

load("@rules_vivado//vivado:simulation.bzl", "xsim_test")

xsim_test(name, ip_blocks, module, module_top, part_number, with_waveform, xsim_test_template)

Run a Vivado xsim simulation as a Bazel test.

ATTRIBUTES

NameDescriptionTypeMandatoryDefault
nameA unique name for this target.Namerequired
ip_blocksIp blocks to include in this design.List of labelsoptional[]
moduleThe top level build.Labelrequired
module_topThe name of the top level verilog module.Stringrequired
part_numberThe targeted xilinx part.Stringrequired
with_waveformGenerate with a waveformBooleanoptionalFalse
xsim_test_templateThe tcl template to run on vivado.Labeloptional"@rules_vivado//vivado/private:xsim_test.tcl.template"

Vivado providers.

Providers

VivadoIPBlockInfo

load("@rules_vivado//vivado:providers.bzl", "VivadoIPBlockInfo")

VivadoIPBlockInfo(is_interface, library, module_top, repo, vendor, version)

Info for a vivado ip block

FIELDS

NameDescription
is_interfacebool: True if this is an interface definition (repo-only, not instantiated via create_ip).
librarystring: The library that the ip block belongs to.
module_topstring: The name of the ip block top module.
repolist[File]: Directories containing the ip block (and any transitive ip block deps).
vendorstring: The vendor of the ip block.
versionstring: The ip block version.

VivadoInterfaceInfo

load("@rules_vivado//vivado:providers.bzl", "VivadoInterfaceInfo")

VivadoInterfaceInfo(abstraction_definition, bus_definition, library, name, setup_tcl, vendor,
                    version)

Info for a Vivado IP-XACT interface definition

FIELDS

NameDescription
abstraction_definitionFile: The abstraction definition XML file.
bus_definitionFile: The bus definition XML file.
librarystring: The library VLNV component.
namestring: The interface name.
setup_tclFile: The TCL setup file for IP packaging.
vendorstring: The vendor VLNV component.
versionstring: The version VLNV component.

VivadoPlacementCheckpointInfo

load("@rules_vivado//vivado:providers.bzl", "VivadoPlacementCheckpointInfo")

VivadoPlacementCheckpointInfo(checkpoint)

Contains information at output of placement.

FIELDS

NameDescription
checkpointFile: a Vivado placement checkpoint (.dcp).

VivadoRoutingCheckpointInfo

load("@rules_vivado//vivado:providers.bzl", "VivadoRoutingCheckpointInfo")

VivadoRoutingCheckpointInfo(checkpoint)

Contains information at output of routing.

FIELDS

NameDescription
checkpointFile: a Vivado post-route checkpoint (.dcp).

VivadoSynthCheckpointInfo

load("@rules_vivado//vivado:providers.bzl", "VivadoSynthCheckpointInfo")

VivadoSynthCheckpointInfo(checkpoint)

Contains information at output of synthesis.

FIELDS

NameDescription
checkpointFile: a Vivado synthesis checkpoint (.dcp).