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

Verilog 求两数的最大公因数和最小公倍数[笔试题]

由于 ain*bin = lcm*gcd 所以可以先通过 更相减损法 求得最大公约数(GCD)
再通过 lcm=ain*bin/gcd 得到最小公倍数(LCM)
举例:求 LCM(3,7)

  1. 先求 GCD
  2. 7-3=4
  3. 4-3=1
  4. 3-1=2
  5. 2-1=1,1-1=0
  6. 所以 GCD(3,7)=1
  7. 因此 LCM(3,7)=3*7/GCD(3,7)=21

代码如下

module get_lcm #(parameter W = 8)(
    input   wire            clk,
    input   wire            rst,

    input   wire            ena,
    input   wire   [W-1:0]  ain,
    input   wire   [W-1:0]  bin,

    output  wire   [W-1:0]  gcd,
    output  reg  [2*W-1:0]  lcm,
    output  wire            val
);
// 由于 ain*bin = lcm*gcd 所以可以先通过 更相减损法 求得最大公约数(GCD)
// 再通过 lcm=ain*bin/gcd 得到最小公倍数(LCM)
// 举例:求 LCM(3,7)
// 1. 先求 GCD
// 2. 7-3=4
// 3. 4-3=1
// 4. 3-1=2
// 5. 2-1=1,1-1=0
// 6.所以 GCD(3,7)=1
// 7.因此 LCM(3,7)=3*7/GCD(3,7)=21
reg [2  :0] st;
reg [W-1:0] ain_b;
reg [W-1:0] bin_b;
reg [W-1:0] gcd_b;
reg [2*W-1:0] mul_b;
wire [2*W-1:0] quotient;
wire [2*W-1:0]   remainder;
reg  ena_div;
wire val_div;
assign gcd = gcd_b;
assign val = st == 3;
always @(posedge clk or posedge rst) begin
    if (rst) begin
        st      <= 'd0;
        ain_b   <= 'd0;
        bin_b   <= 'd0;
        gcd_b   <= 'd0;
        mul_b   <= 'd0;
        ena_div <= 'd0;
        lcm     <= 'd0;
    end else begin
        case (st)
            0: begin
                if (ena) begin
                    ain_b <= ain;
                    bin_b <= bin;    
                    mul_b <= ain*bin;
                    st    <= 1;                    
                end
            end
            1:begin
                if(!(ain_b&&bin_b))begin
                    if (ain_b==0) 
                        gcd_b <= bin_b;
                    else
                        gcd_b <= ain_b;
                    st    <= 2;
                    ena_div<=1'd1;
                end else if (ain_b!=bin_b) begin
                    if (ain_b>bin_b) begin
                        ain_b <= ain_b - bin_b;
                        bin_b <= bin_b;
                    end else begin
                        bin_b <= bin_b - ain_b;
                        ain_b <= ain_b;
                    end
                    st <= st;
                end else  begin  // 得到最大公约数
                    gcd_b <= ain_b;
                    st    <= 2;
                    ena_div<=1'd1;
                end
            end
            2:begin // 开始进行正整数除法
                ena_div<=1'd0;
                if (val_div)begin
                    lcm <= quotient;
                    st  <= 3;
                end
            end
            3:begin
                mul_b   <= 'd0;
                st      <= 0;
            end
            default:st  <= 'd0;
        endcase
    end
