This is a follow-on to last week’s demonstration of developing embedded software using Vitis 2020.1.
In this week’s blog, we are going to look at how we can debug applications running on the processing system using Vitis. To be able to do this, we need a JTAG debugger because the MicroZed does not have on-board JTAG USB capability like some other boards.
To get started debugging the MicroZed, the Digilent JTAG HS2 / HS3 programming cables are the most cost effective. Of course for larger developments, you might want to consider the SmartLynq from Xilinx.
To demonstrate the debugging capabilities of Vitis, we are going to update the software we developed last week because it was far too simple. Therefore, I have updated the software to be able to read the push button switch and turn on or off the LED. Both the switch and LED are connected to the PS MIO.
Once we have updated the code to be able to toggle the LED, we want to be able to download and debug the application. The first stage is to remove the SD card from the MicroZed, connect our JTAG pod, and power on the board.
Within Vitis we want to be able to connect to the JTAG pod, configure the PS for the MicroZed, download the PL design, and of course download the application(s) for the PS cores.
To get started, we need to create a debug configuration. We can create this configuration by selecting the down arrow next to the green bug icon. From the resulting menu, select Debug Configurations.
This will open the Debug Configuration menu. Here we can create debug configurations for a range of solutions from AI engine emulation to single application and system-application debug.
We are going to create a new system project debug configuration.
Select System Project Debug and click on New Launch Configuration from the menu bar.
This will create a new system application debug configuration for our application. The configuration is auto populated for that application because we had selected the application project in the explorer window on the left-hand side when this was created.
On the main tab, you will notice that the hardware server is selected as Local. This means we have a local, not network connection, to the JTAG debugger.
The project will be set to the system application project name. Although we are only debugging a simple application here, using a system application debug enables us to add more applications and domains as the software development matures.
On the Target setup tab, you will see the bitstream to be loaded, the initialisation file, and the options for reset when the debugger is run. This resets the entire system and programs the FPGA before running the PS7 initialisation and post configuration scripts.
Finally, it will download the application and leave the application(s) execution suspended at the first executable line of code.
As the debugger downloads and configures the target, you will notice that the perspective in eclipse switches from the design perspective to the debug perspective.
You can change perspectives easily by selecting the icon on the top right.
The debug perspective will open and show you the source code view with the current line for execution highlighted. In the Debug window, you will see the call stack for the processor core being debugged.
Finally, the Variable, Memory, Breakpoint, Expression and Register windows will be shown. In these windows you can see the variables in the source code (variables window), the contents of memory location (memory window), enabled of disabled breakpoints (breakpoints window), and the contents of the processor registers (register window). We can also view expressions in the expressions window if we add variable to be watched.
All told, we can observe a significant number of elements of the system enabling us to debug it. The memory view is particularly useful as you can view any peripherals connected to the AXI network, including those in the PL.
When first opened, the memory view will be blank. By clicking on the green plus arrow we can add a new address to observe. In the PS memory map, the PS GIO resides at address 0xE000A000 which is what I am going to observe in this example.
We can also add breakpoints to the source code by double clicking Next to a line of code in the source code view.
You can also see any breakpoints in the code and their enabled / disabled status under the breakpoint window.
For this example, I have set a breakpoint which should occur when the button is pressed.
With the memory watch and breakpoint set as desired, the next stage is to run the application. This can be done by clicking on the green Run arrow on the menu bar.
The project will start running and if you have a terminal connected, you will see the initial Hello, World! statements output.
Pressing the button will result in the breakpoint being triggered. (Make sure you press it for more than one second.) You will see the code stop at the line where the breakpoint is set. In the memory window, you will see the memory contents change and the changes highlighted in red.
Using this combination of breakpoints, visibility of memory locations, variables, breakpoints, and registers enables us to start debugging our application.
Of course, as we are working in a heterogeneous SoC, we need to be able to debug applications which use elements of the PL as well as PS. We will look at how we can debug these systems using ILA’s VIO and Cross Trigger in the next blog.