开启辅助访问
 找回密码
 立即注册

FPGA高速设计(二)

yifeichongtian 回答数20 浏览数1460
针对FPGA工程师,要设计出好的代码,必须了解FPGA内部的结构,所以开始也就稍微啰嗦一些书本上的基本知识。要说明所有涉及到的实例都是基于xilinx公司的K7芯片,总结出的逻辑层级在其他结构的FPGA芯片不能通用,需要自行计算得出,本文中也会给出计算方法。
一、FPGA内部结构
        xilinx的FPGA基本逻辑单元叫CLB或者CLE(反正含义都差不多),对于K7芯片,每个CLB中有两个SLICE,SLICE分为SLICEM和SLICEL,通常SLICEM中的LUT更多作为分布式RAM进行使用,逻辑主要靠SLICEL来搭建,所以可以认为SLICE才是真正可重用的基本单元。展开SLICE后,发现内部包含4个6输入的LUT、3个MUX、1个4输入进位链和8个FF。SLICE内部结构随着芯片架构不同而不同,下图列出K7和VU9P的LUT结构,可以看到vu9p的SLICE更为强大,拥有8个6输入的LUT,而K7只有4个6输入LUT,别的不说,单凭这一点就是质的改变。(上图为K7,下图为vu9p)

        我们深挖一下LUT,LUT翻译过来叫查找表,它的作用是实现所有逻辑函数的功能模块。更通俗一点,我们知道对于ASIC是用门电路搭建而成的,而对于FPGA来说,无论什么逻辑都可以用LUT来实现。数字电路中学过与非门、异或门、选择器等等基本单元,但是LUT可以把这些功能全部实现,只要输入够多,它可以实现任何复杂的bit运算。乍一看确实感觉它很神奇,那为啥ASIC也不这么做呢?我们再简单剖析下LUT的内部结构。
        对于6输入的LUT而言,你可以认为它的本质就是6bit位宽的存储器(不够严谨但是可以这么理解),能够存储2^6=64位数据,如下图所示,6个输入表示6位地址线,一台LUT只有一台输出,这1bit的输出值就存储在存储器中,当输入特定地址后将对应的值读出,所以LUT的延迟比一般的专用逻辑电路要大,而且对于FPGA来说,即使综合出一台反相器,也需要使用一台LUT实现,根本上同样的逻辑, FPGA使用的资源要远大于ASIC。

        在设计中,大量的资源都是用逻辑来实现功能,而逻辑都是使用最简单的加减乘除、比较、条件判断和循环等运算搭建的,这也是我们使用FPGA设计电路最重要、重复性最高的部分。提到加法器,就会想到进位链,如果学过数字电路,就会知道常用的有串行进位链以及更快速的超前进位链,很不巧FPGA厂商选择了前者,因为可重用行更高。下图显示出K7中的4输入CARRY的结构图,乍一看有点小复杂,我先说一下pin的含义,一会把加法器说完你就会突然觉得这图很简单。这个进位链有4位输入输出,所以每一位运算都是重复的,我们以最低位说起:CIN为上一级传过来的进位值,CYINIT为初始状态时的进位值;S0为加法器中的最低位的和,同时DI0是一台中间信号,如果要计算O=A[3:0]+B[3:0],那么DI0就是A[0]或者B[0]任意一台,后面会详细介绍原因,O和CO分别是加法器的和与进位值输出。

二、基本运算在FPGA中的实现
2.1  加法器原理
        加法器在数字电路中都学过,没必要花费篇幅去介绍,所以我就直接把公式贴出来。

        ^表示异或逻辑,+表示或逻辑,AB表示A和B的与逻辑。
2.2  加法器在FPGA中的实现
        先上一张综合图,4位加法器在vivado真正综合后的原理图如下图,可以看到4位加法器使用4个LUT2和一台CARRY4,LUT2表示2输入的LUT。

结合加法器的公式,我们知道LUT应该是用于异或计算,所以CARRY4中S[3:0]端的输入应该是{A[3]^B[3], A[2]^B[2], A[1]^B[1], A[0]^B[0]},CI是上一级进位制,CYINIT是进位初始值都是0,但是DI端链接到了A上,上一节解释了其实链接到B上也是可以的,下面我们看看这是为啥?我们将上面的综合图美化一下。

