Using a new hardware design with PYNQ
The tutorial will show you how to use the Vivado hardware design created in the previous tutorial with PYNQ. This tutorial follows on from a previous tutorial which showed (how to create a new hardware design for PYNQ)[Tutorial: Creating a hardware design for PYNQ]. This tutorial is based on the v2.4 PYNQ image and Vivado 2018.2. A Pynq-Z2 board was used to run the code in this tutorial.
If you would like to try this tutorial on your board, you can download the notebook version of this page: Jupyter notebook: Using a new hardware design with PYNQ (12.7 KB)
The design files can be downloaded here:
pynq_tutorial_files_v2_4.zip (221.1 KB)
In the previous tutorial, a simple Vivado design was created with a BRAM, and 3 AXI GPIO controllers. This tutorial will show how to load the overlay, and will focus on using the AXI GPIO controllers. Later tutorials will show how to use other parts of the dsign and the PYNQ framework.
The bitstream (.bit), Tcl file (.tcl) and the Hardware Hand-off file (.hwh) were created and will be used with the PYNQ framework.
The bitstream is used to program the Zynq Programmable Logic (PL) with your custom hardware design.
The Tcl file is used to rebuild the Vivado block diagram. The Hwh is usually exported to the Xilinx SDK tools for building software applications for your system. Both files contain information about the system including clocks, and settings, IP and the system memory map. These files can be parsed in PYNQ and the information used to help use the design from Python.
If the Hwh file is included, the Tcl files is not needed by PYNQ, but both files are included in this example for completeness - the Tcl could be used to rebuild the Vivado block diagram.
A complete PYNQ design (an Overlay) would usually consist of a bitstream, the Tcl, and Hwh, but also a Python API or wrapper to use the design and an installation file. There may also be software libraries, Jupyter notebooks, source code (that would be compiled during installation or later), documentation, and other optional support files.
The aim of this tutorial is to try and help the reader understand more about the PYNQ framework, and how to start using and developing the Python layer for a custom hardware design. For now, we are starting with the bitstream and Tcl/Hwh, and will develop the Python. Later, once the design is complete, the design files can be built into an installable PYNQ overlay package.
Copy the design files to the board
The Python for the hardware design will be developed in Jupyter notebook. The PYNQ image is based on Ubuntu. The files need to be copied to the board in an area in the file system accessible from Jupyter.
The files can be copied to the board in a number of different ways. As the board has a Ubuntu based OS, we could use ftp, ssh, etc. Files can also be uploaded directly through the Jupyter interfaces. The OS is also running Samba by default. See the PYNQ documentation on how to transfer files using Samba
Once you connect to the board with Samba, you should be able to see the ~/xilinx home area. The jupyter_notebooks folder is visible from Python, so any files you want to use from Jupyter should be copied here.
- Create a new folder pynq_tutorial in the ./xilinx/jupyter_notebooks folder, and copy the .bit, .tcl, and .hwh from the previous tutorial to the folder
- Open a web browser and connect to the board.
In the Jupyter home area, you should see the folder you created and copied the design files to.
- Browse to the folder, and create a new Jupyter notebook.
- Rename the notebook from untitled to something meaningful
PYNQ Overlay class
The first step is to instantiate the Overlay using the pynq
Overaly class. PYNQ Overlay class documentation. The Overlay class has the following prototype:
Overlay(bitfile_name, download=True, ignore_version=False)
bitfile_name is the full filename of the FPGA bitstream file.
By default, the bitstream will be downloaded to the Programmable Logic when the Overlay is instantiated. Set download to false to instantiate the Overlay without downloading the bitstream.
The Overlay class will automatically assign a driver (where available) for known IP. Each PYNQ image is verified with a version of Vivado. Each Vivado version may have different versions of IP in the IP catalog. PYNQ assumes that Vivado designs match the version of Vivado verified with the PYNQ image. Other versions of Vivado may be used to create designs, but they are not supported, and it is not guaranteed that they will work.
The version of any IP in the design should be the same as the version the driver was written for. The Overlay class can check IP versions before assigning a driver, or the version check can be skipped. The ignore_version can be set to True to force the Overlay class to check IP versions before assigning drivers.
Where no PYNQ driver is available, the Overlay class will assign an IP a default driver (DefaultIP) which provides basic MMIO read and write functions for that IP’s address space. This can be useful for simple IP where a full PYNQ driver is not required, or for prototyping new IP.
Instantiate the Overlay
Instantiate the design created in the previous tutorial. Only the bitstream is specified, but the Overlay class will also read the corresponding .hwh, or .tcl file. If neither file is provided, the Overlay class will give an error.
from pynq import Overlay tutorial = Overlay("pynq_tutorial.bit")
Check list of IP in the design
Before using the design, you need to know what IP are available. You could determine this based on the Vivado design you created. You would need to remember the exact IP name and hierarchy. For a simple design this is straightforward, but for a larger design, this gets more complicated.
The Overlay can be queried to determine the available IP.
Notice that the buttons, switches, and leds, which used AXI GPIO IP in Vivado, have been assigned the pynq.lib.axigpio.AxiGPIO driver.
The BRAM was assigned the pynq.Overlay.DefaultIP driver. This is because the DefaultIP provides MMIO access to an IP. IN the case of BRAM, a memory, only MMIO is needed to read and write memory locations.
Overlay IP information can also be read from the ‘ip_dict’. This will print a complete list of IP in the design, and various properties. This information is derived from the Hardware Hand-off file (.hwh) provided with the Overlay.
For now, we only need to know the IP names that are in our design. If using the ip_dict instead of the ? help, you could find the names by listing the keys for the ip_dict:
Setup handles to the IP
Once you know the IP names, and hierarchical paths, you can set up more convenient handles.
bram = tutorial.bram buttons = tutorial.buttons switches = tutorial.switches leds = tutorial.leds
Read from the buttons and switches
An AXI GPIO IP can be connected to input or output pins, and supports up to two channels of up to 32 bit. The AxiGPIO driver has read() and write() functions for basic MMIO functionality. Each of the two channels can also be read or written. In the previous design, only one set of pins was connected to each AXI GPIO. i.e. only one channel is being used.
For now, we will do simple reads whcih will default to the first channel.
The following code will read a value from the pushbuttons on the board and print the result. The cell below can be rerun multiple times with different buttons pressed to see a different result.
buttons_value = buttons.read() print("Buttons value is: " + str(buttons_value))
The two dipswitches can be used in a similar way.
switches_value = switches.read() print("Switches value is: " + str(switches_value))
Using the LEDs
The switches and buttons are inputs. As mentioned previously, the AXI GPIO can be used to connect to inputs or outputs. The direction needs to be configured before reading or writing a bi-directional pin. In the case of the buttons and switches, which are inputs, the AXI GPIO by default was configured to enable inputs. Before using the LEDs, the AXI GPIO needs to be configured to enabled the FPGA pins as outputs. This can be done by writing the value 0x0 to the LED or AXI GPIO register at offset 0x4. A “0” enables an individual pin as an input, and a “1” enables it as an output. Writing 0x0 enables all 4 LED pins as outputs, and writing 0xf (binary 1111) would enable the 4 pins as inputs.
Configure the LEDs as outputs now by writing 0xf to register at offset 0x4.
outputMask = 0x0 tristateRegisterOffset = 0x4 leds.write(tristateRegisterOffset, outputMask)
Write a value to the LEDs
Once the pins have been enabled, a value can be write to the AXI GPIO controller. For channel 1, register at offset 0 is connected to the LEDs.
dataRegisterOffset = 0x0 led_pattern = 0xa leds.write(dataRegisterOffset, led_pattern)
Puting it together
The following code will write the value of the pushbuttons to the LEDs, while the switches are “off”. Before running the cell, make sure the dipswitches are in the “off” position.
Pressing a button should cause the corresponding LED to turn on.
while(not switches.read()): leds.write(dataRegisterOffset, buttons.read())
The BRAM and more advanced ways to use the AXI GPIO will be covered in another tutorial.
PYNQ documentation can be found on the PYNQ ReadTheDocs.
Documentation for all modules and functions in the PYNQ package section