第二章 FPGA改善面积方法

​ 通常,我们使用正确的拓扑结构来减少面积,简而言之,将逻辑资源尽可能的复用,当然这会带来速度或者吞吐率的下降。因此常常用在需要迭代递归的运算中,这些运算的输出往往需要反馈回到输入端。

​ 以下策略经常被用于改善FPGA设计的面积:

  1. 卷起流水线

    “卷起流水线”这个名字听起来较难理解,实际上这是一种跟我们常用的插入流水线相反的操作,即将一些并行流水线的逻辑通过更简单的串行结构来实现,具体需结合实际用例更好理解。

    module mult8(
        output [7:0] product,
        input [7:0] A,
        input [7:0] B,
        input clk);
        
        reg [15:0] prod16;
        
        assign product = prod16[15:8];
        
        always @(posedge clk)
         prod16 <= A * B;
    endmodule
    

    优化后为:

    module mult8(
        output done,
        output reg [7:0] product,
        input [7:0] A,
        input [7:0] B,
        input clk,
        input start);
        
        reg [4:0] multcounter;   // counter for number of shift/adds
        reg [7:0] shiftB;        // shift register for B
        reg [7:0] shiftA;        // shift register for A
        
        wire adden;          // enable addition
        
        assign adden = shiftB[7] & !done;
        assign done = multcounter[3];
        
        always @(posedge clk) begin
            // increment multiply counter for shift/add ops
            if(start) 
             multcounter <= 0;
            else if(!done) 
             multcounter <= multcounter + 1;
            // shift register for B
            if(start) 
             shiftB <= B;
            else 
             shiftB[7:0] <= {shiftB[6:0], 1’b0};
            // shift register for A
            if(start) 
             shiftA <= A;
            else 
             shiftA[7:0] <= {shiftA[7], shiftA[7:1]};
            // calculate multiplication
            if(start) 
             product <= 0;
            else if(adden) 
             product <= product + shiftA;
        end
    endmodule
    

    对应RTL图为:


    image.png

    在之前的乘法器中,数据可以流水线的形式每拍输出一个结果,而将流水线卷起后,使用移位寄存器和加法来实现乘法运算,降低了逻辑资源,但这样需要8个clk才能够完成一次运算。

  1. 通过额外控制来复用逻辑

    复用逻辑资源通常需要加入额外的控制来决定资源被谁使用,实际上在第1小节的例子中,便使用了循环移位寄存器(shiftB)的比特来控制累加器是否计算,而在其他应用中,控制的逻辑往往会更加复杂。因此,在复用的逻辑比控制逻辑资源更大时我们可以增加额外的控制来指定计算逻辑的复用,而控制逻辑我们惯用状态机来实现。

    下面以一个数字低通FIR滤波器为例

    Y = coeffA*X[0] + coeffB*X[1] + coeffC*X[2]
    

    优化得到:

    module lowpassfir(
        output reg [7:0] filtout,
        output reg done,
        input clk,
        input [7:0] datain, // X[0]
        input datavalid, // X[0] is valid
        input [7:0] coeffA, coeffB; coeffC); // coeffs for low pass filter
        
        // define input/output samples
        reg [7:0] X0, X1, X2;
        reg multdonedelay;
        reg multstart;           // signal to multiplier to begin computation
        reg [7:0] multdat;
        reg [7:0] multcoeff;     // the registers that are multiplied together
        reg [2:0] state;         // holds state for sequencing through mults
        reg [7:0] accum;         // accumulates multiplier products
        reg clearaccum;      // sets accum to zero
        reg [7:0] accumsum;
        wire multdone;           // multiplier has completed
        wire [7:0] multout;  // multiplier product
        
        // shift-add multiplier for sample-coeff mults
        mult8x8 mult8x8(
        .clk(clk), 
        .dat1(multdat),
        .dat2(multcoeff), 
        .start(multstart),
        .done(multdone), 
        .multout(multout)
        );
        
        always @(posedge clk) begin
            multdonedelay <= multdone;
            // accumulates sample-coeff products
            accumsum <= accum + multout[7:0];
            
            // clearing and loading accumulator
            if(clearaccum) 
             accum <= 0;
            else if(multdonedelay) 
             accum <= accumsum;
             
            // do not process state machine if multiply is not done
            case(state)
            0: begin
                // idle state
                if(datavalid) begin
                    // if a new sample has arrived
                    // shift samples
                    X0 <= datain;
                    X1 <= X0;
                    X2 <= X1;
                    multdat <= datain; // load mult
                    multcoeff <= coeffA;
                    multstart <= 1;
                    clearaccum <= 1; // clear accum
                    state <= 1;
             end
                else begin
                    multstart <= 0;
                    clearaccum <= 0;
                    done <= 0;
                end
            end
            1: begin
                if(multdonedelay) begin
                    // A*X[0] is done, load B*X[1]
                    multdat <= X1;
                    multcoeff <= coeffB;
                    multstart <= 1;
                    state <= 2;
                end
                else begin
                    multstart <= 0;
                    clearaccum <= 0;
                    done <= 0;
                end
            end
            2: begin
                if(multdonedelay) begin
                    // B*X[1] is done, load C*X[2]
                    multdat <= X2;
                    multcoeff <= coeffC;
                    multstart <= 1;
                    state <= 3;
                end
                else begin
                    multstart <= 0;
                    clearaccum <= 0;
                    done <= 0;
                end
            end
            3: begin
                if(multdonedelay) begin
                    // C*X[2] is done, load output
                    filtout <= accumsum;
                    done <= 1;
                    state <= 0;
                end
                else begin
                    multstart <= 0;
                    clearaccum <= 0;
                    done <= 0;
                end
            end
            default
             state <= 0;
            endcase
        end
    endmodule
    

    其对应RTL图为


    image.png
  1. 在不同功能的操作中共享逻辑资源

    在这里所说的资源共享并不是指FPGA布局布线工具对底层的优化,而是指在顶层架构级别的资源块可以被两个不同的功能模块共享。

    下面以一个在FPGA设计中最常用到的计数器为例


    image.png

    在模块A和模块B中,分别使用了8比特和11比特的计数器,将计数器资源放到更高层级,让A和B模块共享使用,如下:


    image.png
  1. 复位对面积优化的影响

    在我们进行FPGA设计时,一个很大的误区就是复位只在全局层面进行实现而对设计的大小影响极小;其实,在考虑设计面积的时候,对大量信号都进行复位是会带来很大的时序和布线压力的。

    听起来为每一个触发器都设置复位是一个比较好的设计习惯,但这样的代价是工程会变得更大也更慢,同时会抑制工具对区域的优化,而这是不必要的。

    接下来会在具体场景中说明复位操作给设计的速度和面积带来的影响,以及怎么进行优化。

