Vector Data Types - 2021.2 English

Vitis High-Level Synthesis User Guide (UG1399)

Document ID
UG1399
ft:locale
English (United States)
Release Date
2021-12-15
Version
2021.2 English

HLS Vector Type for SIMD Operations

The Vitis™ HLS library provides the reference implementation for the hls::vector<T, N> type which represent a single-instruction multiple-data (SIMD) vector of N elements of type T:

  • T can be a user-defined type which must provide common arithmetic operations.
  • N must be a positive integer.
  • The best performance is achieved when both the bit-width of T and N are integer powers of 2.

The vector data type is provided to easily model and synthesize SIMD-type vector operations. Refer to Vitis-HLS-Introductory-Examples/Modeling/using_vectors on Github for an example.

Many operators are overloaded to provide SIMD behavior for vector types. SIMD vector operations are characterized by two parameters:

  1. The type of elements that the vector holds.
  2. The number of elements that the vector holds.

The following example defines how the GCC compiler extensions enable support for vector type operations. It essentially provides a method to define the element type through typedef, and uses an attribute to specify the vector size. This new typedef can be used to perform operations on the vector type which are compiled for execution on software targets supporting SIMD instructions. Generally, the size of the vector used during typedef is specific to targets.

typedef int t_simd __attribute__ ((vector_size (16)));
t_simd a, b, c;
c = a + b;

In the case of Vitis HLS vector data type, SIMD operations can be modeled on similar lines. Vitis HLS provides a template type hls::vector that can be used to define SIMD operands. All the operation performed using this type are mapped to hardware during synthesis that will execute these operations in parallel. These operations can be carried out in a loop which can be pipelined with II=1. The following example shows how an eight element vector of integers is defined and used:

typedef  hls::vector<int, 8> t_int8Vec; 
t_int8Vec intVectorA, intVectorB;
.
.
.
void processVecStream(hls::stream<t_int8Vec> &inVecStream1,hls::stream<t_int8Vec> &inVecStream2, hls::stream<int8Vec> &outVecStream)
{
    for(int i=0;i<32;i++)
    {
        #pragma HLS pipeline II=1
        t_int8Vec aVec = inVecStream1.read();
        t_int8Vec bBec = inVecStream2.read();
        //performs a vector operation on 8 integers in parallel
        t_int8Vec cVec = aVec * bVec;
        outVecStream.write(cVec);
    }
}

Vector Data Type Usage

Vitis HLS vector data type can be defined as follows, where T is a primitive or user-defined type with most of the arithmetic operations defined on it. N is an integer greater than zero. Once a vector type variable is declared it can be used like any other primitive type variable to perform arithmetic and logic operations.

#include <hls_vector.h>
hls::vector<T,N>  aVec;

Memory Layout

For any Vitis HLS vector type defined as hls::vector<T,N>, the storage is guaranteed to be contiguous of size sizeof(T)*N and aligned to the greatest power of 2 such that the allocated size is at least sizeof(T)*N. In particular, when N is a power of 2 and sizeof(T) is a power of 2, vector<T, N> is aligned to its total size. This matches vector implementation on most architectures.

Tip: When sizeof(T)*N is an integer power of 2, the allocated size will be exactly sizeof(T)*N, otherwise the allocated size will be larger to make alignment possible.

The following example shows the definition of a vector class that aligns itself as described above.

constexpr size_t gp2(size_t N)
{
    return (N > 0 && N % 2 == 0) ? 2 * gp2(N / 2) : 1;
}
 
template<typename T, size_t N> class alignas(gp2(sizeof(T) * N)) vector
{
    std::array<T, N> data;
};

Following are different examples of alignment:

hls::vector<char,8> char8Vec; // aligns on 8 Bytes boundary
hls::vector<int,8> int8Vec; // aligns on 32 byte boundary

Requirements and Dependencies

Vitis HLS vector types requires support for C++ 14 or later. It has the following dependencies on the standard headers:

  • <array>
    • std::array<T, N>
  • <cassert>
    • assert
  • <initializer_list>
    • std::initializer_list<T>

Supported Operations

  • Initialization:
    hls::vector<int, 4> x; // uninitialized
    hls::vector<int, 4> y = 10; // scalar initialized: all elements set to 10
    hls::vector<int, 4> z = {0, 1, 2, 3}; // initializer list (must have 4 elements)
    hls::vector<ap_int, 4> a; // uninitialized arbitrary precision data type
  • Access:
    The operator[] enables access to individual elements of the vector, similar to a standard array:
    x[i] = ...; // set the element at index i
    ... = x[i]; // value of the element at index i
  • Arithmetic:

    They are defined recursively, relying on the matching operation on T.

    Table 1. Arithmetic Operation
    Operation In Place Expression Reduction (Left Fold)
    Addition += + reduce_add
    Subtraction -= - non-associative
    Multiplication *= * reduce_mult
    Division /= / non-associative
    Remainder %= % non-associative
    Bitwise AND &= & reduce_and
    Bitwise OR |= | reduce_or
    Bitwise XOR ^= ^ reduce_xor
    Shift Left <<= << non-associative
    Shift Right >>= >> non-associative
    Pre-increment ++x none unary operator
    Pre-decrement --x none unary operator
    Post-increment x++ none unary operator
    Post-decrement x-- none unary operator
  • Comparison:

    Lexicographic order on vectors (returns bool):

    Table 2. Operation
    Operation Expression
    Less than <
    Less or equal <=
    Equal ==
    Different !=
    Greater or equal >=
    Greater than >