The FPGA Chronicles: Open Source It

Wait 5 sec.

Last time, we looked at getting started with the GOWIN tools and a Tang Nano 20K FPGA. The software from GOWIN isn’t bad, but it isn’t open source, and there are a few oddities about it. In addition, simulation is through a third-party simulation package that has undergone some changes since an acquisition. There are tons of free simulation programs that are extremely good, and there is an open-source toolchain for the FPGA.You could go grab everything you need piece by piece. But you don’t have to. There are several efforts to produce a toolchain from all the different pieces. We’re going to look at APIO.APIOAPIO isn’t so much an FPGA toolchain project as it is an aggregator of toolchain projects. It reminded us of PlatformIO, and notes that it was inspired by it. It updates the tools you need, includes its own libraries, and gives you a common workflow across the FPGAs it supports.You can download it for the command line, but you can also install it as a Visual Studio Code extension, which is what I did. You have to create a simple file that describes your project, and that’s about it.Install ProblemsSince APIO has its own libraries, it is possible that you will find some conflicts with your system libraries. In my case, the libreadline.so.8 file (in ~/.apio/bin/_internal) was causing problems that prevented anything from working. I simply renamed it out of the way, or you can just delete it. That took care of the problem.Keep in mind that APIO just orchestrates a bunch of other tools like Yosys and GTKWave. Even if you have your own versions, APIO expects to use its private copies. For example, GTKWave on my system is a different version than the APIO copy, and if I try to read wave files without using APIO, I get error messages. You can, however, open a shell from the Tools/Misc menu of the APIO panel in Visual Studio Code.The APIO Project FileI made a simple LED-blinking design to test APIO. We’ll talk more about the Verilog inside of it next time, but for now, you can treat it as a black box. Each APIO project needs an apio.ini file. Here’s mine:[env:default]board = sipeed-tang-nano-20ktop-module=ledThat’s it. Of course, you can do more. But you don’t often need any more than this. However, you’ll see at the end of this post that I usually add a little more to this bare-bones project.You can use the apio create command to start a project, if you like. You can set options to set the board, the top module, and the path. However, there doesn’t seem to be an easy way to run that from inside Visual Studio Code. The best way I’ve found is to open the APIO panel, click on Tools, and then APIO Shell. From there, you can run apio create with the arguments: use apio create -h to see what you can and must specify.Win Some, Lose SomeA large camp of people will ask, “Why did you use the GOWIN tools at all if this exists?” Another camp asks, “Why bother with this when the GOWIN tools work just fine?” Turns out, there are reasons you might choose one over the other.The open-source tools are excellent when it comes to Verilog. With APIO, you can format your source code and check it for correctness using a sophisticated linter. Then there’s simulation. APIO has a great driver for simulating designs with powerful tools.The GOWIN tools are fine. The simulator, however, is tied to a simulator that appears to no longer be accessible in the way they are using it. That may change, of course, but great open-source tools exist, and APIO makes them trivial to use.A spreadsheet for creating constraintsSo why use GOWIN? If you want to use their IP (function blocks that do things as mundane as generating a clock or as complex as creating a CPU), you will find it difficult to integrate these with APIO, although probably not impossible. There’s also a facility that can add a logic analyzer into your design so you can examine things at runtime. That isn’t easily adaptable to APIO either. There are some open ways to do similar things, but they aren’t as neat as the built-in tools.You also lose the nice GUI constraints editor: the thing that lets you define what pins correspond to what Verilog names. That’s a small problem, because you can just write your own constraint files as text. Or you can use this spreadsheet I built to make it a little easier, or visit GitHub for a lightly-tested Excel version. Just save your own copy and edit it. If you prefer using Visual Studio Code, try this constraint editor extension.There is one other issue. Which synthesis tool does a better job? That’s hard to say without testing and, even then, one may excel in one area and fall behind in another. Do you want faster performance? Lower resource usage? Access to all the special device resources? The only way to know for sure is to try both. Keep in mind that APIO itself doesn’t do the Verilog synthesis or place and route. Other similar toolchain aggregators exist, but they often use the same tools, so choosing one over the other won’t materially change the end result.Example for Open SourceYou can download the example files. For now, I want to focus on using APIO instead of the details of the Verilog. This design uses a 27 MHz clock. There are several ways to get a clock like this into the FPGA. However, clocks are a special kind of signal to FPGAs, and most of them, including ours, have special pins that work best as clocks.Since we won’t look “inside the box” this time, here’s the key part of the led top module:module led #( parameter integer CLOCK_FREQUENCY = 27_000_000, parameter integer INTERVAL_MS = 500) ( input wire reset, input wire Clock, input wire serialin, input wire pushbtn, output wire [5:0] leds, output wire serialout);This lets the module expect a certain clock frequency and a certain blink rate. There are inputs for reset, Clock, serialin, and pushbtn. There are also outputs for the LEDs and serialout. (We’re not using the serial or pushbuttons for this article.)Programming the ClockWhen you plug in the Nano 20K, you should get a serial port. On Linux, try ls -l /dev/serial/by-id to figure it out. If you open that with your favorite serial terminal program, you’ll probably see… nothing. That’s because the FPGA isn’t sending or receiving anything.The TangNano20K menuHowever, the serial port goes through a microcontroller, the same one that flashes the FPGA. It is always watching for a special input character sequence ^X^C [Enter]. That should give you a “TangNano20K />” prompt. If you suspect your version might be old, you can update it using Sipeed’s instructions. Make sure you pick the correct firmware from the table, as there are similar versions you can flash that look newer but are incompatible.The board has a few clocks you can program. We will use O0, which is on FPGA pin 10. So once you are at the prompt, you can type:pll_clk O0=27M -sIf you want to check the status, you can just use pll_clk with no arguments. We don’t use the UART at the moment, but if you did, “choose uart” would put it back in normal mode.Go ahead and download the simple LED project, open Visual Studio Code, and, if you haven’t already, use the extensions marketplace to install the APIO extension. You’ll be able to open the project.From the APIO panel, you can open the “make” commands and select “build.” Once that works, you can use “upload” to send the file to the device’s flash, by default. APIO behaves like Make. That is, if you upload and the source changes, it rebuilds.Instead of flashing the device every time, I wanted to program to SRAM by default. APIO supports environments, so I changed my ini file to look like this:[apio]default-env = ram[common]board = sipeed-tang-nano-20ktop-module = led[env:ram]programmer-cmd = openFPGALoader -b tangnano20k ${BIN_FILE}[env:flash]programmer-cmd = openFPGALoader -b tangnano20k -f ${BIN_FILE}Now, by default, the download goes to SRAM. I can’t find a way to make the Visual Studio Code extension switch environments, however. To flash, you’ll have to open the APIO shell and run:apio upload --env flashOne other small point. I often like to disable files that I use for simulation or debugging, and the GOWIN IDE supports that. For example, I might have three copies of a module that have different debugging outputs. Unfortunately, there’s no easy way to do that using APIO. The best I’ve found is to rename files like foo.v and foo-test.v to foo.v.option and foo-test.v.option. Then you can link foo.v to either one and could even script that easily, if you like. Or just rename them every time, which is also easy to script.Testing, One, Two…It is nice to test your designs before you put them on real hardware. It is often much easier to debug things when you can see everything and set up specific conditions for the test. The way to do that is to create a testbench. This is a Verilog file that drives the “real” Verilog and generates some output.In a regular Verilog file, you have to be careful to only do things the synthesizer can reasonably do on the FPGA. If you do something wrong, it will sometimes refuse. Sometimes, though, it will just generate terrible results that you don’t really want. But in a testbench, you can do lots of things like delays. The simulator is more like a software simulation of parallel execution, so it is much more forgiving.Here’s a simple testbench that we will revisit in a later installment. For now, it is already in the project. `timescale 1ms/1us// each "tick" is worth 1ms so #100 == 0.1 second delay`default_nettype nonemodule led_tb; reg clk; reg reset; wire [5:0] leds; wire serialout;// create top block for testing led #( .CLOCK_FREQUENCY(10), // but tell it to expect 10 Hz .INTERVAL_MS(1000) ) uut ( .reset(reset), .Clock(clk), .serialin(1'b0), .pushbtn(1'b0), .leds(leds), .serialout(serialout) );// generate simulation clock initial begin clk = 0; forever #50 clk = ~clk; end// simulation "main" initial begin $dumpvars(0, led_tb); // store variables reset = 1; // generate a reset pulse #200; reset = 0; #50000 // let simulation run for a bit $finish; // exit endendmoduleHere’s the basic idea: We create the top module of our design (led), but we wire it to run at 10 Hz with a 1-second interval. You could simulate at 27 MHz, but it would create huge files that are harder to work with. At the start of the simulation, we generate a clock change every 50 milliseconds, so the period is 100 milliseconds (10 Hz).The last initial block also operates at the beginning. It first sets the variables we want to dump. If you look at other tutorials or you’ve done this before, you might wonder where $dumpfile is. APIO likes to set that itself. It also insists that you name the file something_tb.v (or _tb.sv if you are using SystemVerilog). You can change the something part, but it has to end with that pattern for APIO to pick it up.Simulation output shows it all.If you don’t include $dumpvars, you’ll get an empty simulation file. Then there’s a brief reset signal. Then we wait a long time (#50000 is 50 seconds here, but the simulator will run that much faster, so you won’t have to wait). Then there’s a call to $finish. Without that, the simulator will run forever.Once you have this file in place, you can open the APIO verify and select “sim.” The result is a nice graphic view in GTKWave. You can add more signals from deeper down in the hierarchy.Usually, it is pretty easy to write your own testbench. However, people have tried to automate the job with varying degrees of success. Years ago I forked one such tool and made some changes but it’s not hard to find a Verilog file it will not parse. You could also ask your favorite LLM to do it. In fact, I asked ChatGPT to do one, and it produced one almost identical to the one above, other than some of the time choices.You can see a demo of the simulation in the video below.UnrealOf course, the simulation only gets you so far. If you want to probe the design live, you can do that too with the GOWIN tools. We’ll be looking at that in the future. Next time, we’ll look more at how this demo works and add some important features to it.We also need to talk about things like metastability, debouncing switches, and crossing clock domains. Fun stuff. But between this and the previous post, this should square you away on toolchains for now. Drop by the Discord and stay tuned for the next installment.