Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bender Documentation

Welcome to the official documentation for Bender, a dependency management tool specifically designed for hardware design projects.

Bender helps you manage complex hardware IP hierarchies, ensuring that every member of your team and every CI runner is working with the exact same source code.

Key Features

  • Hierarchical Dependency Management: Resolve and manage transitive dependencies across multiple Git repositories or local paths, allowing for IPs to be used across projects.
  • Reproducible Builds: A precise lockfile mechanism (Bender.lock) ensures identical design states across environments.
  • HDL-Aware Source Collection: Automatically manages file ordering, include directories, and preprocessor defines for SystemVerilog and VHDL.
  • Target-Based Filtering: Use target expressions to include or exclude files based on your flow (simulation, synthesis, etc.) or design configuration.
  • Local Development Workflow: Easily modify dependencies in-place using the clone and snapshot flow without breaking official manifests.
  • Tool Script Generation: Generate compilation and simulation scripts for major EDA tools like QuestaSim, VCS, Vivado, Verilator, and more.

Getting Started

If you are new to Bender, we recommend following these steps:

  1. Installation: Get the bender binary onto your system.
  2. Getting Started: A quick tutorial to create your first Bender project.
  3. Concepts: Dive deeper into how Bender works under the hood.
  4. Workflows: Practical guides for daily development tasks.

Bender is an open-source project maintained by the PULP Platform at ETH Zurich and the University of Bologna.

Installation

Bender is a single standalone binary. You can either use our recommended shell installer, download a precompiled version, or build it from source.

The fastest way to install Bender is using our shell script. It detects your operating system and architecture, downloads the matching release, installs the bender binary into ${CARGO_HOME:-$HOME/.cargo}/bin, and adds that directory to your PATH:

curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf | sh

After the script finishes, open a new shell (or source the env file it prints) and bender --version should work from anywhere.

Installing a Specific Version

Pass the desired version (e.g. 0.31.0) as a positional argument:

curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf | sh -s -- 0.31.0

Note: Releases prior to v0.32.0 use a different (legacy) installer that drops the bender binary into the current directory by default. The --local / --global flags below let you pin a specific install location regardless of which installer is in play.

Forcing the Install Location

The defaults differ between installers (v0.32.0+ installs globally; pre-v0.32.0 installs into the current directory). Pass one of the following flags to pin the location explicitly:

  • --local: drop the bender binary into the current directory. Useful for CI jobs that prefer a project-local install or for trying Bender without modifying your shell environment. After a --local install, invoke Bender as ./bender or move the binary onto your PATH.
  • --global: install into ${CARGO_HOME:-$HOME/.cargo}/bin. For v0.32.0+ this also adds the directory to your PATH (default behavior); for older versions the binary is relocated there but you may need to add the directory to PATH manually.
# Latest release, local install
curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf | sh -s -- --local

# Specific (legacy) version, global install
curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf | sh -s -- --global 0.31.0

The position of the flag and version is interchangeable. --local and --global are mutually exclusive.

Note: The installer always overwrites an existing bender at the target location without prompting.

Alternative: Build from Source

If you prefer building your own binary, you will need to install Rust.

Using Cargo

You can install the latest official release directly from crates.io:

cargo install bender

Note: By default, Bender includes the pickle command which is backed by Slang. This requires a C++20 compliant compiler and increases build time significantly. To build without this feature, run:

cargo install bender --no-default-features

From Local Source

If you have cloned the repository, you can install the local version by running the following command from the project root:

cargo install --path .

Package Managers

Bender is also available through several third-party package managers:

Homebrew (macOS / Linux)

brew install bender

See the Homebrew formula for more details.

Nix

Bender is packaged in nixpkgs:

nix-env -iA nixpkgs.bender
# or, on a flake-enabled system:
nix profile install nixpkgs#bender

The repository also ships its own flake.nix, so you can run Bender directly from the latest source without installing:

nix run github:pulp-platform/bender

Arch Linux (AUR)

yay -S bender   # or any other AUR helper

See Bender on the AUR for the package page.

Note: Third-party packages may lag behind the latest GitHub release. For the most recent version, prefer the shell installer or cargo install.

Verifying Installation

After installation, verify that Bender is available in your terminal:

bender --version

Shell Completion

Bender supports shell completion for Bash, Zsh, Fish, and PowerShell. To enable it, use the completion command and source the output in your shell’s configuration file.

For example, for Zsh:

bender completion zsh > ~/.bender_completion.zsh
echo "source ~/.bender_completion.zsh" >> ~/.zshrc

Getting Started

This guide will walk you through creating your first Bender project, adding a dependency, and generating a simulation script.

1. Create a New Project

Start by creating a directory for your project and initializing it with Bender:

mkdir my_new_ip
cd my_new_ip
bender init

This creates a default Bender.yml file. Bender will automatically try to fill in your name and email from your Git configuration.

2. Add a Dependency

Open Bender.yml in your editor and add the common_cells library to the dependencies section:

package:
  name: my_new_ip
  authors: ["John Doe <john@doe.com>"]

dependencies:
  common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: "1.21.0" }

sources:
  - src/my_new_ip.sv

3. Resolve and Checkout

Now, tell Bender to resolve the version of common_cells and download it:

bender update

This command creates a Bender.lock file (the “exact” version chosen) and downloads the source code into a hidden .bender directory.

4. Add Source Code

Create a simple SystemVerilog file in src/my_new_ip.sv:

module my_new_ip (
  input  logic clk_i,
  output logic dummy_o
);
  // We can use a module from common_cells here!
  // ...
endmodule

5. Generate a Simulation Script

Finally, generate a compilation script for your EDA tool (e.g., QuestaSim/ModelSim):

bender script vsim > compile.tcl

You can now run vsim -do compile.tcl in your terminal to compile the entire project. See Generating Tool Scripts for the full set of output formats and flags.

Concepts

This section explains the core ideas and files that make Bender work. Understanding these concepts will help you manage complex hardware projects more effectively.

  • Principles: The high-level goals and design philosophy behind Bender.
  • Manifest: How to define your package’s metadata, dependencies, and sources.
  • Lockfile: How Bender ensures reproducible builds across different environments.
  • Local Configuration: Overriding settings for your local development workspace via Bender.local.
  • Comparing the Files: A quick comparison of the three core files (.yml, .lock, .local).
  • Dependencies: How Bender handles hierarchical and transitive dependencies.
  • Sources: Managing HDL source files, include directories, and defines.
  • Targets: Using boolean expressions to conditionally include or exclude files and dependencies.

Principles

Bender is built around two core principles, supported by three feature tiers that build on each other.

Be as opt-in as possible

Bender does not assume a specific EDA tool, workflow, or directory layout beyond a few key files. All features are designed to be modular, so they can be picked up individually and integrated into an existing flow. As long as a Bender.yml is present, Bender can manage the package.

Allow for reproducible builds

Bender maintains a precise lockfile which records the exact Git revision each dependency was resolved to. Committing this file alongside the manifest lets the exact source state of a package — for example at a tape-out or a release — be reconstructed after the fact.


Feature Tiers

Bender’s functionality is organized into three tiers, each building on the previous one. A package only needs to opt into the tiers it uses.

Tier 1: Source Collection

Collect and organize the HDL source files of a hardware IP:

  • Maintain the required order across files, e.g. for package declarations before their use.
  • Stay language-agnostic across SystemVerilog and VHDL.
  • Allow files to be organized into recursive groups.
  • Track defines and include directories individually for each group.

See Sources for the manifest format.

Tier 2: Dependency Management

Manage other packages an IP depends on and provide a local checkout of their sources:

  • Support transitive dependencies.
  • Resolve dependencies directly from Git rather than a central package registry. Projects containing IP under NDA can therefore use private repositories or local paths without exposing them.
  • Use Semantic Versioning to constrain compatible revisions.

See Dependencies for details.

Tier 3: Tool Script Generation

Generate source file listings and compilation scripts for various EDA tools, so the same set of resolved sources can be fed into simulation, synthesis, and downstream flows without manually maintaining tool-specific file lists.

