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.

TIP: The default maximum width allowed is 1024 bits. You can override this default by defining the macro 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> or ap_uint<N>, where N 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.

CAUTION: Setting the value of 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);
Note: To avoid unexpected behavior during co-simulation, do not initialize 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:

  1. Convert the value to a C++ std::string using the ap_[u]fixed classes method to_string().
  2. Convert the result to a null-terminated C character string using the std::string class method c_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.

IMPORTANT: Vitis HLS synthesis of the modulus (%) operator will lead to lead to instantiation of appropriately parameterized Xilinx LogiCORE divider cores in the generated RTL.

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)
CAUTION: When assigning the result of a shift-left operator to a wider destination variable, some or all information may be lost. Xilinx recommends that you explicitly cast the shift expression to the destination type to avoid unexpected behavior.

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.

Note: To avoid unexpected results, explicitly cast C/C++ native types (including integer literals) to an appropriate 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!
Note: The object returned by range select is not an 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 this ap_(u)int.
  • Returns the resulting single bit.
  • Equivalent to comparing this value against -1 (all ones) and returning true if it matches, false otherwise.
OR reduce
bool ap_(u)int::or_reduce ()
  • Applies the OR operation on all bits in this ap_(u)int.
  • Returns the resulting single bit.
  • Equivalent to comparing this value against 0 (all zeros) and returning false if it matches, true otherwise.
XOR reduce
bool ap_(u)int::xor_reduce ()
  • Applies the XOR operation on all bits in this ap_int.
  • Returns the resulting single bit.
  • Equivalent to counting the number of 1 bits in this value and returning false if the count is even or true if the count is odd.
NAND reduce
bool ap_(u)int::nand_reduce ()
  • Applies the NAND operation on all bits in this ap_int.
  • Returns the resulting single bit.
  • Equivalent to comparing this value against -1 (all ones) and returning false if it matches, true otherwise.
NOR reduce
bool ap_int::nor_reduce ()
  • Applies the NOR operation on all bits in this ap_int.
  • Returns the resulting single bit.
  • Equivalent to comparing this value against 0 (all zeros) and returning true if it matches, false otherwise.
XNOR reduce
bool ap_(u)int::xnor_reduce ()
  • Applies the XNOR operation on all bits in this ap_(u)int.
  • Returns the resulting single bit.
  • Equivalent to counting the number of 1 bits in this value and returning true if the count is even or false 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 the ap_[u]int.
  • If the ap_[u]int is wider than 53 bits (the number of bits in the mantissa of a double), the resulting double may not have the exact value expected.
Note: Xilinx recommends that you explicitly call member functions instead of using C-style cast to convert 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.