对硬件编程的一点理解---vitis使用

硬件的核心是并行编程,它主要包括两大部分:多流水并行、流水内部打拍。

1 多流水并行编程是在硬件内部形成多条流水,和cpu多个核心 类似,但是数量可以远远超过cpu核数,一般实现方案有两种:fifo和ram

1) fifo:将执行流程拆解成多个模块,模块间通过fifo连接起来,每个模块独立一个流水,模块的运行受控于fifo是有数据和控制命令。这块有点像软件的多线程通过无锁队列传输数据的方式。

2) ram:是一个模块将数据写入, 另外一个模块读出处理,这个方式的优势是可以一个生产者、多个消费者处理数据。难点在于通知机制和模块间同步,一般可以用fifo传递信号。

如果用到ram跨函数传输,使用dataflow有三个条件:

1) 需要使用stable表示跨函数的raw和参数不用考虑相互关系(需要在顶层--dataflow的地方或用到的两个函数加上stable);

2) 使用的两个函数只能一个写一个读,不允许单个变量两个函数都有读写,当然多个变量之间没关系(比如 a、b在func1、func2使用,a func1写func2读,b func1读func2写实可以的);

3) 如果函数有ram数组类型的参数需要保证函数顺序对ram的参数先写后读,使用dependence宏无效

总体来说fifo的方式一般够用,ram的方式用的场景比较少。另外,在dataflow场景中,有个原则,尽量生产者把消费者需要的信息都提供,消费者尽量减少计算

2 流水内部打拍是指在一个模块内部运行流程周期是M周期,如果需要执行N次,那么总时延是M*N,但是如果流水内部运行流程拆解成多个步骤,每个步骤1拍完成(既拆成M个步骤),然后设计的时候能保证第X步骤生成的中间值在后续步骤使用是不被破坏,就可以每拍启动一次,这样的好处是执行N次的时间是 M+N-1次。

流水打拍可以提前跳出,但是提前跳出的条件需要再一拍内完成,否则就无法完成1拍的流水打拍,比如:根据ram类型判断,因为ram访问需要1拍的时间,导致流水打拍需要两拍才能完成,总就变成2*N+M-2了

dependence参数对循环中数组操作有作用,例子如下:

int   a[100];int old,new;

int   val;

for( int i=0;i<50;i++ )

{

        #pragma HLS pipeline

       /// 步骤1

        a[new]=val;

        val = a[old]+1;

        /// 其它

        ........

}

上述例子中编译器会假设最坏的情况,old和new相等,这个时候由于有相互依赖,II=2,但是实际的逻辑中设计new不会和old相等,这个时候可以使用dependence关闭依赖关系

流水内部打拍设计有两个难点:如何将步骤拆解成1拍完成,产生的中间值后面使用不被后续打拍破坏

1) 步骤拆解成1拍,主要遇到的问题是原子操作的拆解,比如:128b*128b的乘法,如果是组合电路,1拍很难满足。所以需要设计算法拆解。当然也可以2拍来打拍,但是时延将会变成M+2N-2,成倍数上升。

2)产生的中间值后面使用不被后续打拍破坏,一般做法有两种,将中间变量做成数组(数组的长度不小于步骤数),通过多次寄存器赋值实现。例子如下:

int a,b,c,d;

step1: b=a;

step2: c=b;

step3: d=b+c;

上述例子,如果流水打拍会出现以下场景(按照a=1、2、3、4):

初始状态:a=1,b=0,c=0,d=0

一拍后:    a=2,b=1,c=0,d=0

二拍后:    a=3,b=2,c=1,d=0

三拍后:    a=4,b=3,c=2,d=2+1

四拍后:    a=5,b=4,c=3,d=3+2

而我们想要的是第一个结果是 d=1+1,第二个是d=2+2....

改造方案1(待验证)

int a,b,c,d; int b1;

step1: b=a;

step2: c=b; b1=b;

step3: d=b1+c;

初始状态:a=1,b=0,c=0,d=0,    b1=0

一拍后:    a=2,b=1,c=0,d=0,    b1=0

二拍后:    a=3,b=2,c=1,d=0,    b1=1

三拍后:    a=4,b=3,c=2,d=1+1,b1=2

四拍后:    a=5,b=4,c=3,d=2+2,b1=3

这样就做到我们想要的结果了

改造方案2:

int a[3],b[3],c[3],d;

step1: b[i%3]=a[i%3];

step2: c[i%3]=b[i%3];

step3: d=b[i%3]+c[i%3];

初始状态:a[0]=1,b[0]=0,c[0]=0,d=0

                  a[1]=0,b[1]=0,c[1]=0,d=0

                  a[2]=0,b[2]=0,c[2]=0,d=0

一拍后:    a[0]=1,b[0]=1,c[0]=0,d=0

                  a[1]=2,b[1]=0,c[1]=0,d=0

                  a[2]=0,b[2]=0,c[2]=0,d=0

二拍后:    a[0]=1,b[0]=1,c[0]=1,d=0

                  a[1]=2,b[1]=2,c[1]=0,d=0

                  a[2]=3,b[2]=0,c[2]=0,d=0

三拍后:    a[0]=4,b[0]=1,c[0]=1,d=1+1

                  a[1]=2,b[1]=2,c[1]=2,d=1+1

                  a[2]=3,b[2]=3,c[2]=0,d=1+1

四拍后:    a[0]=4,b[0]=4,c[0]=1,d=2+2

                  a[1]=5,b[1]=2,c[1]=2,d=2+2

                  a[2]=3,b[2]=3,c[2]=3,d=2+2

五拍后:    a[0]=4,b[0]=4,c[0]=4,d=3+3

                  a[1]=5,b[1]=5,c[1]=2,d=3+3

                  a[2]=6,b[2]=3,c[2]=3,d=3+3

这样分别在第3拍、4拍、5拍获取到正确的值

方案1

优点:消耗资源少,对于每个步骤少并且明确是几拍的非常有效

缺点:对于步骤多复杂的场景影响大,而且一旦步骤变化需要重新计算;大于一拍打拍比较难处理

方案2

优点:是结构简单;对于步骤变化不敏感(只要数组的长度不小于步骤数);支持大于1拍打拍;缺点:浪费资源

流水打拍pipeline的使用范围,pipeline可以在循环和函数中使用,在循环中使用,会将循环内部的执行过程流水打拍;函数中使用pipeline还需要再好好研究下。

有一点可以确定的是当循环或者函数进行流水打拍时,所在循环体或者函数体包括循环,这些循环会自动展开,也就是说流水打拍会尽量让每个步骤最短时延(它把循环当做一个其中步骤)

如果循环体或函数体包含循环,但是不想让循环展开(资源或者设计局限),应该怎么办?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值