Experience using free software OSS-CAD_SUITE for programming FPGA Gowin

To program FPGAs, you need a design environment. For example, when using Altera/Intel FPGA chips, we use Intel Quartus Prime Software. Perhaps the main value of FPGA technology is not even the chips themselves, but the software that allows you to place a Verilog HDL / VHDL project into logical blocks and distribute connections between them using specified time constraints.

Is it possible to use open source tools for FPGA design? It would be very interesting to see.

I will tell you about my experience using Yosys oss-cad-suite for Mars Rover 3GW-2 board based on the FPGA of the Chinese company Gowin GW1NR-LV9QN88PC6/I5. A photo of the board is shown above at the beginning of the article.

The chip itself is quite interesting and contains many built-in blocks:

  • up to 71 User IO pin

  • 8640 LUT

  • 6480 FF

  • 17280 bit SSRAM

  • 468kb BSRAM

  • 608Kb User Flash

  • 5 DSP Blocks

  • 2 PLL

  • built-in OSC generator

  • built-in serializer blocks OSER/ISER, ELVDS/TLVDS, ODDR/IDDR

  • built-in PSRAM 8Mbytes

The Mars Rover 3GW-2 board contains:

  • dual-channel MBFTDI programmer built on the FT2232H chip

  • 8-bit ADC ADC1175 20MHz

  • crystal oscillator 100MHz

  • 2 user buttons

  • 8 LEDs

  • HDMI output

  • connector for expansion cards, for example, for connecting an Ethernet shield or a 7-segment indicator

I'll try to use yesterday's open source release oss-cad-suite-linux-x64-20240416.tgz in Ubuntu 22.04. I download from https://github.com/YosysHQ/oss-cad-suite-build/releases/tag/2024-04-16

Unpacking. I go to the oss-cad-suite folder and do:

> source environment

In theory, the environment for working with FPGAs is ready to work!

I will compile some demo projects for the Mars Rover 3GW-2 board with this package.
Demo projects for the board can be taken from Github: https://github.com/marsohod4you/Marsohod3GW/tree/Marsohod3GW2_GW1NR-LV9QN88PC6I5

We use the Marsohod3GW2_GW1NR-LV9QN88PC6I5 branch

The simplest project here is _clk_counter. This is simply a binary counter whose output goes to 8 LEDs.

The sequence of commands will be like this:
> cd _clk_counter/src
> mkdir output
> yosys -p "read_verilog top.v; synth_gowin -json output/out-synth.json"
> nextpnr-gowin --json output/out-synth.json --write output/out-pnr.json --device GW1NR-LV9QN88PC6/I5 --family GW1N-9C --cst board.cst
> gowin_pack -d GW1N-9C -o output/pack.fs output/out-pnr.json

These are commands that synthesize (yosys) from Verilog source, place and route (place & route) connections in the FPGA (nextpnr) and then pack the result into a bitstream used for loading into the FPGA (gowin_pack).

You can load it into the FPGA with another command:

> openFPGALoader output/pack.fs

This is all in theory. In fact, none of this worked for me!

There were several problems.

The first problem is related to the fact that the original project was made in the GowinEDA CAD system and apparently the nextpnr-gowin router did not like the syntax of the board.cst assignment file. In order for nextpnr-gowin to eat the assignment file, I had to slightly fix it. For example, remove the IO type assignments and assign each of the TMDS signals separately, rather than in pairs. The result is this slightly modified board_.cst file:

//Copyright (C)2014-2022 Gowin Semiconductor Corporation.
//All rights reserved.
//File Title: Physical Constraints file
//GOWIN Version: 1.9.8.05
//Part Number: GW1NR-LV9QN88PC6/I5
//Device: GW1NR-9C
//Created Time: Wed 03 15 13:45:13 2023