4.1 无复位接口的资源

对于某些FPGA资源来说,它们并不存在复位接口,例如简单的循环移位寄存器:

always @(posedge iClk)
 if(!iReset) 
     sr <= 0;
 else 
     sr <= {sr[14:0], iDat};

always @(posedge iClk)
 sr <= {sr[14:0], iDat};

这两种描述看似差别很细微,仅仅是前者在复位时将寄存器中的值置零而已,然而对于FPGA来说,第一种描述将会使用触发器实现,第二种描述可以使用FPGA内置的循环移位寄存器SRL16来实现,下面给出两种设计对应RTL图:


image.png


image.png

而这两者实现时所消耗资源也会有所差异


image.png

4.2 无置位接口的资源

与4.1相似的,FPGA中有些资源是没有置位接口的,比如一个8*8的乘法器

module mult8(
    output reg [15:0] oDat,
    input iReset, iClk,
    input [7:0] iDat1, iDat2,
    );
    
    always @(posedge iClk)
     if(!iReset) 
            oDat <= 16’hffff;
     else 
            oDat <= iDat1 * iDat2;
endmodule

其对应RTL图为


image.png

由于乘法器中只有复位接口而无置位接口,因此在代码中如果我们使用置位操作,那么复位端口被闲置的同时将会引入额外的逻辑完成置位操作。

4.3 没有异步复位的资源

同样的,FPGA中也有一些资源是不支持异步复位的,比如内置DSP核:

module dspckt(
    output reg [15:0] oDat,
    input iReset, iClk,
    input [7:0] iDat1, iDat2);
    
    reg [15:0] multfactor;
    
    always @(posedge iClk or negedge iReset)
        if(!iReset) begin
            multfactor <= 0;
            oDat <= 0;
        end
        else begin
            multfactor <= (iDat1 * iDat2);
            oDat <= multfactor + oDat;
        end
    
endmodule

由于在上述代码中使用了异步复位,因此会在dsp核对外围引入额外的逻辑


image.png

额外逻辑资源消耗情况如下


image.png

4.4 同步复位RAM

在很多FPGA内置的RAM资源一般是支持同步复位,如果我们在用原语设计时采用了异步复位模式,可能会导致最后综合出不同结构的RAM

module resetckt(
    output reg [15:0] oDat,
    input iReset, iClk, iWrEn,
    input [7:0] iAddr, oAddr,
    input [15:0] iDat);
    
    reg [15:0] memdat [0:255];
    
    always @(posedge iClk or negedge iReset)
        if(!iReset)
         oDat <= 0;
        else begin
         if(iWrEn)
             memdat[iAddr] <= iDat;
             oDat <= memdat[oAddr];
         end
    
endmodule

如果使用同步复位,将会综合出一个BRAM


image.png

而使用异步复位,将会综合处一个distributed RAM,并增加额外解码逻辑


image.png

这两者的资源消耗情况为


image.png

如果使用了错误的RAM复位方式,可能对综合结果产生极大的影响。

笔者注:在当前使用调用IP核的方式实现RAM时,同步复位和异步复位的影响不会如上述情况。

4.5 利用触发器的复位/置位功能实现某些逻辑

FPGA的触发器往往是支持复位或者置位功能的,因此在进行设计时,我们可以利用这一点将一些逻辑从组合逻辑中转移出来,例如,使用置位端实现或门:


image.png

或是利用复位端实现与门


image.png

在下面的设计中,我们可以通过移除异步复位信号并利用复位端实现对应逻辑

module setreset(
    output reg oDat,
    input iReset, iClk,
    input iDat1, iDat2);
    
    always @(posedge iClk or negedge iReset)
        if(!iReset)
         oDat <= 0;
        else
         oDat <= iDat1 | iDat2;
    
endmodule

其对应RTL图为:


image.png

可优化为:


image.png

更进一步的,在下面设计中可以做到更好

oDat = !iDat3 & (iDat1 | iDat2)

可以用以下方式实现

module setreset (
    output reg oDat,
    input iClk,
    input iDat1, iDat2, iDat3);
    
    always @(posedge iClk)
        if(iDat3)
         oDat <= 0;
        else if(iDat1)
         oDat <= 1;
        else
         oDat <= iDat2;
    
endmodule

其对应RTL图为


image.png

最后,总结起来一句话:在考虑设计的面积优化时,尽可能少的时候复位。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容