See Generating Tool Scripts for the supported formats and options.

Manifest (Bender.yml)

The package manifest describes the package, its metadata, its dependencies, and its source files. All paths in the manifest may be relative, in which case they are understood to be relative to the directory that contains the manifest. A manifest is required for each bender package.

Note: The Bender.yml should be located at the root of the Git repository. Git-based dependency resolution currently relies on this assumption, and placing the manifest in a subdirectory of the repository may cause dependencies to not resolve correctly.

It is strongly recommended to start the Manifest file with a license header (open-source or proprietary) as a comment. This provides clarity on the project’s usage.

# Copyright (c) 2026 ETH Zurich and University of Bologna.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0

The first section of the manifest should be the package description, outlining the package itself. The package name is required and must match with the name used to call this bender package as a dependency. The name is interpreted in a case-insensitive manner within bender, so additional care should be taken to avoid name conflicts.

Additionally, authors and a description can be specified. Bender currently does not use these fields but supports their existence.

# Package metadata. Required.
package:
  # The name of the package. Required.
  name: magic-chip

  # The list of package authors and contributors. Optional.
  authors: ["John Doe <john@doe.si>"]

  # A short description of the package. Optional.
  description: "This is a magical chip"

The remotes section allows you to define shorthand names for Git repositories. This makes the dependencies section much cleaner by avoiding repeated long URLs.

# Specify git remotes for dependencies. Optional.
remotes:
  pulp: "https://github.com/pulp-platform"
  openhw:
    url: "https://github.com/openhwgroup"
    default: true # Used if no remote is specified in a dependency

The next section in the manifest is the dependencies. Basic projects not requiring any modules from dependencies can omit this section. All packages this bender project depends on should be listed here for proper functionality, including the version requirements. More details on the specific format can be found here.

# Other packages this package depends on. Optional.
dependencies:
    common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: "1.39" }

The sources section lists the HDL source files belonging to this package. It is optional for packages that only provide headers or are otherwise used without their own source files. More details on the format can be found here.

# List of source files in this package. Optional.
sources:
  # Individual source files.
  - src/pkg.sv
  - src/top.sv

Include directories that should be passed to all packages depending on this one are listed under export_include_dirs. This is the standard mechanism for sharing header files. The include directories listed here also apply to all files in the current package.

# Include directories exported to dependent packages. Optional.
export_include_dirs:
  - include

Setting frozen to true prevents Bender from updating dependencies beyond what is recorded in the lockfile. This is useful for chip packages in tape-out mode where dependency changes would require disastrous amounts of re-verification.

# Freeze dependency updates. Optional. Defaults to false.
frozen: true

The workspace section provides additional options for the local working environment. It is not relevant for library packages and is typically only used in the top-level chip package. Package links creates symlinks for the specified dependencies to known locations, while checkout_dir enforces the checkout of all dependencies to a specific location.

# Additional workspace configuration. Optional.
workspace:
  # Create symlinks to checked-out dependencies.
  package_links:
    links/axi: axi
    common: common_cells

  # Directory where dependencies will be checked out. Optional.
  # Once set, Bender performs the initial checkout and then leaves the directory
  # untouched if any modification is detected. Useful for chip packages that commit
  # all dependencies into their own version control.
  checkout_dir: deps

Note: If a package_links target already exists and is not a symlink to the expected destination, Bender skips it and emits warning W01 rather than overwriting it. If a dependency under a custom checkout_dir is not a Git working tree or has uncommitted changes, Bender emits warning W06 and leaves it alone — use bender snapshot --checkout --force to refresh it (see Package Development).

Packages can expose shell scripts as bender subcommands using the plugins section. These commands are available to packages that depend on this one and can be invoked as bender <cmd>.

# Package-provided commands callable as `bender <cmd>`. Optional.
plugins:
  hello: scripts/hello.sh

The vendor_package section lists files copied from external repositories that do not support Bender. Vendored files are managed with the bender vendor command. More details on the workflow can be found here.

# Vendorized files from external repositories not supporting Bender. Optional.
vendor_package:
  - name: lowrisc_opentitan
    target_dir: vendor/lowrisc_opentitan
    # Only commit hashes are supported for the upstream revision.
    upstream: { git: "https://github.com/lowRISC/opentitan.git", rev: "47a0f4798febd9e53dd131ef8c8c2b0255d8c139" }
    # Custom file mappings from upstream paths to local paths. Optional.
    mapping:
      - { from: "hw/ip/prim/rtl/prim_subreg.sv", to: "src/prim_subreg.sv" }

Lockfile (Bender.lock)

The lockfile, named Bender.lock, is a machine-generated file that records the exact version and Git revision (commit hash) of every dependency in your project’s tree.

Why a Lockfile?

While Bender.yml specifies your intent (e.g., “I need common_cells version 1.21.x”), the lockfile specifies the reality (e.g., “common_cells is version 1.21.5 at commit a1b2c3d”).

The lockfile ensures:

  • Reproducible Builds: Everyone on your team is working with the exact same code.
  • CI Stability: Your CI pipeline won’t suddenly fail because a dependency released a new (but incompatible) version.
  • Speed: Bender doesn’t need to re-resolve the entire dependency tree if the lockfile is already present.

How it Works

The lockfile is managed by two primary commands:

  • bender update: Scans manifests, resolves constraints, and updates the Bender.lock.
  • bender checkout: Reads the Bender.lock and ensures the local state matches the exact recorded revisions.

Structure of the Lockfile

The lockfile is written in YAML. For each package, it stores:

packages:
  common_cells:
    revision: 290c010c26569ec18683510e1688536f98768000
    version: 1.21.0
    source:
      git: "https://github.com/pulp-platform/common_cells.git"
    dependencies:
      - tech_cells_generic
  • revision: The full 40-character Git commit hash.
  • version: The SemVer version that was resolved.
  • source: Where to download the package from.
  • dependencies: A list of other packages that this specific package depends on, ensuring the entire tree is captured.

Best Practices

  • Commit it: Always check Bender.lock into your version control (Git).
  • Update with intention: Only run bender update when you actually want to pull in newer versions of your dependencies.
  • Review changes: When Bender.lock changes, review the diff to see exactly which packages were upgraded.

Frozen Manifests

If you want to prevent accidental updates to your project’s dependency tree, you can set frozen: true in your Bender.yml.

package:
  name: my_chip

frozen: true # Prevents 'bender update' from running

frozen is a top-level manifest field, not a member of the package block.

When frozen: true is set, bender update will fail, ensuring that your Bender.lock remains unchanged until you explicitly unfreeze the manifest. This is mostly recommended for late-stage tape-outs.

Local Configuration (Bender.local)

Bender.local is an optional, user-specific configuration file used to override the project’s default settings. Its primary purpose is to allow local development of dependencies without modifying the shared Bender.yml or Bender.lock.

Bender.local is one entry in Bender’s configuration file chain. See Configuration for the full precedence order, the list of available fields, and the equivalent environment variables and CLI flags.

Overriding Dependencies

The most common use for Bender.local is the overrides section. This forces Bender to use a specific version or a local path for a dependency, regardless of what the manifest requires.

Entries in overrides use the same syntax as entries in the dependencies section of Bender.yml (see Dependencies), with the exception that target expressions are not supported.

overrides:
  # Force a local path for 'common_cells'
  common_cells: { path: "../local_development/common_cells" }
  
  # Force a specific git URL/revision
  axi: { git: "https://github.com/my_fork/axi.git", rev: "experimental_branch" }

When an override is present, Bender will prioritize it over any other version resolution and emit warning W18 listing the package and the override target, so you can spot accidental overrides in Bender.local.

Note: overrides only replace dependencies that already exist somewhere in the resolved dependency tree. They cannot be used to introduce new dependencies that are not pulled in by any package’s Bender.yml.

Management

Bender.local can be managed both manually and automatically. Several Bender commands manage it for you during the development process:

  • bender clone: Automates moving a dependency to a local working directory and adds a path override.
  • bender snapshot: Updates Bender.local with the current Git hashes of your local checkouts.

