论述:FPGA中并行计算的常规方法

论述:FPGA中并行计算的常规方法

更新历史
20190411:

  1. 首次发布

用过FPGA的人应该都知道,在FPGA中,逻辑是并行地运行的,各个状态机同时都在工作,状态机之间可能会有信号交互,也可能毫无关系、各管各地工作。

这就给了我们一个灵感:如果我们要做的计算(例如级数求和)的规模很大,按顺序一步一步算的话,其时间开销是我们所无法忍受的,那么,我们是否可以想办法利用FPGA的并行特性,通过让计算并行地执行,来减小时间开销(或者说提高计算速度)呢?

基于这个灵感,就让我们看看,FPGA中并行计算的常规方法吧。



流水线计算

现在,我们要在FPGA中做这样一个计算:

  • B = A × \times × 2 + 1
  • 能用的计算单元为两输入加法器和两输入乘法器
  • 为了保证时序,一个时钟周期内加法器只能执行一次加法
  • 为了保证时序,一个时钟周期内乘法器只能执行一次乘法

对于上述要求完成的计算,看上去,在一个时钟周期内既完成乘法又完成加法是不可能的了。然而,如果我们这么做呢:

设计一个模块,模块的输入为时钟和参数A,输出为结果B。
该模块在每个时钟周期同时做"C=A×2"的计算和"B=C+1"的计算,其中,C为寄存器。

仿真该模块,就可以发现:

时钟周期参数A(输入)寄存器C结果B(输出)
1A1
2A2A1 × \times × 2
3A3A2 × \times × 2A1 × \times × 2 + 1
4A4A3 × \times × 2A2 × \times × 2 + 1
5A5A4 × \times × 2A3 × \times × 2 + 1

从上表可以看到:

  • 虽然从输入A到输出B,相差了两个时钟周期,但是,每个时钟周期输出的B,都和两个时钟周期前输入的A相对应。即:等效地来看,相当于每个时钟周期,模块都完成了一次"B = A × \times × 2 + 1"的计算!

哇塞,好神奇!这简直就是:

  • 明明一次性无法完成的计算,不知怎么搞的,却等效地"被一次性地完成了"

嗯,这种搞法,就是传说中的"流水线计算",这里为其给出了一个不拘泥于FPGA中的情形的、更加普适的定义:

  • 将一个计算拆分成N级(N ≥ \geq 2),每一级的平均数据吞吐速率都相同,前(N-1)级的计算结果在输入下一级之前都经过了缓存,这种方法称为"流水线计算"

注:"平均数据吞吐速率"指的是:在单位时间内,参数平均能够输入多少次,以及计算结果平均能够输出多少次。

流水线计算的结构如下图所示("一条龙"式的结构):

缓存
缓存
...
参数
第1级
第2级
第3级
第N级
结果

分析定义可知:

  • 正是由于"每一级的平均数据吞吐速率都相同",使得整个计算的平均数据吞吐速率相同,才让输出的结果看上去"像是被一次性地完成的"。
  • 级间缓存在流水线计算中是必不可少的,它们起到了两级间隔离的作用,使得"当第(n+1)级在处理第 i i i个输入对应的计算时,第n级已经在处理第 ( i + 1 ) (i+1) (i+1)个输入对应的计算了"这种情况成为可能。增大级间缓存的延迟时间,会造成整个计算的延迟时间(即输出和输入之间的延迟时间)的加长。

总的来说,流水线计算是个好东西,不过,实际使用时,如果不留心的话,容易为级间缓存分配过长的延迟时间,从而出现"整个计算的延迟时间长"的问题,这一点需要注意。



分布式流水线计算

现在,我们要在FPGA中做这样一个计算:

  • C = A × \times × 2 + B × \times × 3
  • 能用的计算单元为两输入加法器和两输入乘法器
  • 为了保证时序,一个时钟周期内加法器只能执行一次加法
  • 为了保证时序,一个时钟周期内乘法器只能执行一次乘法

对于上述要求完成的计算,看上去,在一个时钟周期内既完成乘法又完成加法是不可能的了。然而,如果我们这么做呢:

