数字电路之乘法器(二)

进阶二进制乘法

进阶二进制乘法器又可称作 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

Guess you like

Origin blog.csdn.net/zhong_ethan/article/details/121592631