其实应该有4个异或门,为了省事只画了一台。上图中的推导比较详细了,比如你要设计进位链的电路,你一开始肯定会想到有S和CI端口,S=0时你推导出输出进位值CO=A或者B;当S=1时你得到CO=Ci,其中Ci表示上一级的进位值。那么一切都变得通顺了,你增加了一台端口DI用于链接输入的任意一台信号,用S端口作为选择器的sel,经过选择就可以直接得到输出进位值CO了。我再把进位链的电路放大版贴出来一遍,目前看上去是不是异常简单,CO0则看作[0]位的输出进位,也是[1]位的输入进位值,最终数据输出O1需要结合S和Ci进行异或运算。到此针对CARRY6或者CARRY8一样的道理,只是多复制了几份级联而已。

我们把SLICE内部彻底搞透以后,很容易发现4比特加法器的电路其实只用一台SLICE就可以实现(4个LUT2+1个CARRY4),下面引出逻辑层级的概念,广义来说,两个寄存器之间的组合逻辑所包含的LUT、CARRY、MUX的最大个数可以认为是逻辑层级,我们通常只考虑关键路径(延迟最大的路径),查看综合网表时由于不涉及线延迟,所以逻辑层级最大的那条路径也认为时关键路径。那么4比特加法器逻辑层级是多少呢?很简单就是两级,LUT+CARRY,那对于8比特、12比特甚至更大位宽的加法器应该如何综合呢?逻辑层级又是多少呢?
        因为每个SLICE只有一台CARRY4,对于8位加法器,进位链就需要级联了,使用一台SLICE实现低4比特加法器,另外一台SLICE实现高4比特加法器,中间需要进位链接,只用把两个CARRY4级联就完成了。所以8比特位宽加法器需要2个SLICE(8个LUT2和2个CARRY4)可以实现,最大级数为3级,最长路径为LUT2--CARRY4--CARRY4;同理对于12位加法器,需要12个LUT2和3个CARRY4,最大级数为4级,最长路径为LUT2--CARRY4--CARRY4--CARRY4。8bit加法器的vivado综合结果如下图所示,两个进位链进行了级联:

        这里临时插一段,为啥说同样的代码,但是不同FPGA综合出的电路不同,逻辑层级也不一样呢?我们以vu9p举个栗子,大家可以翻到上面看看其SLICE的内部结构,是由8个6输入LUT构成的,进位链也是8位的,提问:如果对于vu9p芯片,设计8比特加法器逻辑层级为多少?答案是两级,因为只使用了一台SLICE,最长路径为LUT2--CARRY8。很明显使用高端芯片不仅工艺上可以减少线延迟,在SLICE架构上也可以减少设计代码的逻辑层级,而且加法器位宽越大差距越明显,比如设计24比特加法器,K7上综合出逻辑层级为24/4+1=7级,但是对于vu9p来说逻辑层级为24/8+1=4级,有时候芯片和芯片的差距就是这么大。
下表为总结的针对K7芯片的全加器逻辑层级,其实都可以自个口算出来:

        上面的加法器类型是标准的全加器,但是我们通常写代码的时候用到的加法器并不局限于S=A[3:0]+B[3:0],比如下图中常常用到的两种类型,S=A+1我暂且称为被加数为1的加法器,对于S=S+1则叫它累加器。这个代码其实也是很有迷惑性的,因为只是进行加1的操作,不少同学甚至工作的同行也会认为它没有危害性,殊不知它的逻辑层级也是不容小视。

       我们先说说累加器,累加器可以当做加法器进行综合,即可以使用LUT2和CARRY4的进位链方式进行组合,也可以只用LUT的方式进行组合。对于小位宽的累加器,如果使用LUT2和CARRY4组合,最高层级为2级,如果只使用LUT则只需要1级。所以对于小位宽累加器,vivado更倾向于用LUT方式综合,4bit的累加器代码和电路图如下所示,共使用4个LUT和4个寄存器。

