0% found this document useful (0 votes)
121 views

Basic Embedded System Design Tutorial-2022.2

This document provides a tutorial on designing a two frequency PWM modulator system using a Zynq-7000 AP SoC embedded processor. It describes creating hardware and software platforms in Vivado and Vitis, developing a device driver for a custom IP core, and debugging the design. The goal is to generate a PWM signal modulated by a sine wave at one of two frequencies depending on the state of an onboard switch. Key steps include creating a processor system in Vivado IP integrator, building an application in Vitis, and interfacing with custom IP cores for the modulator design.

Uploaded by

Salim Hajji
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
121 views

Basic Embedded System Design Tutorial-2022.2

This document provides a tutorial on designing a two frequency PWM modulator system using a Zynq-7000 AP SoC embedded processor. It describes creating hardware and software platforms in Vivado and Vitis, developing a device driver for a custom IP core, and debugging the design. The goal is to generate a PWM signal modulated by a sine wave at one of two frequencies depending on the state of an onboard switch. Key steps include creating a processor system in Vivado IP integrator, building an application in Vitis, and interfacing with custom IP cores for the modulator design.

Uploaded by

Salim Hajji
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 143

Basic Embedded System Design Tutorial

using Zynq-7000 AP SOC embedded processor to design two frequencies PWM

modulator system

www.so-logic.net 2023/01/10 1
2 2023/01/10 www.so-logic.net
Contents
1 INTRODUCTION 5
1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Purpose of this Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Objectives of this Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 One Possible Solution for the Modulator Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4.1 Block diagram . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.4.2 Design steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.5 Embedded Design Process Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

2 CREATING THE HARDWARE PLATFORM 17


2.1 Create a New Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2 Vivado Integrated Design Environment . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.3 Create ARM-based Hardware Platform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
2.3.1 Create ARM-based Hardware Platform for Sozius Development Board . . . . . . . . . . . 25

3 CREATING THE SOFTWARE PLATFORM USING VITIS 37


3.1 Create a Platform Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.2 Board Support Package . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.3 Create a Application Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3.4 Creating a C/C++ Source Files for Sozius Board Based Hardware Platform . . . . . . . . . . . . 51
3.5 Viewing and Conguring Linker Script le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
3.6 Building Application and Generating ELF File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
3.7 Running Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3.7.1 Downloading bitstream le . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
3.7.2 Running Application Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
3.8 Application Debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
3.8.1 Debug Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
3.8.2 Debug Conguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
3.8.3 Debug Perspective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

4 USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN 93


4.1 Using Custom IP in ARM-based Processor System . . . . . . . . . . . . . . . . . . . . . . . . . . 95
4.2 Debug a Design using Integrated Vivado Logic Analyzer . . . . . . . . . . . . . . . . . . . . . . . 117
4.3 Developing a Device Driver for the Custom IP Core . . . . . . . . . . . . . . . . . . . . . . . . . 125
4.3.1 Device driver for PWM Modulator IP core . . . . . . . . . . . . . . . . . . . . . . . . . . 125
4.4 Creating Xilinx driver le and folder structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

5 CONCLUSION 139

6 EXERCISES 143

3
CONTENTS

4 2023/01/10 www.so-logic.net
Chapter 1

INTRODUCTION
1.1 Motivation
"Basic Embedded System Design Tutorial" is a document made for beginners who are entering the embedded
system design world using FPGAs. This tutorial explains, step by step, the procedure of designing a simple
digital system using C language, Xilinx Vivado Design Suite and Sozius development board.

1.2 Purpose of this Tutorial


Introduction

This tutorial is made to introduce you how to create and test an project and run it on your development
board.

The following project is designed for:

ˆ Designing Surface: VIVADO 2022.2

ˆ Programming Language: C

ˆ Device: Sozius Development Board

After completing this tutorial, you will be able to:

ˆ Launch and navigate the Vivado Integrated Design Environment (IDE)

ˆ Create a Zynq-7000 AP SoC processor system project using Vivado IP Integrator tool and Tcl program-
ming interface

ˆ Synthesize and implement the design in the Vivado IDE

ˆ Generate the hardware implementation bitstream le and download it to the target development board

ˆ Export a hardware description XML le for later software development

ˆ Create and debug your software application using Vitis tool

ˆ Debug a design in hardware using Vivado Logic Analyzer

ˆ Use custom IPs in your embedded system design

5
CHAPTER 1. INTRODUCTION

1.3 Objectives of this Tutorial


Objectives of this Tutorial

In this tutorial a PWM signal modulated using the sine wave with two dierent frequencies (1 Hz and 3.5
Hz) will be created.

Frequency that will be chosen depends on the position of the two-state on-board switch.

PWM Signal

Pulse-width modulation (PWM) uses a rectangular pulse wave whose pulse width is modulated by some other signal (in
our case we will use a sine wave) resulting in the variation of the average value of the waveform. Typically, PWM signals
are used to either convey information over a communications channel or control the amount of power sent to a load. To
learn more about PWM signals, please visit http://en.wikipedia.org/wiki/Pulse-width_modulation.

Figure 1.1: Example of the PWM signal

Figure 1.1. illustrates the principle of pulse-width modulation. In this picture an arbitrary signal is used to
modulate the PWM signal, but in our case sine wave signal will be used.

1.4 One Possible Solution for the Modulator Design


One Possible Solution

Considering that we are working with digital systems and signals, our task will be to generate an digital
representation of an analog (sine) signal with two frequencies: 1 Hz and 3.5 Hz.

6 2023/01/10 www.so-logic.net
CHAPTER 1. INTRODUCTION

Figure 1.2: Sine wave with 256 samples

Figure 1.2 is showing the sine wave that will be used to modulate the PWM signal.

8
One period of the sine wave is represented with 256 (2 ) samples, where each sample can take one of 4096 (2
12 )
possible values. Since the sine wave is a periodic signal, we only need to store samples of one period of the
signal.

Note : Pay attention that all of sine signals with the same amplitude, regardless their frequency, look the same
during the one period of a signal. The only thing that is dierent between those sine signals is duration of a
signal period. This means that the sample rate of those signals is dierent.

Now, it is obvious that the sine wave can be generated by reading sample values of one period, that are stored
in one table, with appropriate speed. In our case the values will be generated using the sine function from the
C numerics library (math.h) and will be stored in an array.

1.4.1 Block diagram

Block diagram on the Figure 1.3 shows the structure of one possible system that can be used to generate required
PWM signals.

www.so-logic.net 2023/01/10 7
CHAPTER 1. INTRODUCTION

Block diagram

Figure 1.3: Structure of microprocessor-based embedded system that will be used in tutorial

The embedded system is composed of:

ˆ ARM Cortex-A9 MPCore CPU core

ˆ AXI Timer core

ˆ AXI Interrupt Controller core

ˆ AXI GPIO core to drive the LED and to read the status of the SWITCH

Let us briey explain each part of this system:

Zynq-7000 AP Soc Processor - The Zynq-7000 family is based on the Xilinx All Programmable SoC (AP
SoC) architecture. The Zynq-7000 AP SoC is composed of two major functional blocks: Processing System
(PS) and Programmable Logic (PL), see Figure 1.4. The hart of the Processing System block is dual-
core ARM Cortex-A9 MPCore CPU. Beside ARM processor, PS also includes Application Processor Unit
(APU), Memory Interface, I/O Peripherals (IOP) and Interconnect.

8 2023/01/10 www.so-logic.net
CHAPTER 1. INTRODUCTION

Figure 1.4: Zynq-7000 AP SoC block diagram

Processing System (PS):

Application Processor Unit (APU) - provides an extensive oering of high-performance features and
standards-compliant capabilities. APU contains:

Dual ARM Cortex-A9 MPCore CPUs with ARM v7:

ˆ Run time options allows single processor, asymmetrical (AMP) or symmetrical multiprocessing (SMP)
congurations

ˆ ARM version 7 ISA: standard ARM instruction set and Thumb-2, Jazelle RCT and Jazelle DBX Java
acceleration

ˆ NEON 128 SIMD coprocessor and VFPv3 per MPCore

ˆ 32 KB instruction and data L1 caches with parity per MPCore

ˆ 512 KB of shareable L2 cache with parity

ˆ Private timers and watchdog timers

Memory Controller - the memory interfaces includes multiple memory technologies:

ˆ DDR Controller

ˆ DDR Controller Core and Transaction Scheduler

ˆ Quad-SPI Controller

ˆ Static Memory Controller (SMC)

I/O Peripherals - the I/O Peripherals (IOP) are a collection of industry-standard interfaces for external data
communication:

ˆ GPIO

ˆ Gigabit Ethernet Controllers (two)

www.so-logic.net 2023/01/10 9
CHAPTER 1. INTRODUCTION

ˆ USB Controller: Each as Host, Device or OTG (two)

ˆ SD/SDIO Controllers (two)

ˆ SPI Controllers (two): Master or Slave

ˆ CAN Controllers (two)

ˆ UART Controllers (two)

ˆ I2C Controllers (two)

ˆ PS MIO I/Os

UART Controller - is a full-duplex asynchronous receiver and transmitter that supports a wide range of
programmable baud rates and I/O signal formats. The controller can accommodate automatic parity generation
and multi-master detection mode.

The UART operations are controlled by the conguration and mode registers. The state of the FIFOs, modem
signals and other controller functions are read using status, interrupt status and modem status registers.

The controller is structured with separate Rx and Tx data paths. Each path includes a 64-byte FIFO. The
controller serializes and de-serializes data in the Tx and Rx FIFOs and includes a mode switch to support
various loopback congurations for the RxD and TxD signals. Software reads and writes data bytes using Rx
and Tx data port registers.

Each UART controller (UART 0 and UART 1) has the following features:

ˆ Programmable baud rate generator

ˆ 64-byte receive and transmit FIFOs

ˆ Programmable protocol

ˆ Parity, framing and overrun error detection

ˆ Line-break generation

ˆ Interrupts generation

ˆ RxD and TxD modes: Normal/echo and diagnostic loopbacks using the mode switch

ˆ Loop UART 0 and UART 1 option

ˆ Modem control signals

The block diagram of the UART module is shown on the Figure 1.5.

Figure 1.5: UART block diagram

10 2023/01/10 www.so-logic.net
CHAPTER 1. INTRODUCTION

Note : The UART Controller will be used in the Sub-chapter 3.3 Creating a C/C++ source les for
socius ARM-based processor system to transmit debug and system status information during application
execution to the attached PC.

If you want to read and learn more about UART Controller, please refer to Chapter 19 UART Controller in
the Zynq-7000 All Programmable SoC  Technical Reference Manual .

Programmable Logic (PL):

The PL provides a rich architecture of user-congurable capabilities:

ˆ Congurable Logic Blocks (CLB)

ˆ 36 Kb Block RAM

ˆ Digital Signal Processing - DSP48E1 Slice

ˆ Clock Management

ˆ Congurable I/Os

ˆ Low-Power Gigabit Transceivers

ˆ Analog to Digital Converter (XADC)

ˆ Integrated Interface Blocks for PCI Express designs

Note : If you want to read and learn more about the Zynq-7000 AP Soc processor core, please refer to "Zynq-7000
All Programmable SoC - Technical Reference Manual".

LogiCORE IP AXI Timer/Counter - The LogiCORE IP AXI Timer/Counter is a 32/64-bit timer module
that interfaces to the AXI4-Lite interface. The AXI Timer is organized as two identical timer modules. Each
timer module has an associated load register that is used to hold either the initial value for the counter for event
generation or a capture value, depending on the mode of the timer.

The AXI Timer includes the following features:

ˆ AXI interface based on the AXI4-Lite specication

ˆ Two programmable interval timers with interrupt, event generation, and event capture capabilities

ˆ Congurable counter width

ˆ One Pulse Width Modulation (PWM) output

ˆ Cascaded operation of timers in generate and capture modes

ˆ Freeze input for halting counters during software debug

Figure 1.6: AXI Timer core block diagram

www.so-logic.net 2023/01/10 11
CHAPTER 1. INTRODUCTION

Note : If you want to read and learn more about the AXI Timer/Counter core, please refer to "LogiCORE IP
AXI Timer v2.0 Product Guide".

LogiCORE IP AXI Interrupt Controller (INTC) - The LogiCORE IP AXI Interrupt Controller (INTC)
core receives multiple interrupt inputs from peripheral devices and merges them to a single interrupt output
to the system processor. The registers used for storing interrupt vector addresses, checking, enabling and
acknowledging interrupts are accessed through the AXI4- Lite interface.

The AXI Interrupt Controller includes the following features:

ˆ Register access through the AXI4-Lite interface

ˆ Fast Interrupt mode

ˆ Supports up to 32 interrupts. Cascadable to provide additional interrupt inputs

ˆ Single interrupt output

ˆ Priority between interrupt requests is determined by vector position. The least signicant bit (LSB, in
the case bit 0) has the highest priority

ˆ Interrupt Enable Register for selectively enabling individual interrupt inputs

ˆ Master Enable Register for enabling interrupts request output

ˆ Each input is congurable for edge or level sensitivity. Edge sensitivity can be congured for rising or
falling. Level sensitivity can be active-high or active-low

ˆ Output interrupt request pin is congurable for edge or level generation. Edge generation is congurable
for rising or falling and level generation is congurable for active-high or active-low

ˆ Congurable Software Interrupt capability

ˆ Support for nested interrupts

Figure 1.7: AXI INTC core block diagram

The LogiCORE IP INTC core concentrates multiple interrupt inputs from peripheral devices to a single interrupt
output to the system processor. The registers used for checking, enabling, and acknowledging interrupts are
accessed through the AXI4-Lite interface.

Note : If you want to read and learn more about the AXI Interrupt Controller core, please refer to "LogiCORE
IP AXI Interrupt Controller (INTC) v4.1 Product Guide".

12 2023/01/10 www.so-logic.net
CHAPTER 1. INTRODUCTION

LogiCORE IP AXI General Purpose Input/Output (GPIO) - The LogiCORE IP AXI General Pur-
pose Input/Output (GPIO) core provides a general purpose input/output interface to the AXI interface. This
32-bit soft IP core is designed to interface with the AXI4-Lite interface.

The AXI GPIO includes the following features:

ˆ Supports the AXI4-Lite interface specication

ˆ Supports congurable single or dual GPIO channel(s)

ˆ Supports congurable channel width for GPIO pins from 1 to 32 bits

ˆ Supports dynamic programming of each GPIO bit as input or output

ˆ Supports individual conguration of each channel

ˆ Supports independent reset values for each bit of all registers

ˆ Supports optional interrupt request generation

The AXI GPIO design provides a general purpose input/output interface to an AXI4-Lite interface. The AXI
GPIO can be congured as either a single or a dual-channel device. The width of each channel is independently
congurable.

The ports are congured dynamically for input or output by enabling or disabling the 3-state buer. The
channels can be congured to generate an interrupt when a transition on any of their inputs occurs.

Figure 1.8: AXI GPIO block diagram

Note : If you want to read and learn more about the AXI GPIO core, please refer to "LogiCORE IP AXI GPIO
v2.0 Product Guide".

1.4.2 Design steps

This tutorial will be realized step by step with the idea to explain the whole procedure of designing an digital
system, using Vivado tool.

ˆ First, we will create ("modulator" ) project for Zynq-7000 AP SoC processor system using Vivado IP
Integrator tool. The block diagram of this system is shown on the Figure 1.3. Here we will congure the
selected microprocessor and peripherals, and specify the interconnections between these components.

ˆ After we create "modulator" project using Vivado IP Integrator tool, we will perform synthesis, imple-
mentation, bitstream le generation and program target FPGA device.

www.so-logic.net 2023/01/10 13
CHAPTER 1. INTRODUCTION

ˆ Then, we will export our hardware platform desscription from Vivado to Vitis software platform. The
exported le has all the necessary information that Vitis requires for software development and debug
work on the hardware platform that we designed.

ˆ In the Vitis IDE, we will create and debug the software application for this project. There will be two
dierent software applications, one without and one with the interrupt controller. Source codes for these
two applications will be stored in modulator_socius_no_intc.c and modulator_socius_intc.c
source le respectively.

ˆ Now, the design is ready to be implemented. Please notice, that the last step refers only to the MicroBlaze-
based systems where it is necessary to initialize the bitstream with the appropriate ELF le before down-
loading bitstream to the target Xilinx development board.

Design Steps

Figure 1.9: Project Design Steps

1.5 Embedded Design Process Flow


Vivado Design Suite is designed to help us in all phases of the embedded design process. On the Figure 1.10
Vivado embedded design process ow is shown, illustrating how the tools operate together to create an embedded
system in case of processor-based FPGA systems.

In case of using MicroBlaze-based processor systems, bitstream le will be initialized with the appropriate .elf
le and will be downloaded into the BRAM memory on the FPGA device.

In case of using ARM-based processor systems, bitstream le will be automatically downloaded in to the
peripheral DRAM memory and bitstream le initialization is not necessary.

14 2023/01/10 www.so-logic.net
CHAPTER 1. INTRODUCTION

Vivado Embedded Design Process Flow

Figure 1.10: Typical embedded design process ow

www.so-logic.net 2023/01/10 15
CHAPTER 1. INTRODUCTION

16 2023/01/10 www.so-logic.net
Chapter 2

CREATING THE HARDWARE


PLATFORM
In the previous chapter, we have dened the structure of the microprocessor based system that will be used as
a part of the solution of PWM signal generation. In this chapter, we will explain how to generate this system
using Vivado IP Integrator tool. While entire designs can be created using the IP Integrator, the typical design
will consist of HDL, IP and IP integrator block designs.

2.1 Create a New Project


The rst step in creating a new design will be to create a new project. We will crate a new project using the
Vivado IDE New Project wizard. The New Project wizard will create an XPR project le for us. It will be
place where Vivado IDE will organize our design les and save the design status whenever the processes are
run.

To create a new project, follow these steps:

- Launch the Vivado software:

Select Start -> All Programs -> Xilinx Design Tools -> Vivado 2022.2 -> Vivado 2022.2 and the
Vivado Getting Started page will appear, see Figure 2.1.

Getting Started page contains a lot of usable links (shortcuts)


As you can see from the gure below, the
Create Project, Open an existing Project, Open Example Project, Open Hardware Manager,
like
Documentation and Tutorials and so on.

17
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Create New Project

Figure 2.1: The Vivado Getting Started page

- On the Getting Started page, choose rst oered Create Project option, under the Quick Start section.

- In the Create a New Vivado Project dialog box click Next and the wizard will guide you through the
creation of a new project.

18 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.2: Create a New Vivado Project dialog box

- In the Project Name dialog box specify the name and the location of the new project and click Next.

Figure 2.3: Project Name dialog box

ˆ In the Project name eld type modulator as the name of our project

ˆ In the Project location eld specify the location where our project data will be stored

ˆ Leave Create project subdirectory option enabled, see Figure 2.3

- In the Project Type dialog box specify the type of project you want to create. In our case we will choose
RTL Project option. Select Do not specify sources at this time also and click Next.

www.so-logic.net 2023/01/10 19
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.4: Project Type dialog box

As you can see from the gure above, ve dierent types of the project can be created:

ˆ RTL Project - The RTL Project environment enables you to add RTL source les and constraints, congure
IP with the Vivado IP catalog, create IP subsystems with the Vivado IP integrator, synthesize and
implement the design, and perform design planning and analysis.

ˆ Post-synthesis Project - This type of project enables you to import third-party netlists, implement the
design, and perform design planning and analysis.

ˆ I/O Planning Project - With this type of project you can create an empty project for use with early I/O
planning and device exploration prior to having RTL sources.

ˆ Imported Project - This type of project enables you to import existing project sources from the ISE Design
Suite, Xilinx Synthesis Technology (XST), or Synopsys Synplify.

ˆ Congure an Example Embedded Evaluation Board Design - This type of project enables you to target
the example Zynq-7000 or MicroBlaze embedded designs to the available Xilinx evaluation boards.

- In the Default Part dialog box choose a default Xilinx part or board for your project and click Next.

20 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.5: Default Part dialog box

The main component of the Sozius development board is Zynq-7000 AP SoC, so in the Default Part dialog
box select Parts option and set the lter parameters as it is shown on the Figure 2.5.

- In the New Project Summary dialog box click Finish if you are satised with the summary of your project.

Figure 2.6: New Project Summary dialog box

If you are not satised, you can go back as much as necessary to correct all the questionable issues, see Figure
2.6.

www.so-logic.net 2023/01/10 21
CHAPTER 2. CREATING THE HARDWARE PLATFORM

After we nished with the new project creation, in a few seconds Vivado IDE Viewing Environment will
appear, see Figure 2.7.

When Vivado creates new project, it also creates a directory with the name and at the location that we specied
in the GUI. That means that the all project data will be stored in the project_name (modulator) directory
containing the following:

ˆ project_name.xpr le - object that is selected to open a project (Vivado IDE project le)

ˆ project_name.runs directory - contains all run data

ˆ project_name.srcs directory - contains all imported local HDL source les, netlists, and XDC les

ˆ project_name.data directory - stores oorplan and netlist data

ˆ project_name.sim directory - contains all simulation data

Vivado IDE Viewing Environment

Figure 2.7: Vivado IDE Viewing Environment

2.2 Vivado Integrated Design Environment


The Vivado IDE can be used for a variety of purposes at various stages in the design ow and is very helpful
at detecting design problems early in the design ow.

The Vivado IDE allows dierent le types to be added as design sources, including Verilog, VHDL, EDIF, NGC
format cores, SDC, XDC, and TCL constraints les, and simulation test benches. These les can be stored in
variety of ways using the tabs at the bottom of the Sources window: Hierarchy, Library or Compile Order,
see Figure 2.8.

22 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

By default, after launching, the Vivado IDE opens the Default Layout. Each docked window in the Vivado IDE
is called a view, so you can nd Sources View, Properties View, Project Summary View ans so on, see Figure
2.8.

Figure 2.8: Vivado IDE Viewing Environment

Flow Navigator

The vertical toolbar present on the left side of the Vivado IDE is the Flow Navigator. The Flow Navigator
provides control over the major design process tasks, such as project conguration, synthesis, implementation
and bitstream creation.

Sources View

The Sources view displays the list of source les that has been added in the project.

ˆ The Design Sources folder helps you keep track of VHDL and Verilog design source les and libraries.

ˆ The Constraints folder helps you keep track of the constraints les.

ˆ The Simulation Sources folder helps keep track of VHDL and Verilog simulation sources source les
and libraries.

Notice that the design hierarchy is displayed as default.

ˆ In the Libraries tab, sources are grouped by le type, while the Compile Order tab shows the le order
used for synthesis.

www.so-logic.net 2023/01/10 23
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Project Summary View

The Project Summary view provides a brief overview of the status of dierent processes executed in the
Vivado IDE, see Figure 2.9.

Figure 2.9: Project Summary View

The Project Settings panel displays the project name, product family, project part, and top module name.
Clicking a link in this panel you will open the Project Settings dialog box.

ˆ The Messages panel summarizes the number of errors and warnings encountered during the design
process.

ˆ The Synthesis panel summarizes the state of synthesis in the active run. The synthesis panel also shows
the target part and the strategy applied in the run.

ˆ The Implementation panel summarizes the state of implementation in the active run. The Implemen-
tation panel also shows the target part and the strategy applied in the run.

Tcl Console

Below the Project Summary view, see Figure 2.10, is the Tcl Console which echoes the Tcl commands as
operations are performed. It also provides a means to control the design ow using Tcl commands.

2.3 Create ARM-based Hardware Platform


IP Integrator (IPI) Tool

IP
To accelerate the creation of highly integrated and complex designs, Vivado Design Suite is delivered with
Integrator (IPI) which provides a new graphical and Tcl-based IP- and system-centric design development
ow.

The Xilinx Vivado Design Suite IP Integrator feature lets you create complex system designs by instantiating
and interconnecting IP cores from the Vivado IP Catalog onto a design canvas.

24 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

You can create designs interactively through the IP Integrator design canvas GUI, or using a Tcl programming
interface.

Rapid development of smarter systems requires levels of automation that go beyond RTL-level design. The
Vivado IP Integrator accelerates IP- and system-centric design implementation by providing the following:

ˆ Seamless inclusion of IPI sub-systems into the overall design

ˆ Rapid capture and packing of IPI designs for reuse

ˆ Tcl scripting and graphical design

ˆ Rapid simulation and cross-probing between multiple design views

ˆ Support for processor or processor-less designs

ˆ Integration of algorithmic and RTL-level IP

ˆ Combination of DSP, video, analog, embedded, connectivity and logic

ˆ Matches typical designer ows

ˆ Easy to reuse complex sub-systems

ˆ DRCs on complex interface level connections during design assembly

ˆ Recognition and correction of common design errors

ˆ Automatic IP parameter propagation to interconnected IP

ˆ System-level optimizations

You will typically construct design at the AXI interface level for greater productivity, but you may also manip-
ulate designs at the port level for more precise design control.

Create IP Integrator Design

In this tutorial you will instantiate a few IPs in the IP Integrator tool and then stitch them together to create
an IP based system design.

While working on this tutorial, you will be introduced to the IP Integrator GUI, run design rule checks (DRC)
on your design, and then integrate the design in a top-level design in the Vivado Design Suite.

Finally, you will run synthesis and implementation process, generate bitstream le and run your design on the
Sozius development board.

