OS Toolchain Manual Installation

Our recommended method for installing the toolchain is to use our VS Code extension "Lushay Code" as it is the easiest way to get setup and use the open-source toolchain on any operating system with the least moving parts. Full guide can be found here.

For those who are more confident in a terminal / more adventurous we have included the steps below on how to setup and use the toolchain manually.

Installing the Toolchain Manually

The installation process is a bit different per operating system.

Mac

The easiest way to setup all dependencies is by using brew which can be setup by running:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Once installed we can use it to install python through pyenv which is a python version manager.

brew install pyenv
pyenv install 3.9.13
pyenv global 3.9.13

With python installed we can install apicula using the python package manager

pip install apycula

Note that the python package name is with a 'y' and not an 'i' like the project name.

Next we can install yosys using brew:

brew install yosys

NextPnR is a bit trickier because we want to compile it against apicula. We can do this like so:

brew install cmake
brew install eigen

git clone https://github.com/YosysHQ/nextpnr.git

cd nextpnr

cmake . -DARCH=gowin -DGOWIN_BBA_EXECUTABLE=`which gowin_bba` -DPYTHON_INCLUDE_DIR=$(python3 -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())")  \
-DPYTHON_LIBRARY=$(python3 -c "import distutils.sysconfig as sysconfig;import os;  print(os.path.join(sysconfig.get_config_var('LIBDIR'), [f for f in os.listdir(sysconfig.get_config_var('LIBDIR')) if f.endswith('.dylib') or f.endswith('.a')][0]))")

make
sudo make install

Last but not least we can install the openFPGALoader with brew:

brew install openfpgaloader

Windows

For Windows we recommend installing it using WSL 2 which can run Ubuntu on top of windows. To do this we need to first setup WSL, so open up a new powershell window as administrator (right click powershell icon from the toolbar and press run as administrator).

Inside we first need to enable WSL:

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux 

This will enable and setup WSL, once done you will need to restart your computer. Once restarted you can reopen a powershell again as an administrator and run the following:

wsl --update
wsl --set-default-version 2
wsl --install -d Ubuntu-20.04

This will update the wsl kernel, make WSL 2 the default version and install Ubuntu 20.04. Once installed a new window should open prompting you to choose a username and a root password, this new window is the linux ubuntu terminal installed by WSL.

If there is a problem during the installation something about the kernel not being updated you may need to manually download the kernel update from here under step 4. After installing the update manually rerun wsl --install -d Ubuntu-20.04

Next we will need to install usbipd to allow us to connect USB devices to our ubuntu installation. The latest version can be found here https://github.com/dorssel/usbipd-win/releases At the time of writing this the latest installer is usbipd-win_2.4.0.msi

Once installed we need to also install the linux side of this, so in the ubuntu window (if it is closed you can get back to it by running wsl from a powershell window, it is worth noting that only on the initial install will it open in a separate window, when running wsl after the first time it opens in the same window replacing your regular shell). Inside the wsl terminal run the following:

sudo apt update
sudo apt upgrade
sudo apt install linux-tools-virtual hwdata
sudo update-alternatives --install /usr/local/bin/usbip usbip `ls /usr/lib/linux-tools/*/usbip | tail -n1` 20

This will update all packages and install the necessary tools to allow windows to mount the usb devices in linux using usbipd.

Next let's install all the dependencies and pip using the following commands from within the ubuntu wsl terminal:

sudo apt-get update
sudo apt-get install make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \
libffi-dev liblzma-dev git cmake libboost-all-dev libeigen3-dev \
libftdi1-2 libftdi1-dev libhidapi-hidraw0 libhidapi-dev \
libudev-dev zlib1g-dev pkg-config g++ clang bison flex \
gawk tcl-dev graphviz xdot pkg-config zlib1g-dev

sudo apt install python3-pip

This will allow us to install apicula using:

pip install apycula

To make sure the installed binaries from apicula are in the path you can run:

which gowin_bba

