Verilog 求两数的最大公因数和最小公倍数[笔试题]
由于 ain*bin = lcm*gcd 所以可以先通过 更相减损法 求得最大公约数(GCD)
再通过 lcm=ain*bin/gcd 得到最小公倍数(LCM)
举例:求 LCM(3,7)
- 先求 GCD
- 7-3=4
- 4-3=1
- 3-1=2
- 2-1=1,1-1=0
- 所以 GCD(3,7)=1
- 因此 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()