2.3.1 Create ARM-based Hardware Platform for Sozius Development Board


About Sozius development board

Sozius development platform is a small, portable electronic device that can easily be powered from a USB port,
USB charger, Power Over Ethernet or a battery pack.

You can easily develop software and/or digital hardware for it, because it uses an FPGA with an embedded
processors.

Sozius delivers already a well designed board and should help you to focus on the specics of your project and
can be easily extended to meet your needs.

The main system with many interfaces and Linux is already precongured and ready for use.

www.so-logic.net 2023/01/10 25
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Zynq-7000 AP SoC Processor System

Figure 2.10: TTC and GPIO module as integrated Zynq-7000 PS parts

The main component of the Sozius development board is Zynq-7000 AP SoC. As we already said, the Zynq-
7000 AP SoC is composed of two major functional blocks: Processing System (PS) and Programmable
Logic (PL). Since existing LEDs and switches on the Sozius board are connected to the PS part of the Zynq
FPGA, it would require programming PS part of the Zynq FPGA. In this design we will not use PL part of the
Zynq FPGA to implement timer and GPIO modules (as it is presented on the Figure 1.3), because we would
not be able to connect them to the Sozius board LEDs and switches. Instead, we must use timer and GPIO
modules from the PS part of the Zynq FPGA. More specically, we will use one Triple Timer Counter (TTC)
module, TTC0, that is present in the PS part of the Zynq FPGA and four General Purpose IO ports from the
GPIO module that is also present in the PS part of the Zynq FPGA, see Figure 2.10.

This sub-chapter will show how to build Zynq-7000 All Programmable (AP) SoC processor "modulator" design
using Vivado IDE and Tcl programming interface. In this sub-chapter, you will instantiate a few IPs in the IP
Integrator tool and then stitch them together to create an IP based system design. At the end, you will run
synthesis and implementation process and generate bitstream le.

The following steps describe how to create ARM-based hardware platform for Sozius development board.

Create modulator_sozius_arm_rtl.vhd and modulator_components_package.vhd Source Files

- First, we will create modulator_sozius_arm_rtl.vhd and sozius_components_package.vhd les


using Vivado test editor and save them into the working directory.

ˆ modulator_sozius_arm_rtl.vhd le will hold the top-level module of our design, in which Zynq PS
component congured for Sozius development board will be instantiated

ˆ sozius_components_package.vhd le will contain Sozius PS module component declaration.

The content of the both les is presented in the text below.

26 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

modulator_sozius_arm_rtl.vhd:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

library unisim;
use unisim.vcomponents.all;

library work;
use work.sozius_components_package.all;

entity modulator_sozius_arm is
port(
-- ps io
ps_ddr3_addr : inout std_logic_vector(14 downto 0);
ps_ddr3_ba : inout std_logic_vector(2 downto 0);
ps_ddr3_cas_n : inout std_logic;
ps_ddr3_ck_n : inout std_logic;
ps_ddr3_ck_p : inout std_logic;
ps_ddr3_cke : inout std_logic;
ps_ddr3_cs_n : inout std_logic;
ps_ddr3_dm : inout std_logic_vector( 3 downto 0);
ps_ddr3_dq : inout std_logic_vector(31 downto 0);
ps_ddr3_dqs_n : inout std_logic_vector( 3 downto 0);
ps_ddr3_dqs_p : inout std_logic_vector( 3 downto 0);
ps_ddr3_odt : inout std_logic;
ps_ddr3_ras_n : inout std_logic;
ps_ddr3_reset_n : inout std_logic;
ps_ddr3_we_n : inout std_logic;
ps_ddr_vrn : inout std_logic;
ps_ddr_vrp : inout std_logic;
ps_clk_i : inout std_logic;
ps_por_n_i : inout std_logic;
ps_srst_n_i : inout std_logic;
ps_phy_mdc_io : inout std_logic;
ps_phy_mdio_io : inout std_logic;
ps_phy_rx_clk_io : inout std_logic;
ps_phy_rx_ctrl_io : inout std_logic;
ps_phy_rxd_io : inout std_logic_vector(3 downto 0);
ps_phy_tx_clk_io : inout std_logic;
ps_phy_tx_ctrl_io : inout std_logic;
ps_phy_txd_io : inout std_logic_vector(3 downto 0);
ps_i2c_scl_io : inout std_logic;
ps_i2c_sda_io : inout std_logic;
ps_led_error_n_io : inout std_logic;
ps_led_front_n_io : inout std_logic_vector(1 downto 0);
ps_led_sdcard_n_io : inout std_logic;
ps_sw0_a_io : inout std_logic;
ps_sw0_b_io : inout std_logic;
ps_sw1_a_io : inout std_logic;
ps_sw1_b_io : inout std_logic;
ps_sw2_a_io : inout std_logic;
ps_sw2_b_io : inout std_logic;
ps_sw3_a_io : inout std_logic;
ps_sw3_b_io : inout std_logic;
ps_uart_rx_io : inout std_logic;
ps_uart_tx_io : inout std_logic;
ps_qspi_cs_n_io : inout std_logic;
ps_qspi_data_io : inout std_logic_vector(3 downto 0);
ps_qspi_clk_io : inout std_logic;
ps_sdio_clk_io : inout std_logic;
ps_sdio_cmd_io : inout std_logic;
ps_sdio_data_io : inout std_logic_vector(3 downto 0);
ps_usb_clk_io : inout std_logic;
ps_usb_data_io : inout std_logic_vector(7 downto 0);
ps_usb_dir_io : inout std_logic;
ps_usb_nxt_io : inout std_logic;
ps_usb_stp_io : inout std_logic
);
end entity;

architecture structural of modulator_sozius_arm is

-- Between architecture and begin is declaration area for types, signals and constants
-- Everything declared here will be visible in the whole architecture

-- declaration for fixed signal PL to PS


signal pl_clk0_s : std_logic;
signal pl_reset_n_s : std_logic;

-- ps signals
signal ps_mio_s : std_logic_vector(53 downto 0);

begin

www.so-logic.net 2023/01/10 27
CHAPTER 2. CREATING THE HARDWARE PLATFORM

-- instance of processor system PS

sozius_xz_lab_ps_bd_i: component sozius_xz_lab_ps_bd


port map (
ddr3_addr => ps_ddr3_addr,
ddr3_ba => ps_ddr3_ba,
ddr3_cas_n => ps_ddr3_cas_n,
ddr3_ck_n => ps_ddr3_ck_n,
ddr3_ck_p => ps_ddr3_ck_p,
ddr3_cke => ps_ddr3_cke,
ddr3_cs_n => ps_ddr3_cs_n,
ddr3_dm => ps_ddr3_dm,
ddr3_dq => ps_ddr3_dq,
ddr3_dqs_n => ps_ddr3_dqs_n,
ddr3_dqs_p => ps_ddr3_dqs_p,
ddr3_odt => ps_ddr3_odt,
ddr3_ras_n => ps_ddr3_ras_n,
ddr3_reset_n => ps_ddr3_reset_n,
ddr3_we_n => ps_ddr3_we_n,
fixed_io_ddr_vrn => ps_ddr_vrn,
fixed_io_ddr_vrp => ps_ddr_vrp,
fixed_io_mio => ps_mio_s,
fixed_io_ps_clk => ps_clk_i,
fixed_io_ps_porb => ps_por_n_i,
fixed_io_ps_srstb => ps_srst_n_i,
pl_uart_1_rxd => '0',
pl_uart_1_txd => open,
pl_spi_0_io0_i => '0',
pl_spi_0_io0_o => open,
pl_spi_0_io0_t => open,
pl_spi_0_io1_i => '0',
pl_spi_0_io1_o => open,
pl_spi_0_io1_t => open,
pl_spi_0_sck_i => '0',
pl_spi_0_sck_o => open,
pl_spi_0_sck_t => open,
pl_spi_0_ss1_o => open,
pl_spi_0_ss2_o => open,
pl_spi_0_ss_i => '0',
pl_spi_0_ss_o => open,
pl_spi_0_ss_t => open,
pl_iic_1_scl_i => '0',
pl_iic_1_scl_o => open,
pl_iic_1_scl_t => open,
pl_iic_1_sda_i => '0',
pl_iic_1_sda_o => open,
pl_iic_1_sda_t => open,
sdio_0_cdn => '1', -- pl_sd_cd_n_i,
usbind_0_port_indctl => open,
usbind_0_vbus_pwrfault => '1', -- pl_usb_fault_n_i,
usbind_0_vbus_pwrselect => open,
pl_clk0 => pl_clk0_s,
pl_reset_n => pl_reset_n_s
);

-- assignment of MIO to board names

ps_mio_s (53) <= ps_phy_mdio_io;


ps_mio_s (52) <= ps_phy_mdc_io;
ps_mio_s (51) <= ps_uart_tx_io;
ps_mio_s (50) <= ps_uart_rx_io;
ps_mio_s (49) <= ps_led_error_n_io;
ps_mio_s (48 downto 47) <= ps_led_front_n_io(1 downto 0);
ps_mio_s (46) <= ps_led_sdcard_n_io;
ps_mio_s (45 downto 42) <= ps_sdio_data_io;
ps_mio_s (41) <= ps_sdio_cmd_io;
ps_mio_s (40) <= ps_sdio_clk_io;
ps_mio_s (39) <= ps_usb_data_io(7);
ps_mio_s (38) <= ps_usb_data_io(6);
ps_mio_s (37) <= ps_usb_data_io(5);
ps_mio_s (36) <= ps_usb_clk_io;
ps_mio_s (35) <= ps_usb_data_io(3);
ps_mio_s (34) <= ps_usb_data_io(2);
ps_mio_s (33) <= ps_usb_data_io(1);
ps_mio_s (32) <= ps_usb_data_io(0);
ps_mio_s (31) <= ps_usb_nxt_io;
ps_mio_s (30) <= ps_usb_stp_io;
ps_mio_s (29) <= ps_usb_dir_io;
ps_mio_s (28) <= ps_usb_data_io(4);
ps_mio_s (27) <= ps_phy_rx_ctrl_io;
ps_mio_s (26 downto 23) <= ps_phy_rxd_io;
ps_mio_s (22) <= ps_phy_rx_clk_io;
ps_mio_s (21) <= ps_phy_tx_ctrl_io;
ps_mio_s (20 downto 17) <= ps_phy_txd_io;
ps_mio_s (16) <= ps_phy_tx_clk_io;
ps_mio_s (15) <= ps_i2c_sda_io;
ps_mio_s (14) <= ps_i2c_scl_io;
ps_mio_s (13) <= ps_sw3_b_io;
ps_mio_s (12) <= ps_sw3_a_io;
ps_mio_s (11) <= ps_sw2_b_io;
ps_mio_s (10) <= ps_sw2_a_io;

28 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

ps_mio_s (9) <= ps_sw1_b_io;


ps_mio_s (8) <= ps_sw1_a_io;
ps_mio_s (7) <= ps_sw0_b_io;
ps_mio_s (6) <= ps_qspi_clk_io;
ps_mio_s (5 downto 2) <= ps_qspi_data_io;
ps_mio_s (1) <= ps_qspi_cs_n_io;
ps_mio_s (0) <= ps_sw0_a_io;

end architecture;

sozius_components_package.vhd:

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;

package sozius_components_package is

component sozius_xz_lab_ps_bd is
port (
pl_clk0 : out std_logic;
pl_reset_n : out std_logic;
ddr3_cas_n : inout std_logic;
ddr3_cke : inout std_logic;
ddr3_ck_n : inout std_logic;
ddr3_ck_p : inout std_logic;
ddr3_cs_n : inout std_logic;
ddr3_reset_n : inout std_logic;
ddr3_odt : inout std_logic;
ddr3_ras_n : inout std_logic;
ddr3_we_n : inout std_logic;
ddr3_ba : inout std_logic_vector ( 2 downto 0 );
ddr3_addr : inout std_logic_vector ( 14 downto 0 );
ddr3_dm : inout std_logic_vector ( 3 downto 0 );
ddr3_dq : inout std_logic_vector ( 31 downto 0 );
ddr3_dqs_n : inout std_logic_vector ( 3 downto 0 );
ddr3_dqs_p : inout std_logic_vector ( 3 downto 0 );
fixed_io_mio : inout std_logic_vector ( 53 downto 0 );
fixed_io_ddr_vrn : inout std_logic;
fixed_io_ddr_vrp : inout std_logic;
fixed_io_ps_srstb : inout std_logic;
fixed_io_ps_clk : inout std_logic;
fixed_io_ps_porb : inout std_logic;
sdio_0_cdn : in std_logic;
usbind_0_port_indctl : out std_logic_vector ( 1 downto 0 );
usbind_0_vbus_pwrselect : out std_logic;
usbind_0_vbus_pwrfault : in std_logic;
pl_iic_1_sda_i : in std_logic;
pl_iic_1_sda_o : out std_logic;
pl_iic_1_sda_t : out std_logic;
pl_iic_1_scl_i : in std_logic;
pl_iic_1_scl_o : out std_logic;
pl_iic_1_scl_t : out std_logic;
pl_spi_0_sck_i : in std_logic;
pl_spi_0_sck_o : out std_logic;
pl_spi_0_sck_t : out std_logic;
pl_spi_0_io0_i : in std_logic;
pl_spi_0_io0_o : out std_logic;
pl_spi_0_io0_t : out std_logic;
pl_spi_0_io1_i : in std_logic;
pl_spi_0_io1_o : out std_logic;
pl_spi_0_io1_t : out std_logic;
pl_spi_0_ss_i : in std_logic;
pl_spi_0_ss_o : out std_logic;
pl_spi_0_ss1_o : out std_logic;
pl_spi_0_ss2_o : out std_logic;
pl_spi_0_ss_t : out std_logic;
pl_uart_1_txd : out std_logic;
pl_uart_1_rxd : in std_logic
);
end component;

component sozius_xz_ps_bd is

Add modulator_sozius_arm_rtl.vhd and modulator_components_package.vhd Source Files

- When we nished with the modulator_sozius_arm_rtl.vhd and sozius_components_package.vhd


les creation, in the Vivado Flow Navigator, click Add Sources command.

www.so-logic.net 2023/01/10 29
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.11: Add Sources command

- In the Add or Create Design Sources dialog box, click the + icon and select Add Files... option to
Add Files button.
include the existing source les into the project, or just click

Figure 2.12: Add or Create Design Sources dialog box - Add Files option

Add Source Files dialog box, browse to the project working directory and select the modula-
- In the
tor_sozius_arm_rtl.vhd and sozius_components_package.vhd source les.

30 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.13: Add Source Files dialog box

- Click OK and the modulator_sozius_arm_rtl.vhd and sozius_components_package.vhd source


Add or Create Design Sources dialog box.
les should appear in the

Figure 2.14: Add or Create Design Sources dialog box - with added le

Finish and your source les should appear under the Design Sources in the Sources view in the
- Click
Project Manager window.

www.so-logic.net 2023/01/10 31
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.15: Sources view with added new les

Create sozius_xz_modulator_vio.xdc Constraints File

- Now is the time to create constraints le for the Sozius board, sozius_xz_modulator_vio.xdc.

Open Vivado text editor, copy your constraints code in it or write directly in it and save the constraints le in
your working directory.

The complete sozius_xz_modulator_vio.xdc source le you can nd in the further text.

Note : If you want to read and learn more about XDC constraints les, please refer to the "Basic FPGA
Tutorial", sub-chapter 9.1 "Creating XDC File" where you will nd all the necessary information about
the types of constraints, how to create them depending on the target board type and use them in your design.

sozius_xz_modulator_vio.xdc:

# set properties commentstylefor bitstream genration


set_property BITSTREAM.GENERAL.COMPRESS TRUE [current_design]
#set_property BITSTREAM.GENERAL.XADCENHANCEDLINEARITY commentstyleON [current_design]
#set_property BITSTREAM.GENERAL.XADCPOWERDOWN ENABLE [current_design]

# set commentstyleconfiguration bank voltages


set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]

# set condition commentstylefor power analyzer


set_operating_conditions -ambient_temp 50
set_operating_conditions -board small
set_operating_conditions -airflow 250
set_operating_conditions -heatsink low
set_operating_conditions -board_layers 12to15

# pins must be implemented !!


set_property PACKAGE_PIN V13 [get_ports pl_phy_reset_n_o]
set_property IOSTANDARD LVCMOS33 [get_ports pl_phy_reset_n_o]

Add sozius_xz_modulator_vio.xdc Constraints File

- In the Vivado Flow Navigator, click the Add Sources command.

- In the Add Sources dialog box select Add or create constraints option to add the sozius_xz_modulator_vio.xdc
constraints le into our project and click Next.

32 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Figure 2.16: Add or Create Constraints option

- In the Add or Create Constraints dialog box, click + icon and select Add Files... option.

Add Constraint Files dialog box, browse to the project working directory and select the soz-
- In the
ius_xz_modulator_vio.xdc constraints le.

OK and the sozius_xz_modulator_vio.xdc constraints le should appear in the Add or Create
- Click
Constraints dialog box.

- Click Finish and your constraints le should appear under the Constraints in the Sources view.

Figure 2.17: Sources view with added constraints le

Congure the Zynq PS Part to work on Sozius Development Board

Finally, we must congure the Zynq PS part to work on Sozius development board.

This includes a number of conguration steps, one of them being the proper conguration of the PS GPIO module to
connect to the LEDs and switches that are present on the Sozius board.

Also, we must enable the Triple Timer Counter 0 (TTC0) module within Zynq PS, that will be used in the "modulator"
design. All these PS conguration steps can be done using the Vivado GUI, by creating a block design.

However, since this task includes a lot of manual settings of the Zynq PS, a better approach would be to do this manual
conguration only once and then to create a Tcl script le that can be used in all future congurations of the Zynq PS
part.

The Tcl script that should be used to correctly congure Zynq PS to work on Sozius board is sozius_xz_lab_ps_bd.tcl.

This Tcl script le is too long to be shown in the tutorial, so ask your instructor for details.

www.so-logic.net 2023/01/10 33
CHAPTER 2. CREATING THE HARDWARE PLATFORM

Execute Tcl File in the Vivado IDE

- Next step is to execute the presented Tcl le in the Vivado IDE. Go to the Tcl console window and type the
following and press enter:

source <path>/sozius_xz_lab_ps_bd.tcl

<path> stands for the full path to the folder where the sozius_xz_lab_ps_bd.tcl Tcl le is stored.

Figure 2.18: Tcl Console window

After Vivado has nished with the Tcl script execution, a created block diagram containing Zynq PS will be
visible in the Vivado IDE, as shown on the Figure 2.20.

Block Diagram after Vivado Tcl Script Execution

Figure 2.19: Block diagram of Zynq PS congured to run on Sozius board

There are two possibilities for netlist and bitstream le generation. One is to generate these les after a
hardware platform is specied and the second one is to generate them after a software application development
is completed.

If you would like to generate netlist and bitstream le after hardware platform specication:

Synthesize and Implement "modulator" Design and Generate Bitstream File

- In the Vivado Flow Navigator, click Run Synthesis command, and wait for task to be completed.

34 2023/01/10 www.so-logic.net
CHAPTER 2. CREATING THE HARDWARE PLATFORM

- When the synthesis process is completed, click Run Implementation command, and wait for task to be completed.

- At the end, when the implementation process is completed, click Generate Bitstream command. After this step,
bitstream le will be generated.

Figure 2.20: Run Synthesis, Run Implementation and Generate Bitstream commands from the Vivado Flow
Navigator

Program FPGA Device

- The last step in hardware platform creation will be to download generated bitstream le to the target FPGA device.

To download generated bitstream le to the target FPGA device use Flow Navigator's Open Hardware Manager
command.

Figure 2.21: Open Hardware Manager command

Note : If you would like to read detailed steps how to program your target FPGA device, please open Basic FPGA
Tutotial, sub-chapter 9.4 "Program Device".

If you would you like to generate netlist and bitstream les after a software application development is completed
(in case of using MicroBlaze-based processor systems), please follow the next chapter where will be explained
in detail the necessary steps.

www.so-logic.net 2023/01/10 35
CHAPTER 2. CREATING THE HARDWARE PLATFORM

36 2023/01/10 www.so-logic.net
Chapter 3

CREATING THE SOFTWARE


PLATFORM USING VITIS
Software Platform

In the previous chapter we have designed the hardware component of our embedded system.

To complete the design process we must now create a software component for our embedded system.

This application specic software will be executed on the ARM processor that is a part of our hardware
platform.

When using Xilinx development tools, software design process is done using the Vitis Core Development Kit
tool. Our software application will be developed for the hardware platform built in IP Integrator tool.

Figure 3.1 shows, in detail, Vitis application development ow.

Vitis Application Development Flow

Figure 3.1: Vitis application development ow

The rst step in software creation is to export the hardware design into the Vitis IDE.

The Vitis IDE is part of the Vitis software platform. The Vitis IDE is designed to be used for the development
of embedded software applications targeted towards Xilinx embedded processors. The Vitis IDE works with

37
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

hardware designs created with Vivado Design Suite.

The hardware design will be exported to an XSA le that will be used by the Vitis IDE to create a software
application for our hardware.

To create a software platform for your embedded design, use the following steps:

Export Hardware

- When the Sozius board is programmed, select File -> Export -> Export Hardware... option from the
main Vivado IDE menu.

Export Hardware Platform wizard will guide you through the process of exporting hardware platform for
use in the Vitis tools.

To export a Hardware Platform, you will need to provide a name and location for the exported le and specify
the platform properties.

- In the Export Hardware Platform diloag box select Fixed as Platform type and click Next.

Figure 3.2: Export Hardware Platform dialog box

- In the Output dialog box select Include bitstream option and click Next.

38 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.3: Output dialog box

- In the File dialog box enter the name of your XSA le in the XSA le name led and check the directory
where the XSA le will be stored. Click Next.

Figure 3.4: File dialog box

- In the Exporting Hardware Platform dialog box to export the hardware platform just click Finish.

www.so-logic.net 2023/01/10 39
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.5: Exporting Hardware Platform dialog box

Launch Vitis Software Platform

In order to get the internal FPGA clock running, we must run some application on the processing system. In
order to do this, following steps must be performed:

- Launch the Vitis software platform:

Select Start -> All Programs -> Xilinx Design Tools -> Vitis 2022.2 -> Xilinx Vitis IDE 2022.2
and the Vivado Vitis Eclipse Launcher dialog box will appear.

- In the Eclipse Launcher dialog box select a directory as workspace in the Workspace eld and click Next.

Figure 3.6: Eclipse Launcher dialog box

Vivado Vitis IDE will be launched in the separate window.

40 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.7: Vitis Software Platform

3.1 Create a Platform Project

Create a Platform Project

File -> New -> Platform Project... option, or just click the
- On the Vitis IDE getting started page, select
link Create Platform Project and the New Platform Project wizard opens.

Figure 3.8: Platform Project option

- In the Create new platform project dialog box enter a name for your platform project in the Project
name eld, in our case it will be modulator_sozius, enable Use default location option and click Next.

www.so-logic.net 2023/01/10 41
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.9: New Platform Project dialog box

- In the Platform dialog box select Browse beside the Hardware Specication to provide your XSA le.

Figure 3.10: Platform dialog box

42 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

- In the Create Platform from XSA dialog box browse and select the XSA le that you exported from the
Vivado Design Suite and click Open.

Figure 3.11: Create Platform from XSA dialog box

- In the Platform dialog box provide the hardware and software specication for the new platform project.

ˆ In the XSA le eld, browse and select the XSA le that you exported from the Vivado Design Suite

ˆ Use the dropdown menus to select standalone as the operating system and ps7_cortexa9_0 as the
processor

ˆ The Generate boot components checkbox is selected.


You can deselect this if you do not need boot components (FSBL in case of Zynq and PMU rmware in
case of Zynq UltraScale+ MPSoC).

www.so-logic.net 2023/01/10 43
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.12: Platform dialog box with selected XSA le

- Click Finish and Vitis IDE will create your platform project.

Figure 3.13: Vitis IDE with created platform project

Build Project

44 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Build Project option,


- You can build your project platform by right-clicking the system project and selecting
or just clicking on the Build Project button. The Generation Successful message pops up.

Figure 3.14: Build Project button

3.2 Board Support Package


Board Support Package

Each software project must have a corresponding Board Support Package (BSP).

A Board Support Package is a collection of libraries and drivers that form the lowest level of your software
application stack.

Before you can create and use software applications in Vitis IDE, BSP will be created. There are several kinds
of BSPs. The standalone BSP is the most commonly used domain.

Conguring a Standalone BSP Domain

To congure a standalone BSP, perform the following steps:

- In the Explorer window, double-click on the platform.spr le to open the platform tab for viewing and
modication.

Figure 3.15: Explorer window with selected platform.spr le

- In the modulator_sozius window, select the appropriate domain/Board Support Package and the overview
page opens.

www.so-logic.net 2023/01/10 45
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.16: modulator_sozius window with selected domain

- In the overview page, click Modify BSP Settings.

Using the Overview page, you can select the OS Version and which of the Supported Libraries are to be enabled
in this domain/BSP.

- In the Board Support Package Settings dialog box leave all default options as they are set in all four tabs
OK.
(Overview, standalone, drivers and ps7_cortexa9.0) and click

Figure 3.17: Board Support Package Settings dialog box

The Board Support Package settings page enables you to congure parameters of the OS and its constituent
libraries.

