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,不做操作。在實際代碼實現的時候,將被除數一位一位從高到低打進來的時候,可以把商每次左移一位,這樣做完整個序列以后最開始計算出來的商的位就到了高位上,避免了使用寄存器索引。

如果寫成狀態機,則可列出狀態轉移表:

State\Input 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,可以直接同時輸出當前序列的商和余數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM