硬件的核心是并行编程,它主要包括两大部分:多流水并行、流水内部打拍。
1 多流水并行编程是在硬件内部形成多条流水,和cpu多个核心 类似,但是数量可以远远超过cpu核数,一般实现方案有两种:fifo和ram
1) fifo:将执行流程拆解成多个模块,模块间通过fifo连接起来,每个模块独立一个流水,模块的运行受控于fifo是有数据和控制命令。这块有点像软件的多线程通过无锁队列传输数据的方式。
2) ram:是一个模块将数据写入, 另外一个模块读出处理,这个方式的优势是可以一个生产者、多个消费者处理数据。难点在于通知机制和模块间同步,一般可以用fifo传递信号。
总体来说fifo的方式一般够用,ram的方式用的场景比较少
2 流水内部打拍是指在一个模块内部运行流程周期是M周期,如果需要执行N次,那么总时延是M*N,但是如果流水内部运行流程拆解成多个步骤,每个步骤1拍完成(既拆成M个步骤),然后设计的时候能保证第X步骤生成的中间值在后续步骤使用是不被破坏,就可以每拍启动一次,这样的好处是执行N次的时间是 M+N-1次。
流水打拍严格被循环条件控制,循环不能提前break;而且运行过程中循环条件不能在执行过程中修改;也就是说一但启动流水打拍,只能在流水打拍外部控制是否可以结束,内部无法通过beark或修改循环条件方式控制提前结束(这个是个很苦恼的问题,也是在使用流水打拍不很爽的地方)
流水内部打拍设计有两个难点:如何将步骤拆解成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拍打拍;缺点:浪费资源