For a detailed guide on using these commands for multi-package development, see the Package Development Workflow.

Other Configurations

Bender.local can also be used to configure tool-specific settings:

# Change the directory where dependencies are stored (default is .bender)
database: my_deps_cache

# Share only the bare git repos and lock files across projects, while
# keeping per-project checkouts under each project's own .bender/.
# Bender versions before 0.32 silently ignore this field.
db_dir: /var/cache/bender_shared

# Use a custom git binary or wrapper
git: /usr/local/bin/git-wrapper.sh

Best Practices

  • Don’t Commit It: Bender.local should rarely be checked into version control. It can contain paths and settings specific to your local machine. Always add it to your .gitignore.
  • Use for Development: Think of it as your “scratchpad” for multi-package development. Once your changes to a dependency are stable and released (tagged), remember to remove the override from Bender.local and update your Bender.yml with the new version.

Bender Files

Bender relies on three core files to manage your hardware project. While they all use the YAML format, they serve very different roles in the development lifecycle.

Comparison Overview

FeatureBender.ymlBender.lockBender.local
RoleManifest (Intent)Lockfile (Reality)Local Overrides
Main ContentDependencies & Version RangesExact Git RevisionsLocal Paths & Tool Config
Managed ByUser (Manual)bender update (Auto)User or bender clone
Version ControlCommitCommitIgnore (.gitignore)
Shared?Yes, with everyoneYes, for reproducibilityNo, unique to your machine

Summary of Roles

Bender.yml (The Manifest)

This is your Intent. It defines the requirements of your package. You use it to specify which other packages you need (e.g., “I need axi version 0.21.x”). It is the only file required to define a Bender package.

Bender.lock (The Lockfile)

This is the Reality. It is a machine-generated file that captures the exact state of your dependency tree. It records the specific Git commit hash for every dependency. By committing this file, you ensure that every developer and CI machine works with the exact same source code.

Bender.local (Local Overrides)

This is your Workspace. It allows you to override the shared configuration for your local environment. Its most common use is to point a dependency to a local directory (via bender clone) so you can modify its code and see the effects immediately in your top-level project.

Interaction Flow

  1. Define your requirements in Bender.yml.
  2. Run bender update to resolve those requirements into a fixed Bender.lock.
  3. Run bender checkout to download the exact source code specified in the lockfile.
  4. (Optional) Use Bender.local to temporarily swap a dependency for a local version during development.

Dependencies

Bender is designed to manage complex, hierarchical dependency trees for hardware projects. A dependency is any external package that provides its own Bender.yml manifest.

Dependency Types

Dependencies are defined in the dependencies section of your Bender.yml.

Git Dependencies

Git is the primary way to distribute Bender packages. You can specify them in two ways:

Bender uses Semantic Versioning (SemVer) to find the best compatible version. You can use SemVer operators to specify version ranges — the syntax matches Cargo’s, see the Cargo dependency reference for a more accessible overview:

dependencies:
  common_cells: { git: "https://github.com/pulp-platform/common_cells.git", version: "1.21.0" }
  axi: { git: "https://github.com/pulp-platform/axi.git", version: ">=0.23.0, <0.26.0" }

If you have defined a remote in your manifest, you can use the shorthand notation instead:

dependencies:
  common_cells: "1.21.0"
  axi: { version: ">=0.23.0, <0.26.0" }

Note: Bender only recognizes Git tags that follow the vX.Y.Z format (e.g., v1.2.1).

Revision-based

Use this for specific commits, branches, or tags that don’t follow SemVer.

dependencies:
  pulp_soc: { git: "https://github.com/pulp-platform/pulp_soc.git", rev: "develop" }

Path Dependencies

Path dependencies point to a local directory. They are never versioned; Bender simply uses the code found at that location.

dependencies:
  my_local_ip: { path: "../local_ips/my_ip" }

Remotes

To avoid repeating full Git URLs, you can define remotes in your manifest.

Single Remote

If you only define a single remote, it is automatically treated as the default:

remotes:
  pulp: "https://github.com/pulp-platform"

dependencies:
  common_cells: "1.21.0" # Automatically searched in the 'pulp' remote

Multiple Remotes

When using multiple remotes, you must explicitly mark one as the default if you want to use shortened dependency syntax:

remotes:
  pulp:
    url: "https://github.com/pulp-platform"
    default: true
  openhw: "https://github.com/openhwgroup"

dependencies:
  common_cells: "1.21.0"              # Uses the default 'pulp' remote
  cva6: { version: "4.0.0", remote: openhw } # Explicitly uses 'openhw'

Differing Repository Name

By default, Bender appends the dependency’s local name to the remote URL when resolving the Git URL. If the upstream repository is named differently from how you want to refer to the dependency locally, use upstream_name. Without this field, a mismatch between a dependency’s local name and the package.name declared inside the dependency’s own Bender.yml triggers warning W11.

remotes:
  pulp: "https://github.com/pulp-platform"

dependencies:
  cells: { version: "1.21.0", upstream_name: "common_cells" }
  # Resolves to https://github.com/pulp-platform/common_cells.git

You can also embed the dependency name explicitly in the remote URL using the {} placeholder, which is useful for non-trivial URL patterns:

remotes:
  pulp: "https://gitlab.example.com/scm/{}.git"

Targets

Dependencies can be conditionally included or configured using targets. For details on how to use target expressions or pass targets to dependencies, see the Targets documentation.

Note: A target on a dependency only filters that dependency out of source listings and generated scripts. It does not affect dependency resolution: every dependency declared in Bender.yml is still resolved and recorded in Bender.lock regardless of which targets are active.

Git LFS Support

Bender detects whether a dependency uses Git Large File Storage (LFS) via its .gitattributes and reacts as follows:

  • If LFS is detected and git-lfs is installed, Bender configures LFS and pulls the required files automatically.
  • If LFS is detected but git-lfs is not installed, Bender emits warning W26 and continues the checkout. You may end up with pointer files instead of the actual large files, which can cause downstream build failures — install git-lfs to resolve this.
  • If LFS is disabled in your configuration (git_lfs: false) but the dependency appears to use LFS, Bender emits warning W27.
  • If the repository does not use LFS, Bender skips LFS operations entirely.

Submodules

If a dependency contains a .gitmodules file, Bender will automatically initialize and update its Git submodules recursively after checkout. No additional configuration is required.

Version Resolution and the Lockfile

When you run bender update, Bender performs the following:

  1. Resolution: It scans the entire dependency tree and finds a set of versions that satisfy all constraints.
  2. Locking: The exact versions and Git commit hashes are written to Bender.lock.

For details on updating dependencies, see Adding and Updating Dependencies

Reproducibility: Once a Bender.lock exists, running bender checkout will always download the exact same code, even if newer compatible versions have been released. Always commit your Bender.lock to version control.

Sources

The sources section in Bender.yml defines the source files, include directories, and preprocessor definitions that make up your package.

Basic File Listing

The simplest way to include files is to list them as strings. All paths are relative to the location of the Bender.yml file.

sources:
  - src/my_pkg.sv
  - src/my_ip.sv
  - src/my_vhdl.vhd

Source Groups

You can group files together to apply common settings like include directories, preprocessor defines, or targets.

sources:
  - include_dirs:
      - include
      - src/common/include
    defines:
      USE_FAST_ALU: ~        # Define without a value
      BIT_WIDTH: 64          # Define with a value
    target: synthesis
    files:
      - src/rtl/alu.sv
      - src/rtl/top.sv
  • include_dirs: Paths added to the +incdir+ flag during compilation. These directories apply only to the files in this source group and any nested child groups; they are generally not visible to sibling groups or to dependents of the package. To expose headers to other packages, use export_include_dirs instead.
  • defines: Preprocessor macros added via +define+.
  • target: A target expression that determines if this entire group is included in the current flow.

Target-Conditional Include Dirs and Defines

Individual entries in include_dirs and defines can also be filtered by target. Use the { target: <expr>, dir: <path> } form for include directories, and { target: <expr>, value: <value> } for define values:

