Makefiles for Xilinx Tools
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.
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!
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)
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.
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.
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.
Please login (on the right) if you already have an account on this platform.
Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: