Hello Arty - Part 1
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 with Python or JavaScript, 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 CPU from scratch. I hope you find this tutorial helpful and have fun with FPGAs. This series is also available for the Nexys Video.
Already completed part 1? Jump to part 2.
Share your thoughts with @WillFlux on Mastodon or Twitter. If you like what I do, sponsor me. 🙏
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.
For this Hello Arty series you need:
- Digilent Arty A7-35T
- Micro USB cable to program and power the Arty
- Xilinx Vivado 2019 or later: Download and Install Guide
- Digilent board files
NB. The original Arty (without the A7) is the same as the Arty A7-35T, so you can use that too.
Source
The SystemVerilog designs featured in this series are available from the projf-explore git repo under the open-source MIT licence: build on them to your heart’s content. The rest of the blog content is subject to standard copyright restrictions: don’t republish it without permission.
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
In this part, we’re interested in the four green LEDs (LD4-7
), four slide switches (SW0-3
), and four push 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
- Start Vivado.
- Select “Create Project” under “Quick Start”.
- Project name:
hello-arty-1
and make sure “Create project subdirectory” is selected. - Project Type: “RTL Project” and make sure “Do not specify sources at this time” is selected.
- 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. - 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
- In the top left of the Vivado window, under “Project Manager”, select “Add Sources”.
- Choose “Add or create design sources” and click “Next”.
- Select “Create File” in the middle of the dialog.
- Make sure File type is set to “SystemVerilog” and name the file
top.sv
then click “Finish”. - 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 (labelledSW0
on the PCB)led
- one of the green LEDs (labelledLD4
on the PCB)
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.
Direct Connection
You wouldn’t normally directly connect a switch to an LED 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 a flip-flop.
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.
- In the top left of the Vivado window, select “Add Sources” under “Project Manager”.
- Choose “Add or create constraints” and click “Next”.
- Select “Create File” in the middle of the dialog.
- 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:
- Synthesis - convert the SystemVerilog into logic gates
- Implementation - optimise and lay out the logic for the target FPGA
- Generate Bitstream - generate the FPGA bitstream file from the implementation
- Program Device - load the bitstream into the FPGA
Even on a fast PC, this whole process can take a few minutes, so be patient.
- Click the green play button at the top of the Vivado screen.
- 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. - A dialog appears prompting you to “Run Implementation”.
Click “OK” to start implementation. - A dialog appears saying “Implementation successfully completed”.
Select “Generate Bitstream” and click “OK”. - 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
- Connect your Arty board to your PC using a micro USB cable.
- At the bottom left of Vivado select “Open Target” under “Open Hardware Manager”.
- Choose “Auto Connect” from the menu. If this fails, try again, it can be temperamental.
- Select “Program Device” under “Open Hardware Manager”.
- There should be one option “xc7a35t_0”; this is your Arty board.
- Browse to your bitstream file
hello-arty-1/hello-arty-1.runs/impl_1/top.bit
. - 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
.
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 58'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:
- How would you test if either button 2 or 3 were pressed?
- What’s wrong with the literal
4'd20
? How could you write it? - How is a slide switch different from a push button?
What’s Next?
If you enjoyed this post, please sponsor me. Sponsors help me create more FPGA and RISC-V projects for everyone, and they get early access to blog posts and source code. 🙏
In part 2, we move onto clocks, counting, and pulse width modulation (with RGB LEDs thrown in).
If you’re looking for other great resources, try recommended FPGA sites.