sources:
  - include_dirs:
      - include
      # Only added when the `fpga` target is active
      - { target: fpga, dir: include/fpga }
    defines:
      # Static define
      BIT_WIDTH: 64
      # Only set when the `test` target is active
      TEST_COMPONENTS: { target: any(test, regression_test), value: "all" }
    files:
      - src/rtl/top.sv

The same { target: <expr>, dir: <path> } form is accepted in export_include_dirs.

Glob Patterns

Bender supports glob patterns for automatically including multiple files without listing them individually:

sources:
  - src/*.sv                # All .sv files in the src directory
  - src/submodules/**/*.sv  # All .sv files in all subdirectories (recursive)

Custom File Types

While Bender automatically detects file types by standard extensions (.sv, .v, .vhd), you can explicitly specify the type for encrypted or non-standard files:

sources:
  - sv: vendor/encrypted_src.svp
  - v: vendor/legacy_code.vp
  - vhd: vendor/encrypted_vhdl.e

External File Lists (external_flists)

If you have existing EDA tool file lists (often .f or .flist), you can include them directly. Bender will attempt to parse them for source files, include directories, and defines:

sources:
  - external_flists:
      - scripts/files.f
    files: []

File Overrides

The override_files: true flag allows a source group to replace files with the same basename from other parts of the dependency tree. Use it to “patch” dependencies or swap implementations at the top level.

sources:
  - override_files: true
    files:
      - patches/axi_fifo.sv # Will replace any 'axi_fifo.sv' found in the dependency tree

Exported Include Directories

If your package provides headers that its dependents need (e.g., UVM macros or shared packages), use the export_include_dirs section at the top level of the manifest:

package:
  name: my_utils

export_include_dirs:
  - include

Any package that depends on my_utils will automatically have the include directory added to its include search path.

Best Practice: To avoid naming collisions, place your headers in a sub-folder named after your package.

