进阶二进制乘法
进阶二进制乘法器又可称作 right-shift 乘法器,也有left-shift乘法器,由于资源占用比right-shift多,这里不做介绍。
进阶二进制乘法器相比上一种方法可以减小内部加法器的位宽。例如4bits*4bits的乘法,在初级乘法器中需要一个8bits位宽的加法器,而在进阶二进制乘法中,只需要4bits位宽的加法器。
无符号乘法
以1010*1011(10*11)为例,说明right-shift乘法器实现过程。
(1)p0初始值为0,x0a = 1010,p0+x0a = 0_1010 =p1(注意这里,4bit数相加结果用5bit保存)
(2)将p1右移1bits,末尾0保存到移位寄存器中,p1的高4位用与x1a相加得到p2 0_1111
(3)将p2右移1bits,末尾1保存到移位寄存器中,p2的高4位用与x2a相加得到p3 0_0111
(4)将p3右移1bits,末尾1保存到移位寄存器中,p3的高4位用与x3a相加得到p4 0_1101
(2)将p4右移1bits,末尾1保存到移位寄存器中,p4的高4位与移位寄存器拼接结果为最终结果
硬件实现过程如下,只需kbit位宽的加法器
上代码:
module right_shift_multi_u #(
parameter DW = 10
)(
input wire clk,
input wire rst_n,
input wire start,
input wire [DW-1:0] din0,
input wire [DW-1:0] din1,
output wire prod_end,
output reg [2*DW-1:0] prod
);
localparam CNTW= $clog2(DW)+1;
reg [CNTW-1:0] mult_cnt;
wire busy;
wire [DW-1:0] din0_temp;
assign din0_temp = prod[0] ? din0 : 0;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
prod <= 0;
else if(prod_end)
prod <= 0;
else if(start)
prod[DW-1:0] <= din1;
else if(busy)begin
prod[2*DW-1:DW-1] <= prod[2*DW-1:DW] + din0_temp;
prod[DW-2:0] <= prod[DW-1:1];
end
end
assign busy = mult_cnt != 0;
assign prod_end = mult_cnt == (DW+1);
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
mult_cnt <= 0;
else if (prod_end)
mult_cnt <= 0;
else if(busy || start)
mult_cnt <= mult_cnt + 1'b1;
end
endmodule
有符号乘法
与无符号乘法不同,有符号乘法需要用到k+1bits加法器,原因是计算xj*a,要考虑到a是一个无符号数。以1010*1011(-6*-5)为例
(1)p0的初始值为0,x0*a = 1010,p0+x0a = 1_1010
注意这里需先扩成5bits,再相加,加法扩位是要扩符号位
$signed(0000) +$signed(1010) = 0_0000 + 1_1010 = 1_1010。以下在计算加法时都这么操作
(2)将p1右移1bits,末尾0保存到移位寄存器中,p1的高4位用与x1a相加得到p2 1_0111
(3)将p2右移1bits,末尾1保存到移位寄存器中,p2的高4位用与x2a相加得到p3 1_1011
(4)将p3右移1bits,末尾1保存到移位寄存器中,p3的高4位用与(x3a)相加得到p4 0_0011
这里注意,x3=1,这是符号位,表明实际上x3=-1,因此需要将x3a先转成补码,再与p3高4位相加
补码是取反加1,因此1010的补码是0101+0001 = 0110
(2)将p4右移1bits,末尾1保存到移位寄存器中,p4的高4位与移位寄存器结果为乘法的最终结果
0001_1110 = 30
再看一个1_0110 (-10)* 0_1011 (11)的例子,如下
代码如下:
module basic_multi_s #(
parameter DW0 = 10,
DW1 = 10
) (
input wire clk,
input wire rst_n,
input wire start,
input wire [DW0-1:0] din0,
input wire [DW1-1:0] din1,
output reg prod_end,
output reg [DW0+DW1-1:0] prod
);
localparam CNTW= $clog2(DW1);
reg [CNTW-1:0] mult_cnt;
wire busy;
wire [DW0:0] din0_temp; // din0
wire [DW0-1:0] din0_c; // din0
wire [DW0+DW1-1:0] din0_lsft; // din0 left shift
assign busy = start || (mult_cnt != 0);
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
mult_cnt <= 0;
else if (mult_cnt == (DW1-1))
mult_cnt <= 0;
else if(busy)
mult_cnt <= mult_cnt + 1'b1;
end
// if MSB, din0_temp = -din0; else, din0_temp = din0;
assign din0_c = ~din0 + 1'b1;
assign din0_temp = (mult_cnt == (DW1-1)) ? {1'b0,din0_c} : {din0[DW0-1],din0};
assign din0_lsft = din1[mult_cnt] ? {
{(DW1-1){din0_temp[DW0-1]}},din0_temp} << mult_cnt : 0; //din0 is a signed number
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
prod <= 0;
else if(prod_end)
prod <= 0;
else if (busy) begin
prod <= prod + din0_lsft;
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
prod_end <= 1'b0;
else if(mult_cnt == (DW1-1))
prod_end <= 1'b1;
else
prod_end <= 1'b0;
end
endmodule