1.普通乘法器
研究了半天特权同学的16位乘法器的移位累加部分的代码,始终没有搞清楚其中的原理。希望特权同学能对该段代码给出一个详细的分析,举例说明每一步具体是怎样移位并累加的。
本人个人认为:两个二进制数之间相乘,就是用乘数从最低位开始,每一位依次去和被乘数相乘,最终再将每一次所得的乘积相加,这样就得到了最终的乘积。但要注意的是,和十进制数的乘法类似,用乘数的某一位去和被乘数相乘时所得到的结果的最低位必须与该乘数所在位对齐,即每一步所得到的乘积应该依次左移移位,呈阶梯状排列。
基于以上分析,本人对特权同学的移位累加部分的代码作了相应的改进:
if(i==0)
begin //锁存乘数、被乘数
areg <= ain;
breg <= bin;
end
else if(i > 5'd0 && i < 5'd17)
if(areg[i-1])
yout_r <= yout_r+({16'h0000,breg}<<(i-1));
关键语句为红色标示那句,当乘数a的某一位为1(为0可以忽略,因为0和b相乘得到的结果也为0)时,与b相乘的结果为b(16位),在前面补上16个0后,结果即为32位,再左移i-1位,即将该结果前面的i-1个0移到最后补齐。现举例分析(由于16位相对繁琐一点,所以以两个4位数相乘为例,原理都是一样的):
(b)1 0 1 0
x (a)1 1 0 1
------------------
0 0 0 0 1 0 1 0(i=1)
0 0 0 0 0 0 0 0 (i=2)
0 0 0 0 1 0 1 0 (i=3)
+0 0 0 0 1 0 1 0 (i=4)
-------------------------------
左移(i-1)位后:
(b)1 0 1 0
x (a)1 1 0 1
------------------
0 0 0 0 1 0 1 0 (i=1)
0 0 0 0 0 0 0 0 (i=2)
0 0 1 0 1 0 0 0 (i=3)
+ 0 1 0 1 0 0 0 0 (i=4)
-------------------------------
= 1 0 0 0 0 0 1 0
附上修改后完整代码mux16.v
module mux16( clk,rst_n, start,ain,bin,yout,done ); input clk; //芯片的时钟信号。 input rst_n; //低电平复位、清零信号。定义为0表示芯片复位;定义为1表示复位信号无效。 input start; //芯片使能信号。定义为0表示信号无效;定义为1表示芯片读入输入管脚得乘数和被乘数,并将乘积复位清零。 input[15:0] ain; //输入a(被乘数),其数据位宽为16bit. input[15:0] bin; //输入b(乘数),其数据位宽为16bit. output[31:0] yout; //乘积输出,其数据位宽为32bit. output done; //芯片输出标志信号。定义为1表示乘法运算完成. reg[15:0] areg; //乘数a寄存器 reg[15:0] breg; //乘数b寄存器 reg[31:0] yout_r; //乘积寄存器 reg done_r; reg[4:0] i; //移位次数寄存器 //------------------------------------------------ //数据位控制 always @(posedge clk or negedge rst_n) if(!rst_n) i <= 5'd0; else if(start && i < 5'd17) i <= i+1'b1; else if(!start) i <= 5'd0; //------------------------------------------------ //乘法运算完成标志信号产生 always @(posedge clk or negedge rst_n) if(!rst_n) done_r <= 1'b0; else if(i == 5'd16) done_r <= 1'b1; //乘法运算完成标志 else if(i == 5'd17) done_r <= 1'b0; //标志位撤销 assign done = done_r; //------------------------------------------------ //专用寄存器进行移位累加运算 always @(posedge clk or negedge rst_n) begin if(!rst_n) begin areg <= 16'h0000; breg <= 16'h0000; yout_r <= 32'h00000000; end else if(start) begin //启动运算 if(i == 5'd0) begin //锁存乘数、被乘数 areg <= ain; breg <= bin; end else if(i > 5'd0 && i < 5'd17) begin if(areg[i-1]) yout_r = yout_r + ({16'h0000,breg}<<(i-1)); //累加并移位 end end end assign yout = yout_r; endmodule
另附完整测试脚本代码tb_mux16.v
module vtf_test; reg clk; //芯片的时钟信号。 reg rst_n; //低电平复位、清零信号。定义为0表示芯片复位;定义为1表示复位信号无效。 reg start; //芯片使能信号。定义为0表示信号无效;定义为1表示芯片读入输入管脚得乘数和被乘数,并将乘积复位清零。 reg[15:0] ain; //输入a(被乘数),其数据位宽为16bit. reg[15:0] bin; //输入b(乘数),其数据位宽为16bit. wire[31:0] yout; //乘积输出,其数据位宽为32bit. wire done; //芯片输出标志信号。定义为1表示乘法运算完成. mux16 uut( .clk(clk), .rst_n(rst_n), .start(start), .ain(ain), .bin(bin), .yout(yout), .done(done) ); initial begin clk = 0; forever #10 clk = ~clk; //产生50MHz的时钟 end initial begin rst_n = 1'b0; start = 1'b0; ain = 16'd0; bin = 16'd0; #1000; rst_n = 1'b1; //上电后1us复位信号 #1000; ain = 16'd89; bin = 16'd33; #100; start = 1'b1; #4500; start = 1'b0; #1000_000; $stop; end endmodule
此程序代码可以完成两个16位二进制数最大数相乘65535x65535=4294836225。完美。
2.流水线乘法器
一般的快速乘法器通常采用逐位并行的迭代阵列结构,将每个操作数的N位都并行地提交给乘法器。但是一般对于FPGA来讲,进位的速度快于加法的速度,这种阵列结构并不是最优的。所以可以采用多级流水线的形式,将相邻的两个部分乘积结果再加到最终的输出乘积上,即排成一个二叉树形式的结构,这样对于N位乘法器需要lb(N)级来实现。
module multi_4bits_pipelining(mul_a, mul_b, clk, rst_n, mul_out); input [3:0] mul_a, mul_b; input clk; input rst_n; output [7:0] mul_out; reg [7:0] mul_out; reg [7:0] stored0; reg [7:0] stored1; reg [7:0] stored2; reg [7:0] stored3; reg [7:0] add01; reg [7:0] add23; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin mul_out <= 0; stored0 <= 0; stored1 <= 0; stored2 <= 0; stored3 <= 0; add01 <= 0; add23 <= 0; end else begin stored0 <= mul_b[0]? {4'b0, mul_a} : 8'b0; stored1 <= mul_b[1]? {3'b0, mul_a, 1'b0} : 8'b0; stored2 <= mul_b[2]? {2'b0, mul_a, 2'b0} : 8'b0; stored3 <= mul_b[3]? {1'b0, mul_a, 3'b0} : 8'b0; add01 <= stored1 + stored0; add23 <= stored3 + stored2; mul_out <= add01 + add23; end end endmodule