46 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Note : Options for only the libraries that you enabled in the Overview page will be visible. Options for the
OS/standalone supported peripherals, that are present in the hardware platform, are also shown on the page.

Board Support Package Drivers Page

The Drivers page lists all the device drivers assigned for each peripheral in your system. You can select each
peripheral and change its default device driver assignment and its version. If you want to remove a driver for a
peripheral, assign the driver to none.

Some device drivers export parameters that you can congure. If a device in the driver list has parameters, it
is listed in navigation pane on the left and you can access them by clicking on the device name.

Board Support Package Settings Driver Conguration Page

The Driver Conguration page lists all of the congurable driver parameters for the device selected under the
drivers entry on the left. To change a parameter, click on the corresponding Value eld and type the new
setting.

When you nish with all the settings you want to make, click OK. The Vitis software platform regenerates the
domain/BSP sources.

If the Build All option is selected in the Project menu, Vitis software platform automatically rebuilds your
target platform with your new settings applied.

3.3 Create a Application Project


Create a Application Project

After installing the Vitis— software platform, the next step is to create a software application project. Software
application projects are the nal application containers. The project directory that is created contains (or links
to) your C/C++ source les, executable output le, and associated utility les, such as the Makeles used to
build the project.

The Vitis software platform automatically creates a system project for you. A system project is a top-level
container project that holds all of the applications that can run in a system at the same time. This is useful
if you have many processors in your system, especially if they communicate with one another, because you can
debug, launch, and prole applications as a set instead of as individual items.

Create a Sample Application Project

- In the Vitis IDE select File -> New -> Application Project... option.

Figure 3.18: New Application Project option

- In the Create a New Application Project dialog box just click Next to skip the welcome page instructions.

www.so-logic.net 2023/01/10 47
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.19: Create a New Application Project dialog box

- In the Platform dialog box Select a platform from repository tab opens. You should choose a platform
for your project. You can either use a pre-supplied platform (from Xilinx or another vendor), a previously
created custom platform, or you can create one automatically from an exported Vivado hardware project.

- Select modulator_sozius [custom] platform and click Next.

48 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.20: Platform dialog box

- In the Application project Details dialog box, specify the application project name (modulator_sozius_no_intc)
and its system project properties and click Next.

Figure 3.21: Application Project Details dialog box

www.so-logic.net 2023/01/10 49
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Note: In our design we will create two application projects. One will be without interrupt controller
(modulator_sozius_no_intc), and the second one will be with interrupt controller (modulator_sozius_intc).

- In the Domain dialog box, provide the domain and other software details for your project. In our case leave
all default parameters and clickNext.

Figure 3.22: Domain dialog box

- In the Templates dialog box select empty Application application and click Finish.

50 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.23: Templates dialog box

Create modulator_socius_intc Application Project

- Repeat the same procedure to create the other application project, named modulator_socius_intc.

After this step you should have both application projects in the Vitis IDE Project Explorer window.

3.4 Creating a C/C++ Source Files for Sozius Board Based Hard-
ware Platform
Creating a C/C++ Source Files for Sozius Board Based Hardware Platform

Now it's time to start writing the software for this project.

With the Vivado tool we have advantage to develop software independently from the hardware, using Vitis tool.

To create source les necessary for our embedded system that will be running on the Sozius board, we must
create a software component.

This application specic software will be executed on the ARM processor that is already part of our hardware
platform.

As we already said in the previous sub-chapter, we will develop two application projects. One will be without
interrupt controller (modulator_sozius_no_intc), and the second one will be with interrupt controller
(modulator_sozius_intc). The idea was to illustrate how the same problem can be solved in a number of
dierent ways.

In the ARM-based processor system we will also use UART Controller, that is integral part of the Zynq7
processing system, to transmit debug and system status information during application execution to the attached
PC. This will be achieved using xil_printf function.

www.so-logic.net 2023/01/10 51
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Modulator Design without Interrupt Controller

Source les that will be created for modulator_sozius_no_intc application project are:

ˆ modulator_sozius_no_intc.c

ˆ modulator.h

ˆ init_sin.c

Create Necessary Source Files for modulator_sozius_no_intc Application Project

To create source les necessary for modulator_sozius_no_intc application project, please do the following:

- Expand modulator_sozius_no_intc application project in Project Explorer and src folder should
appear.

In the src folder you should nd your source code after creation.

- Right-click on the src folder and select New -> File option.

- In the New File dialog box:

ˆ select the src folder and

ˆ enter the File name (in our case it will be modulator_sozius_no_intc.c)

Figure 3.24: New File dialog box

- Click Finish and your modulator_sozius_no_intc.c source le should appear in the src folder, as we
already said.

- Double-click on the modulator_sozius_no_intc.c source le in the Project Explorer and it will be imme-
diately opened.

- Copy your source code in it, or write directly in it.

- When you nished with all modications, click Save and Vitis IDE will automatically build your application.

- Repeat the same procedure to also create modulator.h and init_sin.c source les.

52 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Note : The complete source les for modulator_sozius_no_intc.c, modulator.h and init_sin.c you can
nd in the text below.

modulator_sozius_no_intc.c:

#include "xparameters.h"
#include "xgpiops.h"
#include "xstatus.h"
#include "xttcps.h"
#include "modulator.h"
#include "xil_printf.h"

#define TTC_DEVICE_ID XPAR_XTTCPS_0_DEVICE_ID

// Definitions of actual pin locations for LED and push-button on the socius board
#define ps_sw2_a 10
#define ps_sw2_b 11
#define ps_sw3_a 12

// New type definition that will hold all relevant configuration parameters for the TTC module
typedef struct {
u32 OutputHz; /* Output frequency */
u16 Interval; /* Interval value */
u8 Prescaler; /* Prescaler value */
u16 Options; /* Option settings */
} TmrCntrSetup;

// Instance of a TmrCntrSetup type, holding TTC0 configuration parameters that will be used in modulator example
static TmrCntrSetup SettingsTable[1] = {
{10, 65000, 2, 0}, /* Ticker timer counter initial setup, only output freq */
};

int main(void)
{
/*********************** Variable Definitions ********************/
XGpioPs GpioLeds; // XGPIO instance that will be used to work with LED
XGpioPs_Config *GPIOConfigPtr;

XTtcPs_Config *Config; // Pointer to a XTtcPs_Config type


XTtcPs TimerInst; // TIMER instance
XTtcPs *Timer; // Pointer to a XTtcPs type
TmrCntrSetup *TimerSetup; // Pointer to a TmrCntrSetup type

int count_depth = 0; // Counter for sine samples


int sw0, prev_sw0; // Switch used for selecting frequency
int scaling_factor; // Will be used to represent the SCALING_FACTOR_0 or SCALING_FACTOR_1 value

unsigned int end_time; // Will be used to represent the END_TIME_0 or END_TIME_1 value
unsigned int threshold; // Will be used to represent the current value of the sine signal
u16 current_time; // Represents the current timer value

// Sine amplitude values that will be used to generate the PWM signal
static unsigned int sine_ampl[COUNT_DEPTH_END];

/*********************** Initialization **************************/

xil_printf("Initializing peripherals!\r\n");

xil_printf("Initializing LEDs!\r\n");
// LEDs initialization
GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
XGpioPs_CfgInitialize(&GpioLeds, GPIOConfigPtr, GPIOConfigPtr ->BaseAddr);
// On the socius board we must properly control both ends of a LED, hence we must use two GPIO ports
XGpioPs_SetDirectionPin(&GpioLeds, ps_sw2_a, 1);
XGpioPs_SetOutputEnablePin(&GpioLeds, ps_sw2_a, 1);
XGpioPs_SetDirectionPin(&GpioLeds, ps_sw2_b, 1);
XGpioPs_SetOutputEnablePin(&GpioLeds, ps_sw2_b, 1);
// Set the value of one end of LED to always be equal to zero, by changing the other end we will turn it on and off
XGpioPs_WritePin(&GpioLeds, ps_sw2_b, 0x0);

xil_printf("Initializing SWITCHes!\r\n");
// SWITCHes initialization
// Set the direction of the GPIO port connected to the push button to INPUT
XGpioPs_SetDirectionPin(&GpioLeds, ps_sw3_a, 0);

xil_printf("Initializing TIMER!\r\n");
// TIMER initialization
TimerSetup = &SettingsTable[TTC_DEVICE_ID];

// Read the current configuration of the timer module


Config = XTtcPs_LookupConfig(TTC_DEVICE_ID);

// Store the current configuration of the timer in a TimerInst object, using a pointer to access it
Timer = &TimerInst;
Timer->Config = *Config;

// Stop the timer

www.so-logic.net 2023/01/10 53
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

XTtcPs_Stop(Timer);

// Initialize the timer with the required configuration parameters


XTtcPs_CfgInitialize(Timer, Config, Config->BaseAddress);
TimerSetup->Options |= (XTTCPS_OPTION_INTERVAL_MODE |
XTTCPS_OPTION_WAVE_DISABLE);
XTtcPs_SetOptions(Timer, TimerSetup->Options);
XTtcPs_SetInterval(Timer, TimerSetup->Interval);
XTtcPs_SetPrescaler(Timer, TimerSetup->Prescaler);

xil_printf("Initializing sine_ampl array!\r\n");


// sine_ampl array initialization
init_sin_f (sine_ampl);

threshold = (SCALING_FACTOR_0/8) * sine_ampl[count_depth];

/************************* Main Loop *****************************/


xil_printf("Entering main loop.\r\n");
while(1)
{
// Start the timer
XTtcPs_Start(Timer);

// Read the switch position


sw0 = XGpioPs_ReadPin (&GpioLeds, ps_sw3_a);

// Check the switch position


if ((sw0 & SWITCH_POS) == 0) // Masking (we want to check the status of SW0 only)
{
end_time = END_TIME_0;
// We must further divide the end_time with the factor of 8, because we use pre-scaler set to value 8
end_time = end_time/8;
// We must further divide the scaling_factor with the factor of 8, because we use pre-scaler set to value 8
scaling_factor = SCALING_FACTOR_0/8;
}
else
{
end_time = END_TIME_1;
end_time = end_time/8;
scaling_factor = SCALING_FACTOR_1/8;
}

// Send current system status information to the terminal using UART


if (sw0 != prev_sw0)
{
if ((sw0 & SWITCH_POS) == 0)
xil_printf("User selected PWM signal generation with 1 Hz frequency.\r\n");
else
xil_printf("User selected PWM signal generation with 3.5 Hz frequency.\r\n");
}
prev_sw0 = sw0;

// Turn on the LED


XGpioPs_WritePin(&GpioLeds, ps_sw2_a, 0x1);

do // Pause
{
current_time = XTtcPs_GetCounterValue(Timer);
}
while(current_time<threshold);

// Turn off the LED


XGpioPs_WritePin(&GpioLeds, ps_sw2_a, 0x0);

do // Pause
{
current_time = XTtcPs_GetCounterValue(Timer);
}
while(current_time<end_time);

// Reset the timer


XTtcPs_ResetCounterValue(Timer);

count_depth ++;
if (count_depth == COUNT_DEPTH_END)
count_depth = 0;

threshold = scaling_factor * sine_ampl[count_depth];


// We must multiply current amplitude value of the sine signal with the scaling_factor
// (48640/4096=11) to "stretch" the range from (0 - 2^width(=4096)) to (0 - 48640)
}

return 0;
}

modulator.h:

54 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

#ifndef MODULATOR_H_
#define MODULATOR_H_

#include "math.h"

/********************** Constant Definitions ***********************/


#define LED_CHANNEL 1 // GPIO channel (1 or 2) to operate on
#define SWITCH_CHANNEL 2 // GPIO channel (1 or 2) to operate on
#define AXI_TIMER_0 0 // timer counter of the device to operate on

#define SYS_CLK_MHZ 100 // 100 MHz system clock


#define CLOCK_RATE 1000000 * SYS_CLK_MHZ // system clock value, expressed in Hz
// CLOCK_RATE = 100000000 Hz (= 100 MHz)

#define F_LOW 1.0 // F_LOW = 1 Hz


#define F_HIGH 3.5 // F_HIGH = 3.5 Hz

#define DEPTH 8 // the number of samples in one period of the signal (2^8=256)
#define WIDTH 12 // the number of bits used to represent amplitude value (2^12=4096)

#define C1 (1 << DEPTH) // 2^DEPTH


#define C2 (1 << (WIDTH-1)) // 2^(WIDTH-1)
#define C3 (1 << WIDTH) // 2^WIDTH

// variable that will be used for calculating, DIV_FACTOR_FREQLOW and DIV_FACTOR_FREQHIGH


// C = SYS_CLK_MHZ / (2^DEPTH * 2^WIDTH) = 95.3674
#define C roundf ((float)CLOCK_RATE / (float)(C1 * C3))

// input clock division factor, when sw0 = 0 (F_LOW = 1 Hz)


// DIV_FACTOR_FREQLOW = (C / F_LOW) * 2^WIDTH = 389120
#define DIV_FACTOR_FREQLOW (roundf((float)C / (float)F_LOW)) * C3;

// input clock division factor, when sw0 = 1 (F_HIGH = 3.5 Hz)


// DIV_FACTOR_FREQHIGH = (C / F_HIGH) * 2^WIDTH = 110592
#define DIV_FACTOR_FREQHIGH (roundf((float)C / (float)F_HIGH)) * C3;

// END_TIME_0 is timer threshold value, when sw0 = 0 (F_LOW = 1 Hz)


#define END_TIME_0 DIV_FACTOR_FREQLOW
// END_TIME_1 is timer threshold value, when sw0 = 1 (F_HIGH = 3.5 Hz)
#define END_TIME_1 DIV_FACTOR_FREQHIGH

# define SCALING_FACTOR_0 roundf ((float)C / (float)F_LOW) // SCALING_FACTOR_0 = C / F_LOW (=95)


# define SCALING_FACTOR_1 roundf ((float)C / (float)F_HIGH) // SCALING_FACTOR_1 = C / F_HIGH (=27)

#define SWITCH_POS 0x01 // mask to select switch position


#define LED_POS 0x01 // mask to select led position

#define COUNT_DEPTH_END 1 << DEPTH // final threshold value for the depth counter (2^8=256)

void init_sin_f (unsigned int *sine_ampl);

#endif /* MODULATOR_H_ */

init_sin.c:

#include "modulator.h"

void init_sin_f (unsigned int *sine_ampl)


{
int i;
float pi = 4.0*atan(1.0); // pi=3.14...

for(i=0; i<256; i++)


//sine_ampl[i] = sin(2*pi*i/pow(2,depth)) * (pow(2,width-1)-1) + pow(2.0,width-1)-1;
sine_ampl[i] = (sin(2*pi*i/C1) * (C2-1) + C2 - 1); // [sin(2*pi*i/N)*(2^(width-1)-1)] + [2^(width-1)1], N = 2^depth
}

Additional Steps

In case of ARM-based design following additional steps must be performed:

- Select modulator_sozius_no_intc application project, right-click on it and choose C/C++ Build Set-
tings option.

- In the Properties for modulator_sozius_no_intc dialog box choose C/C++ Build -> Settings
option.

- In the Settings dialog box, select Tool Settings tab and under the ARM v7 gcc linker select Libraries
option.

www.so-logic.net 2023/01/10 55
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

- In the Libraries (-l) window click on the Add... icon.

- In the Enter Value dialog box, type m and click OK to add math library in the Libraries list.

Figure 3.25: Adding math library to Libraries list

- In the Properties for modulator_sozius_no_intc diloag box, click Apply and Close.

Figure 3.26: Added math library to Libraries list

As you can see from the source code above, we have used a lot of dierent functions.

ˆ For LEDs and swithes, we used:

 XGpioPs_LookupCong (u16 DeviceId)


 XGpioPs_CfgInitialize (XGpioPs* InstancePtr, XGpioPs_Cong* Cong,
UINTPTR EectiveAddr)

 XGpioPs_SetDirectionPin (XGpioPs* InstancePtr, u32 Pin, u32 Direction)


 XGpioPs_SetOutputEnablePin (XGpioPs* InstancePtr, u32 Pin, u32 OpEnable)
 XGpioPs_WritePin (XGpioPs* InstancePtr, u32 Pin, u32 Data)
 XGpioPs_ReadPin (XGpioPs* InstancePtr, u32 Pin)

56 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

ˆ For Triple Timer Counter (TTC), we used:

 XTtcPs_LookupCong (u16 DeviceId)

 XTtcPs_CfgInitialize (XTtcPs* InstancePtr, XTtcPs_Cong* CongPtr, u32 EectiveAddr)

 XTtcPs_SetOptions (XTtcPs* InstancePtr, u32 Options)

 XTtcPs_SetInterval (InstancePtr, Value) InstWriteReg((InstancePtr),


XTTCPS_INTERVAL_VAL_OFFSET, (Value))

 XTtcPs_SetPrescalar (XTtcPs* InstancePtr, u8 PrescalerValue)

 XTtcPs_Stop (InstancePtr)

 XTtcPs_Start (InstancePtr)

 XTtcPs_GetCounterValue (InstancePtr) (u16)InstReadReg((InstancePtr),


XTTCPS_COUNT_VALUE_OFFSET)

 XTtcPs_ResetCounterValue (InstancePtr)

All of these functions and it's denitions and explanations, you can nd in the Xilinx directory:

Xilinx / Vitis / 2022.2 / data / embeddedsw / XilinxProcessorIPLib / drivers / gpiops_v... (or


ttcps_v... or some other peripheral) / doc / html / api / xgpiops_8h.html (or xttcps_8h.html).

There, you can nd a plenty of dierent functions that you can use in your software design. In this tutorial we
have represent just those functions that we have used in our software design. Here are some of them:

ˆ XGpioPs_LookupCong (u16 DeviceId)

#include <xgpiops.h>

This function looks for the device conguration based on the unique device ID.

The table XGpioPs_CongTable[] contains the conguration information for each device in the system.

Parameters:

 DeviceId - is the unique device ID of the device being looked up.

Returns: A pointer to the conguration table entry corresponding to the given device ID, or NULL if no
match is found.

Note: None.

ˆ XGpioPs_CfgInitialize (XGpioPs* InstancePtr, XGpioPs_Cong* Cong,


UINTPTR EectiveAddr)

#include <xgpiops.c>

Initialize the XGpioPs instance provided by the caller based on the given conguration data.

Nothing is done except to initialize the InstancePtr.

Parameters:

 InstancePtr - is a pointer to an XGpioPs instance. The memory the pointer references must be
pre-allocated by the caller. Further calls to manipulate the driver through the XGpioPs API must
be made with this pointer.

 Cong - is a reference to a structure containing information about a specic GPIO device. This
function initializes an InstancePtr object for a specic device specied by the contents of Cong.
This function can initialize multiple instance objects with the use of multiple calls giving dierent
Cong information on each call.

www.so-logic.net 2023/01/10 57
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

 EectiveAddr - is the device base address in the virtual memory address space. The caller is responsi-
ble for keeping the address mapping from EectiveAddr to the device physical base address unchanged
once this function is invoked. Unexpected errors may occur if the address mapping changes after this
function is called. If address translation is not used, use Cong->BaseAddress for this parameters,
passing the physical address instead.

Returns: XST_SUCCESS if the initialization is successfull.

Note: None.

Referenced by XGpioPs_Initialize().

ˆ XGpioPs_SetDirectionPin (XGpioPs* InstancePtr, u32 Pin, u32 Direction)

#include <xgpiops.c>

Set the Direction of the specied pin.

Parameters:

 InstancePtr - is a pointer to the XGpioPs instance. Further calls to manipulate the driver through
the XGpioPs API must be made with this pointer.

 Pin - is the pin number to which the Data is to be written. Valid values are 0-117 in Zynq and 0-173
in Zynq Ultrascale+ MP.

 Direction - is the direction to be set for the specied pin. Valid values are 0 for Input Direction, 1
for Output Direction.

Returns: None.

Note: None.

References: XGpioPs::IsReady.

ˆ XGpioPs_SetOutputEnablePin (XGpioPs* InstancePtr, u32 Pin, u32 OpEnable)

#include <xgpiops.c>

Set the Output Enable of the specied pin.

Parameters:

 InstancePtr - is a pointer to the XGpioPs instance. Further calls to manipulate the driver through
the XGpioPs API must be made with this pointer.

 Pin - is the pin number to which the Data is to be written. Valid values are 0-117 in Zynq and 0-173
in Zynq Ultrascale+ MP.

 OpEnable - species whether the Output Enable for the specied pin should be enabled. Valid values
are 0 for Disabling Output Enable, 1 for Enabling Output Enable.

Returns: None.

Note: None.

References: XGpioPs::IsReady.

ˆ XGpioPs_WritePin (XGpioPs* InstancePtr, u32 Pin, u32 Data)

58 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

#include <xgpiops.c>

Write data to the specied pin.

Parameters:

 InstancePtr - is a pointer to the XGpioPs instance. Further calls to manipulate the driver through
the XGpioPs API must be made with this pointer.

 Pin - is the pin number to which the Data is to be written. Valid values are 0-117 in Zynq and 0-173
in Zynq Ultrascale+ MP.

 Data - is the data to be written to the specied pin (0 or 1).

Returns: None.

Note: This function does a masked write to the specied pin of the specied GPIO bank. The pre-
vious state of other pins is maintained.

References: XGpioPs::IsReady.

ˆ XGpioPs_ReadPin (XGpioPs* InstancePtr, u32 Pin)

#include <xgpiops.c>

Read Data from the specied pin.

Parameters:

 InstancePtr - is a pointer to the XGpioPs instance. Further calls to manipulate the driver through
the XGpioPs API must be made with this pointer.

 Pin - is the pin number for which the data has to be read. Valid values are 0-117 in Zynq and 0-173
in Zynq Ultrascale+ MP. See xgpiops.h for the mapping of the pin numbers in the banks.

Returns: Current value of the Pin (0 or 1).

Note: This function is used for reading the state of the specied GPIO pin.

References: XGpioPs::IsReady.

ˆ XTtcPs_LookupCong (u16 DeviceId)

#include <xttcps.h>

Looks up the device conguration based on the unique device ID.

A table contains the conguration info for each device in the system.

Parameters:

 DeviceId - contains the unique ID of the device.

Returns: A pointer to the conguration found or NULL if the specied device ID was not found. See
XTtcPs_Cong.
xttcps.h for the denition of

Note: None.

ˆ XTtcPs_CfgInitialize (XTtcPs* InstancePtr, XTtcPs_Cong* CongPtr, u32 EectiveAddr)

www.so-logic.net 2023/01/10 59
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

#include <xttcps.c>

Initializes a specic XTtcPs instance such that the driver is ready to use.

This function initializes a single timer counter in the triple timer counter function block.

The state of the device after initialization is:

 Overow Mode

 Internal (pclk) selected

 Counter disabled

 All Interrupts disabled

 Output waveforms disabled

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

 CongPtr - is a reference to a structure containing information about a specic TTC device.

 EectiveAddr - is the device base address in the virtual memory address space. The caller is re-
sponsible for keeping the address mapping from EectiveAddr to the device physical base address
unchanged once this function is invoked. Unexpected errors may occur if the address mapping changes
after this function is called. If address translation is not used, then use CongPtr->BaseAddress for
this parameter, passing the physical address instead.

Returns:

 XST_SUCCESS if the initialization is successful.


 XST_DEVICE_IS_STARTED if the device is started. It must be stopped to re-initialize.

Note: Device has to be stopped rst to call this function to initialize it.

References: XTtcPs_Cong::BaseAddress, XTtcPs::Cong, XTtcPs_Cong::DeviceId,


XTtcPs_Cong::InputClockHz, XTtcPs::IsReady, XTTCPS_CLK_CNTRL_OFFSET,
XTTCPS_CNT_CNTRL_OFFSET, XTTCPS_CNT_CNTRL_RESET_VALUE,
XTTCPS_IER_OFFSET, XTTCPS_INTERVAL_VAL_OFFSET, XTTCPS_ISR_OFFSET,
XTtcPs_IsStarted, XTTCPS_IXR_ALL_MASK, XTTCPS_MATCH_1_OFFSET,
XTTCPS_MATCH_2_OFFSET, XTtcPs_ResetCounterValue, XTtcPs_Stop, and
XTtcPs_WriteReg.

ˆ XTtcPs_SetOptions (XTtcPs* InstancePtr, u32 Options)

#include <xttcps.h>

This function sets the options for the TTC device.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

 Options - contains the specied options to be set. This is a bit mask where a 1 means to turn the
option on, and a 0 means to turn the option o. One or more bit values may be contained in the
mask. See the bit denitions named XTTCPS_*_OPTION in the le xttcps.h.

Returns:

 XST_SUCCESS if options are successfully set.


 XST_FAILURE if any of the options are unknown.

60 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Note: None.

References: XTtcPs_Cong::BaseAddress, XTtcPs::Cong, XTtcPs::IsReady,


XTTCPS_CLK_CNTRL_OFFSET, XTTCPS_CNT_CNTRL_OFFSET, and XTtcPs_ReadReg.

ˆ XTtcPs_SetInterval (InstancePtr, Value) InstWriteReg((InstancePtr),


XTTCPS_INTERVAL_VAL_OFFSET, (Value))

#include <xttcps.h>

This function sets the interval value to be used in interval mode.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

 Value - is the 16-bit value to be set in the interval register.

