天天看點

Verilog實作二進制有符号定點數的乘法運算

Verilog實作有符号數乘法運算

    • 二進制運算原理
    • 1、無符号二進制數(unsigned)乘法運算
      • 1.1 無符号數正常運算
      • 1.2 無符号數乘法補位運算
    • 2、有符号二進制數(signed)乘法運算
      • 2.1 有符号定點數乘法運算
      • 2.2 有符号定點數乘法補位運算
      • 原理總結
    • Verilog代碼實作
    • 總結

二進制運算原理

總結最近FPGA的學習,使我明白了一件事:在Verilog程式的編寫中,“位”(bit)這一概念,對于程式編寫至關重要,Verilog代碼中,資料運算均以位來操作,是以我覺得有必要認真研習一下二進制運算的基本過程和運算方法。

有符号二進制數的表示一般以二進制補碼(two’s-complement)的形式來表示,有助于我們了解和編寫底層二進制運算的程式。相信這對我們單片機和FPGA的學習将收益匪淺。

1、無符号二進制數(unsigned)乘法運算

1.1 無符号數正常運算

對于無符号數的乘法運算相對簡單,直接通過移位後相加得到。這裡我随意舉個無符号數相乘的例子來說明運算法則,比如(1001)2 和(0101)2 相乘可以寫成如下形式:

Verilog實作二進制有符号定點數的乘法運算

從上式可以看出,該運算和我們十進制的運算相差無幾,在二進制運算中乘數(0101)從低位開始與被乘數(1001)相乘。在乘數為1時就相當于對被乘數進行相應的移位操作,乘數使用的是第n位,則被乘數相應的右移(n-1)位,最後對中間結果進行相加,得到運算結果。

原理相當簡單,但是在verilog代碼編寫中,在規定了乘數與被乘數的資料位寬後(如例子的4bits資料),結果資料位寬範圍:4bits~7bits(4+4-1),為防止運算結果溢出則需要預先給結果資料給定7bits的位寬。

根據最近的Verilog的學習,我總結了如下表的資料位寬配置設定規律(當然是針對無符号數而言)

資料名 資料位寬
乘數 a
被乘數 b
乘積 a+b-1

1.2 無符号數乘法補位運算

同時,在最近《深入了解計算機系統》一書的閱讀中,我了解了另一種無符号二進制數的運算方法。是一種補位的方式,在已知乘數和被乘數的位寬後,對其進行相同于乘積位寬的補位,高位補0,一直到所定義的乘積的最高位,這裡我定義乘積資料的位寬為8bits,對于高于所定義位寬的資料進行截斷并舍棄。形式如下圖所示:

Verilog實作二進制有符号定點數的乘法運算

當然補位方法是一種通用與有符号和無符号數的運算方法,對于無符号數的運算優勢可能不突出,但是對于有符号的運算優勢則極其突出。

2、有符号二進制數(signed)乘法運算

2.1 有符号定點數乘法運算

有符号二進制數則和第1節中的無符号數運算過程有些差異,在之前的學習中我參考了一篇部落格園中搬運翻譯的部落格:

符号定點二進制小數(Qn format)乘法原理:https://www.cnblogs.com/Yuya-memeda/p/12708868.html

原部落格(Fixed-Point Representation: The Q Format and Addition Examples):https://www.allaboutcircuits.com/technical-articles/fixed-point-representation-the-q-format-and-addition-examples/

運算過程分成了4種:正數 * 正數;正數 * 負數; 負數 * 正數 以及 負數 * 負數。不同的情況對應不同的運算法則,感興趣可以去看看過程,相信會有幫助。

在這裡我就不做重複的勞動了,我這裡主要要記錄的是第二種方法。

2.2 有符号定點數乘法補位運算

該方法相比2.1節中對有符号定點數,需要分四種情況進行運算的方法會相對簡單得多。

此方法和1.2節中的補位運算相同,對于有符号數的乘法運算,對其乘數和被乘數進行位寬擴充。

這裡我總結了一下,擴充後的位數寬度為正常乘積位數 + 符号位,即:

擴 展 後 位 寬 = 被 乘 數 位 數 a + 乘 數 位 寬 b − 1 + 符 号 位 寬 ( 1 b i t ) = a + b 擴充後位寬=被乘數位數a + 乘數位寬b -1 + 符号位寬(1bit)=a+b 擴充後位寬=被乘數位數a+乘數位寬b−1+符号位寬(1bit)=a+b

擴充時,對高位進行補充的方式和無符号數的運算不一樣,需要依據符号進行補充,符号位為‘1’則高位補‘1’,符号位為‘0’則高位補‘0’。同樣的以(1001)2和(0101)2 為例。乘數與被乘數均是4bits,擴充後不至于溢出的位數為4+4=8bits

