加法器

全加器 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=Ntfat_{ripple} = N \cdot t_{fa},这是很致命的。

超前进位加法器 Carry-Lookahead Adder CLA

[上面](#行波进位加法器 Ripple-Carry Adder RCA)的行波进位加法器之所以延时太长,是因为每一位的进位计算都依赖前一位,层层叠加导致计算只能串行进行。那有没有什么办法能提高计算速度,比如并行计算出后续的进位而不依赖前面的结果呢?有的兄弟有的。

为了方便,我们定义两个新的信号为产生信号传播信号,并对输入数据A与B的每一位都进行计算:

  • 进位产生Generate:如果Ai和Bi均为1,那么第i位就会产生一个进位输出:

    Gi=AiBiG_i = A_i \cdot B_i

  • 进位传播Propagate: 如果Ai或Bi为1,那么第i位将把一个进位输入传播到进位输出:

    Pi=Ai+BiP_i = A_i + B_i

  • 进位输出CarryOut: 第i的进位输出Ci表达式为:

    Ci=AiBi+PiCi1=Gi+PiCi1C_i = A_i \cdot B_i + P_i \cdot C_{i-1} = G_i + P_iC_{i-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; // propagate
assign g = a & b; // generate

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+tpgblock+(Nk1)tAND_OR+ktFAt_{\mathrm{CLA}}=t_{\mathrm{pg}}+t_{\mathrm{pg}_{-}\mathrm{block}}+\left(\frac{N}{k}-1\right)t_{\mathrm{AND\_OR}}+k\cdot t_{\mathrm{FA}}

其中:

  • 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

观察上面的超前进位加法器,可以发现,我们并行计算每一位的进位,比原本的串行计算更快得出结果。还有什么地方可以提升速度呢?

我们使用“归约”的思想,可以得到前缀加法器。前缀加法器是一种高性能的进位计算结构,通过前缀树结构实现多级并行归约,更高效地并行计算每一位的进位结果:

  • 计算每一位的 gp;
  • 采用分层归约方式,逐步合并相邻位的 gp,形成前缀树。例如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
// 一个Kogge-Stone结构的3bit前缀加法器

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;

// 1. 预处理
assign p = a ^ b; // 传递
assign g = a & b; // 产生

// 2. 前缀进位计算(Kogge-Stone结构)
// 第一层
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);

// 3. 进位
assign c[0] = cin;
assign c[1] = g[0] | (p[0] & cin);
assign c[2] = g1_0 | (p1_0 & cin);

// 4. 求和
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, // 加数a,8位
input [7:0] b, // 加数b,8位
inputcin, // 输入进位
output [7:0] sum, // 求和输出
output [7:0] cout // 每一位的进位输出
);

// 1. 预处理
wire [7:0] p, g;
assign p = a ^ b;
assign g = a & b;

// 2. 前缀进位计算(Kogge-Stone结构,分层归约)
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

// 第二层:隔1位
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

// 第三层:隔2位
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

// 3. 进位生成
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);

// 4. 求和
assign sum = p ^ {c[6:0], cin};
assign cout = c;

endmodule

可求得加法延时为:

tPA=tpg+log2N(tpg_prefix)+tXORt_{\mathrm{PA}}=t_{\mathrm{pg}}+\log_{2}N\left(t_{\mathrm{pg}\_\mathrm{prefix}}\right)+t_{\mathrm{XOR}}

其中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
// 4位进位保留加法器 (CSA) 主模块
module csa_4bit (
input [3:0] a, // 第一个4位输入
input [3:0] b, // 第二个4位输入
input [3:0] c, // 第三个4位输入
output [3:0] sum, // 4位和输出
output [3:0] carry // 4位进位输出
);

// 实例化4个全加器,每位一个
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

// 进位传播加法器 (CPA) - 用于最终结果计算
module cpa_5bit (
input [4:0] a, // 5位输入A (包含扩展位)
input [4:0] b, // 5位输入B
output [5:0] result // 6位结果输出
);

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
// CSA + CPA 组合模块 - 完整的三数加法器
module three_input_adder_4bit (
input [3:0] a, // 第一个4位输入
input [3:0] b, // 第二个4位输入
input [3:0] c, // 第三个4位输入
output [5:0] result // 6位最终结果
);

wire [3:0] csa_sum; // CSA的和输出
wire [3:0] csa_carry; // CSA的进位输出
wire [4:0] carry_shifted; // 左移后的进位

// 第一步:使用CSA将三个4位数转换为两个数
csa_4bit csa_inst (
.a(a),
.b(b),
.c(c),
.sum(csa_sum),
.carry(csa_carry)
);

