乘除法基本原理
乘法器和除法器其实都有很多种设计思路,主要复习了一下最基本的一些设计思路。
乘法
主要是进行移位相加:
除法
一种实现思路:其中a的高位为余数,低位为商
牛客verilog-流水线乘法器
牛客的VL56,实现一个4bit的流水线乘法器
1.移位相加法
正好前几天复习看了菜鸟教程,于是用相似的思路写了如下代码:
module multi_pipe#(
parameter size = 4
)( input clk ,
input rst_n ,
input [size-1:0] mul_a ,
input [size-1:0] mul_b ,
output reg [size*2-1:0] mul_out
);
wire [size-1:0] zero_d;
assign zero_d = 'd0;
wire [size*2-1:0] pipe_a [size-1:0];
wire [size-1:0] pipe_b [size-1:0];
wire [size*2-1:0] add_reg [size-1:0];
multi_step#(
.size(size)
) multi_step_inst0 (
.clk (clk),
.rst_n (rst_n),
.step_a_i ({zero_d,mul_a}),
.step_b_i (mul_b),
.pipe_in ('d0),
.step_a_o (pipe_a[0]),
.step_b_o (pipe_b[0]),
.pipe_out (add_reg[0])
);
genvar i;
generate
for(i=0; i<size-1; i=i+1) begin: pipe_step
multi_step#(
.size(size)
) multi_step_inst (
.clk (clk),
.rst_n (rst_n),
.step_a_i (pipe_a[i]),
.step_b_i (pipe_b[i]),
.pipe_in (add_reg[i]),
.step_a_o (pipe_a[i+1]),
.step_b_o (pipe_b[i+1]),
.pipe_out (add_reg[i+1])
);
end
endgenerate
always@(*) begin
mul_out = add_reg[size-1];
end
endmodule
module multi_step#(
parameter size = 4
)( input clk,
input rst_n,
input [size*2-1:0] step_a_i,
input [size-1:0] step_b_i,
input [size*2-1:0] pipe_in,
output reg [size*2-1:0] step_a_o,
output reg [size-1:0] step_b_o,
output reg [size*2-1:0] pipe_out
);
wire [size*2-1:0] step_add;
assign step_add = step_b_i[0] ? step_a_i : 'd0;
always@(posedge clk or negedge rst_n) begin
step_a_o <= step_a_i << 1;
step_b_o <= step_b_i >> 1;
end
always@(posedge clk or negedge rst_n) begin
if(!rst_n)
pipe_out <= 'd0;
else
pipe_out <= pipe_in + step_add;
end
endmodule
仿真结果:(这里是8bit的乘法)
2.加法树法
用上面的方法发现和牛客的时序不一样(不得不吐槽,牛客的verilog题描述实在是太不清楚了,有的时序也不给),于是就改用加法树流水的方式写了一个,其实本质上还是在进行移位相加。
代码如下:(这里同样是8bit)
`timescale 1ns/1ns
module multi_pipe(
input [7:0]mul_a ,
input [7:0]mul_b ,
input clk,
output reg [15:0]mul_out
);
wire [15:0] temp_a [7:0];
reg [15:0] pipe_add_0 [3:0];
reg [15:0] pipe_add_1 [1:0];
genvar i;
generate
for(i=0; i<8; i=i+1) begin:shift_a
assign temp_a[i] = mul_b[i] ? {{(8-i){1'b0}},mul_a,{i{1'b0}}} : 'd0;
end
endgenerate
integer j;
always@(posedge clk) begin
for(j=0; j<4; j=j+1)
pipe_add_0[j] <= temp_a[2*j] + temp_a[2*j+1];
end
always@(posedge clk) begin
pipe_add_1[0] <= pipe_add_0[0] + pipe_add_0[1];
pipe_add_1[1] <= pipe_add_0[2] + pipe_add_0[3];
end
always@(posedge clk) begin
mul_out <= pipe_add_1[0] + pipe_add_1[1];
end
endmodule
仿真结果如下:
3.对比
明显用加法树设计的延迟更小,三个周期就能得到结果,虽然这两种方法后续都能每个周期得到一个结果,但是使用加法树的方法要更加省面积,前一种方法用加法链的方式,级数高了之后消耗的寄存器资源是比较大的。这一点通过vivado综合结果也能看出来。
第一种乘法器综合结果(8bit):
第二种乘法器综合结果(8bit):
有符号乘法器
首先,对于verilog的带符号运算,需要知道的是,如果表达式中有一个无符号数,则所有的操作数都会被强行转换为无符号数,这在涉及到有符号数运算时尤其需要注意。
由于之前项目中需要进行有符号数的乘法,这里还将加法树流水的乘法器改为了有符号数,实际上就是先将有符号数变无符号数(即补码变原码),然后在进行无符号数乘法,最后输出时再转换为有符号数。
代码如下:
module multi_pipe_signed(
input [7:0]mul_a ,
input [7:0]mul_b ,
input clk,
output reg [15:0]mul_out
);
//保存符号位
wire sign;
reg sign_reg_0,sign_reg_1;
assign sign = mul_a[7] ^ mul_b[7];
//转换为原码
wire [6:0] unsigned_a,unsigned_b;
assign unsigned_a = mul_a[7] ? ~mul_a[6:0]+1 : mul_a[6:0];
assign unsigned_b = mul_b[7] ? ~mul_b[6:0]+1 : mul_b[6:0];
//加法树流水线
wire [13:0] temp_a [6:0];
reg [13:0] pipe_add_0 [3:0];
reg [13:0] pipe_add_1 [1:0];
genvar i;
generate
for(i=0; i<7; i=i+1) begin:shift_a
assign temp_a[i] = unsigned_b[i] ? {{(7-i){1'b0}},unsigned_a,{i{1'b0}}} : 'd0;
end
endgenerate
always@(posedge clk) begin
pipe_add_0[0] <= temp_a[0] + temp_a[1];
pipe_add_0[1] <= temp_a[2] + temp_a[3];
pipe_add_0[2] <= temp_a[4] + temp_a[5];
pipe_add_0[3] <= temp_a[6];
sign_reg_0 <= sign;
end
always@(posedge clk) begin
pipe_add_1[0] <= pipe_add_0[0] + pipe_add_0[1];
pipe_add_1[1] <= pipe_add_0[2] + pipe_add_0[3];
sign_reg_1 <= sign_reg_0;
end
wire [13:0] pipe_out;
assign pipe_out = pipe_add_1[0] + pipe_add_1[1];
always@(posedge clk) begin
mul_out <= sign_reg_1 ? {2'b11,~pipe_out+1} : {2'b00,pipe_out};
end
endmodule
仿真结果:
综合结果: