当前位置: 首页 > 教程 > Verilog >

7.1 Verilog 除法器设计

精华
小牛编辑
194浏览
2023-03-14

除法器原理(定点)

和十进制除法类似,计算 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