And it should print out the path to it, if it doesn't print out the location then we need to add it manually. First we need to find the folder where python installs it's packages to do this we can get the root user base:

python3 -m site --user-base

This should print out a directory in my case it was:

/home/lushaylabs/.local

But in your case the username will be different (instead of lushaylabs) and it might be in a different folder. With this folder found we can add it to our path using:

export PATH=$HOME/.local/bin:$PATH

$HOME is instead of writing /home/lushaylabs in my case, but will be automatically set to your home directory. So if you got a similar python location /home/<username>/.local then you can use the same line here. Otherwise replace it with the path printed by the python command adding on the sub-directory /bin. The :$PATH concatenates the current path to what we are adding to not override all the existing values.

With that you should be able to run which gowin_bba and get back the path.

Finally we can install yosys, nextpnr and openFPGALoader exactly like you would in the ubuntu installation:

Yosys using:

git clone https://github.com/YosysHQ/yosys.git
cd yosys
make
sudo make install

Next we will install nextpnr:

cd ~
git clone https://github.com/YosysHQ/nextpnr.git

cd nextpnr

cmake . -DARCH=gowin -DGOWIN_BBA_EXECUTABLE=`which gowin_bba`

make
sudo make install

Finally openFPGAloader can be installed as follows:

cd ~

git clone https://github.com/trabucayre/openFPGALoader.git
cd openFPGALoader
mkdir build
cd build
cmake ../ 
cmake --build .
sudo make install

To connect the tang nano 9k to the WSL shell you need to open up a powershell terminal as administrator and run the following command:

usbipd.exe wsl list

You should see a device named something like the following:

USB Serial Converter A, USB Serial Converter B

Take its hardware id or its bus id and run one of the following commands:

usbipd.exe wsl attach -i 0403:6010
usbipd.exe wsl attach -b 1-1

The first one is an example if using the hardware id (if the hardware id is 0403:6010), and the second is an example using the bus id if the bus id was 1-1. You only need to run 1 of these you can also add -a to auto-reattach if you unplug and replug the device, although you will need to leave the terminal open in the background if you do this.

After this you should see it say attached if you run usbipd.exe wsl list again.

As a final step here I would also run the following from within ubuntu wsl terminal:

sudo chmod 666 /dev/ttyUSB0
sudo chmod 666 /dev/ttyUSB1

(names might be different if you have other devices connected) but this will make it so that you can access the serial terminal for example without being root.

Ubuntu

These are the steps for ubuntu 22.04:

sudo apt-get update
sudo apt-get install make build-essential libssl-dev zlib1g-dev \
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev \
libffi-dev liblzma-dev git cmake libboost-all-dev libeigen3-dev \
libftdi1-2 libftdi1-dev libhidapi-hidraw0 libhidapi-dev \
libudev-dev zlib1g-dev pkg-config g++ clang bison flex \
gawk tcl-dev graphviz xdot pkg-config zlib1g-dev

The above will install all the pre-requisites for the toolchain. Next we need to install python which we can do by first installing pyenv:

curl https://pyenv.run | bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bashrc
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bashrc
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n eval "$(pyenv init -)"\nfi' >> ~/.bashrc

This will install pyenv, you will need to restart your terminal or source ~/.bashrc for the changes to take effect.

Once restarted we can use pyenv to install python as follows:

pyenv install 3.9.13
pyenv global 3.9.13

And with python installed we can now download apicula:

pip install apycula

With that installed we can install yosys using:

git clone https://github.com/YosysHQ/yosys.git
cd yosys
make
sudo make install

Next we will install nextpnr:

cd ~
git clone https://github.com/YosysHQ/nextpnr.git

cd nextpnr

cmake . -DARCH=gowin -DGOWIN_BBA_EXECUTABLE=`which gowin_bba`

make
sudo make install

Finally openFPGAloader can be installed as follows:

cd ~

git clone https://github.com/trabucayre/openFPGALoader.git
cd openFPGALoader
mkdir build
cd build
cmake ../ 
cmake --build .
sudo make install

