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