基于Booth算法的64位浮点乘法器的实现
说明
该文章目的在于理解如何使用Booth算法或MBA算法实现一个简单的64位浮点乘法器,为了简便,省略了很多繁琐的细节,主要为以下几点:
- 64位浮点数符合
IEEE754-1985
浮点运算标准,该标准的内容本文不涉及 - 只考虑规格化的浮点数,对于0、无穷大、无穷小等其他特殊的浮点数都不考虑
- 在对结果的Mantissa舍入时,其可能会影响结果的exponent的值,为了简便,浮点乘法器的舍入规则为
Round to 0
,即截断,这样exponent的值不会因为Mantissa的舍入而改变 - 不考虑标志信号,比如溢出标志、零标志等等
- 本文设计的64位乘法器,设计追求简单,目的是为了理解Booth算法和MBA算法原理,其电路延迟和硬件规模都很不理想。更好的乘法器设计请参考这篇论文 32bits高速CMOS浮点乘法器设计,本文也是基于对该论文的理解而写的。
64位浮点乘法器架构
浮点数乘法的基本原理为:指数相加,尾数相乘。
- 指数直接相加后的结果需要再减去一个bias,对于64位浮点数来说,bias等于1023
- 尾数相乘后的结果可能大于2,此时需要对结果进行规整化(Normalize),注意尾数相乘的结果一定小于4,因此规整化后指数部分最多需要加1
- 对规整化的结果进行舍入,可能会导致进位,从而使得指数的值加1,为了简便,舍入规则采取向0舍入,即截断
按位乘法算法(Bitwise Multiplication Algorithm)
64位浮点乘法器的关键路径在于尾数的相乘,因此如何优化尾数的乘法成为优化浮点乘法器的关键所在。首先来看基础版本的乘法算法,其原理可用下面的伪代码表示
product <- 0
while multiplier != 0:
if multiplier.lsb == 1
product <- product + multiplicand
multiplicand <- multiplicand << 1
multiplier <- multiplier >> 1
该算法本质上和我们列竖式计算十进制数乘法的过程是一样的,将乘数的每一位分别和被乘数相乘,得到部分和,将所有的部分和累加即可得到最终的结果,其可以用下面的公式来表示,其中
B
=
b
n
−
1
b
n
−
2
.
.
.
b
0
B=b_{n-1}b_{n-2}...b_0
B=bn−1bn−2...b0,
A
B
=
∑
i
=
0
n
−
1
2
i
b
i
A
AB=\sum_{i=0}^{n-1}2^ib_{i}A
AB=i=0∑n−12ibiA
Booth算法
首先来看Booth算法,其原理我参考了知乎答主坤坤的回答。
对于两个使用二进制补码表示的有符号数
A
A
A 和
B
B
B,我们可以通过重写
B
B
B 来减少累加的次数,设
B
=
b
n
−
1
b
n
−
2
.
.
.
b
0
B=b_{n-1}b_{n-2}...b_0
B=bn−1bn−2...b0,为了方便表示,再另
b
−
1
=
0
b_{-1}=0
b−1=0,则有
那么
A
⋅
B
A\cdot B
A⋅B 则可以用下面的表达式计算,这即为Booth算法
A
B
=
∑
i
=
0
n
−
1
2
i
(
−
b
i
+
b
i
−
1
)
A
AB=\sum_{i=0}^{n-1}2^i(-b_{i}+b_{i-1})A
AB=i=0∑n−12i(−bi+bi−1)A
只看上面的表达式,和按位乘法算法相比,Booth算法似乎并没有减少累加的次数,但是如果乘数
B
B
B 中原来存在连续多个的1,上式中就会有多个
−
b
i
+
b
i
−
1
-b_{i}+b_{i-1}
−bi+bi−1 的项的值为0,从而达到减少累加的次数的目的。
但是如果乘数中原来存在“01”的连续序列,则使用上面的表达式来计算乘法会增加乘数中 “1” 的数目,反而降低了乘法的运算速度。
因此,Booth算法对乘法速度的提高与乘数 B B B 的值有关,我们希望寻找一种高速且与操作数无关的编码方式,这便有了下面的修正Booth算法(Modified Booth Arithmetic,MBA)。
MBA算法
该算法还是通过重写
B
B
B 来减少累加的次数,设
B
=
b
2
n
+
1
b
2
n
b
2
n
−
1
.
.
.
b
0
B=b_{2n+1}b_{2n}b_{2n-1}...b_0
B=b2n+1b2nb2n−1...b0,如果B的最高位不为
2
n
+
1
2n+1
2n+1,可以对其进行符号拓展,另外,为了方便表示,再另
b
−
1
=
0
b_{-1}=0
b−1=0,则有
那么
A
⋅
B
A\cdot B
A⋅B 则可以用下面的表达式计算,这即为MBA算法
A
B
=
∑
i
=
0
n
4
i
(
−
2
b
2
i
+
1
+
b
2
i
+
b
2
i
−
1
)
A
AB=\sum_{i=0}^{n}4^i(-2b_{2i+1}+b_{2i}+b_{2i-1})A
AB=i=0∑n4i(−2b2i+1+b2i+b2i−1)A
可以看到,如果
B
B
B 为
2
n
+
2
2n+2
2n+2 位,则MBA算法相比按位乘法算法,其累加的次数减少了几乎一半,这样不仅可以提高运算速度,还可以减少所需加法器的数量。
应当注意MBA算法中操作数均采用二进制补码表示,在加、减运算时需要进行符号扩展。
对于64位浮点数的尾数来说,上式中一共有27个项,即
A B = ∑ i = 0 26 4 i ( − 2 b 2 i + 1 + b 2 i + b 2 i − 1 ) A AB=\sum_{i=0}^{26}4^i(-2b_{2i+1}+b_{2i}+b_{2i-1})A AB=i=0∑264i(−2b2i+1+b2i+b2i−1)A
注意 b 53 = 0 , b 52 = 1 , b − 1 = 0 b_{53}=0,b_{52}=1,b_{-1}=0 b53=0,b52=1,b−1=0,其余位和浮点数的二进制表示中的低52位对应。注意到 − 2 b 2 i + 1 + b 2 i + b 2 i − 1 -2b_{2i+1}+b_{2i}+b_{2i-1} −2b2i+1+b2i+b2i−1 只可能为 0 , 1 , − 1 , 2 , − 2 0,1,-1,2,-2 0,1,−1,2,−2,因此在下面的代码实现中,对于每一个项,我采用一个4位的控制信号来控制如何生成 4 i ( − 2 b 2 i + 1 + b 2 i + b 2 i − 1 ) A 4^i(-2b_{2i+1}+b_{2i}+b_{2i-1})A 4i(−2b2i+1+b2i+b2i−1)A 的值。
c t r l [ 3 ] ctrl[3] ctrl[3] 表示 − 2 b 2 i + 1 + b 2 i + b 2 i − 1 -2b_{2i+1}+b_{2i}+b_{2i-1} −2b2i+1+b2i+b2i−1 的正负, c t r l [ 2 : 0 ] ctrl[2:0] ctrl[2:0] 采用独热编码,表示 − 2 b 2 i + 1 + b 2 i + b 2 i − 1 -2b_{2i+1}+b_{2i}+b_{2i-1} −2b2i+1+b2i+b2i−1 的大小
- c t r l [ 2 : 0 ] = 3 ′ b 100 ctrl[2:0]=3'b100 ctrl[2:0]=3′b100,表示其值大小为2
- c t r l [ 2 : 0 ] = 3 ′ b 010 ctrl[2:0]=3'b010 ctrl[2:0]=3′b010,表示其值大小为1
- c t r l [ 2 : 0 ] = 3 ′ b 001 ctrl[2:0]=3'b001 ctrl[2:0]=3′b001,表示其值大小为0
Verilog实现
首先是浮点乘法器的代码,如下所示
//
// Engineer: gaojiejinsi
//
// Create Date: 2022/04/15 14:43:38
// Module Name: FPM
//
// Floating point multiple
module FPM(
input clk, rst,
input [63:0] a, b,
output [63:0] mul,
output OF
);
localparam EXP_WIDTH=11;
wire sign;
reg [11:0] exp; // exp位12位,多出一位用来辅助判断是否发生溢出
reg [51:0] mant;
// 生成符号位
assign sign = a[63] ^ b[63];
// 生成e_sum和e_sum_plus1
// 根据是否要进位,从这两个信号里选择一个连接到exp上
wire [11:0] e_nobias, e_sum, e_sum_plus1;
RippleCarryAdder #(.WIDTH(12)) myadder1(.a({1'b0, b[62:52]}), .b(12'b110000000000), .cin(1'b1), .sum(e_nobias));
RippleCarryAdder #(.WIDTH(12)) myadder2(.a({1'b0, a[62:52]}), .b(e_nobias), .cin(1'b0), .sum(e_sum));
RippleCarryAdder #(.WIDTH(12)) myadder3(.a(e_sum), .b(12'b000000000001), .cin(1'b0), .sum(e_sum_plus1));
// 生成控制信号
// 一共有27个需要进行累加的数,每组控制信号负责如何根据输入信号b来生成一个需要累加的数
// ctrl[i*4+3:i*4]负责控制如何根据b来生成第i个需要累加的数
// ctrl[i*4+3:i*4]的生成,通过真值表获取布尔表达式并化简而得到
reg [27*4-1:0] ctrl;
integer i;
always @* begin
ctrl[0] = (~b[0]) & (~b[1]);
ctrl[1] = b[0];
ctrl[2] = (~b[0]) & b[1];
ctrl[3] = b[1];
for(i = 1; i < 26; i = i + 1) begin
ctrl[4*i+0] = ((~b[2*i+1])&(~b[2*i])&(~b[2*i-1])) | ((b[2*i+1])&(b[2*i])&(b[2*i-1]));
ctrl[4*i+1] = ((b[2*i])&(~b[2*i-1])) | ((~b[2*i])&(b[2*i-1]));
ctrl[4*i+2] = ((~b[2*i+1])&(b[2*i])&(b[2*i-1])) | ((b[2*i+1])&(~b[2*i])&(~b[2*i-1]));
ctrl[4*i+3] = ((b[2*i+1])&(~b[2*i-1])) | ((b[2*i+1])&(~b[2*i]));
end
ctrl[4*26+0] = 1'b0;
ctrl[4*26+1] = (~b[2*26-1]);
ctrl[4*26+2] = (b[2*26-1]);
ctrl[4*26+3] = 1'b0;
end
// 生成27个需要进行累加的数,每个数107位
wire [107*27-1:0] add_items;
wire [27-1:0] couts;
genvar j;
generate
for(j = 0; j < 27; j = j + 1) begin
GenerateF gf(.ctrl(ctrl[4*j+3:4*j]), .a(a), .out(add_items[2*j+54+107*j:2*j+107*j]), .cout(couts[j]));
end
for(j = 1; j < 27; j = j + 1) begin
assign add_items[2*j-1+107*j:107*j] = {{(2*j){add_items[2*j+54+107*j]}}};
end
for(j = 0; j < 26; j = j + 1) begin
assign add_items[107*(j+1)-1:2*j+54+107*j+1] = {{(107-54-1-2*j){add_items[2*j+54+107*j]}}};
end
endgenerate
// 对这27个数分三次累加,每次加9个数
wire [107-1:0] psum1, psum2, psum3;
PartialSum parts1(.a(107'b0), .add_items(add_items[9*107-1:0]), .cin(couts[8:0]), .out(psum1));
PartialSum parts2(.a(psum1), .add_items(add_items[18*107-1:9*107]), .cin(couts[17:9]), .out(psum2));
PartialSum parts3(.a(psum2), .add_items(add_items[27*107-1:18*107]), .cin(couts[26:18]), .out(psum3));
// 生成exp和mant
always @* begin
case(psum3[105])
1'b0: {exp, mant} = {e_sum, psum3[103:52]};
1'b1: {exp, mant} = {e_sum_plus1, psum3[104:53]};
endcase
end
// 生成mul和OF输出信号
assign mul = {sign, exp[10:0], mant};
assign OF = exp[11];
endmodule
下面为模块 GenerateF
的代码,其功能为根据
c
t
r
l
ctrl
ctrl 控制信号,生成
(
−
2
b
2
i
+
1
+
b
2
i
+
b
2
i
−
1
)
A
(-2b_{2i+1}+b_{2i}+b_{2i-1})A
(−2b2i+1+b2i+b2i−1)A 的值
//
// Company:
// Engineer: gaojiejinsi
//
// Create Date: 2022/04/13 08:32:27
// Module Name: GenerateF
//
module GenerateF(
input [3:0] ctrl,
input [63:0] a,
output reg [54:0] out,
output reg cout
);
reg [54:0] out_tmp;
always @* begin
case(ctrl[2:0])
3'b001: out_tmp = 55'b0;
3'b010: out_tmp = {3'b001, a[51:0]};
3'b100: out_tmp = {2'b01, a[51:0], 1'b0};
default: out_tmp = 55'b0;
endcase
case(ctrl[3])
1'b0: {out, cout} = {out_tmp, 1'b0};
1'b1: {out, cout} = {~out_tmp, 1'b1};
endcase
end
endmodule
下面为模块 PartialSum
的代码,其功能为计算 add_terms 中的9个的数的和,再将该和与输入 a
相加得到最终的输出 out
//
// Engineer: gaojiejinsi
//
// Create Date: 2022/04/13 08:11:22
// Module Name: PartialSum
//
module PartialSum(
input [106:0] a,
input [962:0] add_items,
input [8:0] cin,
input [106:0] out
);
// 可以使用generate语句,懒得改了
wire [106:0] out1, out2, out3, out4, out5, out6, out7, out8;
RippleCarryAdder #(.WIDTH(107)) myadder1(.a(a), .b(add_items[107*1-1:107*0]), .cin(cin[0]), .sum(out1));
RippleCarryAdder #(.WIDTH(107)) myadder2(.a(out1), .b(add_items[107*2-1:107*1]), .cin(cin[1]), .sum(out2));
RippleCarryAdder #(.WIDTH(107)) myadder3(.a(out2), .b(add_items[107*3-1:107*2]), .cin(cin[2]), .sum(out3));
RippleCarryAdder #(.WIDTH(107)) myadder4(.a(out3), .b(add_items[107*4-1:107*3]), .cin(cin[3]), .sum(out4));
RippleCarryAdder #(.WIDTH(107)) myadder5(.a(out4), .b(add_items[107*5-1:107*4]), .cin(cin[4]), .sum(out5));
RippleCarryAdder #(.WIDTH(107)) myadder6(.a(out5), .b(add_items[107*6-1:107*5]), .cin(cin[5]), .sum(out6));
RippleCarryAdder #(.WIDTH(107)) myadder7(.a(out6), .b(add_items[107*7-1:107*6]), .cin(cin[6]), .sum(out7));
RippleCarryAdder #(.WIDTH(107)) myadder8(.a(out7), .b(add_items[107*8-1:107*7]), .cin(cin[7]), .sum(out8));
RippleCarryAdder #(.WIDTH(107)) myadder9(.a(out8), .b(add_items[107*9-1:107*8]), .cin(cin[8]), .sum(out));
endmodule
下面为行波进位加法器的代码,浮点乘法器中需要实例化该模块
//
// Engineer: gaojiejinsi
//
// Create Date: 2022/04/11 11:19:01
// Module Name: RippleCarryAdder
//
//
module RippleCarryAdder #(
WIDTH = 32
) (
input [WIDTH-1:0] a, b,
input cin,
output reg [WIDTH-1:0] sum,
output reg cout
);
integer i;
always @* begin
sum = 0;
cout = cin;
for(i = 0; i < WIDTH; i = i + 1) begin
sum[i] = ((a[i]) ^ b[i]) ^ cout;
cout = (b[i]&cout) | (a[i]&cout) | (b[i]&a[i]);
end
end
endmodule