天天看點

Verilog基礎知識2(FPGA小數運算處理--定點小數)(

需求說明: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 -> 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位有符号, 節約一位.

第三部分:我的整理及應用

計算内容

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、如果想降低取整導緻的誤差,可以加大位寬。

來自:時間的詩

</div>
            </div>