vivadoHLS(二)

作者:SunnyFHY    发布于:

HLS学习

1539354344835

上图是HLS完整工作过程,其中Contraints/Directives是在软件里设置的,可以对同一份C语言转电路设计的时候实现全并行或全串行设计等设置。HLS最后生成IP核,可以在Vivado里打开。

部分HLS C语言约束(不完全版)

HLS C语言体系中不支持的元素

虽然在HLS中C语言的大部分特性被广泛支持,但是仍存在一部分不可综合的C语言代码。这些代码在向底层的设计转化过程中会产生错误。接下来将讨论HLS中C语言代码设计时的可综合性和在器件中的可执行性。

可综合的C语言代码具有以下特性:

1、C语言程序的组成部分全部为函数。

2、对操作系统层面有需求的函数不能被综合。

3、C语言构造必须是定长或者有边界的。

4.C语言构造的设计面对FPGA硬件执行层面是明确的。

System Calls 系统调用

系统调用的函数不能被综合的原因在于它们往往会在C语言程序执行的过程中执行基于操作系统层面的任务,而在FPGA底层不存在操作系统。

Vivado HLS在综合的时候自动忽视掉对算法不产生影响的常用系统调用函数,这些函数不需要在综合之前被移除,例如:

• abort() • atexit() • exit() • fprintf() • printf()

• perror() • putchar() • puts() • time() • sleep() • getc()

不过通常来说,不可综合的代码理应在综合前被移除。可以利用如下的宏,确保不可综合的C代码不会被vivado HLS综合

1
macro: __SYNTHESIS__

具体例子,参见如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "hier_func4.h"
int sumsub_func(din_t *in1, din_t *in2, dint_t *outSum, dint_t *outSub)
{
*outSum = *in1 + *in2;
*outSub = *in1 - *in2;
}
int shift_func(dint_t *in1, dint_t *in2, dout_t *outA, dout_t *outB)
{
*outA = *in1 >> 1;
*outB = *in2 >> 2;
}
void hier_func4(din_t A, din_t B, dout_t *C, dout_t *D)
{
dint_t apb, amb;
sumsub_func(&A,&B,&apb,&amb);
#ifndef __SYNTHESIS__
FILE *fp1;// The following code is ignored for synthesis
char filename[255];
sprintf(filename,Out_apb_%03d.dat,apb);
fp1=fopen(filename,w);
fprintf(fp1, %d \n, apb);
fclose(fp1);
#endif
shift_func(&apb,&amb,C,D);
}

值得注意的是:上述的宏仅作用于被综合的C代码,不适用于 test bench。因为这个宏不遵循C仿真和C/RTL仿真的规则。

Dynamic Memory Usage 动态内存使用

调用内存分配管理函数都是基于系统层面的,例如malloc(),alloc(),和free()函数都是在程序运行过程中利用操作系统内存的资源去创建和释放存储空间。为了可被综合,在硬件执行的设计上必须脱离操作系统支持,并且明确资源需求。

内存分配的系统调用代码必须在综合之前被移除。因为动态内存操作在设计流程中属于特有功能点,它们必须被转化为等价的有边界构造。接下来的例程将会展示如何设计一段可被综合的malloc()代码,顺带重点提示一下两点十分有用的代码设计技巧:

1.例程设计没有用

1
__SYNTHESIS__ 这个宏

用户自定义宏NO_SYNTH用于选择可综合和不可综合代码版本。这样子就可以用同样的代码进行C仿真和Vivado HLS综合。

2.原本设计中用于malloc()函数的指针不用修改就可以指向有定长的数据结构。

有定长的数据结构可以被创建,现存的指针用于指向新的数据结构即可。这个技巧可以避免人为地对代码进行大篇幅改动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "malloc_removed.h"
#include <stdlib.h>
//#define NO_SYNTH
dout_t malloc_removed(din_t din[N], dsel_t width) {
#ifdef NO_SYNTH
long long *out_accum = malloc (sizeof(long long));
int* array_local = malloc (64 * sizeof(int));
#else
long long _out_accum;
long long *out_accum = &_out_accum;
int _array_local[64];
int* array_local = &_array_local[0];
#endif
int i,j;
LOOP_SHIFT:for (i=0;i<N-1; i++) {
if (i<width)
*(array_local+i)=din[i];
else
*(array_local+i)=din[i]>>2;
}
*out_accum=0;
LOOP_ACCUM:for (j=0;j<N-1; j++) {
*out_accum += *(array_local+j);
}
return *out_accum;
}