有符号數是以補碼(正數的補碼為其本身,負數的補碼為其反碼+1)的形式來表示的,則該資料的結果為:

補 碼 : 1001 = > 反 碼 : 0110 = > 原 碼 : 0111 ( − 7 ) 補碼:1001 =>反碼:0110=>原碼:0111 (-7) 補碼:1001=>反碼:0110=>原碼:0111(−7)

補 碼 : 0101 = > 原 碼 : 0101 ( 5 ) 補碼:0101 =>原碼:0101 (5) 補碼:0101=>原碼:0101(5)

乘 法 運 算 : − 7 ∗ 5 = − 35 乘法運算:-7*5 = -35 乘法運算:−7∗5=−35

應用補位截斷的方法進行有符号定點數乘法如下圖所示

Verilog實作二進制有符号定點數的乘法運算

高于擴充位數的資料進行截斷舍棄,最後結果如下

補 碼 : 11011101 = > 反 碼 : 00100010 = > 原 碼 : 00100011 補碼:11011101=>反碼:00100010=>原碼:00100011 補碼:11011101=>反碼:00100010=>原碼:00100011

結 果 為 : ( − 1 ) 1 ∗ ( 1 ∗ 2 5 + 1 ∗ 2 1 + 1 ∗ 2 0 ) = − 35 結果為:(-1)^1*(1*2^5+1*2^1+1*2^0)=-35 結果為:(−1)1∗(1∗25+1∗21+1∗20)=−35

和我們乘法運算的結果相同。接下來我們再來驗證 正數 * 正數,正數 * 負數, 負數 * 正數,負數 * 負數。驗證方法的通用性,無需将資料分為政府4種情況進行運算,減少代碼量。

正數 * 正數的情況在1.2節中已經驗證過了,它是該補位截位運算法的特殊情況,負數 * 正數的情況已在上面驗證,這裡不贅述。

下面我們驗證正數乘負數和負數乘負數。(下面的式子中不再寫被截斷舍去的資料位)

  1. 正數乘負數:
    Verilog實作二進制有符号定點數的乘法運算
  2. 負數乘負數

    最後我這裡使用定點數進行運算,來驗證其對于小數同樣具有通用性,可用于定點數運算

11.01 ( B ) = 00.11 ( 原 碼 ) = − 1 ∗ ( 1 / 2 1 + 1 / 2 2 ) = − 0.75 11.01(B) =00.11(原碼)=-1*(1/2^1 + 1/2^2) = -0.75 11.01(B)=00.11(原碼)=−1∗(1/21+1/22)=−0.75

10.11 ( B ) = 01.01 ( 原 碼 ) = − 1 ∗ ( 1 ∗ 2 0 + 1 / 2 2 ) = − 1.25 10.11(B) =01.01(原碼)=-1*(1*2^0+ 1/2^2) = -1.25 10.11(B)=01.01(原碼)=−1∗(1∗20+1/22)=−1.25

結果為:

0000.1111 ( B ) = 0000.1111 ( 原 碼 ) = + 1 ∗ ( 1 / 2 1 + 1 / 2 2 + 1 / 2 3 + 1 / 2 4 ) = 0.9375 0000.1111(B) =0000.1111(原碼)=+1*(1/2^1 + 1/2^2+ 1/2^3+ 1/2^4) = 0.9375 0000.1111(B)=0000.1111(原碼)=+1∗(1/21+1/22+1/23+1/24)=0.9375

進而驗證了該方法的正确性。

Verilog實作二進制有符号定點數的乘法運算

原理總結

到此就完成了對定點數的乘法運算,相比于上面文獻的對于有符号定點數需要分符号進行分類運算,此補位截位的操作方法更加通用。詳細的可以參考《深入了解計算機系統》的内容。

這裡還有一篇部落格可以參考,其中有一些簡單的過程。

有符号二進制數的乘法https://blog.csdn.net/wordwarwordwar/article/details/54172777

Verilog代碼實作

原理過程在上面已經闡述完了,這裡不過多贅述,直接放上Verilog代碼。Vivado中編寫的程式和仿真結果如下:(這段verilog代碼裡的for循環用得不太熟練,如有更好的方式,還請指教)

