Vitis Debug & Development with VS Code

This tutorial will guide users through debug and development of embedded applications using Vitis from the command line interface (CLI), rather than the graphical unser interface (GUI) offered by the Eclipse-based IDE, with the ultimate goal of applying this to the integration of Vitis with a 3rd party tool. Understanding how Vitis can be driven from the CLI aids in the use of both scripted flows flows, version control systems, and 3rd party exampels. This will be demonstrated through integration with Visual Studio Code to develop and debug embedded application.

This tutorial used the Vitis 2021.1 release and although there is nothing specific about that version which this tutorial requires it's recommended that you follow this tutorial with that same release before attempting to use other ones.

Integration with VS Code is done here for demonstration purposes only and is not covered by Xilinx technical support. For more information see Embedded SW Support.

Table of Contents

1. Introduction

In addition to offering normal development functions, like code editing and debug, the Vitis IDE also handles the generation of device support packages, like boot firmware and drivers, based on a supplied hardware description file (XSA). And although the Vitis IDE is not built upon a fully scripted backend (such as Vivado) most functions are made available through the XSCT (Xilinx Software Command-line Tool) utility. Consequently, we will leverage XSCT throughout this tutorial to accomplish tasks that are normally native to the Vitis IDE.

On the subject of debug, the Vitis IDE does rely completely on a scripted backend exposed through a sub-set of XSCT commands. For use-cases where only these debug commands are needed a separate XSDB (Xilinx System Debugger) utility can be used. This XSDB has a smaller footprint and can be installed as part of a minimal set of Xilinx lab tools. Given this benefit, it will be the utility of choice for the debugging portion of this tutorial.

It should be noted that when XSCT is used to manage/create projects from the CLI it generates metadata that matches the metadata that would be generated by the Vitis IDE. This way a project workspace can be interchanged equally between CLI and IDE use.

2. Creating a Vitis Development Workspace

This section will go over creation of a Vitis project workspace including mandatory device support components and a couple example Hello World applications. All the mandatory components are bundled into a Platform Project, while applications are bundled under a System Project.

Initially XSCT will be relied on to generate and build all these projects at least once. But beyond that the application projects can be built independent of Vitis. The one-time build by XSCT is required because it also generates the Makefiles for the applications.

2.1 Initializing Vitis Projects

Here we will rely on XSCT to perform a fair bit of under-the-hood automation with respect to ingesting a hardware specification file (XSA) and producing design-specific output products. All projects will be placed into a workspace folder designated by the WORKSPACE variable while the hardware specification is assigned to XSA. The XSCT commands that build up the initial projects are captured in the supplied Makefile under the init target as shown below.

init: xsct -eval "setws $(ws); \ platform create -name $(plat_name) -hw $(xsa); \ domain create -name $(domain_name_apu) -os standalone -proc psu_cortexa53_0; \ domain create -name $(domain_name_rpu) -os standalone -proc psu_cortexr5_0; \ app create -name $(app_name_apu) -platform $(plat_name) -domain $(domain_name_apu) -sysproj $(sys_name) -template {Hello World}; \ app create -name $(app_name_rpu) -platform $(plat_name) -domain $(domain_name_rpu) -sysproj $(sys_name) -template {Hello World}; \ platform active $(plat_name); \ platform generate; \ app build $(app_name_apu); \ app build $(app_name_rpu)"

Consequently the workspace can be created as shown below.

$ export WORKSPACE=<path-to-workspace> $ export XSA=<full-path-of-XSA-file> $ make init

And the resulting workspace should resemble that shown below.

$ ls -hl $WORKSPACE total 24K drwxrwxr-x 5 shaun shaun 4.0K Aug 5 09:35 hello_world_a53 drwxrwxr-x 5 shaun shaun 4.0K Aug 5 09:35 hello_world_r5 -rw-rw-r-- 1 shaun shaun 4.3K Aug 5 09:35 IDE.log drwxrwxr-x 9 shaun shaun 4.0K Aug 5 09:33 zcu102_plat drwxrwxr-x 2 shaun shaun 4.0K Aug 5 09:33 zcu102_sys

2.2 Building Projects

After project initialization/creation makefiles will be located under a Debug directory. For example:

Now the applications can be built and cleaned using these makefiles. This is captured by the tutorial makefile under the build and clean targets as shown below.

2.3 Building with VS Code

Now let's begin demonstrating how VS Code could be used as a development environment. For the sake of simplicity we will leverage the toolchains that are installed by Vitis. That is, VS Code will call upon the same toolchain that Vitis uses. Note, however, that nothing prevents you from using your own toolchain installations.

