Class Methods and Operators - 2022.2 English

Vitis High-Level Synthesis User Guide (UG1399)

Document ID
UG1399
Release Date
2022-12-07
Version
2022.2 English

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 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)