zoukankan      html  css  js  c++  java
  • Verilog -- 任意整数除以三求商和余数

    Verilog -- 任意整数除以三求商和余数

    @(verilog)

    1. 问题简介

    问题:输入一个16bit的数,现在要求它除以3得到的商和余数,如何优化?

    来源:@笑着刻印在那一张泛黄 提供,面试真题。

    2. 思路

    一开始联想到之前写过的另一篇博文序列模三检测器,但是这只能解决余数的问题,没法得到商。

    后面的想法是直接使用任意整数除法器来实现,由于除数是3,比较特殊,实际上除3只需要考虑三个序列,也就是11,100,101。因为在手算除三的过程中,如果用二进制算,从高位依次往低位计算,就可以发现这一规律,例如:

         0 0 1 1 0 0 1 1 0  (7d'102)
        ------------------
    11 | 1 0 0 1 1 0 1 0 0  (8d'308)
           1 1
         --------
             1 1
             1 1
        ----------------
               0 1 0 1
                   1 1
              -----------
                   1 0 0
                     1 1
               ----------
                       1 0  (余数)
     
    

    整理一下,就是说把被除数从高位到低位排列,从前到后依次找11,100,101这三个序列,遇到这三中序列商就写1,否则商就写0。做完之后移除这个序列。此外,如果遇到100,将3'b100-2'b11=1'b1 插入原序列最高位;遇到101则插入2'b10,遇到11,不做操作。在实际代码实现的时候,将被除数一位一位从高到低打进来的时候,可以把商每次左移一位,这样做完整个序列以后最开始计算出来的商的位就到了高位上,避免了使用寄存器索引。

    如果写成状态机,则可列出状态转移表:

    StateInput 0 1
    0 0/+0/0 1/+0/1
    1 10/+0/10 0/+1/0
    10 1/+1/1 10/+1/10

    表中的值表示next_state/商操作/余数,其中商操作+1就表示商序列加入一个1,+0就表示加入一个0.
    此外,需要一个计数器来控制状态转移次数,理论上只要计数到被除数的位宽-1即可,但我在实际代码中为了规避最后返回IDLE使得余数不正确的问题,将它计数到了被除数位宽(可能有其他写法)。最后得到商和余数,余数就是计数器结束时的下一个状态表示的二进制数

    3. 代码

    
    `timescale 1ns/1ps
    
    module divide_by_three
    #(
    parameter DATAWIDTH = 16
    )(
    input                                   clk,
    input                                   rst_n,
    input                                   vld_in,
    input             [DATAWIDTH-1:0]       data_in,
    output  reg       [DATAWIDTH-1:0]       quotient, 
    output  reg       [1:0]                 reminder,
    output  reg                             vld_out
    );
    
    
    reg [1:0]current_state;
    reg [1:0]next_state;
    
    reg [$clog2(DATAWIDTH):0] cnt;
    reg [DATAWIDTH-1:0] data_reg;
    
    parameter IDLE = 2'b11;
    
    always @(posedge clk or negedge rst_n) 
      if(!rst_n) current_state <= IDLE;
      else current_state <= next_state;
    
    
    always @(*) 
      case(current_state)
        IDLE:  if(vld_in) next_state = 2'b0;
               else       next_state = IDLE;
    
        2'b00: if (cnt == DATAWIDTH)          next_state = IDLE; // cnt = 16 not 15, for the calc of remainder
               else if(data_reg[DATAWIDTH-1]) next_state = 2'b1;
               else                           next_state = 2'b0;
    
        2'b01: if (cnt == DATAWIDTH)          next_state = IDLE;
               else if(data_reg[DATAWIDTH-1]) next_state = 2'b0;
               else                          next_state = 2'b10;
    
        2'b10: if (cnt == DATAWIDTH)          next_state = IDLE;
               else if(data_reg[DATAWIDTH-1]) next_state = 2'b10;
               else                          next_state = 2'b1;
        default: next_state = IDLE;
    endcase
    
    always @(posedge clk or negedge rst_n) 
      if(!rst_n) begin
        {cnt,data_reg,reminder,quotient,vld_out} <= 0;
      end else begin
        case(current_state)
          IDLE: begin
                  {vld_out,cnt} <= 0;
                  if(vld_in) data_reg <= data_in;
                  else data_reg <= data_reg;
                end
          2'b00,2'b01,2'b10: begin
            if(cnt == DATAWIDTH-1) begin
              cnt <= cnt + 1;        // without this,remainder will be next_state=IDLE=2'b11'
              reminder <= next_state;
              vld_out <= 1;
            end else begin
              cnt <= cnt + 1; 
              vld_out <= 0;
              data_reg <= {data_reg[DATAWIDTH-2:0],1'b0};
            end
            if(data_reg[DATAWIDTH-1]) 
              quotient <= {quotient[DATAWIDTH-2:0],current_state[1]|current_state[0]};
            else 
              quotient <= {quotient[DATAWIDTH-2:0],current_state[1]};
          end
        endcase
      end
    
      
    endmodule
    
    
    
    

    testbench:

    
    `timescale 1ns/1ps
    
    module divide_by_three_tb();
    
    parameter DATAWIDTH = 16;
    
    reg                   clk;
    reg                   rst_n;
    reg                   vld_in;
    reg   [DATAWIDTH-1:0] data_in;
    
    wire   [DATAWIDTH-1:0] quotient;
    wire   [1:0]           reminder;
    wire   [1:0]           vld_out;
    
    reg   [DATAWIDTH-1:0] quotient_ref;
    reg   [1:0]           reminder_ref;
    
    
    always #1 clk = ~clk;
    initial begin
      clk = 0;
      vld_in = 0;
      data_in = 0;
      rst_n = 1;
      #4 rst_n = 0; #2 rst_n = 1;
      
      repeat(10) begin
        @(posedge clk);
        vld_in <= 1;
        data_in = $urandom()%100;
        quotient_ref = data_in/3;
        reminder_ref = data_in%3;
        @(posedge clk);
        vld_in <= 0;
        wait(vld_out==1);
      end
    end
    
    divide_by_three #(  .DATAWIDTH ( DATAWIDTH ))
    U_DIVIDE_BY_THREE_0
    (  .clk      ( clk      ),
       .rst_n    ( rst_n    ),
       .vld_in   ( vld_in   ),
       .data_in  ( data_in  ),
       .quotient ( quotient ),
       .reminder ( reminder ),
       .vld_out  ( vld_out  ));
    
    
    initial begin
      $fsdbDumpvars();
      $fsdbDumpMDA();
      $dumpvars();
      #1000 $finish;
    end
    
    endmodule
    

    仿真波形

    对于16位的被除数,需要18个周期得到计算结果。

    相比于直接使用任意整数除法器,节省了减法操作,通过找规律化简了状态机。
    其实对于输入也可以是之前的序列输入的形式,只需要修改输入的形式,调整下内部的数据存储即可。并且这种写法可以实现每输入一个bit,可以直接同时输出当前序列的商和余数

  • 相关阅读:
    openstack 相关服务常用命令整理
    openstack(Pike 版)集群部署(六)--- Horizon 部署
    node express4.x 的安装
    jquery mobile 笔记
    multi-node和generic-pool两大利器
    ADT eclipse的几个快捷键
    安卓入门笔记
    HTML颜色代码表
    [转载]Delphi常用类型及定义单元
    简化连接Buffer对象的过程
  • 原文地址:https://www.cnblogs.com/lyc-seu/p/12917765.html
Copyright © 2011-2022 走看看