It’s often desired to select a minimal kit of files from an FPGA project under development, which are just enough to build the programming file binary from. Possible uses for such a set are

  • Storing this set in a version control repository (possibly diff-comparing with previous versions)
  • Distribution of the package to peers and customers
  • Setting up a repeatable build process
  • Building the project on a remote computer

This set of files should be

  • self-contained (can be used on a fresh computer)
  • relocatable (no absolute file paths)
  • with minimal redundancy (so a change in the project is observed in a single file)
  • based upon text files (preferred)

Xilinx’ Vivado development system maintains a rather opaque set of files, reaching ~150 MB quite easily for a simple project. Its rather complex behavior as a tool, and the absence of a true file cleanup option require a method to create a compact set of files.

Vivado has an option to archive an entire project, along with its dependencies into a single ZIP file, which is relocatable, and apparently intended for storing snapshots. However this file can also reach 20-70 MB, and is hardly suitable for distribution nor version control, even if the ZIP archive is opened into its files: There’s just too much redundancy in the bundle. Merging two sets of the project by virtue of manipulating text files is probably impossible with this format, so it’s not very suitable for version control either.

This page suggest an alternative, which is based upon Tcl scripting. It’s not as difficult as it may appear, since Vivado can output Tcl scripts that regenerate the project’s current state. Unfortunately, Vivado’s Tcl script still needs some modifications to function properly in a standalone package.

Vivado’s revision is assumed to be 2014.1 here. This tool is still evolving, so there are significant differences even with respect to 2013.4, and probably related to later version (to be released).


In Xilinx’ ISE suite, it is possible to delete all files except for a small subset, and the tool gracefully fills the gaps on its next run. This doesn’t seem to be possible with Vivado. Even though the “reset_project” Tcl command can reduce the disk space down to 25% or so from a fully implemented project, it still leaves several very large, opaque and irrelevant files.

The minimal kit has to be crafted partially by hand: The necessary files are collected manually, and a Tcl script which regenerates the Vivado project from scratch is set up. A source control system only needs to track the Tcl script and a rather small set of source files, which are all mentioned in the Tcl script itself or related to as IPs.

Vivado has a function for producing a regeneration Tcl script, but there’s still room for quite some manual tweaking to assure that the kit is relocatable and self-contained. The suggested flow starts with the script that Vivado produces, and turns it the main component of a self-contained kit by making simple changes to it.

Producing and running a script

The first step of creating a kit for packaging is using File > Write Project Tcl… and choose a file name for the Tcl script that generates the project. Alternatively, use the following Tcl command:

write_project_tcl {/path/to/my-project.tcl}

Running a Tcl script can be done with Tools > Run Tcl Script… or

source {/path/to/my-project.tcl}

or, from shell prompt, if a Vivado session should merely run the script and terminate:

vivado -mode batch -source /path/to/my-project.tcl

In a perfect world, this would have been enough. Unfortunately, this script creates a new project in a new location, but relying on the source files in their current, old, position. This is far away from an independent kit: The script will fail to run if the old project has been moved or removed, and even worse: If changes are made to the sources of the old project, they will take effect on the new one.

Nevertheless, the Tcl script is quite easily modified to be part of an self-contained bundle. So let’s look at the Tcl file that Vivado generates, and see how it can be altered.

Section I: Path declarations

In the beginning of this script, in the comment section, there’s a list of files that are included in the project, but will not be generated by the script itself (”remote source files”). Some of these files may be unnecessary for building the project, e.g. upgrade report files. Naturally, these probably have no place in a compact package.

The remote source files are given with their absolute paths, which reflects their current positions. It’s often a good idea to relocate these for convenience into a directory tree structure that makes sense to humans. XCI and BD files (which define IP cores and block designs) should be kept in separate directories which are populated by other files by Vivado.

The first commands in the Tcl script go something like

# Set the reference directory for source file relative paths (by default the value is script directory path)
set origin_dir "."

# Set the directory path for the original project from where this script was exported
set orig_proj_dir "[file normalize "$origin_dir/orig-project"]"

# Create project
create_project myproject ./myproject

# Set the directory path for the new project
set proj_dir [get_property directory [current_project]]

This part sets up the variables containing paths to key directories. As mentioned before, the script assumes that the original project is in place, and a new project should be set up based upon it. $origin_dir is used extensively in the script as a prefix to paths to source files in the “original project”. $proj_dir and $orig_proj_dir aren’t used in the script — it’s not clear why they were defined at all.