Pointer Limitations 指针限制

General Pointer Casting常规指针类型转换

Vivado HLS 不支持常规意义上的指针类型转化。例如,当一个结构体被初始化,且其中的成员变量被定义为有符号数据类型时,结构体指针不能被转换为无符号数据类型。

1
2
3
4
5
6
struct {
short first;
short second;
} pair;
// Not supported for synthesis
*(unsigned*)pair = -1U;

在这种情况下,结构体指针只能用原定义数据类型。

1
2
3
4
5
6
7
struct {
short first;
short second;
} pair;
// Assigned value
pair.first = -1U;
pair.second = -1U;

指针数组也能被综合,见下面例程:此例程中的指针数组用于存储一个全局数组第二维度的起始地址。指针数组的指针只能指向标量或标量数组,而不能指向其他指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "pointer_array.h"
data_t A[N][10];
data_t pointer_array(data_t B[N*10]) {
data_t i,j;
data_t sum1;
// Array of pointers
data_t* PtrA[N];
// Store global array locations in temp pointer array
for (i=0; i<N; ++i)
PtrA[i] = &(A[i][0]);
// Copy input array using pointers
for(i=0; i<N; ++i)
for(j=0; j<10; ++j)
*(PtrA[i]+j) = B[i*10 + j];
// Sum input array
sum1 = 0;
for(i=0; i<N; ++i)
for(j=0; j<10; ++j)
sum1 += *(PtrA[i] + j);
return sum1;
}

Recursive Functions递归函数

无穷递归的递归函数不可被综合:

1
2
3
4
5
unsigned foo (unsigned n)
{
if (n == 0 || n == 1) return 1;
return (foo(n-2) + foo(n-1));
}

Vivado HLS不支持对数值进行无尽调用的尾递归函数。

1
2
3
4
5
6
unsigned foo (unsigned m, unsigned n)
{
if (m == 0) return n;
if (n == 0) return m;
return foo(n, m%n);
}

C Test Bench C测试台

任何模块综合的第一步都是验证C函数的功能是否正确。这个步骤由测试台完成。设计一个好的测试台代码能极大程度上增加你的效率。

C函数执行比RTL仿真快。在综合之前使用C语言去开发和验证算法比开发RTL级代码更有效率。

Vivado HLS 重复利用C测试台去验证RTL设计。在使用Vivado HLS时不需要设计任何RTL测试台代码。如果测试台验证过了顶层函数的结果,RTL仿真也会同时通过验证。

注意:为测试台代码提供参数的时候,选择 Project > Project Settings,点击Simulation ,使用Input Arguments 选项。Test Bench文件不支持交互式用户输入。Vivado HLS图形界面没有命令行控制台,也不支持在测试台程序执行的过程中接收用户输入。

Xilinx建议在综合的时候从测试台文件中将顶层函数分离出来,可以使用头文件。接下来的例程中hier_func函数对两个子函数进行调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hier_func.h"
int sumsub_func(din_t *in1, din_t *in2, dint_t *outSum, dint_t *outSub)
{
*outSum = *in1 + *in2;
*outSub = *in1 - *in2;
}
int shift_func(dint_t *in1, dint_t *in2, dout_t *outA, dout_t *outB)
{
*outA = *in1 >> 1;
*outB = *in2 >> 2;
}
void hier_func(din_t A, din_t B, dout_t *C, dout_t *D)
{
dint_t apb, amb;
sumsub_func(&A,&B,&apb,&amb);
shift_func(&apb,&amb,C,D);
}

顶层函数可以包含多个子函数。只能有一个顶层函数被综合。为了综合多个函数,可以把这些函数都放在一个顶层函数里调用。

为了综合hier_func这个函数:

1.添加上述例程到Vivado HLS工程里作为设计文件。

2.声明hier_func为顶层函数。

综合之后:

• 传递给顶层函数的参数被综合为RTL引脚。

• 顶层函数里包含的子函数被综合成分层次的模块。

下述头文件为上述例程的头文件

