The native data types in C++ are on 8-bit boundaries (8, 16, 32, and 64 bits). RTL signals and operations support arbitrary bit-lengths.
Vitis HLS provides arbitrary precision data types for C++ to allow variables and operations in the C++ code to be specified with any arbitrary bit-widths: 6-bit, 17-bit, 234-bit, up to 1024 bits.
AP_INT_MAX_W
with a positive integer value less than or equal to
4096 before inclusion of the ap_int.h header
file.Arbitrary precision data types have are two primary advantages over the native C++ types:
- Better quality hardware: If for example, a 17-bit multiplier is required,
arbitrary precision types can specify that exactly 17-bit are used in the calculation.
Without arbitrary precision data types, such a multiplication (17-bit) must be implemented using 32-bit integer data types and result in the multiplication being implemented with multiple DSP modules.
- Accurate C++ simulation/analysis: Arbitrary precision data types in the C++ code allows the C++ simulation to be performed using accurate bit-widths and for the C++ simulation to validate the functionality (and accuracy) of the algorithm before synthesis.
The arbitrary precision types in C++ have none of the disadvantages of those in C:
- C++ arbitrary types can be compiled with standard C++ compilers (there
is no C++ equivalent of
apcc
). - C++ arbitrary precision types do not suffer from Integer Promotion Issues.
It is not uncommon for users to change a file extension from .c to .cpp so the file can be compiled as C++, where neither of these issues are present.
For the C++ language, the header file ap_int.h defines the arbitrary precision integer data types ap_(u)int<W>
. For example, ap_int<8>
represents an 8-bit signed integer data type and ap_uint<234>
represents a 234-bit unsigned integer
type.
The ap_int.h file is located in the directory $HLS_ROOT/include, where $HLS_ROOT is the Vitis HLS installation directory.
The code shown in the following example is a repeat of the code shown in
the Basic Arithmetic example in Standard Types. In this example, the data types in the
top-level function to be synthesized are specified as dinA_t
, dinB_t
, and so on.
#include "cpp_ap_int_arith.h"
void cpp_ap_int_arith(din_A inA, din_B inB, din_C inC, din_D inD,
dout_1 *out1, dout_2 *out2, dout_3 *out3, dout_4 *out4
) {
// Basic arithmetic operations
*out1 = inA * inB;
*out2 = inB + inA;
*out3 = inC / inA;
*out4 = inD % inA;
}
In this latest update to this example, the C++ arbitrary precision types are used:
- Add header file ap_int.h to the source code.
- Change the native C++ types to arbitrary precision types
ap_int<N>
orap_uint<N>
, whereN
is a bit-size from 1 to 1024 (as noted above, this can be extended to 4K-bits if required).
The data types are defined in the header cpp_ap_int_arith.h.
Compared with the Basic Arithmetic example in Standard Types, the input data types have
simply been reduced to represent the maximum size of the real input data (for example, 8-bit
input inA
is reduced to 6-bit input). The output types have
been refined to be more accurate, for example, out2
, the
sum of inA
and inB
, need
only be 13-bit and not 32-bit.
The following example shows basic arithmetic with C++ arbitrary precision types.
#ifndef _CPP_AP_INT_ARITH_H_
#define _CPP_AP_INT_ARITH_H_
#include <stdio.h>
#include "ap_int.h"
#define N 9
// Old data types
//typedef char dinA_t;
//typedef short dinB_t;
//typedef int dinC_t;
//typedef long long dinD_t;
//typedef int dout1_t;
//typedef unsigned int dout2_t;
//typedef int32_t dout3_t;
//typedef int64_t dout4_t;
typedef ap_int<6> dinA_t;
typedef ap_int<12> dinB_t;
typedef ap_int<22> dinC_t;
typedef ap_int<33> dinD_t;
typedef ap_int<18> dout1_t;
typedef ap_uint<13> dout2_t;
typedef ap_int<22> dout3_t;
typedef ap_int<6> dout4_t;
void cpp_ap_int_arith(dinA_t inA,dinB_t inB,dinC_t inC,dinD_t inD,dout1_t
*out1,dout2_t *out2,dout3_t *out3,dout4_t *out4);
#endif
If C++ Arbitrary Precision Integer Types are synthesized, it results in a design that is functionally identical
to Standard Types. Rather
than use the C++ cout
operator to output the results to a
file, the built-in ap_int
method .to_int()
is used to convert the ap_int
results
to integer types used with the standard fprintf
function.
fprintf(fp, %d*%d=%d; %d+%d=%d; %d/%d=%d; %d mod %d=%d;\n,
inA.to_int(), inB.to_int(), out1.to_int(),
inB.to_int(), inA.to_int(), out2.to_int(),
inC.to_int(), inA.to_int(), out3.to_int(),
inD.to_int(), inA.to_int(), out4.to_int());