In Part 1 of Nishant’s article series examining the Ultra96-V2—the board based on the 96Boards open specification—he discussed board form factor, design and other aspects of the hardware. In Part 2, he discusses various ways this board can be used to convert ideas into reality, such as how to put the Linux OS into action on the Ultra96 board.
In Part 1, I discussed various hardware aspects of the Ultra96-V2. Here, we will discuss putting the board to use and running software on it. At the heart of the Ultra96 board is the Xilinx ZynqMP FPGA (Part number: XCZU3EG-1SBVA484I). This FPGA has the advantage that it’s packaged with a processor and programmable logic within a few centimeters of each other on its die. This not only reduces the board area and form factor, but also makes the life easy for developers. That’s because developers need not understand the architecture of multiple devices in order to develop a single application.
In the past, in the 90s for example, such boards would contain an x86 processor along with an FPGA for controlling the board’s peripherals. By blending the processor and FPGA logic, the integrated ZynqMP is capable of handling complex applications all on its own, but also of debugging itself—all of which ease the development process for engineers.
The designations PS and PL on the ZynqMP are shorthands that refer the Processing Subsystem (PS) (the Arm Cortex cores) and the Programmable Logic (PL) (FPGA) partitions of the device.
BOOTABLE PETALINUX IMAGE
The Ultra96 board comes with a preloaded SD card, which contains the Linux operating system image along with applications that can be played with or used to create your own applications. Let’s first look at the environment. The Linux image that comes with Ultra96 is the PetaLinux-generated image. PetaLinux a proprietary Xilinx version of Linux and comes bundled with all the necessary protocols and applications. PetaLinux image generation requires that your hardware is designed using Xilinx’s Vivado Design Suite. This Vivado file contains DDR connections and settings, on-board interfaces, communication interfaces and I/O constraints.
Let’s take an example of a sample design that we can create and then make a PetaLinux image for it. Figure 1 shows a bootable design based on the Zynq MP Ultrascale Plus. Note that you may have to change the FPGA device to the device part number I mentioned in the introduction of the article. The steps to create a Vivado project will not be covered in this article. You can visit xilinx.com  or many YouTube videos, which can guide you through the steps. In Figure 1, you can see that the hardware design has an I2C bus and EMIO GPIO (programmable logic GPIO). The device uses its built-in clock and reset. Using the AXI interconnect, the device is connected to I2C—this is a hardware I2C bus, not soft I2C. Because I2C is AXI-compatible IP, it needs an interconnect to connect to the core. Note that an interrupt from I2C is connected and fed back to the code so that the core can monitor the activity.
Next, by double clicking on Zynq Ultrascale, you can set various device settings, such as the expected output clock at which the device is to be run, the expected PS peripherals and their voltage settings and so forth. Note that Xilinx Vivado always provides a board “pre-set” that runs when you select a particular board. However, users can tweak them as needed for their requirements.
Among the most important parts of the board pre-set is the DDR DRAM settings—the DDR controller is inside the Zynq MP FPGA. These settings have to be made carefully, otherwise Linux could crash, panic or not boot at all.
Once the design is ready, click on Generate Bit Stream. This will create a hardware BSP (board support package) for PetaLinux. Using this BSP as a source, a PetaLinux image can be created. To see details of how to create a PetaLinux image, refer to the UG1144 document at  (link available in RESOURCES at the end of the is article).
A PetaLinux image consists of a .dtb file which connects device settings, board settings and Linux drivers, and maps them with each other. Vivado doesn’t contain board related information such as the kind of I2C devices or the kind of clock devices that are present on board. This mapping between the I2C bus, the device drivers and board is done using the .dtb file. Board related device tree can be written on the .dtsi file that is present inside <path>/Linux/images/.
Note that this work typically won’t need to be done at all. That’s because the SD card provided with the board already has the pre-created image. That said, you may want to do some experiments, such as creating your own design and then replacing the provided image.
IMAGE USABILITY WITH ULTRA96
The Ultra96 board has a built in Wi-Fi system that allows you to access the board remotely. The board can be used along with 96board Groove sensor kits. To connect to the board, connect to the board’s Wi-Fi name from your laptop. Once connected, in the Chrome browser type: 192.168.2.1
This will open up the Jupyter notebook, which bundles lot of “getting started” guides. Figure 2 shows the out-of-the-box Jupyter notebook that you get. Note that this environment of Jupyter notebook allows users to try out their own Python code and run it on the board for various applications.
Now let’s look at some examples that interface the external world with the Ultra96 board using some 96boards Grove sensor kits. Using the following calls, users can import all the 96board libraries. Let us try out a simple LCD example provided with the Jupyter notebook:
from PYNQ.overlays.sensors96b import Sensors96bOverlay
from PYNQ.overlays.sensors96b import Sensors96bOverlay
overlay = Sensors96bOverlay('sensors96b.bit')
Next, we import the controller driver and initialize it with the RGB controller address 0x62 and LCD cursor address 0x3E. Then we send the characters to be displayed on the LCD as follows:
from upm import pyupm_jhd1313m1 as lcd
myLcd = lcd.Jhd1313m1(0, 0x3E, 0x62)
_ = myLcd.setCursor(0,0)
_ = myLcd.write('Circuit Cellar')
_ = myLcd.setCursor(1,2)
_ = myLcd.write('Nishant')
_ = myLcd.setColor(53, 39, 249)
We will now look at an I/O example with a switch and an LED. This will give you an idea about handling I/Os in Jupyter notebook with the Ultra96 board. The code is shown in Listing 1. In this example, we first connect the GPIO with the respective I/Os provided on the Grove board. As you can see from Listing 1, button is connected to GPIO 27 and led is connected to GPIO 33. Figure 3 shows the picture of what this example looks like.
LISTING 1 – Shown here is the code for an Ultra96 board I/O example with a switch and an LED.
from pynq.overlays.sensors96b import Sensors96bOverlay
from upm import pyupm_grove as grove
overlay = Sensors96bOverlay('sensors96b.bit')
button = grove.GroveButton(27)
led = grove.GroveLed(33)
for i in range(100):
if button.value() == 1:
print("Button is pressed!")
print("Button is not pressed!")
Similarly, other I/Os for I2C and DP (DisplayPort) are provided, and examples from those are provided in the Jupyter notebook. The image not only contains a PYNQ BSP but also a PetaLinux environment which can be connected to the Jupyter notebook as shown in Figure 4. Run the complete script and the device is booted with the PetaLinux image. To use the PetaLinux image, connect the board to a display interface or use the USB UART to work from the UART terminal.
PROGRAMMABLE LOGIC DESIGNS
We can use Ultra96 board to design our own hardware. Users will need to have the Vivado tool suite installed on their PC. In Vivado, you create a design by selecting Ultra96 board. The Ultra96 board pre-set is not available in Vivado by default. You’ll need to download the pre-set. Figure 5 shows the GUI to select the board pre-set. After creating a project, you need to import your Verilog/system Verilog/VHDL file to the project. Next, you must connect the Zynq Ultrascale Plus with the PL.
Figure 6 shows a sample example of how a design can be created with both PS and PL integrated. The Zynq Ultrascale drop down box contains all the PS settings required, including the DDR controller settings. The RTL that was created can now be dragged down to the GUI to create a block diagram. You can either connect its clock to external clock, or bind it to the internally generated PL clock. The same applies to the reset. For larger designs, it’s recommended that you have a processor reset connected. This prevents any kind of timing failure.
Now that the design is ready, and you have compiled and generated the bitstream, it needs to be converted to a bootable format. If the design implementation to be run is only on the PL part of the ZynqMP, we can enable a Create Bin file from the properties of Create Bitstream. If the design’s logic has to be run on the PS part of the ZynqMP, we need to export the design to Vitis—Xilinx’s unified software platform—and generate an FSBL (first stage bootloader). Next, we need to convert it to .bin file. This bin file will have all the required bootable files for ZynqMP for the particular project. Now that the .bin file is generated, convert the file name to BOOT.BIN and load it onto the SD card. Make sure the boot switch is in SD mode and not on any other mode. Connect the board to UART and you are ready to observe the results.
In this article we discussed various ways that we can use the Ultra96 board and its Xilinx FPGA. We learned ways to boot the FPGA core using the PetaLinux way and the PYNQ way that comes with the board in the form of SD card. We examined the capability of the Ultra96 ecosystem to run our own RTL logic design on Ultra96 and also how to run them on the PS of the FPGA. This article is meant to help you understand Ultra96 and get started with it. A deeper dive into each and every element of the Ultra96 is beyond the scope of the article. Check out the links to user’s guides and more in RESOURCES below.
 Xilinx website: www.Xilinx.com
 Users guide for Petalinux:
Users Guide for Ultra96: http://zedboard.org/product/ultra96-v2-development-board
Users Guide for Pynq: http://www.pynq.io/
Users Guide for ZynqMP Ultrascale Plus:
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JANUARY 2021 #366 – Get a PDF of the issue