SPI, I2C, UART on PYNQ: a PL approach
There are two methods for using devices on FPGA for PYNQ: the integrated microprocessor (PS side) and the programmable logic (PL side). In order to maximize the use of the FPGA, this article shows how you can use the programmable logic for common devices such as SPI, I2C and UART.
We have used this flow also for SMART-IO Project. If you have lost the article about SMART-IO, follow this link!
You can find every design in this article as Github project in the MakarenaLabs Github company.
Design with Vivado for PYNQ
In order to create your programmable logic system, you need to create a Vivado design that includes the target device. Vivado has specific IP for the devices, called LogiCore IP:
- for SPI you can choose AXI Quad SPI;
- also for I2C you can choose AXI IIC Bus Interface;
- then for UART you can choose AXI UART Lite.
Here below we will show you some examples of Vivado block design composition with SPI, I2C and UART design.
You may notice that all the design are similar: there is a connection between processing system and ps7 axi peripheral through a master AXI GP, that connect the block to the device IP block. Only SPI has an additional IP block that is the clocking wizard, that helps you to set the right SPI clock (downsampling or upsampling the processing system clock).
Connection to the physical pin
After the block design composition, you need to connect the block pin interface to the physical interface of your board. In order to do that, you need two things: the constraint file, that allows Vivado to “route” the blocks pin to the FPGA pins, and the schematics of your board.
For example, we want to connect the UART interface to PMODB pins, that we are sure that there is a connection between them and FPGA pins. The procedure is quite easy: we need to choose specific pins of PMODB that represent all the pins of a common UART (i.e. Rx and Tx), then we will obtain the relative pin name of the FPGA.
The schematics
Let’s start using as example of the PYNQ Z2 schematic. You can find the schematics in this link:
https://dpoauwgwqsy2x.cloudfront.net/Download/TUL_PYNQ_Schematic_R12.pdf
So, let’s check the schematics:
The pins 5 and 11 are Ground connection, while 6 and 12 are VCC 3.3V connection. Remember that you will need to connect the Ground pin to your external board, in order to share the ground for both PYNQ Z2 board and the other board. You also need to know that the VCC reference is 3.3V, so if the other device works with a different voltage, the communication will be unstable or impossible to understand!
The pins 1, 2, 3, 4, 7, 8, 9, 10 are free, so you can use it freely. So, take the pin 1 and 2 for Rx and Tx, you see that pins 1 and 2 have labels JB1_P and JB1_N, so these are the names of the link between PMODB and FPGA. Now, we need to find in the schematics where are JB1_P and JB1_N, in order to obtain the real name of the FPGA pins connected to these links.
Ok, fortunately you can use in the most of the schematics the CTRL+F command (find text), but always is a bit struggling!
So, let’s check the links for the JB1_P and JB1_N:
You see that there is a connection between JB1_P and W14 pin of the FPGA and between JB1_N and Y14 pin, so we did it! We will use them on Vivado in order to create the constraint file.
The constraints file
In the constraint file of Vivado you need to specify what are the connections between the IP block and the choosen physical pin of PFGA.
set_property -dict {PACKAGE_PIN W14 IOSTANDARD LVCMOS33} [get_ports uart_rtl_rxd]
set_property -dict {PACKAGE_PIN Y14 IOSTANDARD LVCMOS33} [get_ports uart_rtl_txd]
As we decided before, we connect the package pin (“real” FPGA pin) W14 an Y14 to the design pin of Rx and Tx, identified by uart_rtl_rxd and uart_rtl_txd. These names are the standard name given by Vivado, but if you need different names you can simply double-click on the pin port and you can set the new name in the menu.
After that, you can launch the bitstream generation and wait. Then, you can export the hardware design in order to obtain the XSA file that contains bitstream and hardware description (.bitstream and .hwh files). PYNQ framework will use these files in order to download the design on the FPGA.
You can use the same procedure for all the devices which are shown before.
The MMIO objects
Ok, now we have the bitstream for the FPGA, but how we can use it directly on PYNQ framework?
Due the fact that the devices are AXI slave for the processing system, when you download the bitstream on the FPGA, PYNQ recognizes the devices as MMIO object.
The MMIO class allows the user to access addresses of system memory, and you can use read() and write() methods directly to the device. On PYNQ you can use the MMIO in two way:
- explicit flavour, so you need to istantiate a new MMIO object assigning the base address and range addresses obtainable by Vivado address editor
- implicit flavour, so you get the specific device after the overlay istantiation
In particular, the explicit and implicit flow are shown below (with a GPIO device example):
#EXPLICIT
from pynq import MMIO
address = 0x41200000
XGPIO_DATA_OFFSET = 0x0
class GPIO:
def __init__(self, address):
# Setup axi core
self.gpio = MMIO(address, 0x10000, debug=False)
self.address = address
def write(self, signal):
# Send signal via GPIO using defined offset
self.gpio.write(XGPIO_DATA_OFFSET, signal)
gpio = GPIO(address)
gpio.write(0)
#IMPLICIT
from pynq import Overlay
ol = Overlay("pynqz2_gpio.bit")
gpio = ol.axi_gpio_0
gpio.write(0)
There isn’t a “right way” to choose the explicit or implicit flavour, they are both valid.
So, using the MMIO you can write your own device driver for the specific application. This isn’t a pedantic article, so we have prepared all the code in our github repository 😉
You will find here SPI on PL with PYNQ code, also I2C on PL with PYNQ code, then UART on PL with PYNQ code and last but not least GPIO on PL with PYNQ code. Lots of stuffs for new adventurers 😉
[…] If you need to go into details about the hardware design, please read this article […]
[…] create pinout interfaces with the right constraints according to the PMOD pinout, see this article if you need to know how) […]
[…] Peripheral tutorial article […]
[…] For all the details on how to get to this design and how to manage the various constraints, we recommend the following article: spi-i2c-uart-on-pynq-a-pl-approach. […]
Hi, after building the bitstream, how to deploy the kernel onto the target board?
Hi! You need to use this command:
ol = Overlay(“your bitstream name”)
You can use the full code described here: https://github.com/MakarenaLabs/Common-PL-Devices-on-PYNQ
[…] don’t know what we’re talking about and want more details? click_here to see our article on how to create a UART communication on […]
[…] LINK TO THE ARTICLE : https://www.makarenalabs.com/spi-i2c-uart-on-pynq-a-pl-approach/ […]
[…] you need to know more precisely how to control PYNQ, GPIO, and Microblaze, take a look at the GPIO article and Microblaze […]
hi! i’m a new user of pynq z2. i have pulled a tissue in the github page which you mentioned above. Beg you will see this.