• Verilog -- 求两数最大公因数(gcd)和最小公倍数(lcm)


    Verilog -- 求两数最大公因数和最小公倍数

    @(verilog)

    1. 原理简介

    1.1 辗转相除法求公因数

    求最大公因数的常用算法为辗转相除法,又被称为欧几里德(Euclidean)算法, 是求最大公约数的算法。辗转相除法首次出现于欧几里得的《几何原本》(第VII卷,命题i和ii)中,而在中国则可以追溯至东汉出现的《九章算术》。

    两个数的最大公约数是指能同时整除它们的最大正整数。辗转相除法的基本原理是:两个数的最大公约数等于它们中较小的数和两数之差的最大公约数
    例如,252和105的最大公约数是21(252 = 21 × 12;105 = 21 × 5);因为252 − 105 = 147,所以147和105的最大公约数也是21。在这个过程中,较大的数缩小了,所以继续进行同样的计算可以不断缩小这两个数直至其中一个变成零。这时,所剩下的还没有变成零的数就是两数的最大公约数。

    可以简单证明一下
    (A)(B)的最大公因数为(gcd=X),且(A>B),可以将他们表示为:

    [A = mX, min Z \ B = nX, nin Z ]

    其中,(m,n)互质。计(A,B)的差为(C)

    [C = (m-n)X ]

    首先,可以证明,(m-n)(m,n)都互质,因为(m-n)如果跟(n)有公因数,则(m,n)必也有公因式,违反了条件。因此互质数的和差与原来两数都互质。
    其次,由于(m-n)(n)互质,因此(B,C)的最大公因式就是(X),因为没办法再从(m-n)(n)中提出一个因式放到(X)里。
    所以(A,B)的最大公约数就是(B,C)的最大公约数。
    由此,就可以将(A,B)“辗转”地相互做差,这样不断地进行,最后一定会出现要做差的两个数都为(X)的情形,因为做差做到最后,((m'-n'))(n')一定会变成最小的互质数对((1,1)),也就是经过若干次辗转后较小的那个数与他们的差相等,此时就得到了最大公约数。

    事实上,上面的是辗转相减法,为了加速相减的过程,实际上可以使用除法加速辗转的过程。(除法就是若干次减法)

    1.2 最小公倍数求法

    最小公倍数实际上等于两数之积除以他们的最大公约数。为什么呢?还是可以从上面的假设进行推导:
    首先我们知道,互质数的最小公倍数一定是他们的积。而要求(A,B)的最小公倍数,因为它们已经有了最大的公约数(X),所以其实只要求互质的两个数(m,n)的最小公倍数,所以(A,B)的最小公倍数(lcm=mnX=AB/X)

    2. 代码实现

    
    module lcm_gcd
    #(
    parameter DATAWIDTH=8
    )
    (
    input   [DATAWIDTH-1:0]       A,
    input   [DATAWIDTH-1:0]       B,
    input                         en,
    input                         clk,
    input                         rstn,
    
    output  wire [DATAWIDTH*2-1:0]lcm_out,
    output  wire [DATAWIDTH-1:0]  gcd_out,
    output  wire                  vld_out,
    output  wire                  ready
    );
    reg [DATAWIDTH-1:0]     gcd,a_buf,b_buf;
    reg [DATAWIDTH*2-1:0]   lcm,mul_buf;
    reg [1:0]               current_state,next_state;
    reg done;
    parameter IDLE=2'b00,
              SUB  =2'b01,
    		  DONE  =2'b10;
            
    assign ready=(current_state==IDLE)?1'b1:1'b0;
    
    always@(posedge clk or negedge rstn)
      if(!rstn) current_state <= IDLE;
      else current_state <= next_state;
    
    always@(*) begin
      next_state = 2'bx;
      case(current_state)
        IDLE:if(en)  next_state <= SUB;
             else    next_state <= IDLE;	
        SUB:  if(a_buf!=b_buf) next_state <= SUB;
             else             next_state <= DONE;
        DONE:  next_state <= IDLE;
      endcase
    end
    
    
      
    always@(posedge clk or negedge rstn)
         if(!rstn)begin
    		gcd    <= 0;
            lcm    <= 0;
    		a_buf  <= 0;
    		b_buf  <= 0;	
            mul_buf<= 0;	
            done   <= 0;    
          end
         else begin
    	    case(current_state)
            IDLE:begin
                if(en)begin
    		       a_buf   <= A;
    			   b_buf   <= B;
                   mul_buf <= A*B;              
                 end
                 done <= 0;
               end
    	    SUB:if(a_buf != b_buf)begin
    		     if(a_buf > b_buf)begin
    			    a_buf <= a_buf-b_buf;
    				b_buf <= b_buf;
    			 end
    			 else begin
    			    b_buf <= b_buf-a_buf;
    				a_buf <= a_buf;			 
                  end
               end		
    		DONE:begin
                gcd <= b_buf;
                lcm <= mul_buf / b_buf; 
                done <= 1;
              end
    
    		default:next_state<= IDLE;
    	    endcase
          end	 
    
    assign gcd_out = gcd;
    assign lcm_out = lcm;
    assign vld_out = done;
    
    endmodule
    

    testbench:

    
    `timescale 1ns/1ps
    
    
    
    module lcm_gcd_tb();
    
    parameter DATAWIDTH = 8;
    reg   [DATAWIDTH-1:0]       A;
    reg   [DATAWIDTH-1:0]       B;
    reg                         en;
    reg                         clk;
    reg                         rstn;
    
    wire [DATAWIDTH*2-1:0]      lcm_out;
    wire [DATAWIDTH-1:0]        gcd_out;
    wire                        vld_out;
    wire                        ready;
    
    
    reg [DATAWIDTH*2-1:0]      lcm_ref;
    reg [DATAWIDTH-1:0]        gcd_ref;
    
    always #1 clk = ~clk;
    
    function automatic integer gcd(input integer A,input integer B);
        if(A>B) 
          return gcd(B,A-B);
        else if (A<B) 
          return gcd(A,B-A);
        else 
          return A;
    endfunction
    
    
    integer i;
    initial begin
      clk = 1;
      rstn = 1;
      en = 0;
      #2 rstn = 0; #2 rstn = 1;
      repeat(2) @(posedge clk);
    
      for(i=0;i<20;i=i+1) begin
        en <= 1;
        A <= $urandom()%20+1; // no zero input
        B <= $urandom()%20+1;
        gcd_ref = gcd(A,B);
        lcm_ref = A*B/gcd_ref;
        @(posedge vld_out);
       end
    
    end
    
    
    initial begin
        $fsdbDumpvars();
        $fsdbDumpMDA();
        $dumpvars();
        #1000 $finish;
      end
    
    
    lcm_gcd #(
        .DATAWIDTH                      ( DATAWIDTH                     ))
    U_LCM_GCD_0(
        .A                              ( A                             ),
        .B                              ( B                             ),
        .en                             ( en                            ),
        .clk                            ( clk                           ),
        .rstn                           ( rstn                          ),
        .lcm_out                        ( lcm_out                       ),
        .gcd_out                        ( gcd_out                       ),
        .vld_out                        ( vld_out                       ),
        .ready                          ( ready                         )
    );
    
    
    
    endmodule
    

    上面代码里用到的除法如果无法综合或者延时过大可以考虑使用时序逻辑实现,此外,该代码无法处理输入为0的情况,而且其实输入为0时求公因数也无意义,如有需要可自行修改。(在最开始判断一下是否为0应该就行,懒得改了)
    下面是仿真波形

  • 相关阅读:
    HTTP/2之服务器推送(Server Push)最佳实践
    相似人群画像算法
    Linux也有后悔药,五种方案快速恢复你的系统
    IPv6原理、应用与实践
    护航者,腾讯云: 2017年度游戏行业DDoS态势报告—回溯与前瞻
    放大倍数超5万倍的Memcached DDoS反射攻击,怎么破?
    Unity引擎与C#脚本简介
    腾讯云Redis混合存储版重磅推出,万字长文助你破解缓存难题!
    拒绝平庸,以程序员的名义定义新桌面!
    腾讯云EMR大数据实时OLAP分析案例解析
  • 原文地址:https://www.cnblogs.com/lyc-seu/p/12867336.html
Copyright © 2020-2023  润新知