Besides for the toolchain itself there is some other software which can aid in the development of FPGA cores, and will be used throughout this series.

VSCode

This is a popular light-weight cross-platform editor with many plugins some of which specifically for FPGA development. The editor itself can be downloaded from here.

Once installed, you can open it up and go to the Extensions tab to install plugins. I personally use the following extensions:

  1. Verilog-HDL/SystemVerilog - provides syntax highlighting support for Verilog
  2. WaveTrace - provides built in waveform viewing which can be used for debugging.

On windows it is worth setting the default terminal to WSL. This can be done by opening a new terminal window and then next to the + icon to create a new terminal window you can click on the little arrow and choose the select default profile option to select the WSL ubuntu option. This way when you open a new terminal it will already be from within wsl.

IVerilog

IVerilog or Icarus Verilog is a version of verilog which is can be used to create tests / simulations easily for your verilog generating a waveform file which can be visually debugged. To install it:

Mac

brew install iverilog

Ubuntu / Windows WSL

sudo apt-get install iverilog

Using the Toolchain

As an example using the toolchain let's take the counter.v file from our getting started guide:

module counter
(
    input clk,
    output [5:0] led
);

localparam WAIT_TIME = 13500000;
reg [5:0] ledCounter = 0;
reg [23:0] clockCounter = 0;

always @(posedge clk) begin
    clockCounter <= clockCounter + 1;
    if (clockCounter == WAIT_TIME) begin
        clockCounter <= 0;
        ledCounter <= ledCounter + 1;
    end
end

assign led = ~ledCounter;
endmodule

We will also need the constraints file called tangnano9k.cst:

IO_LOC "clk" 52;
IO_PORT "clk" PULL_MODE=UP;
IO_LOC "led[0]" 10;
IO_LOC "led[1]" 11;
IO_LOC "led[2]" 13;
IO_LOC "led[3]" 14;
IO_LOC "led[4]" 15;
IO_LOC "led[5]" 16;

The first step to building our project is to synthesize the verilog using yosys.

We can do this manually by running yosys from the terminal that has navigated to the project directory (vs code has a built-in terminal which defaults to here). Once inside we can read our verilog file by running the yosys command read_verilog counter.v

You should see the file successfully being read. Now we need to synthesize the code using the synthesizer for gowin-based FGPAs, we can do this with the synth_gowin command (for a list of all commands you can type help in yosys). The full command should be:

synth_gowin -top counter -json counter.json

The -top option selects which module is the "main" module or entrypoint module and the json option tells yosys to export a json file with the results of the synthesis.

If all went well you should see a new file in your project directory called counter.json you can now exit yosys by typing exit

The next step is placing and routing using nextpnr. The full command needed looks like this:

nextpnr-gowin --json counter.json --freq 27 --write counter_pnr.json --device GW1NR-LV9QN88PC6/I5 --family GW1N-9C --cst tangnano9k.cst

The json parameter defines the input json we received from yosys, freq sets the frequency in Mhz and the write parameter defines the output file or where nextpnr should place the results of this stage. Device and family variables tell nextpnr which gowin FPGA we are using, the information above is the device and family for the FPGA on the tangnano 9k. Finally the last parameter is the constraints file we created.

After running this you should now have a new file  counter_pnr.json. The final step is to convert this routed information into the bits format that the FPGA itself requires. This can be done using gowin_pack from apicula.

gowin_pack -d GW1N-9C -o counter.fs counter_pnr.json

The first parameter -d is to specify which device family we are using the -o is to define the output file with the binary data and the final parameter (no flag) is the json file output from nextpnr.

Again after running this you should have the final binary file (in ascii format) counter.fs

Programming it to the TangNano

Now that we have the bitstream file, we can program it to the tang nano in order to make apply the configuration on the FPGA. With the tang nano we have two main options for programming, we can either program to memory which is super fast and will apply the changes, but if the device restarts it will lose this configuration.

The other option is to program to flash, which is slightly slower but will make the FPGA retain the configuration after restarts (or more accurately make it reload the configuration after restart automatically).

