April 24, 2020

Hello Arty - Part 1

People who are really serious about software should make their own hardware.
Alan Kay, Creative Think seminar, 1982

This three-part tutorial provides a quick introduction to FPGA development with SystemVerilog and the Digilent Arty A7 board. No prior experience of FPGA development is required, but basic knowledge of programming concepts is assumed. If you can write a simple program in Arduino, Python, or JavaScript, then you shouldn’t have any trouble.

I find working with FPGAs gives me a sense of delight so often lacking in modern software development. There’s something profoundly satisfying about designing at the hardware level, be it drawing graphics on a screen, producing sound from a speaker, or even implementing your own arcade game from scratch. I hope you find this tutorial useful and have fun experimenting with FPGAs.

Revised 2020-05-04. Get in touch with @WillFlux or open an issue on GitHub.

Requirements

For this series, we are using the Digilent Arty A7-35T, a $130 dev board based on a Xilinx Artix-7 FPGA. This board is widely available and supports Xilinx’s Vivado software, which runs on Linux and Windows 10.

For this Hello Arty series you need:

  1. Digilent Arty A7-35T
  2. Micro USB cable to program and power the Arty
  3. Xilinx Vivado 2019 or later
    NB. Linux users need to install cable drivers after running setup
  4. Digilent board files

The original Arty (without the A7) is the same physical board so you can use that too.

Source

All the designs featured in this tutorial are available in the Exploring FPGAs repo. Look out for [src] throughout the post for links to the relevant files. The designs are open source under the permissive MIT licence, but the tutorial itself is subject to normal copyright restrictions.

Hello World with SystemVerilog & Vivado

We’re going to start with the traditional dev board “hello, world”: using simple logic to control the green LEDs on our board. In part 2 we move onto clocks, counting, and pulse width modulation.

Terminology

Before we dive into creating our first project, I should explain a couple of terms:

  • SystemVerilog is a hardware description language (HDL) that allows you to express your design at a (reasonably) high level instead of directly describing logic gates (AND, OR, NOT). This is roughly analogous to how C lets you express your software design at a higher level than assembly language. Other HDLs include VHDL, SpinalHDL, and nMigen.
  • Vivado is the FPGA development suite from Xilinx. It includes everything you need to write SystemVerilog and program a Xilinx FPGA. There’s no getting around the fact it’s a monster, requiring around 20 GiB of disk space. Thankfully it has a no-cost version and is reasonably intuitive once you’ve played with it for a little while. Vivado is available for Windows and Linux (Ubuntu RHEL/CentOS, & SUSE), but not for macOS.

The Arty Board

Arty Board

In this part, we’re interested in the four green LEDs (LD4-7), four sliding switches (SW0-3), and four buttons (BTN0-3). You can see these towards the bottom of the board in the above photo. The FPGA itself is the square chip in the middle of the board labelled “Artix-7”.

Hello World Project

For our first SystemVerilog, we’re going to use a switch to turn an LED off and on. We then extend this to cover multiple LEDs, switches, and buttons.

Creating a Project

  1. Start Vivado.
  2. Select “Create Project” under “Quick Start”.
  3. Project name: hello-arty-1 and make sure “Create project subdirectory” is selected.
  4. Project Type: “RTL Project” and make sure “Do not specify sources at this time” is selected.
  5. Default Part: Select “Boards” then choose “Arty A7-35”.
    If it’s not on the list, you need to quit Vivado and install the board files.
  6. Review the “New Project Summary” and hit “Finish”.

If you’re using the original Arty (without the A7) you should select ‘Arty’ in step 5.

Hello SystemVerilog

  1. In the top left of the Vivado window, under “Project Manager”, select “Add Sources”.
  2. Choose “Add or create design sources” and click “Next”.
  3. Select “Create File” in the middle of the dialog.
  4. Make sure File type is set to “SystemVerilog” and name the file top.sv then click “Finish”.
  5. In the “Define Module” dialog hit “OK” then click “Yes” when prompted.

Replace the contents of the top module in top.sv with [src]:

module top (
    input wire logic sw,
    output     logic led
    );

    always_comb begin
        led = sw;
    end
endmodule

The first part of the module defines our inputs and outputs:

  • sw - one of the four slide switches
  • led - one of the green LEDs (labelled LD4 on the Arty board)

The second part of the module is where we write our logic. In this case, we wire up led to sw: when the switch is on the LED is on, when the switch is off the LED is off. Don’t worry about the meaning of always_comb for now; we’ll come back to that in part 2.

Save your file by hitting Ctrl-S or by using the save button at the top left of the editor window (yes, it still looks like a floppy disk).

ProTip: You can use Ctrl+/ to (un)comment the current line in the Vivado editor window.

