Unions - 2023.1 English

Vitis High-Level Synthesis User Guide (UG1399)

Document ID
UG1399
Release Date
2023-07-17
Version
2023.1 English

In the following code example, a union is created with a double and a struct. Unlike C/C++ compilation, synthesis does not guarantee using the same memory (in the case of synthesis, registers) for all fields in the union. Vitis HLS perform the optimization that provides the most optimal hardware.

#include "types_union.h"

dout_t types_union(din_t N, dinfp_t F)
{
 union {
    struct {int a; int b; } intval;
    double fpval;
 } intfp;
 unsigned long long one, exp;

 // Set a floating-point value in union intfp
 intfp.fpval = F;

 // Slice out lower bits and add to shifted input
 one = intfp.intval.a;
 exp = (N & 0x7FF);

 return ((exp << 52) + one) & (0x7fffffffffffffffLL);
}

Vitis HLS does not support the following:

  • Unions on the top-level function interface.
  • Pointer reinterpretation for synthesis. Therefore, a union cannot hold pointers to different types or to arrays of different types.
  • Access to a union through another variable. Using the same union as the previous example, the following is not supported:
    for (int i = 0; i < 6; ++i)
    if (i<3) 
     A[i] = intfp.intval.a + B[i];
     else
     A[i] = intfp.intval.b + B[i];
    }
  • However, it can be explicitly re-coded as:
     A[0] = intfp.intval.a + B[0];
     A[1] = intfp.intval.a + B[1];
     A[2] = intfp.intval.a + B[2];
     A[3] = intfp.intval.b + B[3];
     A[4] = intfp.intval.b + B[4];
     A[5] = intfp.intval.b + B[5];

The synthesis of unions does not support casting between native C/C++ types and user-defined types.

Often with Vitis HLS designs, unions are used to convert the raw bits from one data type to another data type. Generally, this raw bit conversion is needed when using floating point values at the top-level port interface. For one example, see below:

typedef float T;
unsigned int value; // the "input"€ of the conversion
T myhalfvalue; // the "output" of the conversion
union
{
  unsigned int as_uint32;
  T as_floatingpoint;
} my_converter;
my_converter.as_uint32 = value;
myhalfvalue = my_converter. as_floatingpoint;

This type of code is fine for float C/C++ data types and with modification, it is also fine for double data types. Changing the typedef and the int to short will not work for half data types, however, because half is a class and cannot be used in a union. Instead, the following code can be used:

typedef half T;
short value;
T myhalfvalue = static_cast<T>(value);

Similarly, the conversion the other way around uses value=static_cast<ap_uint<16> >(myhalfvalue) or static_cast< unsigned short >(myhalfvalue).

ap_fixed<16,4> afix = 1.5;
ap_fixed<20,6> bfix = 1.25;
half ahlf = afix.to_half();
half bhlf = bfix.to_half();

Another method is to use the helper class fp_struct<half> to make conversions using the methods data() or to_int(). Use the header file hls/utils/x_hls_utils.h.