The Vitis installed toolchains are made available by sourcing environment settings provided by Vitis. So these can, in-turn, be made available to VS Code by opening it in a terminal where this environment is already set. For example:

Above we also used the folder-uri flag to open the WORKSPACE folder directly, but you can accomplish the same by selecting File > Open Folder... from within VS Code. With VS Code open to WORKSPACE the project folders and files should appear in the Explorer pane.

Now we'll create tasks for building and cleaning the projects. Select Terminal > Configure Tasks... followed by the drop-down "Create tasks.json file from template" option and then finally the "Others" option. This will create a .vscode folder within the WORKSPACE and a tasks.json file within that new folder.

The exact options to create a tasks.json file may vary by version of VS Code but the general idea is that you should create this file.

The new JSON file will open in the editor with a simple "echo" task example. All we will do is add a couple tasks to call make to build and clean the A53 application project. You can enter these directly based on the completed example below or use the VS Code IntelliSense <Ctrl+I> feature to add a new task entry template (e.g. place cursor after closing curly bracket of existing task, then add a comma followed by a carriage return to position cursor for a new task entry, then use <Ctrl+I> to select a task template) and adjust as shown in the example.

Once the tasks.json file is updated and saved try out the new tasks through Terminal > Run Tasks.... If you keep an eye on the corresponding Debug folder you should see a clean remove the ELF file while a build will regenerate it.

If everything is working as expected you can then go back to the tasks JSON file and add two more similar entries for the R5 application. An example of a completed file is available in the src directory of this repo.

3. Debugging with GDB

This section will guide you through debugging with GDB, which is an alternative to debugging with the Targeted Communication Framework (TCF). It's worth noting that debugging with TCF (referred to as System Debugger with respect to Vitis) is generally preferred over GDB and, consequently, is the default method used by Vitis. This is because, among other things, TCF more gracefully handles multi-core debug. However, GDB tends to have better 3rd party support (such as with VS Code) so it will be the method of choice in this tutorial.

3.1 Acquiring Init Script

The Vitis IDE normally handles some device specific initialization that is required before debugging can start. Ultimately, the goal it to debug an application on a specific core (e.g. Cortex-R5) for which a GDB binary supporting that architecture should suffice. However, that core is located on a unique SoC (Zynq MPCore UltraScale+ in this case) and that SoC must be initialized in a very device-specific manner before the ARM core is ready for running/debugging an application.

In order to make this work outside of the Vitis IDE we will borrow the init script used by the IDE. But to do this we first need to do a trial run within the IDE. So start by opening the Vitis IDE and pointing to the existing workspace folder. For example:

Once the Vitis IDE is open right-click on the hello_world_a53 project (expand the zcu102_sys project under the Explorer view to see the application projects) to bring up the context menu and then select Debug As > 3 Launch Hardware (Single Application Debug (GDB)). This should change the IDE to the Debug perspective and a default breakpoint should become visible in the code editor once the debugger is connected and running.

At this point there are 3 views/tabs worth noting:

  1. Vitis Log

  2. Debugger Console

  3. XSCT Console

The Vitis Log records all the background activity performed by the IDE and also captures and warnings and errors. The Debugger Console will capture output from GDB. And, finally, the XSCT Console will capture XSCT output that results from Vitis background activity.

The init script we're after should appear in the Vitis Log, conveniently enclosed by:

Capture everything between those two lines (represented by the ... above) and paste it into a new file. We'll name that file dbg_init_a53.tcl. Once the script is captured you can terminate the active debug session (Ctrl+F2).

Now repeat the process above for the hello_world_r5 application project. We'll name the file for that script dbg_init_a53.tcl.

Once both scripts are captured the Vitis IDE can be closed. Examples of these scripts can be found in the src directory of this repo.

3.2 Debugging from CLI

To test debugging from the CLI we'll use 3 different terminal windows.

  1. One to capture program serial output (UART)

  2. A 2nd to run the TCL init script and host a gdbserver

  3. A 3rd to run gdb

The 2nd terminal uses XSCT/XSDB to both initialize the board for debug and host a gdbserver connection. XSCT/XSDB actually calls upon a separate hw_server utility for this gdbserver hosting but by default it will terminate if XSCT is closed. To ensure that gdbserver remains available XSCT/XSDB must run the script and then remain open. This is accomplished with the -interactive flag.

The hw_server is also available as a standalone utility and although we don't call upon it directly in this tutorial it's built-in documentation can be referenced for more information on the ports used to host gdbserver. For example:

