Electronics Software Development

Getting Started With The TinyFPGA BX

TinyFPGA Graphic
Written by John Woolsey

Skill Level: Beginner

Table Of Contents

Introduction

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

Background Information

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.

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 top module.

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 7,999,999 needed.

Line 30 assigns the LED output pin (on-board LED) to the new clk_1hz clock.

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 a and b along with outputs of lt, eq, and gt. The 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 a and 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.

The initial block, beginning on line 18, sets up and performs our tests. The $dumpfile and $dumpvars commands will save all waveforms created during the simulation to the comparator_tb.vcd file.

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 (%d, $stime) and module name (%m).

The $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 a, b, lt, eq, or gt changes.

The for-loops beginning on line 24 will compare all possible input values of a and b (0-3 for a WIDTH of 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.

Schematic Diagram Of TinyFPGA Based Comparator Circuit
Schematic Diagram Of TinyFPGA Based Comparator Circuit

The circuit should look similar to the one shown below once completed.

Completed TinyFPGA Based Comparator Circuit
Completed TinyFPGA Based Comparator Circuit

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 (a, b, lt, eq, 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[1] A2
set_io --warn-no-port DIPSW[2] A1
set_io --warn-no-port DIPSW[3] B1
set_io --warn-no-port DIPSW[4] C2
set_io --warn-no-port DIPSW[5] C1
set_io --warn-no-port DIPSW[6] D2
set_io --warn-no-port DIPSW[7] D1
set_io --warn-no-port DIPSW[8] 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.

Additional Resources

The following is a list of resources I found useful for this tutorial.

Summary

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.

About the author

John Woolsey

John is an electrical engineer who loves science, math, and technology and teaching it to others even more.
 
He knew he wanted to work with technology from an early age, building his first robot when he was in 8th grade. His first computer was a Timex/Sinclair 2068 followed by the Tandy 1000 TL (aka really old stuff).
 
He put himself through college (University of Texas at Austin) by working at Motorola where he worked for many years after that in Research and Development.
 
John started developing mobile app software in 2010 for himself and for other companies. He has also taught programming to kids for summer school and enjoyed years of judging kids science projects at the Austin Energy Regional Science Festival.
 
Electronics, software, and teaching all culminate in his new venture to learn, make, and teach others via the Woolsey Workshop website.

Leave a Comment