前言
在之前的文章介绍了定点数为什么需要舍入和几种常见的舍入模式。今天我们再来看看另外一种舍入模式:收敛取整convergent。
10进制数的convergent
convergent: 收敛取整。它的舍入方式和四舍五入非常类似,都是舍入到最近的整数,比如1.75 convergent到2,-0.25 convergent到0等。二者唯一的区别在于对0.5这类数据的处理上。
0.5这种处于中间,舍入到两边的整数距离都一样近,所以可以把它叫做 “中间数”
round对于正的中间数的处理相当于向上取整,对于负的中间数相当于向下取整。例如0.5的round结果是1,-0.5的round结果是-1
convergent对于中间数的处理是将其舍入到最近的偶数。例如0.5舍入到0,1.5舍入到2,-0.5舍入到0,-1.5舍入到-2
以-2到1.75之间的16个数据(步长0.25)为例,它们的convergent结果是这样的:
2进制数的convergent
2进制数的convergent和10进制的convergent类似。以Q4.2格式的定点数(字长4位,小数2位的有符号数)为例,对于负数的小数部分的处理:
- -2(d) = 10_00(b) convergent后的值为 -2,等价于 10,即舍弃小数部分后的值(10)
- -1.75(d) = 10_01(b) convergent后的值为 -2,等价于 10,即舍弃小数部分后的值(10)
- -1.5(d) = 10_10(b) convergent后的值为 -1,等价于 11,即舍弃小数部分(10)后再加1
- -1.25(d) = 10_11(b) convergent后的值为 -1,等价于 11,即舍弃小数部分(10)后再加1
- -1(d) = 11_00(b) convergent后的值为 -1,等价于 11,即舍弃小数部分后的值(11)
- -0.75(d) = 11_01(b) convergent后的值为 -1,等价于 11,即舍弃小数部分后的值(11)
- -0.5(d) = 11_10(b) convergent后的值为 0,等价于 00,即舍弃小数部分(11)后再加1
- -0.25(d) = 11_11(b) convergent后的值为 0,等价于 00,即舍弃小数部分(11)后再加1
对于0的处理:
- 直接舍弃小数部分。
对于正数的小数部分的处理:
- 1.75(d) = 01_11(b) convergent后的值为 2,此时溢出了,需要扩展位宽,处理方式也是舍弃小数部分(001)后再加1即010
- 1.5(d) = 01_10(b) convergent后的值为 2,此时溢出了,需要扩展位宽,处理方式也是舍弃小数部分(001)后再加1即010
- 1.25(d) = 01_01(b) convergent后的值为 1,等价于 01,即舍弃小数部分后(01)的值
- 1(d) = 01_00(b) convergent后的值为 1,等价于 01,即舍弃小数部分后(01)的值
- 0.75(d) = 00_11(b) convergent后的值为 1,等价于 01,即舍弃小数部分后(00)的值再加1
- 0.5(d) = 00_10(b) convergent后的值为 1,等价于 01,即舍弃小数部分后(00)的值再加1
- 0.25(d) = 00_01(b) convergent后的值为 0,等价于 00,即舍弃小数部分后(00)的值
总结一下,就是:
对于正数的convergent处理:首先舍掉小数位,然后加一个进位值:
- 当小数部分的最高位为0时,说明这个数的小数部分是小于0.5的,所以不需要进位,此时的进位值为0。
- 当小数部分的最高位为1且其他位不为全0时,说明这个数的小数部分是大于0.5的,所以需要进位,即此时的进位值为1。
- 当小数部分的最高位为1且其他位为全0时,说明这个数的小数部分是等于0.5的,所以需要进位,因为进位是需要进到最近的偶数,所以还需要看整数部分的最低位,如果最低位为0,说明此时的整数部分就是最近的偶数整数,所以进位值为0;如果为1,则说明+1后的整数才是距离最近的偶数,所以进位值为1。
对于0的convergent处理:首先舍掉小数位,然后加一个进位值,该进位值恒定为0。
对于负数的convergent处理:首先舍掉小数位,然后加一个进位值:
- 当小数部分的最高位为0时,说明这个数的小数部分是小于0.5的,而整数部分又是个负数,相当于二者的和的小数部分小于 -0.5。例如10.01是-1.75,它的小数部分.01是0.25,整数部分10是-2,二者相加是-2+0.25 = -1.75,所以它们的处理方式都是先舍弃小数位,然后加0。
- 当小数部分的最高位为1且其他位不为全0时,说明这个数的小数部分是大于0.5的,而整数部分又是个负数,相当于二者的和的小数部分大于-0.5。例如10.11是-1.25,它的小数部分.11是0.75,整数部分10是-2,二者相加是-2+0.75 = -1.25。所以它们的处理方式都是先舍弃小数位,然后加1。
- 当小数部分的最高位为1且其他位为全0时,说明这个数的小数部分是等于0.5的,此时需要舍入到最近的偶数,如果此时整数部分的最低位的值是1,说明此时最近的偶数还需要加1,;如果此时整数部分的最低位的值是0,说明最近的偶数就是这个整数部分。例如10_10是 -1.5,convergent后的值为 -2(10),10_10的整数部分为10,其最低位为0,所以最近的偶数就是整数部分的值,所以进位值为0,即10_10>>10+0>>10;或者11_10是 -0.5,convergent后的值为 0(00),11_10的整数部分为11,其最低位为1,所以最近的偶数是整数部分的值+1,所以进位值为1,即11_10>>11+1>>00。
上面的内容可以再精简:
- 当小数部分的最高位为0时,相当于整数部分 + 进位值,进位值等于0即小数部分的最高位
- 当小数部分的最高位为1且其他位不为全0时,相当于整数部分 + 进位值,进位值等于1即小数部分的最高位
- 当小数部分的最高位为1且其他位为全0时,相当于整数部分 + 进位值,进位值等于整数部分的最低位
下面以 用convergent的方式来实现Q4.2格式定点数转Q2.0格式定点数为例,Verilog代码如下:
module test( input [3:0] data_4Q2, //有符号数,符号1位,字长4位,小数2位 output [1:0] data_2Q0 //有符号数,符号1位,字长2位,小数0位 ); wire carry; assign carry = (data_4Q2[1] && ~data_4Q2[0]) ? data_4Q2[2] : data_4Q2[1]; assign data_2Q0 = data_4Q2[3:2] + carry; //舍弃低位(即整个小数部分)后再加进位 endmodule
因为一共只有16个数,所以我们可以用穷举的方式来测试,TB如下:
`timescale 1ns/1ns module test_tb(); reg [3:0] data_4Q2; //有符号数,符号1位,整数2位,小数2位 wire [1:0] data_2Q0; //有符号数,符号1位,整数2位,小数0位 integer i; //循环变量 initial begin data_4Q2 = 0; //输入赋初值 for(i=0;i<16;i=i+1)begin //遍历所有的输入,共16个 data_4Q2 = i; #5; $display("data_4Q2:%h data_2Q0:%h",data_4Q2,data_2Q0); end #20 $stop(); //结束仿真 end //例化被测试模块 test test_inst( .data_4Q2 (data_4Q2), .data_2Q0 (data_2Q0) ); endmodule
同时,我们也用matlab来实现同样的功能,观察两者的输出是否一致:
%-------------------------------------------------- % 关闭无关内容 clear; close all; clc; %------------------------------------------------------------------------------- % 生成数据并做convergent处理 x = -2:0.25:1.75; F = fimath('RoundingMethod','convergent'); % 设定舍入模式为convergent %F_c = fimath('RoundingMethod','Convergent'); % 设定舍入模式为convergent data_4Q2 = fi(x,1,4,2,F); % 生成Q4.2格式的定点数 data_2Q0 = fi(data_4Q2,1,2,0,F); % 从Q4.2格式转换成Q2.0格式
下图是2者分别输出的数据(16进制),可以看到有2个数是对不上的:
你如果记性不错的话,就会发现这两个数正是前面讨论的正数会出现溢出的情况。这2个数分别是0110/0111,即10进制数1.5/1.75,它们的convergent结果应该是2。从上图来看,好像是matlab错了,而RTL对了,但实际情况恰恰相反。现在想想结果是什么格式的?Q2.0!它能表示的最大的数是多少?是10进制的1!所以结果溢出了!
那为什么RTL的结果又 ”对“ 了呢?这纯属是乌龙。因为打印结果是16进制的,并不表示10进制数值,结合结果的2位位宽,可知 ”2“,实际上就是10,它是01的溢出产生的,这个数在Q2.0格式的定点数中并不表示 ”数字2“,而是数字 ”-1“。
matlab是有溢出处理进制的(saturate),它把溢出值把都饱和在了最大值,即01(10进制的1),所以为了防止这种情况的发生,我们也要设计对应的溢出处理机制。因为负数的最小值只取决于整数(小数部分是正的权重),而正数的最大值同时取决于小数和整数,例如Q4.2格式的最小值是-2即10_00,而最大值则是1.75即01_11,所以溢出只会是正向的溢出,那么就只要限定最大值即可。把Verilog代码改一下:
module test( input [3:0] data_4Q2, //有符号数,符号1位,字长4位,小数2位 output [1:0] data_2Q0 //有符号数,符号1位,字长2位,小数0位 ); wire carry; wire [2:0] data_temp; //扩展1bit,防止溢出 assign carry = (data_4Q2[1] && ~data_4Q2[0]) ? data_4Q2[2] : data_4Q2[1]; assign data_temp = {data_4Q2[3],data_4Q2[3:2]} + {2'b00,carry}; //中间变量,舍弃低位(即整个小数部分)后再加进位 assign data_2Q0 = (data_temp[2:1]==2'b01) ? 2'b01 : data_temp[1:0]; //data_2Q0的高2位为01说明产生了正向的进位,即溢出 endmodule
这样结果就是正确的了:
定点数从Q4.2格式转Q2.0格式是一个比较特殊的例子,因为它相当于把小数部分全部舍弃掉了,如果舍入要求不是全部小数位,而是部分小数位,那么处理方式是一样的吗?
是一样的。对于其他情况则相当于把小数点移动到了对应的位置。例如Q5.3格式的定点数转Q3.1格式,则只需要把最后两位小数舍弃并加上进位即可即可。