设计一个模块,模块的输入为时钟和参数A、参数B,输出为结果C。
该模块在每个时钟周期同时做"D=A×2"的计算、"E=B×3"的计算和"C=D+E"的计算,其中,D、E为寄存器。

仿真该模块,就可以发现:

时钟周期参数A(输入)参数B(输入)寄存器D寄存器E结果C(输出)
1A1B1
2A2B2A1 × \times × 2B1 × \times × 3
3A3B3A2 × \times × 2B2 × \times × 3A1 × \times × 2 + B1 × \times × 3
4A4B4A3 × \times × 2B3 × \times × 3A2 × \times × 2 + B2 × \times × 3
5A5B5A4 × \times × 2B4 × \times × 3A3 × \times × 2 + B3 × \times × 3

从上表可以看到:

  • 等效地来看,相当于每个时钟周期,模块都完成了一次"C = A × \times × 2 + B × \times × 3"的计算

这种做法明显蕴含了"流水线计算",不过又跟流水线计算的"一条龙"结构似乎不同,这种做法的结构是"树形"的:

  • 对于输入的参数A和B,先是"你算你的,我算我的",分别输出并缓存D和E。然后拿D和E计算得到结果C。
D
E
参数A
第1级
参数B
第1级
第2级
结果C

对于这种做法,我称之为"分布式流水线计算",这个名称借鉴了计算机网络领域的术语"分布式计算"。使用分布式流水线计算的必要前提是:

  • 计算所需的输入参数不止一个

分布式流水线计算可以看成是"在输入参数不止一个的情况下,展露出计算细节的流水线计算",或者说,从本质上来讲,分布式流水线计算属于流水线计算不要因为看上去是树形结构就帮它"自立门户"了。不信?没事,我将上面的"计算C = A × \times × 2 + B × \times × 3"的模块的结构重新画一下你就信了:

D和E
参数A和B
第1级
第2级
结果C

既然说"分布式流水线计算属于流水线计算",那为什么还要专门在这里讲分布式流水线计算呢?原因是这样的:分布式流水线计算展露了计算细节,更加有助于逻辑开发人员弄清楚计算过程中的延时情况,防止在不知不觉间,增大了整个计算的延迟时间。比如,对于上面的"C = A × \times × 2 + B × \times × 3",如果不加注意的话,可能会变成这样:

缓存
D
D和E
参数A
第1级
参数B
第2级
第3级
结果C

从上图就能看出来,明明两级就能完成的计算,一不留神就变成了三级,整个计算的延迟时间变长了。不过需要说明的是,"整个计算的延迟时间"并不是衡量"一个并行计算结构的优劣"的标准。比如,在做并行卷积计算时,采取流水线的结构可以节省计算资源(但是整个计算的延迟时间长),而采取多相的结构可以缩短整个计算的延迟时间(但是消耗更多的计算资源,虽然其实也多不了太多)。

这里展示一个更加复杂的分布式流水线计算的案例:

缓存
缓存
缓存
缓存
缓存
缓存
缓存
缓存
缓存
缓存
参数B
第1级
参数C
第1级
第2级
参数D
第1级
参数E
第1级
第2级
第3级
参数A
第1级
第2级
第3级
第4级
结果F



交替计算

现在,我们要在FPGA中做这样一个计算:

  • C = A + B
  • 能用的计算单元为两输入加法器
  • 为了保证时序,一个时钟周期内加法器只能执行一次加法,时钟频率最高为100MHz
  • 可以用寄存器来存储数据
  • 为了保证时序,寄存器最高只能工作在300MHz的时钟频率下

乍一看,这情况让人有点迷茫:表达式都简单成这样了,还需要玩并行计算?别急,我们的问题是这样的:

  • A和B都是在300MHz时钟的驱动下刷新的,在这种情况下,如何才能完成"C = A + B"的计算?

呵呵,加法器最高工作频率为100MHz,而数据的刷新频率都飙到300MHz去了,没法玩了好吧!咳咳,其实还是可以玩的。考虑一下这么做:

设计一个模块,模块的输入为300MHz时钟和参数A、参数B,输出为结果C(C为寄存器)。

