FPGA

Starting with FPGAs using the Tang nano 9k

last updated: 2025-01-07

Quick links

Intro

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.

Preparing a Raspberry Pi as development hardware

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: . The SSD makes the access to the disk 10 times faster (844MB/s) then with the SD card! Test it with:

    sudo df # to get the name of the NVMe
    sudo hdparm -t --direct /dev/nvme0n1p2

Installing the toolchain for the Tang Nano 9k board

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

First test: Running light

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

Running light in detail

The Verilog code

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.

The constraints file

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.

The make file

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

Using VScode and TerosHDL

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

Second test: UART

For a next project I will need a serial communication with the FPGA with a bitrate of 12 MBit/s! Let's start slower :).

Interesting links