算法中常常会到浮点数运算,而浮点数的处理常常是Verilog初学中常常遇到的问题。以下将就一个简单的例子说明Verilog中浮点数运算处理。
在JPEG图像压缩时遇到色彩空间变换的问题,将YCbCr转换到RGB会遇到浮点数的运算,这个实现复杂,以摄氏温度转换为华氏温度为例 : F = C x 1.8 + 32
R = 1.164(Y-16) + 1.596(Cr-128)
G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
B = 1.164(Y-16) + 2.018(Cb-128)
module C2F( iclk,irstn,ic,of);
input iclk;
input irstn;
input[7:0] ic;
output[10:0] of;
reg[7:0] c;
reg[10:0] of;
always@(posedge iclk or negedge irstn)
begin
if(!irstn)
begin
c <= 0;
of <= 0;
end
else
begin
c <= ic;
of <= c * 1.8 + 32; // 直接处理,在ISE中综合时会报出错
end //ERROR:Xst:850 - "C2F.v" line 31: Unsupported real constant.
end
endmodule
上面直接用浮点数进行乘法运算显然是会出错的!!
//以下为改正后的程序
module C2F( iclk,irstn,ic,of);
input iclk;
input irstn;
input[7:0] ic;
output[10:0] of;
reg[7:0] c;
reg[10:0] of;
reg[10:0] sum;
always@(posedge iclk or negedge irstn)
begin
if(!irstn)
begin
//c <= 0;
of <= 0;
sum <= 0;
end
else
begin
// c <= ic;
sum <= ic * 7+ 128;
of <= (sum >>2); //实际是 近似计算:of=(ic*7+128)/4=ic*1.75+32,
end
end
endmodule
将浮点数乘以相同的扩展倍数,然后再将所得结果除以扩展位,这样可以达到近似精确的效果!!
谓定点小数,就是小数点的位置是固定的。我们是要用整数来表示定点小数,由于小数点的位置是固定的,所以就没有必要储存它(如果储存了小数点的位置,那就是浮点数了)。既然没有储存小数点的位置,那么计算机当然就不知道小数点的位置,所以这个小数点的位置是我们写程序的人自己需要牢记的!
先以10进制为例:如果我们能够计算12+34=46的话,当然也就能够计算1.2+3.4 或者 0.12+0.34了。所以定点小数的加减法和整数的相同,并且和小数点的位置无关。乘法就不同了。 12*34=408,而1.2*3.4=4.08。这里1.2的小数点在第1位之前,而4.08的小数点在第2位之前,小数点发生了移动。所以在做乘法的时候,需要对小数点的位置进行调整?!可是既然我们是做定点小数运算,那就说小数点的位置不能动!!怎么解决这个矛盾呢,那就是舍弃最低位。 也就说1.2*3.4=4.1,这样我们就得到正确的定点运算的结果了。所以在做定点小数运算的时候不仅需要牢记小数点的位置,还需要记住表达定点小数的有效位数。上面这个例子中,有效位数为2,小数点之后有一位。
现在进入二进制:我们的定点小数用16位二进制表达,最高位是符号位,那么有效位就是15位。小数点之后可以有0 - 15位。我们把小数点之后有n位叫做Qn,例如小数点之后有12位叫做Q12格式的定点小数,而Q0就是我们所说的整数。
Q12的正数的最大值是 0 111 . 111111111111,第一个0是符号位,后面的数都是1,那么这个数是十进制的多少呢,很好运算,就是 0x7fff / 2^12 = 7.999755859375。对于Qn格式的定点小数的表达的数值就它的整数值除以2^n。在计算机中还是以整数来运算,我们把它想象成实际所表达的值的时候,进行这个运算
反过来把一个实际所要表达的值x转换Qn型的定点小数的时候,就是x*2^n了。例如 0.2的Q12型定点小数为:0.2*2^12 = 819.2,由于这个数要用整数储存, 所以是819 即 0x0333。因为舍弃了小数部分,所以0x0333不是精确的0.2,实际上它是819/2^12 =0.199951171875
我们用数学表达式做一下总结:
x表示实际的数(一个浮点数), q表示它的Qn型定点小数(一个整数)。
q = (int) (x * 2^n)
x = (float)q/2^n
由于/ 2^n和* 2^n可以简单的用移位来计算,所以定点小数的运算比浮点小数要快得多。下面我们用一个例子来验证一下上面的公式:
用Q12来计算2.1 * 2.2,先把2.1 2.2转换为Q12定点小数:
2.1 * 2^12 = 8601.6 = 8602
2.2 * 2^12 = 9011.2 = 9011
(8602 * 9011) >> 12 = 18923
18923的实际值是18923/2^12 = 4.619873046875 和实际的结果 4.62相差0.000126953125,对于一般的计算已经足够精确了;
计算内容
5.555*4.444=24.68642
第一步:将被乘数乘以256
5.555*256 = 1422.08 = 20’d1422 = 20’h5_8E; (存在误差0.0056%)
4.444*256 = 1137.664 = 20’d1137= 20’h4_71; (存在误差0.058%)
第二步:中间运算
20’h5_8E * 20’h4_71 = 20’h18_ABAE;
第三步:中间结果除以256
20’h18_ABAE >> 8 = 20’h18_AB;
第四步:转换为实际小数比较
20’h18_AB = 24.171(存在误差2%)
注:1、中间乘法操作时,不存在误差。
2、如果想降低取整导致的误差,可以加大位宽
如果两个小数点的位置不相同,比如说分别为0△0101、00△110,代表的十进制数分别是0.3125和0.75。两个数不经过处理,直接相加,Verilog HDL的编译器按照二进制规则逐位相加,结果为01011。如果小数点位置与第一个数相同,则表示0.6875。如果小数点位置与第二个数相同,则表示1.375,显示结果是不正确的。为了进行正确的运算,需要在第二个末位补0,为00△1100,两个数再直接相加,得到“01△1001”,转换成十进制数为1.0625,得到正确的结果;
综上所述,如果对于未对齐的二进制数,需要补齐最低位使得小数位的位宽相同才能进行加减法运算;如果将数据均看成无符号整数,则不需要进行小数位扩展,因为Verilog HDL编译器会自动将参与运算的数据以最低位对齐进行运算;
module CP_3_1_alt_calculate_add
(
input [3:0] d1,
input [3:0] d2,
output [3:0] unsigned_out, //无符号加法输出
output signed [3:0] signed_out //有符号加法输出
);
//无符号加法运算
assign unsigned_out=d1+d2;
//有符号加法运算
wire signed [3:0] s_d1;
wire signed [3:0] s_d2;
assign s_d1=d1;
assign s_d2=d2;
assign signed_out=s_d1+s_d2;
endmodule
乍眼一看,我们的signed_out及unsigned_out的输出结果完全相同,相同的输入数据,进行无符号数运算和有符号数运算的结果竟然没有任何区别!对于加减法来说,无论是否为符号数运算,其结果均完全相同,因为二进制的运算规则相同,如果将二进制数据转换成十进制数据,我们就可以看出两者的差别了:
一个浮点数A由两个数m和e来表示,即A=m×b^e. 在任意一个这样的系统中,我们选择一个基数b(计数系统的基)和精度B(使用多少位来存储)。m(即尾数)是B位二进制数,如±d.ddd…ddd。(其中第一个d必然是1,除非你要表示特别小的数才是0)
大部分计算机采用了二进制(b=2)的表示方法,位是衡量浮点数所需存储空间的单位,通常为32位或64位,分别叫做单精度和双精度。单精度扩展(>=43位,不常用)、双精度扩展(>=79位,通常采用80位进行实现),实际上,现在很多计算机都遵循这个标准;
符号位占1bit,指数位E(Exponent)占8bit,其取值范围为0~255(无符号整数)(注意:E为无符号整数,实际数值e=E-127,这一点务必注意),尾数位M占23bit。尾数也叫做有效数字位、系数位、甚至被称作小数;
浮点数的定点化
转成定点数要定义小数需求多少位,整数需求多少位
例:16位的定点数(MAX:16’d32767 MIN:-32768)
3位整数位宽,12位的小数位,最高位的符号位
取低15位,其中第14,13,12位最大能表示7,
小数最大12位能表示的最大精度:1/4096=0.000244140625
(0.000244140625*4095 = 0.999755859375)
极限最大值表示:7.999755859375