由于是累加器,其中一台加数不管位宽为多少,另一台加数永远是1,每个周期数据最低位都需要翻转,所以第0位(最低位)的输出OUT[0]经过反相器后再进入寄存器;对于第1位,如果最低位为0,最终输出OUT[1]仍然是上一台周期的值,反之则取反;对于第2位,需要将前两位的结果相与,如果结果为1,表示最低两位为2'b11,则累加后必须有进位,输出OUT[2]与上一周期值取反;同理最高位也一样,一共只需要4个LUT就可以搞定了,看来工具也是会偷懒的。下面贴出vivado综合出的结果:

其实这种综合方式在小位宽时比较好用,但是当位宽稍微大一点时,一台SLICE的资源就不够用了,需要将多个SLICE进行级联,而且位宽越大级联越多,导致整个路径的延迟成比例增加。比如当7bit累加器综合时,最高位的计算需要一台6输入LUT和一台2输入LUT级联,更高位宽的依次类推;可推算出对于这种综合方式,N位累加器最大层级为ceil(N/6),使用最多LUT为N+N/6
        当位宽稍微大一点时,vivado就放弃了这种只使用LUT的综合方式,从而使用CARRY4中的逻辑配合LUT进行计算,暂时我们称为进位链综合方式,将上面的电路称为LUT综合方式。下图举例说明了vivado的进位链综合方式是指什么样的,最直观的电路就是下图左边所示,可以看成一台4bit输入和一台4'b0001固定值的加法器,但这样浪费资源,因为对于或门只要一台输入是0,输出都等于另一台输入,所以去掉无用的逻辑,就变为下方右图的电路。

        所以累加器的这种进位链综合方式突然简单多了,不管输入为多大位宽,永远只需要一台LUT,剩下的就是CARRY的级联,因为CARRY是专用电路,延迟比LUT小,所以这样明显划算很多,下图展示了12bit位宽累加器所占用资源,只需要一台LUT1和三个CARRY级联即可。更一般的情况,针对K7芯片来说,对于N位累加器,使用进位链综合方式,最大层级为1+ceil(N/4),使用的LUT数为N,使用CARRY4个数为ceil(N/4)

为了省事记忆,将累加器的资源占用和逻辑层级也做成表格,综合方式为LUT表示第一种方式,即只使用LUT搭建;LUT+CARRY4表示进位链综合方式,Vivado一般在12bit位宽以上才会使用进位链的综合方式。同理如果你用的是vu9p,根据SLICE结构你可以自个进行运算。

其实有些东西越刨越深,越挖越基础,但是当把这些基础的东西融会贯通,写高质量的代码只是手到擒来,我见过很多团队,在FPGA代码review时基本就审核一下格式、命名以及拆分同一台always中看上去很冗余的逻辑,他们真的就认为这是好代码的标准,但是我们是设计电路,不是搞软件开发,代码的可读性很重要,但是有时候不是多拆几个always这种表面的东西可以让设计变得更好的,因为或是需要从最底层思考。今天写不完了,就光加法器还有很多东西要总结,后面还会有比较器、选择逻辑等等基本逻辑的深挖,同时会专门写一下对于加法器这类逻辑如何进行流水拆分和真正的示例,估计还得写个4-5次吧,逻辑写完后看时间再分享一些设计架构和约束方面的东西。
最后链接上一篇谈一谈if-else,因为FPGA综合if-else逻辑并不会占用太多逻辑层级,那是不是可以随便用了呢?其实是有限制的,因为if或者else if中的判断逻辑会决定整体的逻辑层级,比如下图中的代码,很多同学经常这么写,虽然只有两级if-else,但是这中逻辑对高速设计是要命的存在,首先A[23:0]+B[23:0]是一台加法器,对K7芯片来说需要7级逻辑,然后又要判断A+B是否与C相等,又是一台24比特相等的比较,后面会介绍这也会占用不少逻辑层级,而且这两个逻辑的层级会叠加,如果一台项目中大量使用这种设计,这一定是灾难性的。其实资深中很多同行说写代码时心中有电路,但起码看到代码大脑能很快浮现出综合图是不夸张的。今天太晚了先不写了,下回贴上这个代码的综合图和分析。
使用道具 举报
| 来自浙江 用Deepseek满血版问问看
goldendd | 来自云南
A+B==C这类判断,我在工程中遇到的话,(如果不可通过改进算法设计等避免)实现起来其实挺头疼的。

