一、使用HLS开发有别于C语言的经验及注意事项
1、编程注意事项
- 使用适合HLS的C++子集:并非所有C/C++语法都适合HLS。避免使用动态内存分配(如malloc、new)、递归函数、系统调用等。
- 数据类型:使用固定大小的数据类型,如
int8_t
, int16_t
, int32_t
,或者使用HLS提供的任意精度数据类型,如ap_int<8>
, ap_fixed<16,8>
,这些可以节省资源。
- 数组映射:数组通常被映射到BRAM或URAM。考虑数组的分块和重组以优化内存访问。
- 函数内联:小函数通常会被内联,但也可以显式控制内联。
2、 优化技巧
- 流水线:对循环和函数使用流水线,以提高吞吐量。
- 循环展开:对于小循环,可以完全展开以增加并行性,但会消耗更多资源。
- 数组分区:将大数组分割成多个小数组,以便并行访问。
- 数据流:允许函数内的多个操作并行执行,只要它们之间没有数据依赖。
- 依赖关系:注意循环携带依赖和内存依赖,这些会限制并行性。
3、编程约束和限制
(1).不支持的语言特性
// 以下在HLS中通常不支持或有限制:
// 1. 动态内存分配
int *arr = new int[100]; // 不支持
// 2. 递归函数</em>
int factorial(int n) { // 有限制
if(n <= 1) return 1;
return n * factorial(n-1);
}
// 3. 系统调用
printf(); // 仅用于调试,不会生成硬件
FILE *fp = fopen(); // 不支持
// 4. 虚函数和多态
class Base {
virtual void func(); // 有限制
};
(2).必须使用固定大小数组
// 传统C
void process(int *arr, int size) { // 动态大小,HLS不友好
for(int i = 0; i < size; i++) {
// ...
}
}
// HLS推荐
#define ARRAY_SIZE 1024
void process(int arr[ARRAY_SIZE]) { // 固定大小,HLS友好
#pragma HLS PIPELINE
for(int i = 0; i < ARRAY_SIZE; i++) {
// ...
}
}
4、 关键优化技术
(1).流水线 (Pipelining)
// 无流水线 - 性能差
for(int i = 0; i < N; i++) {
// 每个迭代需要多个周期
result[i] = complex_operation(input[i]);
}
// 有流水线 - 高性能
for(int i = 0; i < N; i++) {
#pragma HLS PIPELINE II=1 // 每个时钟周期开始一个新迭代
result[i] = complex_operation(input[i]);
}
(2).循环展开 (Loop Unrolling)
// 完全展开
#pragma HLS UNROLL
for(int i = 0; i < 4; i++) {
sum += a[i] * b[i];
}
// 部分展开
#pragma HLS UNROLL factor=2
for(int i = 0; i < 8; i++) {
sum += a[i] * b[i];
}
(3).数组分区 (Array Partitioning)
int buffer[1024];
// 完全分区 - 每个元素独立访问
#pragma HLS ARRAY_PARTITION variable=buffer complete
// 块分区 - 分成多个块
#pragma HLS ARRAY_PARTITION variable=buffer block factor=4
// 循环分区 - 按访问模式分区
#pragma HLS ARRAY_PARTITION variable=buffer cyclic factor=4