一、定点纯小数乘除法器
1.原理分析
1.1 补码乘法
Booth算法,由校正法推导而出,对乘数X和被乘数Y的符号没有分类要求。
特点:被乘数和部分积均采用双符号位,作为数值位参与计算。取一附加位添加到乘数末尾,该附加位初始值Yn+1 = 0。每次观察乘数的两位,规则如下:
当运算至最后一位时(即最后一次判断),部分积不右移。最后的部分积作为乘积,同样借助双符号位判断是否溢出。
例题:
1.1 补码除法
补码交替加减法,其步骤分为:求余数,判商值,移位,求余数...,如此重复,直到求出商值(达到要求精度)。
关键点:第一步求余数
中间步骤求新余数,判断商值
最后一步估值减小误差,有“末位恒置1法”和“0舍1入法”,前者硬件实现比较简单。
商值结果溢出判断
例题:
2.代码解析
设计文件
算法分析:当S为0时,C=A*B。从例题可知,两个N位数补码(含一位符号位)相乘,要重复N次操作(判断YnYn+1;部分积加值;部分积移位)可得到最终结果。但是最后一次操作没有移位操作,需要单独处理,思路是使用C_reg寄存器储存操作后移位前的结果,用C_shift变量存储操作后移位后的结果,作为取C_reg的值的部分作为输出C。
当S为1时,C=A/B。从例题可知,两个N位数补码(含一位符号位)相除,欲得到N位结果(一位符号位,N-1位小数位),要重复N次操作(判断被除数(余数)和除数的符号位;商值移位;余数加值,商值判断)得到结果。但第一次操作没有商值判断和余数移位;最后一步没有余数加值。且重复N次操作后,还要做末位置1操作。
为方便共享硬件,除法的除数和被除数均用两位符号位。乘法需要循环8次,故除法结果取7位结果(1位符号位,6位小数位),使用7次操作(判断被除数(余数)和除数的符号位;商值移位;余数加值,商值判断)和一次末位置1操作。而除法的商值移位,可以借助count完成。最后一步没有余数加值可以直接忽略这个要求,因为不影响结果。但第一次操作没有商值判断和余数移位,商值同样放置在变量中,但最终不取;余数移位则用一个选择器和B_shift实现。
//输入:1_符号 7_小数
//输出:1_符号 14_小数
//输入输出均为补码表示
//S:0 乘法 1 除法
//C:A*B A/B
//ready 结果有效
//start 开始运算
//乘法为Booth算法
//除法为加减交替法
module ALU_fixed(
input clk,
input rst_n,
input start,
input S,
input signed [7:0] A,
input signed [7:0] B,
output signed [14:0] C,
output error,
output ready
);
reg signed [8:0] A_reg;
reg signed [8:0] B_reg;
reg signed [15:0] C_reg;
wire signed [15:0] C_shift;
wire signed [8:0] A_shift;
wire signed [8:0] A_not;
wire signed [8:0] B_not;
reg busy,busy_D;
reg [2:0] count;
always @(posedge clk , negedge rst_n) begin
if (!rst_n) begin
A_reg <= 0;
B_reg <= 0;
C_reg <= 0;
end else if(S == 0) begin
A_reg <= {A[7],A};
if(busy) begin
case (B_reg[1:0])
2'b00:begin
C_reg <= C_shift;
end
2'b01:begin
C_reg[15:7] <= C_shift[15:7] + A_reg;
C_reg[6:0] <= C_shift[6:0];
end
2'b10:begin
C_reg[15:7] <= C_shift[15:7] + A_not;
C_reg[6:0] <= C_shift[6:0];
end
2'b11:begin
C_reg <= C_shift;
end
endcase
B_reg <= B_reg >> 1;
end
else begin
B_reg <= {B,1'b0};
C_reg <= 0;
end
end
else begin
if(busy) begin
if(count == 3'd7)
C_reg[4'd15 - count] = 1;
else
if (A_reg[8] ^ B_reg[8]) begin
A_reg = A_shift + B_reg;
C_reg[4'd15 - count] = 0;
end
else begin
A_reg = A_shift + B_not;
C_reg[4'd15 - count] = 1;
end
end
else begin
A_reg <= {A[7],A};
B_reg <= {B[7],B};
C_reg <= 0;
end
end
end
always @(posedge clk , negedge rst_n) begin
if (!rst_n) begin
busy <= 0;
busy_D <= 0;
end else begin
busy_D <= busy;
if(start == 1)
busy <= 1;
else if(count == 3'd7)
busy <= 0;
else
busy <= busy;
end
end
always @(posedge clk , negedge rst_n) begin
if (!rst_n) begin
count <= 0;
end else begin
if(busy)
count <= count + 3'd1;
else
count <= 0;
end
end
assign error = (S == 0) ? (C_reg[15]^C_reg[14]) : (A[7] ^ B[7] ^ C[14]);
assign C_shift = C_reg >>> 1;
assign A_shift = (count == 0) ? A_reg : A_reg << 1;
assign A_not = ~A_reg + 1'b1;
assign B_not = ~B_reg + 1'b1;
assign ready = busy_D & ~busy;
assign C = C_reg[14:0];
endmodule
仿真文件
module ALU_fixed_tb(
);
reg clk,rst_n,start,S;
reg [7:0] A,B;
wire [14:0] C;
wire ready,error;
ALU_fixed ALU1(
.clk(clk),
.rst_n(rst_n),
.start(start),
.S(S),
.A(A),
.B(B),
.C(C),
.error(error),
.ready(ready)
);
always #1 clk = ~clk;
initial begin
clk = 0;
rst_n = 0;
A = 0;
B = 0;
start = 0;
#3
rst_n = 1;
#4
S = 0;
A = 8'b0_1101000;
B = 8'b0_1011000;
start = 1;
#2
start = 0;
#20
A = 8'b1_0101000;
B = 8'b1_0011000;
start = 1;
#2
start = 0;
#20
S = 1;
A = 8'b0_1001000;
B = 8'b0_1101000;
start = 1;
#2
start = 0;
#20
A = 8'b1_0111000;
B = 8'b0_0000001;
start = 1;
#2
start = 0;
#20
$stop;
end
endmodule
3.结果
乘法:0.8125*0.6875=0.55859375
除法:0.5625/0.8125=0.692307,模块结果为0.703125,误差为0.010818,算法误差为(0,2^(-6)=0.0156],在误差范围内。当然,由于使用的是末位置1法,对一些比较明显的结果如-0.5,-1并不能得到准确的结果,得到的是误差范围内的结果。
溢出判断(纯小数乘法不溢出,纯小数除法会溢出)
4.收获
本文算法参考《计算机组成原理》,更多细节自己去跑代码。