Using Xilinx Open Source FPGA Toolchain on Docker Containers

In this post I’ll explore a bit the Open Source toolchain for Xilinx Series 7 FPGAs and will focus the Artix 7. The post will detail how to run the complete flow using Docker containers avoiding local toolchain installs and homogenizing the process for most platforms (Linux, Mac, Windows).

As seen above, the architecture and dependency is not a walk in the park but abstracting them with straightforward tools like FuseSoc, Edalize and containers makes things a bit easier.

Installing the pre-requisites

Unfortunately not everything is in containers or work on them and you need to install some pre-requisites on your computer for this.

First Docker on Linux or Docker Desktop on your Mac or Windows. This is the runtime that runs the containers. If using Windows I recommend using WSL2 as it makes things easier providing the command line tools.

Second, you need a local Python 3 install to run FuseSoc. It can be downloaded from https://www.python.org/.

Finally the programming tool that can transfer the bitstream to your FPGA. This task is still not straightforward to run on a container in Windows and Mac since Docker Desktop runs in a virtual machine and is not able to access your locally connected USB devices. I recommend getting the pre-built OpenOCD binaries on https://github.com/xpack-dev-tools/openocd-xpack/releases or using brew install open-ocd if using a Mac.

Build the Docker image locally

Since the container image targeting Xilinx Artix7 FPGAs will be a hefty 23GB (due to the device databases), I did not upload it to DockerHub. Instead I recommend building it locally. It's quite fast depending mostly on your download speeds.

You need two files, the entrypoint script that sets environment and the Dockerfile itself. Both are embedded here:

To build the image, save both files in a directory and run:

docker build -t carlosedp/symbiflow -f Dockerfile.symbiflow .

Just remember that in this container only the database files for Artix 7 were added. I will investigate further on adding the other Xilinx Series 7 FPGAs.

Running the toolchain with FuseSoc on Containers

We will use a new feature from Edalize (the EDA backend tool used by FuseSoc) that allows overriding the default tool with a wrapped version of it.

For this lets use a Python script that grabs the tool name called by FuseSoc and run it on a Docker container locally.

The script below maps most of the current images in use for the open source toolchains, Project Trellis (Lattice ECP5), Project IceStorm (Lattice ICE40) and Symbiflow (Xilinx 7 Series). It can be used for projects targeting these EDAs.

Copy or download the file above and save it locally as runme.py. Add executable permission to it with chmod +x runme.py.

Generating a project with the containerized Toolchain

Now let’s generate the bitstream for the Digilent Arty A7–35T board by using this toolchain. Here I’ll use my Blinky sample project written in Chisel, an HDL based on Scala that abstracts and eases up a lot of hardware generation in comparison to Verilog or VHDL. To leverage the flow, the Chisel code generation will also happen inside a container so no local tools are needed except Docker itself, of course.

By using the instructions from the Blinky project, let’s create a project directory and create the files. I advise having the runme.py script created previously here as well.

# Lets install FuseSoc, the FPGA package manager and runner
pip3 install --upgrade --user fusesoc
export PATH=~/.local/bin:$PATH
# Check it it's installed properly.fusesoc --version
> 1.12.0
# Create the project dir
mkdir fusesoc-chiselblinky && cd fusesoc-chiselblinky
# Add project libraries (for FuseSoc and Blinky)
fusesoc library add fusesoc-cores https://github.com/fusesoc/fusesoc-cores
git clone https://github.com/carlosedp/chisel-playground
fusesoc library add chiselblinky $(pwd)/chisel-playground/blinky

We will now call FuseSoc and add our wrapper script to the prefix variable EDALIZE_LAUNCHER. We will also use the target artya7–35t-oss that uses Symbiflow toolchain instead of Vivado in my project.

# Run FuseSoc with wrapper script (assuming it's in parent dir)EDALIZE_LAUNCHER=$(realpath ../runme.py) fusesoc run --target=artya7-35t-oss carlosedp:demo:chiselblinky

Below a screen capture of the process:

FuseSoc will now build the Verilog files from Chisel, then the project scripts and Makefiles. Finally it will call the EDA tools from Symbiflow and the script will wrap them on containers.

At the end, you will have a build directory with the project name and all generated files, including the bitstream for the board:

I added to the end of the process the programming instructions for this board. It’s implemented in the proginfo.py script in the Blinky repository if you are curious.

Programming the bitstream

Now we will program the generated bitstream to the FPGA with OpenOCD. As specified in the blinky project config, the required programming files are copied by FuseSoc to our output directory.

With OpenOCD installed and the board connected, we run:

openocd -f /Users/cdepaula/projects/fusesoc/symbiflow/build/carlosedp_demo_chiselblinky_0/artya7-35t-oss-symbiflow/../src/carlosedp_demo_chiselblinky_0/openocd/digilent-hs1.cfg -f /Users/cdepaula/projects/fusesoc/symbiflow/build/carlosedp_demo_chiselblinky_0/artya7-35t-oss-symbiflow/../src/carlosedp_demo_chiselblinky_0/openocd/xilinx-xc7.cfg -c "transport select jtag;init; pld load 0 /Users/cdepaula/projects/fusesoc/symbiflow/build/carlosedp_demo_chiselblinky_0/artya7-35t-oss-symbiflow/Toplevel.bit; exit"

The command is pretty long but we are telling OpenOCD three things, first load the programmer config from digilent-hs1.cfg and the board config from xilinx-xc7.cfg. Then we issue the programming commands to load the Toplevel.bit file that is our bitstream.

In the end we have our blinker that was built by a 100% open source toolchain.

Understanding the FuseSoc structure and adding boards

You can get more details about adding support to new boards in this post and look at this commit that added the open source Symbiflow support to this board.

Conclusion

The steps above can be used for many boards that use the open source toolchains like those based on Lattice ECP5 and ICE40. For those, the containers are already pre-built and uploaded to DockerHub so there is no need for the container image generation step.

As an example, to generate the same project for the Radiona ULX3S board, you just need to run:

EDALIZE_LAUNCHER=$(realpath ../runme.py) fusesoc run --target=ulx3s_85 carlosedp:demo:chiselblinky

And the bitstream will be generated with the programming information printed to the screen.

For the Xilinx boards there is the extra step to build the container image due to it’s size. In the future the files might get compressed and a small image might be viable.

Please send your feedback and comments to my Twitter @carlosedp.

Writing everything cloud and all the tech behind it. If you like my projects and would like to support me, check my Patreon on https://www.patreon.com/carlosedp

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store