当前位置:   article > 正文

基于FPGA的数字信号处理(14)--定点数的舍入模式(5)向上取整ceil_fpga ceil

fpga ceil

前言

在之前的文章介绍了定点数为什么需要舍入和几种常见的舍入模式。今天我们再来看看另外一种舍入模式:向上取整ceil。

10进制数的ceil

ceil:也叫 向上取整向正无穷方向取整。它的舍入方式是数据往正无穷的方向,舍入到最近的整数,比如1.75 ceil到2,-0.25 ceil到0等。以-2到1.75之间的16个数据(步长0.25)为例,它们的ceil结果是这样的:

从上图可以看到:

  • 正数的ceil,分为两个部分:

    • 小数部分不为0时就是把小数部分(或者约定精度外的部分)丢掉然后加1。例如1.5 >> 1>> 1+1 >> 2,0.5 >> 0 >> 0 +1 >> 1 等
    • 小数部分为0时就是把小数部分(或者约定精度外的部分)丢掉。例如1>> 1,2 >> 2 等
  • 负数的ceil,也分为两个部分:

    • 小数部分不为0时就是把小数部分(或者约定精度外的部分)丢掉。例如-1.5 >> -1,-0.5 >> 0 等
    • 小数部分为0时就是把小数部分(或者约定精度外的部分)丢掉。例如-1>> -1,-2 >> -2 等
  • 0的ceil,就是直接丢掉小数部分

2进制数的ceil

2进制数的ceil和10进制的ceil类似,但是对于负数部分的处理是不同的。以Q4.2格式的定点数(字长4位,小数2位的有符号数)为例,对于负数的小数部分的处理:

  • -2(d) = 10_00(b) ceil后的值为 -2,等价于 10,即舍弃小数部分后的值(10)
  • -1.75(d) = 10_01(b) ceil后的值为 -1,等价于 11,即舍弃小数部分后的值(10)再加1
  • -1.5(d) = 10_10(b) ceil后的值为 -1,等价于 11,即舍弃小数部分后的值(10)再加1
  • -1.25(d) = 10_11(b) ceil后的值为 -1,等价于 11,即舍弃小数部分后的值(10)再加1
  • -1(d) = 11_00(b) ceil后的值为 -1,等价于 11,即舍弃小数部分后的值(11)
  • -0.75(d) = 11_01(b) ceil后的值为 0,等价于 00,即舍弃小数部分后的值(11)再加1
  • -0.5(d) = 11_10(b) ceil后的值为 0,等价于 00,即舍弃小数部分后的值(11)再加1
  • -0.25(d) = 11_11(b) ceil后的值为 0,等价于 00,即舍弃小数部分后的值(11)再加1

总结一下,就是:

  • 小数部分不为0时就是把小数部分(或者约定精度外的部分)丢掉再加1。
  • 小数部分为0时就是把小数部分(或者约定精度外的部分)丢掉(或再加0)。

对于正数的处理和10进制的方式相同,都是:

  • 小数部分不为0时就是把小数部分(或者约定精度外的部分)丢掉然后加1。
  • 小数部分为0时就是把小数部分(或者约定精度外的部分)丢掉。

对于0的处理:就是直接丢掉小数部分。

综上可以发现,2进制的ceil可以分为两种情况:

  • 整数的ceil,相当于直接丢弃小数部分。
  • 非整数的ceil,相当于先丢小数部分,然后把剩余的整数部分+1。

因此,ceil的实现可以简化为:

首先舍去小数部分,然后剩余整数部分加上一个进位。当该数是整数时,进位为0;否则为1。

image-20240421154352503

下面以 用ceil的方式来实现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:0];				//是整数时进位为0,非整数进位为1
assign	data_2Q0 = data_4Q2[3:2] + carry;	//舍弃低位(即整个小数部分)后再加进位

endmodule
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

因为一共只有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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

同时,我们也用matlab来实现同样的功能,观察两者的输出是否一致:

%--------------------------------------------------
% 关闭无关内容
clear;
close all;
clc;

%--------------------------------------------------
% 生成数据并做ceil处理
x = -2:0.25:1.75;
F = fimath('RoundingMethod','Ceiling');     % 设定舍入模式为ceil
data_4Q2 = fi(x,1,4,2,F);                   % 生成Q4.2格式的定点数
data_2Q0 = fi(data_4Q2,1,2,0,F);            % 从Q4.2格式转换成Q2.0格式

% 打印数据
for i=1:length(data_4Q2)
    fprintf('data_4Q2:%s    data_2Q0:%s\n',hex(data_4Q2(i)),hex(data_2Q0(i)))
end
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

下图是2者分别输出的数据(16进制),可以看到有几个数是对不上的:

image-20240420162219640

这几个数的输入分别是0101/0110/0111,即10进制数1.25/1.5/1.75,它们ceil结果应该是2。从上图来看,好像是matlab错了,而RTL对了,但实际情况恰恰相反。现在想想结果是什么格式的?Q2.0!它能表示的最大的数是多少?是10进制的1!所以结果溢出了!

那为什么RTL的结果又 ”对“ 了呢?这纯属是乌龙。因为打印结果是16进制的,并不表示10进制数值,结合结果的2位位宽,可知 ”2“,实际上就是10,它是01的溢出产生的,这个数在Q2.0格式的定点数中并不表示 ”数字2“,而是数字 ”-1“。

matlab是有溢出处理进制的(saturate),它把溢出值把都饱和在了最大值,即01(10进制的1),所以为了防止这种情况的发生,我们也要设计对应的溢出处理机制。因为是向上取整,所以结果只会是正向的溢出,那么就只要限定最大值即可,把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:0];											//是整数时进位为0,非整数进位为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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这样结果就是正确的了:

image-20240420165316917

定点数从Q4.2格式转Q2.0格式是一个比较特殊的例子,因为它相当于把小数部分全部舍弃掉了,如果舍入要求不是全部小数位,而是部分小数位,那么处理方式是一样的吗?

是一样的。对于其他情况则相当于把小数点移动到了对应的位置。例如Q5.3格式的定点数转Q3.1格式,则只需要把最后两位小数舍弃并加上进位即可即可,例如:

00.001 是0.125,向上距离它最近的Q3.1格式的数是0.5即00.1,即00.111 >> 00.0 + 1 >> 00.1

00.100 是0.5,向上距离它最近的Q3.1格式的数就是它自身0.5即00.1,即00.100 >> 00.1

01.111 是1.875,向上距离它最近的Q3.1格式的数就是它自身2,但是这个数超出了范围,所以只能饱和在最大的数01,即1.5

其他类似,不赘述了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/720922
推荐阅读
相关标签
  

闽ICP备14008679号