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

综述

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

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

原始工程

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

    module TEST_TOP(
    input                clk_sys,    // 50M
    input                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 ;
    endfunction

    reg [31:0]  cnt_s ;
    always@(posedge clk_sys or negedge rst)begin
        if(rst)begin
            cnt_s <= 'd0 ;
        end else if(cnt_s >= count_s(80)) begin
            cnt_s <= 'd0 ;
        end else begin 
            cnt_s <= cnt_s + 1 ;
        end
    end  
     
   reg  plus_d1,plus_d2;
   always@(posedge clk_sys)begin
       plus_d1    <= plus ;
       plus_d2    <= plus_d1 ;
    end 
     
    always@(posedge clk_sys)begin
        if(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,可获取该路径的原理图:

扫描二维码关注公众号,回复: 16447040 查看本文章

从原理图可以看到,两个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 ;
    endfunction

    function [15:0]count_s(  input  [7:0]   s_n        );
        count_s = 1_000* s_n ;
    endfunction
   
    reg [15:0]   cnt_ms ;
    reg [15:0]   cnt_s ;
    always@(posedge clk_sys or negedge rst)begin
        if(rst)begin
            cnt_ms <= 'd0 ;
        end else if(cnt_ms >= count_ms(1)) begin
            cnt_ms <= 'd0 ;
        end else begin
            cnt_ms <= cnt_ms + 1 ;
        end
    end

    always@(posedge clk_sys or negedge rst)begin
        if(rst)begin
            cnt_s <= 'd0 ;
        end else if(cnt_ms >= count_ms(1)) begin
            if(cnt_s >= count_s(80))
                cnt_s <= 'd0 ;
            else 
                cnt_s <= cnt_s + 1 ;
        end
    end  

   reg  plus_d1,plus_d2;
   always@(posedge clk_sys)begin
       plus_d1    <= plus ;
       plus_d2    <= plus_d1 ;
    end 
     
    always@(posedge clk_sys)begin
        if(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)begin
        if(rst)begin
            cnt_s <= 'd0 ;
        end else if(cnt_ms >= count_ms(1)) begin
            if(cnt_s >= count_s(80))
                cnt_s <= 'd0 ;
            else 
                cnt_s <= cnt_s + 1 ;
        end
    end 

我们修改一下:

module TEST_TOP(
    input                clk_sys,    // 50M
    input                rst  ,
    input                plus ,
    output reg  [15:0]   d 
    
    );
    
    function [15:0]count_ms(  input  [7:0]   ms_n        );
        count_ms = 50_000*ms_n ;
    endfunction

    function [15:0]count_s(  input  [7:0]   s_n        );
        count_s = 1_000* s_n ;
    endfunction
 
    reg   ms_carry_en ;
    always@(posedge clk_sys)begin
        if(cnt_ms == count_ms(1)-1)
            ms_carry_en <= 'd1 ;
        else 
            ms_carry_en <= 0 ;
    end   
     reg   s_carry_en ;
    always@(posedge clk_sys)begin
        if(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)begin
        if(rst)begin
            cnt_ms <= 'd0 ;
        end else if(ms_carry_en) begin
            cnt_ms <= 'd0 ;
        end else begin
            cnt_ms <= cnt_ms + 1 ;
        end
    end
  

    always@(posedge clk_sys or negedge rst)begin
        if(rst)begin
            cnt_s <= 'd0 ;
        end else if(ms_carry_en) begin
            if(s_carry_en)
                cnt_s <= 'd0 ;
            else 
                cnt_s <= cnt_s + 1 ;
        end
    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为当前时钟域的时钟周期)。

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

猜你喜欢

转载自blog.csdn.net/ypcan/article/details/129888933