单精度浮点数
根据 IEEE 754 标准,单精度浮点数(float)为32位,其存储格式为:
sign:符号位,1bit,S
exponent:指数,8 bits,原码,E = (exponent)10-12710
fraction:尾数,23 bits,原码。在浮点数中隐去了小数点前的1,因此 M = 1+(fraction)10/223
该浮点数表示的10进制数为:(-1)S×M×2E
一般情况下,浮点数加法的处理步骤为:
- 对阶(align)
通过exponent将计算数对齐到相同指数,尾数只有在指数相同时才可以做加减运算。对齐方式为向高阶对齐,即将阶数低的尾数右移,这样不会损失高位数据的精度。 - 尾数求和
- 规格化(normalization)
将计算后的数据规格化。对于产生溢出位的结果右移;对于操作数符号位不同,产生结果多个高位为0时,需要左移,直到1出现在最高位。 - 舍入(round)
因为计算过程中需要扩展位数保证精度,因此在输出浮点数结果前需要截断扩展的尾数。
但浮点数表示的数据范围有限,处理时会遇到正溢出、负溢出、数值无效的特殊情况,就需要对这些情况进行特殊处理:
Special Number 处理:
当exp=8’hff 且man=23’h0时,输入为inf,表示溢出,根据sign,可以为+inf/ -inf。
当exp=8’hff 且man!=23’h0时,表示NaN。
计算过程中可产生inf的情况:
- input为存在+inf或-inf
- rounding前的exp=8’hfe,且rounding产生了进位
计算过程中可产生NaN的情况:
- input为NaN
- input两操作数分别为+inf与-inf
Round 处理
就近舍入:
即十进制下的四舍五入。保留23位尾数,当需要舍去的数大于2-24,进一;小于2-24,舍去;当等于2-24时,舍向最近的偶数。
例如:
- 多余数字是 (1001)2,它大于 (0.5)10,故最低位进 12。
- 多余数字是 (0111)2,它小于 (0.5)10,则直接舍掉多余数字。
- 多余数字是 (1000)2,正好是等于 (0.5)10 的特殊情况;那么此时最低位为 02 则舍掉多余位,最低位为 12 则进位 12。
对于浮点数加法实现方式的学习,可参考的代码大多数是通过时序逻辑实现的,其条理清晰,也容易理解,但大多用到循环语句,并不高效,在此不再进行赘述。以下是参考了Howeng98在GitHub上的组合逻辑实现的浮点数加法器。
链接: 参考github代码
用优先编码器修改了代码的normalization部分,使其可综合(原代码用while循环,不知道是怎么跑通的=_=);
添加了sepcial处理:针对输入为inf和NaN的情况,并对inf与NaN区分输出。
/* 对于数据格式的说明:
signed exp man
31 30...23 22...0
特殊情况:
exponent fraction value
0 zero 0
0 non-zero +-2^(-126)*0.(man)
1~254 any +-2^(exp-127)*1.(man)
255 zero +-inf
255 non-zero NaN
*/
module fpadder (
input [31:0] src1,
input [31:0] src2,
output reg [31:0] out
);
reg [66:0] fraction_1;
reg [66:0] fraction_2;
reg [66:0] sum;
reg [66:0] fraction_Ans;
reg [7:0] exponent_1;
reg [7:0] exponent_2;
reg [7:0] exponent_Ans;
reg [4:0] norm_count;
reg norm_vld;
reg sign_1;
reg sign_2;
reg sign_Ans;
reg guard_bit;
reg round_bit;
reg sticky_bit;
reg inf_1;
reg inf_2;
reg nan_1;
reg nan_2;
/ FP ADDER ///
always@(*) begin
//loading
begin
fraction_1 = {2'd0,src1[22:0],42'd0}; // 对尾数扩展。前2bit第一位为进位位,第二位为隐藏的1,
fraction_2 = {2'd0,src2[22:0],42'd0}; // 此处不赋值2'b01是留在special case中处理指数全0的情况。后42bit保障精度,可适当减少
exponent_1 = src1[30:23];
exponent_2 = src2[30:23];
sign_1 = src1[31];
sign_2 = src2[31];
inf_1 = 1'b0;
nan_1 = 1'b0;
inf_2 = 1'b0;
nan_2 = 1'b0;
end
//preprocessing
begin
if(exponent_1 == 0) begin //when exponent is zero but fraction is non-zero,set it to 1
exponent_1 = 1;
fraction_1[65] = 0; //make 0.(Frac) //参考代码开头对于特殊情况的说明
end
else
fraction_1[65] = 1;
if(exponent_2 == 0) begin
exponent_2 = 1;
fraction_2[65] = 0;
end
else
fraction_2[65] = 1; //make 1.(Frac)
end
//special case
begin
if((exponent_1 == 0) && (fraction_1 == 0)) begin //if src1 is zero, then return src2
sign_Ans = sign_2;
exponent_Ans = exponent_2;
fraction_Ans = fraction_2;
end
else if (exponent_1 == 8'hff) begin
if (fraction_1 == 23'h0) inf_1 = 1'b1; // 补充原代码缺失的对inf与NaN的处理
else nan_1 = 1'b1;
end
else begin
end
if((exponent_2 == 0) && (fraction_2 == 0)) begin // if src2 is zero, then return src1
sign_Ans = sign_1;
exponent_Ans = exponent_1;
fraction_Ans = fraction_1;
end
else if (exponent_2 == 8'hff) begin
if (fraction_2 == 23'h0) inf_2 = 1'b1;
else nan_2 = 1'b1;
end
else begin
end
end
//align
begin
if(exponent_1 > exponent_2) begin
fraction_2 = fraction_2 >> (exponent_1 - exponent_2);
exponent_Ans = exponent_1;
end
else if(exponent_1 < exponent_2) begin
fraction_1 = fraction_1 >> (exponent_2 - exponent_1);
exponent_Ans = exponent_2;
end
else begin
exponent_Ans = exponent_1;
end
end
//add significands
begin
if(sign_1 == sign_2) begin
fraction_Ans = fraction_1 + fraction_2;
sign_Ans = sign_1;
end
else begin
if(fraction_1 >= fraction_2) begin
fraction_Ans = fraction_1 - fraction_2;
sign_Ans = sign_1;
end
else begin
fraction_Ans = fraction_2 - fraction_1;
sign_Ans = sign_2;
end
end
end
sum = fraction_Ans; //sum is for checking the addition of src1 and src2
//overflow
begin
if(fraction_Ans[66]) begin
fraction_Ans = fraction_Ans >> 1;
exponent_Ans = exponent_Ans + 1;
end
end
//normalization
begin
if(fraction_Ans == 67'b0) begin
fraction_Ans = 67'b0;
exponent_Ans = 8'b0;
end
else begin
if(fraction_Ans[65] == 0)begin
PENC32({11'b0, fraction_Ans[64:42]}, norm_count, norm_vld); // 用优先编码器计算最高位1所在bit
fraction_Ans = fraction_Ans << (5'd23 - norm_count); // 左移完成规格化
exponent_Ans = exponent_Ans + norm_count - 5'd23; // 对应更新阶数
// while((fraction_Ans[65] == 0) && (fraction_Ans[64:42] > 0))begin
// fraction_Ans = fraction_Ans << 1;
// exponent_Ans = exponent_Ans - 1;
// end
end
else if(fraction_Ans[65]) begin
// do nothing
end
end
end
//round
begin
guard_bit = fraction_Ans[41];
round_bit = fraction_Ans[40];
if(fraction_Ans[39:0] > 0)
sticky_bit = 1;
else
sticky_bit = 0;
if(guard_bit && (fraction_Ans[42] | round_bit | sticky_bit)) begin // 对照进位的条件
fraction_Ans = fraction_Ans + 67'b0000000000000000000000001000000000000000000000000000000000000000000;
end
end
//convert:
begin
out[22:0] = fraction_Ans[64:42];
out[30:23] = exponent_Ans[7:0];
out[31] = sign_Ans;
//special case
if(fraction_Ans == 0) //when fraction is 23'd0
out = 0;
if(nan_1 || nan_2 || (inf_1 && inf_2 && (sign_1 ^ sign_2))) begin // return NaN
exponent_Ans = 8'hff;
fraction_Ans = 23'h1;
end
else if(inf_1 || inf_2 || (exponent_Ans == 8'b11111111)) begin // return inf
fraction_Ans = 0;
out[22:0] = 23'h0;
end
// if(exponent_Ans == 8'b11111111) //when exponent is 11111111
// fraction_Ans = 0;
// out[22:0] = fraction_Ans[64:42];
end
end
// PENC TASK // 用4个PENC8拼接实现32bit优先编码
task PENC8;
//port declaration
input [7:0] D;
output [2:0] Q;
output vld;
//internal node signals declaration
begin
vld = D[7]|D[6]|D[5]|D[4]|D[3]|D[2]|D[1]|D[0];
Q[2] = D[7]|D[6]|D[5]|D[4];
Q[1] = (D[7]|D[6]|D[5]|D[4]) ? (D[7]|D[6]) : (D[3]|D[2]);
Q[0] = (D[7]|D[6]|D[5]|D[4]) ? (D[7]|((~D[6])&D[5])) : (D[3]|((~D[2])&D[1]));
end
endtask
task PENC16;
//port declaration
input [15:0] D;
output [3:0] Q;
output vld;
//internal node signals declaration
reg [2:0] Q1_h, Q1_l;
reg vld1_h, vld1_l;
begin
PENC8( D[15:8], Q1_h, vld1_h);
PENC8( D[7:0], Q1_l, vld1_l);
Q[3] = vld1_h;
Q[2:0] = vld1_h ? Q1_h : Q1_l;
vld = vld1_h | vld1_l;
end
endtask
task PENC32;
//port declaration
input [31:0] D;
output [4:0] Q;
output vld;
//internal node signals declaration
reg [3:0] Q2_h, Q2_l;
reg vld2_h, vld2_l;
begin
PENC16( D[31:16], Q2_h, vld2_h);
PENC16( D[15:0], Q2_l, vld2_l);
Q[4] = vld2_h;
Q[3:0] = vld2_h ? Q2_h : Q2_l;
vld = vld2_h | vld2_l;
end
endtask
endmodule
这个代码应该还可以用assign的方式重写,将一些分支语句改为并行执行,然后通过mux输出,以获得更高的速度。