verilog 实现9位有符号乘法器
一、移位相加乘法器
移位相加的原理
从被乘数的最低位开始判断,若为1,则乘数左移i(i=0,1(width-1))位后,与上一次和进行相加,若为0,则乘数左移i位后,以0相加。直到被乘数的最高位。
实际是由移位运算和加法运算构成。比较高速。
优点:
占用资源较少,主要在低速信号处理中
缺点:
串行乘法器的速度比较慢,一个结果输出需要更多的时钟周期。在高位宽的乘法运算中非常明显。所以可以采用改进的流水线形式来实现移位相加。
1、串行形式,使用状态机来实现
module multiply1#(
parameter DATAWIDTH=9
)(clk, x, y, result);
parameter s0 = 0, s1 = 1, s2 = 2;
input clk;
input [DATAWIDTH-1:0] x, y;
output [DATAWIDTH*2-2:0] result;
reg [DATAWIDTH*2-2:0] result_reg;
reg [DATAWIDTH-1:0] count = 0;
reg [1:0] state = 0;
reg [DATAWIDTH*2-3:0] P, T;
wire [DATAWIDTH-2:0] x_reg;
reg [DATAWIDTH-2:0] y_reg;
reg msb=0;
always @(posedge clk) begin
case (state)
s0: begin
y_reg<=(y[DATAWIDTH-1]==0)?y[DATAWIDTH-2:0]:~y[DATAWIDTH-2:0]+1'b1;
msb <= x[DATAWIDTH-1] ^ y[DATAWIDTH-1];
count <= 0;
P <= 0;
T <= {{(DATAWIDTH-1){1'b0}}, x_reg};
state <= s1;
end
s1: begin
if(count == (DATAWIDTH-1))
state <= s2;
else begin
if(y_reg[0] == 1'b1)
P <= P + T;
else
P <= P;
y_reg <= y_reg >> 1;
T <= T << 1;
count <= count + 1;
state <= s1;
end
end
s2: begin
result_reg <= {msb,P[DATAWIDTH*2-3:0]};
state <= s0;
end
default: ;
endcase
end
assign x_reg = (x[DATAWIDTH-1]==0)? x[DATAWIDTH-2:0] : ~x[DATAWIDTH-2:0]+1'b1;
assign result = (result_reg[DATAWIDTH*2-2]==0)? result_reg :
{result_reg[DATAWIDTH*2-2],~result_reg[DATAWIDTH*2-3:0]+1'b1};
endmodule
这里有一点需要说明的是,一开始我把x_reg
放在了always
模块中,也即和现在的y_reg
赋值放在了一起。而恰巧紧接着下面有这句T <= {{(DATAWIDTH-1){1'b0}}, x_reg};
代码,由于是非阻塞赋值,那么此时其实T
里面的x_reg
是上一个时钟周期的,而T
和y_reg
由于是在下一个时钟周期使用,也即状态机的下一状态,所以这就导致乘法器计算时的输入数据产生了错位。也即最终计算的是上一个周期的x
与本周期的y
的乘积。
所以,最后我把x_reg
放在了always
模块外面,这样就可以少一个延时,以使计算乘积时不会产生输入数据的错误。当然你也可以使用其他方式,只要保证输入数据在时序上没有错位即可。
仿真程序:
`timescale 1ns / 1ps
module tb_multiply1();
parameter DATAWIDTH=9;
reg clk;
reg [DATAWIDTH-1:0] Ain,Bin;
wire [DATAWIDTH*2-2:0] result;
initial
begin
Ain = 5;
Bin = 3;
clk = 0;
end
always #5 clk = ~clk;
always @(posedge clk)
begin
#110
Ain = Ain-2;
Bin = Bin+1;
end
multiply1 #(.DATAWIDTH( DATAWIDTH)) u1(clk,Ain,Bin,result);
endmodule
仿真这一块,也有一点需要注意:因为使用的是状态机,根据程序可以判断,输出结果会比输入数据延迟10个时钟周期,然后在下一个时钟周期,也即输入数据后的第11个时钟周期才会进入第一个状态,开始读取下一组输入数据。所以,这就会导致在这期间,有九个时钟周期的输入数据是不会被读取的,也即下一个输出结果,就已经是第11个时钟周期的输入数据的乘积了。看图:
仿真结果:
所以,为了解决这个问题,你可以在仿真的时候,在testbench
程序中,手动的将相邻两组输入数据之间延迟n个时钟周期,只要目的是为了在11个时钟周期之后,采样到的是下一组数据即可。下面展示一下我将输入数据延时11个时钟周期之后的仿真波形,可以看出结果是正确的:
2、流水线形式
该形式我就不修改成有输入数据宽度参数的通用程序了,因为不同位宽,需要定义不同个数的变量,而变量的个数不容易通过一个值来控制。不过,肯定是有办法的,你自己可以试一下!!!
一般的快速乘法器通常采用逐位并行的迭代阵列结构,将每个操作数的N位都并行地提交给乘法器,但是一般对于FPGA来讲,进位的速度快于加法的速度,这种阵列并不是最优的。所以可以采用多级流水线的形式,将相邻的两个部分乘积结果再加到最终的输出乘积上,即形成一个二叉树形式的结构,这样对于N位乘法器需要log2(N)级来实现。
在实现有符号数相乘的时候,我是先把符号位给计算好,然后使用上述流水线计算输入数据低8位的无符号乘积结果,最终再把符号位给加上,就得到了最后的有符号的结果。这里主要需要注意各变量位宽的选择,以免出错。
module multiply2 (
mul_a,
mul_b,
mul_out,
clk,
rst_n
);
parameter MUL_WIDTH = 9;
parameter MUL_RESULT = 17;
input [MUL_WIDTH-1:0] mul_a;
input [MUL_WIDTH-1:0] mul_b;
input clk;
input rst_n;
output [MUL_RESULT-1:0] mul_out;
wire [MUL_RESULT-1:0] mul_out;
wire [MUL_RESULT-1:0] mul_out_reg;
reg msb;
reg msb_reg_0;
reg msb_reg_1;
reg msb_reg_2;
reg [MUL_WIDTH-1:0] mul_a_reg;
reg [MUL_WIDTH-1:0] mul_b_reg;
reg [MUL_RESULT-2:0] stored0;
reg [MUL_RESULT-2:0] stored1;
reg [MUL_RESULT-2:0] stored2;
reg [MUL_RESULT-2:0] stored3;
reg [MUL_RESULT-2:0] stored4;
reg [MUL_RESULT-2:0] stored5;
reg [MUL_RESULT-2:0] stored6;
reg [MUL_RESULT-2:0] stored7;
reg [MUL_RESULT-2:0] out1,out2;
reg [MUL_RESULT-2:0] add1,add2,add3,add4;
reg [MUL_RESULT-2:0] add;
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n )
begin
stored0 <= 16'b0;
stored1 <= 16'b0;
stored2 <= 16'b0;
stored3 <= 16'b0;
stored4 <= 16'b0;
stored5 <= 16'b0;
stored6 <= 16'b0;
stored7 <= 16'b0;
mul_a_reg<=9'b0;
mul_b_reg<=9'b0;
add<=16'b0;
msb<=0;
msb_reg_0<=0;
msb_reg_1<=0;
msb_reg_2<=0;
add1 <= 16'b0;
add2 <= 16'b0;
add3 <= 16'b0;
add4 <= 16'b0;
end
else
begin
//注意,下面两句是没有延迟的,因为他们的右侧的mul_a,mul_b是输入信号
mul_a_reg <= (mul_a[8]==0)? mul_a : {mul_a[8],~mul_a[7:0]+1'b1};
mul_b_reg <= (mul_b[8]==0)? mul_b : {mul_b[8],~mul_b[7:0]+1'b1};
msb_reg_0 <= mul_a_reg[8] ^ mul_b_reg[8];
msb_reg_1<=msb_reg_0;
msb_reg_2<=msb_reg_1;
msb<=msb_reg_2;
stored0 <= mul_b_reg[0] ? {8'b0,mul_a_reg[7:0]} : 16'b0;
stored1 <= mul_b_reg[1] ? {7'b0,mul_a_reg[7:0],1'b0} : 16'b0;
stored2 <= mul_b_reg[2] ? {6'b0,mul_a_reg[7:0],2'b0} : 16'b0;
stored3 <= mul_b_reg[3] ? {5'b0,mul_a_reg[7:0],3'b0} : 16'b0;
stored4 <= mul_b_reg[4] ? {4'b0,mul_a_reg[7:0],4'b0} : 16'b0;
stored5 <= mul_b_reg[5] ? {3'b0,mul_a_reg[7:0],5'b0} : 16'b0;
stored6 <= mul_b_reg[6] ? {2'b0,mul_a_reg[7:0],6'b0} : 16'b0;
stored7 <= mul_b_reg[7] ? {1'b0,mul_a_reg[7:0],7'b0} : 16'b0;
add1 <= stored1 + stored0;
add2 <= stored3 + stored2;
add3 <= stored5 + stored4;
add4 <= stored6 + stored7;
out1 <= add1 + add2;
out2 <= add3 + add4;
add <= out1 + out2;
end
end
assign mul_out_reg = {msb,add[15:0]};
assign mul_out=(mul_out_reg[16]==0)?mul_out_reg:{mul_out_reg[16],~mul_out_reg[15:0]+1'b1};
endmodule
仿真程序:
`timescale 1ns / 1ps
module tb_multiply2();
reg clk,rst_n;
reg [8:0] Ain,Bin;
wire [16:0] result;
initial
begin
#1
Ain = 5;
Bin = 2;
clk = 0;
rst_n=0;
#3
rst_n=1;
end
always #5 clk = ~clk;
always @(posedge clk)
begin
#1
Ain = Ain - 2;
Bin = Bin + 1;
end
multiply2 u2(Ain,Bin,result,clk,rst_n);
endmodule
仿真结果:
从仿真结果可以看出,计算结果是完全正确的。但是,我们需要注意的是,由于上面采用了3级流水线,再加上mul_a_reg
和mul_b_reg
的一级延时,一共有4个时钟周期的延时。所以,为了保证计算结果的符号位是正确的,我们的msb
也要做一个四级的延时,这样才能刚好和result
的延时对应,输出结果的符号才不会出错。
你可以自己改变一下msb
的延迟周期,看一下计算结果是不是正确的。
二、并行乘法器
也就是用乘法运算符实现,下面的代码实现8bit无符号数的乘法。若要做有符号数乘法,需将数据声明为signed类型。
采用并行乘法设计的乘法器,在Verilog中直接采用*
设计,这种方法设计出来的乘法器需要根据综合工具得到乘法结果,往往这种算法都是比较差的算法。
特点:
由乘法运算符描述、由EDA软件综合
运算速度快、耗用资源多
module multiply3(rst_n,
clk,
a,
b,
out
);
parameter DATA_SIZE = 8;
input rst_n;
input clk;
input signed [DATA_SIZE - 1 : 0] a;
input signed [DATA_SIZE - 1 : 0] b;
output signed [2*DATA_SIZE - 1 : 0] out;
reg signed [DATA_SIZE - 1 : 0] a_r;
reg signed [DATA_SIZE - 1 : 0] b_r;
wire signed [2*DATA_SIZE - 1 : 0] out_tmp;
reg signed [2*DATA_SIZE - 1 : 0] out;
//输入数据打一拍
always@(posedge clk)
if(!rst_n)
begin
a_r <= 8'd0;
b_r <= 8'd0;
end
else
begin
a_r <= a;
b_r <= b;
end
//只能做无符号数的相乘,若要做有符号数乘法,需将数据声明为signed类型
assign out_tmp = a_r*b_r;
//输出数据打一拍
always@(posedge clk)
if(!rst_n)
begin
out <= 16'd0;
end
else
begin
out <= out_tmp;
end
endmodule
仿真程序:
`timescale 1ns / 1ps
module tb_multiply3();
reg clk,rst_n;
reg signed [7:0] Ain,Bin;
wire signed [15:0] result;
initial
begin
#1
Ain = 6;
Bin = 7;
clk = 0;
rst_n=0;
#3
rst_n=1;
end
always #5 clk = ~clk;
always @(posedge clk)
begin
#1
Ain = Ain-2;
Bin =Bin+3;
end
multiply3 u3(rst_n,clk,Ain,Bin,result);
endmodule
仿真结果:
可以看出,和程序中的描述方式一致,输出比输入延迟了一个时钟周期!!!
附录 :例化8位无符号乘法器实现9位有符号乘法器
一、前言
这里和上面的内容差不多,只不过我将无符号乘法器单独写成一个模块,然后在计算有符号数的乘积时对无符号乘法器进行例化。
二、移位相加乘法器—串行形式
1、8位无符号乘法器
module unsigned_mul_8bit #(
parameter DATAWIDTH=8
)(clk, x, y, result);
parameter s0 = 0, s1 = 1, s2 = 2;
input clk;
input [DATAWIDTH-1:0] x, y;
output [DATAWIDTH*2-1:0] result;
reg [DATAWIDTH*2-1:0] result;
reg [DATAWIDTH-1:0] count = 0;
reg [1:0] state = 0;
reg [DATAWIDTH*2-1:0] P, T;
reg [DATAWIDTH-1:0] y_reg;
always @(posedge clk) begin
case (state)
s0: begin
count <= 0;
P <= 0;
y_reg <= y;
T <= {{DATAWIDTH{1'b0}}, x};
state <= s1;
end
s1: begin
if(count == 8)
state <= s2;
else begin
if(y_reg[0] == 1'b1)
P <= P + T;
else
P <= P;
y_reg <= y_reg >> 1;
T <= T << 1;
count <= count + 1;
state <= s1;
end
end
s2: begin
result <= P;
state <= s0;
end
default: ;
endcase
end
endmodule
2、9位有符号乘法器(例化8位无符号乘法器)
module signed_mul_9bit(clk, x, y, result);
parameter in_width=9,
result_width=2*in_width-1;
input clk;
input [in_width-1:0] x, y;
output [result_width-1:0] result;
wire [result_width-1:0] result_reg;
wire [2*(in_width-1)-1:0] out;
wire [in_width-2:0] x_reg;
wire [in_width-2:0] y_reg;
reg msb=0;
reg msb_reg_0=0;
reg msb_reg_1=0;
reg msb_reg_2=0;
reg msb_reg_3=0;
reg msb_reg_4=0;
reg msb_reg_5=0;
reg msb_reg_6=0;
reg msb_reg_7=0;
reg msb_reg_8=0;
reg msb_reg_9=0;
assign x_reg = (x[8]==0)? x[7:0] : ~x[7:0]+1'b1;
assign y_reg = (y[8]==0)? y[7:0] : ~y[7:0]+1'b1;
always @(posedge clk) begin
msb_reg_0 <=x[8] ^ y[8];
msb_reg_1<=msb_reg_0;
msb_reg_2<=msb_reg_1;
msb_reg_3<=msb_reg_2;
msb_reg_4<=msb_reg_3;
msb_reg_5<=msb_reg_4;
msb_reg_6<=msb_reg_5;
msb_reg_7<=msb_reg_6;
msb_reg_8<=msb_reg_7;
msb_reg_9<=msb_reg_8;
msb<=msb_reg_9;
end
unsigned_mul_8bit #(.DATAWIDTH( in_width-1)) u1(clk, x_reg, y_reg, out);
assign result_reg = {msb,out[15:0]};
assign result=(result_reg[16]==0)?result_reg:{result_reg[16],~result_reg[15:0]+1'b1};
endmodule
这里之所以使msb
延迟10个时钟周期,是因为输出比输入延迟10个时钟周期。所以为了保证输出结果的符号正确,我们需要使msb
和输出数据保持对齐。
3、仿真程序
`timescale 1ns / 1ps
module tb_m1();
reg clk;
reg [8:0] Ain,Bin;
wire [16:0] result;
initial
begin
Ain = 5;
Bin = 3;
clk = 0;
end
always #5 clk = ~clk;
always @(posedge clk)
begin
#110
Ain = Ain-2;
Bin = Bin+1;
end
signed_mul_9bit u1(clk,Ain,Bin,result);
endmodule
4、仿真结果
三、移位相加乘法器—流水线形式
1、8位无符号乘法器
module un_signed_mul_8bit(
mul_a,
mul_b,
mul_out,
clk,
rst_n
);
parameter MUL_WIDTH = 8;
parameter MUL_RESULT = 16;
input [MUL_WIDTH-1:0] mul_a;
input [MUL_WIDTH-1:0] mul_b;
input clk;
input rst_n;
output [MUL_RESULT-1:0] mul_out;
reg [MUL_RESULT-1:0] mul_out;
reg [MUL_RESULT-1:0] stored0;
reg [MUL_RESULT-1:0] stored1;
reg [MUL_RESULT-1:0] stored2;
reg [MUL_RESULT-1:0] stored3;
reg [MUL_RESULT-1:0] stored4;
reg [MUL_RESULT-1:0] stored5;
reg [MUL_RESULT-1:0] stored6;
reg [MUL_RESULT-1:0] stored7;
reg [MUL_RESULT-1:0] out1,out2;
reg [MUL_RESULT-1:0] add1,add2,add3,add4;
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n )
begin
stored0 <= 14'b0;
stored1 <= 14'b0;
stored2 <= 14'b0;
stored3 <= 14'b0;
stored4 <= 14'b0;
stored5 <= 14'b0;
stored6 <= 14'b0;
out1<= 14'b0;
out2<= 14'b0;
add1 <= 14'b0;
add2 <= 14'b0;
add3 <= 14'b0;
add4 <= 14'b0;
end
else
begin
//注意,下面两句是没有延迟的,因为他们的右侧的mul_a,mul_b是输入信号
stored0 <= mul_b[0] ? {8'b0,mul_a} : 16'b0;
stored1 <= mul_b[1] ? {7'b0,mul_a,1'b0} : 16'b0;
stored2 <= mul_b[2] ? {6'b0,mul_a,2'b0} : 16'b0;
stored3 <= mul_b[3] ? {5'b0,mul_a,3'b0} : 16'b0;
stored4 <= mul_b[4] ? {4'b0,mul_a,4'b0} : 16'b0;
stored5 <= mul_b[5] ? {3'b0,mul_a,5'b0} : 16'b0;
stored6 <= mul_b[6] ? {2'b0,mul_a,6'b0} : 16'b0;
stored7 <= mul_b[7] ? {1'b0,mul_a,7'b0} : 16'b0;
add1 <= stored1 + stored0;
add2 <= stored3 + stored2;
add3 <= stored5 + stored4;
add4 <= stored6 + stored7;
out1 <= add1 + add2;
out2 <= add3 + add4;
mul_out <= out1 + out2;
end
end
endmodule
2、9位有符号乘法器(例化8位无符号乘法器)
module signed_mul_9bit(x, y, result,clk, rst_n);
parameter in_width=9,
result_width=2*in_width-1;
input clk,rst_n;
input [in_width-1:0] x, y;
output [result_width-1:0] result;
wire [result_width-1:0] result_reg;
wire [2*(in_width-1)-1:0] out;
wire [in_width-2:0] x_reg;
wire [in_width-2:0] y_reg;
reg msb=0;
reg msb_reg_0=0;
reg msb_reg_1=0;
reg msb_reg_2=0;
assign x_reg = (x[8]==0)? x[7:0] : ~x[7:0]+1'b1;
assign y_reg = (y[8]==0)? y[7:0] : ~y[7:0]+1'b1;
always @(posedge clk) begin
msb_reg_0 <=x[8] ^ y[8];
msb_reg_1<=msb_reg_0;
msb_reg_2<=msb_reg_1;
msb<=msb_reg_2;
end
un_signed_mul_8bit u1(x_reg, y_reg, out,clk, rst_n);
assign result_reg = {msb,out[15:0]};
assign result=(result_reg[16]==0)?result_reg:{result_reg[16],~result_reg[15:0]+1'b1};
endmodule
这里之所以使msb
延迟3个时钟周期,是因为输出比输入延迟3个时钟周期。所以为了保证输出结果的符号正确,我们需要使msb
和输出数据保持对齐。
3、仿真程序
`timescale 1ns / 1ps
module tb_m2();
reg clk,rst_n;
reg [8:0] Ain,Bin;
wire [16:0] result;
initial
begin
#1
Ain = 5;
Bin = 2;
clk = 0;
rst_n=0;
#3
rst_n=1;
end
always #5 clk = ~clk;
always @(posedge clk)
begin
#1
Ain = Ain - 2;
Bin = Bin + 1;
end
signed_mul_9bit u1(Ain,Bin,result,clk,rst_n);
endmodule
4、仿真结果