last updated: 2025-01-07
For years I wanted to test FPGAs, but I did not find the right access, especially because the tools were proprietary and every producer had another approach. Then I bought a book from Jörg Rippel (in German). Jörg uses only open source tools to program the FPGAs and the book is easy to read. The open source toolchain helps to better understand whats going on. He uses an IceZero board (ICE40HX, Lattice) and a Tang Nano 9k board (Gowin-GW1NR-LV9).
In the French Mag Hackable Magazine nr 35 I found in 2020 a cool article to use cheap LED control boards named Colorlight 5A-75B with an ECP5 FPGA as development board. I have two of this boards.
So first I will document my progress with the book and then I will use hopefully my knowledge to program the Colorlight FPGAs.
Using a Raspi as base is a good idea because it's always available for my FPGA development and nothing is altered by updates like on my desktop computer. The Raspberry 5 is fast enough to be used as computer and with VNC I'm able to use my usual keyboard, screen and mouse.
So for Christmas I get a new Raspi 5 with cooler, NVMe hat and a 1TB NVMe. How to set the Raspi up is described here:
sudo df # to get the name of the NVMe
sudo hdparm -t --direct /dev/nvme0n1p2
The IceZero board is cool because it uses directly the Raspberry pi pins to access the FPGA. It is based on a board by Black Mesa Labs, but unfortunately it is no more available (trenz-electronic.de) until mid 2025.
So I will test the examples with the Tang Nano 9k board from Sipeed.
We can download the material of the book to our Raspi with:
wget https://www.rippel.info/fpga-buch/Material-zum-Buch.zip
After unzipping the file (GUI) we find a script called: toolchain_tang_nano_install.sh
and run it with:
cd fpga
./toolchain_tang_nano_install.sh
As you see the script installs first some packages with apt
and WiringPi from github, if not already installed. After this the following tools are installed from there github repos or with pip in a virtual environment (Apicula).
Manually you could use the following commands:
sudo apt install qtcreator qtbase5-dev qt5-qmake
# Yosys
cd ~/fpga/git
git clone https://github.com/YosysHQ/yosys
cd ~/fpga/git/yosys
git submodule update --init
make -j$(nproc)
sudo make install
# Apicula
cd ~/fpga/git
git clone https://github.com/YosysHQ/apicula.git
cd ~/fpga/git/apicula
mkdir -p ~/Programme/apycula_env # directory for virtual environment
python3 -m venv ~/Programme/apycula_env # create virtual environment (venv)
source ~/Programme/apycula_env/bin/activate # activate venv
pip install apycula # install Apycula in venv
deactivate # deactivate venv
# nextpnr
cd ~/fpga/git
git clone https://github.com/YosysHQ/nextpnr
cd ~/fpga/git/nextpnr
cmake . -DARCH=gowin -DGOWIN_BBA_EXECUTABLE="$HOME/Programme/apycula_env/bin/gowin_bba" -DBUILD_GUI=ON
make -j$(nproc)
sudo make install
# openFPGALoader
cd ~/fpga/git
git clone https://github.com/trabucayre/openFPGALoader
cd ~/fpga/git/openFPGALoader
export CC=clang
export CXX=clang++
mkdir build
cd build
cmake
cmake --build .
sudo make install
Now let's test ou toolchain with a simple running light.
Connect the Sipeed board with an USB cable to your Raspi. Test with the following commands:
dmesg
ls -l /dev/ttyUSB*
openFPGALoader --detect
We see that we get 2 FTDI serial devices (ttyUSB0 and ttyUSB1) belonging to the group plugdev
. The standard user is normally in this group (grep plugdev /etc/group
). If openFPGALoader
finds the GWIN(R)-9C the toolchain is ok. If there is a problem, try reducing the JTAG frequency:
openFPGALoader --detect --freq=2000000
Now we extract the zip file gowin_blinky.zip
in ~/fpga/src/tangnano9k
and cd
to this directory. Zhen we run a script, that loads the bitstream to the FPGA:
cd ~/fpga/src/tangnano9k/gowin_blinky
sh blinky.sh
Now let's do the same thing step by step on our own. First we look at the Verilog file (ending .v
). We rename it to running_light.v
and copy it to our project directory (e.g. fpga/tang_nano_9k/running_light
).
I changed the Verilog code a little bit to make it clearer for me.
module led (
input sys_clk, // in system clock
input sys_rst_n, // in reset active low
output reg [5:0] led // out 6 LEDS pin
);
parameter COUNTER_MAX = 24'd1349_9999; // for 0.5s delay, clk = 27MHz
reg [23:0] counter;
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
counter <= 24'd0;
else if (counter == COUNTER_MAX)
counter <= 24'd0;
else
counter <= counter + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst_n)
begin
if (!sys_rst_n)
led <= 6'b111110;
else if (counter == COUNTER_MAX)
led <= {led[4:0], led[5]};
end
endmodule
Our modul is called led
and contains two inputs, the system clock and a reset button (S1 ob the Sipeed board) and an output with the 6 LEDs organized in a register (first variable).
The second variable (24 bit) contains a counter that is needed to get our delay of 0.5 s. The system clock is 27 MHz (27000000 Hz). Half of the frequency gives us the value of 13499999 for the counter test, so that we react twice a second.
The always loops react on the positive edge of the system clock and the negative edge of a reset. A reset sets the counter to zero (1 loop) and illuminates the first LED (negative logic, 2 loop). The positive edge of the clock increments the counter if the number 13499999 is not reached and sets it to 0 if this is the case (1 loop). In the second loop the positive edge of the clock shifts effectively the bits of the led vector to the left by one position, with the highest bit (led[5]) being moved to the lowest position (led[0]) if the number 13499999 is reached.
This is done with a concatenation (combining) operation, that is used to combine multiple bits or bit vectors into a single bit vector. The concatenation operator is the curly brace {}. The concatenation {led[4:0], led[5]} creates a new 6-bit vector where the bits from led[4:0] are placed in positions 5 down to 1, and led[5] is placed in position 0.
Every FPGA project needs a physical constraint file defining to which real pins of the chip or board the in- and output signals are wired up. Unfortunately every producer of FPGAs uses a different ending and syntax for this file. For the Tang Nano the files ending is .cst
. So let's look at the file tangnano9k.cst
. We see that the file is provided by gowin and defines the physical constraints of the Sipeed board.
IO_LOC "led[5]" 16;
IO_PORT "led[5]" PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8;
IO_LOC "led[4]" 15;
IO_PORT "led[4]" PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8;
IO_LOC "led[3]" 14;
IO_PORT "led[3]" PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8;
IO_LOC "led[2]" 13;
IO_PORT "led[2]" PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8;
IO_LOC "led[1]" 11;
IO_PORT "led[1]" PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8;
IO_LOC "led[0]" 10;
IO_PORT "led[0]" PULL_MODE=UP DRIVE=8 BANK_VCCIO=1.8;
IO_LOC "sys_rst_n" 4;
IO_PORT "sys_rst_n" PULL_MODE=UP BANK_VCCIO=1.8;
IO_LOC "sys_clk" 52;
IO_PORT "sys_clk" IO_TYPE=LVCMOS33 PULL_MODE=UP BANK_VCCIO=3.3;
To understand this we need to look at the schematic of the board. We find it herehttps://dl.sipeed.com/shareURL/TANG/Nano%209K.
The 6 LEDs are connected to 1.8 V. So clearly they are active low (neg logic). And they are connected to the 1.8 V bank (left bank IOL, Bank 3) of the FPGA. The pin numbers of the FPGA are 10, 11, 13, 14, 15 and 16 and are defined with IO_LOC
. The IO_PORT
definition also specifies a pull-Up resistance for the negative logic.
The 2 buttons are connected to FPGA pin 3 and 4. They have 10 kΩ pull-ups to 1.8 V and are also active low.
The system clock can be found on pin 52 of the FPGA. The definition IO_TYPE
=LVCMOS33 gives information of the type of voltage. The pin uses a Low Voltage CMOS signal with 3.3 V (right Bank 1). It has a pull-up and is connected to the crystal of 27 MHz.
More infos to our pins can also be found in the tangnano9k.cst
-file in the folder src/tangnano9k/
.
We save the file with the name running_light.cst
to our project folder.
We use the well known make system to get the bitstream and load it to the FPGA. Here a makefile for our project:
# project name (no comments behind assignment lines because spaces are added!)
PROJ = running_light
# constraints file (same as projectname)
PIN_DEF = $(PROJ).cst
DEVICE = GW1NR-LV9QN88PC6/I5
DEVICE_FAMILY = GW1N-9C
# Verilog file (same as projectname)
SOURCE = $(PROJ).v
# board name for OpenFPGALoader
BOARD = tangnano9k
# where should the code reside: "SRAM" or "FLASH"
TARGET ?= SRAM
$(info PROJ = $(PROJ)) # Print variables for debugging
all: $(PROJ).fs # final bitstream file
$(PROJ)-yosys.json: $(SOURCE) # synthesizes Verilog-source to JSON using Yosys
yosys -q -p "synth_gowin -json $@" $(SOURCE)
$(info Building $(PROJ)-yosys.json)
$(PROJ)-nextpnr.json: $(PIN_DEF) $(PROJ)-yosys.json # Create place and route JSON file from Yosys JSON file
nextpnr-gowin --json $(PROJ)-yosys.json --write $@ --device $(DEVICE) --family $(DEVICE_FAMILY) --cst $(PIN_DEF)
$(PROJ).fs: $(PROJ)-nextpnr.json # pack the NextPNR JSON-file to final Gowin pack-file
PATH="$(HOME)/Programme/apycula_env/bin:$$PATH" gowin_pack -d $(DEVICE_FAMILY) -o $@ $(PROJ)-nextpnr.json
prog: $(PROJ).fs # load bitstream fs-file to SRAM or FLASH
ifeq ($(TARGET),FLASH)
openFPGALoader -f --freq 2000000 -b $(BOARD) $(PROJ).fs
else
openFPGALoader -m --freq 2000000 -b $(BOARD) $(PROJ).fs
endif
clean: # clean the files that are needed no more
rm -f *.json *.fs
.PHONY: all prog clean
# EOF
The names of the device can be found on github: https://github.com/YosysHQ/apicula and the device family can be found with the following command: openFPGALoader --list-boards
Apicula has a Folder in our home directory called Programme/apycula_env
. In the bin directory resides the needed gowin_pack program.
The file is named Makefile
(no extension) and must reside in our project folder. Then we use this file to get the bitstream and load it with this 3 make
commands:
cd ~/fpga/tang_nano_9k/running_light
make clean && make && mage prog
If everything works, try reducing the delay of the running light :).
The code is loaded to SRAM, so it is no more available after repowering the board.
If you want to load it to FLASH, you must use sudo
for the OpenFPGALoader or install an UDEV rule in `/etc/udev/
99-openfpgaloader.rules
We need python (already installed on the Raspi) and pip to install programs that are not in the Raspi repo. On the Raspi the Python environment is managed externally, which means you can't install Python packages directly using pip. We need a virtual environment. We call it venv
and use a hidden directory (dot in front). We create the environment and activate it with:
python -m venv .venv
source .venv/bin/activate
Now we can install the packages needed by the TerosHDL addon:
pip install vunit-hdl edalize yowasp-yosys cocotb
sudo apt install code
Install your favorite tools: Quartus Pro, ModelSim, Yosys, GHDL, Vivado... For open-source tools, we recommend downloading OSS CAD Suite (https://github.com/YosysHQ/oss-cad-suite-build/releases). OSS CAD Suite is a binary software distribution for a number of open source software used in digital logic design. You will find tools for RTL synthesis, formal hardware verification, place & route, FPGA programming, and testing with support for HDLs like Verilog, Migen, and Amaranth.
? sudo apt-get install gtkwave ? sudo apt-get install verilator ? sudo apt-get install iverilog
https://github.com/phdussud/pico-dirtyJtag/releases/tag/V1.04
For a next project I will need a serial communication with the FPGA with a bitrate of 12 MBit/s! Let's start slower :).
https://www.hackster.io/adam-taylor/developing-fpgas-on-a-raspberry-pi-400-with-cocotb-fd4b7e