Quick Aside: Direct Connection
You wouldn’t normally connect a switch directly to an LED with an FPGA like this. However, it’s good to be able to work with real hardware right away, so we’ve taken some liberties.
In part 2 we’ll store the LED state in flip-flops.

Constraints File

An FPGA is very flexible; you can connect many different types of inputs and outputs to the many hundreds of pins. Before synthesising our code, we need to tell Vivado how our Arty board is wired up. Vivado uses constraints to map an input or output, such as led, to the appropriate pin.

  1. In the top left of the Vivado window, select “Add Sources” under “Project Manager”.
  2. Choose “Add or create constraints” and click “Next”.
  3. Select “Create File” in the middle of the dialog.
  4. Make sure File type is set to “XDC” and name the file arty.xdc then click “Finish”.

Select arty.xdc in the editor window, add the following [src] content then save the file.

## FPGA Configuration I/O Options
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]

# I/O Pins
set_property -dict {PACKAGE_PIN A8  IOSTANDARD LVCMOS33} [get_ports {sw}];
set_property -dict {PACKAGE_PIN H5  IOSTANDARD LVCMOS33} [get_ports {led}];

ProTip: These constraints are written in Tcl, which is used throughout Vivado.

Design to Board

To bring our design to life on the Arty board, we need to go through four steps:

  1. Synthesis - converts the SystemVerilog into logic gates
  2. Implementation - optimises and lays out the logic design for the target FPGA
  3. Generate Bitstream - generates the FPGA bit file (gateware) from the implementation
  4. Program Device - loads the bit file onto the FPGA

Even on a fast PC, this whole process can take a few minutes, so be patient.

  1. Click the green play button at the top of the Vivado screen.
  2. Select “Run Synthesis”.
    If a “Launch Runs” dialog appears then just select “OK”.
    You’ll see a message “Running synth_design” at the top-right of Vivado.
  3. A dialog appears prompting you to “Run Implementation”.
    Click “OK” to start implementation.
  4. A dialog appears saying “Implementation successfully completed”.
    Select “Generate Bitstream” and click “OK”.
  5. A dialog appears saying “Bitstream Generation successfully completed”.
    Select “Open Hardware Manager” and click “OK”.

If you accidentally close any step you can find commands to run the different stages on the left-hand side of the Vivado window.

Hardware Manager

  1. Connect your Arty board to your PC using a micro USB cable.
  2. At the bottom left of Vivado select “Open Target” under “Open Hardware Manager”.
  3. Choose “Auto Connect” from the menu. If this fails, try again, it can be temperamental.
  4. Select “Program Device” under “Open Hardware Manager”.
  5. There should be one option “xc7a35t_0”; this is your Arty board.
  6. Browse to your bitstream file hello-arty-1/hello-arty-1.runs/impl_1/top.bit.
  7. Click “Program”.

If you’re on Linux and hardware manager never connects then you may have forgotten to install the cable drivers.

Hello LED

Slide the switch SW0 on and off on your board to control green LED LD4.

LED on!

This functionality isn’t anything you couldn’t trivially achieve with a microcontroller. However, it’s worth pausing to think about what you’ve done here. You’ve created a simple circuit board within the FPGA. There is no CPU running code here: it’s as if you’d wired up the switch to the LED on a breadboard.

Moar Switches, Moar LEDs!

You didn’t buy an FPGA to emulate a single wire on a breadboard; let’s extend our design so that one switch can control two LEDs. Granted you didn’t buy an FPGA board to do that either, but it does at least involve some logic!

If you’re still in Hardware Manager; you can get back to your Verilog source code by selecting “Project Manager” at the top left of the Vivado window.

Replace the existing top module in top.sv with the following [src]:

module top (
    input wire logic [1:0] sw,
    output     logic [3:0] led
    );
  
    always_comb begin
        if (sw[0]) begin
            led[1:0] = 2'b11;
        end else begin
            led[1:0] = 2'b00;
        end

        if (sw[1]) begin
            led[3:2] = 2'b11;
        end else begin
            led[3:2] = 2'b00;
        end
    end
endmodule

2'b defines a 2-bit binary literal: we have two LEDs each expecting a signal, so a two-bit value is appropriate here. You use d for decimal literals, and h for hexadecimal literals:

  • 2'b11 - 2 bits with value 112 (decimal 3)
  • 4'd5 - 4 bits with value 5
  • 8'h5E - 8 bits with value 5E16 (decimal 94)
  • 15'h7FF - 15 bits with value 7FF16 (decimal 2,047)

You can also see how we’ve referred to multiple LEDs: led[1:0] includes both led[0] and led[1].

We’re added a switch and three LEDs to our design, but we haven’t defined them in our constraints file; Vivado won’t know what to connect them to. Open up arty.xdc and replace it with [src]:

## FPGA Configuration I/O Options
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]

