Writing Hls C Code for Dynamatic
Before passing your C kernel (function) to Dynamatic for compilation, it is important that you ensure it meets some guidelines. This document presents the said guidelines and some constraints that the user must follow to make their code suitable inputs for Dynamatic.
note
These guidelines target the function to be compiled and not the main
function of your program except for the CALL_KERNEL
. Main is primarily useful for passing inputs for simulation and is not compiled by Dynamatic
Summary
- Dynamatic header
CALL_KERNEL
macro inmain
- Variable Types and Names in
main
Must Match Parameter Names in Kernel Declaration - Inline functions called by the kernel
- No recursive calls
- No pointers
- No dynamic memory allocation
- Pass global variables
- No support for local array declarations
- Data type support
1. Include the Dynamatic Integration Header
To be able to compile in Dynamatic, your C files should include the Integration.h
header that will be a starting point for accessing other relevant Dynamatic libraries at compile time.
#include "dynamatic/Integration.h"
2. Use the CALL_KERNEL Macro in the main
Function
Do not call the kernel function directly, use the CALL_KERNEL
macro provided through Dynamatic’s integration header.
It does two things in the compiler flow:
- Dumps the argument passed to the kernel to files in sim/INPUT_VECTORS (for C/HDL cosimulation when the
simulate
command is ran). - Dumps the argument passed to the kernel to a profiler to determine which loops are more important to be optimized using buffer placement.
CALL_KERNEL(func, input_1, input_2, ... , input_n)
3. Match Variable Names and Types in main
to the Parameter Declared as Kernel Inputs
For simulation purposes, the variables declared in the main
function must have the same names and data types as the function parameters of your function under test. This makes it easy for the simulator to correctly identify and properly match parameters when passing them. For example:
void loop_scaler(int arr[10], int scale_factor){
...
}; // function declaration
int main(){
int arr[10]; // same name and type
int size; // as in kernel declaration
scale_factor = 50;
// initialize arr[10] values
CALL_KERNEL(loop_scaler, arr, scale_factor);
return 0;
}
Limitations
1. Do Not Call Functions in Your Target Function
The target function is the top level function to be implemented by Dynamatic. Dynamatic does not support calling other functions in the target kernel. Alternatively, you can use macros to implement any extra functionality before using them in your target kernel.
#define increment(x) x+1; // macro for increment function
void loop(int x) {
while (x<20) {
increment(x); // macro
}
} // inlined with macro definition.
2. Recursive Calls Are Not Supported
Like other HLS tools, Dynamatic does not support recursive function calls because:
- they are difficult to map to hardware
- have unpredictable depths and control flow
- unbounded execution
- the absence of call-stack in FPGA platforms would be too resource demanding to implement efficiently epecially without knowing the bounds ahead of time.
An alternative would be to manually unroll recursive calls and replace them with loops where possible.
3. Pointers Are Not Supported
Pointers should not be used. *(x + 1) = 4;
is invalid. Use regular indexing and fixed sized arrays if need be as shown below.
int x[10]; // fixed sized
x[1] = 4; // non-pointer indexing
4. Dynamic Memory Allocation is Not Supported
Dynamic memory allocation is also not allowed because it’s not deterministic enough to allow enough hardware resources to be allocated at compile time.
5. Global Variables
Dynamatic compiles the kernel code only. Any variables declared outside the kernel function will not be converted unless they are passed to the kernel. Global variables are no exception. You can pass global variables as parameters to your kernel or define them as macros to make your kernel simpler.
#define scale_alternative (2)
int scale = 2;
int scaler(int scale, int number) // scale is still passed as parameter
{
return number * scale * scale_alternative;
}
6. Local Array Declarations are Not Supported
Local array declaration in kernels is not yet supported by Dynamatic. Pass all arrays as parameters to your kernel.
void convolution(unsigned char input[HEIGHT][WIDTH], unsigned char output[HEIGHT][WIDTH]) {
int kernel[3][3] = {
{1, 1, 1},
{1, 1, 1},
{1, 1, 1}
};
int kernel_sum = 9;
for (int y = 1; y < HEIGHT - 1; y++) {
for (int x = 1; x < WIDTH - 1; x++) {
int sum = 0;
for (int ky = -1; ky <= 1; ky++) {
for (int kx = -1; kx <= 1; kx++) {
sum += input[y + ky][x + kx] * kernel[ky + 1][kx + 1]; // one issue hear...non-affine apparently..
// the kernel indexing is considered non-affine
}
}
output[y][x] = sum / kernel_sum;
printf("output[%d][%d] = %d\n", y, x, output[y][x]);
}
}
}
The above code will yield an error at compilation about array flattening. Pass it as a parameter to bypass the error:
void convolution(int kernel[3][3], unsigned char input[HEIGHT][WIDTH], unsigned char output[HEIGHT][WIDTH])
Data Types Supported by Dynamatic
These types are most crucial when dealing with function parameters. Some of the unsupported types may work on local variables without any compilation errors.
note
Arrays of supported data types are also supported as function parameters
buffer algorithm/data type | Supported |
---|---|
unsigned | ✓ |
int32_t / int16_t / int8_t | ✓ |
uint32_t / uint16_t / uint8_t | ✓ |
char / unsigned char | ✓ |
short | ✓ |
float | ✓ |
double | ✓ |
long/long long/long double | x |
uint64_t / int64_t | x |
__int128 | x |
Supported Operations
- Arithmetic operations:
+
,-
,*
,/
,++
,--
. - Logical operations on
int
:>
,<
,&&
,||
,!
,^
Unsupported Operations
- Arithmetic operations:
%
- Pointer operations:
*
,&
(indexing is supported -a[i]
) - Most math functions excluding absolute value functions
- Logical operations can be used with variables of type
float
in C but the following are not yet supported in Dynamatic:&&
,||
,!
,^
.
tip
Data type and operation related errors generally state explicitly that an operation or type is not supported. Kindly report those as bugs on our repository while we work on making more data types supported.
Other C Constructs
Structs
struct
s are currently not supported. Consider passing inputs individually rather than grouping with structs
Function Inlining
The inline
keyword is not yet supported. Consider #define
as an alternative for inlining blocks of code into your target function
Volatile
The volatile
keyword is supported but has zero impact on the circuits generated.
warning
Do not use on function parameters!
Dynamatic is being refined over time and is yet to support certain constructs such as local array declarations in the target function which must rather be passed as inputs. If you encounter any issue in using Dynamatic, kindly report the bug on the github repository.
In the meantime, visit our examples page to see an example of using Dynamatic.