Skill Level: Beginner
Table Of Contents
- What Is Needed
- Background Information
- Installing The Toolset
- Creating Your First Design
- Building A Comparator
- Testing The Comparator
- Finalizing Your Design
- Additional Resources
This tutorial will teach you how to blink an LED on a TinyFPGA BX board. I will also expand on the basics and show you how to design, test, and build a simple comparator circuit that will run on your FPGA board. A basic understanding of electronics and programming is expected. I will be using the Verilog hardware description language for this tutorial, so some understanding of the language is helpful, but not strictly necessary. In addition, this tutorial will use a solderless breadboard to build a circuit from a schematic diagram. The All About Circuit’s Understanding Schematics, SparkFun’s How to Read a Schematic, Core Electronics’ How to Use Breadboards, and Science Buddies’ How to Use a Breadboard, guides are good resources for learning how to translate a schematic to a breadboard.
This tutorial is not meant to teach you Verilog, but I will point out some of the more interesting features of the language as they are used. Please see the Verilog specific links in the Additional Resources section at the bottom of this tutorial if you are interested in learning more.
The resources created for this tutorial are available on GitHub for your reference.
What Is Needed
- Linux, macOS, Or Windows Based Computer With A USB Port
- TinyFPGA Development Board (available on Crowd Supply and Adafruit) With Compatible Micro USB Cable
- 8-Position DIP Switch (available on SparkFun and Radio Shack)
- Standard 5mm Green, Yellow, and Red LEDs (one each, available on Adafruit and SparkFun)
- 8 x 10 KΩ Resistors (available on SparkFun and Amazon)
- 3 x 330 Ω Resistors (available on SparkFun and Amazon)
A field programmable gate array (FPGA) is an integrated circuit that contains a large number of configurable logic blocks (CLB) that can be wired together to create custom digital circuits. FPGAs, depending on their size, can accommodate very large and complex designs.
Hardware description languages (HDL), such as Verilog or VHDL, are used to design your custom digital logic using high level programming constructs. These programs are then compiled into synthesized netlists containing primitives such as AND and OR gates. The netlists are then compiled further into FPGA specific bitstreams that are uploaded to FPGA boards to run your custom digital logic designs in actual hardware.
TinyFPGA is a family of low cost, open source FPGA development boards designed and created by Luke Valenty. The boards are supported by free (and many open source) toolsets.
I will be using the TinyFPGA BX board and APIO toolset for this tutorial running on macOS.
Installing The Toolset
The TinyFPGA BX User Guide does a good job of helping you install the required tools and getting you started with an example project. Follow the instructions in that guide first in order to get your initial toolset up and running with your board. I will then go into a little bit more detail, especially with using the command line.
APIO provides a simple command line interface to verify, build, simulate, and upload your Verilog based designs. It uses the following tools for specific functionality under the hood.
- IceStorm – synthesizing FPGA bitstreams
- Icarus Verilog – simulation synthesis
- GTKWave – viewing simulation waveforms
The IceStorm and Icarus Verilog tools were installed as part of the APIO package, but GTKWave was not. The GTKWave application still needs to be installed if you are interested in viewing signal waveforms created during test bench simulations. I will be using this functionality later in this tutorial. Click the GTKWave link above and follow the instructions to install the appropriate binary for your system. Once installed, add the executable location to your path. This will enable GTKWave to function properly in both the Atom editor and the APIO command line. The path is /Applications/gtkwave.app/Contents/Resources/bin for my Mac.
Creating Your First Design
We are now going to create a very simple blink style project and upload it to our FPGA. It just blinks the on-board LED once every second.
Open a terminal window, create a directory named blink, then go into that new directory. Initialize a new project with the following command.
$ apio init --board TinyFPGA-BX
The above command creates an apio.ini file in the current directory that configures the project for the TinyFPGA BX board. The APIO IDE toolbar within the Atom editor has this capability in general, but the TinyFPGA BX board is currently not available for selection. It is my understanding it will be available in the next release.
Next, copy the pins.pcf file from the apio_template project used in the TinyFPGA BX User Guide into the new blink project directory. The pins.pcf file defines the pins available on the TinyFPGA BX board. For instance, the CLK pin (defined on line 94) and the on-board LED pin (defined on line 86) will be used as an input and output respectively in our design. The –warn-no-port flags print warnings during the build if that particular pin is not used. These can be ignored.
Now you can either open the Atom editor and continue operations using the APIO-IDE plugin toolbar or continue with the command line. I will describe how to perform operations using both methods. If you are continuing with the Atom editor, go back a directory and then open the editor with the blink project using the following.
$ atom blink
Create a new file named top.v using either the Atom editor (File > New File from the main menu) or your favorite editor that contains the following contents.
// blink - top.v // // Description: // Blinks the on-board LED once per second. // Pins are defined in pins.pcf file. // // Created by John Woolsey on 08/26/2019. // Copyright © 2019 Woolsey Workshop. All rights reserved. module top ( input CLK, // 16 MHz on-board clock output LED, // on-board LED output USBPU // USB pull-up enable, set low to disable ); reg [22:0] clk_1hz_counter = 23'b0; // 1 Hz clock generation counter reg clk_1hz = 1'b0; // 1 Hz clock // 1 Hz clock always @(posedge CLK) begin if (clk_1hz_counter < 23'd7_999_999) clk_1hz_counter <= clk_1hz_counter + 23'b1; else begin clk_1hz_counter <= 23'b0; clk_1hz <= ~clk_1hz; end end assign LED = clk_1hz; // blink on-board LED every second assign USBPU = 1'b0; // disable USB endmodule // top
By the way, the name of the module is named
top since it represents the top level of the design. Child modules should be named according to their functionality. For example, later we will have a
comparator module instantiated into our
Lines 21-28 generate a 1 Hz clock (
clk_1hz) from the on-board 16 MHz clock (
CLK). This uses the 23-bit
clk_1hz_counter (register) to negate the value of
clk_1hz every 8,000,000 cycles of the 16 MHz clock. Only 8,000,000 is needed, instead of the full 16,000,000, since we are updating
clk_1hz for each half period for full periods of the on-board clock. The
clk_1hz_counter is required to have a minimum size of 23-bits since 2^23 = 8,388,608, which just fits the highest counter value of
Line 30 assigns the
LED output pin (on-board LED) to the new
Likewise, line 31 assigns the 1 bit value of
0 to the
USBU output pin to disable the USB during operation.
Once the top.v file is created, verify there are no syntax errors by either clicking the Verify icon within the APIO toolbar of the Atom editor or running the following from the command line. Note, if you hover your cursor over the icons within the APIO toolbar, it will display the command associated with that icon.
$ apio verify
Fix any errors it found. Now build the project by either clicking the Build icon within the APIO toolbar or running a build from the command line. This will synthesize the design and generate the bitstream file that will be uploaded to the FPGA.
$ apio build
Now it is time to upload our design to the FPGA. Connect your FPGA board to the USB port on your computer if it is not already connected. Either click the Upload icon within the APIO toolbar of the Atom editor or run the following from the command line to upload your design. You will probably need to press the reset button on your board to reactivate the bootloader again if the upload did not occur properly.
$ apio upload
Once uploaded, you should see the on-board LED blinking. By the way, the upload command will automatically run the build command for you if needed so you do not specifically have to run the build command in the future. When you’re satisfied that your design is working properly, you can remove all of the temporary build files by either clicking the Clean icon within the APIO toolbar or running the operation from the command line.
$ apio clean
You should now have the basic building blocks under your belt for creating and uploading your designs to your TinyFPGA BX development board.
Building A Comparator
In this section, we will start going a little further to create a slightly more complicated design. We will build a comparator that will compare two input numbers and provide output signals as to whether the first number is less than, equal to, or greater than the second number.
Let’s begin by creating a new project named comparator. Create the directory, go into the new directory, and initialize the project like we did previously for the blink project.
$ apio init --board TinyFPGA-BX
If you are using the Atom editor, go back a directory and then open the Atom editor with your new comparator project.
$ atom comparator
Create a new file name comparator.v with the following contents.
// comparator - comparator.v // // Description: // Compares two input values. // // Created by John Woolsey on 08/26/2019. // Copyright © 2019 Woolsey Workshop. All rights reserved. module comparator #( parameter WIDTH = 8 // data width ) ( input [WIDTH-1:0] a, b, // values to compare output lt, // high when a < b output eq, // high when a = b output gt // high when a > b ); assign lt = (a < b) ? 1'b1 : 1'b0; assign eq = (a == b) ? 1'b1 : 1'b0; assign gt = (a > b) ? 1'b1 : 1'b0; endmodule // comparator
As you can see, this is a fairly simple design with inputs of
b along with outputs of
assign statements compare the inputs and set the appropriate outputs. This is a good example showing how complex hardware can be built using simple Verilog statements. The
WIDTH parameter determines the size of our inputs and its value can be passed in from a parent module. The default of
8 means that
b will each be 8 bits wide.
Verify your comparator’s Verilog code is syntactically correct (APIO toolbar’s Verify or $ apio verify) before moving to the next section.
Testing The Comparator
Although the comparator design appears to be quite simple and straightforward, it is always a good idea to test your design before implementation. This is not quite as important when using an FPGA, but it is crucial when implementing and fabricating a chip in silicon.
Create a new file named comparator_tb.v with the following contents. The _tb means it is the testbench for our underlying module. The testbench code tests that our design is correct.
// comparator - comparator_tb.v // // Description: // Testbench for comparator module. // // Created by John Woolsey on 08/26/2019. // Copyright © 2019 Woolsey Workshop. All rights reserved. `timescale 1ns/10ps module comparator_tb; parameter WIDTH = 2; // data width integer i, j; // for-loop variables reg [WIDTH-1:0] a, b; // input values to compare wire lt, eq, gt; // output comparison status initial begin $dumpfile("comparator_tb.vcd"); // waveforms file $dumpvars; // save waveforms $display("%d %m: Starting testbench simulation...", $stime); $monitor("%d %m: MONITOR - a = %d, b = %d, lt = %d, eq = %d, gt = %d.", $stime, a, b, lt, eq, gt); #1; for (i = 0; i < 2 ** WIDTH; i = i + 1) begin for (j = 0; j < 2 ** WIDTH; j = j + 1) begin #1 a = i; b = j; #1; if (a < b && (!lt || eq || gt)) begin $display("%d %m: ERROR - Status flags lt (%d) eq (%d) gt (%d) are not correct for a (%d) less than b (%d).", $stime, lt, eq, gt, a, b); $finish; end if (a == b && (lt || !eq || gt)) begin $display("%d %m: ERROR - Status flags lt (%d) eq (%d) gt (%d) are not correct for a (%d) equal to b (%d).", $stime, lt, eq, gt, a, b); $finish; end if (a > b && (lt || eq || !gt)) begin $display("%d %m: ERROR - Status flags lt (%d) eq (%d) gt (%d) are not correct for a (%d) greater than b (%d). ", $stime, lt, eq, gt, a, b); $finish; end end end #1 $display("%d %m: Testbench simulation PASSED.", $stime); $finish; // end simulation end // Instances comparator #(.WIDTH(WIDTH)) comparator_1(.a(a), .b(b), .lt(lt), .eq(eq), .gt(gt)); endmodule // comparator_tb
Let’s first look at the bottom of the file at line 47. This line creates a child instance of the
comparator module with the name of
comparator_1. The comparator’s ports are connected to wires and registers that have the same name in the parent testbench module. It is not necessary to use the same names, but it can be very helpful during testing and debugging. We are also passing along our
WIDTH parameter which in this case is set to
2 on line 13.
Line 10 sets the
timescale that defines the time unit reference and precision times. This means that whenever we use a delay (
#) in our testbench, it will be set as a multiple of
1ns. For example, the command
#5; will create a delay of 5 nanoseconds.
initial block, beginning on line 18, sets up and performs our tests. The
$dumpvars commands will save all waveforms created during the simulation to the
$display command displays text to the terminal window. The one on line 21 specifically lets the user know that a simulation is starting along with printing the simulation time (
$stime) and module name (
$monitor command is like a special
$display command that will print each and every time any one of the arguments change. Line 22 will specifically notify us when
The for-loops beginning on line 24 will compare all possible input values of
b (0-3 for a
2) and display any errors that occur. The
$finish command will stop and exit the simulation.
Run a test simulation (APIO toolbar’s Simulation or $ apio sim) and you should see the
$monitor command displaying value changes along with
34 comparator_tb: Testbench simulation Passed.
in the simulation output. If you are using the Atom editor, this will appear in the output pane at the bottom of the editor window. If GTKWave was installed correctly, a GTKWave window should have popped up displaying the simulation waveforms. The simulation output will also show you if it could not complete the simulation due to syntax errors.
To view the waveforms, select comparator_tb listed at the bottom of the left top panel, select all of the signals shown in the left bottom panel, click the Append button under that panel, then select Time > Zoom > Zoom Best Fit from the main menu. You should now see waveforms for all of the signals in the testbench across the entire simulation time. The lt, eq, and gt signals should be high for appropriate values of a and b. If you click somewhere in the waveform viewer, it will show you the values for all of the signals at that particular simulation time. Viewing signals is very handy for debugging and ensuring your design is correct.
Once everything is looking good, exit the GTKWave application.
Finalizing Your Design
Now it’s time for us to finalize our comparator design and upload it to the FPGA.
Disconnect your FPGA board from your computer. Attach the pin headers if they are not already soldered on to your board and then wire up the comparator circuit on a breadboard as shown below. I am using an 8 position DIP switch to input the two binary numbers (a and b), each being 4 bits wide, which allows for numbers in the range of 0 – 15. Set all of your switches to their off positions.
The circuit should look similar to the one shown below once completed.
We now need to create our parent (top) module for the FPGA. Create a new file named top.v with the following contents.
// comparator - top.v // // Description: // Compares two values read from a DIP switch and displays the results on LEDs. // Pins are defined in pins.pcf file. // // Created by John Woolsey on 08/26/2019. // Copyright © 2019 Woolsey Workshop. All rights reserved. `timescale 1ns/10ps module top #( parameter WIDTH = 4 // data width ) ( input [1:8] DIPSW, // 8 x DIP switches (a = DIPSW[1:4], b = DIPSW[5:8]) output LED_G, // green status LED, high when a < b output LED_Y, // yellow status LED, high when a = b output LED_R, // red status LED, high when a > b output USBPU // USB pull-up enable, set low to disable ); wire [WIDTH-1:0] a, b; // values to compare wire lt, eq, gt; // comparison results assign a = DIPSW[1:4]; assign b = DIPSW[5:8]; assign LED_G = lt; assign LED_Y = eq; assign LED_R = gt; assign USBPU = 1'b0; // disable USB // Instances comparator #(.WIDTH(WIDTH)) comparator_1(.a(a), .b(b), .lt(lt), .eq(eq), .gt(gt)); endmodule // top
The top level module should be fairly straight forward to understand so I won’t go into any detail here. I will note, however, that I could have chosen not to create any extra wires (
gt) and just assigned the FPGA ports directly to the comparator ports, but I decided to match the comparator port names for readability. If I had chosen to do so, all of the wire creation and assign statements, except for
USBU, could have been deleted and then the comparator would be instantiated with the following.
comparator #(.WIDTH(WIDTH)) comparator_1(.a(DIPSW[1:4]), .b(DIPSW[5:8]), .lt(LED_G), .eq(LED_Y), .gt(LED_R));
Next we need to create the pins.pcf file to define our hardware based pin connections to the FPGA board. Copy over the one we used for the blink project earlier and then make the following modifications that define the DIP switch and the three LEDs.
# Left side of board set_io --warn-no-port DIPSW A2 set_io --warn-no-port DIPSW A1 set_io --warn-no-port DIPSW B1 set_io --warn-no-port DIPSW C2 set_io --warn-no-port DIPSW C1 set_io --warn-no-port DIPSW D2 set_io --warn-no-port DIPSW D1 set_io --warn-no-port DIPSW E2 set_io --warn-no-port LED_G E1 set_io --warn-no-port LED_Y G2 set_io --warn-no-port LED_R H1 set_io --warn-no-port PIN_12 J1 set_io --warn-no-port PIN_13 H2
Normally we would write and simulate separate testbenches for all sub-modules along with the top level module. In this case, since we are only incorporating a single module, the
comparator, and then changing the port names, I will forgo creating a separate
top_tb testbench this time. Shame on me, but I believe that risk is acceptable in this case since we are using an FPGA. It could be a good exercise for you though to test your knowledge.
Ready to upload our new design to the FPGA? Connect your board to the USB port on your computer, then initiate the upload command (APIO toolbar’s Upload or $ apio upload). The APIO tool will build and upload your design to the FPGA board. If necessary, fix any errors that are reported and try building again. Remember, you may have to press the reset button on your board to reactivate the bootloader if the upload did not occur properly.
Once uploaded, you should see the yellow LED lit since all of the DIP switches are turned off and 0 = 0. Turn on and off the various switches to enter your 4-bit binary a and b values. View this Wikipedia page, especially the figure on the right side of the page, if you are not familiar with binary numbers.
The following is a list of resources I found useful for this tutorial.
- TinyFPGA Website
- TinyFPGA BX GitHub
- APIO GitHub
- APIO Documentation
- Wikipedia: Verilog
- Wikipedia: Binary Number
- Verilog HDL Quick Reference Guide
- ASIC WORLD
- NCSU Digital ASIC Design With Verilog Course
In this tutorial, we learned how to create, test (simulate), view signal waveforms, build, and upload digital designs to a TinyFPGA BX development board. We used the Verilog language to describe our designs and the APIO toolset to implement them.
The final source code and schematic used for this tutorial are available on GitHub.
Thank you for joining me in this journey and I hope you enjoyed the experience. Please feel free to share your thoughts in the comments section below.