//IO_LOC “TMDS_D_P[0]” 49.48;
//IO_PORT “TMDS_D_P[0]” IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
//IO_LOC “TMDS_D_P[1]” 52.51;
//IO_PORT “TMDS_D_P[1]” IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
//IO_LOC “TMDS_D_P[2]”54.53;
//IO_PORT “TMDS_D_P[2]” IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
//IO_LOC “TMDS_CLK_P” 41,42;
//IO_PORT “TMDS_CLK_P” IO_TYPE=LVCMOS18D PULL_MODE=NONE DRIVE=8;
IO_LOC “TMDS_D_P[0]” 49;
IO_LOC “TMDS_D_N[0]” 48;
IO_LOC “TMDS_D_P[1]” 52;
IO_LOC “TMDS_D_N[1]” 51;
IO_LOC “TMDS_D_P[2]” 54;
IO_LOC “TMDS_D_N[2]” 53;
IO_LOC “TMDS_CLK_P” 41;
IO_LOC “TMDS_CLK_N” 42;
//IO_PORT “TMDS_D_N[0]” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_D_P[0]” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_D_N[1]” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_D_P[1]” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_D_N[2]” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_D_P[2]” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_CLK_P” PULL_MODE=NONE DRIVE=8;
//IO_PORT “TMDS_CLK_N” PULL_MODE=NONE DRIVE=8;

IO_LOC “IO[19]” 40;
IO_LOC “IO[18]” 39;
IO_LOC “IO[17]” 38;
IO_LOC “IO[16]” 37;
IO_LOC “IO[15]” 36;
IO_LOC “IO[14]” 35;
IO_LOC “IO[13]” 34;
IO_LOC “IO[12]” 33;
IO_LOC “IO[11]” 32;
IO_LOC “IO[10]” 31;
IO_LOC “IO[9]” thirty;
IO_LOC “IO[8]” 29;
IO_LOC “IO[7]” 28;
IO_LOC “IO[6]” 27;
IO_LOC “IO[5]” 26;
IO_LOC “IO[4]” 25;
IO_LOC “IO[3]” 20;
IO_LOC “IO[2]” 19;
IO_LOC “IO[1]” 18;
IO_LOC “IO[0]” 17;
//IO_PORT “IO[18]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[17]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[16]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[15]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[14]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[13]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[12]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[11]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[10]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[9]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[8]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[7]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[6]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[5]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[4]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[3]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[2]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[1]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;
//IO_PORT “IO[0]” IO_TYPE=LVCMOS18 PULL_MODE=NONE;

IO_LOC “LED[7]” 87;
IO_LOC “LED[6]” 88;
IO_LOC “LED[5]” 10;
IO_LOC “LED[4]” eleven;
IO_LOC “LED[3]” 13;
IO_LOC “LED[2]” 14;
IO_LOC “LED[1]” 15;
IO_LOC “LED[0]” 16;

IO_LOC “FTB0” 60;
IO_LOC “FTB1” 59;
//IO_LOC “FTB2” 10;
//IO_LOC “FTB3” 9;
//IO_PORT “FTB0” PULL_MODE=NONE;
//IO_PORT “FTB1” PULL_MODE=NONE;
//IO_PORT “FTB2” IO_TYPE=LVCMOS33 PULL_MODE=NONE;
//IO_PORT “FTB3” IO_TYPE=LVCMOS33 PULL_MODE=NONE;

IO_LOC “FTC[7]” 61;
IO_LOC “FTC[6]” 62;
IO_LOC “FTC[5]” 63;
IO_LOC “FTC[4]” 68;
IO_LOC “FTC[3]” 69;
IO_LOC “FTC[2]” 70;
IO_LOC “FTC[1]” 71;
IO_LOC “FTC[0]” 72;
//IO_PORT “FTC[7]” PULL_MODE=NONE;
//IO_PORT “FTC[6]” PULL_MODE=NONE;
//IO_PORT “FTC[5]” PULL_MODE=NONE;
//IO_PORT “FTC[4]” PULL_MODE=NONE;
//IO_PORT “FTC[3]” PULL_MODE=NONE;
//IO_PORT “FTC[2]” PULL_MODE=NONE;
//IO_PORT “FTC[1]” PULL_MODE=NONE;
//IO_PORT “FTC[0]” PULL_MODE=NONE;