Returns: None.

Note: C-style signature: void XTtcPs_SetInterval(XTtcPs *InstancePtr, u16 Value).

ˆ XTtcPs_SetPrescalar (XTtcPs* InstancePtr, u8 PrescalerValue)

#include <xttcps.c>

This function sets the prescaler enable bit and if needed sets the prescaler bits in the control regis-
ter.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

 PrescalerValue - is a number from 0-16 that sets the prescaler to use. If the parameter is
0 - 15, use a prescaler on the clock of 2( P rescalerV alue + 1), or 2-65536. If the parameter
is XTTCPS_CLK_CNTRL_PS_DISABLE, do not use a prescaler.

Returns: None.

Note: None.

References: XTtcPs_Cong::BaseAddress, XTtcPs::Cong, XTtcPs::IsReady,


XTTCPS_CLK_CNTRL_OFFSET, XTTCPS_CLK_CNTRL_PS_DISABLE,
XTTCPS_CLK_CNTRL_PS_EN_MASK, XTTCPS_CLK_CNTRL_PS_VAL_MASK,
XTTCPS_CLK_CNTRL_PS_VAL_SHIFT, XTtcPs_ReadReg, and XTtcPs_WriteReg..

ˆ XTtcPs_Stop (InstancePtr)

#include <xttcps.h>

Value: InstWriteReg((InstancePtr), XTTCPS_CNT_CNTRL_OFFSET, \


(InstReadReg((InstancePtr), XTTCPS_CNT_CNTRL_OFFSET) | \
XTTCPS_CNT_CNTRL_DIS_MASK))

This function stops the counter/timer.

This macro may be called at any time to stop the counter. The counter holds the last value until it
is reset, restarted or enabled.

www.so-logic.net 2023/01/10 61
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

Returns: None.

Note: C-style signature: void XTtcPs_Stop(XTtcPs *InstancePtr).

Referenced by XTtcPs_CfgInitialize().

ˆ XTtcPs_Start (InstancePtr)

#include <xttcps.h>

Value: InstWriteReg((InstancePtr), XTTCPS_CNT_CNTRL_OFFSET, \


(InstReadReg((InstancePtr), XTTCPS_CNT_CNTRL_OFFSET) & \
∼XTTCPS_CNT_CNTRL_DIS_MASK))

This function starts the counter/timer without resetting the counter value.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

Returns: None.

Note: C-style signature: void XTtcPs_Start(XTtcPs *InstancePtr).

ˆ XTtcPs_GetCounterValue (InstancePtr) (u16)InstReadReg((InstancePtr),


XTTCPS_COUNT_VALUE_OFFSET)

#include <xttcps.h>

This function returns the current 16-bit counter value.

It may be called at any time.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

Returns: zynq:16 bit counter value. zynq ultrascale+mpsoc:32 bit counter value.

Note: C-style signature: zynq: u16 XTtcPs_GetCounterValue(XTtcPs *InstancePtr) zynq


ultrascale+mpsoc: u32 XTtcPs_GetCounterValue(XTtcPs *InstancePtr).

ˆ XTtcPs_ResetCounterValue (InstancePtr)

#include <xttcps.h>

Value: InstWriteReg((InstancePtr), XTTCPS_CNT_CNTRL_OFFSET, \


(InstReadReg((InstancePtr), XTTCPS_CNT_CNTRL_OFFSET) | \
(u32)XTTCPS_CNT_CNTRL_RST_MASK))

This macro resets the count register.

It may be called at any time. The counter is reset to either 0 or 0xFFFF, or the interval value, depending

62 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

on the increment/decrement mode. The state of the counter, as started or stopped, is not aected by
calling reset.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

Returns: None.

Note: C-style signature: void XTtcPs_ResetCounterValue(XTtcPs *InstancePtr).

Referenced by XTtcPs_CfgInitialize().

Modulator Design with Interrupt Controller

Source les that will be created for modulator_sozius_intc application project are:

ˆ modulator_sozius_intc.c
ˆ modulator.h
ˆ init_sin.c

modulator.h and init_sin.c source les are the same, only modulator sozius_intc.c source le is dierent.

Create Necessary Source Files for modulator_sozius_intc Application Project

To create source les necessary for modulator_sozius_intc application project, please do the following:

- Expand modulator_sozius_intc application project in Project Explorer and src folder should appear.
In the src folder you should nd your source code after creation.

- Repeat steps from slides 30, 31, 32.

Note: In the step 3 use modulator_sozius_intc.c as the le name for the C source code le.

modulator.h - this le is identical with the modulator.h le used in the design without interrupt controller.

init_sin.c - this le is identical with the init_sin.c le used in the design without interrupt controller.

- Final step requires adding a math library to the ARM v7 gcc linker library settings. To include math library,
please repeat steps from the end of the previous "Modulator design without interrupt controller" section, from
the slides 42, 43, 44.

modulator_sozius_intc.c:

#include "xparameters.h"
#include "xparameters_ps.h"
#include "xgpiops.h"
#include "xstatus.h"
#include "xttcps.h"
#include "xscugic.h"
#include "xil_exception.h"
#include "modulator.h"
#include "xil_printf.h"

#define TTC_DEVICE_ID XPAR_XTTCPS_0_DEVICE_ID


#define TTC_INTR_ID XPAR_XTTCPS_0_INTR

// Definitions of actual pin locations for LED and push-button on the socius board

www.so-logic.net 2023/01/10 63
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

#define ps_sw2_a 10
#define ps_sw2_b 11
#define ps_sw3_a 12

// New type definition that will hold all relevant configuration parameters for the TTC module
typedef struct {
u32 OutputHz; /* Output frequency */
u16 Interval; /* Interval value */
u8 Prescaler; /* Prescaler value */
u16 Options; /* Option settings */
} TmrCntrSetup;

// Instance of a TmrCntrSetup type, holding TTC0 configuration parameters that will be used in modulator example
static TmrCntrSetup SettingsTable[1] = {
{10, 65000, 2, 0}, /* Ticker timer counter initial setup, only output freq */
};

// global variables necessary for the Global Interrupt Controller initialization


XScuGic INTCInst;
XScuGic_Config *IntcConfig;

static int interrupt_occurred = 0; // variable which will signal when that interrupt has occurred

// Interrupt handler for the timer


void Timer_InterruptHandler(void *CallBackRef)
{
u32 StatusEvent;

interrupt_occurred = 1;

// stop timer
XTtcPs_Stop((XTtcPs *)CallBackRef);
StatusEvent = XTtcPs_GetInterruptStatus((XTtcPs *)CallBackRef);
XTtcPs_ClearInterruptStatus((XTtcPs *)CallBackRef, StatusEvent);
}

// Interrupt system initialization function


int IntcInitFunction(u16 DeviceId, XTtcPs *TtcPsInt)
{
int status;

// Interrupt controller initialization


IntcConfig = XScuGic_LookupConfig(DeviceId);
status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
if(status != XST_SUCCESS) return XST_FAILURE;

// Connect the interrupt controller interrupt handler to the hardware interrupt


// handling logic in the ARM
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
&INTCInst);

// Enable interrupt controller


Xil_ExceptionEnable();

// Connect timer interrupt to device driver handler


status = XScuGic_Connect(&INTCInst,
TTC_INTR_ID,
(Xil_ExceptionHandler)Timer_InterruptHandler,
(void *)TtcPsInt);
if(status != XST_SUCCESS) return XST_FAILURE;

// Enable timer interrupt in the interrupt controller


XScuGic_Enable(&INTCInst, TTC_INTR_ID);

// Enable timer interrupt generation in the timer module


XTtcPs_EnableInterrupts(TtcPsInt, XTTCPS_IXR_ALL_MASK);

return XST_SUCCESS;
}

int main(void)
{
/*********************** Variable Definitions ********************/
XGpioPs GpioLeds; // XGPIO instance that will be used to work with LED
XGpioPs_Config *GPIOConfigPtr;

XTtcPs_Config *Config; // Pointer to a XTtcPs_Config type


XTtcPs TimerInst; // TIMER instance
XTtcPs *Timer; // Pointer to a XTtcPs type
TmrCntrSetup *TimerSetup; // Pointer to a TmrCntrSetup type

int count_depth = 0; // Counter for sine samples


int sw0, prev_sw0; // Switch used for selecting frequency
int scaling_factor; // Will be used to represent the SCALING_FACTOR_0 or SCALING_FACTOR_1 value

unsigned int end_time; // Will be used to represent the END_TIME_0 or END_TIME_1 value
unsigned int threshold; // Will be used to represent the current value of the sine signal

unsigned int sine_ampl[COUNT_DEPTH_END]; // Sine amplitude values that will be used to generate the PWM signal

64 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

int led_state; // Current state of the LED


int reset_value_0; // Is the value for timer to now from which value will start counting downwards
// reset_value_0 = end_time - threshold
int reset_value_1; // Is the value for timer to now from which value will start counting downwards
// reset_value_1 = threshold

int status; // Interrupt initialization function return status variable

/*********************** Initialization **************************/


xil_printf("Initializing peripherals!\r\n");

xil_printf("Initializing LEDs!\r\n");
// LEDs initialization
GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
XGpioPs_CfgInitialize(&GpioLeds, GPIOConfigPtr, GPIOConfigPtr ->BaseAddr);
// On the socius board we must properly control both ends of a LED, hence we must use two GPIO ports
XGpioPs_SetDirectionPin(&GpioLeds, ps_sw2_a, 1);
XGpioPs_SetOutputEnablePin(&GpioLeds, ps_sw2_a, 1);
XGpioPs_SetDirectionPin(&GpioLeds, ps_sw2_b, 1);
XGpioPs_SetOutputEnablePin(&GpioLeds, ps_sw2_b, 1);
// Set the value of one end of LED to always be equal to zero, by changing the other end we will turn it on and off
XGpioPs_WritePin(&GpioLeds, ps_sw2_b, 0x0);

xil_printf("Initializing SWITCHes!\r\n");
// SWITCHes initialization
// Set the direction of the GPIO port connected to the push button to INPUT
XGpioPs_SetDirectionPin(&GpioLeds, ps_sw3_a, 0);

xil_printf("Initializing TIMER!\r\n");
// TIMER initialization
TimerSetup = &SettingsTable[TTC_DEVICE_ID];

// Read the current configuration of the timer module


Config = XTtcPs_LookupConfig(TTC_DEVICE_ID);

// Store the current configuration of the timer in a TimerInst object, using a pointer to access it
Timer = &TimerInst;
Timer->Config = *Config;

// Stop the timer


XTtcPs_Stop(Timer);

// Initialize the timer with the required configuration parameters


XTtcPs_CfgInitialize(Timer, Config, Config->BaseAddress);
TimerSetup->Options |= (XTTCPS_OPTION_INTERVAL_MODE |
XTTCPS_OPTION_DECREMENT |
XTTCPS_OPTION_WAVE_DISABLE);
XTtcPs_SetOptions(Timer, TimerSetup->Options);
XTtcPs_SetPrescaler(Timer, TimerSetup->Prescaler);

xil_printf("Initializing INTERRUPT CONTROLLER!\r\n");


// INTERRUPT CONTROLLER initialization
status = IntcInitFunction(XPAR_PS7_SCUGIC_0_DEVICE_ID, Timer);
if(status != XST_SUCCESS) return XST_FAILURE;

xil_printf("Initializing sine_ampl array!\r\n");


// sine_ampl array initialization
init_sin_f (sine_ampl);

// Read the switch position


sw0 = XGpioPs_ReadPin (&GpioLeds, ps_sw3_a);

// Check the switch position


if ((sw0 & SWITCH_POS) == 0) // Masking (we want to check the status of SW0 only)
{
end_time = END_TIME_0;
// We must further divide the end_time with the factor of 8, because we use pre-scaler set to value 8
end_time = end_time/8;
// We must further divide the scaling_factor with the factor of 8, because we use pre-scaler set to value 8
scaling_factor = SCALING_FACTOR_0/8;
}
else
{
end_time = END_TIME_1;
end_time = end_time/8;
scaling_factor = SCALING_FACTOR_1/8;
}
threshold = scaling_factor * sine_ampl[count_depth]; // threshold = current amplitude value of the sine signal
// We must multiply current amplitude value of the sine signal with the scaling_factor
// (48640/4096=11) to "stretch" the range from (0 - 2^width(=4096)) to (0 - 48640)

reset_value_0 = end_time - threshold;


reset_value_1 = threshold;

// Set the timer countdown interval


XTtcPs_SetInterval(Timer, reset_value_1);

// Turn on the LED


XGpioPs_WritePin(&GpioLeds, ps_sw2_a, 0x1);

www.so-logic.net 2023/01/10 65
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

// Start the timer


XTtcPs_Start(Timer);

interrupt_occurred = 0; // Interrupt_occurred initialization


led_state = 1; // Set the initial state of the LED

/************************* Main Loop *****************************/


xil_printf("Entering main loop.\r\n");
while(1)
{
// Read the switch position
sw0 = XGpioPs_ReadPin (&GpioLeds, ps_sw3_a);

// Check the switch position


if ((sw0 & SWITCH_POS) == 0) // Masking (we want to check the status of SW0 only)
{
end_time = END_TIME_0;
// We must further divide the end_time with the factor of 8, because we use pre-scaler set to value 8
end_time = end_time/8;
// We must further divide the scaling_factor with the factor of 8, because we use pre-scaler set to value 8
scaling_factor = SCALING_FACTOR_0/8;
}
else
{
end_time = END_TIME_1;
end_time = end_time/8;
scaling_factor = SCALING_FACTOR_1/8;
}

// Send the current system status information to the terminal using UART
if (sw0 != prev_sw0)
{
if ((sw0 & SWITCH_POS) == 0)
xil_printf("User selected PWM signal generation with 1 Hz frequency.\r\n");
else
xil_printf("User selected PWM signal generation with 3.5 Hz frequency.\r\n");
}
prev_sw0 = sw0;

if (interrupt_occurred == 1)
{
interrupt_occurred = 0;

if (led_state == 1)
{
// Write the starting counter value, reset_value_0 = end_time - threshold
XTtcPs_SetInterval(Timer, reset_value_0);

// Turn off the LED


XGpioPs_WritePin(&GpioLeds, ps_sw2_a, 0x0);

// Start the timer


XTtcPs_Start(Timer);

led_state = 0;

count_depth ++;
if (count_depth == COUNT_DEPTH_END)
count_depth = 0;
threshold = scaling_factor * sine_ampl[count_depth];

reset_value_0 = end_time - threshold;


reset_value_1 = threshold;

}
else
{
// Write the starting counter value, reset_value_1 = threshold
XTtcPs_SetInterval(Timer, reset_value_1);

// Turn on the LED


XGpioPs_WritePin(&GpioLeds, ps_sw2_a, 0x1);

// Start the timer


XTtcPs_Start(Timer);

led_state = 1;
}
}
}

return 0;
}

In the modulator_sozius_intc.c source code with the interrupt controller, we have used almost the same

66 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

functions as in the modulator_sozius_no_intc.c source code without using an interrupt controller. Here
are the functions that we have used in our design. Some of them are already explained below the
modulator_sozius_no_intc.c source le.

ˆ For LEDs and swithes, we used:

 XGpioPs_LookupCong (u16 DeviceId)


 XGpioPs_CfgInitialize (XGpioPs* InstancePtr, XGpioPs_Cong* Cong,
UINTPTR EectiveAddr)

 XGpioPs_SetDirectionPin (XGpioPs* InstancePtr, u32 Pin, u32 Direction)


 XGpioPs_SetOutputEnablePin (XGpioPs* InstancePtr, u32 Pin, u32 OpEnable)
 XGpioPs_WritePin (XGpioPs* InstancePtr, u32 Pin, u32 Data)
 XGpioPs_ReadPin (XGpioPs* InstancePtr, u32 Pin)

ˆ For Triple Timer Counter (TTC), we used:

 XTtcPs_LookupCong (u16 DeviceId)


 XTtcPs_CfgInitialize (XTtcPs* InstancePtr, XTtcPs_Cong* CongPtr, u32 EectiveAddr)
 XTtcPs_SetOptions (XTtcPs* InstancePtr, u32 Options)
 XTtcPs_SetInterval (InstancePtr, Value) InstWriteReg((InstancePtr),
XTTCPS_INTERVAL_VAL_OFFSET, (Value))
 XTtcPs_SetPrescalar (XTtcPs* InstancePtr, u8 PrescalerValue)
 XTtcPs_Stop (InstancePtr)
 XTtcPs_Start (InstancePtr)
 XTtcPs_GetCounterValue (InstancePtr) (u16)InstReadReg((InstancePtr),
XTTCPS_COUNT_VALUE_OFFSET)
 XTtcPs_ResetCounterValue (InstancePtr)
 XTtcPs_GetInterruptStatus (InstancePtr) InstReadReg((InstancePtr), XTTCPS_ISR_OFFSET)
 XTtcPs_ClearInterruptStatus (InstancePtr, InterruptMask)
 XTtcPs_EnableInterrupts (InstancePtr, InterruptMask)

ˆ For Interrupt Controller, we used:

 XScuGic_LookupCong (u16 DeviceId)


 XScuGic_CfgInitialize (XScuGic* InstancePtr, XScuGic_Cong* CongPtr, u32 EectiveAddr)
 XScuGic_Connect (XScuGic* InstancePtr, u32 Int_Id, Xil_InterruptHandler Handler,
void* CallBackRef )

 XScuGic_Enable (XScuGic* InstancePtr, u32 Int_Id)

As we already said, all of these functions and it's denitions and explanations, you can nd in the Xilinx
directory:

Xilinx / Vitis / 2022.2 / data / embeddedsw / XilinxProcessorIPLib / drivers / gpiops_v...


(or ttcps_v... or scugic_v or some other peripheral) / doc / html / api / xgpiops_8h.html (or
xttcps_8h.html or xscugic_8h.html).

ˆ XTtcPs_GetInterruptStatus (InstancePtr) InstReadReg((InstancePtr), XTTCPS_ISR_OFFSET)

#include <xttcps.h>

This function reads the interrupt status.

Parameters: InstancePtr - is a pointer to the XTtcPs instance.

Returns: None.

Note: C-style signature: u32 XTtcPs_GetInterruptStatus(XTtcPs *InstancePtr).

www.so-logic.net 2023/01/10 67
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

ˆ XTtcPs_ClearInterruptStatus (InstancePtr, InterruptMask)

#include <xttcps.h>

Value: InstWriteReg((InstancePtr), XTTCPS_ISR_OFFSET, \


(InterruptMask))

This function clears the interrupt status.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

 InterruptMask - denes which interrupt should be cleared. Constants are dened in xttcps_hw.h
as XTTCPS_IXR_*. This is a bit mask, all set bits will be cleared, cleared bits will not be cleared.

Returns: None.

Note: C-style signature: void XTtcPs_ClearInterruptStatus(XTtcPs *InstancePtr,


u32 InterruptMask).

ˆ XTtcPs_EnableInterrupts (InstancePtr, InterruptMask)

#include <xttcps.h>

Value: InstWriteReg((InstancePtr), XTTCPS_IER_OFFSET, \


(InstReadReg((InstancePtr), XTTCPS_IER_OFFSET) | \
(InterruptMask)))

This function enables the interrupts.

Parameters:

 InstancePtr - is a pointer to the XTtcPs instance.

 InterruptMask - denes which interrupt should be enabled. Constants are dened in xttcps_hw.h
as XTTCPS_IXR_*. This is a bit mask, all set bits will be enabled, cleared bits will not be disabled.

Returns: None.

Note: C-style signature: void XTtcPs_EnableInterrupts(XTtcPs *InstancePtr,


u32 InterruptMask).

If you dive deeper into the modulator_socius_intc.c source code, you can nottice that additional code has
been included before the main. The function IntcInitFunction (u16 DeviceId, XtmrCtr *TmrInstan-
cePtr) is necessary and contains additional code to:

ˆ initialize interrupt controller

ˆ connect the interrupt controller interrupt handler to the hardware interrupt handling logic in the ARM
processor

ˆ enable the interrupt controller

ˆ connect a timer device driver handler that will be called when an interrupt for the timer occurs. This
device driver handler performs the specic interrupt processing for the device.

ˆ Enable timer interrupt in the interrupt controller

As you can see from the code above, in the IntcInitFunction (u16 DeviceId, XtmrCtr *TmrInstancePtr)
denition we have used some new functions:

68 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

ˆ XScuGic_LookupCong (u16 DeviceId)

#include <xscugic.h>

Looks up the device conguration based on the unique device ID.

A table contains the conguration info for each device in the system.

Parameters:

 DeviceId - is the unique identier for a device.

Returns: A pointer to the XScuGic conguration structure for the specied device, or NULL if the
device was not found.

Note: None.

Referenced by ScuGicExample().

ˆ XScuGic_CfgInitialize (XScuGic* InstancePtr, XScuGic_Cong* CongPtr, u32 EectiveAddr)

#include <xscugic.c>

CfgInitialize a specic interrupt controller instance/driver.

The initialization entails:

 Initialize elds of the XScuGic structure


 Initial vector table with stub function calls
 All interrupt sources are disabled

Parameters:

 InstancePtr - is a pointer to the XScuGic instance.


 CongPtr - is a pointer to a cong table for the particular device this driver is associated with.
 EectiveAddr - is the device base address in the virtual memory address space. The caller is responsi-
ble for keeping the address mapping from EectiveAddr to the device physical base address unchanged
once this function is invoked. Unexpected errors may occur if the address mapping changes after this
function is called. If address translation is not used, use Cong->BaseAddress for this parameters,
passing the physical address instead.

Returns: XST_SUCCESS if initialization was successful.

Note: None.

Referenced by ScuGicExample().

ˆ XScuGic_Connect (XScuGic* InstancePtr, u32 Int_Id, Xil_InterruptHandler Handler,


void* CallBackRef )

#include <xscugic.c>

Makes the connection between the Int_Id of the interrupt source and the associated handler that is
to run when the interrupt is recognized.

The argument provided in this call as the Callbackref is used as the argument for the handler when
it is called.

Parameters:

www.so-logic.net 2023/01/10 69
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

 InstancePtr - is a pointer to the XScuGic instance.

 Int_Id - contains the ID of the interrupt source and should be in the range of 0 to
XSCUGIC_MAX_NUM_INTR_INPUTS - 1.

 Handler - to the handler for that interrupt.

 CallBackRef - is the callback reference, usually the instance pointer of the connecting driver.

Returns: XST_SUCCESS if the handler was connected correctly.

Note: WARNING: The handler provided as an argument will overwrite any handler that was previ-
ously connected.

References: XScuGic::Cong, XScuGic_Cong::HandlerTable, and XScuGic::IsReady.

Referenced by ScuGicExample().

ˆ XScuGic_Enable (XScuGic* InstancePtr, u32 Int_Id)

#include <xscugic.c>

Enables the interrupt source provided as the argument Int_Id.

Any pending interrupt condition for the specied Int_Id will occur after this function is called.

Parameters:

 InstancePtr - is a pointer to the XScuGic instance.

 Int_Id - contains the ID of the interrupt source and should be in the range of 0 to
XSCUGIC_MAX_NUM_INTR_INPUTS - 1

Returns: None.

Note: None.

References: XScuGic::IsReady, XScuGic_DistWriteReg, and XSCUGIC_ENABLE_SET_OFFSET.

3.5 Viewing and Conguring Linker Script le


Viewing and Conguring Linker Script File

A linker is a program that takes one or more object les (.o) generated by a compiler and combines them into
a single executable (.elf ) le.

70 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.27: Linking and Locating process

Linker program combines all les from the application project into the executable .elf le. This process is
controlled by the Linker Script le.

Elf le is organized by logical section from each object le. Each section is located in a physical memory space
as dened by the linker script. Relocatable symbols are resolved to their physical addresses. Other symbols,
such as those for debugging are also added to the .elf le.

Figure 3.28: Linker and Locator Flows

When the linker executes, it rst combines all of the object sections. Then it resolves addresses and writes LDL
les, see Figure 3.23.

Linker Script

Linker Script controls the linking process. It maps the code and data to a specied memory space, sets the
entry point to the executable, reserve space for the heap and stack, dene the layout and start address of each
section.

www.so-logic.net 2023/01/10 71
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Linker script is required if the design contains a discontinuous memory space. It has it's own language and can
be dicult to write. Because of this, Xilinx provides a Linker Script Generator.

Linker Script will be automatically generated when you create an Xilinx C Project within Xilinx Vitis tool. In
our case it will be in the moment when we have created modulator_socius_no_intc C project.

If you want to view or make some modications to existing linker script le, please do the following:

- In the Vitis IDE ProjectExplorer tab, expand the modulator_sozius_no_intc application project,
select modulator_sozius_no_intc.prj le and select Xilinx -> Generate Linker Script command and
the Linker Scrip dialog box will appear, see Figure 3.24.

Figure 3.29: Generate linker script dialog box

The left side of the dialog box is read-only, except for the Output Script name and project build settings in
the Modify project build settings as follows eld. This region shows all the available memory areas for
the design. You have two choices of how to allocate memory: using the Basic tab or the Advanced tab. Both
perform the same tasks; however, the Basic tab is less granular and treats all types of data as data and all
types of instructions as code. This is often sucient to accomplish most tasks. Use the Advanced tab for
precise allocation of software blocks into various types of memory.

