Makefiles for Xilinx Tools

Victor YurkovskyMay 12, 20155 comments

Building a bitstream from an HDL is a complicated process that requires the cooperation of a lot of tools.  You can hide behind an IDE or grow a pair and use command line tools and a makefile to tie your build process together.  I am not a huge fan of makefiles either (I believe a language should be expressive enough to automate the build process), but the alternatives are dismal. 

Command-line driven workflow is easier on the hands and faster.  The example project (just an LED blinker) goes from zero to bitstream in less then 20 seconds on my machine.   And I am allowed to use Emacs for editing code.

Xilinx tools do not make it easy to be organized: they are picky about where certain files are.  They spew out fountains of logs, reports and temporary files from each component.  Each component requires a lot of settings, and often script files where even more settings are stored.  With a little planning and investigation, there are ways to keep this mess under control.

This article is available in PDF format for easy printing

I often create projects that work on several different devboards, sometimes with different FPGAs, and generally with different pinouts. With a makefile, it's pretty easy to maintain a single codebase and build for a different board when needed.

While I am not entirely happy with my setup, it's pretty minimal, requires few changes when new files are added, and supports multiple configurations.  I've seen possibly better make environments, but they are much more complicated, and I like keeping it simple.  Since it took me a stupid long time to get to this point, I would like to share a minimal Xilinx build environment with you.  You can use it as a starting point in your projects, although I can't vouch for the correctness of any settings!

So to start with, I organize my projects as follows:

This way I can keep the source files (and the dreaded .prj file; more on that later) in the src directory.  Xilinx tools can pollute the ise directory where our Makefile sits (and 'make clean' wipes it clean, of course).  Note that the xst directory (and projnav.xst/ inside it) is required by the 'xst' executable to keep temporary files.  The board directory contains board and chip-related files and allows me to build the project for different devboards.

My basic Makefile is very simple (git clone an improved one from https://github.com/stacksmith/xilinx-makefile.git- this is just a picture):

A few observations:

The clean section is massive.  I add to it often, and Xilinx tools manage to outsmart me by generating new kinds of crud all the time.

I generally use -intstyle silent to keep screen output to a minimum.

xst uses top.xst script.  It in turn refers to the project file, src/top.prj.  This file contains a list of source HDL files.  This is riduculous - this information belongs in the makefile! I haven't had any luck with cleaning this up, so when new HDL source files are added to the project, I add a line to the .prj file and modify the makefile.  

This leaves a bad taste in my mouth, but is not that hard.  If you figure out how to let xst know about the source files without the darned .prj files, please let me know!

UPDATE: nrclark (who knows a few things about makefiles) resolved the issue by having the makefile parse the prj file and create SOURCES automatically! Thanks!

References:

git clone https://github.com/stacksmith/xilinx-makefile.git

UG628 Xilinx Command Line User Guide (formerly devref)

UG627 Xilinx XST User Guide



Previous post by Victor Yurkovsky:
   BGA and QFP at Home 1 - A Practical Guide.
Next post by Victor Yurkovsky:
   Homebrew CPUs: Messing around with a J1

Comments:

[ - ]
Comment by nrclarkMay 13, 2015
Good looking start! I've done a bunch of Makefile-based automation of Xilinx tools. For your current workflow, I'd say the easiest way to get your dependency tracking to work is to keep your .prj file in source-control (which you're doing), and then parse it in your Makefile to get a SOURCES list that you can use as dependencies for your synthesis step.

That would look something like:

...
UCF_FILE := ../board/$(BOARD)/top.ucf
PRJ_FILE := ../src/top.prj
SOURCES := $(shell cat $(PRJ_FILE) | grep -oE $'[^ \t]+[ \t]*$' | tr -d '"')
...
top.ngc: .$(SOURCES) $(PRJ_FILE) $(UCF_FILE)
[ - ]
Comment by stackMay 13, 2015
Very clever! I was thinking all backwards - as in generating the .prj file from the $(SOURCES)... This makes more sense. Thanks!
[ - ]
Comment by deaksMay 14, 2015
Your Makefile looks like it could benefit from using the conditional "set equal if not already defined" syntax:

DEVICE?=xc6slx9-2-ftg256
BOARD? =xula2

Then when you call make you can override these values by providing them as command line arguments to Make instead of changing your Makefile.

Another handy option for common code bases is to use a specialized Makefile for each build you are supporting (often in their own directories) and push all of the common make rules out to a common Makefile that is included by the specialized Makefiles. Again this avoids fiddling with tbe Makefile when moving between builds, this in turn facilitates build automation in the future across all device/board combinations.
[ - ]
Comment by jt_eatonJune 6, 2015
One thing that hardware designers must never forget is that make was created by software designers for their own use and that we are simply trying to piggyback our needs on their tools. Hardware tool flows are more complex than software and some things that they do will not work for hardware.

One of these things is your clean function. Software tool flows are simple enough that a few wildcards will do the job but as you noted cleaning up after a hardware tool can be messy. A solid clean function is needed because the last thing you want is to make a change and have it not used because an old generated file wasn't cleaned out and is used instead.

The solution is simple. Create a new build directory everytime you run your toolflow. Create a symbolic link that links the design files under this build directory and run your toolflow. When finished all you have to do is rm -r the build directory and recreate a new one. That takes less time than trying to maintain your clean function in a makefile and is a solid no-fail solution.

The added bonus is that you are never running your tools inside of a design repository so you will never have to maintain your vcs ignore function. Considering how most hardware toolflows are really fragile the fact the you can have files in your repository that are hidden should scare the hell out of you.


John Eaton

[ - ]
Comment by Andrea SterbiniJanuary 22, 2016
Nice!
I have been able to show my very first blinking light on my ml507 board!
BTW, I used xc3sprog instead than impact to load the top.bit file and it works perfectly

Thanks a lot!

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Registering will allow you to participate to the forums on ALL the related sites and give you access to all pdf downloads.

Sign up
or Sign in