end
div_uint #(2*W)div_uint(  
    .clk (clk),
    .rst (rst),
    .ena (ena_div),
    .ain (mul_b), // ain/bin 正整数除法
    .bin ({{(W){1'd0}},gcd_b}),
    .dout({remainder,quotient}),// {余高W位,商低W位}
    .dval(val_div)
);
endmodule

除法器使用的是最简单的手工除法方式, 参见无符号整数除法器
除法流程:

在这里插入图片描述

module div_uint #(parameter W = 8)(  
    input   wire          clk,
    input   wire          rst,

    input   wire          ena,
    input   wire [W-1:0]  ain, // ain/bin 正整数除法
    input   wire [W-1:0]  bin,

    output  reg  [2*W-1:0]dout,// {商高W位,余低W位}
    output  wire          dval
);
reg [2    :0]st;
reg [W*2-1:0]abuf;
reg [W*2-1:0]bbuf;
reg [W-1  :0]fin;
assign dval = st == 4;
always @(posedge clk or posedge rst) begin
    if (rst) begin
        st      <= 'd0;
        abuf    <= 'd0;
        bbuf    <= 'd0;
        fin     <= 'd0;
        dout    <= 'd0;
    end else begin
        case (st)
            0: begin
                if (ena) begin // 1 位扩展
                    abuf   <=  {{W{1'd0}},ain};
                    bbuf   <=  {bin,{W{1'd0}}};
                    st     <=  2;
                end  
            end 
            1:begin
                if (abuf>=bbuf) begin // 3 比较 a >= b
                    abuf <= abuf - bbuf + 1'd1;
                    if (&fin) begin
                        st  <= 3;
                        fin <= 'd0;
                    end else begin
                        st  <= 2;
                    end
                end else begin
                    if (&fin) begin //检查是否结束
                        st  <= 3;
                        fin <= 'd0;
                    end else begin
                        st  <= 2;
                    end
                end
            end
            2:begin // 2 Shifft
                abuf <= {abuf,1'd0};
                fin  <= {fin ,1'd1};
                st   <= 1;       
            end
            3:begin // Finish
                st   <= 4;    
                dout <=abuf;  
            end
            4:begin // Finish Dumpy
                st   <= 0;    
            end
            default:st   <= 0;    
        endcase
    end
end
endmodule
//verilog testbench
`timescale  1ns / 1ps
module tb_get_lcm;
// get_lcm Parameters
parameter PERIOD  = 10;
parameter W  = 16;
parameter WM  = 2**W;
parameter TIMES = 1000;
integer seed=100;
// get_lcm Inputs
reg   clk                                  = 0 ;
reg   rst                                  = 1 ;
reg   ena                                  = 0 ;
reg   [W-1:0]  ain                         = 0 ;
reg   [W-1:0]  bin                         = 0 ;
// get_lcm Outputs
wire  [W-1:0]  gcd                         ;
wire  [2*W-1:0]  lcm                       ;
wire  val                                  ;
initial
begin
    forever #(PERIOD/2)  clk=~clk;
end
initial
begin
    #(PERIOD*2) rst  =  0;
end
get_lcm#(W)  u_get_lcm (
    .clk                     ( clk            ),
    .rst                     ( rst            ),
    .ena                     ( ena            ),
    .ain                     ( ain  [W-1:0]   ),
    .bin                     ( bin  [W-1:0]   ),
    .gcd                     ( gcd  [W-1:0]   ),
    .lcm                     ( lcm  [2*W-1:0] ),
    .val                     ( val            )
);
reg [15:0]time_out=0 ;
task automatic get;
    input integer a;
    input integer b;
    begin
        $display("%0d",a);
        $display("%0d",b);
        @(posedge clk)
        #0 ena=1;
        ain = a;//<-ain->
        bin = b;//<-bin->
        @(posedge clk)
        #0 ena=0;
        fork
            begin
                @(posedge val)
                $display("%0d",gcd);
                $display("%0d",lcm);                  
            end
            begin
                @(posedge clk)
                if(&time_out)begin
                    time_out=0;
                    $display("Error time_out");
                    $finish;
                end else begin
                    time_out=time_out+1;
                end
            end
        join

    end
endtask 
integer i;
initial
begin
    $dumpfile("wave.vcd");
    $dumpvars(0,tb_get_lcm);
    $display("%0d",TIMES);
    #(PERIOD*100)
    for(i=0;i<TIMES;i=i+1)begin
        get({$random(seed)}%WM,{$random(seed)}%WM);
        #(PERIOD*5);
    end
    #(PERIOD*100)
    $finish;

end
endmodule

仿真整个工程需要执行以下 python testbench

import os
import math
import re
os.system("iverilog -y. -o tb_get_lcm.vvp tb_get_lcm.v")
fp1=os.popen("vvp tb_get_lcm.vvp",'r',9000)
fp1.readline()
times = fp1.readline()
times = int(re.sub(r'\s+','',times))
print(times)
for i in range(times):
    ret = [fp1.readline(),fp1.readline(),fp1.readline(),fp1.readline()]
    for i in range(4):
        ret[i] = re.sub(r'\s+','',ret[i])
        ret[i] = int(ret[i])
    res0 = ret[2] == math.gcd(ret[0],ret[1])
    res1 = ret[3] == (ret[0]*ret[1]//math.gcd(ret[0],ret[1]))
    if res0 and res1:
        print('[%0d,%0d]gcd:%d lcm:%d'%(ret[0],ret[1],ret[2],ret[3])+' Pass')
    else:
        print('Error')
        exit()
print('Success')
if not os.path.exists("wave.gtkw"):
    os.system("gtkwave wave.vcd")
else:
    os.system("gtkwave wave.gtkw")
os.system('del wave.vcd')
print()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值