IO_LOC “FTD[7]” 73;
IO_LOC “FTD[6]” 74;
IO_LOC “FTD[5]” 75;
IO_LOC “FTD[4]” 76;
IO_LOC “FTD[3]” 56;
IO_LOC “FTD[2]” 55;
IO_LOC “FTD[1]” 57;
IO_LOC “FTD[0]” 50;
//IO_PORT “FTD[7]” PULL_MODE=NONE;
//IO_PORT “FTD[6]” PULL_MODE=NONE;
//IO_PORT “FTD[5]” PULL_MODE=NONE;
//IO_PORT “FTD[4]” PULL_MODE=NONE;
//IO_PORT “FTD[3]” PULL_MODE=NONE;
//IO_PORT “FTD[2]” PULL_MODE=NONE;
//IO_PORT “FTD[1]” PULL_MODE=NONE;
//IO_PORT “FTD[0]” PULL_MODE=NONE;

IO_LOC “ADC_D[7]” 81;
IO_LOC “ADC_D[6]” 82;
IO_LOC “ADC_D[5]” 83;
IO_LOC “ADC_D[4]” 84;
IO_LOC “ADC_D[3]” 85;
IO_LOC “ADC_D[2]” 86;
IO_LOC “ADC_D[1]” 3;
IO_LOC “ADC_D[0]” 4;
//IO_PORT “ADC_D[7]” PULL_MODE=NONE;
//IO_PORT “ADC_D[6]” PULL_MODE=NONE;
//IO_PORT “ADC_D[5]” PULL_MODE=NONE;
//IO_PORT “ADC_D[4]” PULL_MODE=NONE;
//IO_PORT “ADC_D[3]” PULL_MODE=NONE;
//IO_PORT “ADC_D[2]” PULL_MODE=NONE;
//IO_PORT “ADC_D[1]” PULL_MODE=NONE;
//IO_PORT “ADC_D[0]” PULL_MODE=NONE;

IO_LOC “ADC_CLK” 77;
//IO_PORT “ADC_CLK” PULL_MODE=NONE;

IO_LOC “KEY1” 79;
IO_LOC “KEY0” 80;
IO_PORT “KEY1” PULL_MODE=UP;
IO_PORT “KEY0” PULL_MODE=UP;

The second serious problem I encountered is that openFPGAloader does not work
When I try to load bitstream into the FPGA I get an error unable to open ftdi device: -4 (usb_open() failed)

It turns out that users by default do not have access to the USB FTDI programmer. Permissions can be set in udev rules. To do this, you need to create a file 99-openfpgaloader.rules with the following content:

Then put this file in /etc/udev/rules.d/, the contents of the file are:

# Copy this file to /etc/udev/rules.d/
ACTION!=”add|change”, GOTO=”openfpgaloader_rules_end”

# gpiochip subsystem
SUBSYSTEM==”gpio”, MODE=”0664″, GROUP=”plugdev”, TAG+=”uaccess”
SUBSYSTEM!=”usb|tty|hidraw”, GOTO=”openfpgaloader_rules_end”

#Original FT232/FT245 VID:PID
ATTRS{idVendor}==”0403″, ATTRS{idProduct}==”6001″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#Original FT2232 VID:PID
ATTRS{idVendor}==”0403″, ATTRS{idProduct}==”6010″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#Original FT4232 VID:PID
ATTRS{idVendor}==”0403″, ATTRS{idProduct}==”6011″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#Original FT232H VID:PID
ATTRS{idVendor}==”0403″, ATTRS{idProduct}==”6014″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#Original FT231X VID:PID
ATTRS{idVendor}==”0403″, ATTRS{idProduct}==”6015″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# anlogic cable
ATTRS{idVendor}==”0547″, ATTRS{idProduct}==”1002″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# altera usb-blaster
ATTRS{idVendor}==”09fb”, ATTRS{idProduct}==”6001″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”
ATTRS{idVendor}==”09fb”, ATTRS{idProduct}==”6002″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”
ATTRS{idVendor}==”09fb”, ATTRS{idProduct}==”6003″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# altera usb-blasterII – uninitialized
ATTRS{idVendor}==”09fb”, ATTRS{idProduct}==”6810″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# altera usb-blasterII – initialized
ATTRS{idVendor}==”09fb”, ATTRS{idProduct}==”6010″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#dirtyJTAG
ATTRS{idVendor}==”1209″, ATTRS{idProduct}==”c0ca”, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#Jlink
ATTRS{idVendor}==”1366″, ATTRS{idProduct}==”0105″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# NXP LPC-Link2
ATTRS{idVendor}==”1fc9″, ATTRS{idProduct}==”0090″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# NXP ARM mbed
ATTRS{idVendor}==”0d28″, ATTRS{idProduct}==”0204″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