在300MHz时钟的驱动下:
	在第(i+1)个时钟周期将A、B的值分别赋给A1、B1寄存器,
		在第(i+2)个时钟周期将A、B的值分别赋给A2、B2寄存器,
			在第(i+3)个时钟周期将A、B的值分别赋给A3、B3寄存器,
	在第(i+4)个时钟周期将A、B的值分别赋给A1、B1寄存器,
		在第(i+5)个时钟周期将A、B的值分别赋给A2、B2寄存器,
			……(嗯,基本明白这里的赋值规律了吧?)


模块中,将300MHz的时钟三分频后得到100MHz时钟:clk100m_1,clk100m_2,clk100m_3。
三个100MHz时钟之间的相位都相差(360°/3=)120°。

在100MHz时钟的驱动下(假定分配的100MHz时钟都能满足所需的建立保持时间需求):
	使用加法器,在clk100m_1的每个时钟周期计算C1 = A1 + B1,其中,C1为寄存器。
	使用加法器,在clk100m_2的每个时钟周期计算C2 = A2 + B2,其中,C2为寄存器。
	使用加法器,在clk100m_3的每个时钟周期计算C3 = A3 + B3,其中,C3为寄存器。

在300MHz时钟的驱动下:
	在第(k+1)个时钟周期将C1的值赋给C,(其中,k=i+4)
		在第(k+2)个时钟周期将C2的值赋给C,
			在第(k+3)个时钟周期将C3的值赋给C,
	在第(k+4)个时钟周期将C1的值赋给C,
		在第(k+5)个时钟周期将C2的值赋给C,
			……(嗯,基本明白这里的输出规律了吧?)

上述模块的时序如下图所示:

交替计算的时序图
从上图可以看到:

  • 加法器都工作在100MHz的时钟下
  • A、B、C的刷新频率都是300MHz

可见,我们利用3个最高工作频率100MHz的加法器,等效地实现了1个最高工作频率300MHz的加法器。

这里所采用的方法通常被称为是"乒乓计算"(或者"乒乓操作"),不过,窃以为这个名称不够形象,因此,为这种方法取了个新名称:“交替计算”。交替计算的基本思想是:

  • 将计算单元复制若干份,交替地将输入的参数分配给各个计算单元,并且交替地将各个计算单元的计算结果输出,从而实现"以N倍的计算单元换取N倍的计算速度"。

交替计算的基本结构如下图所示:
交替计算的基本结构



混合型并行计算

在实际的FPGA并行计算中,常常将流水线计算、交替计算混合使用,来满足计算需求。嗯,这就是所谓的"混合型并行计算",没毛病。



用一句话将本论述归纳总结一下,那就是:

  • FPGA中并行计算的基本常规方法为:流水线计算和交替计算。
  • 20
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Xilinx官方翻译的《FPGA并行编程》,本书以10个数字信号处理为例,带我们了解HLS如何使C代码并行运行,深入浅出的将HLS实现方法,硬件设计的考虑 以及系统优化都一一介绍。本书可以在小白仓库微信公众号号免费下载,还可以在Xilinx学术合作找到相应的下载链接。 本人还制作了该书的读书笔记,详情请见《FPGA并行编程》读书笔记专栏启动说明:https://blog.csdn.net/qq_35712169/article/details/99738006 。 本书将着重介绍高层次综合(HLS) 算法的使用并以此完成一些比较具体、细分的FPGA应用。我们的 目的是让读者认识到用HLS创造并优化硬件设计的好处。当然,FPGA的并行编程肯定是有别于在多核处理 器、GPU上实行的并行编程,但是一些最关键的概念是相似的,例如,设计者必须充分理解内存层级和带 宽、空间局部性与时间局部性、并行结构和计算与存储之间的取舍与平衡。 本书将更多的作为一个实际应用的向导,为那些对于研发FPGA系统有兴趣的读者提供帮助。对于大学教育来说,这本书将更适用于高阶的本科课程或研究生课程,同时也对应用系统设计师和嵌入式程序员有 所帮助。我们不会对C/C++方面的知识做过多的阐述,而会以提供很多的代码的方式作为示范。另外,读者 需要对基本的计算机架构有所熟悉,例如流水线(pipeline),加速,阿姆达尔定律(Amdahl's Law)。以寄存器传输级(RTL)为基础FPGA设计知识并不是必需的,但会对理解本书有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值