除法器原理(定點)

和十進製除法類似,計算 27 除以 5 的過程如下所示:

除法運算過程如下:

  • (1) 取被除數的高幾位數據,位寬和除數相同(實例中是 3bit 數據)。
  • (2) 將被除數高位數據與除數作比較,如果前者不小於後者,則可得到對應位的商為 1,兩者做差得到第一步的餘數;否則得到對應的商為 0,將前者直接作為餘數。
  • (3) 將上一步中的餘數與被除數剩餘最高位 1bit 數據拚接成新的數據,然後再和除數做比較。可以得到新的商和餘數。
  • (4) 重複過程 (3),直到被除數最低位數據也參與計算。

需要說明的是,商的位寬應該與被除數保持一致,因為除數有可能為1。所以上述手動計算除法的實例中,第一步做比較時,應該取數字 27 最高位 1 (3'b001) 與 3'b101 做比較。 根據此計算過程,設計位寬可配置的流水線式除法器,流水延遲周期個數與被除數位寬一致。

除法器設計

單步運算設計

單步除法計算時,單步被除數位寬(信號 dividend)需比原始除數(信號 divisor)位寬多 1bit 才不至於溢出。

為了便於流水,輸出端需要有寄存器來存儲原始的除數(信號 divisor 和 divisor_kp)和被除數信息(信號 dividend_ci 和 dividend_kp)。

單步的運算結果就是得到新的 1bit 商數據(信號 merchant)和餘數(信號 remainder)。

為了得到最後的除法結果,新的 1bit 商數據(信號 merchant)還需要與上一周期的商結果(merchant_ci)進行移位累加。

單步運算單元設計如下(文件名 divider_cell.v):

實例

// parameter M means the actual width of divisor
module    divider_cell
    #(parameter N=5,
      parameter M=3)
    (
      input                     clk,
      input                     rstn,
      input                     en,

      input [M:0]               dividend,
      input [M-1:0]             divisor,
      input [N-M:0]             merchant_ci , //上一級輸出的商
      input [N-M-1:0]           dividend_ci , //原始除數

      output reg [N-M-1:0]      dividend_kp,  //原始被除數信息
      output reg [M-1:0]        divisor_kp,   //原始除數信息
      output reg                rdy ,
      output reg [N-M:0]        merchant ,  //運算單元輸出商
      output reg [M-1:0]        remainder   //運算單元輸出餘數
    );

    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            rdy            <= 'b0 ;
            merchant       <= 'b0 ;
            remainder      <= 'b0 ;
            divisor_kp     <= 'b0 ;
            dividend_kp    <= 'b0 ;
        end
        else if (en) begin
            rdy            <= 1'b1 ;
            divisor_kp     <= divisor ;  //原始除數保持不變
            dividend_kp    <= dividend_ci ;  //原始被除數傳遞
            if (dividend >= {1'b0, divisor}) begin
                merchant    <= (merchant_ci<<1) + 1'b1 ; //商為1
                remainder   <= dividend - {1'b0, divisor} ; //求餘
            end
            else begin
                merchant    <= merchant_ci<<1 ;  //商為0
                remainder   <= dividend ;        //餘數不變
            end
        end // if (en)
        else begin
            rdy            <= 'b0 ;
            merchant       <= 'b0 ;
            remainder      <= 'b0 ;
            divisor_kp     <= 'b0 ;
            dividend_kp    <= 'b0 ;
        end
    end

endmodule

流水級例化

將單步計算的餘數(信號 remainder)和原始被除數(信號 dividend)對應位的 1bit 數據重新拚接,作為新的單步被除數輸入到下一級單步除法計算單元。

其中,被除數、除數、及商的數據信息也要在下一級運算單元中傳遞。

流水級模塊例化完成除法的設計如下(文件名 divider_man.v):

實例

//parameter N means the actual width of dividend
//using 29/5=5...4
module    divider_man
    #(parameter N=5,
      parameter M=3,
      parameter N_ACT = M+N-1)
    (
      input                     clk,
      input                     rstn,

      input                     data_rdy ,  //數據使能
      input [N-1:0]             dividend,   //被除數
      input [M-1:0]             divisor,    //除數

      output                    res_rdy ,
      output [N_ACT-M:0]        merchant ,  //商位寬:N
      output [M-1:0]            remainder ); //最終餘數

    wire [N_ACT-M-1:0]   dividend_t [N_ACT-M:0] ;
    wire [M-1:0]         divisor_t [N_ACT-M:0] ;
    wire [M-1:0]         remainder_t [N_ACT-M:0];
    wire [N_ACT-M:0]     rdy_t ;
    wire [N_ACT-M:0]     merchant_t [N_ACT-M:0] ;

    //初始化首個運算單元
    divider_cell      #(.N(N_ACT), .M(M))
       u_divider_step0
    ( .clk              (clk),
      .rstn             (rstn),
      .en               (data_rdy),
      //用被除數最高位 1bit 數據做第一次單步運算的被除數,高位補0
      .dividend         ({{(M){1'b0}}, dividend[N-1]}),
      .divisor          (divisor),                  
      .merchant_ci      ({(N_ACT-M+1){1'b0}}),   //商初始為0
      .dividend_ci      (dividend[N_ACT-M-1:0]), //原始被除數
      //output
      .dividend_kp      (dividend_t[N_ACT-M]),   //原始被除數信息傳遞
      .divisor_kp       (divisor_t[N_ACT-M]),    //原始除數信息傳遞
      .rdy              (rdy_t[N_ACT-M]),
      .merchant         (merchant_t[N_ACT-M]),   //第一次商結果
      .remainder        (remainder_t[N_ACT-M])   //第一次餘數
      );

    genvar               i ;
    generate
        for(i=1; i<=N_ACT-M; i=i+1) begin: sqrt_stepx
            divider_cell      #(.N(N_ACT), .M(M))
              u_divider_step
              (.clk              (clk),
               .rstn             (rstn),
               .en               (rdy_t[N_ACT-M-i+1]),
               .dividend         ({remainder_t[N_ACT-M-i+1], dividend_t[N_ACT-M-i+1][N_ACT-M-i]}),   //餘數與原始被除數單bit數據拚接
               .divisor          (divisor_t[N_ACT-M-i+1]),
               .merchant_ci      (merchant_t[N_ACT-M-i+1]),
               .dividend_ci      (dividend_t[N_ACT-M-i+1]),
               //output
               .divisor_kp       (divisor_t[N_ACT-M-i]),
               .dividend_kp      (dividend_t[N_ACT-M-i]),
               .rdy              (rdy_t[N_ACT-M-i]),
               .merchant         (merchant_t[N_ACT-M-i]),
               .remainder        (remainder_t[N_ACT-M-i])
              );
        end // block: sqrt_stepx
    endgenerate

    assign res_rdy       = rdy_t[0];
    assign merchant      = merchant_t[0];  //最後一次商結果作為最終的商
    assign remainder     = remainder_t[0]; //最後一次餘數作為最終的餘數

endmodule

testbench

取被除數位寬為 5,除數位寬為 3,testbench 中加入自校驗,描述如下:

實例

`timescale 1ns/1ns

module test ;
    parameter    N = 5 ;
    parameter    M = 3 ;
    reg          clk;
    reg          rstn ;
    reg          data_rdy ;
    reg [N-1:0]  dividend ;
    reg [M-1:0]  divisor ;

    wire         res_rdy ;
    wire [N-1:0] merchant ;
    wire [M-1:0] remainder ;

    //clock
    always begin
        clk = 0 ; #5 ;
        clk = 1 ; #5 ;
    end

    //driver
    initial begin
        rstn      = 1'b0 ;
        #8 ;
        rstn      = 1'b1 ;

        #55 ;
        @(negedge clk ) ;
        data_rdy  = 1'b1 ;
                dividend  = 25;      divisor      = 5;
        #10 ;   dividend  = 16;      divisor      = 3;
        #10 ;   dividend  = 10;      divisor      = 4;
        #10 ;   dividend  = 15;      divisor      = 1;
        repeat(32)    #10   dividend   = dividend + 1 ;
        divisor      = 7;
        repeat(32)    #10   dividend   = dividend + 1 ;
        divisor      = 5;
        repeat(32)    #10   dividend   = dividend + 1 ;
        divisor      = 4;
        repeat(32)    #10   dividend   = dividend + 1 ;
        divisor      = 6;
        repeat(32)    #10   dividend   = dividend + 1 ;
    end

    //對輸入延遲,便於數據結果同周期對比,完成自校驗
    reg  [N-1:0]   dividend_ref [N-1:0];
    reg  [M-1:0]   divisor_ref [N-1:0];
    always @(posedge clk) begin
        dividend_ref[0] <= dividend ;
        divisor_ref[0]  <= divisor ;
    end

    genvar         i ;
    generate
        for(i=1; i<=N-1; i=i+1) begin
            always @(posedge clk) begin
                dividend_ref[i] <= dividend_ref[i-1];
                divisor_ref[i]  <= divisor_ref[i-1];
            end
        end
    endgenerate

    //自校驗
    reg  error_flag ;
    always @(posedge clk) begin
    # 1 ;
        if (merchant * divisor_ref[N-1] + remainder != dividend_ref[N-1] && res_rdy) beginb      //testbench 中可直接用乘號而不考慮運算周期
            error_flag <= 1'b1 ;
        end
        else begin
            error_flag <= 1'b0 ;
        end
    end

    //module instantiation
    divider_man  #(.N(N), .M(M))
    u_divider
     (
      .clk              (clk),
      .rstn             (rstn),
      .data_rdy         (data_rdy),
      .dividend         (dividend),
      .divisor          (divisor),
      .res_rdy          (res_rdy),
      .merchant         (merchant),
      .remainder        (remainder));

   //simulation finish
   initial begin
      forever begin
         #100;
         if ($time >= 10000)  $finish ;
      end
   end

endmodule // test

仿真結果

由圖可知,2 個輸入數據在延遲了和被除數相同位寬的周期數以後,輸出了正確的除法結果。而且可流水式無延遲輸出,符合設計。

源碼下載

Download