DSP

Verilog基础知识(定点小数运算)

2019-07-13 15:50发布

https://blog.csdn.net/maxwell2ic/article/details/81076475   需求说明:FPGA视频处理算法基本知识       第一部分:FPGA内部计算小数       第二部分:FPGA小数乘法       第三部分:我的整理及应用   第一部分:FPGA内部计算小数 来自:http://www.cnblogs.com/woshitianma/archive/2013/05/19/3087258.html     谓定点小数,就是小数点的位置是固定的。我们是要用整数来表示定点小数,由于小数点的位置是固定的,所以就没有必要储存它(如果储存了小数点的位置,那就是浮点数了)。既然没有储存小数点的位置,那么计算机当然就不知道小数点的位置,所以这个小数点的位置是我们写程序的人自己需要牢记的。   先以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 由以上公式我们可以很快得出定点小数的+-*/算法: 假设q1,q2,q3表达的值分别为x1,x2,x3 q3 = q1 + q2   若 x3 = x1 + x2 q3 = q1 - q2   若 x3 = x1 - x2 q3 = q1 * q2 / 2^n若 x3 = x1 * x2 q3 = q1 * 2^n / q2若 x3 = x1 / x2 我们看到加减法和一般的整数运算相同,而乘除法的时候,为了使得结果的小数点位不移动,对数值进行了移动。 用c语言来写定点小数的乘法就是: short q1,q2,q3; .... q3=((long q1) * (long q2)) >> 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,对于一般的计算已经足够精确了。  
第二部分:FPGA小数乘法 来自:http://blog.sina.com.cn/s/blog_be7040250101kcgn.html
经常有人问, fpga里小数乘法怎么搞?

如果你乐意, 按照IEEE754标准做”浮点”型运算的ip当然最好(虽然面积上不太好).
不过,很多情况下,没有这个必要.

一般我们就用”定点”了.
你得自己”定个点”, 比如用16位, 分成8位整数8位小数(后面记为”(8.8)”), 即”定点”在第8位.
那么:
1 -> 16’h0100;
1.5 -> 1.5*256 = 384 -&t ; 16’h0180;
-1.5 -> -1.5*256 + 65536(补码) -> 16’hFE80(其实就是-16’sh0180, 让综合器给我们算补码去~~);
1.164 -> 1.164*256 = 298 = 16’h012A;

所以 signed input [15:0] a (也是”8整.8小”)和 1.164相乘给 signed output [15:0] mul (也是”8整.8小”), 直接写:
assign mul = (a * 16’sh012A) >>>8;
就行了, 当然, 你的fpga里有dsp block最好, 不然也要几百个LE的.

因为 (8.8) 乘  (8.8) 得到  (16.16), 为了恢复成 (8.8), 所以帯符号右移8位即可.
把低8位小数舍掉, 高8位整数也丢了, 所以你得保证你的16位(8.8)的”定点小数”乘积不能超过范围, 多数数字信号处理系数都是区间[-1.0, 1.0]的,多半不存在问题, 积分什么的, 还有其它可能有问题的自己想清楚就行, 当然你要保留16位整.16位小也可以~~~

总结:
module fixpmul

(

    parameter IW = 8,
    parameter FW = 8 
)(
    input signed [IW+FW-1 : 0] a,
    input signed [IW+FW-1 : 0] b,
    output signed [IW+FW-1 : 0] o
);
    (* multstyle = “dsp” *) wire signed [IW*2+FW*2-1 : 0] long;
    assign long = a * b;
    assign o = long >>> FW;
endmodule

PS: 作为一个完美主义者的想法: 以8位整型为例, 其实 8位有符号 乘 8位有符号得到的 16位有符号, 中的第14位(权2^14的位, 符号位右边的位), 很讨厌, 它只有在 -128 * -128时才等于1, 其它65535种情况, 全是0, 很浪费.
所以我们一般在数字信号处理系统中, 永远把[-1,1]映射到[-127,127], 这样那个讨厌的第14位永远用不到, 然后就可以: wire signed [15:0] mul = a * b; wire signed [14:0] out = {wire[15], wire[13:0]}, 8位有符号 乘 8位有符号 得到 15位有符号, 节约一位.

第三部分:我的整理及应用   计算内容(8位整数8位小数) 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、如果想降低取整导致的误差,可以加大位宽。