Building a Project That Exercises the GPIOs, FIFOs, and DMA, on the Arty Z7-20

I wanted to create a project suitable for beginners that does more than your average hello world sample out there on the internet. The project I created that will be described on this page and will show you how to run an augmented hello world that includes:

  • A GPIO example that is wired up to an accumulator implemented using RTL (Verilog)
  • A FIFO loopback test
  • A DMA FIFO loopback test

Additionally, this example can be run in multiple ways, all from the command line or by using the Vitis User Interface:

If you are a beginner, I hope this saves you a lot of time.

Getting Started

1 – Download and Install:

2 – Connect your Arty Z7-20 to your machine via USB

3 – Clone the Git Repository

mkdir -p ~/work/artyz7
cd ~/work/artyz7
git clone git@github.com:FpgaJohn/arty_hw.git

Manual Recreation of Project

NOTE: You must set the Arty Z7-20 jumper to JTAG, otherwise the commands will not work out-of-the box:

1 – Open Vivado and Recreate Block Design

# Source Vivado Tools
 . /tools/Xilinx/Vivado/2024.1/settings64.sh

# Launch Vivado
vivado &

Find the TCL Console at the bottom of Vivado, and navigate to the arty_hw/vivado directory and source the arty_hw.tcl TCL script.

cd ./arty_hw/vivado
source ./arty_hw.tcl

Inspect the block design and you will see:

  • A Dual channel GPIO with all Outputs set and connected to a custom IP Block named “my_state”
  • A Dual channel GPIO with all Inputs set and connected to the output of the same custom IP Block named “my_state”
  • An AXI-Stream FIFO (4.3) configured to use AXI4-Lite as its interface and whose TXD is connected to its RXD port
  • An AXI Direct Memory Access (7.1) IP Block configured with all vanilla options, that is without scatter gather mode that does not allow unaligned writes
    • If you follow the S_AXIS_S2MM and the M_AXIS_MM2S ports you will see that they are both connected to the same AXI4-Stream Data FIFO (2.0).  This too is also configured to be 32-bits wide, see the TDATA width value which is set to 4 bytes.

2 – Synthesize, implement and generate the bitstream

In the bottom left of the Vivado window click on the “Generate Bitstream” and wait for it to complete.  You can always check the TCL Console to see it progress through its various stages.

When the bitstream has been generated you will see the following window, just click cancel.

3 – Export the XSA file

Now export the bitstream:

  • Clicking “File->Export->Export Hardware”.
  • Select “Include bitstream”
  • Update the file name to arty_hw, and the directory to be the parent directory ~/work/arty_z7/arty_hw/vivado (see screenshot below)
    • This will allow the Makefile to be able to find the xsa file in case you want to run the apps via the Makefile in the future.
  • vivado/arty_hw.xsa

4 – Start Vitis

You can exit from Vivado if you wish. Regardless, you will have to source the Vitis tools and then start Vitis in classic mode from inside the apps /vitis directory.  You will have to create the vitis directory and set that directory to be your workspace path.

# Source the Vitis Tools
. /tools/Xilinx/Vitis/2024.1/settings64.sh

# Create a workspace directory named vitis
cd ~/work/artyz7_arty_hw/apps
mkdir vitis

# Start Vitis 2024.1 in Classic Mode
vitis --classic &

5 – Create Platform Project

  • Click “File->New->Platform Project”
  • Name the project plat_bm
  • Browse to and select the XSA file from the previous step
  • No need to change anything else, click Finish
  • Right click on the project and select Build

6 – Create C Application with Board Support Project

Create a C Application:

  • Click File->New->New Application
  • Make sure plat_bm is selected as the Platform
  • Name the project c_hw
  • The defaults should be fine until you reach the Templates window
    • Select “Empty C Application” 
  • Click Finish

Now import the sources:

  • Right click on src and select “Import Sources”
  • Browse to the ~/work/artyz7/arty_hw/app/arty_hw_bm directory and select Open
  • Check the following files:
    • main.c
    • mmio.c
    • mmio.h
  • Right click on the c_hw application and select Build

7 – Run Bare-Metal Application in Debug Mode

Right click on the project “c_hw” and select “Debug As-> Debug Configurations…”

Click on the left most icon to add a new configuration under “Single Application Debug”

This should not be an issue for most users, but it is in my case since I have multiple FPGA boards plugged in.  Go to “Target Setup” And make sure that the PL Device and PS Device options are both pointing to the Digilent Arty Z7.

Hit Debug and monitor the console

Then connect the Vitis Serial Terminal to the appropriate port and view the output:

8 – Create FreeRTOS App

Now you are going to have to create another Application Project with a new Domain that supports FreeRTOS.

Click “File->New->Application Project”

Select your existing Platform project (plat_bm)

Name the project c_hw_rtos

Create a new domain and set the Operating System to “freertos10_xilinx”

Select FreeRTOS Hello World

Import the same 3 files as before:

  • main.c
  • mmio.c
  • mmio.h

9 – Run FreeRTOS App

Create a Debug Configuration like before and run the application

