Managing Project Files

Porting a Core, DeMiSTified – Part 3 – 2021-03-01

When an FPGA core is a self-contained standalone project with only one target board, managing the project from within the FPGA vendor’s IDE works fine. However, once there’s a significant amount of shared code between projects, or you’re building for multiple targets, things get more tedious,

EightThirtyTwoDemos currently has targets for nine different boards, and there are another four within easy reach that I could add if I wished. There are nine demo projects in the repo, so I could have to rebuild as many as 81 projects when something changes. Loading each project, one after another, and rebuilding them manually would be ridiculously inefficient – a better solution is needed.

Luckily the FPGA toolchains have extensive support for scripting and automation. What follows will be Quartus-centric since that’s the toolchain required to build for Turbo Chameleon 64, however most of the principles will apply to ISE and Vivado too, even if the syntax and finer details are different.

When targetting an FPGA core to multiple boards it helps to categorise the components as follows:

  • Code specific to the board being used, required only for interfacing to the specific hardware on that board. Examples would be the multiplexer on the Turbo Chameleon 64 V1, or the shift register on the Turbo Chameleon 64 V2, or the infra-red sensor on both – or the audio codec found on the DE1 and DE2 boards. Generally such code won’t be shared outside a particular board or family of boards.
  • Code that belongs to the core itself and will thus be common between all targets.
  • Code that belongs to the core but has to be customised for each target board. PLLs are a good example of this, since the target boards don’t necessarily have the same incoming clock frequency. For instance, MiST’s incoming clock is 27MHz. TC64v1’s is 8MHz while TC64v2’s and DE10-lite’s are both 50MHz.

One thing I’ve learned in recent months is that for many questions about Quartus the answer is “it’s a TCL script”! This applies to project files, .qip files, timing constraints and no doubt many aspects of the software which I have yet to explore.

The command line utility quartus_sh can be used to run TCL scripts, and these in turn can create project files, start the compilation process and much more besides. For instance, compiling a project is as simple as:

load_package flow                       
project_open project_name -revision project_name
execute_flow -compile
project_close

One of the most useful tcl commands for project management is “source”, which acts a bit like #include in C, and for my target boards I’ve taken a working project (.qsf) file and split it into the following files, which can be “source”d when creating a new project:

  • a _pins.tcl file which contains pin definitions, drive strengths, IO standards, etc.,
  • an _opts.tcl file which specifies the target FPGA and standard compilation process settings, and
  • a _support.tcl file which lists the components required to handle the board-specific hardware.

In addition I create a board-specific _defs.tcl which can be used to specify such things as the board’s incoming clock frequency, and whether or not the board supports SDRAM, and also a project-specific _defs.tcl file which specifies, among other things, the relative path from the project to the board-specific files. This makes it easier to keep the latter in a submodule.

Finally, I have a _files.tcl script which lists the files required by the core itself. This is script-generated in a vendor-specific format from a plaintext list of files; this should make life easier if and when I add a Xilinx-based board to the list of targets.

Creating a new project file using this method looks like this:

 load_package flow  
# ${project} and ${board} are extracted from command line
# arguments earlier in the script.
set corename "${project}_${board}"
source ${projectpath}/project_defs.tcl
source ${boardpath}${board}/${board}_defs.tcl

project_new $corename -revision $corename -overwrite
set_global_assignment -name TOP_LEVEL_ENTITY ${board}_top

source ${boardpath}${board}/${board}_opts.tcl
source ${boardpath}${board}/${board}_pins.tcl
source ${boardpath}${board}/${board}_support.tcl
source ${corename}_files.tcl

In combination with a collection of Makefiles and shell scripts, I can thus loop through a list of boards and projects, and automate the process of creating project files and of building cores from those projects.

Leave a Reply

Your email address will not be published.