基于FPGA的高效乘法器

1、设计思路

  二进制的乘法运算与十进制的乘法运算相似,如下图所示,二进制数据6’b110010乘以二进制数据4’b1011,得到乘积结果10’b1000100110。

基于FPGA的高效乘法器_第1张图片

图1 二进制乘法运算

  仔细观察上图发现,乘数最低位为1(上图紫色数据位),则得到紫色数据,乘数第1位为1,将被乘数左移1位,得到橙色数据,然后乘数的第2位是0,0乘以被乘数为0,则舍弃。乘数的第3位为1,则将被乘数左移3位,得到红色数据。然后将紫色、橙色、红色数据相加,得到乘积。

  这就是二进制乘法运算思路,乘法的运算时间与乘数的位宽有关。乘数为1时需要左移的位数与数据位的权重其实有关,但是FPGA实现这样的运算并不算特别简单,还能不能简化?

  当乘数或者被乘数为0时,直接输出0即可,不需要运算。

  当乘数和被乘数均不等于0时,乘积的初始值为0,每个时钟周期把乘数右移一位,被乘数左移一位,如果乘数最低位为1,则乘积等于乘积加上此时被乘数的值,当乘数为1时,计算完成,输出乘积的运算结果。

  计算流程如下图所示,其实就是将图1的运算拆分,每次只需要判断乘数的最低位是否为1,从而确定乘积是否需要加上被乘数,乘数每右移一次,被乘数就必须左移一次,这样来能保证乘积不变。当乘数变为1时,移位结束,此时乘数最低位为1,被乘数加上乘积后作为运算结果,完成运算。

基于FPGA的高效乘法器_第2张图片

图2 简化的移位相加运算

2、代码设计

  由此,就可以编写FPGA代码了,为了模块通用,位宽全部进行参数化设计,增加开始计算信号和模块忙闲指示信号,以及乘积计算完成的有效指示信号。

  端口信号如下表所示:

表1 端口信号列表