一般是先去vivado/ISE的IP库里点一个加法器IP出来,例化,算出A+B的和SUM,然后再判断SUM==C,结果保存到一个1bit的寄存器flag。
实际A+B==C就被转化成了flag==1'b1。

一般情况下最终生成后,能跑的频率确实挺高,没有问题(加法器那一块一般直接选的它推荐的寄存器流水线级数)。
但是这样搞,确实麻烦,毕竟这点东西都要绕很大一个弯;而且还得计划好各个方面的流水线级数(保证需要的信号能在规定的时钟碰到一起)

不知道前辈在这些方面有没有更好的实现方法?目前我的做法只能解决时钟频率问题,但是设计/RTL代码和当前的需求高度绑定,工程大了以后,想做修改很难。
用Deepseek满血版问问看
回复
使用道具 举报
netyxu | 来自北京
1、在我们设计中很少关注这种逻辑是因为资源利用率还不是很高,一般情况下工具是比较智能的,对于大位宽加法器或者说高层级的逻辑,vivado在布局时首先会处理它们,方法就是尽量让这些逻辑所用到的slice相邻,这样就可以大量避免线延迟(在k7这类芯片中我们预估逻辑延迟和线延迟为1:3,当然不一定准确只是经验值),这样一来你会发现它还能跑的挺快的。但是如果资源利用率很高,工具不能找到相邻的slice那么就会出问题,如果代码通篇都是这种逻辑那后期修改时序的人也肯定是奔溃的。
2、优化方法你说的思想是对的,但是不一定非要调用ip,还是针对if(a+b==c)这种情况,假设位宽都在24bit,总得逻辑层级很难接受,那么就使用流水线拆分逻辑,先设置一个寄存器reg_temp<=a+b,然后再定义一个寄存器flag<=(temp==c)? 1:0即可,最后在if中只需要判断flag的值就可以了。这样一来把逻辑拆分为多级流水,但是在初始阶段运算的有效值不能在1 cycle完成,所以设计时就要这样做,如果回过头去更改别人的代码那注定是奔溃的,因为各个模块时序强关联,牵一发而动全身。
3、当然如果上面逻辑的位宽很大,比如64bit,这在设计中也很常见,那仅仅是a+b可能逻辑层级已经飞上天了,这时候可以调用加法器ip,设置好流水级数,工具自动把加法器逻辑拆分,也是多个cycle后出数。又或者可以自己写可重用的加法器拆分代码,可以减少不同片子间移植问题(xilinx转intel)。
回复
使用道具 举报
alewis | 未知
谢谢
回复
使用道具 举报
Sky-Blue.com.hk | 来自北京
请问一下为啥这个加法器正常流程写综合出来是纯LUT实现的?
回复
使用道具 举报
学习者o | 来自北京
fpga是以lut为逻辑单元的,所以加法都用lut加carry形式,当然代码加入*use dsp*的约束就会使用dsp资源。一般超大位宽高速运算才会考虑用dsp,否则比较浪费
回复
使用道具 举报
9999vuf | 未知
我综合了一版本程序,但它是用纯lut实现的,连carry都没用,一个3bit+3bit的加法器;但我如果是30bit+30bit他就会用lut+carry了,请问一下这是为什么
回复
使用道具 举报
燕妮妮 | 来自广东
感谢分享。
Carry4 应该就是超前进位加法器吧。Guide里说的很清楚,是 lookahead carry logic,然后输入S是propagate,输入DI是generate。
回复
使用道具 举报
RIEyjbGO | 来自北京
Carry4只是进位链,并不是超前进位链,因为它仍然是逐级进行运算,而超前进位原则,是每一位的进位值不依靠上一位进位值算出来后再进行运算
回复
使用道具 举报
cnpwr | 来自浙江
这个是综合器的策略问题,其实不管再复杂的电路,你只用门级就能搭建,同样你只用LUT也可以实现,只是当位宽大的时候耗费的LUT会增加很多,所以需要用到carry,而小位宽的时候只用LUT会更省资源,你可以根据综合出的电路看看为什么这么做
回复
使用道具 举报

相关问题更多>

123下一页
快速回复
您需要登录后才可以回帖 登录 | 立即注册

当贝投影