进位链FPGA

进位链
在这里插入图片描述

CI是上一个CARRY4的进位输出,位宽为1;
CYINT是进位的初始化值,位宽为1;
DI是数据的输入(两个加数的任意一个),位宽为4;
SI是两个加数的异或,位宽为4;
O是加法结果输出,位宽为4;
CO是进位输出,位宽为4;(为什么进位输出是4bit?后面有解释)
Carry4的内部结构如下图所示
在这里插入图片描述
这里我们要先解释一下FPGA中利用Carry Chain实现加法的原理,比如两个加数分别为a = 4’b1000和b=4’b1100,其结果应该是8+12=20。

a = 4'b1000;
b = 4'b1100;

S = a ^ b = 4'b0100;
D = b = 4'b1100;          //D取a也可以
CIN = 0;                  //没有上一级的进位输入
CYINIT = 0;               //初始值为0
// 下面为CARRY4的计算过程,具体的算法跟上图中过程一样
S0 = 0//S的第0位
O0 = S0 ^ 0 = 0 ^ 0 = 0;
CO0 = DI0 = 0;            //上图中的MUXCY,S0为0时,选择1,也就是DI0,S0为1是选择2
S1 = 0;
O1 = S1 ^ CO0 = 0 ^ 0 = 0;
CO1 = DI1 = 0;
S2 = 1;
O2 = S2 ^ CO1 =1 ^  0 = 1;
CO2 = CO1 = 0;
S3 = 0;
O3 = S3 ^ CO2 = 0 ^ 0 = 0;
CO3 = DI3 = 1;

加法最终的输出结果为:{CO3,O3,O2,O1,O0} = 5’b10100 = 20。进位输出在CARRY4的内部也使用到了,因此有4个bit的进位输出CO,但输出给下一级的只是CO[3]。

module top(

 input clk,
 input [7:0] din_a,
 input [7:0] din_b,
 output reg[7:0] dout
    );

 always @ ( posedge clk )
 begin
    dout <= din_a + din_b;
 end  
endmodule

综合之后的电路如下:
在这里插入图片描述
在本程序中,加数为din_a和din_b,图中

1表示CARRY4的进位输出到下一级的进入输入;
2表示输入的一个加数din_a(换成din_b也是可以的);
3表示第二级输入的DI端口,因为第二级CARRY是通过第一级的进位输出进行累加,因此该接口为0;
4表示输入两个加数的异或结果。
可以看出,当进行两个8bit的数据进行加法操作时,会使用两个CARRY4级联,那如果是对48位的数据进行相加,那就会用到12个的CARRY4的级联,这样明显就会使逻辑延迟过大,很容易造成时序不收敛。(这里需要注意的是,在Vivado的默认设置下,如果进行的是12bit以下的数据加1’b1的操作,那么Vivado综合的结果并不会使用CARYY4,而是使用LUT来实现加法器)。

那如何解决这种问题呢?我们可以把加法操作进行拆解,比如拆解成3个16bit的计数器,那这样就会只有4个CARRY4的级联,时序情况就好了很多。

对比程序如下:

module top(

 input clk,
 input [47:0] din1,
 input [47:0] din2,
 output reg[47:0] dout1,
 output    [47:0] dout2
 );

 always @ ( posedge clk )
 begin
    dout1 <= din1 + 1'b1;
 end  

 genvar i;
 generate
 for(i = 0;i < 3;i=i+1) begin:LOOP
    wire carry_co;
    reg [15:0] carry_o=0;
    wire ci;
    if(i==0)  begin
        always @ ( posedge clk )
         begin
            carry_o <= din2[i*16+:16] + 1'b1;
         end
     end //if
     else begin
        always @ (posedge clk) begin
            if(LOOP[i-1].carry_co == 1)
                carry_o <= carry_o + 1'b1;
        end
     end //else
    assign LOOP[i].carry_co = (LOOP[i].carry_o==16'hffff)?1'b1:1'b0;
    assign dout2[i*16+:16] = LOOP[i].carry_o;

 end //for

 endgenerate

endmodule

打开综合后的schematic后可以发现,在dout2的输出中,每4个CARRY4后都会有一级的触发器,这样时序就会好很多,但这样做的代价是LUT会增加。

对于不同的位宽的数据,我们后面会给出一个通用的Verilog代码,
ref

https://zhuanlan.zhihu.com/p/92385125

发布了415 篇原创文章 · 获赞 251 · 访问量 68万+

猜你喜欢

转载自blog.csdn.net/qq_35608277/article/details/104601772
今日推荐