八位右移位乘法器
虛假的右移位
其實移位總是相對的,所以右移還是有左移的成分。
左移位乘法器很好理解,因為在列豎式的時候就能看明白,符合我們的常規思維:
也就是說,每一次乘法之后,只要把對應的部分積左移相應的位數,再相加,就可以得到最終的結果,這個過程像極了小學二年級的乘法課,非常的直觀了,這里不做贅述~
那么什么是右移呢?我聽到有同學說,把乘數和被乘數換一下就是了,像這樣:
運算順序從右往左,理論上結果是正確的,就是看起來有點別扭,我們把它轉過來:
這下應該看明白了吧,無非是最后的結果再加移位一次,從而實現一個“虛假的右移位”
真正的右移位
真正的右移位,其實是針對小數計算而言的,我們計算整數乘法的時候,習慣性的左移,根本原因是我們習慣了整數的小數點在最右側,只要不超過那個小數點,就可以直接相加得結果。
而小數計算,因為小數點在左邊的緣故,在計算機當中,就可以根據小數點位置不變的思想,來將部分積右移,從而實現一個右移乘法器。這種思想所成的乘法器,也成為定點乘法器。
以二進制小數計算“0.1101 x 0.1011
”為例:
由圖可以看出,最后一位的結果並不參與移位相加的運算,什么意思呢?
我們再畫詳細點:
計算時,每次得到的部分積,都會與上一次得到的部分積相加,而每次相加結果的最后一位不參與運算,可直接右移轉存,則部分積占用的空間顯然比左移運算小多了。這樣說,想必應該足夠明白了吧?
設計文件(方式一)
知道了上述原理,我們來寫一個右移位的乘法器,邏輯很清晰,每次根據乘數位是否為0
進行判斷,如果是0
,則部分積=前一步的部分積+0
,如果是1
,則部分積=前一步的部分積+被乘數
,同時每次將部分積的最低位存儲至結果的低位中,最后一次的部分積則為結果的高位。
實現代碼如下:
module mul_8_1(result,mul1,mul2);
input wire[7:0] mul1;
input wire[7:0] mul2;
output reg[15:0] res;
reg [7:0] A; //部分積
integer i;
reg [7:0] temp_x; //乘法運算時,被乘數與乘數最后一位的積的最后一位不參與最后的加法運算,將其右移另存。
always @ (*)
begin
A=0;
for (i=0;i<8;i=i+1)
begin
if(mul2[i]) A=A+{1'b0,mul1}; //部分積移位前可能會比乘數多一位
else A=A+9'b000000000;
temp_x[i]=A[0];
A=A>>1;
end
res={A,temp_x};
end
endmodule
在RTL
級電路綜合中卻很少使用for
循環語句。主要原因就是for
循環會被綜合器展開為所有變量情況的執行語句,每個變量獨立占用寄存器資源,每條執行語句並不能有效地復用硬件邏輯資源,造成巨大的資源浪費。也就是說,for
語句循環幾次,就是將相同的電路復制幾次。循環次數越多,綜合電路占用面積越大,綜合就越慢。
所以我們稍微改改寫法,就可以解決for
循環了。
綜合電路
23
個元件,32
個IO
口,202
根線
設計文件(方式二)
所幸八位的乘法器也並不算復雜,可以寫一個並行計算的部分積。這樣也就解決了for
循環的問題。
`define size 8
module mul_8_1(
input wire [`size - 1:0] mul1,mul2,
output wire [2*`size - 1:0] res
);
wire [`size : 0] A[0:`size-1];
wire [`size -1 : 0] low_pro;
assign A[0] = mul2[0] ? {1'b0,mul1} : 9'd0;
assign A[1] = mul2[1] ? ((A[0]>>1) + {1'b0,mul1}) : ((A[0]>>1) + 9'd0);
assign A[2] = mul2[2] ? ((A[1]>>1) + {1'b0,mul1}) : ((A[1]>>1) + 9'd0);
assign A[3] = mul2[3] ? ((A[2]>>1) + {1'b0,mul1}) : ((A[2]>>1) + 9'd0);
assign A[4] = mul2[4] ? ((A[3]>>1) + {1'b0,mul1}) : ((A[3]>>1) + 9'd0);
assign A[5] = mul2[5] ? ((A[4]>>1) + {1'b0,mul1}) : ((A[4]>>1) + 9'd0);
assign A[6] = mul2[6] ? ((A[5]>>1) + {1'b0,mul1}) : ((A[5]>>1) + 9'd0);
assign A[7] = mul2[7] ? ((A[6]>>1) + {1'b0,mul1}) : ((A[6]>>1) + 9'd0);
assign low_pro[0] = A[0][0];
assign low_pro[1] = A[1][0];
assign low_pro[2] = A[2][0];
assign low_pro[3] = A[3][0];
assign low_pro[4] = A[4][0];
assign low_pro[5] = A[5][0];
assign low_pro[6] = A[6][0];
assign low_pro[7] = A[7][0];
assign res = {(A[7] >> 1),low_pro};
endmodule
綜合電路
23
個元件,32
個IO
口,224
根線,和使用for循環的方式相比,多了幾根線??!
好吧果然是我的能力問題,有見解的朋友們請務必說一下,我想學!
測試文件
由於兩個都是乘法器,輸入和輸出端口都一樣,則可以用同樣的測試文件進行測試:
module mul_tb(
);
reg [7:0] mul1,mul2;
wire [15:0] res;
reg clk;
initial begin
mul1 <= 8'd7;
mul2 <= 8'd3;
clk <= 0;
end
always # 10 clk = ~clk;
always @ (posedge clk) begin
mul2 <= mul2 + 1'b1;
end
mul_8_1 try(.mul1(mul1),.mul2(mul2),.res(res),);
endmodule
仿真波形
調成無符號十進制數進行觀察,如圖所示,7x7=49
,7x8=56
,結果正確。