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
andN
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:
- The type of elements that the vector holds.
- 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.
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 >