Structs in the Interface - 2024.1 English

Vitis High-Level Synthesis User Guide (UG1399)

Document ID
UG1399
Release Date
2024-07-03
Version
2024.1 English

Structs in the interface are kept aggregated by Vitis HLS by default; combining all of the elements of a struct into a single wide vector. This allows all members of the struct to be read and written-to simultaneously. You can disaggregate structs in the interface by using the DISAGGREGATE pragma or directive. When a struct contains one or more hls::stream objects Vitis HLS will automatically disaggregate the struct as described below in Structs in the Interface with hls::stream Elements.

Important: Disaggregating a struct in the interface is not supported in the Vitis kernel flow because the Vitis tool cannot map a single C-argument to multiple RTL ports. When disaggregating a struct in the interface, either manually or automatically, Vitis HLS will build and export the Vitis kernel output (.xo), but that output will result in an error when used with the v++ command. To support the Vitis Kernel flow you must manually break the struct into its constituent elements, and define any hls::stream objects as using an AXIS interface.

As part of aggregation, the elements of the struct are also aligned on a 4 byte alignment for the Vitis kernel flow, and on 1 byte alignment for the Vivado IP flow. This alignment might require the addition of bit padding to keep or make things aligned, as discussed in Struct Padding and Alignment. By default the aggregated struct is padded rather than packed, but in the Vivado IP flow you can pack it using the compact=bit option of the AGGREGATE pragma or directive. However, any port that gets defined as an AXI4 interface (m_axi, s_axilite, or axis) cannot use compact=bit.

The member elements of the struct are placed into the vector in the order they appear in the C/C++ code: the first element of the struct is aligned on the LSB of the vector and the final element of the struct is aligned with the MSB of the vector. This allows more data to be accessed in a single clock cycle. Any arrays in the struct are partitioned into individual array elements and placed in the vector from lowest to highest, in order.

In the following example, struct data_t is defined in the header file shown. The struct has two data members:

  • An unsigned vector varA of type short (16-bit).
  • An array varB of four unsigned char types (8-bit).
    typedef struct {
       unsigned short varA;
       unsigned char varB[4];
       } data_t;
    
    data_t struct_port(data_t i_val, data_t *i_pt, data_t *o_pt);
    

Aggregating the struct on the interface results in a single 48-bit port containing 16 bits of varA, and 4x8 bits of varB.

Tip: The maximum bit-width of any port or bus created by data packing is 8192 bits, or 4096 bits for axis streaming interfaces.

There are no limitations in the size or complexity of structs that can be synthesized by Vitis HLS. There can be as many array dimensions and as many members in a struct as required. The only limitation with the implementation of structs occurs when arrays are to be implemented as streaming (such as a FIFO interface). In this case, follow the same general rules that apply to arrays on the interface (FIFO Interfaces).

Structs on the Interface with hls::stream Elements

User-defined structs on the interface containing hls::stream elements are automatically disaggregated by Vitis HLS. This disaggregated struct is supported in the Vivado IP flow, and the exported IP will work as expected. However, this disaggregated struct is not supported for the Vitis Kernel flow, and the exported kernel (.xo) will cause an error when used with the v++ --link command. To support the Vitis Kernel flow you must manually break the struct into its constituent elements, and define the hls::stream object as using an AXIS interface.

If you have a struct that is disaggregated automatically, Vitis HLS applies any INTERFACE pragmas to the individual elements of the disaggregated struct. If there is only one INTERFACE pragma specified for the struct, it is applied to each element of the struct. If you provide an INTERFACE pragma for each element of the disaggregated struct, it is applied as expected.