C++ Arbitrary Precision Integer Types
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());
C++ Arbitrary Precision Integer Types: Reference Information
For comprehensive information on the methods, synthesis behavior, and all aspects
of using the ap_(u)int<N>
arbitrary precision data
types, see C++ Arbitrary Precision Types. This section includes:
- Techniques for assigning constant and initialization values to arbitrary precision integers (including values greater than 1024-bit).
- A description of Vitis HLS helper methods, such as printing, concatenating, bit-slicing and range selection functions.
- A description of operator behavior, including a description of shift operations (a negative shift values, results in a shift in the opposite direction).
C++ Arbitrary Precision Types
Vitis HLS provides a C++ template class,
ap_[u]int<>
, that implements arbitrary
precision (or bit-accurate) integer data types with consistent, bit-accurate behavior
between software and hardware modeling.
This class provides all arithmetic, bitwise, logical and relational operators allowed for native C integer types. In addition, this class provides methods to handle some useful hardware operations, such as allowing initialization and conversion of variables of widths greater than 64 bits. Details for all operators and class methods are discussed below.
Compiling ap_[u]int<> Types
To use the ap_[u]int<>
classes, you must
include the ap_int.h
header file in all source files that reference
ap_[u]int<>
variables.
When compiling software models that use these classes, it may be necessary
to specify the location of the Vitis HLS header files, for example by
adding the -I/<HLS_HOME>/include
option
for g++
compilation.
Declaring/Defining ap_[u] Variables
There are separate signed and unsigned classes:
ap_int<int_W>
(signed)ap_uint<int_W>
(unsigned)
The template parameter int_W
specifies the total
width of the variable being declared.
User-defined types may be created with the C/C++ typedef
statement as shown in the following examples:
include "ap_int.h"// use ap_[u]fixed<> types
typedef ap_uint<128> uint128_t; // 128-bit user defined type
ap_int<96> my_wide_var; // a global variable declaration
The default maximum width allowed is 1024 bits. This default may be overridden
by defining the macro AP_INT_MAX_W
with a positive
integer value less than or equal to 32768 before inclusion of the ap_int.h
header file.
AP_INT_MAX_W
too High can
cause slow software compile and runtimes.Following is an example of overriding AP_INT_MAX_W
:
#define AP_INT_MAX_W 4096 // Must be defined before next line
#include "ap_int.h"
ap_int<4096> very_wide_var;
Initialization and Assignment from Constants (Literals)
The class constructor and assignment operator overloads, allows initialization
of and assignment to ap_[u]fixed<>
variables
using standard C/C++ integer literals.
This method of assigning values to ap_[u]fixed<>
variables is subject to the limitations of C++ and the system upon which
the software will run. This typically leads to a 64-bit limit on integer
literals (for example, for those LL
or ULL
suffixes).
To allow assignment of values wider than 64-bits, the ap_[u]fixed<>
classes provide constructors that allow initialization from a string
of arbitrary length (less than or equal to the width of the variable).
By default, the string provided is interpreted as a hexadecimal value as long as it contains only valid hexadecimal digits (that is, 0-9 and a-f). To assign a value from such a string, an explicit C++ style cast of the string to the appropriate type must be made.
Following are examples of initialization and assignments, including for values greater than 64-bit, are:
ap_int<42> a_42b_var(-1424692392255LL); // long long decimal format
a_42b_var = 0x14BB648B13FLL; // hexadecimal format
a_42b_var = -1; // negative int literal sign-extended to full width
ap_uint<96> wide_var(“76543210fedcba9876543210”, 16); // Greater than 64-bit
wide_var = ap_int<96>(“0123456789abcdef01234567”, 16);
ap_uint<N> a ={0}
.The ap_[u]<>
constructor may be explicitly
instructed to interpret the string as representing the number in radix
2, 8, 10, or 16 formats. This is accomplished by adding the appropriate
radix value as a second parameter to the constructor call.
A compilation error occurs if the string literal contains any characters that are invalid as digits for the radix specified.
The following examples use different radix formats:
ap_int<6> a_6bit_var(“101010”, 2); // 42d in binary format
a_6bit_var = ap_int<6>(“40”, 8); // 32d in octal format
a_6bit_var = ap_int<6>(“55”, 10); // decimal format
a_6bit_var = ap_int<6>(“2A”, 16); // 42d in hexadecimal format
a_6bit_var = ap_int<6>(“42”, 2); // COMPILE-TIME ERROR! “42” is not binary
The radix of the number encoded in the string can also be inferred by
the constructor, when it is prefixed with a zero (0
)
followed by one of the following characters: “b
”,
“o
” or “x
”.
The prefixes “0b
”, “0o
”
and “0x
” correspond to binary, octal
and hexadecimal formats respectively.
The following examples use alternate initializer string formats:
ap_int<6> a_6bit_var(“0b101010”, 2); // 42d in binary format
a_6bit_var = ap_int<6>(“0o40”, 8); // 32d in octal format
a_6bit_var = ap_int<6>(“0x2A”, 16); // 42d in hexidecimal format
a_6bit_var = ap_int<6>(“0b42”, 2); // COMPILE-TIME ERROR! “42” is not binary
If the bit-width is greater than 53-bits, the ap_[u]fixed
value must be initialized with a string, for example:
ap_ufixed<72,10> Val(“2460508560057040035.375”);
Support for Console I/O (Printing)
As with initialization and assignment to ap_[u]fixed<>
variables, Vitis HLS supports printing values that require more than
64-bits to represent.
Using the C++ Standard Output Stream
The easiest way to output any value stored in an ap_[u]int
variable is to use the C++ standard output stream:
std::cout (#include <iostream>
or <iostream.h>
)
The stream insertion operator (<<
) is overloaded to
correctly output the full range of values possible for any given
ap_[u]fixed
variable. The following stream manipulators
are also supported:
- dec (decimal)
- hex (hexadecimal)
- oct (octal)
These allow formatting of the value as indicated.
The following example uses cout
to print values:
#include <iostream.h>
// Alternative: #include <iostream>
ap_ufixed<72> Val(“10fedcba9876543210”);
cout << Val << endl; // Yields: “313512663723845890576”
cout << hex << val << endl; // Yields: “10fedcba9876543210”
cout << oct << val << endl; // Yields: “41773345651416625031020”
Using the Standard C Library
You can also use the standard C library (#include
<stdio.h>
) to print out values larger than 64-bits:
- Convert the value to a C++
std::string
using theap_[u]fixed
classes methodto_string()
. - Convert the result to a null-terminated C character string using the
std::string
class methodc_str()
.
Optional Argument One (Specifying the Radix)
You can pass the ap[u]int::to_string()
method an optional
argument specifying the radix of the numerical format desired. The valid radix
argument values are:
- 2 (binary) (default)
- 8 (octal)
- 10 (decimal)
- 16 (hexadecimal)
Optional Argument Two (Printing as Signed Values)
A second optional argument to ap_[u]int::to_string()
specifies
whether to print the non-decimal formats as signed values. This argument is boolean.
The default value is false, causing the non-decimal formats to be printed as
unsigned values.
The following examples use printf
to print values:
ap_int<72> Val(“80fedcba9876543210”);
printf(“%s\n”, Val.to_string().c_str()); // => “80FEDCBA9876543210”
printf(“%s\n”, Val.to_string(10).c_str()); // => “-2342818482890329542128”
printf(“%s\n”, Val.to_string(8).c_str()); // => “401773345651416625031020”
printf(“%s\n”, Val.to_string(16, true).c_str()); // => “-7F0123456789ABCDF0”
Expressions Involving ap_[u]<> types
Variables of ap_[u]<>
types may generally
be used freely in expressions involving C/C++ operators. Some behaviors
may be unexpected. These are discussed in detail below.
Zero- and Sign-Extension on Assignment From Narrower to Wider Variables
When assigning the value of a narrower bit-width signed
(ap_int<>
) variable to a wider one, the value is
sign-extended to the width of the destination variable, regardless of its
signedness.
Similarly, an unsigned source variable is zero-extended before assignment.
Explicit casting of the source variable may be necessary to ensure expected behavior on assignment. See the following example:
ap_uint<10> Result;
ap_int<7> Val1 = 0x7f;
ap_uint<6> Val2 = 0x3f;
Result = Val1; // Yields: 0x3ff (sign-extended)
Result = Val2; // Yields: 0x03f (zero-padded)
Result = ap_uint<7>(Val1); // Yields: 0x07f (zero-padded)
Result = ap_int<6>(Val2); // Yields: 0x3ff (sign-extended)
Truncation on Assignment of Wider to Narrower Variables
Assigning the value of a wider source variable to a narrower one leads to truncation of the value. All bits beyond the most significant bit (MSB) position of the destination variable are lost.
There is no special handling of the sign information during truncation. This may lead to unexpected behavior. Explicit casting may help avoid this unexpected behavior.
Class Methods and Operators
The ap_[u]int
types do not support implicit conversion
from wide ap_[u]int
(>64bits) to builtin C/C++
integer types. For example, the following code example return s1, because
the implicit cast from ap_int[65]
to bool
in the if-statement returns a 0.
bool nonzero(ap_uint<65> data) {
return data; // This leads to implicit truncation to 64b int
}
int main() {
if (nonzero((ap_uint<65>)1 << 64)) {
return 0;
}
printf(FAIL\n);
return 1;
}
To convert wide ap_[u]int
types to built-in
integers, use the explicit conversion functions included with the ap_[u]int
types:
to_int()
to_long()
to_bool()
In general, any valid operation that can be done on a native C/C++ integer data
type is supported using operator overloading for ap_[u]int
types.
In addition to these overloaded operators, some class specific operators and methods are included to ease bit-level operations.
Binary Arithmetic Operators
Standard binary integer arithmetic operators are overloaded to provide arbitrary precision arithmetic. These operators take either:
- Two operands of
ap_[u]int
, or - One
ap_[u]int
type and one C/C++ fundamental integer data type
For example:
- char
- short
- int
The width and signedness of the resulting value is determined by the width and signedness of the operands, before sign-extension, zero-padding or truncation are applied based on the width of the destination variable (or expression). Details of the return value are described for each operator.
When expressions contain a mix of ap_[u]int
and C/C++ fundamental integer types, the C++ types assume the
following widths:
char
(8-bits)short
(16-bits)int
(32-bits)long
(32-bits)long long
(64-bits)
Addition
ap_(u)int::RType ap_(u)int::operator + (ap_(u)int op)
Returns the sum of:
- Two
ap_[u]int
, or - One
ap_[u]int
and a C/C++ integer type
The width of the sum value is:
- One bit more than the wider of the two operands, or
- Two bits if and only if the wider is unsigned and the narrower is signed
The sum is treated as signed if either (or both) of the operands is of a signed type.
Subtraction
ap_(u)int::RType ap_(u)int::operator - (ap_(u)int op)
Returns the difference of two integers.
The width of the difference value is:
- One bit more than the wider of the two operands, or
- Two bits if and only if the wider is unsigned and the narrower signed
This is true before assignment, at which point it is sign-extended, zero-padded, or truncated based on the width of the destination variable.
The difference is treated as signed regardless of the signedness of the operands.
Multiplication
ap_(u)int::RType ap_(u)int::operator * (ap_(u)int op)
Returns the product of two integer values.
The width of the product is the sum of the widths of the operands.
The product is treated as a signed type if either of the operands is of a signed type.
Division
ap_(u)int::RType ap_(u)int::operator / (ap_(u)int op)
Returns the quotient of two integer values.
The width of the quotient is the width of the dividend if the divisor is an unsigned type. Otherwise, it is the width of the dividend plus one.
The quotient is treated as a signed type if either of the operands is of a signed type.
Modulus
ap_(u)int::RType ap_(u)int::operator % (ap_(u)int op)
Returns the modulus, or remainder of integer division, for two integer values.
The width of the modulus is the minimum of the widths of the operands, if they are both of the same signedness.
If the divisor is an unsigned type and the dividend is signed, then the width is that of the divisor plus one.
The quotient is treated as having the same signedness as the dividend.
Following are examples of arithmetic operators:
ap_uint<71> Rslt;
ap_uint<42> Val1 = 5;
ap_int<23> Val2 = -8;
Rslt = Val1 + Val2; // Yields: -3 (43 bits) sign-extended to 71 bits
Rslt = Val1 - Val2; // Yields: +3 sign extended to 71 bits
Rslt = Val1 * Val2; // Yields: -40 (65 bits) sign extended to 71 bits
Rslt = 50 / Val2; // Yields: -6 (33 bits) sign extended to 71 bits
Rslt = 50 % Val2; // Yields: +2 (23 bits) sign extended to 71 bits
Bitwise Logical Operators
The bitwise logical operators all return a value with a width that is the maximum of the widths of the two operands. It is treated as unsigned if and only if both operands are unsigned. Otherwise, it is of a signed type.
Sign-extension (or zero-padding) may occur, based on the signedness of the expression, not the destination variable.
Bitwise OR
ap_(u)int::RType ap_(u)int::operator | (ap_(u)int op)
Returns the bitwise OR
of the two operands.
Bitwise AND
ap_(u)int::RType ap_(u)int::operator & (ap_(u)int op)
Returns the bitwise AND
of the two operands.
Bitwise XOR
ap_(u)int::RType ap_(u)int::operator ^ (ap_(u)int op)
Returns the bitwise XOR
of the two operands.
Unary Operators
Addition
ap_(u)int ap_(u)int::operator + ()
Returns the self copy of the ap_[u]int
operand.
Subtraction
ap_(u)int::RType ap_(u)int::operator - ()
Returns the following:
- The negated value of the operand with the same width if it is a signed type, or
- Its width plus one if it is unsigned.
The return value is always a signed type.
Bitwise Inverse
ap_(u)int::RType ap_(u)int::operator ~ ()
Returns the bitwise-NOT
of the operand with the same width and
signedness.
Logical Invert
bool ap_(u)int::operator ! ()
Returns a Boolean false
value if and only if the operand is
not equal to zero (0
).
Returns a Boolean true
value if the operand is equal to zero
(0
).
Ternary Operators
When you use the ternary operator with the standard C int
type, you
must explicitly cast from one type to the other to ensure that both results have the same
type. For example:
// Integer type is cast to ap_int type
ap_int<32> testc3(int a, ap_int<32> b, ap_int<32> c, bool d) {
return d?ap_int<32>(a):b;
}
// ap_int type is cast to an integer type
ap_int<32> testc4(int a, ap_int<32> b, ap_int<32> c, bool d) {
return d?a+1:(int)b;
}
// Integer type is cast to ap_int type
ap_int<32> testc5(int a, ap_int<32> b, ap_int<32> c, bool d) {
return d?ap_int<33>(a):b+1;
}
Shift Operators
Each shift operator comes in two versions:
- One version for unsigned right-hand side (RHS) operands
- One version for signed right-hand side (RHS) operands
A negative value supplied to the signed RHS versions reverses the shift operations direction. That is, a shift by the absolute value of the RHS operand in the opposite direction occurs.
The shift operators return a value with the same width as the left-hand side (LHS) operand. As with C/C++, if the LHS operand of a shift-right is a signed type, the sign bit is copied into the most significant bit positions, maintaining the sign of the LHS operand.
Unsigned Integer Shift Right
ap_(u)int ap_(u)int::operator << (ap_uint<int_W2> op)
Integer Shift Right
ap_(u)int ap_(u)int::operator << (ap_int<int_W2> op)
Unsigned Integer Shift Left
ap_(u)int ap_(u)int::operator >> (ap_uint<int_W2> op)
Integer Shift Left
ap_(u)int ap_(u)int::operator >> (ap_int<int_W2> op)
Following are examples of shift operations:
ap_uint<13> Rslt;
ap_uint<7> Val1 = 0x41;
Rslt = Val1 << 6; // Yields: 0x0040, i.e. msb of Val1 is lost
Rslt = ap_uint<13>(Val1) << 6; // Yields: 0x1040, no info lost
ap_int<7> Val2 = -63;
Rslt = Val2 >> 4; //Yields: 0x1ffc, sign is maintained and extended
Compound Assignment Operators
Vitis HLS supports compound assignment operators:
- *=
- /=
- %=
- +=
- -=
- <<=
- >>=
- &=
- ^=
- |=
The RHS
expression is first evaluated then supplied as the
RHS
operand to the base operator, the result of which is assigned
back to the LHS
variable. The expression sizing, signedness, and
potential sign-extension or truncation rules apply as discussed above for the relevant
operations.
ap_uint<10> Val1 = 630;
ap_int<3> Val2 = -3;
ap_uint<5> Val3 = 27;
Val1 += Val2 - Val3; // Yields: 600 and is equivalent to:
// Val1 = ap_uint<10>(ap_int<11>(Val1) +
// ap_int<11>((ap_int<6>(Val2) -
// ap_int<6>(Val3))));
Increment and Decrement Operators
The increment and decrement operators are provided. All return a value of the same width as the operand and which is unsigned if and only if both operands are of unsigned types and signed otherwise.
Pre-Increment
ap_(u)int& ap_(u)int::operator ++ ()
Returns the incremented value of the operand.
Assigns the incremented value to the operand.
Post-Increment
const ap_(u)int ap_(u)int::operator ++ (int)
Returns the value of the operand before assignment of the incremented value to the operand variable.
Pre-Decrement
ap_(u)int& ap_(u)int::operator -- ()
Returns the decremented value of, as well as assigning the decremented value to, the operand.
Post-Decrement
const ap_(u)int ap_(u)int::operator -- (int)
Returns the value of the operand before assignment of the decremented value to the operand variable.
Relational Operators
Vitis HLS supports all relational operators. They return a Boolean value based on the
result of the comparison. You can compare variables of ap_[u]int
types to C/C++ fundamental integer types with these operators.
Equality
bool ap_(u)int::operator == (ap_(u)int op)
Inequality
bool ap_(u)int::operator != (ap_(u)int op)
Less than
bool ap_(u)int::operator < (ap_(u)int op)
Greater than
bool ap_(u)int::operator > (ap_(u)int op)
Less than or equal to
bool ap_(u)int::operator <= (ap_(u)int op)
Greater than or equal to
bool ap_(u)int::operator >= (ap_(u)int op)
Other Class Methods, Operators, and Data Members
The following sections discuss other class methods, operators, and data members.
Bit-Level Operations
The following methods facilitate common bit-level operations on the value stored in
ap_[u]int
type variables.
Length
int ap_(u)int::length ()
Returns an integer value providing the total number of bits in the
ap_[u]int
variable.
Concatenation
ap_concat_ref ap_(u)int::concat (ap_(u)int low)
ap_concat_ref ap_(u)int::operator , (ap_(u)int high, ap_(u)int low)
Concatenates two ap_[u]int
variables, the
width of the returned value is the sum of the widths of the operands.
The High and Low arguments are placed in the higher and lower order bits of the result
respectively; the concat()
method places the argument in the lower order
bits.
When using the overloaded comma operator, the parentheses are required. The comma operator version may also appear on the LHS of assignment.
ap_[u]int
type before concatenating.ap_uint<10> Rslt;
ap_int<3> Val1 = -3;
ap_int<7> Val2 = 54;
Rslt = (Val2, Val1); // Yields: 0x1B5
Rslt = Val1.concat(Val2); // Yields: 0x2B6
(Val1, Val2) = 0xAB; // Yields: Val1 == 1, Val2 == 43
Bit Selection
ap_bit_ref ap_(u)int::operator [] (int bit)
Selects one bit from an arbitrary precision integer value and returns it.
The returned value is a reference value that can set or clear the
corresponding bit in this ap_[u]int
.
The bit argument must be an int
value. It
specifies the index of the bit to select. The least significant bit has index 0. The highest
permissible index is one less than the bit-width of this ap_[u]int
.
The result type ap_bit_ref
represents the reference to one bit of this
ap_[u]int
instance specified by bit.
Range Selection
ap_range_ref ap_(u)int::range (unsigned Hi, unsigned Lo)
ap_range_ref ap_(u)int::operator () (unsigned Hi, unsigned Lo)
Returns the value represented by the range of bits specified by the arguments.
The Hi
argument specifies the most significant bit (MSB) position of
the range, and Lo
specifies the least significant bit (LSB).
The LSB of the source variable is in position 0. If the Hi
argument has
a value less than Lo
, the bits are returned in reverse order.
ap_uint<4> Rslt;
ap_uint<8> Val1 = 0x5f;
ap_uint<8> Val2 = 0xaa;
Rslt = Val1.range(3, 0); // Yields: 0xF
Val1(3,0) = Val2(3, 0); // Yields: 0x5A
Val1(3,0) = Val2(4, 1); // Yields: 0x55
Rslt = Val1.range(4, 7); // Yields: 0xA; bit-reversed!
ap_(u)int
object and lacks
operators, but can be used for assignment. To use the range select result in a chained
expression with ap_(u)int
methods, add an explicit
constructor like
below.ap_uint<32> v = 0x8fff0000;
bool r = ap_uint<16>(v.range(23, 8)).xor_reduce();
AND reduce
bool ap_(u)int::and_reduce ()
- Applies the
AND
operation on all bits in thisap_(u)int
. - Returns the resulting single bit.
- Equivalent to comparing this value against
-1
(all ones) and returningtrue
if it matches,false
otherwise.
OR reduce
bool ap_(u)int::or_reduce ()
- Applies the
OR
operation on all bits in thisap_(u)int
. - Returns the resulting single bit.
- Equivalent to comparing this value against
0
(all zeros) and returningfalse
if it matches,true
otherwise.
XOR reduce
bool ap_(u)int::xor_reduce ()
- Applies the
XOR
operation on all bits in thisap_int
. - Returns the resulting single bit.
- Equivalent to counting the number of
1
bits in this value and returningfalse
if the count is even ortrue
if the count is odd.
NAND reduce
bool ap_(u)int::nand_reduce ()
- Applies the
NAND
operation on all bits in thisap_int
. - Returns the resulting single bit.
- Equivalent to comparing this value against
-1
(all ones) and returningfalse
if it matches,true
otherwise.
NOR reduce
bool ap_int::nor_reduce ()
- Applies the
NOR
operation on all bits in thisap_int
. - Returns the resulting single bit.
- Equivalent to comparing this value against
0
(all zeros) and returningtrue
if it matches,false
otherwise.
XNOR reduce
bool ap_(u)int::xnor_reduce ()
- Applies the
XNOR
operation on all bits in thisap_(u)int
. - Returns the resulting single bit.
- Equivalent to counting the number of
1
bits in this value and returningtrue
if the count is even orfalse
if the count is odd.
Bit Reduction Method Examples
ap_uint<8> Val = 0xaa;
bool t = Val.and_reduce(); // Yields: false
t = Val.or_reduce(); // Yields: true
t = Val.xor_reduce(); // Yields: false
t = Val.nand_reduce(); // Yields: true
t = Val.nor_reduce(); // Yields: false
t = Val.xnor_reduce(); // Yields: true
Bit Reverse
void ap_(u)int::reverse ()
Reverses the contents of ap_[u]int
instance:
- The LSB becomes the MSB.
- The MSB becomes the LSB.
Reverse Method Example
ap_uint<8> Val = 0x12;
Val.reverse(); // Yields: 0x48
Test Bit Value
bool ap_(u)int::test (unsigned i)
Checks whether specified bit of ap_(u)int
instance is
1
.
Returns true if Yes, false if No.
Test Method Example
ap_uint<8> Val = 0x12;
bool t = Val.test(5); // Yields: true
Set Bit Value
void ap_(u)int::set (unsigned i, bool v)
void ap_(u)int::set_bit (unsigned i, bool v)
Sets the specified bit of the ap_(u)int
instance to the value of
integer V
.
Set Bit (to 1)
void ap_(u)int::set (unsigned i)
Sets the specified bit of the ap_(u)int
instance to the value
1
(one).
Clear Bit (to 0)
void ap_(u)int:: clear(unsigned i)
Sets the specified bit of the ap_(u)int
instance to the value
0
(zero).
Invert Bit
void ap_(u)int:: invert(unsigned i)
Inverts the bit specified in the function argument of the ap_(u)int
instance. The specified bit becomes 0
if its original value is
1
and vice versa.
Example of bit set, clear and invert bit methods:
ap_uint<8> Val = 0x12;
Val.set(0, 1); // Yields: 0x13
Val.set_bit(4, false); // Yields: 0x03
Val.set(7); // Yields: 0x83
Val.clear(1); // Yields: 0x81
Val.invert(4); // Yields: 0x91
Rotate Right
void ap_(u)int:: rrotate(unsigned n)
Rotates the ap_(u)int
instance n places to right.
Rotate Left
void ap_(u)int:: lrotate(unsigned n)
Rotates the ap_(u)int
instance n places to left.
ap_uint<8> Val = 0x12;
Val.rrotate(3); // Yields: 0x42
Val.lrotate(6); // Yields: 0x90
Bitwise NOT
void ap_(u)int:: b_not()
- Complements every bit of the
ap_(u)int
instance.
ap_uint<8> Val = 0x12;
Val.b_not(); // Yields: 0xED
Bitwise NOT Example
Test Sign
bool ap_int:: sign()
- Checks whether the
ap_(u)int
instance is negative. - Returns
true
if negative. - Returns
false
if positive.
Explicit Conversion Methods
To C/C++ “(u)int”
int ap_(u)int::to_int ()
unsigned ap_(u)int::to_uint ()
- Returns native C/C++ (32-bit on most systems) integers with the value
contained in the
ap_[u]int
. - Truncation occurs if the value is greater than can be represented by an
[unsigned] int
.
To C/C++ 64-bit “(u)int”
long long ap_(u)int::to_int64 ()
unsigned long long ap_(u)int::to_uint64 ()
- Returns native C/C++ 64-bit integers with the value contained in the
ap_[u]int
. - Truncation occurs if the value is greater than can be represented by an
[unsigned] int
.
To C/C++ “double”
double ap_(u)int::to_double ()
- Returns a native C/C++
double
64-bit floating point representation of the value contained in theap_[u]int
. - If the
ap_[u]int
is wider than 53 bits (the number of bits in the mantissa of adouble
), the resultingdouble
may not have the exact value expected.
ap_[u]int
to other data types.Sizeof
The standard C++ sizeof()
function should not be used with
ap_[u]int
or other classes or instance of object. The
ap_int<>
data type is a class and sizeof
returns the storage used by that class or instance object.
sizeof(ap_int<N>)
always returns the number of bytes used. For
example:
sizeof(ap_int<127>)=16
sizeof(ap_int<128>)=16
sizeof(ap_int<129>)=24
sizeof(ap_int<130>)=24
Compile Time Access to Data Type Attributes
The ap_[u]int<>
types are provided with a static member that
allows the size of the variables to be determined at compile time. The data type is provided
with the static const member width
, which is automatically assigned the
width of the data type:
static const int width = _AP_W;
You can use the width
data member to extract the data width of an
existing ap_[u]int<>
data type to create another
ap_[u]int<>
data type at compile time. The following example shows
how the size of variable Res
is defined as 1-bit greater than variables
Val1
and Val2
:
// Definition of basic data type
#define INPUT_DATA_WIDTH 8
typedef ap_int<INPUT_DATA_WIDTH> data_t;
// Definition of variables
data_t Val1, Val2;
// Res is automatically sized at compile-time to be 1-bit greater than data type
data_t
ap_int<data_t::width+1> Res = Val1 + Val2;
This ensures that Vitis HLS correctly models the bit-growth caused by the addition even if
you update the value of INPUT_DATA_WIDTH for data_t
.