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:
FPGA Bitstream for Xilinx Artix7 running on FuseSoc containers with open source toolchain
Recorded by carlosedp
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
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.