虽然与数学库的原始计算相比,算法的速度有了明显的改善,但客户需要的是速度快得多的实现。不过前文所述的最后一步给我们提供了一种能够轻松转向硬件实现的方法。
这种实现方法需要某些用于拆分 xi值的运算。要在硬件中做到这一点,只需将所需的位进行连接即可。然后我们需要三个表;我们使用以我们的PC模型计算出的预定义值推导出ROM,然后将其转入IP的VHDL代码中。该IP能够一次读取所有三个表,从而能够再度节省时间。最后,我们需要进行一次浮点MUL和一次浮点ADD运算。
对于该任务,我们发现用于浮点运算的CORE GeneratorTM模块非常适合。
图 3 — 无流水线功能的加速器 IP
我们使用一些Slice和乘法器,对这些硬件模块中的两个进行例化。两个内核都要求4到5个周期的延迟,以匹配我们设计的时序要求。延迟在此不是什么问题,我们将在下面的步骤中进行讨论。
我们将最终的IP以MicroBlaze的快速单工链路 (FSL) IP 的形式进行实现。对时序的第一次估算结果表明:
• 将数据从MicroBlaze传输到FSL总线需用一个时钟周期
• 将数据从FSL总线传输至FSL IP(当正弦计算的自变量从FSL总线读出时,将立即从BRAM读取数据,因而无需时钟周期)需用一个时钟周期
• 完成MUL运算 (cos(x)*sin(d)) 需用四个时钟周期
• 将方程的结果存储到寄存器中需用一个时钟周期
• 完成ADD运算需用四个时钟周期
• 将数据发送回FSL总线需用一个时钟周期
• MicroBlaze从FSL IP读取数据需用一个时钟周期。
请注意,在没有使用任何额外流水线(我们将在下一步骤中讨论这一点)的情况下,自变量数据在整个过程中必须保持稳定。这就意味着MicroBlaze仅能请求一次正弦计算,且必须读取该值,然后至少要等上13个时钟周期,才能请求下一次计算。
因此,我们估计进行该实现需要13个时钟周期。当然,要处理软件上的函数调用以及某些其他运算,还需要更多的时钟周期。
我们简单地把一些标准时钟组合在一起,不到一天就实现了该IP,随即在硬件中对该算法进行测量。整个算法(软硬件混合)耗用了360个时钟周期(包括所有的函数调用)。虽然这已是显著的进步,但是仍不足以充分满足客户的需求。
在我们的加速器IP处理所有数据之前,我们使用一个SRL16来延迟信号的写入。
虽然该算法现在可与我们的MicroBlaze并行运行,但它每次只能计算一个值。
步骤六:添加流水线和适配客户代码
设计到了这一步,我们就可以开始向我们的内核添加流水线。浮点ADD和浮点MUL的CORE Generator模块已采用流水线实现,因而我们在此无需再做什么。第一个版本的算法要求自变量保持恒定,直至计算完成。在开始新计算之前(自变量数据到达FSL IP内部),立刻读取两个BRAM并执行浮点MUL。运算的结果在数个时钟周期后生效。
我们的 sin(xi) 的自变量 xi 是一个20位宽的整数,它分为 x 和 d 两个部分。因此,我们必须对自变量 xi的MSB部分 x 进行几个时钟周期的延迟,以读取 BRAM 的内容,存储自变量xi,并将其与MUL运算的结果相匹配。
我们为我们的10位宽数值使用了少量SRL16元件(总共 10 个),共占用了10个LUT(但由于Spartan-6具有LUT组合功能,如果采用该器件较宽的LUT6结构,则仅需 5 个 LUT 即可)。
最后的工作量相当小。在图4中已对增加的SRL16x10位用红圈进行了标注。
图 4:带流水线的加速器内核
然后我们使用EDK向导来修改我们的FSL总线FIFO,以便存储多个值(我们确定能够存储8个值就足以达到我们的目的,但可根据需要轻松增加更多)。
这就意味着我们的客户甚至在请求第一个结果之前即能获得多达8个值。这足以满足我们客户当前的需求,但如果想请求更多正弦值的话,则可以轻松将FIFO缓冲参数扩展为较大的值。
我们在与客户讨论这种新的方案时,发现可将正弦计算进一步划分为两个部分:
1. 请求正弦计算(fslput 运算)
2. 请求正弦计算的结果(fslget运算)
由于我们在运算中有一个固定时延,所以如果这两个运算依次衔接、紧密地按顺序执行,那么MicroBlaze将停顿,并等待FSL IP完成对请求的处理。如果能够将这两组运算分开(这在客户的算法中是可以的),那么我们即可进一步提
升运算的总体速度。通过增加流水线, 在MicroBlaze上执行的最终代码如下:
putfsl(arg1,fsl1_id);
putfsl(arg2,fsl1_id);
putfsl(arg3,fsl1_id);
putfsl(arg4,fsl1_id);
putfsl(arg5,fsl1_id);
putfsl(arg6,fsl1_id);
putfsl(arg7,fsl1_id);
putfsl(arg8,fsl1_id);
...
getfsl(result1,fsl1_id);
getfsl(result2,fsl1_id);
getfsl(result3,fsl1_id);
getfsl(result4,fsl1_id);
getfsl(result5,fsl1_id);
getfsl(result6,fsl1_id);
getfsl(result7,fsl1_id);
getfsl(result8,fsl1_id);
这给我们带来了显著的优势。内核不仅可完全实现流水线功能,而且还能够将正弦计算的两个调用分开。IP核的时延依然存在,但不再明显。MicroBlaze也不再发生停顿和等待未完成的IP计算的情况,从而提高了整体性能。
客户同意对代码进行相应调整,这对客户来说只是小量工作。通过使用C语言的宏命令取代函数调用,我们就能够把所有要求的调用插入代码库中。
图 5 - EDK为FSL总线实现了深度为 8 的 FIFO 以提升流水线的性能
最终实现的算法一次计算只需要四个时钟周期。处理的总体时延不再明显,而被调用的划分以及结果请求所隐藏。另外,整体IP需要一些额外的BRAM(需为我们的三个表增加六个BRAM)和一定数量的乘法器或DSP Slice以及一些其他Slice。
但结果非常令人吃惊。我们的MicroBlaze现在就能够如同超高端处理器内核一样运行,而且其运行频率仍然相当低(现在比原来的正弦计算约快9,600 倍)。
步骤七:进一步优化?
当我们达到这种实现水平时,我们的客户对结果感到非常满意,并且我们也完成了加速器IP方面的工作。速度和精度都非常不错。
当然,还有一项最终优化需要完成。如果我们在d值非常小的情况下对sin(d) 值进行考察,算法还可以进一步完善:
sin(d) = ~d
若d值小于2*π/1024,即小于0.0061359,那么总体误差则小于 1E-8(针对有 1,024 个值的表)。
我们算法的最后步骤将为:
sin(x+d) = sin(x) + cos(x) * d
这样只会存在非常小的额外误差,但我们可以去掉第三个表。当然,我们必须保留 fadd 和 fmul运算器。虽然我们还可以通过其他方式来计算浮点值的正弦值,但这种方案充分显示了增添硬件加速器的强大功能。我们的开发经历表明,你们无需为了将含有浮点计算的算法在硬件中实现而担心。
评论