verilog 实现9位有符号乘法器

一、移位相加乘法器

移位相加的原理

从被乘数的最低位开始判断,若为1,则乘数左移i(i=0,1(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是上一个时钟周期的,而Ty_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个时钟周期才会进入第一个状态,开始读取下一组输入数据。所以,这就会导致在这期间,有九个时钟周期的输入数据是不会被读取的,也即下一个输出结果,就已经是第11个时钟周期的输入数据的乘积了。看图:

仿真结果
在这里插入图片描述

所以,为了解决这个问题,你可以在仿真的时候,在testbench程序中,手动的将相邻两组输入数据之间延迟n个时钟周期,只要目的是为了在11个时钟周期之后,采样到的是下一组数据即可。下面展示一下我将输入数据延时11个时钟周期之后的仿真波形,可以看出结果是正确的:

在这里插入图片描述

2、流水线形式

该形式我就不修改成有输入数据宽度参数的通用程序了,因为不同位宽,需要定义不同个数的变量,而变量的个数不容易通过一个值来控制。不过,肯定是有办法的,你自己可以试一下!!!

一般的快速乘法器通常采用逐位并行的迭代阵列结构,将每个操作数的N位都并行地提交给乘法器,但是一般对于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

仿真结果
在这里插入图片描述

从仿真结果可以看出,计算结果是完全正确的。但是,我们需要注意的是,由于上面采用了3级流水线,再加上mul_a_regmul_b_reg的一级延时,一共有4个时钟周期的延时。所以,为了保证计算结果的符号位是正确的,我们的msb也要做一个四级的延时,这样才能刚好和result的延时对应,输出结果的符号才不会出错。

你可以自己改变一下msb的延迟周期,看一下计算结果是不是正确的。

二、并行乘法器

也就是用乘法运算符实现,下面的代码实现8bit无符号数的乘法。若要做有符号数乘法,需将数据声明为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、仿真结果

在这里插入图片描述

posted @ 2020-09-28 09:31  耐心的小黑  阅读(628)  评论(0编辑  收藏  举报