- If you want to modify the default settings for the Linker Script le, make the required modications, click
Generate button and the new linker script le will be created.

3.6 Building Application and Generating ELF File


In a microprocessor-based design an ELF le generated in the Vivado Vitis or in some other software development
tool, can be imported and associated with a block design in the Vivado IDE. A bistream le can be generated
that includes the ELF content from the Vivado IDE and run on target hardware.

To build an executable le for this application, Vivado IDE performs the following actions, see Figure 3.25.

72 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Building Application and Generating ELF File

Figure 3.30: Software Flow

ˆ First, Vitis IDE builds the Board Support Package (BSP) using LibGen tool. In our case it is called
software platform.

ˆ Then, Vitis IDE compiles the application software using platform- specic gcc/g++ compiler.

ˆ At the end, the object les from the application and the BSP are linked together to form the nal
executable le (.elf le). This step is performed by a linker which takes as input a set of object les and
a linker script that species where object les should be placed in memory.

Vitis IDE builds BSP ones, after it's creation. For every source le modication, Vitis IDE will automatically
generate a new .elf le, compiling all source les that are out of date and linking them with the BSP.

The following sections provide an overview of concepts involved in building applications:

Makeles

Compilation of source les into object les is controlled using Makeles. With Vitis IDE, there are two possible
options for Makeles:

1. Managed Make : For Managed Make projects, Vitis IDE automatically creates Makeles. Makeles created
by Vitis IDE typically compile the sources into object les, and nally link the dierent object les into
an executable. In most cases, managed make simply eliminates the job of writing Makeles. This is the
suggested option.

2. Standard Make : If you want ultimate control over the compilation process, use standard make projects.
In this case, you must manually write a Makele with steps to compile and link an application. Using the
standard Make ow hides a number of dependencies from Vitis IDE, so you must follow manual steps for

www.so-logic.net 2023/01/10 73
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

other tasks such as debugging or running the application from within Vitis IDE. Therefore, the Standard
Make ow is not recommended for general use.

Build Congurations

Software developers typically build dierent versions of executables, with dierent settings used to build those
executables. For example, an application that is built for debugging uses a certain set of options (such as
compiler ags and macro denitions), while the same application is built with a dierent set of options for
eventual release to customers. Vitis IDE makes it easier to maintain these dierent proles using the concept
of build congurations.

Each build conguration could customize:

ˆ Compiler Settings: Debug and Optimization levels

ˆ Macros passed for compilation

ˆ Linker Settings

Build Report

When the program nishes building selected conguration, build report will be visible in the Vitis Console
window, including generated code size information.

For example, in case of building modulator_sozius_no_intc conguration ARM microprocessor, code size
report is shown on the following gure.

Figure 3.31: Console window with code size information for the modulator_socius_no_intc build conguration
for ARM-based system

As a part of building process, information on the size of your application will normally be displayed at the end
of the build log of the Console view, as it is shown on the Figure 3.18. Here is the explanation for each column
separately:

ˆ text - shows the size of the code and read-only (constant) data in your application (in decimal).

ˆ data - shows the size of the initialised data in your application (in decimal). Data counted in the "data"
section is not constant, so it will end up in RAM memory. But, because this data is initialised and the
initial value is constant, it will be stored in the FLASH memory. The linker allocates the space for initial
data values in FLASH which are then copied to RAM in the startup code.

ˆ bss - shows the size of uninitialized data in your application (in decimal). bss counts for the uninitialized
data in the RAM which will be initialized to zero value in the startup code.

ˆ dec - total size, "text" + "data" + "bss" (in decimal).

ˆ hex - hexadecimal equivalent of "dec".

Typically,

ˆ the FLASH consumption of your application will be "text" + "data".

ˆ the RAM consumption of your application will be "data"+ "bss".

74 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Remember that the RAM consumption provided by this is only that of global data. It will not include any
memory consumed by application stack and heap when application is actually executing.

Build Report - Comparations

By comparing the sizes of the generated executable les for MicroBlaze and ARM processors, they dier.

This is typical if we compile the same source code targeting dierent processors.

The reason for this is that dierent tool-chains (compilers, linkers, assemblers) are used with dierent processors.

Furthermore, dierent processors have dierent instruction sets and micro-architectures.

All this inevitably leads to generation of dierent sizes of executable les when targeting dierent processors, even if
completely identical source code is used.

When planing to migrate an existing software application to a new processor, careful considerations need to be made
regarding the expected executable le size, which can either increase or decrease compared to existing solution.

The same holds for the execution time of the software application when targeting dierent processor platforms.

Programming Languages in Embedded Systems

Additional aspect that can also inuence the size and speed of the generated software platform is the choice of the
programming language used to specify the software part of an embedded system.

Currently C language dominates embedded software development, but C++ language is increasingly starting to be used
also.

The reason for this is that the C++ is one of the dominant programming languages used in desktop applications, servers
and networks to which an embedded system will typically interface.

Furthermore, there are many additional pros for using C++ language in embedded system development: better support
for multicore programming, use of object oriented programming style, function overloaded, use of templates, etc.

However, when compiled, C++ programs tend to be bigger and slower then equivalent C programs.

In the past this was a major concern, but nowadays, with the availability of matured C++ compilers targeting
embedded systems and with careful usage of advanced C++ language features this no longer needs to be or is
the case. Having said this, this doesn't imply that C language will stop being one of the major programming
languages used for embedded software development in the foreseeable future, especially in the resource- and
time-critical embedded applications.

3.7 Running Application


Running Application

You can run your software application on your hardware platform using Vitis tool. The program will run to
termination.

You can also stop the program at any time of execution.

www.so-logic.net 2023/01/10 75
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.32: Run Workow diagram

The run workow is described in the previous diagram, see Figure 3.27.

The workow is made up of the following components:

ˆ Executable ELF File: To debug your application, you must use a compiled Executable and Linkable
Format (ELF) le.

ˆ Run Conguration: To launch the run session, you must create a run conguration in Vitis IDE. This
conguration captures options required to start a run session, including the executable name, processor
target to run and other information.

ˆ JTAG Settings: In most cases, Vitis IDE can automatically detect the JTAG settings and doesn't
require special settings.

ˆ Run Console: This XMD console view enables you to stop the program execution or terminate the run
session.

You can repeat the cycle of modifying the code, building the executable, and running the program in Vitis IDE.
The program can be run on all supported debug targets.

Before you can run your application, you must generate netlist and bitstream le (if you didn't generate them
after the hardware platform is specied) and download the FPGA's bitstream le to the board.

To generate netlist and bitstream le, go back to the Vivado IDE main window and follow the same steps as it is
explained at the of the Chapter 2.3.1 Create ARM-based hardware platform for Sozius development
board.

3.7.1 Downloading bitstream le


Program FPGA

To download your ARM-based bitstream le to the target board using Vitis IDE tool, connect the additional
USB cable that will be used to provide UART interface, that will be used during system debug, and do the
following steps:

- Select Xilinx -> Program Device from the Vitis main window.

76 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

- The Program FPGA diloag box will appear. The bitstream eld should already be populated with the
Program.
correct bitstream le. Click

Figure 3.33: Program FPGA dialog box

3.7.2 Running Application Project


Running Application Project

- Select target application project and rst start Build Project process by pressing Build Project button.
After building project process is completed press Run project button to run the target application project.

Figure 3.34: Build Project and Run Project buttons

Program Debugging using Terminal Window for ARM-based Design

Debug button to open a Debug section. In the bottom of the Vitis IDE Debug section,
- In the Vitis IDE select
open Vitis Serial Terminal tab and click the green "+" button to connect with the serial port.

Figure 3.35: SDK Terminal window

- In the Connect to serial port dialog box, in the Port eld choose COM4 serial port to connect with and
leave all other parameters unchanged. Click OK.

www.so-logic.net 2023/01/10 77
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.36: Connect to serial port dialog box

After connecting the terminal with the serial port, in the SDK Terminal window you should see notication
about successfully connection.

Figure 3.37: Terminal notication about successful connection to serial port

- When the ZynqPL is successfully congured with the bitstream le, we can now launch our software application
on the Zynq PS:

In the Project Explorer select your application project, right-click on it and select Run As -> Launch on
Hardware (System Debugger) option.

- Open the SDK Terminal window and you should see all the messages sent by software application.

As you can see, before changing switch position, one PWM signal generation frequency is selected.

Figure 3.38: Terminal window with messages sent by software application

- Change the switch position on the development board.

In case of using Sozius development board, press and hold the push button 4 and the terminal will detect
that the second PWM signal generation frequency is selected as we predicted in the software application.

When you release the push button 4, the PWM signal generation frequency will return to the value that was
present before pressing the button.

78 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.39: Terminal window with messages sent by software application after changing the switch position on
the development board

3.8 Application Debugging


Debugging is an integral part of embedded systems development. The debugging process implies testing,
stabilizing, localizing and correcting errors. There are two methods of debugging:

ˆ Hardware debugging via logic probe, logic analyzer, on- circuit emulator, or background debugger

ˆ Software debugging via a debugging instrument

SDK supports software debugging through:

ˆ GDB tools

GDB (GNU Debugger) is a powerful, exible tool that provides a unied graphical interface for debugging
and verifying MicroBlaze processor systems during various development phases. With GDB debugger you
can debug programs written in C and C++.

ˆ Xilinx Microprocessor Debugger (XMD)


 Runs all the hardware debugging tools and communicates with the hardware
 Shell for hardware communication
 Tool command language (Tcl) syntax and command interpreter

ˆ GNU tools
 Communicate with the hardware through XMD

The actual debugger is XMD. GDB is the user interface, or GUI, that talks to XMD through a TCP/IP port
via Tcl commands, see Illustration 3.34.

Figure 3.40: GDB overview

The main purpose of XMD is to attach to the debug hardware interface of the embedded processor, the MicroB-
laze Debug Module (MDM). This is done via an internal JTAG chain facilitated by the BSCAN component on

www.so-logic.net 2023/01/10 79
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

the FPGA. The MDM also oers a JTAG uart feature that will show up as a AXI bus uart peripheral for the
MicroBlaze. XMD provides many services, including download cable connection and control. One of the main
functions of XMD is the debug engine. This engine provides the command interface to the processor debug
hardware via a Tcl script and/or simple command line interface. You could directly debug a program from the
XMD command line console, but this would be a painful process. The GDB debugger provides an easy-to-use
graphical interface that interfaces Tcl with XMD.

3.8.1 Debug Overview


With the Vitis IDE debugger, you can see what is happening to a program while it executes. You can set
breakpoints or watchpoints to stop the processor, step through program execution, view the program variables
and stack, and view the contents of the memory in the system. The Vitis IDE debugger uses the GNU Debugger
(GDB) with Xilinx Microprocessor Debugger (XMD) as the underlying debug engine. It translates each user
interface action into a sequence of GDB commands and processes the output from GDB to display the current
state of the program being debugged. It communicates to the processor on the hardware and Instruction Set
Simulator (ISS) target using XMD.

Figure 3.41: Debug Workow diagram

The workow is made up of the following components:

ˆ Executable ELF File: To debug your application, you must use an Executable and Linkable Format
(ELF) le compiled for debugging. The debug ELF le contains additional debug information for the
debugger to make direct associations between the source code and the binaries generated from that original
source.

ˆ Debug Conguration: In order to launch the debug session, you must create a debug conguration in
Vitis IDE. This conguration captures options required to start a debug session, including the executable
name, processor target to debug, and other information.

ˆ JTAG Settings: When debugging the program on a hardware target, Vitis IDE uses XMD for commu-
nication to the processor using a JTAG interface on the board. The JTAG settings for the debug session
can be specied in the JTAG Settings dialog box. In most cases, the debugger can automatically detect
the JTAG settings and do not need to provide special settings.

ˆ Vitis IDE Debug Perspective: Using the Debug perspective, you can manage the debugging or running
of a program in the Workbench. You can control the execution of your program by setting breakpoints,
suspending launched programs, stepping through your code, and examining the contents of variables.

80 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

You can repeat the cycle of modifying the code, building the executable, and debugging the program in the
Vitis IDE.

Note : If you edit the source code after compiling, the line numbering will be out of step because the debug
information is tied directly to the source. Similarly, debugging optimized binaries can also cause unexpected
jumps in the execution trace.

Hardware debug target

Vitis IDE supports debugging of a program on processor running on a FPGA. All processor architectures are
supported. Vitis IDE communicates to the processor on the FPGA over the JTAG interface using the Xilinx
JTAG cable. Before you debug the processor on the FPGA, you should congure the FPGA with the appropriate
system bitstream.

The debug logic for each processor enables program debugging by controlling the processor execution. The
debug logic on hard ARM processor cores is built in and always available for debugging. However, the debug
logic on soft MicroBlaze processor cores is congurable and can be enabled or disabled by the hardware designer
when building the embedded hardware.

Enabling the debug logic on MicroBlaze processors provides advanced debugging capabilities such as hard-
ware breakpoints, read/write memory watchpoints, safe-mode debugging, and more visibility into MicroBlaze
processors. This is the recommended method of debugging MicroBlaze software.

If the debug logic is disabled on the hardware, you can debug programs using XMDStub (a ROM monitor).
XMDStub is a small debug stub that runs on MicroBlaze processors and can perform basic debug operations
such as reading and writing memory and register values and controlling the program execution. It should be
initialized to the processor local memory at the reset location, so when the processor resets, the XMDStub is
run and ready for debugging. It communicates to XMD over a Universal Asynchronous Receiver-Transmitter
(UART), which could be JTAG-based or RS232-based. This method is not supported in Vitis IDE and you
should use the XMD command-line tool for debugging.

3.8.2 Debug Conguration


To debug, run, and prole an application, you must create a conguration that captures the settings for executing
the application. The congurations for debugging, running, and proling an application are similar.

To setup a debug conguration, do the following:

modulator_sozius_no_intc application project and select Debug


- In the Vitis IDE main window, select
-> Debug Congurations... option, see Figure 3.36.

Figure 3.42: Debug Congurations option

The another way to open Debug Congurations dialog box is to select modulator_sozius_no_intc project
in the Project Explorer window, right-click on it and choose Debug As -> Debug Congurations... option.

Debug Congurations dialog box, you can see that the Vitis IDE tool has automatically created
- In the
Debugger_modulator_sozius_no_intc-Default debug conguration for us, see Figure 3.37.

www.so-logic.net 2023/01/10 81
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.43: Automatically generated debug congurations

- Click Debug.

The ELF le will be downloaded to the FPGA into the bootloop placeholder space in the betstream.

- If the Conrm Perspective Switch dialog box appears, click Yes to switch to the Debug perspective.

You can also switch to this perspective by clicking on the Window -> Open perspective... command, see
Figure 3.39.

Figure 3.44: Open Perspective command

82 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

When the Open Pespective dialog box appears, choose Debug option and click OK, see Figure 3.40.

Figure 3.45: Open Perspective dialog box

3.8.3 Debug Perspective

The Debug perspective lets you manage the debugging or running of a program in the Workbench. You can
control the execution of your program by setting breakpoints, suspending launched programs, stepping through
your code, and examining the content of variables.

The Debug perspective displays the following information:

ˆ Each program running on the processor (represented as a node in the tree)

ˆ The stack frame for the suspended program that you are debugging

The Debug perspective also drives the C/C++ Editor. As you step through your program, the C/C++ Editor
highlights the location of the execution pointer, see Figure 3.41.

- The Debug perspective will open, showing the modulator_sozius_no_intc.c source le in the source view,
various variables dened in the le in the Variables view, Outline view showing the objects which are in the
current scope, thread created and the program suspended in the Debug view. Note that the program operation
is suspended at the rst executable statement (at the main() entry point), see Figure 3.41.

www.so-logic.net 2023/01/10 83
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.46: Vitis Debug Perspective

In the process of debugging the most important task is adding breakpoints to halt program execution at the user
Step Into, Step Over, Step Return
specied points. Once program execution is suspended, user can use
and Resume commands to control program execution from the encountered breakpoint. These commands,
together with variables and memory views, enable user to have total control and overview of the program
execution process during debugging.

Following steps will illustrate how these commands can be used in debug process.

Step Into, Step Over and Step Return commands. As already noted,
- First we will illustrate the usage of
main() function. We can
after debugging process is started, program operation will suspend at the start of the
use Step Into button to enter into the Xilinx provided function for the text printing, xil_printf, in order to
overview the function execution in more details. After you press the Step Into button, debugger will reach the
xil_printf function call within the main() function. After pressing the Step Into button once more, debugger
will automatically jump to the rst executable statement of the xil_printf function as shown on the Figure
3.42.

Figure 3.47: Result of the execution of the Step Into command on the xil_printf function

- While the debugger is working within a function call, you can use Step Return button to execute all
remaining statements within a function in order to quickly return to the point where a function has been called.

84 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

In our case if we press Step Return button once, debugger will execute all remaining statements within the
xil_printf function and return to the main() function, because xil_printf function has been called from the
main() function, and suspend program execution at the next executable statement, which in our case is yet
another function call, this time to the another xil_printf function, as shown on the Figure 3.43.

Figure 3.48: Result of Step Return command execution within xil_printf function

- Although Step Into command can be very useful in the process of program debugging, quite often we are
not interested into details of every function execution. If we would like to skip over known working functions,
because we have debugged them previously, we can use Step Over button that will execute the complete
function in one step, treating C function calls as a single C statement. Please notice that our program that
we are currently debugging has suspended execution at the second xil_printf function call as shown on the
previous gure. If you are not interested in the details of the execution of this function, you can execute it at
once by clicking on the Step Over button. Debugger will now execute all the statements within the xil_printf
function and only then suspend the program execution once more, after reaching the rst executable statement
located after the xil_printf function, in our case this would be another XGpioPs_LookupCong function
call, as shown on the Figure 3.44.

Figure 3.49: Result of the execution of the Step Over command on the second xil_printf function

Breakpoints

Next we will illustrate how to use breakpoints to suspend program execution at the user-selected line of program
code. A breakpoint suspends the execution of a program at the location where the breakpoint is set. By default,
Vitis sets breakpoints at main() and exit() functions. When you start a debug session, the processor stops at
the start of the main() function of the program. There are two types of breakpoints used by the debugger:

ˆ Software Breakpoint - To set a software breakpoint, the debugger modies the program instruction
at the breakpoint address. The debugger does not require any hardware resources for setting a software
breakpoint, so you can essentially set any number of software breakpoints in your debug session. The de-
bugger requires access to read and write to the breakpoint address location. This is the default breakpoint
used by the debugger.

www.so-logic.net 2023/01/10 85
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

ˆ Hardware Breakpoint - To set a hardware breakpoint, the debugger does not require modication of
the program instruction at the breakpoint address. Each processor provides a limited set of hardware
breakpoints. In the case of MicroBlaze processors, this is congurable and set by the hardware developer
and should be used wisely. You should use hardware breakpoints when the debugger cannot read or write
to the program memory, such as when using Flash memory.

- We will place the rst breakpoint withininit_sin_f function, at the line 36, where for loop is located.
init_sin_f function is dened in the init_sin.c source le. First we must select init_sin.c source le by
clicking on the init_sin.c tab. Next, point the mouse to the line 36 in the init_sin.c source le and right-click
on the blue stripe located on the left border of the Sources window. A drop-down menu will appear from which
Add Breakpoint... option should be selected, see Figure 3.45.

Figure 3.50: Add Breakpoint option

- When you select Add Breakpoint... option a Properties for C/C++ Line Breakpoint dialog box will
appear allowing you to specify the properties of the new breakpoint as shown on the Figure 3.46. Since we want
to add a simple breakpoint at this moment, we don't have to change anything, so simply click OK.

Figure 3.51: Properties for C/C++ Line Breakpoint dialog box

86 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

- After you have added a new breakpoint, its location in the program code will be made visible by the blue circle
marker located on the blue stripe just left of the program code line for which the breakpoint was specied, see
Figure 3.47.

Figure 3.52: Breakpoint added

- Please remember that our program execution is currently suspended at line 86 in the modulator_sozius_no_intc.c
source le and that there are several executable statements located between this line and line 126 where call
to the init_sin_f function is located, which contains the breakpoint. These statements need to be executed
before reaching the breakpoint. These executable statements can be executed by pressing the Step Over
button appropriate number of times until we reach line 126. However, this would be a very inecient way of
program debugging. Instead we can use Resume button to quickly execute all executable statements between
our current position and the breakpoint position.

- After we have pressed Resume button, debugger will execute all necessary statements until it reaches a
breakpoint set at line 36 within the init_sin.c source le and then suspend program execution, as show on the
Figure 3.48.

Figure 3.53: Breakpoint reached

- Next we will illustrate how the Memory tab can be used to monitor the content of the array variables. Please
notice that program execution is suspended at line 36. We will use the Monitor tab to overlook this initialization
process. First thing that must done is to determine the base address at which the sine_ampl array is stored
in the memory. To do so, look in the Variables tab for variable with sine_ampl name. Inspect the content of
the Value eld located in the same row. This is the starting address of the sine_ampl array.

www.so-logic.net 2023/01/10 87
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.54: Starting address of the sine_ampl array

- To open Memory tab, select Window -> Show View -> Memory option from the main menu

- In the Memory tab, click on the Add Memory Monitor button. A new dialog box will appear where we
should specify the address or expression to monitor. In our case we will specify the starting address of the
sine_ampl array, 0x0010c024, see Figure 3.50. After you do so, click OK.

Figure 3.55: Monitor Memory dialog box

- In the Memory tab, please notice that a new Memory monitor has been added, monitoring the memory
content starting from the address 0x0010c024 as shown on the Figure 3.51. Currently the content of all memory
locations staring from the address 0x0010c024 is 0x00000000. This is ne, since we still have not initialized the
sine_ampl array.

Figure 3.56: Content of the sine_ampl array in Memory window before array initialization

- Let us initialize the rst member of the sine_ampl array, with index value 0. Please press Step Over button
once. After the rst step over command, debugger will execute the for statement. Since this is the rst time
i to 0, as specied by the for statement.
this statement is executed it will set the value of the iterator variable
This change of the variable i value is also indicated in the Variables tab, where line holding the variable i is
coloured yellow and holds the new value for the variable i, see Figure 3.52.

88 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.57: Change of i variable value indication in the Variables tab

- Press Step Over button three times more. This time debugger will execute the line of code that initialises
sine_ampl array member with index value i=0. After debugger nishes executing this line of code it will
suspend program execution and update the Memory tab as shown on the Figure 3.53. If you inspect the value
stored at the memory location 0x0010c024, you can see that it has changed from 0x00000000 to 0x000007FF
which is the correct initial value for the sine_ampl[0] array member.

Figure 3.58: Indication of the change of the sine_ampl value in Memory window

- Finally, we will create a conditional breakpoint in order to stop the sine_ampl initialization process after a
specied number of array element have been initialized. Right-click on the blue stripe just left of the line 38
and select Add Breakpoint... option once more. Properties for C/C++ Line Breakpoint dialog box
will appear as before. Since now we would like to place a conditional breakpoint we must specify breakpoint
condition using the Condition eld. In this example we would like to break a program execution when loop
iterator i reaches the value 5. This would mean that debugger should stop sine_ampl array initialization process
after sine_ampl members 0-4 have been initialized. To specify this condition type i==5 in the Condition
eld as show on the Figure 3.54 press OK button to complete the conditional breakpoint setup.

www.so-logic.net 2023/01/10 89
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.59: Properties for C/C++ Line Breakpoint dialog box - condition breakpoint setup i==5

- You can verify that a new conditional breakpoint has been placed at the line 38 which is designated by the
blue circle located on the blue stripe just left to the line 38. Since this breakpoint is conditional breakpoint,
next to the blue circle a question marker is also visible as shown on the Figure 3.55.

Figure 3.60: Conditional breakpoint added

- Remove the existing breakpoint at line 36 by right-clicking on the blue stripe just left of the line 36 and
selecting Toggle Breakpoint option.

- Press Resume button to continue program execution. Debugger will continue initialising sine_ampl array
until it reaches the condition specied in the conditional breakpoint located at line 38. This will happen when
loop iterator i reaches the value of 5. After this condition is met, debugger will suspend program execution and
display the current content of sine_ampl array in the memory tab as show on the Figure 3.56. Please notice
that sine_ampl members with index values 0-4 have already been initialised to appropriate values, because
memory locations with addresses from 0x0010c024 to 0x0010c034 have values that are dierent from 0.
Since sine_ampl array is an array of unsigned integers, each array member occupies one double word in the
memory. This means that sine_ampl members with index values 0-4 should occupied memory block starting
from 0x0010c024 to 0x0010c034, which is exactly the memory block that has values dierent from 0 as show in
the Memory tab.

90 2023/01/10 www.so-logic.net
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

Figure 3.61: Conditional breakpoint reached, i==5

www.so-logic.net 2023/01/10 91
CHAPTER 3. CREATING THE SOFTWARE PLATFORM USING VITIS

92 2023/01/10 www.so-logic.net
Chapter 4

USING CUSTOM IPS IN EMBEDDED


SYSTEM DESIGN
In this chapter you will learn how to integrate a custom IP within the ARM-based embedded system using
Xilinx Vivado and Vitis tools.

Structure of This Chapter

First we will show how to create a hardware platform that includes a custom IP, in our case it will be PWM
Modulator IP core (modulator_axi_ip_v1.0 ) with AXI-Lite interface.