信号 I/O 位宽 含义
clk I 1 系统时钟
rst_n I 1 系统复位,低电平有效
start I 1 开始运算,高电平有效
multiplicand I MULT_D 被乘数
multiplier I MULT_R 乘数
product O MULT_D+ MULT_R 乘积
product_vld O 1 乘积有效指示信号,高电平有效
rdy O 1 模块空闲指示信号,高电平有效。

  当开始计算信号有效且乘数与被乘数均不等于0且模块不处于运算状态时,把开始计算信号start_f拉高,运算状态标志信号flag初始值为0,当检测到开始运算start_f有效时拉高,当乘数为1时结束运算,flag信号拉低,对应代码如下所示:

    //开始计算信号有效且乘数和被乘数均不等于0;
    assign start_f = (~flag) && (start && (multiplicand != 0) && (multiplier != 0));

    //运算标志信号,
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            flag <= 1'b0;
        end
        else if(start_f)begin//开始运算时拉高
            flag <= 1'b1;
        end
        else if(multiplier_r == 1)begin//运算结束时拉低;
            flag <= 1'b0;
        end
    end

  然后就是对乘数和被乘数信号的处理,如下所示。初始值均为0,当开始运算时,将输入的乘数和被乘数保存到相应寄存器中,如果flag信号有效,则每个时钟周期把乘数右移1位,把被乘数左移1位。

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            multiplicand_r <= {{MULT_D + MULT_R}{1'b0}};
            multiplier_r <= {{MULT_R}{1'b0}};
        end
        else if(start_f)begin//当计算开始时;
            multiplicand_r <= multiplicand;//将被乘数加载到被乘数寄存器中。
            multiplier_r <= multiplier;//将乘数加载到乘积寄存器中。
        end
        else if(flag)begin//正常计算标志信号有效时,被乘数左移一位,乘数右移一位。
            multiplicand_r <= multiplicand_r << 1;
            multiplier_r <= multiplier_r >> 1;
        end
    end

  之后就是乘积的运算,出数字为0,当开始信号有效时,不管乘数和被乘数的状态是什么,将乘积寄存器设置为0。在之后的运算中,如果flag有效并且乘数最低位为1,则把乘积寄存器的值与被乘数寄存器的值相加,得到乘积寄存器数据。

    //计算乘法运算结果,开始信号有效时,将乘积清零。
    //当乘数寄存器最低位为1时,加上此时被乘数的值。
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            product_r <= {{MULT_D + MULT_R}{1'b0}};
        end
        else if(start)//当乘数或者被乘数为0时,乘积输出0.
            product_r <= {{MULT_D + MULT_R}{1'b0}};
        else if(flag && multiplier_r[0])begin//如果乘积的最低位为1,则把乘积的高位数据与被乘数相加。
            product_r <= product_r + multiplicand_r;
        end
    end

  最后就是乘积运算的输出,如果开始信号有效时,乘数和被乘数其中一个为0,则乘积输出0,拉高乘积有效指示信号。如果在计算乘积的过程中(flag为高电平)且乘数等于1,则表示计算完成,把乘积寄存器值加上此时被乘数的值作为乘积输出,并且把乘积有效指示信号拉高一个时钟周期。乘积有效指示信号在其余时间均为0。

    //输出乘积和乘积有效指示信号;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            product <= {{MULT_D + MULT_R}{1'b0}};
            product_vld <= 1'b0;
        end
        else if((~flag) && (start && ((multiplicand == 0) || (multiplier == 0))))begin
            product <= {{MULT_D + MULT_R}{1'b0}};//如果开始计算时,乘数或者被乘数为0,则直接输出0;
            product_vld <= 1'b1;
        end
        else if(flag && (multiplier_r == 1))begin//计算完成时,把计算结果输出,且乘积有效指示信号拉高;
            product <= product_r + multiplicand_r;
            product_vld <= 1'b1;
        end
        else begin//其余时间把有效指示信号拉低;
            product_vld <= 1'b0;
        end
    end

  最后就是模块忙闲指示信号,当开始信号有效或者模块处于计算状态时拉低,其余时间拉高,上游模块检测到该信号后就可以拉高start信号,开始下一次运算。注意该信号只能使用组合逻辑电路生成,并且上游只能通过时序电路检测该信号状态。

    //生成模块忙闲指示信号;
    always@(*)begin//当开始信号有效或者标志信号有效时,模块处于工作状态;
        if(start || flag)
            rdy = 1'b0;
        else//否则模块处于空闲状态;
            rdy = 1'b1;
    end

  代码就这么多,相对比较简单,参考代码如下:

module mult #(
    parameter			MULT_D		        =		8		        ,//被乘数位宽;
    parameter			MULT_R		        =		4		         //乘数位宽;
)(
    input									        clk		        ,//系统时钟信号;
    input									        rst_n	        ,//系统复位信号,低电平有效;

    input                                           start           ,//开始运算信号,高电平有效;
    input               [MULT_D - 1 : 0]            multiplicand    ,//被乘数;
    input               [MULT_R - 1 : 0]            multiplier      ,//乘数;
    output  reg         [MULT_D + MULT_R - 1 : 0]   product         ,//乘积输出;
    output  reg                                     product_vld     ,//乘积有效指示信号,高电平有效;
    output  reg                                     rdy              //模块忙闲指示信号,高电平表示空闲;
);
    reg                                             flag            ;
    reg                 [MULT_D - 1 : 0]            multiplier_r    ;//乘数的寄存器
    reg                 [MULT_D + MULT_R - 1 : 0]   multiplicand_r  ;//被乘数的寄存器。
    reg                 [MULT_D + MULT_R - 1 : 0]   product_r       ;//乘积寄存器;

    wire                                            start_f         ;

    //开始计算信号有效且乘数和被乘数均不等于0;
    assign start_f = (~flag) && (start && (multiplicand != 0) && (multiplier != 0));

    //运算标志信号,
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            flag <= 1'b0;
        end
        else if(start_f)begin//开始运算时拉高
            flag <= 1'b1;
        end
        else if(multiplier_r == 1)begin//运算结束时拉低;
            flag <= 1'b0;
        end
    end

    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            multiplicand_r <= {{MULT_D + MULT_R}{1'b0}};
            multiplier_r <= {{MULT_R}{1'b0}};
        end
        else if(start_f)begin//当计算开始时;
            multiplicand_r <= multiplicand;//将被乘数加载到被乘数寄存器中。
            multiplier_r <= multiplier;//将乘数加载到乘积寄存器中。
        end
        else if(flag)begin//正常计算标志信号有效时,被乘数左移一位,乘数右移一位。
            multiplicand_r <= multiplicand_r << 1;
            multiplier_r <= multiplier_r >> 1;
        end
    end

    //计算乘法运算结果,开始信号有效时,将乘积清零。
    //当乘数寄存器最低位为1时,加上此时被乘数的值。
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            product_r <= {{MULT_D + MULT_R}{1'b0}};
        end
        else if(start)//当乘数或者被乘数为0时,乘积输出0.
            product_r <= {{MULT_D + MULT_R}{1'b0}};
        else if(flag && multiplier_r[0])begin//如果乘积的最低位为1,则把乘积的高位数据与被乘数相加。
            product_r <= product_r + multiplicand_r;
        end
    end

    //输出乘积和乘积有效指示信号;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            product <= {{MULT_D + MULT_R}{1'b0}};
            product_vld <= 1'b0;
        end
        else if((~flag) && (start && ((multiplicand == 0) || (multiplier == 0))))begin
            product <= {{MULT_D + MULT_R}{1'b0}};//如果开始计算时,乘数或者被乘数为0,则直接输出0;
            product_vld <= 1'b1;
        end
        else if(flag && (multiplier_r == 1))begin//计算完成时,把计算结果输出,且乘积有效指示信号拉高;
            product <= product_r + multiplicand_r;
            product_vld <= 1'b1;
        end
        else begin//其余时间把有效指示信号拉低;
            product_vld <= 1'b0;
        end
    end

    //生成模块忙闲指示信号;
    always@(*)begin//当开始信号有效或者标志信号有效时,模块处于工作状态;
        if(start || flag)
            rdy = 1'b0;
        else//否则模块处于空闲状态;
            rdy = 1'b1;
    end

endmodule

3、模块仿真

  对应的TestBench如下所示:

`timescale 1 ns/1 ns
module test();
    localparam  CYCLE		        =   10          ;//系统时钟周期,单位ns,默认10ns;
    localparam  RST_TIME	        =   10          ;//系统复位持续时间,默认10个系统时钟周期;
    localparam  MULT_D              =   8           ;//被乘数位宽;
    localparam  MULT_R              =   4           ;//乘数位宽;

    reg			                        clk         ;//系统时钟,默认100MHz;
    reg			                        rst_n       ;//系统复位,默认低电平有效;
    reg                                 start       ;//开始运算信号,高电平有效;
    reg     [MULT_D - 1 : 0]            multiplicand;//被乘数;
    reg     [MULT_R - 1 : 0]            multiplier  ;//乘数;

    wire    [MULT_D + MULT_R - 1 : 0]   product     ;//乘积输出;
    wire                                product_vld ;//乘积有效指示信号,高电平有效;
    wire                                rdy         ;//模块忙闲指示信号,高电平表示空闲;

    //例化需要仿真的模块;
    mult #(
        .MULT_D         ( MULT_D        ),//被乘数位宽;
        .MULT_R         ( MULT_R        ) //乘数位宽;
    )
    u_mult (
        .clk            ( clk           ),//系统时钟,默认100MHz;
        .rst_n          ( rst_n         ),//系统复位,默认低电平有效;
        .start          ( start         ),//开始运算信号,高电平有效;
        .multiplicand   ( multiplicand  ),//被乘数;
        .multiplier     ( multiplier    ),//乘数;
        .product        ( product       ),//乘积输出;
        .product_vld    ( product_vld   ),//乘积有效指示信号,高电平有效;
        .rdy            ( rdy           ) //模块忙闲指示信号,高电平表示空闲;
    );

    //生成周期为CYCLE数值的系统时钟;
    initial begin
        clk = 0;
        forever #(CYCLE/2) clk = ~clk;
    end

    //生成复位信号;
    initial begin
        rst_n = 1;start = 0;multiplicand = 0;
        multiplier = 0;
        #2;
        rst_n = 0;//开始时复位10个时钟;
        #(RST_TIME*CYCLE);
        rst_n = 1;
        #(5*CYCLE);
        multiplicand = 4;
        multiplier = 15;
        start = 1'b1;
        #(CYCLE);
        start = 1'b0;
        #(CYCLE);
        repeat(30)begin//产生30组随机数据进行测试;
            @(posedge rdy);
            #(8*CYCLE);
            #1;
            multiplicand = {$random};//产生随机数据,作为被乘数;
            multiplier = {$random};//产生随机数据,作为乘数;
            start = 1'b1;
            #(CYCLE);
            start = 1'b0;
        end
        @(posedge rdy);
        #(8*CYCLE);
        $stop;//停止仿真;
    end

endmodule

  简要截取仿真的一段数据进行查看,如下所示,start信号有效时,乘数为13,被乘数为92。之后被相应的寄存器暂存,然后flag信号为高电平时,每个时钟周期乘数寄存器右移一位,被乘数寄存器数据左移一位。

  如果乘数最低位为1,则乘积寄存器的值就会与被乘数的值相加,得到新的乘积寄存器值,最后当乘数为1时,蓝色的乘积信号就会把乘积寄存器的值460与被乘数的值736相加得到1196作为输出,完成乘法运算。

基于FPGA的高效乘法器_第3张图片

图3 仿真截图

  至此,该模块的设计到此结束,该模块的位宽全部进行了参数化处理,需要修改乘数和被乘数的位宽时,只需要修改位宽的参数即可,代码不需要做任何修改。

  该模块在后续设计中可能作为子模块出现,因为这种靠移位和加法的运算,在面对较大位宽的乘法运算时,可以得到更高的时钟频率。

  源文件可以在公众号后台回复“基于FPGA的乘法器“(不包括引号)获取。

你可能感兴趣的:(FPGA,fpga开发)