通常,我们使用正确的拓扑结构来减少面积,简而言之,将逻辑资源尽可能的复用,当然这会带来速度或者吞吐率的下降。因此常常用在需要迭代递归的运算中,这些运算的输出往往需要反馈回到输入端。
以下策略经常被用于改善FPGA设计的面积:
-
卷起流水线
“卷起流水线”这个名字听起来较难理解,实际上这是一种跟我们常用的插入流水线相反的操作,即将一些并行流水线的逻辑通过更简单的串行结构来实现,具体需结合实际用例更好理解。
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小节的例子中,便使用了循环移位寄存器(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
-
在不同功能的操作中共享逻辑资源
在这里所说的资源共享并不是指FPGA布局布线工具对底层的优化,而是指在顶层架构级别的资源块可以被两个不同的功能模块共享。
下面以一个在FPGA设计中最常用到的计数器为例
image.png在模块A和模块B中,分别使用了8比特和11比特的计数器,将计数器资源放到更高层级,让A和B模块共享使用,如下:
image.png
-
复位对面积优化的影响
在我们进行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图:
和
而这两者实现时所消耗资源也会有所差异
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图为
由于乘法器中只有复位接口而无置位接口,因此在代码中如果我们使用置位操作,那么复位端口被闲置的同时将会引入额外的逻辑完成置位操作。
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核对外围引入额外的逻辑
额外逻辑资源消耗情况如下
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
而使用异步复位,将会综合处一个distributed RAM,并增加额外解码逻辑
这两者的资源消耗情况为
如果使用了错误的RAM复位方式,可能对综合结果产生极大的影响。
笔者注:在当前使用调用IP核的方式实现RAM时,同步复位和异步复位的影响不会如上述情况。
4.5 利用触发器的复位/置位功能实现某些逻辑
FPGA的触发器往往是支持复位或者置位功能的,因此在进行设计时,我们可以利用这一点将一些逻辑从组合逻辑中转移出来,例如,使用置位端实现或门:
或是利用复位端实现与门
在下面的设计中,我们可以通过移除异步复位信号并利用复位端实现对应逻辑
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图为:
可优化为:
更进一步的,在下面设计中可以做到更好
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图为
最后,总结起来一句话:在考虑设计的面积优化时,尽可能少的时候复位。