`timescale 1ns / 1ps
///
//   32bit,16位小數的定點數乘法
//

module Fix32_16mult(
    input    clk,
    input    rst_n,
    input    [32-1:0]  a,
    input    [32-1:0]  b,
    output   [64-1:0]  ret
    );
    
    //-----------------------------------------------
    //  擴充a,b
    reg   [64-1:0]  a_r, b_r;
    always @ (posedge clk or negedge rst_n) begin
        if(rst_n == 1'b0) begin 
            a_r <= 64'h0000_0000_0000_0000;
            b_r <= 64'h0000_0000_0000_0000;
        end
        else begin 
            a_r <= {{32{a[31]}}, a[31:0]};
            b_r <= {{32{b[31]}}, b[31:0]};
        end
    end
    
    //--------------------------------------------------
    //  依位移位
    reg [64-1:0] ret_r [64-1:0];
    integer i, j, k;
    always @ (posedge clk or negedge rst_n) 
        if(rst_n == 1'b0)
            for(i = 0; i < 64; i = i+1)
                ret_r[i] <= 64'h0000_0000_0000_0000;
        else 
            for(k = 0; k<64; k = k + 1)
                if(b_r[k] == 1)
                    ret_r[k] <= (a_r << k);
                else
                    ret_r[k] <=  64'h0000_0000_0000_0000;
                    
    
    //---------------------------------------------------
    // 對結果求和 截位
    reg [64-1:0] sum;
    always @ (posedge clk or negedge rst_n) 
        if(rst_n == 1'b0)
            sum <= 64'h0000_0000_0000_0000;
        else begin 
                sum <= ret_r[0]+ret_r[1]+ret_r[2]+ret_r[3]+ret_r[4]+ret_r[5]+ret_r[6]+ret_r[7]+ret_r[8]+ret_r[9]+ret_r[10]+ret_r[11]+ret_r[12]+ret_r[13]+ret_r[14]+ret_r[15]+ret_r[16]+ret_r[17]+ret_r[18]+ret_r[19]+ret_r[20]+ret_r[21]+ret_r[22]+ret_r[23]+ret_r[24]+ret_r[25]+ret_r[26]+ret_r[27]+ret_r[28]+ret_r[29]+ret_r[30]+ret_r[31]
                +ret_r[32]+ret_r[33]+ret_r[34]+ret_r[35]+ret_r[36]+ret_r[37]+ret_r[38]+ret_r[39]+ret_r[40]+ret_r[41]+ret_r[42]+ret_r[43]+ret_r[44]+ret_r[45]+ret_r[46]+ret_r[47]+ret_r[48]+ret_r[49]+ret_r[50]+ret_r[51]+ret_r[52]+ret_r[53]+ret_r[54]+ret_r[55]+ret_r[56]+ret_r[57]+ret_r[58]+ret_r[59]+ret_r[60]+ret_r[61]+ret_r[62]+ret_r[63];    
        end 
        
    assign  ret = sum;
    
    
    
endmodule

           

資料為32bits定點數,16bits為小數位。

以+810 (0000_0008)16 和 -810 (ffff_fff8)16 以及 -1110 (ffff_fff5)16 乘 -1010 (ffff_fff6)16為例,編寫測試檔案進行所說的32位定點數乘法子產品測試,仿真結果如下圖所示。

仿真中,講述調整為real類型,選擇fix,signed有符号定點數類型,小數位數選擇16bits。

Verilog實作二進制有符号定點數的乘法運算

仿真結果:

1 、 ( 0000.0008 ) H = + 0.0001220703125 ; 1、(0000.0008)H = +0.0001220703125; 1、(0000.0008)H=+0.0001220703125;

( f f f f . f f f 8 ) H = − 0.0001220703125 ; (ffff.fff8)H =-0.0001220703125; (ffff.fff8)H=−0.0001220703125;

結 果 : ( f f f f , f f f f , f f f f , f f c 0 ) H = − 1.490116119384765625 e − 08 結果:(ffff,ffff,ffff,ffc0)H=-1.490116119384765625e-08 結果:(ffff,ffff,ffff,ffc0)H=−1.490116119384765625e−08

結 果 正 确 結果正确 結果正确

2 、 ( f f f f , f f f 5 ) H = − 0.0001678466796875 ; ( f f f f , f f f 6 ) H = − 0.000152587890625 ; 2、(ffff,fff5)H = -0.0001678466796875; (ffff,fff6)H =-0.000152587890625; 2、(ffff,fff5)H=−0.0001678466796875;(ffff,fff6)H=−0.000152587890625;

結 果 : ( 0000 , 0000 , 0000 , 006 e ) H = 2.56113708019257 e − 08 結果:(0000,0000,0000,006e)H=2.56113708019257e-08 結果:(0000,0000,0000,006e)H=2.56113708019257e−08

結 果 正 确 結果正确 結果正确

同樣把資料作為32位有符号定點數,無小數位,結果同樣正确,不多驗證,直接對Vivado中Real值小數位進行設定即可。

總結

上述仿真中就可以驗證有符号二進制定點數的乘法器設計完成,計算機二進制乘法,總結起來主要包括三個過程,1)對乘數和被乘數進行位數擴充;2)依據乘數各位上的值進行對應的移位操作;3)對所有移位的結果進行累加,得到乘法結果。

如有錯誤的地方還望多多指正

學習不總結等于沒學,多做總結,共勉

繼續閱讀