We'll start by opening the UART connection to the board. Here we use minicom but, of course, this can be substituted with any software of choice.

Term 1:

Now in a separate terminal start XSCT/XSDB with the debug init script. Although not shown below (substituted with ...), this will result in a lot of output as the script is processed.

Term 2:

If the script succeeds at initializing the device confirmation will be seen on the UART in the form of output from the boot loader firmware.

Term 1 Continued:

At this point the device should be ready for debug and we can start gdb. The below example will also walk through setting a breakpoint after the first line printed by the example application. That first line should appear on the UART and then will be followed by the second line only once gdb is instructed to continue running. Note that <Ctrl+C> is used to terminate the program after it runs to completion.

Term 3:

The following additional output should appear on the UART when the program completes.

Term 1 Continued:

3.3 Debugging with VS Code

We'll now complete this tutorial by demonstrating how debugging can be integrated into VS Code. Start by installing the "Native Debug" extension which will be used to support interactions with GDB. For example:

Now, in manner similar to how tasks were created, we will create some run configurations. A separate launch.json file manages these run configurations so the first step is to create this file.

Select Run > Add Configuration... then select GDB from the drop-down. This should open a newly created launch.json file which is also placed under the .vscode directory. VS Code should also change to the Run view; if not you can manually switch with View > Run.

From the Run view a drop-down should be available from which you can select Add Configuration... (also available under the Run menu).

This will open an IntelliSense menu in the editor from which you can select "GDB: Debug external embedded device".

A new configuration entry will be added to the JSON file which should be updated to match the example below. In particular, make the following changes:

  1. Add "gdbpath" property to point to corresponding version of gdb (otherwise native x86 gdb will be used)

  2. Update "target" property such that gdb connection command uses the appropriate port on localhost (note that extended-remote is just the long form of the tar ext command we used from the CLI).

  3. Modify "autorun" property to match what we did from the CLI after connecting GDB to the gdbserver (i.e. load symbols).

Now that we have a Run/Debug configuration there's just one last thing needed before we can take Debug for a spin. Another task will be needed to initialize the device prior to running debug.

Once again edit the tasks.json file. If it's no longer open you can select Terminal > Configure Tasks... and choose an arbitrary task.

Add a new task entry (either manually or using IntelliSense <Ctrl+I>) and populate it as given below. Notice that the task is just implementing the same XSCT/XSDB call we used on the CLI.

Now we're ready for debug! Start by running the debug init task. You should be able to see the task running in the Terminal window with confirmation that it's complete when the terminal comes to rest at the XSCT/XSDB prompt (e.g. xsdb%).

Unlike the Vitis IDE there will be no default breakpoint set. So a debug run would just run the application to completion unless a breakpoint is added.

Open the $WORKSPACE/hello_world_a53/src/helloworld.c file and add a breakpoint at the 2nd print function. This will allow one lines to print over UART before the program is stalled. Then the second line will print when you either step one line or continue execution with VS Code.

Try it out by selecting "Debug APU" from the Run drop-down and then pressing the green play button just next to it.

Before wrapping up this demonstration it's important to note that each time a debug session is completed/detached the XSDB terminal session should be terminated (a button with a trashcan icon is provided by VS Code to terminate a terminal). Attempting to restart a debug session through the VS Code run buttons alone will not work. To start a new debug session simply restart the "gdb init" task after terminating the previous one.

Also, in case it wasn't already obvious, take note of the default location of the JSON files. They fall under the $WORKSPACE directory so if you decide to purge this directory make sure you've saved these files somewhere first.

Below is a closing screenshot of VS Code in debug action!

4. Conclusion

Although a full installation of Vitis is required to initialize a project workspace subsequent use of the workspace from the perspective of application development can be accomplished with a much smaller set of Xilinx tools. For example, if the required GNU toolchains are supplied by the host system then only XSDB is needed outside of standard Linux development tools. It's worth noted that XSDB can be installed as part of a free Vivado Lab Edition with a much smaller footprint than fully functional Vivado installs.

However, any changes to the hardware definition file (XSA) will require XSCT, and therefore a complete Vitis installation, to regenerate device support components. In fact, even changes to the configuration of these components is ideally done through XSCT in order to keep the workspace compatible for use with the Vitis IDE (e.g. direct modification of makefiles will not be picked up by the IDE). This is obviously not ideal but moving forward we should start to see decreased reliances on XSCT for HW/SW interactions. In fact, work towards a device-tree based hardware description interface is already a work in progress.

© Copyright 2019 - 2022 Xilinx Inc. Privacy Policy