A couple of remarks for Tcl newbies: Expressions that are enclosed with square brackets (e.g. “[get_filesets sources_1]“) mean “run this command and replace the [ ] expression with the result. “file normalize” takes the next argument, and replaces it with an absolute path to the file or directory, after resolving possible “../” and “.” tokens. So on a Linux machine, you’ll get something like

puts [file normalize ~/]

puts [file normalize ~/../]

Back to Vivado’s script: Unlike what the comment at the top suggests, $origin_dir is set to the directory from which Vivado was run (and to which it outputs its .jou,.log files etc). This means that when the script is run, the source files are looked for in directories relative to where Vivado was run from, regardless of where the script itself resides.

Also note, that create_project is called with ./myproject as the target directory path — so the new project is created in the directory in which Vivado was run.

The intuitive place for everything to happen is relative to where the script resides. For example,

# Set the reference directory to where the script is
set origin_dir [file dirname [info script]]

# Create project
create_project myproject $origin_dir/myproject

Clarifications: “info script” returns the full path of the currently running script. “file dirname” chops off the file’s name in the end, leaving us with the path to the directory. The project is hence created in the same directory as the script.

create_project will fail if the project has been created already. There’s no need for another safety mechanism for preventing an accidental deletion of an existing project.

Section II: General project properties

# Set project properties
set obj [get_projects myproject]
set_property "default_lib" "xil_defaultlib" $obj
set_property "part" "xc7z010clg400-1" $obj

set_property "simulator_language" "Mixed" $obj
set_property "source_mgmt_mode" "DisplayOnly" $obj
set_property "target_language" "Verilog" $obj

This part speaks for itself, more or less. The only suggestion is to change the setting of the part number (marked in bold above) to

set thepart "xc7z010clg400-1"
set_property "part" $thepart $obj

The rationale is that the part number is mentioned again further on in the script. It therefore makes sense to state the part number explicitly in one place, and use the variable in the others.

It’s worth to note that variables such as $obj, $file, $files and $file_obj are used as temporary placeholders for immediate use in the lines following the assignment. When editing Tcl scripts, care must be taken not to move a line using those variables to a place where they have another value, or none at all.

Section III: Adding sources

A fileset, sources_1, is created and set up to contain the information about the project’s sources. This includes HDL files, IP cores (in XCI format) and block designs (in BD format).

Note that if the project requires IP cores that are not in Xilinx’ standard IP library (possibly from a block design), they need to be available in the path set by “ip_repo_path” along with files that these IP cores need to build properly.

