Lab 2 - Toolchain & FPGA Bring-up
Goal
By the end of this lab, you should be able to: write a tiny SystemVerilog design, simulate it, synthesize it for the Tang Nano 9K, and see it run on real hardware (an LED blinking is enough).
0. Get your repository
Your pair's repository already contains everything this lab needs: setup.sh, the blink.sv interface, the board pin constraints, a Makefile driving the whole flow, and a worked Verilator testbench. Clone it and follow its README.md — this page explains why each step exists, the repository's README is the literal walkthrough.
1. Install the OSS CAD Suite
Running ./setup.sh at the root of your repository downloads everything below into tools/ for you. If you would rather install it by hand, download a release of the OSS CAD Suite for your platform. It bundles everything we need:
- Yosys - synthesis.
- nextpnr-himbaechel (Gowin backend) - place and route for the GW1NR-9 (Tang Nano 9K's FPGA).
- Verilator - fast SystemVerilog simulation for testbenches.
- GTKWave - waveform viewer.
- openFPGALoader - bitstream upload over USB.
Add the suite's bin directory to your PATH:
The suite also ships an environment.sh/activate script that does this for you - but that script also reorders several other things (its own bundled Python among them), which can quietly break unrelated Python-based tools you install later in the course (e.g. the optional LiteX tutorials need litex, a Python package, importable from python3 - sourcing the full script can shadow that with the suite's own bundled Python instead). Prefer the plain PATH export above - it's all any command in this course actually needs from the suite.
2. Connect the board
Connect the Tang Nano 9K via USB. Verify openFPGALoader can detect it:
It should list one device with the GW1NR-9C part. Seeing nothing is the most common way to get stuck in this lab, and the cause depends on your system.
Linux, and inside WSL2 — udev rules
Non-root users cannot open the USB device until the udev rules are installed:
Unplug and replug the board afterwards. If it still fails, add yourself to plugdev (sudo usermod -aG plugdev $USER) and log out and back in.
Windows + WSL2 — the board is not there at all
WSL2 has no access to USB devices by default. Installing the toolchain inside WSL is not enough: you must forward the device in from Windows with usbipd-win.
Then, in an administrator PowerShell:
Attaching is needed every time you replug the board, and does not need administrator:
Keep a WSL terminal open while doing this, or the WSL VM shuts down and the device detaches. Check with lsusb inside WSL — and you still need the udev rules above.
If this is more friction than you want to live with for a whole semester, a native Linux install or dual boot will save you time.
3. Blink an LED
hw/rtl/bringup/blink.sv in your repository gives you the module interface — parameters, ports, and a placeholder body. The implementation is yours: a counter fed by sys_clk that flips the LEDs at BLINK_HZ. Keep the port names as they are; hw/constraints/tangnano9k.cst maps them onto physical pins.
The placeholder already builds, so run the flow once before writing any logic — it proves your toolchain works, and the LEDs staying off is the expected result:
Behind those two targets are three tools, each consuming the previous one's output:
Read the Makefile — you should be able to point at the line that runs each of these. make blink-synth, make blink-pnr and make blink-pack stop after each stage, which is how you find out which one is complaining.
Never write the board's flash
make blink-program loads the bitstream into the FPGA's SRAM, so it is gone after a power cycle. That is the only mode we use: the on-board flash takes a limited number of write cycles and these boards are reused across semesters.
4. Simulate with Verilator
This course uses two testbench styles, and blink is given in both so you can compare them on the same design before writing your own:
Both run the same four checks and fail the build if your blink is wrong, so make itself tells you whether the design is right.
A SystemVerilog testbench is a module with no ports: it instantiates the design under test, drives it from an initial block, generates the clock with an always block, and overrides the DUT's parameters where it instantiates it. Reach for this first — it keeps everything in one language, and it is all a self-contained design needs. This is the style you will use in Lab 3.
A C++ harness compiles the design into a C++ class you drive from main(): every port becomes a member, eval() recomputes the outputs, and toggling the clock yourself is what advances time. More ceremony, but the testbench is an ordinary C++ program — it can read real data files, call a reference implementation written in C and compare against it, or push thousands of vectors. That is what the KWS kernel and accelerator labs need, which is why you meet it now.
Both write a waveform; open whichever you just ran:
Note the parameter override. Blinking at 1 Hz from a 27 MHz clock is 27 million cycles per blink — far too slow to simulate. The SystemVerilog testbench sets CLK_FREQ_HZ where it instantiates the DUT; the C++ one cannot reach into a parameter list, so it takes VFLAGS=-GCLK_FREQ_HZ=1000 on the command line instead. Either way, this is why blink.sv takes the frequency as a parameter rather than hardcoding it.
Reading both files is part of this lab. They are commented as tutorials, and every testbench in this course is one of those two shapes.
Checklist
- OSS CAD Suite installed and on
PATH. - Board detected by
openFPGALoader --detect(udev rules on Linux; usbipd on WSL2). -
make blinkproduces a bitstream andmake blink-programloads it. - Your
blink.svimplementation actually blinks the LEDs on the board. -
make sim-blinkandmake simcc-blinkboth report~~~ALL TESTS PASSED~~~. - You have read both
tb_blink.svandtb_blink.cppand can say what each line does. - You opened the resulting waveform in GTKWave.
-
EQUIPE.mdfilled in; work committed and pushed.