Introduction #

When using Vivado for FPGA development, something that often comes up is how best to manage projects in source control and run builds from the command line. Thankfully, Vivado provides a full-featured TCL interface to control nearly every aspect of the project flow programmatically.

While looking at how to write a custom build system that would call these TCL commands via a set of scripts, I came across Ruckus, a Makefile-based build system for Vivado created by the SLAC National Accelerator Laboratory. Ruckus seemed to be able to do everything that I needed, and so I began looking into how to integrate it into my workflow. Along the way, I discovered that Ruckus is what the SURF VHDL component library uses, which makes integrating SURF into your builds much easier.

Concepts #

Targets #

Ruckus defines a general split between target-specific code, and shared code. Each target is independent from the perspective of the build system, and can have different chips, different sources, and different build hooks.

Ruckus focusses on generating the bitstreams and other build artifacts, but does not directly flash any hardware. Automated flashing can be done through a post_buildscript (discussed below).

Build Info #

Ruckus will automatically pass build-time information to your sources, which can be used to embed build timestamps, versions, or git commit hashes into the HDL sources.

This metadata is passed in two ways. The first is as a top level generic, called BUILD_INFO_G. The second, apparently intended for when top-level generics aren’t supported, is through a constant BUILD_INFO_C in a package called ruckus.BuildInfoPkg

The type of both the generic and the constant are the same, a 2240-bit vector that can be decomposed to a record using the definitions and functions in surf.StdRtlPkg.

User-Defined TCL Scripts #

In order to provide more flexibility and to integrate custom code into the build system, ruckus allows user-specified TCL scripts to be called at various stages of the synthesis and implementation.

An example of when this can be useful is to run HDL code generation tools (like PeakRDL for register generation) prior to synthesis, or to implement an ILA post-synthesis.

A list of available hooks is available in the Ruckus README, however it is a little out-of-date. Check the ruckus sources as the source of truth, but promgen.tcl is no longer required, and post_build.tcl is called after bitstream generation, which can be used to flash the bitstream to a device.

Project Structure #

Ruckus has a general structure that it expects you to use, however is relatively flexible. The following structure is closest to what many of SLAC’s ruckus-based projects use, but this can be customised to specific use-cases.

build - autogenerated build directory
submodules
  ruckus - the ruckus source, checked out to a release tag
  surf - the surf source, checked out to a release tag
shared - source files that are common across targets
  ruckus.tcl - script to specify source locations
targets
  YourTargetName
    images - generated bistreams and ila files
    vivado - directory that contains any custom hooks
    hdl - target-specific sources
    xdc - constraints files
    tb - testbenches
    bd - block design files, typically as .tcl scripts
    ruckus.tcl - script to specify source locations
    Makefile - build system entrypoint and configuration

A gitignore suitable for this structure would look like this, to remove any generated files:

/build
/targets/*/images
build.info

Makefile #

The target Makefile is the main entrypoint into ruckus. A barebones file looks like this:

# Define the TOP_DIR path
export TOP_DIR = $(abspath $(PWD)/../..)

# Define target output
target: prom

# Define target part
export PRJ_PART = XC7A35TICSG324-1L

include $(TOP_DIR)/submodules/ruckus/system_vivado.mk

TOP_DIR needs to be set to point to the root of the project. It is used throughout ruckus as a reference to find the other directories needed.

The default target, here set to prom, defines what will be run when you just type make. Running to promis a good default as it does the full synthesis & implementation flow, bitstream generation, and mcs generation if it is used.

PRJ_PART defines the part the project uses. This needs to be set correctly depending on the hardware you are using. A list of options can be found in Vivado through the get_parts TCL command.

Finally, the include of Ruckus’s system_vivado.mk brings in the base Makefile which then kicks off the rest of the build system.

ruckus.tcl #

The ruckus.tcl files generally follow the same structure, and one for a target might look like this:

# Load RUCKUS environment and library
source $::env(RUCKUS_PROC_TCL)

# Load shared and sub-module ruckus.tcl files
loadRuckusTcl $::env(TOP_DIR)/submodules/surf
loadRuckusTcl $::env(TOP_DIR)/shared

# Load local source Code and constraints
loadSource      -dir "$::DIR_PATH/hdl" -fileType {VHDL 2019}
loadSource      -dir "$::DIR_PATH/ip_integrator"
loadConstraints -dir "$::DIR_PATH/xdc"

# Load block design
loadBlockDesign -dir "$::DIR_PATH/bd"

# Load local SIM source Code
loadSource -sim_only -dir "$::DIR_PATH/tb" -fileType {VHDL 2019}
  1. At the start of the ruckus.tcl files, a source $::env(RUCKUS_PROC_TCL) is used to include all the commands that Ruckus provides.
  2. Then, loadRuckusTcl is used to load other ruckus.tcl files. Here, the one for surf is included, which then loads all of the surf sources into the project. Likewise, a single ruckus.tcl in the shared directory allows the common source locations to be defined in a single place, rather than having to be duplicated into every target.
  3. Sources local to this target are then loaded. This includes HDL sources, constraints, and block designs. The fileType argument can be used to specify which language version is used while loading sources, and the -sim_only argument can be used to only load sources in simulation runs.

Common Make Targets #

At any point, running make gui will open Vivado with the project loaded, which can be invaluable to look at warnings, diagnose timing violations, or to access the ILA.

Running make clean will delete the Vivado project directory, allowing Ruckus to start from scratch.

Further Resources #

Ruckus Repository

SURF Repository

Presentation on Ruckus and SURF by SLAC