基于Booth算法的64位浮点乘法器的实现

说明

该文章目的在于理解如何使用Booth算法或MBA算法实现一个简单的64位浮点乘法器,为了简便,省略了很多繁琐的细节,主要为以下几点:

  1. 64位浮点数符合 IEEE754-1985 浮点运算标准,该标准的内容本文不涉及
  2. 只考虑规格化的浮点数,对于0、无穷大、无穷小等其他特殊的浮点数都不考虑
  3. 在对结果的Mantissa舍入时,其可能会影响结果的exponent的值,为了简便,浮点乘法器的舍入规则为 Round to 0,即截断,这样exponent的值不会因为Mantissa的舍入而改变
  4. 不考虑标志信号,比如溢出标志、零标志等等
  5. 本文设计的64位乘法器,设计追求简单,目的是为了理解Booth算法和MBA算法原理,其电路延迟和硬件规模都很不理想。更好的乘法器设计请参考这篇论文 32bits高速CMOS浮点乘法器设计,本文也是基于对该论文的理解而写的。

64位浮点乘法器架构

浮点数乘法的基本原理为:指数相加,尾数相乘。

  1. 指数直接相加后的结果需要再减去一个bias,对于64位浮点数来说,bias等于1023
  2. 尾数相乘后的结果可能大于2,此时需要对结果进行规整化(Normalize),注意尾数相乘的结果一定小于4,因此规整化后指数部分最多需要加1
  3. 对规整化的结果进行舍入,可能会导致进位,从而使得指数的值加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=bn1bn2...b0
A B = ∑ i = 0 n − 1 2 i b i A AB=\sum_{i=0}^{n-1}2^ib_{i}A AB=i=0n12ibiA

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=bn1bn2...b0,为了方便表示,再另 b − 1 = 0 b_{-1}=0 b1=0,则有
请添加图片描述

那么 A ⋅ B A\cdot B AB 则可以用下面的表达式计算,这即为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=0n12i(bi+bi1)A
只看上面的表达式,和按位乘法算法相比,Booth算法似乎并没有减少累加的次数,但是如果乘数 B B B 中原来存在连续多个的1,上式中就会有多个 − b i + b i − 1 -b_{i}+b_{i-1} bi+bi1 的项的值为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+1b2nb2n1...b0,如果B的最高位不为 2 n + 1 2n+1 2n+1,可以对其进行符号拓展,另外,为了方便表示,再另 b − 1 = 0 b_{-1}=0 b1=0,则有
请添加图片描述

那么 A ⋅ B A\cdot B AB 则可以用下面的表达式计算,这即为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=0n4i(2b2i+1+b2i+b2i1)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=0264i(2b2i+1+b2i+b2i1)A
注意 b 53 = 0 , b 52 = 1 , b − 1 = 0 b_{53}=0,b_{52}=1,b_{-1}=0 b53=0,b52=1,b1=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+b2i1 只可能为 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+b2i1)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+b2i1 的正负, 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+b2i1 的大小

  • c t r l [ 2 : 0 ] = 3 ′ b 100 ctrl[2:0]=3'b100 ctrl[2:0]=3b100,表示其值大小为2
  • c t r l [ 2 : 0 ] = 3 ′ b 010 ctrl[2:0]=3'b010 ctrl[2:0]=3b010,表示其值大小为1
  • c t r l [ 2 : 0 ] = 3 ′ b 001 ctrl[2:0]=3'b001 ctrl[2:0]=3b001,表示其值大小为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+b2i1)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
  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值