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
cloneandsnapshotflow 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:
- Installation: Get the
benderbinary onto your system. - Getting Started: A quick tutorial to create your first Bender project.
- Concepts: Dive deeper into how Bender works under the hood.
- 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.
Recommended: Shell Installer
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
benderbinary into the current directory by default. The--local/--globalflags 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 thebenderbinary 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--localinstall, invoke Bender as./benderor move the binary onto yourPATH.--global: install into${CARGO_HOME:-$HOME/.cargo}/bin. For v0.32.0+ this also adds the directory to yourPATH(default behavior); for older versions the binary is relocated there but you may need to add the directory toPATHmanually.
# 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
benderat 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
picklecommand 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.ymlshould 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_linkstarget already exists and is not a symlink to the expected destination, Bender skips it and emits warningW01rather than overwriting it. If a dependency under a customcheckout_diris not a Git working tree or has uncommitted changes, Bender emits warningW06and leaves it alone — usebender snapshot --checkout --forceto 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 theBender.lock.bender checkout: Reads theBender.lockand 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.lockinto your version control (Git). - Update with intention: Only run
bender updatewhen you actually want to pull in newer versions of your dependencies. - Review changes: When
Bender.lockchanges, 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:
overridesonly 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’sBender.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 apathoverride.bender snapshot: UpdatesBender.localwith 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.localshould 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.localand update yourBender.ymlwith 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
| Feature | Bender.yml | Bender.lock | Bender.local |
|---|---|---|---|
| Role | Manifest (Intent) | Lockfile (Reality) | Local Overrides |
| Main Content | Dependencies & Version Ranges | Exact Git Revisions | Local Paths & Tool Config |
| Managed By | User (Manual) | bender update (Auto) | User or bender clone |
| Version Control | Commit | Commit | Ignore (.gitignore) |
| Shared? | Yes, with everyone | Yes, for reproducibility | No, 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
- Define your requirements in
Bender.yml. - Run
bender updateto resolve those requirements into a fixedBender.lock. - Run
bender checkoutto download the exact source code specified in the lockfile. - (Optional) Use
Bender.localto 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:
Version-based (Recommended)
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.Zformat (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
targeton a dependency only filters that dependency out of source listings and generated scripts. It does not affect dependency resolution: every dependency declared inBender.ymlis still resolved and recorded inBender.lockregardless 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-lfsis installed, Bender configures LFS and pulls the required files automatically. - If LFS is detected but
git-lfsis not installed, Bender emits warningW26and continues the checkout. You may end up with pointer files instead of the actual large files, which can cause downstream build failures — installgit-lfsto resolve this. - If LFS is disabled in your configuration (
git_lfs: false) but the dependency appears to use LFS, Bender emits warningW27. - 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:
- Resolution: It scans the entire dependency tree and finds a set of versions that satisfy all constraints.
- 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, useexport_include_dirsinstead. - 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 (
axiandapb) both export a header namedtypedefs.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.svhandinclude/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 targetnameis 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 ifTdoes 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:
| Format | Default Targets |
|---|---|
flist, flist-plus | flist |
vsim | vsim, simulation |
vcs | vcs, simulation |
verilator | verilator, synthesis |
synopsys | synopsys, synthesis |
formality | synopsys, synthesis, formality |
riviera | riviera, simulation |
genus | genus, synthesis |
vivado | vivado, fpga, xilinx, synthesis |
vivadosim | vivado, fpga, xilinx, simulation |
precision | precision, fpga, synthesis |
Special Targets
- RTL: If you use the
--assume-rtlflag, Bender will automatically assign thertltarget to any source group that does not have an explicit target specification. - ASIC: While
asicis a common convention, it is not set automatically by Bender. It should be manually activated via-t asicwhen needed.
Recommended Conventions
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_targetactivatesmy_targetonly formy_pkg. - Negative targets:
-t -old_targetexplicitly disablesold_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):
- Command-Line Flags: Explicitly passed arguments (e.g.,
--git-throttle 8). - Environment Variables: System-level variables (e.g.,
BENDER_GIT_THROTTLE=8). - 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:
.benderin 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 setsdb_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
pluginsfrom the configuration file is deprecated and may be removed in a future release. Prefer declaringpluginsin 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 to1,2, or3)
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: Setting up a new Bender package from scratch.
- Adding and Updating Dependencies: Managing external packages and ensuring reproducible builds.
- Generating Tool Scripts: Creating compilation and simulation scripts for various EDA tools.
- Package Development: Developing multiple packages simultaneously using the
cloneandsnapshotflow. - Vendorizing External Code: Managing external repositories that don’t natively support Bender.
- Continuous Integration: Optimizing CI pipelines.
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.nameandgit 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.lockis 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
sourcesorscript) 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:
| Format | Tool / Use Case | Output Type |
|---|---|---|
vsim | ModelSim / QuestaSim | TCL |
vcs | Synopsys VCS | Shell |
verilator | Verilator | Shell |
vivado | Xilinx Vivado (Synthesis) | TCL |
vivadosim | Xilinx Vivado (Simulation) | TCL |
synopsys | Synopsys Design Compiler | TCL |
genus | Cadence Genus | TCL |
flist / flist-plus | Generic file lists | Text |
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 thertltarget 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 (likesimulationforvsim). 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:
- Performs a
git cloneof the dependency into that folder. - Adds a
pathoverride to yourBender.localfile.
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:
- Detect all dependencies that are currently overridden by a local path.
- Check the current Git commit hash of those local repositories.
- Update
Bender.localto use agitoverride with that specificrev(commit hash). - Automatically update
Bender.lockto 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 toworking_dir, matchingbender clone’s default).--checkout: after writing the lockfile, also check out the resolved revisions into the configuredcheckout_dir(if any). Dependencies with uncommitted changes are skipped by default; warningW25is emitted for each one. Pass--no-skipto snapshot them anyway.--force: combined with--checkout, overwrite an existing customcheckout_dir. Use with care, as this can discard local work.
Finalizing Changes
Once your changes are stable and you are ready to “release” them:
- Tag the Dependency: Push your changes to the remote repository and create a new version tag (e.g.,
v1.2.2). - Update Manifest: Update the version requirement in your
Bender.ymlto include the new version. - Clean Up: Remove the local overrides from your
Bender.localfile. - Resolve: Run
bender updateto re-resolve the dependency tree and updateBender.lockto 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.patchfiles 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 thesourcessection of yourBender.ymlfor 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:
- Clone the upstream repository separately.
- Check out the same revision (
abcd123). - Apply your patch:
git am /path/to/your_repo/deps/patches/my_ip/0001-fix-bug.patch. - 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.
- Commit
Bender.lock: Ensure the lockfile is checked into your repository. - 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.lockas 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_dirandBENDER_DB_DIRand 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
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 viapass_targetsfrom 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 asvivado, but specifically for simulation targets.precision: A Tcl compilation script for Mentor Precision.template: A custom tera template, provided using the--templateflag.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 updateshould 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.localfile by adding a persistent section toBender.lock.
Note: The newly created directory will be a git repo with a remote origin pointing to the
gittag 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.
snapshot — Relinks current checkout of cloned dependencies
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:
bashelvishfishpowershellzsh