verilog 实现9位有符号乘法器
一、移位相加乘法器
移位相加的原理
从被乘数的最低位开始检查每一位,若当前位为1,则将乘数向左移动i位(其中i从0开始,依次递增至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个时钟周期进入第一个状态,以读取下一组输入数据。这意味着,在这期间会有9个时钟周期的输入数据无法被读取,而下一个输出结果将基于第11个时钟周期的输入数据。例如图中所示:
仿真结果 :

所以,为了解决这个问题,可以在仿真过程中,在testbench程序中进行人工干预,将相邻两组输入数据之间延时n个时钟周期,只要其达到11个时钟周期的目标,就能确保在采样后获取下一组数据。通过仿真波形,可以直观地验证这种方法的正确性:

2、流水线形式
我就不建议将该形式修改为具有输入数据宽度参数的通用程序。原因在于,不同位宽的输入数据需要定义不同数量的变量,而变量数量的控制也并非易事。不过,确实是有办法实现的,你可以自行尝试一下。
一般的快速乘法器通常采用逐位并行的迭代阵列结构,将每个操作数的N位同时输入到乘法器中进行处理。然而,对于FPGA设备而言,进位的速率快于加法的速率,这使得传统的并行阵列结构在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
代码解读
仿真结果 :

从仿真结果的验证来看,计算结果的正确性得到了充分的确认。需要注意的是,由于采用了三级流水线结构,并且在mul_a_reg和mul_b_reg组件中各引入了一级时延,总共造成了4个时钟周期的延迟。因此,为了确保计算结果符号位的准确性,我们需要在msb端增加四级时延,这样可以使msb的延时与result的延时达到一致,从而保证最终输出结果的符号位不会出现错误。
你可以自己改变一下msb的延迟周期,看一下计算结果是不是正确的。
二、并行乘法器
通过乘法运算符来实现,代码中实现了8位无符号数的乘法运算。进行有符号数乘法时,需将数据声明为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、仿真结果