// 进位左移1位
assign carry_shifted = {csa_carry, 1'b0};

// 第二步:使用CPA计算最终结果
cpa_5bit cpa_inst (
.a ({1'b0, csa_sum}), // 扩展和到5位
.b (carry_shifted), // 5位左移进位
.result(result)
);

endmodule

看这张图也很直观:

出自B站@一个郭果果

ALU

ALU(Arithmetic Logic Unit),即算术逻辑单元,是计算机中负责运算和逻辑处理的核心模块。下面是一个简易的ALU结构图,包含CPSR高位的条件码标志NZCV:

可输出NZCV的简易ALU

其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;

// 1'b0为加法,1'b1为减法
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

波形看起来也没啥问题:

简单ALU波形仿真

乘法器

乘法器输入俩N位宽的值,输出一个2N位宽的值。

自动综合乘法器

没啥说的必要。

1
2
3
4
5
6
7
8
9
// 自动综合版
module mul4 (
input [3:0] a, // 4位乘数
input [3:0] b, // 4位被乘数
output [7:0] p // 8位乘积
);
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, // 4位乘数
input [3:0] b, // 4位被乘数
output [7:0] p // 8位乘积
);
wire [7:0] ab0, ab1, ab2, ab3;

// 部分积生成
assign ab0 = b[0] ? {4'b0, a } : 8'b0, // a*1 or 0, 不移位
ab1 = b[1] ? {3'b0, a, 1'b0} : 8'b0, // a*2 or 0, 左移1位
ab2 = b[2] ? {2'b0, a, 2'b0} : 8'b0, // a*4 or 0, 左移2位
ab3 = b[3] ? {1'b0, a, 3'b0} : 8'b0; // a*8 or 0, 左移3位

// 部分积相加
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
);
// abx[y]指的是a{x}b{y}的积
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(N1)tfat_{AM} = t_{AND} + 2(N-1)t_{fa}

其中tANDt_{AND}指所有的乘积项aibja_ib_j可以由一个与门阵列同时产生

Booth编码

参考文献:

Booth2乘法器设计 - 知乎

工作原理

那有人说了:这部分积也不少啊,总共需要N个部分积。4bit还好,那我要是64bit你不炸了吗?

Booth夫妇也是一对天才,发明了Booth算法,用它将无符号的乘法推广到补码的乘法运算,并减少部分积的数量。Booth算法的基本思路是:对于具有连续0和1的组,需要产生的部分积较少。对于乘数中每个0,仅需要将前面的累加的部分积向右移动一位即可。

先看下面的式子,我们认为y1=0y_{-1}=0

y=2n1yn1+2n2yn2+2n2yn22n2yn2+2n3yn3+2n3yn32n3yn3++2y1+2y12y1+y0+y0y0+y1=2n1yn1+2×2n2yn22n2yn2+2×2n3yn32n3yn3++2×2y12y1+2×y0y0+y1=2n1yn1+2n1yn22n2yn2+2n2yn32n3yn3++22y12y1+2y0y0+y1()=+2n1(yn1+yn2)+2n2(yn2+yn3)+2(y1+y0)+(y0+y1)\begin{aligned} y=& -2^{n-1}y_{n-1}\\ &+2^{n-2}y_{n-2}+\textcolor{red}{2^{n-2}y_{n-2}-2^{n-2}y_{n-2}}\\ &+2^{n-3}y_{n-3}+\textcolor{red}{2^{n-3}y_{n-3} -2^{n-3}y_{n-3}}+\cdots+2y_{1}+\textcolor{red}{2y_{1}-2y_{1}}+y_{0}+\textcolor{red}{y_{0}-y_{0}}+y_{-1} \\\\ =& -2^{n-1}y_{n-1}\\&+2\times2^{n-2}y_{n-2}-2^{n-2}y_{n-2}\\&+2\times2^{n-3}y_{n-3}-2^{n-3}y_{n-3}+\cdots+2\times2y_{1}-2y_{1}+2\times y_{0}-y_{0}+y_{-1} \\\\ =&\textcolor{blue}{-2^{n-1}y_{n-1}+2^{n-1}y_{n-2}}\\&\textcolor{blue}{-2^{n-2}y_{n-2}+2^{n-2}y_{n-3}}-2^{n-3}y_{n-3}+\cdots+2^{2}y_{1}-2y_{1}+2y_{0}-y_{0}+y_{-1} \\\\ \textcolor{green}{\small(*\small) }=& \textcolor{blue}{+}2^{n-1}(-y_{n-1}+y_{n-2})+2^{n-2}(-y_{n-2}+y_{n-3})+\cdots2(-y_{1}+y_{0})+(-y_{0}+y_{-1}) \end{aligned}

其中 (*) 式便为 1 位一组的 Booth1 算法,其编码后的表示范围为乘数的-1至1倍,采用相加和相减的操作计算补码数据的乘积。编码之后,乘数y中的位被划分为不同的组,每一组包括2位,这些组互相交叠。

比如,对于一个8bit的数8'b0111_1110,我们对其用 (*) 进行变换,可得到:8'b1000_00(-1)0,其中低四位含有一个负值1。变换后,我们可以显著减少1的数量,并使得0尽可能挨在一起。计算该乘法只需两次部分积相加,可大大加快计算速度。Booth编码的作用正在于此:它保证了在每两个连续位中最多只有一个是1或-1

改进的Booth编码

这还是太烦了。

能不能再简化一下?

那再来一点简单的数学化简:

y=2×2n2yn1+2n2yn2+2n3yn3+2n3yn32n3yn3+2n4yn4+2n5yn5+2n5yn52n5yn5+2n6yn6++23y3+23y323y3+22y2+2y1+2y12y1+y0+y1\begin{aligned} y= \textcolor{red}{-2\times 2^{n-2}}y_{n-1}&+2^{n-2}y_{n-2} \\+2^{n-3}y_{n-3}&+\textcolor{red}{2^{n-3}y_{n-3} -2^{n-3}y_{n-3}}+2^{n-4}y_{n-4} \\+2^{n-5}y_{n-5}&+\textcolor{red}{2^{n-5}y_{n-5} -2^{n-5}y_{n-5}}+2^{n-6}y_{n-6}+\cdots \\+2^{3}y_{3}&+\textcolor{red}{2^{3}y_{3} -2^{3}y_{3}}+2^{2}y_{2} \\+2y_1&+\textcolor{red}{2y_{1}-2y_{1}}+y_0+y_{-1} \\\\ \end{aligned}

化简可得:

y=2n2(2yn1+yn2+yn3)+2n4(2yn3+yn4+yn5)++22(2y3+y2+y1)+(2y1+y0+y1)\begin{aligned} y &= 2^{n-2}(-2y_{n-1}+y_{n-2}+y_{n-3}) \\&+2^{n-4}(-2y_{n-3}+y_{n-4}+y_{n-5})+\cdots \\&+2^{2}(-2y_{3}+y_{2}+y_{1}) \\&+(-2y_{1}+y_{0}+y_{-1}) \end{aligned}

可知道这是 2 位一组的 Booth2 算法,其表示范围为乘数的-2至2倍。

我们同样用上面的例子来计算。对于8'b0111_1110,计算后得到8'b(2)(0)(0)(-2)[Booth2]。我们可以将每一位拆分为两位,易知结果和上面是一样的。

对于有符号数补码而言,扩展其最高位对数值并无影响,因此任何奇数位宽的有符号数补码都可以看成是偶数位宽。Booth2 算法每次交叠编码的位数为 3 位,使得部分积的数目减为传统算法的一半。对于操作数位宽为奇数时,可以在其高位扩展符号位从而使位宽变成偶数。

然而,如果部分积为负数,通过符号扩展来补齐位数时将其符号位补齐为1,就会使得累加计算时出现大量进位和翻转,乘法器的功耗也会随之增加[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
// 两位Booth编码器模块
// 输入:3位数据(包含前一位)
// 输出:编码控制信号
module booth_encoder (
input [2:0] booth_bits, // 三位输入:{Yi+1, Yi, Yi-1}
output reg [2:0] encode // 编码输出:{single, double, negative}
);

always @(*) begin
case (booth_bits) // 编码表:
3'b000: encode = 3'b000; // 000 -> 000 (加0)
3'b001: encode = 3'b100; // 001 -> 100 (加1倍被乘数)
3'b010: encode = 3'b100; // 010 -> 100 (加1倍被乘数)
3'b011: encode = 3'b010; // 011 -> 010 (加2倍被乘数)
3'b100: encode = 3'b011; // 100 -> 011 (减2倍被乘数)
3'b101: encode = 3'b101; // 101 -> 101 (减1倍被乘数)
3'b110: encode = 3'b101; // 110 -> 101 (减1倍被乘数)
3'b111: encode = 3'b001; // 111 -> 001 -> 000 (减0即加0)
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
// 部分积生成器模块
// 根据Booth编码信号生成相应的部分积
module partial_product_generator (
input [3:0] multiplicand, // 被乘数
input [2:0] encode, // Booth编码:{single, double, negative}
output [7:0] partial_product // 部分积输出
);

wire [7:0] multiplicand_ext; // 符号扩展的被乘数
wire [7:0] temp_product;
wire single, double, negative;

assign {single, double, negative} = encode;

// 符号扩展被乘数到8位
assign multiplicand_ext = {{4{multiplicand[3]}}, multiplicand};

// 根据编码生成部分积
assign temp_product = (single && !double) ? multiplicand_ext : // +1X
(!single && double) ? (multiplicand_ext << 1) : // +2X
(single && double) ? (multiplicand_ext << 1) : // +2X (这种情况不应该出现)
8'b0; // +0

// 如果neg为1,则取反加1(二进制补码)
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
// 四位Booth编码乘法器(使用两位Booth编码)
`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 // 乘积结果
);

// 扩展乘数以便进行两位Booth编码
wire [4:0] multiplier_ext;
assign multiplier_ext = {multiplier, 1'b0}; // 在末尾添加0

// Booth编码器的输入和输出
wire [2:0] booth_bits_0, booth_bits_1, booth_bits_2;
wire [2:0] encode_0, encode_1, encode_2;

// 生成Booth编码位组
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]};
// 最高位组,前面补0

// Booth编码器实例化
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; // 左移2位

// 最终结果累加
assign product = shifted_pp_0 + shifted_pp_1;

endmodule



  1. 熊书伟,宋树祥. 基于符号扩展的booth乘法器设计与实现[J]. 电子测量技术,2024,47(20):124-131. DOI:10.19651/j.cnki.emt.2416555.