当前位置:   article > 正文

逻辑级数与逻辑延时优化实战

逻辑级数

综述

      FPGA设计无可避免的会在FF之间穿插组合逻辑,那么这些组合逻辑如何量化分析?如何优化收敛?如何从RTL设计时就预估到可能产生的延时大小?

      接下来就通过一个简单的工程,进行实战演示。

原始工程

定义一个32的计数定时器,定时计数80S,假设主时钟频率50M,代码如下:

  1. module TEST_TOP(
  2. input clk_sys, // 50M
  3. input rst ,
  4. input plus ,
  5. output reg [15:0] d
  6. );
  7. function [31:0]count_s( input [7:0] s_n );
  8. count_s = 50_000_000* s_n ;
  9. endfunction
  10. reg [31:0] cnt_s ;
  11. always@(posedge clk_sys or negedge rst)begin
  12. if(rst)begin
  13. cnt_s <= 'd0 ;
  14. end else if(cnt_s >= count_s(80)) begin
  15. cnt_s <= 'd0 ;
  16. end else begin
  17. cnt_s <= cnt_s + 1 ;
  18. end
  19. end
  20. reg plus_d1,plus_d2;
  21. always@(posedge clk_sys)begin
  22. plus_d1 <= plus ;
  23. plus_d2 <= plus_d1 ;
  24. end
  25. always@(posedge clk_sys)begin
  26. if(s_carry_en)
  27. d <= d + plus_d2 ;
  28. end
  29. endmodule

上述这段代码,我们如何获取各路径的延时呢?Xilinx提供一种评估方式叫逻辑级数(logic_level),简单来讲就是组合逻辑串联的个数,那么如何获取当前设计的逻辑级数呢?

打开这段代码综合后的文件,在Tcl consol 中运行前面的查询命令可得到当前设计各逻辑级数的路径数,

逻辑级数查询命令

  1. report_design_analysis -logic_level_distribution
  2. -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位的计数器:

  1. function [15:0]count_ms( input [7:0] ms_n );
  2. count_ms = 50_000*ms_n ;
  3. endfunction
  4. function [15:0]count_s( input [7:0] s_n );
  5. count_s = 1_000* s_n ;
  6. endfunction
  7. reg [15:0] cnt_ms ;
  8. reg [15:0] cnt_s ;
  9. always@(posedge clk_sys or negedge rst)begin
  10. if(rst)begin
  11. cnt_ms <= 'd0 ;
  12. end else if(cnt_ms >= count_ms(1)) begin
  13. cnt_ms <= 'd0 ;
  14. end else begin
  15. cnt_ms <= cnt_ms + 1 ;
  16. end
  17. end
  18. always@(posedge clk_sys or negedge rst)begin
  19. if(rst)begin
  20. cnt_s <= 'd0 ;
  21. end else if(cnt_ms >= count_ms(1)) begin
  22. if(cnt_s >= count_s(80))
  23. cnt_s <= 'd0 ;
  24. else
  25. cnt_s <= cnt_s + 1 ;
  26. end
  27. end
  28. reg plus_d1,plus_d2;
  29. always@(posedge clk_sys)begin
  30. plus_d1 <= plus ;
  31. plus_d2 <= plus_d1 ;
  32. end
  33. always@(posedge clk_sys)begin
  34. if(cnt_ms >= count_ms(1))
  35. d <= d + plus_d2 ;
  36. 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 块书写方式:

  1. always@(posedge clk_sys or negedge rst)begin
  2. if(rst)begin
  3. cnt_s <= 'd0 ;
  4. end else if(cnt_ms >= count_ms(1)) begin
  5. if(cnt_s >= count_s(80))
  6. cnt_s <= 'd0 ;
  7. else
  8. cnt_s <= cnt_s + 1 ;
  9. end
  10. end

我们修改一下:

  1. module TEST_TOP(
  2. input clk_sys, // 50M
  3. input rst ,
  4. input plus ,
  5. output reg [15:0] d
  6. );
  7. function [15:0]count_ms( input [7:0] ms_n );
  8. count_ms = 50_000*ms_n ;
  9. endfunction
  10. function [15:0]count_s( input [7:0] s_n );
  11. count_s = 1_000* s_n ;
  12. endfunction
  13. reg ms_carry_en ;
  14. always@(posedge clk_sys)begin
  15. if(cnt_ms == count_ms(1)-1)
  16. ms_carry_en <= 'd1 ;
  17. else
  18. ms_carry_en <= 0 ;
  19. end
  20. reg s_carry_en ;
  21. always@(posedge clk_sys)begin
  22. if(cnt_s == count_s(80)-1)
  23. s_carry_en <= 'd1 ;
  24. else
  25. s_carry_en <= 0 ;
  26. end
  27. reg [15:0] cnt_ms ;
  28. reg [15:0] cnt_s ;
  29. always@(posedge clk_sys or negedge rst)begin
  30. if(rst)begin
  31. cnt_ms <= 'd0 ;
  32. end else if(ms_carry_en) begin
  33. cnt_ms <= 'd0 ;
  34. end else begin
  35. cnt_ms <= cnt_ms + 1 ;
  36. end
  37. end
  38. always@(posedge clk_sys or negedge rst)begin
  39. if(rst)begin
  40. cnt_s <= 'd0 ;
  41. end else if(ms_carry_en) begin
  42. if(s_carry_en)
  43. cnt_s <= 'd0 ;
  44. else
  45. cnt_s <= cnt_s + 1 ;
  46. end
  47. end

再看一下综合后的效果:

最大的逻辑级数只有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为当前时钟域的时钟周期)。

最后,逻辑级数并不是越低越好,一定程度上优化逻辑级数会带来额外的资源消耗,但是当设计不满足性能要求是,优化是必须的。最好是在设计的时候就做到心中有数,避免最后无法实现设计收敛,再回过头来一个个修改,浪费时间。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/82783
推荐阅读
相关标签
  

闽ICP备14008679号