The output should be different, notice the “xTaskCreate” and “vTaskStartScheduler” function calls:

Automated – Linux Only

1 – Build the Bitstream and XSA

Now for the fun part.  With the help of Claude I wrote a bunch of Makefile rules/recipes that will automate everything done in the Manual mode.  Simple, just run make and check the help menu.

john@asus:~/work/artyz7/arty_hw$ make
arty_hw — Arty Z7-20 hardware test suite

Vivado:
  make xsa               Build Vivado project and export XSA (25-35 min)
  make xsa-clean         Remove Vivado project and build artifacts

Bare-metal (JTAG):
  make bare-metal-build  Build standalone ELF (ps7_cortexa9_0, via xsct)
  make bare-metal-run    Program PL + run ELF via JTAG, capture UART
  make bare-metal-clean  Remove bare-metal Vitis workspace

FreeRTOS (JTAG):
  make rtos-build        Build FreeRTOS ELF (ps7_cortexa9_0, via xsct)
  make rtos-run          Program PL + run ELF via JTAG, capture UART
  make rtos-clean        Remove FreeRTOS Vitis workspace

Linux (PetaLinux board via SSH):
  make deploy            Cross-compile and scp test app to board
  make deploy-run        Deploy + run test app on board via SSH

Setup:
  make fetch-board-parts Fetch Digilent board files into Vivado 2024.1 xhub

Utilities:
  make tty               Open Arty Z7 PS-UART in screen (115200 8N1)
  make tty-list          List screen sessions and processes holding any /dev/ttyUSB*
  make tty-kill          Kill any screen/process holding the Arty Z7 PS-UART
  make board-reset       System-reset the Arty Z7 PS+PL via JTAG (xsct rst -system)
  make help              Show this help

Variables:
  ARTY_HOST=arty          Board hostname/IP for SSH deploy
  ARTY_USER=petalinux      Board username
  ARTY_UART=/dev/ttyUSB4  PS-UART device (auto-detected)

You can tinker and see what boards are connected to your system. In my case I have the Arty Z7-20 and the Kria KR260:

john@asus:~/work/artyz7/arty_hw$ make list-all
==> USB serial ports:
  /dev/ttyUSB1   usb=3-2        if=01  Xilinx KR Carrier Card  serial=XFL1GRCZBY0R
  /dev/ttyUSB2   usb=3-2        if=02  Xilinx KR Carrier Card  serial=XFL1GRCZBY0R
  /dev/ttyUSB3   usb=3-2        if=03  Xilinx KR Carrier Card  serial=XFL1GRCZBY0R
  /dev/ttyUSB4   usb=3-3        if=01  Digilent Digilent Adept USB Device  serial=003017B7EAFD
==> JTAG scan chains (xsct):
  cable: Digilent Arty Z7 003017B7EAFDA
    device: arm_dap
    device: xc7z020
  cable: Xilinx SCK-KR XFL1GRCZBY0RA
    device: xck26
    device: arm_dap

And you can see which one is for your board. In this case we know it it /dev/ttyUSB4, but we can be more explicit by calling “make tty-list”

john@asus:~/work/artyz7/arty_hw$ make tty-list
==> screen sessions:
  (none)
==> /dev/ttyUSB* holders:
  (none)
==> Arty Z7 PS-UART (auto-detected): /dev/ttyUSB4

2 – Prerequisite – Build the XSA

Just run make xsa. It took 13 minutes on my ASUS ExpertCenter PN65

john@asus:~/work/artyz7/arty_hw$ time make xsa
make -C vivado xsa
. /tools/Xilinx/Vivado/2024.1/settings64.sh && JOBS=1 vivado -mode batch -nojournal -log build.log -source build.tcl

****** Vivado v2024.1 (64-bit)
  **** SW Build 5076996 on Wed May 22 18:36:09 MDT 2024
  **** IP Build 5075265 on Wed May 22 21:45:21 MDT 2024
  **** SharedData Build 5076995 on Wed May 22 18:29:18 MDT 2024
  **** Start of session at: Thu Jun  4 14:44:27 2026
    ** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
    ** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.
...
----
# puts "Wrote XSA: [file normalize arty_hw.xsa]"
Wrote XSA: /home/john/work/artyz7/arty_hw/vivado/arty_hw.xsa
INFO: [Common 17-206] Exiting Vivado at Thu Jun  4 14:59:46 2026...

real    13m11.710s
user    9m9.382s
sys     0m26.627s

3 – Build and Run the Bare-Metal Application

Now run “make bare-metal-build” and then “make bare-metal-run”, all output (for 10 seconds) will be captured and printed to the console

john@asus:~/work/artyz7/arty_hw$ make bare-metal-run
make -C apps/arty_hw_bm run
==> Configuring /dev/ttyUSB4 (115200 8N1)
==> Programming PL + downloading ELF via xsct load.tcl
attempting to launch hw_server

****** Xilinx hw_server v2024.1
  **** Build date : May 16 2024 at 12:16:21
    ** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
    ** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.

INFO: hw_server application started
INFO: Use Ctrl-C to exit hw_server application