Next, we will show how to develop a basic driver for PWM Modulator IP core, how to integrate it in the
Xilinx Vitis tool chain, and how to develop an application that will use this driver to communicate with PWM
Modulator IP core.

PWM Modulator IP core (modulator_axi_ip_v1.0 ) was developed and packaged in the sub-chapter 11.2 "Cre-
ating Modulator IP Core with AXI4 Interface" of the Vivado "Basic FPGA Tutorial".

Users not familiar with the details of the PWM Modulator IP core and the process of packaging it to the Vivado
compliant IP core, please refer to the mentioned sub-chapter 11.2 for more details.

Block diagram of the hardware platform that we will create is shown on the Figure 4.1.

93
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.1: Structure of microprocessor-based embedded system, using a custom IP to generate pwm signal

Comparing the Figure 4.1 with the Figure 1.3 it can be seen that the structure of new hardware platform is
simpler. In the new hardware platform only two IP cores are needed: standard Xilinx one-channel GPIO IP core
and an instance of PWM Modulator (modulator_axi_ip_v1.0) custom IP core. Since all the functionality
needed to generate pwm signal is packaged inside PWM Modulator custom IP core, there is no need for timer
and interrupt controller IP cores that are present on Figure 1.3.

About PWM Modulator (modulator_axi_ip_v1.0 ) Custom IP Core

PWM Modulator (modulator_axi_ip_v1.0 ) custom IP core is designed to be fully self-contained pwm generator
module.

It can generate pwm output signal, modulated by the sine signal, with two dierent, user-dened frequencies.

It uses an AXI-Lite interface to connect to the AXI4 internal system bus of any AXI enabled microprocessor.

Operation of the PWM Modulator custom IP core is controlled through the set of three internal 32-bit cong-
uration registers, which are accessed through the AXI-Lite interface:

ˆ the rst register, sel REGISTER, will be used to replace the sel switch from the board

ˆ the second register, inc_freqhigh REGISTER, will be used for storing inc_freqhigh increment values

ˆ the third register, (inc_freqlow REGISTER, will be used for storing inc_freqlow increment values

Table 4.1 shows the internal 32-bit conguration registers address map.

94 2023/01/10 www.so-logic.net
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Table 4.1: Internal Registers Address Map of the Modulator IP Core

Internal Register Name S_AXI_AWADDR Value


"sel" register "0000" (0)
"inc_freqhigh" register "0100" (4)
"inc_freqlow" register "1000" (8)

This information is important for the software developer in order to correctly access, congure and control
PWM Modulator IP core.

Next, we will show how to create a hardware platform, based on the ARM processor, using Vivado IP integrator
tool.

4.1 Using Custom IP in ARM-based Processor System


In order to create a ARM-based embedded system that uses PWM Modulator custom IP core, following steps
need to be performed.

Create New Project

- Create a new modulator_sozius_axi project using Vivado IDE wizard. To create a new project, please
repeat steps 1-7 from the Sub-chapter 2.1 Create a New Project.

Settings command and in the Settings


- In the Vivado Flow Navigator, under the Project Manager, click on the
General window check is the Target language set to VHDL. If it is not, please change it to be VHDL and
click OK.

When we have created a new project, we have to add packaged IP to the IP Catalog. The following steps will
show you how to add packaged IP to the IP Catalog:

Add Packaged IP into the IP Catalog

- Create a new folder, ip_repository, in the same directory where modulator_sozius_axi project is created.
This new folder will be place where we will copy the packaged modulator_axi_ip IP core.

- Copy the packaged modulator_axi_ip IP core to the ip_repository folder and extract it inside the same
folder.

- Then, in the Flow Navigator, under the Project Manager, click on the Settings command.

Settings dialog box, under the Project Settings commands from the left pane, expand IP and select
- In the
Repository option.

Repository Manager lets you add or remove user repositories and establish precedence between repositories.

www.so-logic.net 2023/01/10 95
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.2: Repository manager window

- In the Repository manager window, click + icon to add the desired repository.

- In the IP Repositories window, choose ip_repository folder with packaged modulator_axi_ip IP core
and click Select.

- In the Add Repository dialog box, click OK to add the selected repository (ip_repository with 1 IP) to the
project.

Figure 4.3: Add Repository dialog box

- In the Repository manager window, when ip_repository is added to the IP Repositories section, click
OK.

96 2023/01/10 www.so-logic.net
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.4: Repository manager with selected ip_repository

- In the Flow Navigator, under the Project Manager, click IP Catalog command to verify the presence
of the previously added IP in the IP Catalog.

In the Search eld type the name of your IP core (modulator_axi_ip) and you should nd it under AXI
Peripheral IPs.

Figure 4.5: IP Catalog with added modulator_axi_ip_v1.0 IP core

Now, when we have all the necessary IPs for our design, we will create block design for our project.

The following steps describe how to use the IP Integrator within your project:

www.so-logic.net 2023/01/10 97
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Create ARM-based Hardware Platform for Sozius Development Board

1 - 15 from the Sub-chapter 2.3.1 "Create ARM-based hardware platform for


- First repeat steps
Sozius development board" to create a block design with Zynq PS congured to run on Sozius development
board.

After Vivado has nished with the Tcl script execution, a created block diagram containing Zynq PS will be
visible in the Vivado IDE.

Figure 4.6: Block diagram of Zynq PS congured to run on Sozius board

- The next step will be to add the rest of the necessary IPs into the design canvas. These IPs are located in the
Vivado IP Catalog and IP Integrator gave you a possibility to add them on three ways:

ˆ In the design canvas, right-click and choose Add IP... option, see Figure 4.7, or

Figure 4.7: Add IP option

ˆ Use the Add IP link in the IP Integrator canvas, see Figure 4.8, or

98 2023/01/10 www.so-logic.net
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.8: Add IP link

ˆ on the Add IP button in the IP Integrator sidebar menu, see Figure 4.9.

Figure 4.9: Add IP button

Add modulator_axi_ip IP Core into the Design

- In the design canvas, right-click and choose Add IP... option.

- In the IP Catalog, search for the modulator_axi_ip IP core.

Figure 4.10: modulator_axi_ip_v1.0 IP core in the IP Catalog

- When you nd it, press enter on the keyboard or simply double-click on the modulator_axi_ip_v1.0 core
in the IP Catalog and the selected core will be automatically instantiated into the IP Integrator design canvas.

www.so-logic.net 2023/01/10 99
CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.11: Automatically instantiated modulator_axi_ip_v1.0 core in the IP Integrator design canvas

Re-customize modulator_axi_ip IP Core

- Double-click on the modulator_axi_ip_v1.0 (modulator_axi_ip_0 ) IP core to re-customize it.

- In the modulator_axi_ip_v1.0(1.0) customization window, set:

ˆ Lut Depth G to value 12

ˆ Lut Width G to value 16

ˆ Nco Width G to value 31

- Click OK.

Figure 4.12: Re-customize IP - modulator_axi_ip_v1.0 (1.0) dialog box

100 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

As we already said, in our design we will program PL part of the Zynq FPGA. Since existing LEDs and switches
on the Sozius development board are connected to the PS part of the Zynq FPGA, we have to instantiate
Integrated Logic Analyzer (ILA) and Virtual Input/Output (VIO) cores into our design. All the
detailed information about ILA and VIO cores you can nd in the Chapter 10 "Debugging Design" of the
"Basic FPGA Tutorial" tutorial.

- The next IPs necessary for our design are Binary Counter (c_counter_binary_0 ), ILA (ila_0 ) and VIO
vio_0 IPs. Add all three IPs into the "modulator_sozius_axi" block design as it is shown on the Figure 4.13
and make the following IP customizations.

Add Binary Counter, ILA and VIO IP Core into the Design

Figure 4.13: IP Integrator design canvas with instantiated Counter, ILA and VIO IPs

- Double-click on the Binary Counter (c_counter_binary_0 ) IP and in the Binary Counter (12.0) Re-
customization IP dialog box set the following parameters:

ˆ in the Basic tab:

 set Output Width value to 32, see Figure 4.14 and

www.so-logic.net 2023/01/10 101


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize Binary Counter (c_counter_binary_0 ) IP Core - Basic Tab

Figure 4.14: Binary Counter (12.0) re-customization IP dialog box - Basic tab

ˆ in the Control tab:

 enable Clock Enable (CE) and Synchronous Clear (SCLR) options, see Figure 4.15 and click
OK.

102 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize Binary Counter (c_counter_binary_0 ) IP Core - Control Tab

Figure 4.15: Binary Counter (12.0) re-customization IP dialog box - Control tab

Add Utility Vector Logic (util_vector_logic_0 ) IP Core into the Design

- Because of the structure of the binary counter that we need, we also had to include one invertor into our IP
Integrator design.

- Add Utility Vector Logic (util_vector_logic_0 ) IP into design canvas, double-click on it and re-customize
it.

In the Utility Vector Logic (2.0) dialog box, make the following changes:

ˆ change the C_OPERATION to not and

ˆ set the C_SIZE to be 1, see Figure 4.16, and

ˆ click OK.

www.so-logic.net 2023/01/10 103


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize Utility Vector Logic (util_vector_logic_0 ) IP Core

Figure 4.16: Utility Vector Logic (2.0) re-customization IP dialog box

- Double-click on the ILA IP and in the ILA (Integrated Logic Analzyer (6.2)) dialog box, in the General
Options, set the following parameters:

ˆ select Native as Monitor Type

ˆ set 2 as Number of Probes, and

ˆ enable Capture Control option in the Trigger And Storage Settings section, as it is shown on the
Figure 4.17.

104 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize ILA (ila_0) IP Core - General Options Tab

Figure 4.17: ILA (Integrated Logic Analyzer (6.2)) Re-customize IP dialog box - General Options

and in the Probe Ports(0..7), set the following parameters:

ˆ set 32 bits as Probe Width[1..4096] value of PROBE0 probe, as it is shown on the Figure 4.18, and

ˆ click OK.

www.so-logic.net 2023/01/10 105


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize ILA (ila_0) IP Core - Probe Ports(0..7) Tab

Figure 4.18: ILA (Integrated Logic Analyzer (6.2)) Re-customize IP dialog box - Probe Ports(0..7)

- In case of VIO core, double-click on theVIO IP and in the VIO (Virtual Input/Output (3.0)) dialog
box, in the General Options tab, set the Output Probe Count to be 0, see Figure 4.19. In case of VIO
core we will need only one input probe, to connect it with the modulator_axi_ip_0 pwm_o port.

106 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize VIO (vio_0) IP Core - General Options


Tab

Figure 4.19: VIO (Virtual Input/Output (3.0)) Re-customize IP dialog box - General Options

- In the PROBE_IN Ports(0..0) tab, leave PROBE_IN0 port to be 1-bit width, because pwm_o port is
OK, see Figure 4.20.
also 1-bit wide and click

Re-customize VIO (vio_0) IP Core - PROBE_IN Ports(0..0) Tab

Figure 4.20: VIO (Virtual Input/Output (3.0)) Re-customize IP dialog box - PROBE_IN Ports(0..0)

www.so-logic.net 2023/01/10 107


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Add AXI Protocol Converter (axi_protocol_converter_0) IP Core into the Design

The last IP that we need in our design is AXI Protocol Converter.

- Add the AXI Protocol Converter (axi_protocol_converter_0 ) IP core into the block design.

- In the AXI Protocol Converter (2.1) dialog box, make the following changes:

ˆ change SI PROTOCOL mode from Auto to Manual and

ˆ select AXI3 from the drop-down list of SI PROTOCOLs and

ˆ click OK, see Figure 4.21.

Re-custimize AXI Protocol Converter (axi_protocol_converter_0) IP Core

Figure 4.21: AXI Protocol Converter (2.1) Re-customize IP dialog box

At this stage of the design process, the IP Integrator design canvas should look like as it is shown on the Figure
4.22.

108 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

IP Integrator Design Canvas With All Necessary IPs

Figure 4.22: IP Integrator design canvas with added all the necessary IPs

- In the block design double-click on the ZYNQ7 Processing System IP block.

ZYNQ7 Processing System (5.5) re-customize IP dialog box, select PS-PL Conguration option
- In the
PS-PL Conguration section, expand AXI Non Secure Enablement, then
from the left menu. In the
expand GP Master AXI Interface and enable M AXI GP0 interface option, see Figure 4.23, and click
OK.

www.so-logic.net 2023/01/10 109


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Re-customize ZYNQ7 Processing System IP Core

Figure 4.23: ZYNQ7 Processing System (5.5) Re-customize IP dialog box

We need this extra GPIO port to connect it with AXI Protocol Converter that will be connected with the
modulator_axi_ip IP core.

After we re-customized ZYNQ7 Processing System, the new M_AXI_GP0 port will appear in the ZYNQ IP
block, see Figure 4.24.

After we added all the necessary IPs into our design and after all the necessary IP customizations, the IP
Integrator design canvas should look as it is shown on the Figure 4.24.

110 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

IP Integrator Design Canvas With All Necessary and Re-customized IPs

Figure 4.24: IP Integrator design canvas with all necessary IPs and re-customizations

- Next step will be to manually connect the IPs:

Place the cursor on top of the desired pin and you can notice that the cursor changes into a pencil indicating
that a connection can be made from that pin. Clicking the left mouse button a connection starts. Click and
drag the cursor from one pin to another. You must press and hold down the left mouse button while dragging
the connection from one pin to another. As you drag the connection wire, a green checkmark appears on the
port indicating that a valid connection can be made between these points. The Vivado IP Integrator highlights
all possible connections points in the subsystem design as you interactively wire the pins and ports. Release the
left mouse button and Vivado IP integrator makes connection between desired ports. Repeat this procedure
until all the pins become associated.

Note : Connect all the IPs on the same way as it is shown on the Figure 4.25. As you can see we connected all
the IPs, except clock ports and reset ports.

www.so-logic.net 2023/01/10 111


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Manually Connect the IPs

Figure 4.25: IP Integrator design canvas with manually connected IPs

Run Connection Automation

- In the IP Integrator window, click the Run Connection Automation link and the list of the ports/interfaces
that can use the Connection Automation feature will show up.

Run Connection Automation link is IP Integrator feature that assist you in putting together a basic
microprocessor system, making internal connections between dierent blocks and making connections to external
interfaces.

- In the Run Connection Automation dialog box enable All Automation (6 out of 6 selected) and click
OK.

Figure 4.26: Run Connection Automation dialog box

112 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

After Running Connection Automation

After running the connection automation, the connections will be made and highlighted in the IP Integrator
design canvas.

Figure 4.27: IP Integrator design canvas after running connection automation

Create Address Map for Your Embedded System

- Click on the Address Editor tab, beside Diagram tab, to open the Address Editor window.

- In the Address Editor window, expand sozius_xz_lab_ps, then expand Data, after that expand Un-
mapped Slaves and right-click on the S00_AXI and choose Assign option to assign some memory space for
S00_AXI of the modulator_axi_ip_0 IP core.

Figure 4.28: Assign Address command

Validate Design

- From the toolbar menu of the design canvas, run the IP subsystem design rule checks by clicking the Validate
Design button.

www.so-logic.net 2023/01/10 113


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.29: Validate Design button from the main toolbar menu

Alternatively, you can do the same by selecting Tools -> Validate Design from the main menu

Figure 4.30: Validate Design option from the main menu

- In the Validate Design dialog box, click OK, see Figure 4.31.

Figure 4.31: Validate Design dialog box

Save Block Design

- At this point, you should save the IP integrator design.

Use the File -> Save Block Design command from the main menu to save the design.

Synthesize and Implement Design, Generate Bitstream File and Program Target Device

- Synthesize your design using Run Synthesis command from the Flow Navigator / Synthesis section.

- Implement your design using Run Implementation command from the Flow Navigator / Implementa-
tion section.

- Generate bitstream le using Generate Bitstream command from the Flow Navigator / Program and
Debug section.

- Program FPGA device using Open Hardware Manager command from the Flow Navigator / Program
and Debug section.

To complete the design, we must now create a software component for our embedded system. This application
specic software will be executed on the ARM processor that is already part of our hardware platform.

114 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Create Software Component for Your Embedded System

- The rst step in software creation is to export the hardware design into the Vitis tool. To export your hardware
platform into the Vitis tool and to launch Vitis IDE, please repeat steps 1 - 7 from the Chapter 3 "Creating
the Software Platform using Vitis".

modulator_sozius platform project, repeat steps from the Sub-chapter 3.1 "Create a Plat-
- To create
form Project".

modulator_sozius_axi application project, repeat steps from the Sub-chapter 3.3 "Create a
- To create
Application Project".

- In the modulator_sozius_axi application project, create modulator_sozius_axi.c and


modulator_sozius_axi.h source les on the same way as it is explained in the Sub-chapter 3.4 "Creating
a C/C++ Source Files for Sozius Board Based Hardware Platform".

The complete modulator_sozius_axi.c and modulator_sozius_axi.h source les you can nd in the text
below.

modulator_sozius_axi.c

#include "xparameters.h"
#include "xgpiops.h"
#include "xstatus.h"
#include "math.h"
#include "modulator_sozius_axi.h"

int main(void)
{
/*********************** Variable Definitions ********************/
XGpioPs GpioSwitches; // XGPIO instance that will be used to work with SWITCHes
XGpioPs_Config *GPIOConfigPtr;

int *modulator_axi4_lite_interface; // pointer to modulator_axi_ip memory-mapped internal registers

int sel = 0; // switch used for selecting frequency


int current_sel = 0; // current value of the sel bit in the modulator_axi_ip IP core

int temp;

/*********************** Initialization **************************/

// SWITCHes initialization
GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
XGpioPs_CfgInitialize(&GpioSwitches, GPIOConfigPtr, GPIOConfigPtr ->BaseAddr);
XGpioPs_SetDirectionPin(&GpioSwitches, SWITCH_CHANNEL, 0);

// modulator_axi_ip IP core initialization


modulator_axi4_lite_interface = SEL_REGISTER_ADDRESS;
*modulator_axi4_lite_interface = sel;

modulator_axi4_lite_interface = INC_FREQHIGH_REGISTER_ADDRESS;
*modulator_axi4_lite_interface = INC_FREQHIGH;

modulator_axi4_lite_interface = INC_FREQLOW_REGISTER_ADDRESS;
*modulator_axi4_lite_interface = INC_FREQLOW;

// readback
modulator_axi4_lite_interface = SEL_REGISTER_ADDRESS;
temp = *modulator_axi4_lite_interface;

modulator_axi4_lite_interface = INC_FREQHIGH_REGISTER_ADDRESS;
temp = *modulator_axi4_lite_interface;

modulator_axi4_lite_interface = INC_FREQLOW_REGISTER_ADDRESS;
temp = *modulator_axi4_lite_interface;

/************************* Main Loop *****************************/


while(1)
{
// read the switch position
sel = XGpioPs_ReadPin (&GpioSwitches, SWITCH_CHANNEL);

sel = sel & SWITCH_POS; // masking (we want to check the status of sel only)

if (sel != current_sel)
{
current_sel = sel;

www.so-logic.net 2023/01/10 115


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

modulator_axi4_lite_interface = SEL_REGISTER_ADDRESS;
*modulator_axi4_lite_interface = sel;
}
}

return 0;
}

modulator_sozius_axi.h

#ifndef MODULATOR_H_
#define MODULATOR_H_

#include "math.h"
#include "xparameters.h"

/********************** Constant Definitions ***********************/


#define SWITCH_CHANNEL 12

// address map for modulator_axi_ip internal registers


// address of sel register
#define SEL_REGISTER_ADDRESS XPAR_MODULATOR_AXI_IP_0_S00_AXI_BASEADDR
// address of inc_freqhigh register
#define INC_FREQHIGH_REGISTER_ADDRESS XPAR_MODULATOR_AXI_IP_0_S00_AXI_BASEADDR + 4
// address of inc_freqlow register
#define INC_FREQLOW_REGISTER_ADDRESS XPAR_MODULATOR_AXI_IP_0_S00_AXI_BASEADDR + 8

#define SYS_CLK_MHZ 50.0 // 50 MHz system clock


#define CLOCK_RATE (1000000.0 * SYS_CLK_MHZ) // system clock value, expressed in Hz
// CLOCK_RATE = 50000000 Hz (= 50 MHz)

#define F_LOW 1.0 // F_LOW = 1 Hz


#define F_HIGH 3.5 // F_HIGH = 3.5 Hz

#define NCO_WIDTH 31 // number of bits used for numerically controlled oscillator

#define C pow(2, NCO_WIDTH) // 2^NCO_WIDTH

// increment value for lower frequency, when sel = 0 (F_LOW = 1 Hz)


// INC_FREQLOW = C * F_LOW / CLOCK_RATE = 43
#define INC_FREQLOW roundf(C * F_LOW / CLOCK_RATE)

// increment value for higher frequency, when sel = 1 (F_HIGH = 3.5 Hz)
// INC_FREQHIGH = C * F_HIGH CLOCK_RATE = 150
#define INC_FREQHIGH roundf(C * F_HIGH / CLOCK_RATE)

#define SWITCH_POS 0x01 // mask to select switch position

#endif /* MODULATOR_H_ */

Run the Application Project

- Select the target application project (modulator_sozius_axi), then build project and at the end run your
application project.

Importnat Note: If an Makele error occurs after starting the build project process (as it is shown in the
Figure 4.32), it is necessary to do the following:

Figure 4.32: Building project errors

ˆ go to the folder vitis_axi/modulator_sozius/ps7_cortexa9_0/standalone_domain/bsp/


ps7_cortexa9_0/libsrc and open any of the oered folders. In the src folder copy the Makele and
rewrite it over it the Makele in the vitis_axi/modulator_sozius/ps7_cortexa9_0/standalone_domain/
bsp/ps7_cortexa9_0/libsrc/modulator_axi_ip_v1_0/src folder.

116 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

ˆ vitis_axi/modulator_sozius/zynq_fsbl/zynq_fsbl_bsp/ps7_cortexa9_0/libsrc
go to the folder
src folder copy the Makele and rewrite it over it the Makele
and open any of the oered folders. In the
in the vitis_axi/modulator_sozius/zynq_fsbl/zynq_fsbl_bsp/ps7_cortexa9_0/libsrc/
modulator_axi_ip_v1_0/src folder.

ˆ Re-build the project once more

Automatically Detected ILA and VIO Dashboards

- Turn back to the Vivado IDE and in the Hardware window of the Hardware Manager right-click on the
FPGA device (xc7z020_1) and select Refresh Device option.

When the debug cores are detected upon refreshing a hardware device, the default dashboard for each debug
core is automatically opened.

Figure 4.33: ILA Dashboard

4.2 Debug a Design using Integrated Vivado Logic Analyzer


Vivado Logic Analyzer

Vivado Logic Analyzer is an integrated logic analyzer in the Vivado Design Suite.

In this chapter you will learn how to debug your ARM-based system using the Vivado logic analyzer and you
will take advantage of it's functions to debug and discover some potential root causes of your design.

In-system debugging allows you to debug your design in real-time on your target hardware.

IP Integrator provides ways to instrument your design for debugging, which will be explained in this chapter.

After programming the FPGA device with the .bit le that contains the ILA and VIO cores, the Hardware
window now shows the ILA and VIO cores that were detected after scanning the device, see Figure 4.40.

Ones you have the debug cores in your design, you can use the run time logic analyzer features to debug the
design in hardware. The Vivado logic analyzer feature is used to interact with new ILA, VIO, and JTAG-to-AXI
Master debug cores that are in your design.

www.so-logic.net 2023/01/10 117


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

The next step in our design process is to set up the ILA core.

Automatically Detected ILA and VIO Dashboards

When the debug cores are detected upon refreshing a hardware device, the default dashboard for each debug
core is automatically opened.

The default ILA Dashboard can be seen on the following gure.

Figure 4.34: ILA Dashboard

Every default dashboard contains windows relevant to the debug core the dashboard is created for. The default
dashboard created for the ILA debug core contains ve windows, as can be seen on the previous illustration:

ˆ Settings window

ˆ Status window

ˆ Trigger Setup window

ˆ Capture Setup window

ˆ Waveform window

Add Probes to the VIO Dashboard

- Open the VIO dashboard by clicking the hw_vios tab and press blue + button in the middle of the VIO
dashboard to add the probes.

- In the Add Probes window select the only oered pwm_o probe and click OK.

118 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.35: Add Probes to the VIO window

- In the VIO Probes window you will see one 1-bit probe, pwm_o.

pwm_o probe is actually connected to the pwm_o output port of the Modulator AXI module. In the VIO
pwm_o signal.
Probes window, you can observe the rate of change of the

Figure 4.36: VIO Probes window

Add Probes to the Trigger Setup Window

- Turn back to the ILA dashboard by clicking the h_ila_1 tab and in the Trigger Setup window press blue
+ button in the middle to add the probes.

- In the Add Probes window select only pwm_o_1 probe and click OK.

www.so-logic.net 2023/01/10 119


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.37: Add Probes to the Trigger Setup window

The another way to add debug probes to the Trigger Setup window is to drag and drop the probes from the
Debug Probes window to the Trigger Setup window.

Important : Only probes that are in the Trigger Setup or Capture Setup window participate in the trigger
condition. Any probes that are not in the window are set to "don't care" values and are not used as part of the
trigger condition.

The Debug Probes window contains information about the nets that you probed in your design using the ILA
and/or VIO cores. This debug probe information is extracted from your design and stored in a data le that
typically has an .ltxle extension. Normally, the ILA probe le is automatically created during implementation
process. This le is automatically associated with the FPGA hardware device if the probes le is called debug
nets.ltx and is found in the same directory as the bitstream le that is associated with the device.

Change the Compare Values in the Trigger Setup Window

Now, when the ILA debug probe pwm_o_1 is in the Trigger Setup window, we can create trigger conditions
and debug probe compare values.

- In the Trigger Setup window, leave == (equal) value in the Operator cell, [H] (Hexadecimal) value in
the Radix cell and set the Value parameter to be 0 (logical zero).

Figure 4.38: Changing the Compare Values in the Trigger Setup window

As you can see from the illustration above, the Trigger Setup window contains three elds that you can
congure:

ˆ Operator : This is the comparison operator that you can set to the following values:

 == (equal)

 != (not equal)

120 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

 < (less then)

 <= (less then or equal)

 > (greater than)

 >= (greater than or equal)

ˆ Radix : This is the radix or base of the Value that you can set to the following values:

 [B] Binary
 [H] Hexadecimal
 [O] Octal
 [A] ASCII
 [U] Unsigned Decimal
 [S] Signed Decimal

ˆ Value : This is the comparison value that will be compared (using the Operator) with the real-time on
the nets(s) in the design that are connected to the probe input of the ILA debug core. Depending on the
radix settings, the Value string is as follows:

 Binary
* 0 : logical zero
* 1 : logical one
* X : don't care
* R : rising or low-to-high transition
* F : falling or high-to-low transition
* B : either low- to-high or high-to-low transitions
* N : no transition (current sample value is the same as the previousvalue)
 Hexadecimal
* X : All bits corresponding to the value string character are "don't care" values

* 0-9 : Values 0 through 9


* A-F : Values 10 through 15
 Octal
* X : All bits corresponding to the value string character are "don't care" values

* 0-7 : Values 0 through 7


 ASCII
* Any string made up of ASCII characters

 Unsigned Decimal
* Any non-negative integer value

 Signed Decimal
* Any integer value

You can use the ILA Dashboard to interact with the ILA core in several ways:

ˆ Use BASIC and ADVANCED trigger modes to trigger on various events in hardware

ˆ Use ALLWAYS and BASIC capture modes to control ltering of the data to be captured

ˆ Set the data depth of the ILA capture window

ˆ Set the trigger position to any sample within the capture window

ˆ Monitor the trigger and capture status of the ILA debug core

Capture mode - selects what condition is evaluated before each sample is captured:

ˆ ALWAYS: store a data sample during a given clock cycle regardless of any capture conditions

www.so-logic.net 2023/01/10 121


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

ˆ BASIC: store a data sample during a given clock cycle only if the capture condition evaluates "true"

Data Depth - sets the data depth of the ILA core captured data buer. You can set the data depth to any
power of two from 1 to the maximum data depth.

Trigger Position - sets the position of the trigger mark in the captured data buer. You can set the trigger
position to any sample number in the captured data buer. For instance, in the case of a captured data buer
that is 1024 sample deep:

ˆ sample number 0 corresponds to the rst (left- most) sample in the captured data buer

ˆ sample number 1023 corresponds to the last (right-most) sample in the captured data buer

ˆ sample numbers 511 and 512 correspond to the two "center" samples in the captured data buer

Add Probes to the Capture Setup Window

- In the ILA Settings window, change the Capture mode to be BASIC in the Capture Mode Settings
section.

- In the Capture Setup window press blue + button in the middle to add the probes.

- In the Add Probes window select only pwm_o_1 probe and click OK.

Figure 4.39: Add Probes to the Capture Setup

Change the Compare Values in the Capture Setup Window

Capture Setup window, leave == (equal) value in the Operator cell, [B] (Binary) value in the
- In the
Radix cell and set the Value parameter to be F (1-to-0 transition).

122 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.40: Changing the Compare Values in the Capture Setup window

Run ILA Core Trigger

- After we set all the ILA core parameters, we can run or arming the ILA core trigger.

We can run or arm the ILA core trigger in two dierent modes:
ˆ Run Trigger mode - arms the ILA core to detect the trigger event that is dened by the ILA core trigger
condition and probe compare values.

To run this mode, click the Run Trigger button in the Hardware or Debug Probes window.

ˆ Run Trigger Immediate mode  arms the ILA core to trigger immediately regardless of the settings of the ILA
core trigger condition and probe compare values. This command is useful for capturing any values present at the
probe inputs of the ILA core.

To run this mode, click the Run Trigger Immediate button in the Hardware or Debug Probes window.

You can also arm the trigger by selecting and right-clicking on the ILA core (hw_ila_1) in the
Hardware
window and selecting Run Trigger or Run Trigger Immediate option from the popup menu, see Figure
4.47.

Figure 4.41: Run Trigger option

Captured Waveform - sel = 0

Once the ILA core captured data has been uploaded to the Vivado IDE, it is displayed in the Waveform
Viewer.

After triggering the ILA core, in the waveform viewer change the c_counter_binary_0_Q[31:0] Waveform
Style from Digital to Analog, and your captured waveform should look like as the waveform on the following
gure.

www.so-logic.net 2023/01/10 123


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.42: Captured waveform of the sine signal, when sel=0

Captured Waveform - sel = 1

- To change the switch position from 0 to 1, press and hold the push button 4 on the Sozius development
board and while holding the push button 4 run trigger for the ILA core. Do not release the button until the
trigger is complete.

- Now when you have changed the switch position, run the trigger for the ILA core one more. After triggering
the ILA core your captured waveform should look like as the waveform on the following gure.

Figure 4.43: Captured waveform of the sine signal, when sel=1

By comparing the waveforms shown on Figures 4.48 and 4.49 we can observe that they dier in the amplitude
value. This is expected since the waveforms actually represent the width of the PWM pulse generated by the
modulator module. Since the frequencies of two generated PWM signals der (one has a frequency of 1 Hz
and the other of 3.5 Hz) and the PWM pulse width measurement module always uses the same frequency for
measuring the duration of the PWM pulse, when the PWM frequency increases the duration of the PWM pulse
will decrease, therefore decreasing the amplitude of the output signal of the PWM pulse width measurement
module.

The ILA core can capture data samples when the core status is Pre-Trigger, Waiting for Trigger or Port-Trigger.
As we already said, Capture mode selects what condition is evaluated before each sample is captured. Capture
mode stores a data sample during a given clock cycle only if the capture condition evaluates "true". We used
pwm_o signal to do the signal capturing.

Capture condition is a Boolean combination of events that is detected by match unit comparators that are
attached to the trigger ports of the core. Only when this combination is detected, data will be stored in the
ILA's buer.

To be able to capture at least one period of the sine signal and to store it in the ILA buer, we have to use
capture condition feature. After triggering the ILA core, in the waveform viewer change the Waveform Style

124 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

from Digital to Analog and your captured waveform should look like as the waveform on the Figure 4.48 or
Figure 4.49.

4.3 Developing a Device Driver for the Custom IP Core


Device Driver

In all previous software applications communication with the modulator_axi_ip IP core was done by directly accessing
its internal registers.

This requires intimate knowledge of modulator_axi_ip IP core's internal organization, as well as exact address values
for every internal register.

This approach signicantly complicates software application development by a typical software developer, who is not
aware and even doesn't want to be aware of all these details about the hardware device he is using.

A better approach includes an additional software layer that provides a software interface to the hardware device, enabling
user application to access hardware functions without needing to know precise details of the hardware being used.

This additional software layer is known as a device driver.

Device Driver Working Principles

A device driver typically communicates with the hardware device through the system bus or some other communication
subsystem to which the hardware device is connected.

When a user application program invokes a routine in the driver, the driver issues low level commands to the hardware
device.

Once the hardware device sends data back to the device driver,the driver may invoke routines in the original calling
program.

Device drivers are hardware dependent and operating system specic.

Device drivers simplify programming by acting as translator between a hardware device and the applications or operating
system that use it.

Programmers can write the higher level application code independently of what ever specic hardware the end-user is
using.

4.3.1 Device driver for PWM Modulator IP core


Device Driver for PWM Modulator IP Core

In this sub-chapter it will be shown how to develop a device driver for the modulator_axi_ip IP core and
how to integrate it and use it within the Xilinx Vitis tool.

Writing your drivers with a hierarchical structure is considered to be good design practice, and will save you a
lot of time when you need to debug the design.

Once you have veried that you have some measurable results by writing to one register, you can then move on
to testing some of the others.

It is advisable to create a function that can be re-used in your code, because this will form the basis of your
driver hierarchy.

www.so-logic.net 2023/01/10 125


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

"xil_io" Driver Function

The great way to start writing your own driver is to use the supplied Xilinx IO driver functions.

The xil_io driver provides some useful IO functions which can read and write data values to registers in the
hardware IP core, and these are perfect when the user wishes to design a driver for custom IP.

xil_io is easy to use and can be included in any project using a simple #include statement in the C code.

The most commonly used functions calls are Xil_Out32() and Xil_In32(), but similar functions exist for 16 bit
and 8 bit data transfers.

For example, following two functions can be used to write and read a value from the selected internal register
of the IP core. modulator_axi_ip_Set_Register function writes a user specied value (supplied through
the value input argument) to the selected custom IP internal register (selected by specifying base address value,
via baseaddr input argument and oset value, via oset input argument) using Xil_Out32 function. Similarly,
modulator_axi_ip_Get_Register function reads the current value from the selected custom IP internal
register and returns it to the caller, using Xil_In32 function.

void modulator_axi_ip_Set_Register(int baseaddr, int offset, int value)


{
Xil_Out32(baseaddr + offset, value);
}

int modulator_axi_ip_Get_Register(int baseaddr, int offset)


{
int temp = 0;
temp = Xil_In32(baseaddr + offset);
return (temp);
}

In these functions, we have implemented the use of osets from the base address, rather then using hard-coded
addresses each time. Another good coding practice would be to use #dene statements to specify names for
commonly used hex values, enabling the user to quickly identify which register is being addressed without having
to constantly cross-reference everything to the address map of the IP.

Additional Driver Functions

Presented two functions enable us to communicate with the custom IP core's internal registers to exercise the
various features of the custom IP core.

In the case of the modulator_axi_ip IP core, following additional driver functions can be dened:

ˆ modulator_axi_ip_Set_PWM_Frequency function - used to set the desired frequency of the


PWM signal that will be generated

ˆ modulator_axi_ip_Get_PWM_Frequency function - used to read the current frequency settings


used in the PWM signal generation

ˆ modulator_axi_ip_Select_PWM_Frequency function - used to select one of two possible frequen-


cies for the PWM signal generation

ˆ modulator_axi_ip_Get_Selected_PWM_Frequency function - used to read the current selec-


tion of the PWM signal frequency

Source code of the modulator_axi_ip_Set_PWM_Frequency driver function is shown below.

void modulator_axi_ip_Set_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel, float freq_val)


