综述
FPGA设计无可避免的会在FF之间穿插组合逻辑,那么这些组合逻辑如何量化分析?如何优化收敛?如何从RTL设计时就预估到可能产生的延时大小?
接下来就通过一个简单的工程,进行实战演示。
原始工程
定义一个32的计数定时器,定时计数80S,假设主时钟频率50M,代码如下:
module TEST_TOP(input clk_sys, // 50Minput rst ,input plus ,output reg [15:0] d ); function [31:0]count_s( input [7:0] s_n );count_s = 50_000_000* s_n ;endfunctionreg [31:0] cnt_s ;always@(posedge clk_sys or negedge rst)beginif(rst)begincnt_s <= 'd0 ;end else if(cnt_s >= count_s(80)) begincnt_s <= 'd0 ;end else begin cnt_s <= cnt_s + 1 ;endend reg plus_d1,plus_d2;always@(posedge clk_sys)beginplus_d1 <= plus ;plus_d2 <= plus_d1 ;end always@(posedge clk_sys)beginif(s_carry_en)d <= d + plus_d2 ;end
endmodule
上述这段代码,我们如何获取各路径的延时呢?Xilinx提供一种评估方式叫逻辑级数(logic_level),简单来讲就是组合逻辑串联的个数,那么如何获取当前设计的逻辑级数呢?
打开这段代码综合后的文件,在Tcl consol 中运行前面的查询命令可得到当前设计各逻辑级数的路径数,
逻辑级数查询命令
report_design_analysis -logic_level_distribution
-logic_level_dist_paths 5000 -name design_analysis_prePlace
如下图所示,最高逻辑级数有12,共有4条逻辑级数为12的路径。
一般优先分析级数最高的路径,通过report 命令可获得具体的路径信息:
以Path1为例,选中路径,快捷键F4,可获取该路径的原理图:
从原理图可以看到,两个FF之间,共经过1LUT5+2LUT6+8CARRY4 = 11 logic_level ;
首先科普一个概念:
什么是LUT?什么是CARRY?
LUT : look up table,查找表,是FPGA实现组合逻辑的一种方式,具体会在其他讲解底层资源的博
客详细说明;
CARRY:进位链,进位溢出,这个在大学微机原理都学过,为了连接一个过大位宽累加器而存在
的逻辑工具;
if-else的判定条件需要LUT实现,累加器的进位需要靠CARRY实现。
若两个FF直连,则logic_level = 1;
上图中插入了11个逻辑单位,所以logic_level = 12;
该路径的总共传输延时3.015ns,逻辑延时1.46ns。
第一步优化----拆分大位宽累加器
我们再换一种写法,将32位的计数器拆分成两个16位的计数器:
function [15:0]count_ms( input [7:0] ms_n );count_ms = 50_000*ms_n ;endfunctionfunction [15:0]count_s( input [7:0] s_n );count_s = 1_000* s_n ;endfunctionreg [15:0] cnt_ms ;reg [15:0] cnt_s ;always@(posedge clk_sys or negedge rst)beginif(rst)begincnt_ms <= 'd0 ;end else if(cnt_ms >= count_ms(1)) begincnt_ms <= 'd0 ;end else begincnt_ms <= cnt_ms + 1 ;endendalways@(posedge clk_sys or negedge rst)beginif(rst)begincnt_s <= 'd0 ;end else if(cnt_ms >= count_ms(1)) beginif(cnt_s >= count_s(80))cnt_s <= 'd0 ;else cnt_s <= cnt_s + 1 ;endend reg plus_d1,plus_d2;always@(posedge clk_sys)beginplus_d1 <= plus ;plus_d2 <= plus_d1 ;end always@(posedge clk_sys)beginif(cnt_ms >= count_ms(1))d <= d + plus_d2 ;end
重新综合后,输出分析报告我们发现,新设计的最大逻辑级数只有7,
打开逻辑级数为7的路径
传输路径变成2LUT + 4 CARRY4;逻辑单元相对原来减少1个LUT6,和 4个CARRY4。
逻辑延时由之前的1.46ns 降到1.16ns,减少0.3ns ,减少20% ;
CARRY减少很好理解,因为我们将32位的累加器拆成两个16位的累加器,
原来1级累加,拆分后变成两级累加;拆分后每一级只有16位宽;所以每一级FF之间所需要的进位链也相应的减少了;
通过这一步我们可以发现:
1、CARRY4是4输入的,如果累加器或计数器的位宽每超过4就会多消耗一个CARRY4:
比如:例1中,计数器定义32bit,最后消耗了8个进位链;而例2优化成16bit后,就只消耗4个进位链了。
2、正常情况下,布线延时与逻辑延时整体是接近1:1,当降低逻辑级数,减少了逻辑延时,也相应的减少了布线延时。
再看一下路径的具体时序报告,可以看到具体的每一级逻辑的延时:
Incr为增加的延时,Path为中间每个节点的时刻;
第二步优化----简化if-else判定条件
从前面我们可以发现,减少累加器位宽,可以极大减少进位链的级数,进而减少逻辑延时与布线延时,除此之外是否还有别的方法可以达到减少组合逻辑延时呢?从前面看组合逻辑延时主要由两部分组成:1、进位链 ; 2、LUT。
进位链的级数由累加器的位宽决定,那么LUT的个数呢?我们知道LUT是用来实现组合逻辑,且一个LUT只有6个输入,当组合逻辑的复杂度较高和输入信号的位宽数较大时,自然所需要消耗的LUT数量就更多。以前面的工程为例,我们并没有使用assign 这种组合逻辑赋值语句,那么是哪里使用了组合逻辑呢?
答案就是if-else 的逻辑判定条件。if-else 判定条件涉及多位宽数据对比,以及多条件嵌套都会增加实现该判定功能的组合逻辑复杂度。
这是原始代码的always 块书写方式:
always@(posedge clk_sys or negedge rst)beginif(rst)begincnt_s <= 'd0 ;end else if(cnt_ms >= count_ms(1)) beginif(cnt_s >= count_s(80))cnt_s <= 'd0 ;else cnt_s <= cnt_s + 1 ;endend
我们修改一下:
module TEST_TOP(input clk_sys, // 50Minput rst ,input plus ,output reg [15:0] d );function [15:0]count_ms( input [7:0] ms_n );count_ms = 50_000*ms_n ;endfunctionfunction [15:0]count_s( input [7:0] s_n );count_s = 1_000* s_n ;endfunctionreg ms_carry_en ;always@(posedge clk_sys)beginif(cnt_ms == count_ms(1)-1)ms_carry_en <= 'd1 ;else ms_carry_en <= 0 ;end reg s_carry_en ;always@(posedge clk_sys)beginif(cnt_s == count_s(80)-1)s_carry_en <= 'd1 ;else s_carry_en <= 0 ;end reg [15:0] cnt_ms ;reg [15:0] cnt_s ;always@(posedge clk_sys or negedge rst)beginif(rst)begincnt_ms <= 'd0 ;end else if(ms_carry_en) begincnt_ms <= 'd0 ;end else begincnt_ms <= cnt_ms + 1 ;endendalways@(posedge clk_sys or negedge rst)beginif(rst)begincnt_s <= 'd0 ;end else if(ms_carry_en) beginif(s_carry_en)cnt_s <= 'd0 ;else cnt_s <= cnt_s + 1 ;endend
再看一下综合后的效果:
最大的逻辑级数只有6,较例2减少了1级,由原来的LUT4+LUT5减少为1个LUT1(优化后的判定条件只有1bit输入);
总延时由之前的2.356,降至1.451,减少0.9ns ,减少比例达到38%;
逻辑延时由之前的1.16,减少量并不多,通过对比data path数据发现,优化后主要减少了一个LUT5、以及该LUT5前后级的连线。
第三步优化----拆分赋值表达式(面积换速度)
其实这个很好理解,就是将一步运算拆分成多步运算,构建流水线;
例如:S = A + B+ C ;
可以设计成:
S1 = A+B ;
S = S1+C 。
这一步的目的与第二步其实类似,赋值等式右边的实现方式,也是通过LUT与进位链的组合实现,过于复杂的赋值表达式会带来过长的组合逻辑级联。
由于我们的原始例程并没很冗长的赋值表达式,而且这种情况非常常见,也很好理解,暂时不单独举例分析。
总结
1、FF之间的data_delay主要由两部分构成逻辑延时和布线延时;逻辑级数增多、布线节点
增加,布线延时也会相应的增加;
2、布线延时与逻辑延时的占比应该是接近1:1;
当逻辑延时 > 布线延时的50%,请优化逻辑延时;
当布线延时 > 逻辑延时的50% ,请优化布线延时;(参考UG1292)
当延时不满足时,建议先优化逻辑延时,因为这是我们能做的,
布线延时只能靠工具的策略优化,很多时候组合逻辑不合理,可能会导致同一个信号组 被布局到不同列的CLB,导致布线困难。
3、过大的计数器位宽会带来过多的进位链,从而造成过多的逻辑级数;
尽量避免大位宽计数器,250M以内的设计,最好不要超过16bits;
4bits位宽会占用一个进位链;
4、复杂的if - else 判定条件,需要多级LUT实现,也会造成过多的逻辑级数;
设计中尽量避免if-else 嵌套、if-case嵌套;
尽量避免if-else判定条件的输入变量位宽过大;
尽量避免在判定条件处实现多条件的逻辑运算,可以提前打一拍转换成单bit条件;
5、当赋值表达式过于冗长,可以考虑拆分成多级处理,以提高设计性能;
6、组合逻辑级数的合理经验值:≤2N(N为当前时钟域的时钟周期)。
最后,逻辑级数并不是越低越好,一定程度上优化逻辑级数会带来额外的资源消耗,但是当设计不满足性能要求是,优化是必须的。最好是在设计的时候就做到心中有数,避免最后无法实现设计收敛,再回过头来一个个修改,浪费时间。