Vitis HLS Math Library
The Vitis™ HLS Math Library
(hls_math.h) provides coverage of math
functions from C++ (cmath
) libraries, and can be
used in both C simulation and synthesis. It offers floating-point (single-precision,
double-precision, and half-precision) for all functions and fixed-point support for
the majority of the functions. The functions in hls_math.h is grouped in hls
namespace, and can be used as in-place replacement of function of std
namespace from the standard C++ math library
(cmath
).
HLS Math Library Accuracy
The HLS math functions are implemented as synthesizable bit-approximate functions from the hls_math.h
library. Bit-approximate HLS math library functions do not provide the same accuracy as the standard C function. To achieve the desired result, the bit-approximate implementation might use a different underlying algorithm than the standard C math library version. The accuracy of the function is specified in terms of ULP (Unit of Least Precision). This difference in accuracy has implications for both C simulation and C/RTL co-simulation.
The ULP difference is typically in the range of 1-4 ULP.
- If the standard C math library is used in the C source code, there may be a difference between the C simulation and the C/RTL co-simulation due to the fact that some functions exhibit a ULP difference from the standard C math library.
- If the HLS math library is used in the C source code, there will be no difference between the C simulation and the C/RTL co-simulation. A C simulation using the HLS math library, may however differ from a C simulation using the standard C math library.
In addition, the following seven functions might show some differences, depending on the C standard used to compile and run the C simulation:
- copysign
- fpclassify
- isinf
- isfinite
- isnan
- isnormal
- signbit
C90 mode
Only isinf
, isnan
, and copysign
are usually provided by the system header files, and they
operate on doubles. In particular, copysign
always returns a double result. This might result in unexpected results after
synthesis if it must be returned to a float, because a double-to-float conversion
block is introduced into the hardware.
C99 mode (-std=c99)
All seven functions are usually provided under the expectation that
the system header files will redirect them to __isnan(double)
and __isnan(float)
. The usual GCC header files do not redirect
isnormal
, but implement it in terms of
fpclassify
.
C++ Using math.h
All seven are provided by the system header files, and they operate on doubles.
copysign
always returns a
double result. This might cause unexpected results after synthesis if it must be
returned to a float, because a double-to-float conversion block is introduced into
the hardware.
C++ Using cmath
Similar to C99 mode(-std=c99)
,
except that:
- The system header files are usually different.
- The functions are properly overloaded for:
float(). snan(double)
isinf(double)
copysign
and copysignf
are handled as built-ins even when using
namespace std;
.
C++ Using cmath and namespace std
No issues. Xilinx recommends using the following for best results:
-std=c99
for C-fno-builtin
for C and C++
-std=c99
, use the Tcl command add_files
with the -cflags
option.
Alternatively, use the Edit CFLAGs button in
the Project Settings dialog box.HLS Math Library
The following functions are provided in the HLS math library. Each
function supports half-precision (type half
),
single-precision (type float
) and double precision
(type double
).
func
listed below, there is also an
associated half-precision only function named half_func
and single-precision only function named funcf
provided in the library.When mixing half-precision, single-precision and double-precision data types, check for common synthesis errors to prevent introducing type-conversion hardware in the final FPGA implementation.
Trigonometric Functions
acos | acospi | asin | asinpi |
atan | atan2 | atan2pi | cos |
cospi | sin | sincos | sinpi |
tan | tanpi |
Hyperbolic Functions
acosh | asinh | atanh | cosh |
sinh | tanh |
Exponential Functions
exp | exp10 | exp2 | expm1 |
frexp | ldexp | modf |
Logarithmic Functions
ilogb | log | log10 | log1p |
Power Functions
cbrt | hypot | pow | rsqrt |
sqrt |
Error Functions
erf | erfc |
Rounding Functions
ceil | floor | llrint | llround |
lrint | lround | nearbyint | rint |
round | trunc |
Remainder Functions
fmod | remainder | remquo |
Floating-point
copysign | nan | nextafter | nexttoward |
Difference Functions
fdim | fmax | fmin | maxmag |
minmag |
Other Functions
abs | divide | fabs | fma |
fract | mad | recip |
Classification Functions
fpclassify | isfinite | isinf | isnan |
isnormal | signbit |
Comparison Functions
isgreater | isgreaterequal | isless | islessequal |
islessgreater | isunordered |
Relational Functions
all | any | bitselect | isequal |
isnotequal | isordered | select |
Fixed-Point Math Functions
Fixed-point implementations are also provided for the following math functions.
All fixed-point math functions support ap_[u]fixed and ap_[u]int data types with following bit-width specification,
ap_fixed<W,I>
where I<=33 and W-I<=32ap_ufixed<W,I>
where I<=32 and W-I<=32ap_int<I>
where I<=33ap_uint<I>
where I<=32
Trigonometric Functions
cos | sin | tan | acos | asin | atan | atan2 | sincos |
cospi | sinpi |
Hyperbolic Functions
cosh | sinh | tanh | acosh | asinh | atanh |
Exponential Functions
exp | frexp | modf | exp2 | expm1 |
Logarithmic Functions
log | log10 | ilogb | log1p |
Power Functions
pow | sqrt | rsqrt | cbrt | hypot |
Error Functions
erf | erfc |
Rounding Functions
ceil | floor | trunc | round | rint | nearbyint |
Floating Point
nextafter | nexttoward |
Difference Functions
erf | erfc | fdim | fmax | fmin | maxmag | minmag |
Other Functions
fabs | recip | abs | fract | divide |
Classification Functions
signbit |
Comparison Functions
isgreater | isgreaterequal | isless | islessequal | islessgreater |
Relational Functions
isequal | isnotequal | any | all | bitselect |
The fixed-point type provides a slightly-less accurate version of the function value, but a smaller and faster RTL implementation.
The methodology for implementing a math function with a fixed-point data types is:
- Determine if a fixed-point implementation is supported.
- Update the math functions to use
ap_fixed
types. - Perform C simulation to validate the design still operates with the required precision. The C simulation is performed using the same bit-accurate types as the RTL implementation.
- Synthesize the design.
For example, a fixed-point implementation of the function sin
is specified by using fixed-point types with the
math function as follows:
#include "hls_math.h"
#include "ap_fixed.h"
ap_fixed<32,2> my_input, my_output;
my_input = 24.675;
my_output = sin(my_input);
When using fixed-point math functions, the result type must have the same width and integer bits as the input.
Verification and Math Functions
If the standard C math library is used in the C source code, the C simulation results and the C/RTL co-simulation results may be different: if any of the math functions in the source code have an ULP difference from the standard C math library it may result in differences when the RTL is simulated.
If the hls_math.h
library is used in the C source code, the C simulation and C/RTL co-simulation results are identical. However, the results of C simulation using hls_math.h
are not the same as those using the standard C libraries. The hls_math.h
library simply ensures the C simulation matches the C/RTL co-simulation results. In both cases, the same RTL implementation is created. The following explains each of the possible options which are used to perform verification when using math functions.
Verification Option 1: Standard Math Library and Verify Differences
In this option, the standard C math libraries are used in the source code. If any of the functions synthesized do have exact accuracy the C/RTL co-simulation is different than the C simulation. The following example highlights this approach.
#include <cmath>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;
typedef float data_t;
data_t cpp_math(data_t angle) {
data_t s = sinf(angle);
data_t c = cosf(angle);
return sqrtf(s*s+c*c);
}
In this case, the results between C simulation and C/RTL co-simulation are different. Keep in mind when comparing the outputs of simulation, any results written from the test bench are written to the working directory where the simulation executes:
- C simulation: Folder <project>/<solution>/csim/build
- C/RTL co-simulation: Folder <project>/<solution>/sim/<RTL>
where <project>
is the project folder,
<solution>
is the name of the solution folder and
<RTL>
is the type of RTL verified (Verilog or VHDL). The
following figure shows a typical comparison of the pre-synthesis results file on the left-hand
side and the post-synthesis RTL results file on the right-hand side. The output is shown in the
third column.
The results of pre-synthesis simulation and post-synthesis simulation differ by fractional amounts. You must decide whether these fractional amounts are acceptable in the final RTL implementation.
The recommended flow for handling these differences is using a test bench that
checks the results to ensure that they lie within an acceptable error range. This can be
accomplished by creating two versions of the same function, one for synthesis and one as a
reference version. In this example, only function cpp_math
is
synthesized.
#include <cmath>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;
typedef float data_t;
data_t cpp_math(data_t angle) {
data_t s = sinf(angle);
data_t c = cosf(angle);
return sqrtf(s*s+c*c);
}
data_t cpp_math_sw(data_t angle) {
data_t s = sinf(angle);
data_t c = cosf(angle);
return sqrtf(s*s+c*c);
}
The test bench to verify the design compares the outputs of both functions to
determine the difference, using variable diff
in the following
example. During C simulation both functions produce identical outputs. During C/RTL
co-simulation function cpp_math
produces different results and
the difference in results are checked.
int main() {
data_t angle = 0.01;
data_t output, exp_output, diff;
int retval=0;
for (data_t i = 0; i <= 250; i++) {
output = cpp_math(angle);
exp_output = cpp_math_sw(angle);
// Check for differences
diff = ( (exp_output > output) ? exp_output - output : output - exp_output);
if (diff > 0.0000005) {
printf("Difference %.10f exceeds tolerance at angle %.10f \n", diff, angle);
retval=1;
}
angle = angle + .1;
}
if (retval != 0) {
printf("Test failed !!!\n");
retval=1;
} else {
printf("Test passed !\n");
}
// Return 0 if the test passes
return retval;
}
If the margin of difference is lowered to 0.00000005, this test bench highlights the margin of error during C/RTL co-simulation:
Difference 0.0000000596 at angle 1.1100001335
Difference 0.0000000596 at angle 1.2100001574
Difference 0.0000000596 at angle 1.5100002289
Difference 0.0000000596 at angle 1.6100002527
etc..
When using the standard C math libraries (math.h and cmath.h) create a “smart” test bench to verify any differences in accuracy are acceptable.
Verification Option 2: HLS Math Library and Validate Differences
An alternative verification option is to convert the source code to use the HLS math library. With this option, there are no differences between the C simulation and C/RTL co-simulation results. The following example shows how the code above is modified to use the hls_math.h
library.
- Include the
hls_math.h
header file. - Replace the math functions with the equivalent
hls::
function.#include <cmath> #include "hls_math.h" #include <fstream> #include <iostream> #include <iomanip> #include <cstdlib> using namespace std; typedef float data_t; data_t cpp_math(data_t angle) { data_t s = hls::sinf(angle); data_t c = hls::cosf(angle); return hls::sqrtf(s*s+c*c); }
Verification Option 3: HLS Math Library File and Validate Differences
Including the HLS math library file lib_hlsm.cpp
as a design file ensures Vitis HLS uses the HLS math library for C simulation. This option is identical to option2 however it does not require the C code to be modified.
The HLS math library file is located in the src
directory in the Vitis HLS installation area. Simply copy the file to your local folder and add the file as a standard design file.
As with option 2, with this option there is now a difference between the C simulation results using the HLS math library file and those previously obtained without adding this file. These difference should be validated with C simulation using a “smart” test bench similar to option 1.
Common Synthesis Errors
The following are common use errors when synthesizing math functions. These are often (but not exclusively) caused by converting C functions to C++ to take advantage of synthesis for math functions.
C++ cmath.h
If the C++ cmath.h
header file is used, the floating point functions (for
example, sinf
and cosf
) can be used.
These result in 32-bit operations in hardware. The cmath.h
header file also
overloads the standard functions (for example, sin
and
cos
) so they can be used for float and double types.
C math.h
If the C math.h
library is used, the single-precision functions (for
example, sinf
and cosf
) are required
to synthesize 32-bit floating point operations. All standard function calls (for
example, sin
and cos
) result in
doubles and 64-bit double-precision operations being synthesized.
Cautions
When converting C functions to C++ to take advantage of math.h
support, be
sure that the new C++ code compiles correctly before synthesizing with Vitis HLS.
For example, if sqrtf()
is used in the code with
math.h
, it requires the following code extern added to the C++ code to
support it:
#include <math.h>
extern “C” float sqrtf(float);
To avoid unnecessary hardware caused by type conversion, follow the warnings on mixing double and float types discussed in Floats and Doubles.