{
// c = 2^NCO_WIDTH
float c = 1.0;
for (int i = 1; i <= InstancePtr->NCO_Width; i++)
c = c *2;

// inc_factor = C * F_LOW / CLOCK_RATE


int inc_factor = (int)((c * freq_val/(float)InstancePtr->ClockRate) + 0.5);

modulator_axi_ip_Set_Register(InstancePtr->BaseAddress, 4+4*freq_sel, inc_factor);


}

126 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

When called, this function sets the selected PWM frequency value to the user specied value in the selected
instance of the modulator_axi_ip IP core. Instance of the modulator_axi_ip IP core for which one of the
PWM frequency values should be changes is specied through the Modulator_AXI *InstancePtr input argu-
ment. Which one of the two PWM frequency values should be changed is specied through the int freq_sel
input argument. Finally, new PWM frequency value that should be used is specied through oat freq_val
input argument. Function calculates the necessary value of the increment factor (variable inc_factor) based on
the desired PWM frequency value and specied characteristics of the selected instance of the modulator_axi_ip
IP core (InstancePtr->NCO_Width and InstancePtr->ClockRate ). Calculated increment factor is then written
to the appropriate internal register of the selected instance of the modulator_axi_ip IP core, using modula-
tor_axi_ip_Set_Register function.

Source code of the modulator_axi_ip_Get_PWM_Frequency driver function is shown below:

float modulator_axi_ip_Get_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel)


{
// c = 2^NCO_WIDTH
float c = 1.0;
for (int i = 1; i <= InstancePtr->NCO_Width; i++)
c = c *2;

float inc_factor = (float) modulator_axi_ip_Get_Register(InstancePtr->BaseAddress, 4+4*freq_sel);

// freq = inc_factor * CLOCK_RATE / C


float temp = (inc_factor * (float)InstancePtr->ClockRate / c);

return temp;
}

When called, this function returns the current value of selected PWM frequency from the selected instance of the
modulator_axi_ip IP core. Instance of the modulator_axi_ip IP core from which one of the PWM frequency
values will be read is specied through the Modulator_AXI *InstancePtr input argument. Which one of the
two PWM frequency values should be read is specied through the int freq_sel input argument. Function rst
reads the current value of the increment factor from the selected instance of the modulator_axi_ip IP core,
using modulator_axi_ip_Get_Register function. This increment factor value is then converted to frequency
value and returned to the calling function.

Source code of the modulator_axi_ip_Select_PWM_Frequency driver function is shown below:

void modulator_axi_ip_Select_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel)


{
modulator_axi_ip_Set_Register(InstancePtr->BaseAddress, 0, freq_sel);
}

When called, this function sets the value of the PWM frequency selector in the selected instance of the mod-
ulator_axi_ip IP core to the user specied value. Instance of the modulator_axi_ip IP core for which the
PWM frequency selector value will be set is specied through the Modulator_AXI InstancePtr input argument.
Which one of the two PWM frequency values should be used is specied through the int freq_sel input argu-
ment. Function simply writes the freq_sel input argument's value to the appropriate internal register of the
selected instance of the modulator_axi_ip IP core by calling modulator_axi_ip_Set_Register function.

Source code of the modulator_axi_ip_Get_Selected_PWM_Frequency driver function is shown be-


low:

int modulator_axi_ip_Get_Selected_PWM_Frequency (Modulator_AXI * InstancePtr)


{
return modulator_axi_ip_Get_Register(InstancePtr->BaseAddress, 0);
}

When called, this function simply returns the current PWM frequency selector value from the selected instance
of the modulator_axi_ip IP core. Instance of the modulator_axi_ip IP core from which one of the PWM
frequency values will be read is specied through the Modulator_AXI InstancePtr input argument. Current
PWM frequency selector value is read from the selected instance of the modulator_axi_ip IP core, using
modulator_axi_ip_Get_Register function.

www.so-logic.net 2023/01/10 127


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

modulator_axi_ip_Initialize" Driver Function

To facilitate the possibility of having more then one instance of the modulator_axi_ip IP core in the system,
driver functions presented above were written using a parametrized approach, via Modulator_AXI *Instan-
cePtr input argument.

By modifying this input argument value, calling function can communicate with dierent instances of the
modulator_axi_ip IP core that may be present in the system.

However, this approach requires that the user initialize every instance of the modulator_axi_ip IP core that is
present in the system, and keep track of this via a separate Modulator_AXI object.

For this purpose modulator_axi_ip_Initialize driver function can be used.

int modulator_axi_ip_Initialize(Modulator_AXI *InstancePtr, u16 DeviceId)


{
modulator_axi_ip_Config *ConfigPtr = NULL;
int Index;

// Assert arguments
Xil_AssertNonvoid(InstancePtr != NULL);

/* Lookup configuration data in the device configuration table.


* Use this configuration info down below when initializing this
* driver. */
for (Index = 0; Index < XPAR_MODULATOR_AXI_IP_NUM_INSTANCES; Index++)
{
if (modulator_axi_ip_ConfigTable[Index].DeviceId == DeviceId)
{
ConfigPtr = &modulator_axi_ip_ConfigTable[Index];
break;
}
}

if (ConfigPtr == (modulator_axi_ip_Config *) NULL)


{
InstancePtr->IsReady = 0;
return (XST_DEVICE_NOT_FOUND);
}

// Initialize the driver


InstancePtr->BaseAddress = ConfigPtr->BaseAddress;
InstancePtr->LUT_Width = ConfigPtr->LUT_Width;
InstancePtr->LUT_Depth = ConfigPtr->LUT_Depth;
InstancePtr->NCO_Width = ConfigPtr->NCO_Width;

// Indicate the instance is now ready to use, initialized without error


InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
return (XST_SUCCESS);
}

modulator_axi_ip_Initialize function initializes the selected Modulator_AXI object (specied by the Mod-
ulator_AXI InstancePtr input argument) with the conguration information related to the modulator_axi_ip
IP core whose device ID value is specied via the u16 DeviceId input argument. First, the function searches
for the conguration information of the selected modulator_axi_ip IP core by searching through a modula-
tor_axi_ip_CongTable array. modulator_axi_ip_CongTable array holds the conguration information of
every modulator_axi_ip IP core instance that is present in the system. Please notice that this conguration
information array is created automatically by the Xilinx Vitis tool, during the building of the Board Support
Package, using the instructions in the specic user-dened TCL le, that will be discussed later in this chapter.
If a device ID match is found, conguration of the selected modulator_axi_ip IP core is copied to the CongPtr
object. Using this conguration information, supplied Modulator_AXI is initialized and returned to the calling
function, along with the status information about the completed initialization process.

Finally, to complete our device driver for the modulator_axi_ip IP core, one more driver function is needed,
modulator_axi_ip_Set_Input_Clock_Frequency. This driver function is called for each instance
of the modulator_axi_ip IP core during the initialization process, to correctly set the system clock fre-
quency value that is used in every instance of the modulator_axi_ip IP core. This information is needed
to correctly calculate increment factors in the modulator_axi_ip_Set_PWM_Frequency and modula-
tor_axi_ip_Get_PWM_Frequency driver functions.

128 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

void modulator_axi_ip_Set_Input_Clock_Frequency (Modulator_AXI *InstancePtr, int clock_rate)


{
InstancePtr->ClockRate = clock_rate;
}

When called, modulator_axi_ip_Set_Input_Clock_Frequency driver function simply updates the ClockRate


eld of the specied Modulator_AXI object with the user-specied value (specied via the int clock_rate input
argument).

Create modulator_axi_ip.c and modulator_axi_ip.h Source Files

The driver functions shown above should illustrate that drivers can quickly be built to exercise the various
features of the custom IP.

It should also be very obvious that we are starting to generate signicant amounts of code, and we already have
many functions in our source le which are beginning to be untidy.

To continue the development of the driver functions, we can now move to code into a separate modula-
tor_axi_ip.c source le, and the custom prototypes into a modulator_axi_ip.h header le.

To maintain visibility of the functions across the dierent les, be sure to add the line #include "modula-
tor_axi_ip.h" at the top of the main application source le, and also to the modulator_axi_ip.c source le.

With this le structure in place, it is possible to quickly and easily continue the development of the custom
IP drivers, while keeping the source code in the main test application tidy and manageable. For very complex
driver functions, additional source les can be added to the le set to facilitate hierarchy in the source code.

Below, the complete device driver source code for modulator_axi_ip IP core, organized into two separate les,
modulator_axi_ip.c and modulator_axi_ip.h, is presented.

modulator_axi_ip.c:

#include "xil_io.h"
#include "xstatus.h"
#include "xparameters.h"
#include "modulator_axi_ip.h"

extern modulator_axi_ip_Config modulator_axi_ip_ConfigTable[];

/****************************************************************************/
/**
* Initialize the Modulator_AXI instance provided by the caller based on the
* given DeviceID.
*
* Nothing is done except to initialize the InstancePtr.
*
* @param InstancePtr is a pointer to an Modulator_AXI instance. The memory the
* pointer references must be pre-allocated by the caller. Further
* calls to manipulate the instance/driver through the Modulator_AXI API
* must be made with this pointer.
* @param DeviceId is the unique id of the device controlled by this Modulator_AXI
* instance. Passing in a device id associates the generic Modulator_AXI
* instance to a specific device, as chosen by the caller or
* application developer.
*
* @return
* - XST_SUCCESS if the initialization was successful.
* - XST_DEVICE_NOT_FOUND if the device configuration data was not
* found for a device with the supplied device ID.
*
* @note None.
*
*****************************************************************************/
int modulator_axi_ip_Initialize(Modulator_AXI *InstancePtr, u16 DeviceId)
{
modulator_axi_ip_Config *ConfigPtr = NULL;
int Index;

// Assert arguments
Xil_AssertNonvoid(InstancePtr != NULL);

/* Lookup configuration data in the device configuration table.


* Use this configuration info down below when initializing this
* driver. */
for (Index = 0; Index < XPAR_MODULATOR_AXI_IP_NUM_INSTANCES; Index++)

www.so-logic.net 2023/01/10 129


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

{
if (modulator_axi_ip_ConfigTable[Index].DeviceId == DeviceId)
{
ConfigPtr = &modulator_axi_ip_ConfigTable[Index];
break;
}
}

if (ConfigPtr == (modulator_axi_ip_Config *) NULL)


{
InstancePtr->IsReady = 0;
return (XST_DEVICE_NOT_FOUND);
}

// Initialize the driver


InstancePtr->BaseAddress = ConfigPtr->BaseAddress;
InstancePtr->LUT_Width = ConfigPtr->LUT_Width;
InstancePtr->LUT_Depth = ConfigPtr->LUT_Depth;
InstancePtr->NCO_Width = ConfigPtr->NCO_Width;

// Indicate the instance is now ready to use, initialized without error


InstancePtr->IsReady = XIL_COMPONENT_IS_READY;
return (XST_SUCCESS);
}

void modulator_axi_ip_Set_Input_Clock_Frequency (Modulator_AXI *InstancePtr, int clock_rate)


{
InstancePtr->ClockRate = clock_rate;
}

void modulator_axi_ip_Set_Register(int baseaddr, int offset, int value)


{
Xil_Out32(baseaddr + offset, value);
}

int modulator_axi_ip_Get_Register(int baseaddr, int offset)


{
int temp = 0;
temp = Xil_In32(baseaddr + offset);
return (temp);
}

void modulator_axi_ip_Set_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel, float freq_val)


{
// c = 2^NCO_WIDTH
float c = 1.0;
for (int i = 1; i <= InstancePtr->NCO_Width; i++)
c = c *2;

// inc_factor = C * F_LOW / CLOCK_RATE


int inc_factor = (int)((c * freq_val/(float)InstancePtr->ClockRate) + 0.5);

modulator_axi_ip_Set_Register(InstancePtr->BaseAddress, 4+4*freq_sel, inc_factor);


}

float modulator_axi_ip_Get_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel)


{
// c = 2^NCO_WIDTH
float c = 1.0;
for (int i = 1; i <= InstancePtr->NCO_Width; i++)
c = c *2;

float inc_factor = (float) modulator_axi_ip_Get_Register(InstancePtr->BaseAddress, 4+4*freq_sel);

// freq = inc_factor * CLOCK_RATE / C


float temp = (inc_factor * (float)InstancePtr->ClockRate / c);

return temp;
}

void modulator_axi_ip_Select_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel)


{
modulator_axi_ip_Set_Register(InstancePtr->BaseAddress, 0, freq_sel);
}

int modulator_axi_ip_Get_Selected_PWM_Frequency (Modulator_AXI * InstancePtr)


{
return modulator_axi_ip_Get_Register(InstancePtr->BaseAddress, 0);
}

130 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

modulator_axi_ip.h:

#ifndef MODULATOR_AXI_IP_H /* prevent circular inclusions */


#define MODULATOR_AXI_IP_H /* by using protection macros */

#include "xil_types.h"

/**************************** Type Definitions ******************************/

/**
* This typedef contains configuration information for the device.
*/
typedef struct {
u16 DeviceId; /* Unique ID of device */
u32 BaseAddress; /* Device base address */
u32 LUT_Depth; /* Number of samples in one period of the signal */
u32 LUT_Width; /* Number of bits used to represent amplitude value */
u32 NCO_Width; /* Number of bits used for numerically controlled oscillator */
} modulator_axi_ip_Config;

/**
* The Modulator_AXI driver instance data. The user is required to allocate a
* variable of this type for every Modulator_AXI device in the system. A pointer
* to a variable of this type is then passed to the driver API functions.
*/
typedef struct {
u32 BaseAddress; /* Device base address */
u32 IsReady; /* Device is initialized and ready */
u32 ClockRate; /* Input clock frequency, specified in Hz */
u32 LUT_Depth; /* Number of samples in one period of the signal */
u32 LUT_Width; /* Number of bits used to represent amplitude value */
u32 NCO_Width; /* Number of bits used for numerically controlled oscillator */
} Modulator_AXI;

int modulator_axi_ip_Initialize(Modulator_AXI *InstancePtr, u16 DeviceId);


void modulator_axi_ip_Set_Input_Clock_Frequency (Modulator_AXI *InstancePtr, int clock_rate);
void modulator_axi_ip_Set_Register(int baseaddr, int offset, int value);
int modulator_axi_ip_Get_Register(int baseaddr, int offset);
void modulator_axi_ip_Set_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel, float freq_val);
float modulator_axi_ip_Get_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel);
void modulator_axi_ip_Select_PWM_Frequency (Modulator_AXI * InstancePtr, int freq_sel);
int modulator_axi_ip_Get_Selected_PWM_Frequency (Modulator_AXI * InstancePtr);

