The Xclbin Type

Another important element of the package is the XRT.Xclbin type, as it wraps the native xrt::xclbin class and allows access to metadata, such as kernel or memory information of the bitstream container file. To create an Xclbin object, the path to a valid Xclbin container file must be specified. When loading, the package checks whether the bitstream is compatible with the current system configuration and potentially issues a warning.

julia> xclbin = XRT.Xclbin("path/to/communication_PCIE_emulate.xclbin")
┌ Warning: xclbin target type 'sw_emu' does not match the currently set value 'hw'
└ This can lead to incorrect execution
/pc2/users/l/user/path/to/communication_PCIE_emulate.xclbin

All attributes can easily be read using fields and chained vector accesses, instead of the native APIs getter functions. To obtain the C++ class object from an Xclbin struct, just call .xclbin, .kernel, .arg, or .mem depending on the respective type. Thus, the following two calls are equivalent:

julia> xclbin.kernels[1].args[1].mems[1].type
ddr4

julia> XRT.XRTWrap.get_type(XRT.XRTWrap.get_mems(XRT.XRTWrap.get_args(XRT.XRTWrap.get_kernels(xclbin.xclbin)[1])[1])[1])
ddr4

Similar to the XRT.XilinxDevice type, the information provided by the XRT.Xclbin object can be printed in a human-readable way by calling the show function:

julia> xclbin = XRT.Xclbin("path/to/communication_PCIE.xclbin")
┌────────┬──────────────────────────────────────────────────────────────────────────────────────┐
│   hw   │                     /absolute/path/to/communication_PCIE.xclbin                      │
└────────┴──────────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────┬────────────────────────────────────────────────────────────────────────────┐
│         XSA Name │                    xilinx_u280_gen3x16_xdma_1_202211_1                     │
│ FPGA Device Name │                    virtexuplusHBM:xcu280:fsvh2892:-2L:e                    │
│             UUID │                    0c0b1f4c-9b50-464d-bc14-1b34dea8fdb2                    │
└──────────────────┴────────────────────────────────────────────────────────────────────────────┘
Kernels:
┌───────┬─────────────┬─────────┬───────────────────────────────────────┐
│ Index │ Kernel Name │ No. CUs │ Arguments                             │
├───────┼─────────────┼─────────┼───────────────────────────────────────┤
│   1   │ dummyKernel │    1    │ char*      char           int         │
│       │             │         │ output_r   verification   messageSize │
└───────┴─────────────┴─────────┴───────────────────────────────────────┘

julia> xclbin.kernels[1]
================
  dummyKernel: 
================

Compute Units:
┌───────┬─────────────────────────┬──────┬──────────────┬──────────────┬──────┬──────────┐
│ Index │         CU Name         │ Type │ Control Type │ Base Address │ Size │ No. Args │
├───────┼─────────────────────────┼──────┼──────────────┼──────────────┼──────┼──────────┤
│   1   │ dummyKernel:dummyKernel │  pl  │    chain     │   0x800000   │  44  │    3     │
└───────┴─────────────────────────┴──────┴──────────────┴──────────────┴──────┴──────────┘
Arguments:
┌───────┬───────────┬──────────────┬───────────────┬──────────────┬────────┬──────────────────────────┐
│ Index │ Host Type │     Name     │     Port      │ Size [Bytes] │ Offset │ Connected Memory Objects │
├───────┼───────────┼──────────────┼───────────────┼──────────────┼────────┼──────────────────────────┤
│   1   │   char*   │   output_r   │  M_AXI_GMEM   │      8       │   16   │          bank0           │
│   2   │   char    │ verification │ S_AXI_CONTROL │      4       │   28   │                          │
│   3   │    int    │ messageSize  │ S_AXI_CONTROL │      4       │   36   │                          │
└───────┴───────────┴──────────────┴───────────────┴──────────────┴────────┴──────────────────────────┘

Visualize XCLBIN Streams

To simplify the analysis of Xclbin files, kernels, compute units, memory objects and their connections to each other can be written to the console using XRT.jl. Therefore, the information of the XRT.Xclbin type, and our Custom XCLBIN Parser can be printed in a human-readable way, using the XRT.visualize_streams function.

The following code snippet shows an example of a single plotted graph:

julia> XRT.visualize_streams(XRT.Xclbin("path/to/communication_PCIE.xclbin"))
┌─────────────┐                                     
│   Kernel:   │                                     
│             ├────────────────────────────────────┐
│ dummyKernel │                                    │
└─────┬───────┘                                    │
      │                                            │
      │       ┌───────────────┐     ┌─────────┐    │
      └──────►┤ Compute Unit: ├►─┐  │ Memory: │    │
              │               │  │  │         ├◄───┘
              │  dummyKernel  │  └─►┤  bank0  │     
              └───────────────┘     └─────────┘     

As the graphs created can very quickly become too large and confusing when the bitstream becomes more complex, it is possible to filter out only a single kernel or compute unit by its name:

julia> XRT.visualize_streams(XRT.Xclbin("path/to/stream.xclbin"), "k1")
┌─────────────┐                                     
│   Kernel:   │                                     
│             ├────────────────────────────────────┐
│ stream_calc │                                    │
└─────┬───────┘                                    │
      │                                            │
      │       ┌───────────────┐     ┌─────────┐    │
      └──────►┤ Compute Unit: ├►─┐  │ Memory: │    │
              │               │  │  │         ├◄───┘
              │       k1      │  └─►┤  DDR[0] │     
              └───────────────┘     └─────────┘     

Custom XCLBIN Parser

In addition to the XRT.Xclbin type, XRT.jl comes with a custom parser for the Xclbin bitstream container format, since the native API does not provide the entire intended range of functions. This parser allows extended user-defined inspection of Xclbin files.

The most important functions provided are get_kernel_info(path) and get_system_info(path) which both take as input a path to the bitstream file. The functions extract the JSON data that is encoded in the bitstream and return it as a LazyJSON data structure.

get_kernel_info(path) returns data about the implemented compute kernel, such as input parameters, compute instances, memory addresses, and offset. get_system_info(path) returns information about resource utilization of individual compute units and the available resources on the system.

Example

For our simple dummy kernel that is also used in the examples, we can get the resource utilization of the kernel like this:

julia> js = XRT.get_system_info("path/to/communication_PCIE.xclbin")
1-element LazyJSON.Array{Nothing, String}:
 LazyJSON.Object{Nothing, String}("name" => ...

julia> js[1]["compute_units"][1]
LazyJSON.Object{Nothing, String}(...):
  "id"               => "0"
  "kernel_name"      => "dummyKernel"
  "cu_name"          => "dummyKernel"
  "base_address"     => "0x800000"
  "actual_resources" => Any[Object{Nothing, String}("design_state"=>"routed", "…
  "clock_name"       => ""
  "clock_id"         => 0
  "clocks"           => Any[Object{Nothing, String}("port_name"=>"ap_clk", "id"…
  "reset_port_names" => Any["ap_rst_n"]
  "slr_resources"    => Any[Object{Nothing, String}("slr_name"=>"SLR0", "resour…

Loading an Xclbin onto a Device

In order to execute the kernels stored in one Xclbin file, they first need to be loaded onto an available device. This can be done using the XRT.load_xclbin! function:

julia> xclbin_uuid = load_xclbin!(xclbin)

The function returns the UUID of the Xclbin file. It is also possible to set the device manually using the device keyword argument. The loading process is skipped, when the Xclbin is already on the device by comparing the XRT.get_xclbin_uuid return value. However, it can be forced by setting the force keyword argument.