The Problem: If two packages (axi and apb) both export a header named typedefs.svh, a file including `include "typedefs.svh" will be ambiguous, and the compiler will pick whichever directory it finds first in the include path.

The Solution: Structure your files as include/axi/typedefs.svh and include/apb/typedefs.svh, then include them with the package prefix:

`include "axi/typedefs.svh"
`include "apb/typedefs.svh"

Targets

Targets are the primary mechanism in Bender for managing project configurations. They allow you to conditionally include source files, include directories, and dependencies based on the current context (e.g., simulation vs. synthesis, or FPGA vs. ASIC) or the desired configuration (e.g., with/without cache).

Target Expressions

Bender uses a simple boolean expression language for targets:

  • *: Wildcard, matches all target sets.
  • name: Matches if the target name is active (case-insensitive).
  • all(T1, T2, ...): Matches if all arguments match (boolean AND).
  • any(T1, T2, ...): Matches if at least one argument matches (boolean OR).
  • not(T): Matches if T does not match (boolean NOT).
  • (T): Parentheses for grouping.

Syntax Rules

Target names can contain alphanumeric characters, dots (.), underscores (_), and hyphens (-).

Restrictions:

  • They cannot contain colons (:), as colons are used for package-specific targets in the CLI.
  • They cannot start with a hyphen (-), as leading hyphens are used to disable targets in the CLI.

Logical Examples

  • all(asic, synthesis): Matches when both ‘asic’ and ‘synthesis’ are set.
  • any(vsim, vcs, riviera): Matches if any of the listed simulation tools are active.
  • not(simulation): Matches only when ‘simulation’ is not set.
  • any(test, all(rtl, simulation)): Matches for testbenches, or for RTL code during simulation.

Usage in Bender.yml

Source Groups

You can wrap a group of files in a target specification. This allows you to manage different implementations or verification components within the same package.

Testbench Inclusion

Ensures that verification-only code is never included in the synthesis or production flow:

sources:
  # RTL sources
  - files:
      - src/core.sv
      - src/alu.sv

  # Testbench only
  - target: test
    files:
      - tb/driver.sv
      - tb/tb_top.sv

Simulation vs. Synthesis

Commonly used to swap between an actual hardware macro and a fast behavioral model for simulation:

sources:
  # Behavioral model for faster simulation
  - target: all(simulation, not(synthesis))
    files:
      - src/behavioral/ip_model.sv

Technology Selection

Useful when choosing between different physical implementations, such as ASIC standard cells versus FPGA primitives:

sources:
  - target: asic
    files:
      - target/asic/clock_gate.sv
  - target: fpga
    files:
      - target/fpga/clock_gate.sv

Core Configuration

Targets can be used to select between different hardware architectures or feature sets:

sources:
  # 32-bit architecture
  - target: rv32
    files:
      - src/core/alu_32.sv
      - src/core/regfile_32.sv
  # 64-bit architecture
  - target: rv64
    files:
      - src/core/alu_64.sv
      - src/core/regfile_64.sv

Dependencies

You can make a dependency conditional using target expressions. This is commonly used to include verification IP or platform-specific components only when needed:

dependencies:
  # Included only during simulation
  uvm: { version: "1.2.0", target: simulation }
  # Included for either test or simulation
  common_verification: { version: "0.2", target: any(test, simulation) }

Built-in Targets

Bender automatically activates certain targets based on the subcommand and output format. These “default targets” ensure that tool-specific workarounds or flow-specific files are included correctly. You can disable this behavior with the --no-default-target flag.

Script Format Targets

The bender script command activates the following targets based on the chosen format:

FormatDefault Targets
flist, flist-plusflist
vsimvsim, simulation
vcsvcs, simulation
verilatorverilator, synthesis
synopsyssynopsys, synthesis
formalitysynopsys, synthesis, formality
rivierariviera, simulation
genusgenus, synthesis
vivadovivado, fpga, xilinx, synthesis
vivadosimvivado, fpga, xilinx, simulation
precisionprecision, fpga, synthesis

Special Targets

  • RTL: If you use the --assume-rtl flag, Bender will automatically assign the rtl target to any source group that does not have an explicit target specification.
  • ASIC: While asic is a common convention, it is not set automatically by Bender. It should be manually activated via -t asic when needed.

Bender does not enforce these, but the following user-defined targets are widely used across PULP projects and are a good default for new packages:

  • test — testbench code and verification IP, kept out of synthesis flows.
  • rtl — synthesizable RTL code.
  • gate — gate-level netlists, used in post-synthesis simulation and timing flows.

Similarly, asic vs. fpga is the conventional way to swap between technology-specific implementations.

Activating Targets via CLI

Use the -t or --target flag with Bender commands:

# Enable the 'test' target
bender script vsim -t test

# Enable multiple targets
bender script vivado -t synthesis -t fpga

Advanced CLI Syntax

  • Package-specific: -t my_pkg:my_target activates my_target only for my_pkg.
  • Negative targets: -t -old_target explicitly disables old_target.

Passing Targets Hierarchically

Bender allows you to configure your dependencies by passing specific targets down to them using the pass_targets field. This lets you propagate global settings or select alternative implementations within sub-modules.

Simple Passing

You can pass a target name as a string, which will then always be active for that specific dependency:

dependencies:
  my_submodule:
    version: "1.0.0"
    pass_targets: ["enable_debug"] # 'enable_debug' is always active for my_submodule

Conditional Passing

You can also pass targets conditionally, based on the targets active in the parent package:

dependencies:
  ariane:
    version: 5.3.0
    pass_targets:
      - {target: rv64, pass: "cv64a6_imafdcv_sv39"}
      - {target: rv32, pass: "cv32a6_imac_sv32"}

In this example, if the rv64 target is active globally, Bender will ensure the cv64a6_imafdcv_sv39 target is active specifically for the ariane dependency.

Global and Local Configuration

Bender uses a flexible configuration system that combines files, environment variables, and command-line flags. This page documents the configuration fields and their resolution order; for the role of Bender.local within this system and its use as a per-workspace override file, see Local Configuration.

Configuration Methods and Precedence

When resolving a configuration setting, Bender follows this order of precedence (highest to lowest):

  1. Command-Line Flags: Explicitly passed arguments (e.g., --git-throttle 8).
  2. Environment Variables: System-level variables (e.g., BENDER_GIT_THROTTLE=8).
  3. Configuration Files: Settings loaded from YAML files. These files are merged, with lower-level files overwriting higher-level ones:
    • Bender.local (Local workspace, ignored by Git)
    • .bender.yml (Project-specific, checked into Git)
    • $HOME/.config/bender.yml (User-specific)
    • /etc/bender.yml (System-wide)

Path Substitution

On Unix-like systems, paths within configuration files can use environment variables (e.g., $HOME or ${VAR}). These will be automatically substituted when the configuration is loaded.


Configuration Fields

database

The directory where Bender stores cloned and checked-out dependencies.

  • Config Key: database
  • Default: .bender in the project root.
  • Example: database: /var/cache/bender_dependencies

db_dir

Optional override for the directory that holds bare git repositories and their lock files (i.e. <db_dir>/git/db/ and <db_dir>/git/locks/). When set, it takes precedence over database (whether explicitly configured or left at its default) for these two paths only; the working-tree checkouts continue to follow database (or workspace.checkout_dir in the project manifest). This makes it possible to share the heavy git data across projects on a persistent runner without also relocating per-project checkouts. See Continuous Integration › Sharing the Database for the recommended setup.

  • Config Key: db_dir
  • Env Var: BENDER_DB_DIR (used only when no configuration file sets db_dir; configuration files always take precedence).
  • Default: unset (falls back to database).
  • Example: db_dir: /var/cache/bender_shared

Note: Bender versions before 0.32 silently ignore this field and fall back to their per-project default, so it is safe to ship in a shared configuration that mixed bender versions may read.

git

The command or path used to invoke Git.

  • Config Key: git
  • Default: git
  • Example: git: /usr/local/bin/git-wrapper.sh

git_throttle

The maximum number of concurrent Git operations.

  • Config Key: git_throttle
  • CLI Flag: --git-throttle
  • Env Var: BENDER_GIT_THROTTLE
  • Default: 4
  • Example: git_throttle: 8

git_lfs

Enable or disable Git Large File Storage (LFS) support. Requires git-lfs to be installed on the system.

  • Config Key: git_lfs
  • Default: true
  • Example: git_lfs: false

overrides

Forces specific dependencies to use a particular version or local path. This is primarily used in Bender.local for development.

  • Config Key: overrides
  • Example:
    overrides:
      common_cells: { path: "../local_development/common_cells" }
    

plugins

Auxiliary plugin dependencies that are loaded for every package. These allow you to provide additional Bender subcommands across your entire environment. The entry uses the same format as the dependencies section of a manifest.

  • Config Key: plugins

Deprecated: Configuring plugins from the configuration file is deprecated and may be removed in a future release. Prefer declaring plugins in the package manifest (Bender.yml).


Global CLI Options

The following options can be set via command-line flags or environment variables, but do not have a corresponding key in the configuration files.

dir

Sets a custom root working directory. This directory is used as the starting point to search for Bender.yml and configuration files. It also determines the default location of the database.

  • CLI Flag: -d, --dir
  • Env Var: BENDER_DIR
  • Default: Current working directory.

local

Disables fetching of remotes. Useful for working on air-gapped computers or when you want to ensure no network operations occur. When set, Bender emits warning W14 to remind you that resolution may not pick up newly available versions.

  • CLI Flag: --local
  • Env Var: BENDER_LOCAL

verbose

Increases logging verbosity.

  • CLI Flag: -v, -vv, -vvv (info, debug, trace)
  • Env Var: BENDER_VERBOSE (set to 1, 2, or 3)

no_progress

Disables interactive progress bars.

  • CLI Flag: --no-progress
  • Env Var: BENDER_NO_PROGRESS

suppress

Suppresses specific warnings.

  • CLI Flag: --suppress <WARNING> (can be used multiple times)
  • Env Var: BENDER_SUPPRESS_WARNINGS (comma-separated list)

Usage Example

A typical Bender.local file used for local development might look like this:

# Speed up git operations locally
git_throttle: 8

# Work on a local copy of common_cells
overrides:
  common_cells: { path: "../common_cells" }

Workflows

This section provides practical guides on how to use Bender for common hardware development tasks throughout the project lifecycle.

Initialization

To start a new Bender project, use the init command to set up a basic manifest:

bender init

How it Works

Bender attempts to intelligently pre-fill your Bender.yml with the following information:

  • Package Name: Set to the name of the current working directory.
  • Authors: Pulled automatically from your global Git configuration (git config user.name and git config user.email).

The Generated Manifest

The command creates a Bender.yml file with a structure similar to this:

package:
  name: my_new_ip
  authors:
    - "John Doe <john@doe.com>"

dependencies:

sources:
  # Source files grouped in levels. 
  # Level 0

Once the manifest is created, you can begin adding your dependencies and source files to the respective sections.

Adding and Updating Dependencies

As working with dependencies is one of bender’s main features, there are a few commands to ensure functionality and assist with understanding the dependency structure. For background on how dependencies are declared in the Bender.yml manifest, see Dependencies.

New dependencies

Once new dependencies are added to the manifest, bender needs to first be made aware of their existence. Otherwise, the internally used dependency tree will be incorrect or mismatched, and some commands will not work correctly, returning an error. To update dependencies, run the following command:

bender update

In case other dependencies already exist and you do not want to re-resolve these, you can add the --new-only flag to the update command.

Note: On update, bender creates or modifies the Bender.lock, which keeps track of the currently selected dependency versions.

Note: Most bender commands will automatically run an update if no Bender.lock is found.

Updating dependencies

Similar to when adding new dependencies, updating existing dependencies to more recent versions is also done with the update command.

During an update, Bender picks the highest SemVer-compatible version and a unique revision that simultaneously satisfies the constraints of every package in the dependency tree. If the requirements conflict and no single revision satisfies them, Bender will prompt you to choose how to resolve the conflict. See Dependencies for more on version resolution.

Single Dependency Update

You don’t always have to update your entire dependency tree. To update only a specific package, provide its name:

bender update <PKG_NAME>

Recursive Updates

By default, updating a single dependency will not update its own child dependencies. If you want to update a package and all of its dependencies recursively, use the -r/--recursive flag:

bender update <PKG_NAME> --recursive

Checking out Dependencies

While bender update resolves versions and updates the Bender.lock, it does not necessarily download all the source code. To ensure all dependencies are locally available, use:

bender checkout

Bender will download the exact revisions specified in Bender.lock. This command is safe to run multiple times; it will only download missing packages or update those that have changed in the Bender.lock.

Note: Many other commands (like sources or script) will automatically trigger a checkout if they detect missing dependencies.

Inspecting the Dependency Tree (packages)

To see the full list of dependencies and how they relate to each other, use the packages command:

bender packages

This prints a tree structure showing every package in your project, its resolved version, and its source. Use the -f/--flat flag if you just want a simple list of names and versions.

Finding Package Paths (path)

If you need to know where a specific dependency is stored (e.g., to point an external tool to it), use:

bender path <PKG_NAME>

This will output the absolute path to the dependency’s checkout directory. If the package has not yet been checked out, bender path will check it out first.

Checking Usage (parents)

If you are wondering why a specific dependency was included or why a certain version was forced, you can use:

bender parents <PKG_NAME>

This will show you all packages in your tree that depend on <PKG_NAME> and what version constraints they have placed on it.

Auditing the Dependency Tree (audit)

To get a quick overview of the state of your dependencies, including outdated packages, version conflicts, and dependencies pinned to a Git hash rather than a SemVer version, use:

bender audit

The output classifies each package as one of:

  • Up-to-date: the resolved version is the highest available.
  • Auto-update: a newer version exists that still satisfies all constraints; can be picked up by running bender update.
  • Update: a newer version exists but is outside the current SemVer constraints; requires manual updating of constraints in Bender.yml.
  • Hash: the dependency is pinned to a specific Git revision rather than a SemVer version.
  • Path: the dependency points to a local directory.
  • Conflict: the dependency tree has incompatible version requirements or remote URLs — use bender parents <PKG> to investigate.

Useful flags:

  • --only-update: show only packages that have a possible update.
  • -f/--fetch: force a re-fetch of all Git remotes before auditing.
  • --ignore-url-conflict: suppress conflict reporting when the same dependency is reached via different remote URLs (e.g. an HTTPS and an SSH URL for the same repository).

Generating Tool Scripts

Bender’s script command is the bridge between dependency management and your EDA tools. It generates TCL or shell scripts that include all necessary source files, include directories, and preprocessor defines in the correct order.

Basic Usage

The script command requires a format (the target EDA tool or output style):

bender script <FORMAT>

For example, to generate a script for ModelSim/QuestaSim:

bender script vsim > compile.tcl

Supported Formats

Bender supports a wide range of EDA tools and generic formats:

FormatTool / Use CaseOutput Type
vsimModelSim / QuestaSimTCL
vcsSynopsys VCSShell
verilatorVerilatorShell
vivadoXilinx Vivado (Synthesis)TCL
vivadosimXilinx Vivado (Simulation)TCL
synopsysSynopsys Design CompilerTCL
genusCadence GenusTCL
flist / flist-plusGeneric file listsText

For a full list of formats and their specific options, see the Command Reference.

Targets and Filtering

When you generate a script, Bender automatically activates certain built-in targets. For example, bender script vsim automatically enables the simulation and vsim targets.

You can manually enable additional targets using the -t/--target flag:

# Generate a simulation script with the 'test' and 'gate' targets enabled
bender script vsim -t test -t gate > compile.tcl

Useful Flags

The script command provides several flags to fine-tune the generated output:

Package Filtering

  • -p/--package <PKG>: Only include source files from the specified package (and its dependencies).
  • -n/--no-deps: Exclude all dependencies. This generates a script containing only the files from the current package or the packages explicitly listed with -p.
  • -e/--exclude <PKG>: Exclude a specific package from the generated script.

RTL Assumption

  • --assume-rtl: Automatically adds the rtl target to any source group that does not have an explicit target specification. This is an optional shorthand for generating synthesis scripts without having to tag every RTL file.

Compilation Control

  • --compilation-mode <separate|common>:
    • separate (default): Compiles each source group as a separate unit.
    • common: Attempts to compile all source groups together in a single compilation unit.
  • --no-abort-on-error: Tells the EDA tool to continue analysis/compilation even if errors are encountered in individual files.

Metadata and Debugging

  • --source-annotations: Includes comments in the generated script that indicate which Bender package and source group each file belongs to. This is very helpful for debugging ordering or missing-file issues.
  • --no-default-target: Disables the automatic activation of built-in targets (like simulation for vsim). Use this if you want absolute control over which targets are active.

Advanced Options

Adding Defines

You can pass additional preprocessor macros to all files in the script using the -D flag:

bender script vsim -D USE_DDR4 -D CLK_FREQ=100

Relative Paths

For generic file lists (flist or flist-plus), you can force Bender to use paths relative to the current directory using the --relative-path flag. This is useful for sharing file lists between different machines or environments.

Custom Templates

Bender uses the Tera templating engine for its scripts. If the built-in formats don’t meet your needs, you can provide your own template file:

bender script template --template my_custom_format.tera > output.txt

To help develop a custom template, you can inspect the JSON structure that Bender passes to the template engine using:

bender script template_json

This prints the same data that would be exposed to your .tera template, making it easy to discover available fields.

Package Development

Bender makes it easy to develop multiple packages simultaneously. If you find yourself needing to modify a dependency, you don’t have to manually manage local paths and Git remotes. Instead, you can use the clone and snapshot workflow.

The Development Workflow

1. Clone the Dependency

Use the clone command to move a dependency from Bender’s internal cache into a local directory where you can modify it:

bender clone <PKG_NAME>

By default, the package is checked out into a working_dir folder (you can change this with -p/--path). Bender automatically:

  1. Performs a git clone of the dependency into that folder.
  2. Adds a path override to your Bender.local file.

Now, any changes you make in that folder are immediately reflected in your top-level project when you run Bender commands.

2. Modify and Commit

You can now work on the cloned package as if it were a normal Git repository. You can add files, fix bugs, and commit your changes within that directory.

3. Snapshot the State

Once you have committed changes in your cloned dependency and want to record that specific state (for sharing with others or for CI), use the snapshot command:

bender snapshot

Bender will:

  1. Detect all dependencies that are currently overridden by a local path.
  2. Check the current Git commit hash of those local repositories.
  3. Update Bender.local to use a git override with that specific rev (commit hash).
  4. Automatically update Bender.lock to include these exact revisions.

Why use Snapshot? The main benefit of a snapshot is portability. Because the lockfile is updated with the specific commit hashes, you can commit Bender.lock and share it with colleagues or run it in CI. The other environments will download the exact revisions you were working on from the Git remotes, without needing access to your local development paths.

Useful flags:

  • --working-dir <DIR>: scan a different directory for local checkouts (defaults to working_dir, matching bender clone’s default).
  • --checkout: after writing the lockfile, also check out the resolved revisions into the configured checkout_dir (if any). Dependencies with uncommitted changes are skipped by default; warning W25 is emitted for each one. Pass --no-skip to snapshot them anyway.
  • --force: combined with --checkout, overwrite an existing custom checkout_dir. Use with care, as this can discard local work.

Finalizing Changes

Once your changes are stable and you are ready to “release” them:

  1. Tag the Dependency: Push your changes to the remote repository and create a new version tag (e.g., v1.2.2).
  2. Update Manifest: Update the version requirement in your Bender.yml to include the new version.
  3. Clean Up: Remove the local overrides from your Bender.local file.
  4. Resolve: Run bender update to re-resolve the dependency tree and update Bender.lock to point to the new official version.

Vendorizing External Code

Bender’s vendor command allows you to manage external dependencies that aren’t natively packaged for Bender. It works by copying a subset of files from an upstream Git repository into a local directory within your project, allowing you to track changes and maintain local patches.

This flow is heavily inspired by the vendor.py script used in the OpenTitan project.

Configuration

Vendorized packages are defined in the vendor_package section of your Bender.yml:

vendor_package:
  - name: my_ip
    target_dir: "deps/my_ip"
    upstream: { git: "https://github.com/external/my_ip.git", rev: "abcd123" }
    include_from_upstream:
      - "src/*.sv"
      - "include/*.svh"
    exclude_from_upstream:
      - "src/deprecated/*"
    patch_dir: "deps/patches/my_ip"
    mapping:
      - {from: 'src/old_name.sv', to: 'src/new_name.sv' }

Key Fields

  • name: A unique identifier for the vendorized package.
  • target_dir: Where the files should be copied to in your repository.
  • upstream: The Git repository and specific revision (commit/tag/branch) to pull from.
  • include_from_upstream: (Optional) Glob patterns of files to copy.
  • exclude_from_upstream: (Optional) Glob patterns of files to ignore.
  • patch_dir: (Optional) A directory containing .patch files to apply after copying.
  • mapping: (Optional) A list of specific file renames or movements during the copy process.

The Vendor Workflow

Using the configuration above, here is how you manage a vendorized IP:

1. Initialize (init)

To download the upstream code and copy it into your target_dir:

bender vendor init

Bender clones the upstream repository, filters the files based on your rules, and copies them to your project. If a patch_dir is specified, any existing patches are applied automatically.

Important: Vendored files are not automatically picked up as compilation sources. After bender vendor init, you still need to list the relevant files (or globs matching them) in the sources section of your Bender.yml for them to participate in script generation and source collection.

2. Make Local Changes and Diff

Assume you need to fix a bug in deps/my_ip/src/top.sv. You edit the file directly in your workspace. You can see how your local code differs from the upstream source (plus existing patches) by running:

bender vendor diff

3. Create a Patch (patch)

To make your fix permanent and shareable, stage the change and generate a patch:

# Stage the change in your main repository
git add deps/my_ip/src/top.sv

# Generate the patch file
bender vendor patch

Bender will prompt for a commit message and create a numbered patch file in your patch_dir (e.g., 0001-fix-bug.patch).

4. Commit Everything

Now you can create an atomic commit in your repository that contains both the modified source and the new patch file:

git add deps/patches/my_ip/0001-fix-bug.patch
git commit -m "Update my_ip with local bugfix"

The next time you (or a teammate) run bender vendor init, Bender will pull the fresh upstream code and automatically apply your patch.

Upstreaming Patches

If you want to contribute your fix back to the upstream repository:

  1. Clone the upstream repository separately.
  2. Check out the same revision (abcd123).
  3. Apply your patch: git am /path/to/your_repo/deps/patches/my_ip/0001-fix-bug.patch.
  4. The fix is now a proper Git commit in the upstream repo, with all metadata (author, timestamp) preserved.

When to use Vendor?

Use the vendor flow when:

  • The external IP does not have its own Bender.yml.
  • You need to include only a small subset of a massive repository.
  • You must maintain local modifications (bug fixes or tool workarounds) that haven’t been merged upstream yet.

Continuous Integration

Integrating Bender into your CI/CD pipeline ensures that your hardware project is consistently built, linted, and simulated using the exact same dependency versions as your local environment.

Reproducibility

In a CI environment, you should never run bender update. Instead, your pipeline should rely on the Bender.lock file to fetch the exact revisions of your dependencies.

  1. Commit Bender.lock: Ensure the lockfile is checked into your repository.
  2. Use bender checkout: This command reads the lockfile and reconstructs the dependency tree without changing any versions.

GitHub Actions

If you are using GitHub Actions, the pulp-platform/pulp-actions repository provides a dedicated action to simplify the setup.

bender-install

The bender-install action handles downloading the Bender binary and adding it to your PATH.

steps:
  - uses: actions/checkout@v4
  
  - name: Install Bender
    uses: pulp-platform/pulp-actions/bender-install@v2
    with:
      version: 0.31.0 # Optional: specify a version

  - name: Checkout dependencies
    run: bender checkout

GitLab CI

For GitLab CI, you can use the standard installation script to fetch the Bender binary at the start of your job if bender is not already installed in the system.

Example Workflow

variables:
  BENDER_VERSION: "0.31.0"

before_script:
  # Install Bender locally so the binary lands in the current directory across
  # all versions, then add CWD to PATH. (For v0.32.0+ the installer would
  # otherwise default to a global install; --local pins the project-local
  # behavior. For pre-v0.32.0 versions --local is a no-op.)
  - curl --proto '=https' --tlsv1.2 https://pulp-platform.github.io/bender/init -sSf | sh -s -- --local $BENDER_VERSION
  - export PATH=$PATH:$(pwd)

sim_job:
  stage: test
  script:
    - bender checkout
    - bender script vsim > compile.tcl
    - # Run your simulation tool here...

Caching

Since Bender.lock uniquely identifies the state of all dependencies, it is theoretically possible to cache the .bender directory to speed up pipelines. However, for most projects, the overhead of managing the cache (uploading/downloading) might outweigh the time saved by bender checkout, especially with fast network connections to Git remotes.

Caching is only recommended for projects with exceptionally large dependency trees or slow network access.

Caching Examples

GitHub Actions

  - name: Cache Bender dependencies
    uses: actions/cache@v4
    with:
      path: .bender
      key: bender-${{ hashFiles('**/Bender.lock') }}

GitLab CI

cache:
  key:
    files:
      - Bender.lock
  paths:
    - .bender/

Note on Cache Keys: We use the hash of Bender.lock as the cache key. This ensures the cache is only reused when the dependencies haven’t changed.

Sharing the Database Across Runs and Projects

When jobs run on a persistent runner, the bare git repositories Bender clones can be shared across CI runs and even across projects to drastically reduce fetch times and disk usage. The recommended way is the db_dir setting, which relocates only the bare repos and lock files; per-project working-tree checkouts stay under each project’s own .bender/ directory and cannot collide.

Place a Bender.local in a parent directory of your projects so it is picked up automatically:

db_dir: /var/cache/bender_shared

Every job on the runner now reuses the already-fetched Git data and serializes safely against concurrent jobs via per-dependency filesystem locks living next to the bare repos (<db_dir>/git/locks/).

If you’d rather not place a Bender.local on the runner at all, exporting BENDER_DB_DIR=/var/cache/bender_shared in the job environment has the same effect — any project that explicitly sets db_dir in its own configuration overrides the env var.

Bender versions before 0.32 silently ignore both db_dir and BENDER_DB_DIR and fall back to their normal per-project .bender/ cache, so the shared config above is safe to deploy on a runner that still hosts pinned-to-old-bender projects — those projects simply won’t benefit from the shared cache, but they won’t misbehave either.

Sharing working-tree checkouts too

If you want to share the bare repos and the per-dependency working-tree checkouts (uncommon), use database instead of db_dir:

database: /var/cache/bender_shared

Caveat: two top-level projects with the same name will collide on the same <database>/git/checkouts/<name>-<hash>/ directory. Give each top-level project its own workspace.checkout_dir in Bender.yml so the checkouts remain isolated.

Commands

bender is the entry point to the dependency management system. Bender always operates within a package; starting at the current working directory, search upwards the file hierarchy until a Bender.yml is found, which marks the package.

path — Get the path of a checked-out package

The bender path <PKG> prints the path of the checked-out version of package PKG. One or more package names may be passed.

Useful in scripts:

#!/bin/bash
cat `bender path mydep`/src/hello.txt

If a package has not been checked out yet, bender path checks it out before printing. Pass --checkout to force a re-checkout even when the directory already exists.

packages — Display the dependency graph

  • bender packages: List the package dependencies. The list is sorted and grouped according to a topological sorting of the dependencies. That is, leaf dependencies are compiled first, then dependent ones.
  • bender packages -f/--flat: Produces the same list, but flattened.
  • bender packages -g/--graph: Produces a graph description of the dependencies of the form <pkg>TAB<dependencies...>.
  • bender packages --version (alias --versions): Print the resolved version of each package. Implies --flat.
  • bender packages --targets (alias --target): Print the available targets for each package.

sources — List source files

Code

Produces a sources manifest, a JSON description of all files needed to build the project. See Sources for the manifest format and Dependencies for how dependencies contribute their sources.

The manifest is recursive by default; meaning that dependencies and groups are nested. Use the -f/--flatten switch to produce a simple flat listing.

To enable specific targets, use the -t/--target option. Adding a package and colon <PKG>:<TARGET> before a target will apply the target only to that specific package. Prefixing a target with - will remove that specific target, even for predefined targets (e.g., -t-<TARGET> or -t <PKG>:-<TARGET>).

To get the sources for a subset of packages, exclude specific packages and their dependencies, or exclude all dependencies, the following flags exist:

  • -p/--package: Specify package to show sources for.
  • -e/--exclude: Specify package to exclude from sources.
  • -n/--no-deps: Exclude all dependencies, i.e. only top level or specified package(s).

For multiple packages (or excludes), multiple -p (or -e) arguments can be added to the command.

Additional flags:

  • --raw: Output the raw internal source tree as JSON, useful for debugging Bender itself.
  • --ignore-passed-targets: Ignore any targets that would otherwise be inherited via pass_targets from a parent package.

config — Emit the current configuration

The bender config command prints the currently active configuration as JSON to standard output.

script — Generate tool-specific scripts

The bender script <format> command can generate scripts to feed the source code of a package and its dependencies into a vendor tool. These scripts are rendered using internally stored templates with the tera crate, but custom templates can also be used.

Supported formats:

  • flist: A flat whitespace-separated file list.
  • flist-plus: A flat file list amenable to be directly inlined into the invocation command of a tool, e.g. verilate $(bender script flist).
  • vsim: A Tcl compilation script for Mentor ModelSim/QuestaSim.
  • vcs: A Tcl compilation script for VCS.
  • verilator: Command line arguments for Verilator.
  • synopsys: A Tcl compilation script for Synopsys DC and DE.
  • formality: A Tcl compilation script for Formality (as reference design).
  • riviera: A Tcl compilation script for Aldec Riviera-PRO.
  • genus: A Tcl compilation script for Cadence Genus.
  • vivado: A Tcl file addition script for Xilinx Vivado.
  • vivado-sim: Same as vivado, but specifically for simulation targets.
  • precision: A Tcl compilation script for Mentor Precision.
  • template: A custom tera template, provided using the --template flag.
  • template_json: The json struct used to render the tera template.

Furthermore, similar flags to the sources command exist.

pickle — Parse and rewrite SystemVerilog sources with Slang

The bender pickle command parses SystemVerilog sources with Slang and prints the resulting source again. It supports optional renaming and trimming of unreachable files for specified top modules.

This command is only available when Bender is built with the slang feature, which is part of the default feature set. If you previously installed Bender with --no-default-features, rebuild with --features slang (or the default feature set) to enable pickle.

Useful options:

  • --top <MODULE>: Trim output to files reachable from one or more top modules.
  • --prefix <PFX> / --suffix <SFX>: Add a prefix and/or suffix to renamed symbols. Both require --expand-macros.
  • --exclude-rename <NAME>: Exclude specific symbols from renaming.
  • --ast-json: Emit AST JSON instead of source code.
  • --expand-macros, --strip-comments, --squash-newlines: Control output formatting.
  • -I <DIR>, -D <DEFINE>: Add extra include directories and preprocessor defines beyond those declared in the manifest.
  • -o/--output <FILE>: Write to a file instead of standard output.

The -t/--target, -p/--package, --exclude, and --no-deps flags work like for sources.

Examples:

# Keep only files reachable from top module `my_top`.
bender pickle --top my_top

# Rename symbols, but keep selected names unchanged.
bender pickle --top my_top --expand-macros --prefix p_ --suffix _s --exclude-rename my_top

update — Re-resolve dependencies

Whenever you update the list of dependencies, you likely have to run bender update to re-resolve the dependency versions, and recreate the Bender.lock file.

Calling update with the --fetch/-f flag will force all git dependencies to be re-fetched from their corresponding urls.

Note: bender update should ideally be run automatically when dependencies are added; for now this has to be done manually.

clone — Clone dependency to make modifications

The bender clone <PKG> command checks out the package PKG into a directory (default working_dir, can be overridden with -p / --path <DIR>). To ensure the package is correctly linked in bender, the Bender.local file is modified to include a path dependency override, linking to the corresponding package.

This can be used for development of dependent packages within the parent repository, allowing to test uncommitted and committed changes, without the worry that bender would update the dependency.

To clean up once the changes are added, ensure the correct version is referenced by the calling packages and remove the path dependency in Bender.local, or have a look at bender snapshot.

Note: The location of the override may be updated in the future to prevent modifying the human-editable Bender.local file by adding a persistent section to Bender.lock.

Note: The newly created directory will be a git repo with a remote origin pointing to the git tag of the resolved dependency (usually evaluated from the manifest (Bender.yml)). You may need to adjust the git remote URL to properly work with your remote repository.

After working on a dependency cloned with bender clone <PKG>, modifications are generally committed to the parent git repository. Once committed, this new hash can be quickly used by bender by calling bender snapshot.

With bender snapshot, all dependencies previously cloned to a working directory are linked to the git repositories and commit hashes currently checked out. The Bender.local is modified correspondingly to ensure reproducibility. Once satisfied with the changes, it is encouraged to properly tag the dependency with a version, remove the override in the Bender.local, and update the required version in the Bender.yml.

parents — Lists packages calling the specified package

The bender parents <PKG> command lists all packages calling the PKG package, along with the version requirement each parent imposes.

Pass --targets to additionally print the targets each parent passes down to PKG via pass_targets.

checkout — Checkout all dependencies referenced in the Lock file

This command will ensure all dependencies are downloaded from remote repositories. This is usually automatically executed by other commands, such as sources and script.

fusesoc — Create FuseSoC .core files

This command will generate FuseSoC .core files from the bender representation for open-source compatibility to the FuseSoC tool. It is intended to provide a basic manifest file in a compatible format, such that any project wanting to include a bender package can do so without much overhead.

If the --single argument is provided, only to top-level Bender.yml file will be parsed and a .core file generated.

If the --single argument is not provided, bender will walk through all the dependencies and generate a FuseSoC .core file where none is present. If a .core file is already present in the same directory as the Bender.yml for the corresponding dependency, this will be used to link dependencies (if multiple are available, the user will be prompted to select one). Previously generated .core files will be overwritten, based on the included Created by bender from the available manifest file. comment in the .core file.

The --license argument will allow you to add multiple comment lines at the top of the generated .core files, e.g. a License header string.

The --fuse-vendor argument will assign a vendor string to all generated .core dependencies for the VLNV name.

The --fuse-version argument will assign a version to the top package being handled for the VLNV name.

vendor — Copy files from dependencies that do not support bender

Collection of commands to manage monorepos. Requires a subcommand.

Please make sure you manage the includes and sources required for these files separately, as this command only fetches the files and patches them. This is in part based on lowRISC’s vendor.py script.

vendor init — (Re-)initialize the vendorized dependencies

This command will (re-)initialize the dependencies listed in the vendor_package section of the Bender.yml file, fetching the files from the remote repositories, applying the necessary patch files, and writing them to the respective target_dir.

If the -n/--no-patch argument is passed, the dependency is initialized without applying any patches.

vendor diff — Print a diff of local, unpatched changes

This command will print a diff to the remote repository with the patches in patch_dir applied.

vendor patch — Generate a patch file from local changes

If there are local, staged changes in a vendored dependency, this command prompts for a commit message and generates a patch for that dependency. The patch is written into patch_dir.

If the --plain argument is passed, this command will not prompt for a commit message and generate a patch of all (staged and unstaged) local changes of the vendored dependency.

Example workflow

Let’s assume we would like to vendor a dependency my_ip into a project monorepo. A simple configuration in a Bender.yml could look as follows (see the Bender.yml description above for more information on this):

vendor_package:
  - name: my_ip
    target_dir: deps/my_ip
    upstream: { git: "<url>", rev: "<commit-hash>" }
    patch_dir: "deps/patches/my_ip"

Executing bender vendor init will now clone this dependency from upstream and place it in target_dir.

Next, let’s assume that we edit two files within the dependency, deps/my_ip/a and deps/my_ip/b. We can print these changes with the command bender vendor diff.

Now, we would like to generate a patch with the changes in deps/my_ip/a (but not those in deps/my_ip/b). We stage the desired changes using git add deps/my_ip/a (of course, you can also just stage parts of a file using git add --patch). The command bender vendor patch will now ask for a commit message that will be associated with this patch. Then, it will place a patch that contains our changes in deps/my_ip/a into deps/patches/my_ip/0001-commit-message.patch (the number will increment if a numbered patch is already present).

We can easily create a corresponding commit in the monorepo. deps/my_ip/a is still staged from the previous step. We only have to git add deps/patches/my_ip/0001-commit-message.patch and git commit for an atomic commit in the monorepo that contains both our changes to deps/my_ip/a and the corresponding patch.

Upstreaming patches to the dependency is easy as well. We clone the dependencies’ repository, check out <commit-hash> and create a new branch. Now, git am /path/to/monorepo/deps/patches/my_ip/0001-commit-message.patch will create a commit out of this patch – including all metadata such as commit message, author(s), and timestamp. This branch can then be rebased and a pull request can be opened from it as usual.

Note: when using mappings in your vendor_package, the patches will be relative to the mapped directory. Hence, for upstreaming, you might need to use git am --directory=<mapping.from> instead of plain git am.

completion — Generate shell completion script

The bender completion <SHELL> command prints a completion script for the given shell.

Installation and usage of these scripts is shell-dependent. Please refer to your shell’s documentation for information on how to install and use the generated script (bash, zsh, fish).

Supported shells:

  • bash
  • elvish
  • fish
  • powershell
  • zsh