#endif /* end of protection macro */

4.4 Creating Xilinx driver le and folder structure


Folder Structure

When the driver source les have been written, the next step is to put the driver les into a directory tree
structure that the Xilinx Vitis will understand.

This is a very important step because the Xilinx tools will expect to nd les and folders with reserved names.

An example of the required folder structure is show on the following gure.

Figure 4.44: Required folder structure

Required Folder Structure

- Create the folder structure in the same way as it is shown on the previous gure, wherever you like on the
disk.

www.so-logic.net 2023/01/10 131


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

The top level folder can be called anything that the user chooses, and be placed anywhere that they choose,
but must contain a sub-folder called "drivers".

Below that, there may be one or more folders which represent each driver for the custom IP.

We have just one driver in our example which is called "modulator_axi_v1_00_a".

Driver Sub-folders

Under the structure described above, three further folders are expected:

ˆ The "src" folder contains the .c and .h source les for the driver, in addition to a Makele which can be
written to describe the build process required to compile the sources in the correct order / dependency.

ˆ The "example" folder contains examples of software application code, showing how your driver must be
used in a nal application.

ˆ The "data" folder contains following control les which are specic to the operation of the Xilinx Vitis
tools, and which detail how various device driver source les should be used:

 the MDD le - Microprocessor Driver Denition le

 the TCL le - used by the Xilinx BSP creation tools to automatically generate some parameters
which can be used later by the software developer

Save the modulator_axi_ip.c and modulator_axi_ip.h in the "src" Folder

Save the source les, modulator_axi_ip.c and modulator_axi_ip.h, created in the previous sub-chapter
in the "src" folder, because the "src" folder should contain the .c and .h source les for the driver.

The MDD File

The rst control le in the "data" folder is the Microprocessor Driver Denition (MDD) le.

The le name is required to have a sux of "_v2_1_0.mdd", and in the case of our example has the full name
of "modulator_axi_v2_1_0.mdd".

The sux relates to the version of the syntax that is used within the MDD le, in this case v2.10.

The content of the MDD le is relatively simple, and for most MDD les the text is identical with the exception
of one or two lines.

In our case, the content of the "modulator_axi_v2_1_0.mdd" MDD le is the following:

modulator_axi_v2_1_0.mdd:

OPTION psf_version = 2.1;

BEGIN DRIVER modulator_axi

OPTION supported_peripherals = (modulator_axi_ip);


OPTION driver_state = ACTIVE;
OPTION copyfiles = all;
OPTION VERSION = 1.0;
OPTION NAME = modulator_axi;

END DRIVER

The "OPTION supported_peripherals" line should be updated to list the peripherals that are served by
the custom driver. In the example shown here, there is just one peripheral which will be supported by the
driver, but multiple peripherals can be listed, separated by a space. The names of supported peripherals must
match the names of the IPs that have been created using the IP Packager. In our case driver supports only one
IP, modulator_axi_ip.

132 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

The "BEGIN driver" line should be edited to create a name for the driver that is being developed. There
are no specic rules for the naming convention of this parameter, but it is wise to create a name that will be
clearly identiable to the end user. In our case, a name for the driver will be modulator_axi. Identical name
should be used in the "OPTION NAME" line of the MDD le.

The "OPTION driver_state" line allows the user to maintain a recognised lifespan of their custom driver,
and list the driver as either ACTIVE, DEPRECATED, or OBSOLETE as and when the developer choose to
supersede or retire it from the service. The eect of changing this option away from the "active" state, generates
either warnings or errors in the driver compile process when it is invoked by the end user. Driver that we are
developing is in the active state, so this option is set to ACTIVE.

The "OPTION copyles" line tells the Xilinx Vitis which source les in the custom driver's "src" directory
should be copied when the Vitis generates the BSP. In most cases this will be left set to "all".

The "VERSION" line allows the user to specify a version number for their driver. This should match the
directory name used previously.

- Create modulator_axi_v2_1_0.mdd control le, following instructions from the text above, and save it
into the "data" folder.

The TCL File

The second control le in the "data" folder is the TCL le, which is used by the Xilinx BSP creation tools to
automatically generate some parameters which can be used later by the software developers.

The content of the TCL le is shown in the following text.

modulator_axi_v2_1_0.tcl :

proc generate {drv_handle} {

::his::utils::define_include_file $drv_handle "xparameters.h" "modulator_axi_ip" "NUM_INSTANCES" "DEVICE_ID" "C_S00_AXI_BASEADDR"


"C_S00_AXI_HIGHADDR" "lut_depth_g" "lut_width_g" "nco_width_g" "FCLK_CLK0"

::hsi::utils::define_config_file $drv_handle "modulator_axi_driver_g.c" "modulator_axi_ip" "DEVICE_ID" "C_S00_AXI_BASEADDR"


"lut_depth_g" "lut_width_g" "nco_width_g"

::hsi::utils::define_canonical_xpars $drv_handle "xparameters.h" "modulator_axi_ip" "NUM_INSTANCES" "DEVICE_ID" "C_S00_AXI_BASEADDR"


"C_S00_AXI_HIGHADDR" "lut_depth_g" "lut_width_g" "nco_width_g"
}

- Create modulator_axi_v2_1_0.tcl control le, following instructions from the text above, and save it
into the "data" folder.

The TCL le shown above, that is part of a device driver we are developing, is actually only 4 lines in length,
including the nal curly bracket on its own line, but lines 2, 3 & 4 are extremely long and therefore dicult to
re-produce clearly in this document.

The lines 2 & 4 should be edited by the developer (beginning with "dene_include_le" and "dene_canonical_xpars"),
to list a number of parameters that will be generated automatically by the BSP generation tools before being
placed into a le called "xparameters.h" within the automatically generated board support package.

The editable section of these lines begins with the parameter after "xparameters.h". In this example, the rst two
editable parameters are "modulator_axi_ip" and "NUM_INSTANCES". These two parameters are used to cre-
ate a #dene statement in the "xparameters.h" le called "XPAR_MODULATOR_AXI_IP_NUM_INSTANCES"
and assign a numerical value to it. The Xilinx BSP generation tools have the ability to automatically count
the number of instances of each type of peripheral that are added into the user's embedded processor design.
This information can be of great use when the same driver is used to control multiple instances of the same
peripheral, and provides the ability for a loop to be created in software that will automatically update when
the number of instances of a given peripheral is increased and decreased in the design. In our example there is
only one instance of the modulator_axi_ip peripheral, so we need not worry about such a feature, but the line
"#dene XPAR_MODULATOR_AXI_IP_NUM_INSTANCES 1" will be automatically added to
the "xparameters.h" le when the BSP is generated.

www.so-logic.net 2023/01/10 133


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

xparameters.h File

The list of parameters in the TCL le on the rest of lines 2 and 4 represent additional #dene statements that will be
generated in the "xparameters.h" le.

Each of the #dene statements will have a prex related to the instance name of the IP, and a sux copied from the
text string in quotes on lines 2 and 4 of the TCL le.

Each of the text strings matches the name of a Generic in the top level VHDL entity for the custom IP, and the value
of each #dene is populated using either the Generic's default value in the VHDL code, or the value that the user has
set in the Vivado block diagram tool to override that default.

In our case, eight #dene statements will be created in the "xparameters.h" le during BSP generation, and they will
be populated according to the user's chosen settings and the number of instances of the custom IP that were added to
the user's design.

In our case, a section of "xparameters.h" le related to the modulator_axi_ip peripheral, that will be automat-
ically generated, is shown below:

/* Definitions for driver MODULATOR_AXI */


#define XPAR_MODULATOR_AXI_IP_NUM_INSTANCES 1

/* Definitions for peripheral MODULATOR_AXI_IP_0 */


#define XPAR_MODULATOR_AXI_IP_0_DEVICE_ID 0
#define XPAR_MODULATOR_AXI_IP_0_S00_AXI_BASEADDR 0x43C00000
#define XPAR_MODULATOR_AXI_IP_0_S00_AXI_HIGHADDR 0x43C0FFFF
#define XPAR_MODULATOR_AXI_IP_0_LUT_DEPTH_G 12
#define XPAR_MODULATOR_AXI_IP_0_LUT_WIDTH_G 16
#define XPAR_MODULATOR_AXI_IP_0_NCO_WIDTH_G 31
#define XPAR_MODULATOR_AXI_IP_0_FCLK_CLK0 0

Tcl command in line 3 is used to automatically generate modulator_axi_ip conguration table array object,
that was discussed in the modulator_axi_ip_Initialize function. This time, the editable section of this line
begins with the parameter after $drv_handle. First parameter species the lename of the C source code le
("modulator_axi_driver_g.c", in our case) that will be automatically generated by the Xilinx Vitis tool during
the BSP generation process. This C le will hold a denition of the conguration table related to the IP, whose
name is specied by the second parameter ("modulator_axi_ip" ). Remaining parameters in the line dene the
elds that each conguration table array member should contain (in our case each member should consist from
four elds, "DEVICE_ID" "C_S00_AXI_BASEADDR" "lut_depth_g" "lut_width_g" "nco_width_g").
As before, each of the text strings must match the name of a Generic in the top level VHDL entity for the
custom IP, and the values of these elds will be populated automatically using either the Generic's default value
in the VHDL code, or the value that the user has set in the Vivado block diagram tool to override that default.

The ability to generate a software BSP which will automatically update itself based upon changes made to the
hardware design is an incredibly powerful feature, and can save the software engineering team a lot of manual
development eort and time. The end user's software application can then make use of these parameters,
allowing the hardware and software teams to work completely independently, yet vastly reduce the possibility
for software errors and bugs to occur due to any changes made in the hardware design that were not manually
communicated to the software engineering team.

Conguring the Xilinx Vitis

With the driver control les written and placed in the correct folder structure, the nal step is to congure the
Xilinx Vitis tools so that they have visibility of the new driver in the list of available driver repositories.

Conguring the Xilinx Vitis

- In the Vitis IDE, open the Preferences dialog by choosing Window -> Preferences from the menu bar.

- In the Preferences dialog box, select the Xilinx -> Software Repositories pane.

134 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

Figure 4.45: Preferences dialog box

- In the Preferences dialog box, click the New... button next to the Local Repositories list to add a new
repository.

- In the Select Folder dialog box, point to the folder that you created for your custom drivers and click OK.

Figure 4.46: Browse For Folder dialog box

The folder you choose here should be the level of the le system above the drivers folder. In our case it is
pwm_modulator_driver folder.

- Click the Rescan Repositories button, followed by Apply and Close button.

This setting will provide visibility of your custom driver to the Vitis tool, and will enable your driver to be
selected in the BSP settings.

There is an addition list shown on this screen called Global Repositories, see Figure 4.44. This performs
an identical function to that of adding the drivers to the "Local Repositories" list, with the exception that the
drivers will be visible to any Vitis workspace that is created or opened in the future. This is a powerful feature if
your goal is to create a single repository of custom drivers for multiple custom IPs, and still have them routinely
available and visible to all of your Vitis workspaces.

Selecting a Custom Driver in the BSP

www.so-logic.net 2023/01/10 135


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

The task of creating and conguring your custom IP and custom driver is now complete, and the Vivado and
Vitis tools will now have visibility of them. The nal stage is to select your custom driver and allocate it to be
automatically compiled as part of the BSP for your user application.

Selecting a Custom Driver in the BSP

- Select the modulator_sozius platform project and then select Modify BSP Settings....

- In the Board Support Package Settings dialog box, choose the drivers pane from the choices shown on
the left of the window.

- In the Drivers conguration table, identify the instance of your custom IP (modulator_axi). It will now
be possible to select your custom driver from the drop down menu in the Driver column of the table.

- Click OK and the BSP will automatically be re-generated.

Figure 4.47: Board Support Package Settings dialog box with selected modulator_axi custom driver

Note : If you had created multiple folders representing dierent versions of the same driver, you will also be able
to choose the version of the driver in the Driver Version column.

If you now examine the "include" and "libsrc" folders in the standalone_bsp_0 BSP's source tree, you
will nd that your header le (modulator_axi_ip.h ) and C source le (modulator_axi_ip.c ) have been au-
tomatically copied and compiled into the BSP. Furthermore, one additional C source le is automatically
modulator_axi_driver_g.c. Remember that this is the source le we have specied in the
generated,
modulator_axi_v2_1_0.tcl TCL le. This source le contains the conguration table array object, hold-
ing all relevant conguration information for every modulator_axi_ip peripheral that is present in the embedded

136 2023/01/10 www.so-logic.net


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

system. In our case, we have only one instance of the modulator_axi_ip peripheral, so the generated congura-
tion table array object has only one entry, as can be seen by inspecting the generated modulator_axi_driver_g.c
source le. There is no longer any requirement for custom driver functions to be added to the list of sources
for each application, because they are now included in the BSP alongside the drivers provided by Xilinx for
supplied IP.

Creating New Application Project that will use Developed Device Driver

Creating New Application Project that will use Developed Device Driver

The last step in our design process will be to create a new application project that will use developed modu-
lator_axi device driver.

One possible solution is shown in the modulator_axi_using_driver.c and modulator_axi_using_driver.h


source les.

Note : For example, these two les can be stored in the "examples" folder from the device driver folder
structure.

modulator_axi_using_driver.c:

#include "xparameters.h"
#include "xgpiops.h"
#include "xstatus.h"
#include "modulator_axi_using_driver.h"
#include "modulator_axi_ip.h"

int main(void)
{
/*********************** Variable Definitions ********************/
XGpioPs GpioSwitches; // XGPIO instance that will be used to work with SWITCHEs
XGpioPs_Config *GPIOConfigPtr;

Modulator_AXI pwm_modulator; // Modulator AXI instance

int sel = 0; // switch used for selecting frequency


int current_sel = 0; // current value of the sel bit in the modulator_axi_ip IP core
float temp;

/*********************** Initialization **************************/

// SWITCHes initialization
GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
XGpioPs_CfgInitialize(&GpioSwitches, GPIOConfigPtr, GPIOConfigPtr ->BaseAddr);
XGpioPs_SetDirectionPin(&GpioSwitches, SWITCH_CHANNEL, 0);

// modulator_axi_ip IP core initialization


modulator_axi_ip_Initialize(&pwm_modulator, XPAR_MODULATOR_AXI_IP_0_DEVICE_ID);

// Set the input clock frequency for the Modulator_AXI instance to appropriate value
modulator_axi_ip_Set_Input_Clock_Frequency (&pwm_modulator, CLOCK_RATE);

// Set the values for two possible frequencies for the PWM signal
modulator_axi_ip_Set_PWM_Frequency (&pwm_modulator, 0, F_HIGH);
temp = modulator_axi_ip_Get_PWM_Frequency (&pwm_modulator, 0);

modulator_axi_ip_Set_PWM_Frequency (&pwm_modulator, 1, F_LOW);


temp = modulator_axi_ip_Get_PWM_Frequency (&pwm_modulator, 1);

// Select the initial frequency that will be used to generate PWM signal
modulator_axi_ip_Select_PWM_Frequency (&pwm_modulator, sel);
current_sel = modulator_axi_ip_Get_Selected_PWM_Frequency (&pwm_modulator);

/************************* Main Loop *****************************/


while(1)
{
// read the switch position
sel = XGpioPs_ReadPin (&GpioSwitches, SWITCH_CHANNEL);

sel = sel & SWITCH_POS; // masking (we want to check the status of sel only)

if (sel != current_sel)
{
// Write the new switch position to the pwm_modulator core
modulator_axi_ip_Select_PWM_Frequency (&pwm_modulator, sel);
current_sel = modulator_axi_ip_Get_Selected_PWM_Frequency (&pwm_modulator);
}

www.so-logic.net 2023/01/10 137


CHAPTER 4. USING CUSTOM IPS IN EMBEDDED SYSTEM DESIGN

return 0;
}

modulator_axi_using_driver.h:

#ifndef MODULATOR_H_
#define MODULATOR_H_

#include "math.h"
#include "xparameters.h"

/********************** Constant Definitions ***********************/


#define SWITCH_CHANNEL 12 // GPIO channel to operate on

#define SYS_CLK_MHZ 50 // 50 MHz system clock


#define CLOCK_RATE 1000000 * SYS_CLK_MHZ // system clock value, expressed in Hz
// CLOCK_RATE = 50000000 Hz (= 50 MHz)

#define F_LOW 1.0 // F_LOW = 1 Hz


#define F_HIGH 3.5 // F_HIGH = 3.5 Hz

#define SWITCH_POS 0x01 // mask to select switch position

#endif /* MODULATOR_H_ */

Creating a New Application Project

- In the Vitis IDE, select File -> New -> Application Project... option from the main Vitis menu.

- In the Create a New Application Project dialog box just click Next.

- In the Platform dialog box select modulator_sozius [custom] and click Next.

- In the Application Project Details dialog box type modulator_axi_using_driver as a new application
project name and click Next.

- In the Domain dialog box leave all parameters unchanged and click Next.

- In the Templates dialog box select Empty Application and click Finish.

Run Application Project with Developed Device Driver

- Expand modulator_axi_using_driver application project in the Project Explorer window and in the
src folder add previously created modulator_axi_using_driver.c and modulator_axi_using_driver.h
les.

Sub-chapter
- Run your application on the Sozius development board, on the same way as it is explained in the
3.7. "Running Application Project" and you should get the same results like in the Sub-chapter 4.2
"Debug a Design using Integrated Vivado Logic Analyzer".

138 2023/01/10 www.so-logic.net


Chapter 5

CONCLUSION
In this tutorial a range of dierent implementations of the PWM Modulator system has been presented:

1. A software solution that uses an external timer, PWM_SW_no_intc

2. A software solution that uses an external timer and interrupt controller, PWM_SW_intc

3. A hardware/software co-design solution, that uses a custom PWM Modulator IP core, PWM_HW/SW

Furthermore, in the "Basic FPGA Tutorial" a pure hardware solution for the PWM Modulator system has also
been presented, PWM_HW.

All these solutions represent valid and functionally correct implementations of the PWM Modulator system,
but they dier in a way how this functionality is actually implemented and therefore have dierent performance
results and resource requirements.

The performance of the PWM Modulator system can be represented by three attributes:

1. Maximum frequency of generated PWM signal

2. Maximum accuracy of duty cycle value of generated PWM signal

3. Minimum jitter value of generated PWM signal

Maximum frequency of generated PWM signal is probably the most important performance attribute of any
PWM Modulator system. Typical PWM signal frequency values range from 0.05 Hz, in case of an electric stove,
120 Hz, in case of a lamp dimmer, from few KHz to tens of KHz for a motor drive, and well into the tens or
hundreds of kHz in audio ampliers and computer power supplies.

Four proposed PWM Modulator systems have dierent maximum PWM frequency values.

PWM_HW and PWM_HW/SW solutions can generate PWM signals with the highest frequency values, be-
cause the PWM signal generation is done completely in hardware. In the proposed hardware solution, maximum
PWM frequency is limited by two factors:

ˆ maximum operating frequency of the Frequency Trigger module (see Chapter 2 "Frequency Trigger" from
the "Basic FPGA Tutorial" ), Fmax, and

ˆ the number of bits used to represent amplitude value of the PWM modulating signal ("Width G" eld
from the modulator_axi_ip customization window, shown on the Figure 5.12, or width eld in the de-
sign_setting_g generic from the modulator top-level entity, see Chapter 8 "Modulator" from the "Basic
FPGA Tutorial" ), width.

The maximum PWM frequency that can be generated by the PWM_HW and PWM_HW/SW solutions can
be calculated by the following formula:

Fmax
P W MF max = 2width

139
CHAPTER 5. CONCLUSION

For example, maximum PWM signal frequency that can be generated with the conguration of the PWM
Modulator IP core that was used in this tutorial is equal to:

100M Hz
P W MF max = 212 = 24414Hz

This value is sucient for the majority of PWM applications, described earlier.

Estimating the maximum PWM signal frequencies for the software solutions (PWM_SW_no_intc and PWM_SW_intc )
is not so straightforward. In these implementations maximum PWM frequency is also constrained by the
amount of time required to complete one iteration of the while loop located in the main procedures from
themodulator_no_intc_mb.c , modulator_intc_mb.c, modulator_no_intc_axi.c and modula-
tor_intc_axi.c source codes. The estimations of exact maximum PWM signal frequency values would require
a measurement of this while loop time. Furthermore, this time would also depend on the type of the processor
that executes the PWM Modulator software and the C/C++ compiler settings that were used to build the
executable version.

Next, we will comment on the pros and cons of each of the proposed implementation of the PWM Modulator
system.

PWM_SW_no_intc implementation

Pros:

ˆ Easiest implementation with the shortest design time.

ˆ Easy to modify the used PWM generator algorithm, for instance easy to change the waveform of the
modulating signal, all we need to do is to recompile the modied PWM Modulator source code.

Cons:

ˆ Maximum frequency of generated PWM signal is the lowest from all proposed implementations, because
the duration of one iteration of the while loop from the modulator_no_intc_mb.c and modula-
tor_no_intc_axi.c source codes is the longest. This duration becomes even longer if the processor
needs to carry out some additional tasks other then PWM generation, because all these tasks would have
to be included in this while loop.

ˆ Jitter of generated PWM signal is signicant, and potentially can be very high, but it can be relatively
accurately estimated (since we know the amount and ordering of additional commands that are located
in the while loop) and compensated.

PWM_SW_intc implementation

Pros:

ˆ More dicult to implement than the PWM_SW_no_intc implementation, because it requires the con-
guration of the interrupt controller and writing the interrupt routine.

ˆ In case a processor has to perform some other actions apart from the PWM signal generation, this is the
only acceptable solution (from "pure" software solutions).

ˆ Easy to modify the used PWM generator algorithm, for instance easy to change the waveform of the
modulating signal, all we need to do is to recompile the modied PWM Modulator source code.

Cons:

ˆ Maximum frequency of generated PWM signal should be a little bit higher then the maximum PWM signal
frequency generated by the PWM_SW_no_intc implementation, because the duration of one iteration
of the while loop in this case is shorter.

ˆ Jitter of generated PWM signal is signicant, and potentially can be very high, but this time it can not be
predicted in advance, because we can not estimate the actual point in time when an PWM timer interrupt
will be generated. This is even more unpredictable if there are other interrupt sources with equal or higher
interrupt priority present in the system.

140 2023/01/10 www.so-logic.net


CHAPTER 5. CONCLUSION

PWM_HW/SW implementation

Pros:

ˆ Oers the generation of PWM signal with the highest possible frequency, identical to the PWM_HW
implementation, because in this implementation, as well as PWM_HW implementation, PWM signal
generation is done completely in hardware.

ˆ Jitter of generated PWM signal is minimal.

ˆ Great exibility (for example, easy adjusting of frequencies of generated PWM signals, easy integration
into more complex systems like multiple PWM generators, etc.), especially when compared with the
PWM_HW implementation.

Cons:

ˆ If PWM Modulator IP core is not readily available, then the development time is signicant, however if
this IP is available then the development time is comparable with the "pure" software solutions.

PWM_HW implementation

Pros:

ˆ Oers the generation of PWM signal with the highest possible frequency, identical to the PWM_HW
implementation, because in this implementation, as well as PWM_HW implementation, PWM signal
generation is done completely in hardware.

ˆ Jitter of generated PWM signal is minimal.

ˆ Minimum amount of required hardware resources, minimum power consumption.

Cons:

ˆ Limited exibility, because every modication of the design requires a modication of hardware, leading
to the necessity to reimplement the complete system (redo the hardware synthesis, place and route the
design), which in large systems can take several hours.

ˆ Long development time, because we need to design and verify a complete hardware system, which is much
more time consuming that software design.

www.so-logic.net 2023/01/10 141


CHAPTER 5. CONCLUSION

142 2023/01/10 www.so-logic.net


Chapter 6

EXERCISES
This section holds a set of exercises that can be used to verify the knowledge acquired by reading this tutorial.

Exercise 1

Modify the init_sin.c source code in order to be able to generate PWM signal modulated by the following
signals:

1. Triangle wave signal

2. Sawtooth wave signal

Exercise 2

Make the necessary modications to the ARM-based embedded system and modulator_no_intc.c application
to enable the user to specify which type of modulating signal (sine, triangle or sawtooth) should be used to
generate PWM signal on-the-y, during the operation of the system.

Exercise 3

Using the ARM-based embedded system and modulator_no_intc.c application as a starting point, make the
necessary modications to both the hardware and software parts of the ARM-based embedded system in order
to be able to generate two completely independent PWM generators.

Exercise 4

Using the ARM-based embedded system and modulator_intc.c application as a starting point, make the nec-
essary modications to both the hardware and software parts of the ARM-based embedded system in order to
be able to generate two completely independent PWM generators.

Exercise 5

Using the ARM-based embedded system based on the PWM modulator custom IP core and modulator_axi.c
application as a starting point, make the necessary modications to both the hardware and software parts of the
ARM-based embedded system in order to be able to generate two completely independent PWM generators.

Exercise 6

Using the ARM-based embedded system based on the PWM modulator custom IP core, modulator_axi driver
and modulator_axi_using_driver.c application as a starting point, make the necessary modications to both
the hardware and software parts of the ARM-based embedded system in order to be able to generate two
completely independent PWM generators.

143

You might also like