In our example let's program the flash we can do this with the following command (may require sudo permission):

openFPGALoader -b tangnano9k -f counter.fs

The first parameter is the board we are using, the second parameter tells it to use the flash option (leave off the -f to program to memory instead) and the final parameter is the fs file generated by gowin_pack.

If all went well you should now see the tangnano counting up over it's LEDs.

Automating the Build Process

To automate the build process we can create a simple file called Makefile which will define targets or actions. Here is a simple example we can use for our project.

BOARD=tangnano9k
FAMILY=GW1N-9C
DEVICE=GW1NR-LV9QN88PC6/I5

all: counter.fs

# Synthesis
counter.json: counter.v
	yosys -p "read_verilog counter.v; synth_gowin -top counter -json counter.json"

# Place and Route
counter_pnr.json: counter.json
	nextpnr-gowin --json counter.json --freq 27 --write counter_pnr.json --device ${DEVICE} --family ${FAMILY} --cst ${BOARD}.cst

# Generate Bitstream
counter.fs: counter_pnr.json
	gowin_pack -d ${FAMILY} -o counter.fs counter_pnr.json

# Program Board
load: counter.fs
	openFPGALoader -b ${BOARD} counter.fs -f

.PHONY: load
.INTERMEDIATE: counter_pnr.json counter.json

The first three lines simply define variables with the board name device name and device family, same values we used when we compiled it manually.

The next line defines the first target. The name doesn't matter by default when running make if a specific target wasn't specified it will run the first target which in our example is called all.

The format for targets are:

<target name>: <target dependencies>
	<target commands>

So in this target we have no commands to run, but we are saying that it depends on another target called counter.fs. Make will automatically detect that the target name matches a file name and based on whether the file exists or not will know what it needs to do left.

So we essentially created here a dependency chain where:

  1. counter.fs requires counter_pnr.json in-order to run.
  2. counter_pnr.json  requires counter.json in-order to run.
  3. counter.json requires counter.v in-order to run.

So the first target that will actually be run is the target counter.json since we will only have the .v file and not the json / fs files.

Inside each of the targets for you can see we are running the same commands we ran manually. The only difference maybe being that with yosys we are using the -p parameter to pass it the commands inline, instead of entering yosys and typing them manually.

The last two lines are directives for Makefile. The .PHONY directive will tell makefile that the load target is not meant to represent a file. and the .INTERMEDIATE directives tell Makefile that these are files that are not really needed and can be deleted after compilation.

By defining the make file in this manner, Makefile itself can understand when the underlying verilog file has changed and when a new compilation is required on it's own.

To test it out, delete the two json files and fs file we created manually, and from the terminal run make.

You should see it recreate the .fs file and delete the 2 json intermediate files. Running it again you should see that there is nothing to do because it sees none of the dependencies have changed.

Running make from the terminal should recompile everything now, and then running make load (maybe with sudo) will select the load target and program the updated fs file to our FPGA.

It is worth noting we could have also directly ran make load without first running make, since the counter.fs is also a dependency of load and it would have seen the change in the verilog file re-compiling it automatically.

Conclusion

Setting up the open-source toolchain manually can pose its challenges but ultimately it gives you a much better understanding of the toolchain and more control on how and where to set it up.

I would like to thank you for reading and if you have any questions / comments feel free to leave a comment below or reach out on twitter @LushayLabs.

All the code from the above example along with all our other tang nano projects are available in our github repo here and each project has a Makefile so it supports the manual toolchain installation. If you would like to order a tang nano 9K and support this site you can do so from our store.

You've successfully subscribed to Lushay Labs
Great! Next, complete checkout to get full access to all premium content.
Error! Could not sign up. invalid link.
Welcome back! You've successfully signed in.
Error! Could not sign in. Please try again.
Success! Your account is fully activated, you now have access to all content.
Error! Stripe checkout failed.
Success! Your billing info is updated.
Error! Billing info update failed.