# Create 'sources_1' fileset (if not found)
if {[string equal [get_filesets -quiet sources_1] ""]} {
  create_fileset -srcset sources_1

# Set IP repository paths
set obj [get_filesets sources_1]
set_property "ip_repo_paths" "[file normalize "$origin_dir/old-project/my-ip"]" $obj

# Set 'sources_1' fileset object
set obj [get_filesets sources_1]
set files [list \
 "[file normalize "$origin_dir/old-project/src/topmodule.v"]"\
 "[file normalize "$origin_dir/old-project/src/mymodule.v"]"\
 "[file normalize "$origin_dir/old-project/mysystem/"]"\
 "[file normalize "$origin_dir/old-project/myfifo/myfifo.xci"]"\
add_files -norecurse -fileset $obj $files

It’s important to keep in mind that Vivado doesn’t make a copy of the file in response to the add_files command, but rather registers the file’s path in the project. The Tcl file generated by Vivado points at the sources in the existing project (sometimes with long and tangled path definitions, and not as shown above). It therefore makes sense to make copies of the files mentioned into a dedicated file tree, possibly with another structure than the original, and edit the Tcl file to match the new setting.

Several useless files may appear in the list of sources. Removing files that don’t appear to contribute to building the project is recommended to keep things tidy.

It’s possible that the add_files command will be followed by several segments that set the “file_type” property of distinct files. Their need is questionable in many cases (for example, for VHDL source files), but they sometimes help spotting files that have nothing to do in the project in the first place.

This part is wrapped up with setting the toplevel module:

# Set 'sources_1' fileset properties
set obj [get_filesets sources_1]
set_property "top" "topmodule" $obj

Section IV: Adding constraint file(s)

This is similar to adding sources, but it goes to a different fileset.

# Create 'constrs_1' fileset (if not found)
if {[string equal [get_filesets -quiet constrs_1] ""]} {
  create_fileset -constrset constrs_1

# Set 'constrs_1' fileset object
set obj [get_filesets constrs_1]

# Add/Import constrs file and set constrs file properties
set file "[file normalize "$origin_dir/src/xillydemo.xdc"]"
set file_added [add_files -norecurse -fileset $obj $file]
set file "$origin_dir/src/xillydemo.xdc"
set file [file normalize $file]
set file_obj [get_files -of_objects [get_filesets constrs_1] [list "*$file"]]
set_property "file_type" "XDC" $file_obj

This is maybe not the most concise code for doing the job, but this is what is generated automatically.

Again, the file isn’t copied, but referenced, so it’s recommended to make a copy of the file and reference the copy.

Section V: Creating runs

The rest of the script is usually dedicated to set up the project’s standard filesets and runs: sim_1, synth_1 and impl_1. There isn’t much to change here, except possibly changing the appearances of the part number with $thepart, as suggested in Part II.

When Vivado is upgraded

Attempting to deploy a project, which was packaged with a Tcl script, using a revision of Vivado other than the one it was intended for, is quite likely to fail, even when the current Vivado revision is later.

A crucial issue is the evolution of Xilinx’ IP cores. Upgrading from one Vivado revision to another involves an upgrade of the IP core set. The IP core’s release notes include notices about incompatibilities that require human action in some cases, or can be compensated for automatically in other. Xilinx’ apparently intended flow for upgrading a project is that a newer revision of Vivado loads an older version of the project, leading the tool to lock the IP cores and require the user to read the change logs, and then manually and consciously migrate each IP core to its updated revision.

This is not a concern if the generating Tcl script merely sets up the list of files. However if it attempt to manipulate anything related to IP cores or block designs, this will fail with an error when a newer Vivado revision is used, since the IP cores are locked until they’re upgraded. This makes it harder to distribute a project, but on the other hand, given that the IP cores may change in a way that require human intervention to maintain proper functionality, maybe it’s best to distribute a project in a format that is unfriendly to revision changes.

If an automatic upgrade of all IPs is desired, despite the risks, the following Tcl command should be added after part III (adding sources):

upgrade_ip [get_ips]

This doesn’t necessarily solve all problems. For example, when upgrading from Vivado 2013.4 to 2014.1, a Zynq processor in a block design suffered several parameter changes, with or without calling upgrade_ips.

At any rate, the recipe to make the bundle for distribution work with a newer version of Vivado is fairly straightforward: Generate the project with the Tcl script on the older version. Then load the project (i.e. the *.xpr file) with the newer one, and update the IP cores as necessary, possibly making modifications in the design to adapt to the changes in the IP cores. Then exit Vivado and save the updated resource files.

Odds are that based upon the new resource files, the already existing Tcl script will now set up the project properly on the newer revision of Vivado. If it doesn’t, generate a Tcl file in the newer version for the upgraded project, compare, and make changes as necessary.

Avoiding repeated generation of IPs

FIFOs, memory controllers and other logic elements, which are represented by an XCI file, are almost always part of an FPGA project. Having these IPs included as XCI files forces Vivado to regenerate these every time the project's script runs, which can be a major waste of time, in particular if the script is used for each implementation of the project.

A relatively straightforward solution is to load the DCP (Design CheckPoint) file that was generated on the previous run. The script checks for the existence of the DCP, and if it's there, it's used instead of including the XCI. This speeds up the implementation significantly on some projects, but there are two things to be note:

  • The DCP can be outdated with respect to the attributes of the IP, if these were changed and the output product weren't generated. It's therefore a good idea to clean the project's tree and check it out from the repository, following a change of any IP's attribute, in order to be sure that all DCPs are built from the XCIs on the implementation.
  • When an IP is included as a DCP, its attributes can't be edited within Vivado. Once again, a cleanup of the project tree and a fresh run of the project's Tcl script rectifies this.

The following snippet can be used to add IPs as suggested (to be added after the part shown in Section III above, so $obj equals the source fileset). The cores' XCI files are expected to reside in e.g. coredir/fifo_8x2048/ifo_8x2048.xci.

set oocs [list fifo_8x2048 ddr3_ctrl ]

foreach i $oocs {
  if [file exists "$origin_dir/coredir/$i/$i.dcp"] {
    read_checkpoint "$origin_dir/coredir/$i/$i.dcp"
  } else {
    add_files -norecurse -fileset $obj "$origin_dir/coredir/$i/$i.xci"

General notes

  • Absolute paths to files should be replaced with relative descriptions in all files of the project.
  • In XCI/XML files, $PPRDIR can be used to represent the project’s root directory (where the .xpr is), possibly in conjunction with ../../-kind of paths for directories above it. This is true for paths given without quotes as well (e.g. USER_REPO_PATHS in XCI files). This is useful to replace absolute paths with.
  • Rather than importing a block design as a .bd file, it can be created by virtue of a script: With the block design open, choose File > Export > Export Block Design… or use the write_bd_tcl Tcl command.