#icebreaker bitsy
ATTRS{idVendor}==”1d50″, ATTRS{idProduct}==”6146″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# orbtrace-mini dfu
ATTRS{idVendor}==”1209″, ATTRS{idProduct}==”3442″, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

# QinHeng Electronics USB To UART+JTAG (ch347)
ATTRS{idVendor}==”1a86″, ATTRS{idProduct}==”55dd”, MODE=”664″, GROUP=”plugdev”, TAG+=”uaccess”

LABEL=”openfpgaloader_rules_end”

You also need to add yourself as a user to the plugdev group.
We execute the following commands:

>sudo cp 99-openfpgaloader.rules /etc/udev/rules.d/
> sudo udevadm control --reload-rules && sudo udevadm trigger # force udev to take new rule
> sudo usermod -a $USER -G plugdev # add user to plugdev group

Now disconnect and reconnect the Mars Rover 3GW-2 board to the computer’s USB port and openFPGAloader successfully loads the bitstream into the board.

Loads, but the project does not work!!!
Big disappointment, but what to do? There is still hope…

The nextpnr project is developing quite actively, and there is a kind of new development branch in it, codenamed himbaechel. I don't know what this means, but you can try it. To do this, we will collect this version of nextpnr from the source code.

I installed everything on a work computer with Ubuntu, so cmake, gcc, python3 are already installed by default. What was required additionally to install this

> sudo apt install python3-pip
> pip install apycula
> sudo apt install libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-iostreams-dev libboost-dev
> sudo apt install libeigen3-dev

After this, you can take the nextpnr sources from GitHub and compile them:

> git clone https://github.com/YosysHQ/nextpnr.git
> cd nextpnr
> mkdir build
> cd build
> cmake .. -DARCH="himbaechel" -DHIMBAECHEL_GOWIN_DEVICES="all"
> make -j20

Upon successful compilation, I got the file nextpnr-himbaechel. I'll try to use it for place&route.

I’ll tell you in advance – everything worked out!

Now I have successfully compiled and loaded three different simple demo projects onto the Mars Rover3GW-2 board: _clk_counter, _clk_pll_counter, _adc_led.

Project _clk_counter is simply a binary counter with LED mapping.

The _clk_pll_counter project is almost the same counter, but it also uses the PLL built into the FPGA. In addition, by pressing the board buttons, the display mode changes to LEDs..

The _adc_led project receives 8-bit data from the board's ADC and displays it on LEDs.

I will give in more detail the commands used to build the project and the output:

Synthesis:
>yosys -p "read_verilog top.v gowin_rpll/gowin_rpll.v; synth_gowin -json output/out-synth.json"

I wanted to attach the output of this command to the article, but, unfortunately, with the modern Habrov editor it turned out to be beyond my power. Some kind of impossible task. The text itself is marked anyhow, changes the font and all that. It is quite interesting to look at the output of the command.

Accommodation (Place&Route):

> ~/fpga/nextpnr/build/nextpnr-himbaechel --json output/out-synth.json --write output/out-pnr.json --device GW1NR-LV9QN88PC6/I5 --vopt family=GW1N-9C --vopt cst=board_.cst

Package:

> gowin_pack -d GW1N-9C -o output/pack.fs output/out-pnr.json

Loading into the FPGA board:
> openFPGALoader output/pack.fs

Works!

Conclusions..

I believe that the emergence of open source compilers for FPGAs is a huge step forward. This is useful for both technology companies and students learning FPGA technology. The nextpnr project can be adapted for any FPGA if you have technical documentation describing the internal structure of the chip.

Perhaps this project can give Russian companies that develop FPGAs a quick start in the development of their chips.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *