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, m\in Z \\ B = nX, n\in 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


	    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應該就行,懶得改了)
下面是仿真波形


免責聲明!

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



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