INFO: To connect to this hw_server instance use url: TCP:127.0.0.1:3121

load.tcl: ELF running. UART output on ttyUSB at 115200 8N1.
==> ELF running -- capturing UART for 10s

==> Captured output (saved to bm.log):
------------------------------------------------
...
----- [mmio] AXI DMA echo (mem->MM2S->S2MM->mem) -----
  tx PA=0x00112880  rx PA=0x00113880  size=4096 B
  after reset:  MM2S SR=0x00000001  S2MM SR=0x00000001
  MM2S done after 37 polls, SR=0x00001002
  S2MM done after 0 polls, SR=0x00001002
  tx[0,1]=0xA0000000,0xA0000001  tx[1022,1023]=0xA00003FE,0xA00003FF
  rx[0,1]=0xA0000000,0xA0000001  rx[1022,1023]=0xA00003FE,0xA00003FF
  DMA echo: PASS (4096 bytes round-tripped)

=========================================
RESULT: ALL PASS -- 0 failures
=========================================
------------------------------------------------

4 – Build and Run the FreeRTOS Application

There are similar commands for the rtos version.  Run “make rtos-build” and “make rtos-run”:

john@asus:~/work/artyz7/arty_hw$ make rtos-run
make -C apps/arty_hw_rtos run
==> Configuring /dev/ttyUSB4 (115200 8N1)
==> Programming PL + downloading ELF via xsct load.tcl
attempting to launch hw_server

****** Xilinx hw_server v2024.1
  **** Build date : May 16 2024 at 12:16:21
    ** Copyright 1986-2022 Xilinx, Inc. All Rights Reserved.
    ** Copyright 2022-2024 Advanced Micro Devices, Inc. All Rights Reserved.

INFO: hw_server application started
INFO: Use Ctrl-C to exit hw_server application

INFO: To connect to this hw_server instance use url: TCP:127.0.0.1:3121
...
----- [mmio] AXI Stream FIFO loopback (PG080) -----
  post-reset: TX vacancy=508 words
  writing 4 words to TX FIFO...
    tx[0] = 0xDEADBEEF
    tx[1] = 0xCAFEBABE
    tx[2] = 0x12345678
    tx[3] = 0xAABBCCDD
  RX occupancy=4 words, RLR=16 bytes (4 words)
  received 4 words:
    rx[0] = 0xDEADBEEF OK
    rx[1] = 0xCAFEBABE OK
    rx[2] = 0x12345678 OK
    rx[3] = 0xAABBCCDD OK
  FIFO echo: PASS

----- [mmio] AXI DMA echo (mem->MM2S->S2MM->mem) -----
  tx PA=0x0011E880  rx PA=0x0011F880  size=4096 B
  MM2S done after 37 polls, SR=0x00001002
  S2MM done after 0 polls, SR=0x00001002
  tx[0,1]=0xA0000000,0xA0000001  tx[1022,1023]=0xA00003FE,0xA00003FF
  rx[0,1]=0xA0000000,0xA0000001  rx[1022,1023]=0xA00003FE,0xA00003FF
  DMA echo: PASS (4096 bytes round-tripped)

=========================================
RESULT: ALL PASS -- 0 failures
=========================================
------------------------------------------------

Miscellaneous

Enumerate all connected boards

I added a utility recipe / rule to list all connected FPGA boards. For this portion of this article, I connected 3 total boards:

  • Kria KR260
  • ArtyZ7-20
  • Arty A7-100T

 

john@asus:~/work/artyz7/arty_hw$ make list-all
==> USB serial ports:
  /dev/ttyUSB0   usb=3-4        if=00  Digilent Digilent USB Device  serial=210319AFEEC7
  /dev/ttyUSB1   usb=3-2        if=01  Xilinx KR Carrier Card  serial=XFL1GRCZBY0R
  /dev/ttyUSB2   usb=3-2        if=02  Xilinx KR Carrier Card  serial=XFL1GRCZBY0R
  /dev/ttyUSB3   usb=3-2        if=03  Xilinx KR Carrier Card  serial=XFL1GRCZBY0R
  /dev/ttyUSB4   usb=3-3        if=01  Digilent Digilent Adept USB Device  serial=003017B7EAFD
  /dev/ttyUSB5   usb=3-4        if=01  Digilent Digilent USB Device  serial=210319AFEEC7
==> JTAG scan chains (xsct):
  cable: Digilent Arty A7-100T 210319AFEEC7A
    device: xc7a100t
  cable: Digilent Arty Z7 003017B7EAFDA
    device: arm_dap
    device: xc7z020
  cable: Xilinx SCK-KR XFL1GRCZBY0RA
    device: xck26
    device: arm_dap

Reboot the Board

When playing around with Linux (Ubuntu) or PetaLinux, I sometimes want to reboot the boad without having to unplug it and plug it back in (or by pressing the reset button).

What I do is (using tmux) I run “make tty” to listen to the serial port interface from one window, and then from another I run “make board-reset” and I can watch the tty window reset.  In my case I switched by Dip switch back to SD card and can watch it boot up to Ubuntu:

 

 

Leave a Comment