1
2
3
4
5
6
7
8
9
#ifndef _HIER_FUNC_H_
#define _HIER_FUNC_H_
#include <stdio.h>
#define NUM_TRANS 40
typedef int din_t;
typedef int dint_t;
typedef int dout_t;
void hier_func(din_t A, din_t B, dout_t *C, dout_t *D);
#endif

这个头文件包含了很多原设计文件里面没有的定义(比如NUM_TRANS)。这些定义是用于测试台文件,测试台文件同样包含这个头文件。

下述代码是这个工程里的测试台文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include "hier_func.h"
int main() {
// Data storage
int a[NUM_TRANS], b[NUM_TRANS];
int c_expected[NUM_TRANS], d_expected[NUM_TRANS];
int c[NUM_TRANS], d[NUM_TRANS];
//Function data (to/from function)
int a_actual, b_actual;
int c_actual, d_actual;
// Misc
int retval=0, i, i_trans, tmp;
FILE *fp;
// Load input data from files
fp=fopen(tb_data/inA.dat,r);
for (i=0; i<NUM_TRANS; i++){
fscanf(fp, %d, &tmp);
a[i] = tmp;
}
fclose(fp);
fp=fopen(tb_data/inB.dat,r);
for (i=0; i<NUM_TRANS; i++){
fscanf(fp, %d, &tmp);
b[i] = tmp;
}
fclose(fp);
// Execute the function multiple times (multiple transactions)
for(i_trans=0; i_trans<NUM_TRANS-1; i_trans++){
//Apply next data values
a_actual = a[i_trans];
b_actual = b[i_trans];
hier_func(a_actual, b_actual, &c_actual, &d_actual);
//Store outputs
c[i_trans] = c_actual;
d[i_trans] = d_actual;
}
// Load expected output data from files
fp=fopen(tb_data/outC.golden.dat,r);
for (i=0; i<NUM_TRANS; i++){
fscanf(fp, %d, &tmp);
c_expected[i] = tmp;
}
fclose(fp);
fp=fopen(tb_data/outD.golden.dat,r);
for (i=0; i<NUM_TRANS; i++){
fscanf(fp, %d, &tmp);
d_expected[i] = tmp;
}
fclose(fp);
// Check outputs against expected
for (i = 0; i < NUM_TRANS-1; ++i) {
if(c[i] != c_expected[i]){
retval = 1;
}
if(d[i] != d_expected[i]){
retval = 1;
}
}
// Print Results
if(retval == 0){
printf( *** *** *** *** \n);
printf( Results are good \n);
printf( *** *** *** *** \n);
} else {
printf( *** *** *** *** \n);
printf( Mismatch: retval=%d \n, retval);
printf( *** *** *** *** \n);
}
// Return 0 if outputs are correct
return retval;
}