## Slide Switches
set_property -dict {PACKAGE_PIN A8  IOSTANDARD LVCMOS33} [get_ports {sw[0]}];
set_property -dict {PACKAGE_PIN C11 IOSTANDARD LVCMOS33} [get_ports {sw[1]}];

## LEDs
set_property -dict {PACKAGE_PIN H5  IOSTANDARD LVCMOS33} [get_ports {led[0]}];
set_property -dict {PACKAGE_PIN J5  IOSTANDARD LVCMOS33} [get_ports {led[1]}];
set_property -dict {PACKAGE_PIN T9  IOSTANDARD LVCMOS33} [get_ports {led[2]}];
set_property -dict {PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports {led[3]}];

Rerun synthesis, implementation, bitstream generation, and program the board as before. You should be able to control two LEDs with each of the first two slide switches.

Conditional Love

Those if blocks can get annoying fast, plus bad things happen if you don’t provide a value for all possible outcomes (latches: yuuurgh). Verilog has an answer for this with the every-handy conditional operator condition ? value_if_true : value_if_false;

The easiest way to understand it is to see it in action; replace top.sv with the following [src]:

module top (
    input wire logic [1:0] sw,
    output     logic [3:0] led
    );
  
    always_comb begin
        led[1:0] = sw[0] ? 2'b11 : 2'b00;
        led[3:2] = sw[1] ? 2'b11 : 2'b00;
    end
endmodule

Go through the usual rigmarole of synthesis, implementation, bitstream generation, and programming your board. It should work as before, but I think you’ll agree the conditional operator makes this design simpler to write and understand.

Smooth Operator

We can expand our conditional expressions with logical operators:

  • && - logical AND
  • || - logical OR
  • ! - logical NOT (or negation)

These work as you’d expect from software development; for example:
A && B is true if both A and B are true.

These logical operators are not to be confused with bitwise operators, such as a single &, which operate on the individual bits of a value. We’ll encounter bitwise operators in later tutorials.

For now let’s test two switches with the logical AND operator and add a push button [src]:

module top (
    input wire logic [3:0] sw,
    input wire logic [3:0] btn,
    output     logic [3:0] led
    );

    always_comb begin
        if (sw[0] == 0 && sw[1] == 1) begin
            led[3:0] = btn[0] ? 4'b1001 : 4'b0110;
        end else begin
            led[3:0] = 4'b0000;
        end
    end
endmodule

We don’t have push buttons in our constraints; let’s add them and the other slide switches [src]:

## FPGA Configuration I/O Options
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property CFGBVS VCCO [current_design]

## Slide Switches
set_property -dict {PACKAGE_PIN A8  IOSTANDARD LVCMOS33} [get_ports {sw[0]}];
set_property -dict {PACKAGE_PIN C11 IOSTANDARD LVCMOS33} [get_ports {sw[1]}];
set_property -dict {PACKAGE_PIN C10 IOSTANDARD LVCMOS33} [get_ports {sw[2]}];
set_property -dict {PACKAGE_PIN A10 IOSTANDARD LVCMOS33} [get_ports {sw[3]}];

## Buttons
set_property -dict {PACKAGE_PIN D9  IOSTANDARD LVCMOS33} [get_ports {btn[0]}];
set_property -dict {PACKAGE_PIN C9  IOSTANDARD LVCMOS33} [get_ports {btn[1]}];
set_property -dict {PACKAGE_PIN B9  IOSTANDARD LVCMOS33} [get_ports {btn[2]}];
set_property -dict {PACKAGE_PIN B8  IOSTANDARD LVCMOS33} [get_ports {btn[3]}];

## LEDs
set_property -dict {PACKAGE_PIN H5  IOSTANDARD LVCMOS33} [get_ports {led[0]}];
set_property -dict {PACKAGE_PIN J5  IOSTANDARD LVCMOS33} [get_ports {led[1]}];
set_property -dict {PACKAGE_PIN T9  IOSTANDARD LVCMOS33} [get_ports {led[2]}];
set_property -dict {PACKAGE_PIN T10 IOSTANDARD LVCMOS33} [get_ports {led[3]}];

Rerun synthesis, implementation, bitstream generation, and program your board as before.
SW0 and SW1 control whether the LEDs are enabled; BTN0 selects two different patterns of lights.

Explore

You should put your new skills to use! Create with your own designs using switches, buttons, and LEDs. Just remember that when assigning things they should be the same width; for example, three bits for three LEDs: led[3:1] = 3'b101;

Try answering the following questions:

  1. How would you test if either button 2 or 3 were pressed?
  2. What’s wrong with the literal 4'd20? How could you write it?
  3. How is a slide switch different from a push button?

What’s Next?

In the next part of this series, we move onto clocks, counting, and pulse width modulation (with RGB LEDs thrown in for good measure).

©2020 Will Green, Project F