加法器
全加器 Full Adder FA
很简单,输入1bit的两个数以及前一级的进位,输出和与进位符:
1 2 3 4 5 6 7 8 9
| module f_add ( input a, input b, input cin, output sum, output cout ); assign {cout, sum} = a + b + cin; endmodule
|
行波进位加法器 Ripple-Carry Adder RCA
我们把N个上面的全加器f_add首尾串联,便得到了一长串全加器链,它们的计算输入是N位,进位输入与输出均为1位。每次进行计算时,前一个的进位输出都会传进后一个的进位输入,看起来就像是进位在一整条全加器链上产生了波动,因此称其为“行波进位加法器”。
显然,行波进位加法器的优点是结构简单——只需串联就行,但缺点是延时太长。如上图所示,N个全加器串联后,总延时为tripple=N⋅tfa,这是很致命的。
超前进位加法器 Carry-Lookahead Adder CLA
[上面](#行波进位加法器 Ripple-Carry Adder RCA)的行波进位加法器之所以延时太长,是因为每一位的进位计算都依赖前一位,层层叠加导致计算只能串行进行。那有没有什么办法能提高计算速度,比如并行计算出后续的进位而不依赖前面的结果呢?有的兄弟有的。
为了方便,我们定义两个新的信号为产生信号和传播信号,并对输入数据A与B的每一位都进行计算:
-
进位产生Generate:如果Ai和Bi均为1,那么第i位就会产生一个进位输出:
Gi=Ai⋅Bi
-
进位传播Propagate: 如果Ai或Bi为1,那么第i位将把一个进位输入传播到进位输出:
Pi=Ai+Bi
-
进位输出CarryOut: 第i的进位输出Ci表达式为:
Ci=Ai⋅Bi+Pi⋅Ci−1=Gi+PiCi−1
将上面的流程应用到每一位,因其进位求值不需要前一级的结果,而是可以直接由每一位计算出,因此称其为“超前进位加法器”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| module cla_3bit ( input [2:0] a, b, inputcin, output [2:0] cout, output [2:0] sum );
wire [3:0] p, g;
assign p = a ^ b; assign g = a & b;
assign cout[0] = g[0] | (p[0] & cin), cout[1] = g[1] | (p[1] & g[0]) | (p[1] & p[0] & cin), cout[2] = g[2] | (p[2] & g[1]) | (p[2] & p[1] & g[0]) | (p[2] & p[1] & p[0] & cin);
assign sum = p ^ {cout[1:0], cin};
endmodule
|
可求得加法延时为:
tCLA=tpg+tpg−block+(kN−1)tAND_OR+k⋅tFA
其中:
- tpg为生成Pi和Gi的列传播和产生门电路(单个 AND 或 OR 门)的延迟
- tpg_block是在 k位块中生成块传播信号Pi:j和产生信号Gi:j的延迟
- gd_or是在k位的 CLA 块中通过最后的 AND/OR 逻辑从 Cin 到 Cout 的延迟
前缀加法器 Parallel-Prefix Adder PPA
参考:树形加法器(Brent-Kung加法器) - CSDN
观察上面的超前进位加法器,可以发现,我们并行计算每一位的进位,比原本的串行计算更快得出结果。还有什么地方可以提升速度呢?
我们使用“归约”的思想,可以得到前缀加法器。前缀加法器是一种高性能的进位计算结构,通过前缀树结构实现多级并行归约,更高效地并行计算每一位的进位结果:
- 计算每一位的
g 和 p;
- 采用分层归约方式,逐步合并相邻位的
g 和 p,形成前缀树。例如Kogge-Stone结构:
- 第一层合并相邻两位
- 第二层合并更远的位
- 直到所有进位都被并行计算出来
- 进位的生成是通过多级归约实现的,层数为 log2(N),N为位宽。
- 最终和的计算与超前进位加法器类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
module prefix_adder_3bit ( input [2:0] a, input [2:0] b, inputcin, output [2:0] sum, output cout );
wire [2:0] p, g; wire [2:0] c;
assign p = a ^ b; assign g = a & b;
wire g1_0, p1_0; assign g1_0 = g[1] | (p[1] & g[0]); assign p1_0 = p[1] & p[0];
wire g2_1, p2_1; assign g2_1 = g[2] | (p[2] & g[1]); assign p2_1 = p[2] & p[1];
wire g2_0; assign g2_0 = g[2] | (p[2] & g1_0);
assign c[0] = cin; assign c[1] = g[0] | (p[0] & cin); assign c[2] = g1_0 | (p1_0 & cin);
assign sum = p ^ c; assign cout = g2_0 | (p[2] & p1_0 & cin);
endmodule
|
再来一个8bit的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| module prefix_adder_8bit ( input [7:0] a, input [7:0] b, inputcin, output [7:0] sum, output [7:0] cout );
wire [7:0] p, g; assign p = a ^ b; assign g = a & b;
wire [7:0] g1, p1, g2, p2, g3, p3;
assign g1[0] = g[0]; assign p1[0] = p[0]; genvar i; generate for (i = 1; i < 8; i = i + 1) begin : L1 assign g1[i] = g[i] | (p[i] & g[i-1]); assign p1[i] = p[i] & p[i-1]; end endgenerate
assign g2[0] = g1[0]; assign p2[0] = p1[0]; assign g2[1] = g1[1]; assign p2[1] = p1[1]; generate for (i = 2; i < 8; i = i + 1) begin : L2 assign g2[i] = g1[i] | (p1[i] & g1[i-2]); assign p2[i] = p1[i] & p1[i-2]; end endgenerate
assign g3[0] = g2[0]; assign p3[0] = p2[0]; assign g3[1] = g2[1]; assign p3[1] = p2[1]; assign g3[2] = g2[2]; assign p3[2] = p2[2]; assign g3[3] = g2[3]; assign p3[3] = p2[3]; generate for (i = 4; i < 8; i = i + 1) begin : L3 assign g3[i] = g2[i] | (p2[i] & g2[i-4]); assign p3[i] = p2[i] & p2[i-4]; end endgenerate
wire [7:0] c; assign c[0] = g[0] | (p[0] & cin); assign c[1] = g1[1] | (p1[1] & cin); assign c[2] = g2[2] | (p2[2] & cin); assign c[3] = g2[3] | (p2[3] & cin); assign c[4] = g3[4] | (p3[4] & cin); assign c[5] = g3[5] | (p3[5] & cin); assign c[6] = g3[6] | (p3[6] & cin); assign c[7] = g3[7] | (p3[7] & cin);
assign sum = p ^ {c[6:0], cin}; assign cout = c;
endmodule
|
可求得加法延时为:
tPA=tpg+log2N(tpg_prefix)+tXOR
其中tpg_prefix是黑色前缀单元(AND/OR逻辑)的延时。
进位保留加法器 Carry-Save Adder CSA
进位保留加法器在执行加法运算时,不立即处理低位产生的进位,而是将其保存下来,以便后续处理,其优点是O(1)的时间复杂度,即计算时间不依赖位宽,且所有位同时处理,因此适合多个数相加。
进位保留加法器将3个n位数相加,产生2个n位输出:和(Sum)与进位(Carry),最终结果可写为:Fin = Sum + (Carry << 1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| module csa_4bit ( input [3:0] a, input [3:0] b, input [3:0] c, output [3:0] sum, output [3:0] carry );
full_adder fa0 (a[0],b[0],c[0],sum[0],carry[0]); full_adder fa1 (a[1],b[1],c[1],sum[1],carry[1]); full_adder fa2 (a[2],b[2],c[2],sum[2],carry[2]); full_adder fa3 (a[3],b[3],c[3],sum[3],carry[3]);
endmodule
module cpa_5bit ( input [4:0] a, input [4:0] b, output [5:0] result );
assign result = a + b;
endmodule
|
主计算模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| module three_input_adder_4bit ( input [3:0] a, input [3:0] b, input [3:0] c, output [5:0] result );
wire [3:0] csa_sum; wire [3:0] csa_carry; wire [4:0] carry_shifted;
csa_4bit csa_inst ( .a(a), .b(b), .c(c), .sum(csa_sum), .carry(csa_carry) );
assign carry_shifted = {csa_carry, 1'b0};
cpa_5bit cpa_inst ( .a ({1'b0, csa_sum}), .b (carry_shifted), .result(result) );
endmodule
|
看这张图也很直观:

ALU
ALU(Arithmetic Logic Unit),即算术逻辑单元,是计算机中负责运算和逻辑处理的核心模块。下面是一个简易的ALU结构图,包含CPSR高位的条件码标志NZCV:
其Verilog实现也比较简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| module easyALU ( input [3:0] a, input [3:0] b, input [2:0] op, output n, z, c, v, output reg [3:0] r );
wire [3:0] m_bus, sumout; wire c_t, v_t1, v_t2;
assign n = r[3]; assign z = ~|r;
assign {c_t, sumout} = a + (op[0] ? (~b + 1) : b);
assign c = c_t & ~op[0]; assign v_t1 = ~(a[3] ^ b[3]); assign v_t2 = a[3] ^ sumout[3];
assign v = v_t1 & v_t2 & ~op[0];
always @(*) begin case (op[2:1]) 2'b10: r = a & b; 2'b11: r = a | b; default: r = sumout; endcase end
endmodule
|
波形看起来也没啥问题:

乘法器
乘法器输入俩N位宽的值,输出一个2N位宽的值。
自动综合乘法器
没啥说的必要。
1 2 3 4 5 6 7 8 9
| module mul4 ( input [3:0] a, input [3:0] b, output [7:0] p ); assign p = a * b;
endmodule
|
结构化移位加法乘法器
结构化移位加法乘法器很简单:算四遍之后移位相加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| module mul4_struct ( input [3:0] a, input [3:0] b, output [7:0] p ); wire [7:0] ab0, ab1, ab2, ab3;
assign ab0 = b[0] ? {4'b0, a } : 8'b0, ab1 = b[1] ? {3'b0, a, 1'b0} : 8'b0, ab2 = b[2] ? {2'b0, a, 2'b0} : 8'b0, ab3 = b[3] ? {1'b0, a, 3'b0} : 8'b0; assign p = ab0 + ab1 + ab2 + ab3;
endmodule
|
阵列乘法器
上面的乘法器依旧用到加法,最终计算是串行计算,会降低计算速度。考虑并行计算,提出了阵列乘法器:
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| module array_mul4 ( input [3:0] a, input [3:0] b, output [7:0] p ); wire [3:0] ab0 = {4{a[0]}} & b; wire [3:0] ab1 = {4{a[1]}} & b; wire [3:0] ab2 = {4{a[2]}} & b; wire [3:0] ab3 = {4{a[3]}} & b;
assign p[0] = ab0[0];
wire s1_0, c1_0; full_adder fa1_0 (.a(ab1[0]),.b(ab0[1]),.cin(1'b0),.sum(s1_0),.cout(c1_0)); assign p[1] = s1_0;
wire s2_0, c2_0, s2_1, c2_1; full_adder fa2_0 (.a(ab2[0]),.b(ab1[1]),.cin(1'b0),.sum(s2_0),.cout(c2_0)); full_adder fa2_1 (.a(s2_0), .b(ab0[2]),.cin(c1_0),.sum(s2_1),.cout(c2_1)); assign p[2] = s2_1;
wire s3_0, c3_0, s3_1, c3_1, s3_2, c3_2; full_adder fa3_0 (.a(ab3[0]),.b(ab2[1]),.cin(1'b0),.sum(s3_0),.cout(c3_0)); full_adder fa3_1 (.a(s3_0), .b(ab1[2]),.cin(c2_0),.sum(s3_1),.cout(c3_1)); full_adder fa3_2 (.a(s3_1), .b(ab0[3]),.cin(c2_1),.sum(s3_2),.cout(c3_2)); assign p[3] = s3_2;
wire s4_0, c4_0, s4_1, c4_1, s4_2, c4_2; full_adder fa4_0 (.a(ab3[1]),.b(ab2[2]),.cin(c3_0),.sum(s4_0),.cout(c4_0)); full_adder fa4_1 (.a(s4_0), .b(ab1[3]),.cin(c3_1),.sum(s4_1),.cout(c4_1)); full_adder fa4_2 (.a(s4_1), .b(1'b0), .cin(c3_2),.sum(s4_2),.cout(c4_2)); assign p[4] = s4_2;
wire s5_0, c5_0, s5_1, c5_1; full_adder fa5_0 (.a(ab3[2]),.b(ab2[3]),.cin(c4_0),.sum(s5_0),.cout(c5_0)); full_adder fa5_1 (.a(s5_0), .b(c4_2), .cin(c4_1),.sum(s5_1),.cout(c5_1)); assign p[5] = s5_1;
wire s6_0, c6_0; full_adder fa6_0 (.a(ab3[3]),.b(c5_1), .cin(c5_0),.sum(s6_0),.cout(c6_0)); assign p[6] = s6_0; assign p[7] = c6_0;
endmodule
|
阵列乘法器的优点是其时间延时可预测,且易于流水线化。完成乘法所需最长时间,由最右边的对角线以及最后一行来估算。两个N位数相乘的时间可以表示为:
tAM=tAND+2(N−1)tfa
其中tAND指所有的乘积项aibj可以由一个与门阵列同时产生。
Booth编码
参考文献:
Booth2乘法器设计 - 知乎
工作原理
那有人说了:这部分积也不少啊,总共需要N个部分积。4bit还好,那我要是64bit你不炸了吗?
Booth夫妇也是一对天才,发明了Booth算法,用它将无符号的乘法推广到补码的乘法运算,并减少部分积的数量。Booth算法的基本思路是:对于具有连续0和1的组,需要产生的部分积较少。对于乘数中每个0,仅需要将前面的累加的部分积向右移动一位即可。
先看下面的式子,我们认为y−1=0。
y===(∗)=−2n−1yn−1+2n−2yn−2+2n−2yn−2−2n−2yn−2+2n−3yn−3+2n−3yn−3−2n−3yn−3+⋯+2y1+2y1−2y1+y0+y0−y0+y−1−2n−1yn−1+2×2n−2yn−2−2n−2yn−2+2×2n−3yn−3−2n−3yn−3+⋯+2×2y1−2y1+2×y0−y0+y−1−2n−1yn−1+2n−1yn−2−2n−2yn−2+2n−2yn−3−2n−3yn−3+⋯+22y1−2y1+2y0−y0+y−1+2n−1(−yn−1+yn−2)+2n−2(−yn−2+yn−3)+⋯2(−y1+y0)+(−y0+y−1)
其中 (*) 式便为 1 位一组的 Booth1 算法,其编码后的表示范围为乘数的-1至1倍,采用相加和相减的操作计算补码数据的乘积。编码之后,乘数y中的位被划分为不同的组,每一组包括2位,这些组互相交叠。
比如,对于一个8bit的数8'b0111_1110,我们对其用 (*) 进行变换,可得到:8'b1000_00(-1)0,其中低四位含有一个负值1。变换后,我们可以显著减少1的数量,并使得0尽可能挨在一起。计算该乘法只需两次部分积相加,可大大加快计算速度。Booth编码的作用正在于此:它保证了在每两个连续位中最多只有一个是1或-1。
改进的Booth编码
这还是太烦了。
能不能再简化一下?
那再来一点简单的数学化简:
y=−2×2n−2yn−1+2n−3yn−3+2n−5yn−5+23y3+2y1+2n−2yn−2+2n−3yn−3−2n−3yn−3+2n−4yn−4+2n−5yn−5−2n−5yn−5+2n−6yn−6+⋯+23y3−23y3+22y2+2y1−2y1+y0+y−1
化简可得:
y=2n−2(−2yn−1+yn−2+yn−3)+2n−4(−2yn−3+yn−4+yn−5)+⋯+22(−2y3+y2+y1)+(−2y1+y0+y−1)
可知道这是 2 位一组的 Booth2 算法,其表示范围为乘数的-2至2倍。
我们同样用上面的例子来计算。对于8'b0111_1110,计算后得到8'b(2)(0)(0)(-2)[Booth2]。我们可以将每一位拆分为两位,易知结果和上面是一样的。
对于有符号数补码而言,扩展其最高位对数值并无影响,因此任何奇数位宽的有符号数补码都可以看成是偶数位宽。Booth2 算法每次交叠编码的位数为 3 位,使得部分积的数目减为传统算法的一半。对于操作数位宽为奇数时,可以在其高位扩展符号位从而使位宽变成偶数。
然而,如果部分积为负数,通过符号扩展来补齐位数时将其符号位补齐为1,就会使得累加计算时出现大量进位和翻转,乘法器的功耗也会随之增加。
乘法例子
我们举个简单例子:2 x 3 = 6。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 4'd2 = 4'b0010, | 4'd3 = 4'b0110 ================================================== Booth1: | 对应操作数:
4'b0010 = 4'b01(-1)0(b1) | 3/-3
2^{2}x3+2^{1}x(-3) = 6 ================================================== Booth2: | 对应操作数:
4'b0010 = 4'b1(-2)(b2) | 6/3/-3/-6
2^{2}x3+2^{0}x(-6) = 6
|
Verilog实现
Booth编码器模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
module booth_encoder ( input [2:0] booth_bits, output reg [2:0] encode );
always @(*) begin case (booth_bits) 3'b000: encode = 3'b000; 3'b001: encode = 3'b100; 3'b010: encode = 3'b100; 3'b011: encode = 3'b010; 3'b100: encode = 3'b011; 3'b101: encode = 3'b101; 3'b110: encode = 3'b101; 3'b111: encode = 3'b001; endcase end
endmodule
|
部分积生成器模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
module partial_product_generator ( input [3:0] multiplicand, input [2:0] encode, output [7:0] partial_product );
wire [7:0] multiplicand_ext; wire [7:0] temp_product; wire single, double, negative;
assign {single, double, negative} = encode;
assign multiplicand_ext = {{4{multiplicand[3]}}, multiplicand};
assign temp_product = (single && !double) ? multiplicand_ext : (!single && double) ? (multiplicand_ext << 1) : (single && double) ? (multiplicand_ext << 1) : 8'b0;
assign partial_product = negative ? (~temp_product + 1'b1) : temp_product;
endmodule
|
Booth乘法器模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| `include "booth_encoder.v" `include "partial_product_generator.v" module booth_multiplier_4bit ( input signed [3:0] multiplicand, input signed [3:0] multiplier, output signed [7:0] product );
wire [4:0] multiplier_ext; assign multiplier_ext = {multiplier, 1'b0};
wire [2:0] booth_bits_0, booth_bits_1, booth_bits_2; wire [2:0] encode_0, encode_1, encode_2;
assign booth_bits_0 = {multiplier_ext[2], multiplier_ext[1], multiplier_ext[0]}; assign booth_bits_1 = {multiplier_ext[4], multiplier_ext[3], multiplier_ext[2]}; assign booth_bits_2 = {1'b0, 1'b0, multiplier_ext[4]};
booth_encoder encoder_0 (.booth_bits(booth_bits_0),.encode(encode_0)); booth_encoder encoder_1 (.booth_bits(booth_bits_1),.encode(encode_1));
wire [7:0] partial_product_0, partial_product_1;
partial_product_generator ppg_0 ( .multiplicand(multiplicand), .encode(encode_0), .partial_product(partial_product_0) );
partial_product_generator ppg_1 ( .multiplicand(multiplicand), .encode(encode_1), .partial_product(partial_product_1) );
wire signed [7:0] shifted_pp_0, shifted_pp_1;
assign shifted_pp_0 = partial_product_0; assign shifted_pp_1 = partial_product_1 << 2;
assign product = shifted_pp_0 + shifted_pp_1;
endmodule
|