池化和卷积HLS C语言代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//池化代码main函数
#include "stdio.h"
#include "pool_core.h"
#define MODE 0 //mode: 0:MEAN, 1:MIN, 2:MAX
#define IN_WIDTH 6
#define IN_HEIGHT 6
#define IN_CH 1
#define KERNEL_WIDTH 3
#define KERNEL_HEIGHT 3
#define OUT_WIDTH (IN_WIDTH/KERNEL_WIDTH)
#define OUT_HEIGHT (IN_HEIGHT/KERNEL_HEIGHT)
int main(void)
{
Dtype_f feature_in[IN_HEIGHT][IN_WIDTH][IN_CH];
Dtype_f feature_out[OUT_HEIGHT][OUT_WIDTH][IN_CH];
for(int i=0;i<IN_HEIGHT;i++)
for(int j=0;j<IN_WIDTH;j++)
for(int cin=0;cin<IN_CH;cin++)
feature_in[i][j][cin]=i*IN_WIDTH+j;
Pool(IN_CH,IN_HEIGHT,IN_WIDTH,
KERNEL_WIDTH,KERNEL_HEIGHT,MODE,
feature_in[0][0],feature_out[0][0]
);//mode: 0:MEAN, 1:MIN, 2:MAX
for(int i=0;i<OUT_HEIGHT;i++)
for(int j=0;j<OUT_WIDTH;j++)
for(int cout=0;cout<IN_CH;cout++)
{
printf("OUT[%d][%d][%d]=%f\n",i,j,cout,feature_out[i][j][cout]);
}
}
//池化代码池化函数
#include "pool_core.h"
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a>b)?b:a)
void Pool(ap_uint<16> CHin,ap_uint<16> Hin,ap_uint<16> Win,
ap_uint<8> Kx,ap_uint<8> Ky,ap_uint<2> mode,
Dtype_f feature_in[],Dtype_f feature_out[]
)//mode: 0:MEAN, 1:MIN, 2:MAX
{
#pragma HLS INTERFACE m_axi depth=4294967295 port=feature_out offset=slave
#pragma HLS INTERFACE m_axi depth=4294967295 port=feature_in offset=slave
#pragma HLS INTERFACE s_axilite port=Win
#pragma HLS INTERFACE s_axilite port=Kx
#pragma HLS INTERFACE s_axilite port=Hin
#pragma HLS INTERFACE s_axilite port=mode
#pragma HLS INTERFACE s_axilite port=Ky
#pragma HLS INTERFACE s_axilite port=CHin
#pragma HLS INTERFACE s_axilite port=return
ap_uint<16> Hout,Wout;
Wout=Win/Kx;
Hout=Hin/Ky;
for(int c=0;c<CHin;c++)
for(int i=0;i<Hout;i++)
for(int j=0;j<Wout;j++)
{
Dtype_f sum;
if(mode==0)
sum=0;
else
if(mode==1)
sum=99999999999999999;
else
sum=-99999999999999999;
for(int ii=0;ii<Ky;ii++)
for(int jj=0;jj<Kx;jj++)
{
ap_int<16> h=i*Ky+ii;
ap_int<16> w=j*Kx+jj;
switch(mode)
{
case 0:{sum+=feature_in[h*CHin*Win+w*CHin+c];break;}
case 1:{sum=min(sum,feature_in[h*CHin*Win+w*CHin+c]);break;}
case 2:{sum=max(sum,feature_in[h*CHin*Win+w*CHin+c]);break;}
default:break;
}
}
if(mode==0)
sum=sum/(Kx*Ky);
feature_out[i*Wout*CHin+j*CHin+c]=sum;
}
}
//卷积代码main函数
#include "stdio.h"
#include "conv_core.h"
#define IN_WIDTH 10
#define IN_HEIGHT 10
#define IN_CH 16
#define KERNEL_WIDTH 5
#define KERNEL_HEIGHT 5
#define X_STRIDE 1
#define Y_STRIDE 1
#define RELU_EN 0
#define MODE 0 //0:VALID, 1:SAME
#define X_PADDING (MODE?(KERNEL_WIDTH-1)/2:0)
#define Y_PADDING (MODE?(KERNEL_HEIGHT-1)/2:0)
#define OUT_CH 1
#define OUT_WIDTH ((IN_WIDTH+2*X_PADDING-KERNEL_WIDTH)/X_STRIDE+1)
#define OUT_HEIGHT ((IN_HEIGHT+2*Y_PADDING-KERNEL_HEIGHT)/Y_STRIDE+1)
int main(void)
{
Dtype_f feature_in[IN_HEIGHT][IN_WIDTH][IN_CH];
Dtype_w W[KERNEL_HEIGHT][KERNEL_WIDTH][IN_CH][OUT_CH];
Dtype_w bias[OUT_CH];
Dtype_f feature_out[OUT_HEIGHT][OUT_WIDTH][OUT_CH];
for(int i=0;i<IN_HEIGHT;i++)
for(int j=0;j<IN_WIDTH;j++)
for(int cin=0;cin<IN_CH;cin++)
feature_in[i][j][cin]=i*IN_WIDTH+j;
for(int i=0;i<KERNEL_HEIGHT;i++)
for(int j=0;j<KERNEL_WIDTH;j++)
for(int cin=0;cin<IN_CH;cin++)
for(int cout=0;cout<OUT_CH;cout++)
W[i][j][cin][cout]=i*KERNEL_WIDTH+j;
for(int cout=0;cout<OUT_CH;cout++)
bias[cout]=0;
printf("1234\n");
Conv(IN_CH,IN_HEIGHT,IN_WIDTH,OUT_CH,
KERNEL_WIDTH,KERNEL_HEIGHT,X_STRIDE,Y_STRIDE,MODE,RELU_EN,
feature_in[0][0],W[0][0][0],bias,feature_out[0][0]
);
for(int i=0;i<OUT_HEIGHT;i++)
for(int j=0;j<OUT_WIDTH;j++)
for(int cout=0;cout<OUT_CH;cout++)
{
printf("OUT[%d][%d][%d]=%f\n",i,j,cout,feature_out[i][j][cout]);
}
return 0;
}
//卷积代码卷积函数
#include "conv_core.h"
//Feature: [H][W][C]
//kernel: [Ky][Kx][CHin][CHout]
void Conv(ap_uint<16> CHin,ap_uint<16> Hin,ap_uint<16> Win,ap_uint<16> CHout,
ap_uint<8> Kx,ap_uint<8> Ky,ap_uint<8> Sx,ap_uint<8> Sy,ap_uint<1> mode,ap_uint<1> relu_en,
Dtype_f feature_in[],Dtype_w W[],Dtype_w bias[],Dtype_f feature_out[]
)//mode: 0:VALID, 1:SAME
{
//#pragma HLS PIPELINE enable_flush
#pragma HLS INTERFACE m_axi depth=4294967295 port=feature_out offset=slave
#pragma HLS INTERFACE m_axi depth=4294967295 port=bias offset=slave
#pragma HLS INTERFACE m_axi depth=4294967295 port=W offset=slave
#pragma HLS INTERFACE m_axi depth=4294967295 port=feature_in offset=slave
#pragma HLS INTERFACE s_axilite port=relu_en
#pragma HLS INTERFACE s_axilite port=CHout
#pragma HLS INTERFACE s_axilite port=Sx
#pragma HLS INTERFACE s_axilite port=Hin
#pragma HLS INTERFACE s_axilite port=CHin
#pragma HLS INTERFACE s_axilite port=Kx
#pragma HLS INTERFACE s_axilite port=mode
#pragma HLS INTERFACE s_axilite port=Sy
#pragma HLS INTERFACE s_axilite port=Ky
#pragma HLS INTERFACE s_axilite port=Win
#pragma HLS INTERFACE s_axilite port=return
ap_uint<8> pad_x,pad_y;
if(mode==0)
{
pad_x=0;pad_y=0;
}
else
{
pad_x=(Kx-1)/2;pad_y=(Ky-1)/2;
}
ap_uint<16> Hout,Wout;
Wout=(Win+2*pad_x-Kx)/Sx+1;
Hout=(Hin+2*pad_y-Ky)/Sy+1;
for(int cout=0;cout<CHout;cout++)
for(int i=0;i<Hout;i++)
for(int j=0;j<Wout;j++)
{
Dtype_acc sum=0;
for(int ii=0;ii<Ky;ii++)
for(int jj=0;jj<Kx;jj++)
{
ap_int<16> h=i*Sy-pad_y+ii;
ap_int<16> w=j*Sx-pad_x+jj;
if(h>=0 && w>=0 && h<Hin && w<Win)
{
for(int cin=0;cin<CHin;cin++)
{
//Feature [H][W][C]
//kernel: [Ky][Kx][CHin][CHout]
//Dtype_mul tp=feature_in[h][w][cin]*w[ii][jj][cin][cout];
//std::cout<<"h:"<<h<<",w"<<w<<",cin"<<cin<<"\n";
//std::cout<<"feature_in["<<h*CHin*Win+w*CHin+cin<<"]*W["<<ii*Kx*CHin*CHout+jj*CHin*CHout+cin*CHout+cout<<"]\n";
Dtype_mul tp=feature_in[h*CHin*Win+w*CHin+cin]*W[ii*Kx*CHin*CHout+jj*CHin*CHout+cin*CHout+cout];
sum+=tp;
}
}
}
sum+=bias[cout];
if(relu_en & sum<0)
sum=0;
//feature_out[i][j][cout]=sum;
feature_out[i*Wout*CHout+j*CHout+cout]=sum;
}
}
format_list_numbered

(无)

  1. 1. HLS学习
    1. 1.1. 部分HLS C语言约束(不完全版)
    2. 1.2. 池化和卷积HLS C语言代码
vertical_align_top

Copyright © 2017 SunnyFHY

Powered by Hexo & Theme - Vateral