verilog乘法器以及booth编码改进
第一章 整數乘法器
1.1 整數的概念
整數在IEEE 的規定上有,短整數short integer , 中整數integer 和 長整數long integer ,它們之間的關系如下:
?
?
整數 | 字節空間 | 取值范圍 |
短整數 | 一個字節 | -127 ~ 127 |
中整數 | 兩個字節 | -32767~32767 |
長整數 | 和四個字節 | -2147483647~2147483647 |
?
?
在這里筆者以短整數為筆記的主角。
?
?
短整數的最高位是符號位,符號位的正負表示了該值是“正還是負”?。正值的表示方法很簡單,反之負值的表示方法是以補碼來表示。
?
?
+127 亦即8'b0111_1111;
+4 亦即8'b0000_0100;
-127 亦即8'b1000_0001;
-4 亦即8'b1111_1100;
?
?
補碼在英文又叫2nd?implementation?? , 其實是“正值的求反又加一”的操作。(哎~年輕時的筆者曾經這個東西頭疼過)。一個負值表示如-4 ,是由+4 求反由加一后而成。
?
?
8'b0000_0100;? // 正值4
8'b1111_1011;? // 求反
8'b1111_1100;? // 加1 , 負值4
那么符號位和正值,負值,補碼,取值由有什么關系呢?舉個例子 :A = 8'b0111_1111 (+127) 和B = 8'b1000_0001 ( -127 )。
當我們在進行判斷一個短整數是正值還是負值的時候,我們可以這樣表示:
?if ( !A[7] ) ...? // A是正值
?if ( B[7] ) ...? // B是負值
在事實的事實上。我們知道短整數 28?,亦即取值范圍是0~255,但是符號位的出現吃掉了最高位,所以造成由28?的取值范圍變成27?= 0~127 。
你知道嗎?在短整數家族里面永遠存在一個幽靈成員。該成員很神秘,它不是正值,即不是負值或者0值。而且它的能力也不可忽視,它劃分了正值和負值的邊界,它就是8'b1000_0000。
+127 ??? 8'b0111_1111;
劃分????? 8'b1000_0000;
-127 ???? 8'b1000_0001;
換句話說,在8'b1000_0000 之前的都是正值 ,然而在8'b1000_0000 之后是負值。如果讀者硬是要說8'b1000_0000 是 “負0”,筆記也無話可說......
從上述的內容,我們可以知道:正值可以進行求反又加一之后成為負值。那么負值如何變成正值?同樣的一個道理“負值求反又加一后,成為正值”。
8'b1111_1100;? // 負4
8'b0000_0011;? // 求反
8'b0000_0100;? // 加1 , 正4
?
1.2 傳統乘法的概念
筆者還記得筆者在上小學三年級的時候,老師在黑板上寫上3 x 4 = 12。筆者對這神秘的數學公式迷糊了頭腦。后來老師解釋道: " 3粒蘋果重復加上4 次等于12粒蘋果",小時的筆者頓時恍然大悟!
當筆者上了初中,老師在黑板上寫上3 + -4 = -1。大伙們都明白那是整數,但是初中的筆者,腦袋過很遲鈍。因為在現實中,初中的筆者認為沒有“-3粒蘋果”類似實體的概念純在,后來老師解釋道:“ 小明欠小黃4粒蘋果,后來小明還了小黃3粒蘋果,結果小明還欠小黃一粒蘋果 ”,初中的筆者又恍然大悟。
又在初中,當老師又在黑板上寫上如下的內容。那時候的筆者,嘴巴長得大大 ,有好一段時間說不出話來 。好一段時間筆者都是自己在嘀咕....
?3 x 4 = 12;??? " 3粒蘋果重復疊加4次,等于12粒蘋果"
-3 x 4 = -12;? ? " 欠3粒蘋果,重復欠4次,等于欠12粒蘋果"
3 x -4 = -12;??? " 欠4粒蘋果,重復欠3次,等于欠12粒蘋果"
-3 x -4 = 12;??? " @#¥%#¥*!%……" ( 嘀咕中... )
讀者們不要笑,上述的故事確實是筆者的真實故事。那時候的筆者,真的拿不到整數的乘法的門兒,考試還常常滿江紅,真的悲劇的初衷時代......
在傳統的概念上乘法等價于“重復幾次”。打個比方:B = 4;A x B 亦即A要重復加四次才能得到答案。
然而在乘法中“負值正值的關系”就是“異或的關系”。
A值 | B值 | 結果 |
正(0) | 正(0) | 正(0) |
正(0) | 負(1) | 負(1) |
負(1) | 正(0) | 負(1) |
負(1) | 負(1) | 正(0) |
?A x B = C;
?3 x 4 = 12;???
-3 x 4 = -12;???
3 x -4 = -12;???
-3 x -4 = 12;?
從上面的內容看來,無論A值和B值是什么樣的“正值和負值的關系”,結果C都是一樣。
那么我們可以換一個想法:
“在作乘法的時候只是我們只要對正值進行操作。然而“負值和正值的結果”,我們用“異或”關系來判斷... ”
實驗一 :傳統的乘法器
該乘法器的大致操作如下:
(一)在初始化之際,取乘數和被乘數的正負關系,然后取被乘數和乘數的正值。
(二)每一次累加操作,遞減一次乘數。直到乘數的值為零,表示操作結束。
(三)輸出結果根據正負關系取得。
multiplier_module.v
第3~11行是該模塊的輸入輸出。看到Start_Sig 和Done_Sig 是仿順序操作的標志性結構,不明白的去看筆者之前寫的筆記。Multiplicand 和Multiplier (被乘數和乘數),都是8位位寬,所以輸出Product 是16位位寬。
第16~21行是該模塊所使用的寄存器,i寄存表示步驟,Mcand 用來暫存Multiplicand 的正值,Mer 用來暫存Multiplier 的正值,Temp 寄存器是操作空間。然而isNeg 標志寄存器是用來寄存Multiplicand 和Multiplier 之間的正負關系。
在步驟0(36~45行)是初始化的步驟。第39行isNeg寄存“乘數和被乘數之間的正負關系”。第40行,Mcand寄存 Multiplicand 的正值,該行表示:如果被乘數的符號位是邏輯1的話,就將負值轉換為正值,然后Mcand寄存該值,否則Mcand直接寄存Multiplicand 的值。第41行是用來寄存Multiplier 的正值,該行的操作和40行很相識。
在步驟1(47~49行),是“重復加幾次”的操作。Temp寄存器的每一次值的疊加,Mer寄存就遞減(49行)。直到Mer的值等于0(48行),就進入下一個步驟。步驟2~3是產生完成信號。
在62行,Product輸出信號的輸出值是由isNeg寄存器作決定,如果isNeg是邏輯1,那么Temp的結果從負值轉換為正值。否則直接輸出Temp的值。
multiplier_module.vt
?
第16~22行是復位信號和時鐘信號的激勵。第26~35行是multiplier_module.v 的實例化。
第39行以下和普通的仿順序操作的寫法一樣,不明白的話請看筆者以往寫過的筆記。
步驟0~3, 會輸入不同的乘數和被乘數來激勵multiplier_module.v。
仿真結果:
?實驗說明:
其實傳統的乘法器是很容易的,但是短整數的出現,負值和正值隨著出現,使得設計上難以下手。但是只要掌握負值和正值的關系以后,乘法只作正值也“無問題”。只要在輸出下一點手腳就行了。
實驗結論:
傳統的乘法器雖然簡單,但是它有一個致命的問題。就是被乘數越大就越消耗時鐘。具體的原因在下一章節解釋......
1.3 傳統乘法器的改進
Verilog HDL 語言所描述的乘法器的消耗是以“時鐘”作為時間單位。反之,組合邏輯所建立的乘法器是以“廣播時間”作為時間單位。說簡單點就是,Verilog HDL 語言所描述的乘法器“快不快”是根據“時鐘消耗”作為評估。
假設A = 10 , B = 20,? A x B ,那么時鐘的消耗至少需要20個,因為A值需要累加20次才能得到結果。到底有沒有什么辦法,改進這個缺點呢?
有學過乘法的朋友都知道A ( B ) = B ( A )。如果以實驗一的乘法器作為基礎,那么A( B ) 和B( A ) 所消耗的時間就不一樣了。所以我們可以這樣改進:
如果被乘數小于乘數,那么被乘數和乘數互換。
{ Multiplier , Multiplicand } = Multiplicand < Multiplier ? { Multiplicand ,Multiplier } :
???????????????????????? {Multiplier ,Multiplicand };
?
舉個例子:Multiplicand = 2 ,Multiplicand = 10 ;
更換之前 被乘數2 需要10次的累加,才能得到結果。 更換之后 被乘數為10 乘數為2,亦即被乘數10只要累加2次就能得到結果。
如此一來,可以減少不少時鐘的消耗。
?
實驗二: 傳統乘法器改進
和實驗一相比,在進行累加操作之間,多了一個被乘數和乘數比較的步驟。
(一)在初始化之際,取乘數和被乘數的正負關系,然后取被乘數和乘數的正值。
(二)乘數和被乘數比較,如果被乘數小于乘數,結果乘數和被乘數互換。
(三)每一次累加操作,遞減一次乘數。直到乘數的值為零,表示操作結束。
(四)輸出結果根據正負關系取得。
multiplier_module_2.v
和實驗一先比,添加了一個比較的步驟(46~49行)。仿真結果:
仿真.vt 文件和實驗一樣。
?
在仿真的結果上,10 x 2 和2 x 10 的時鐘消耗都一樣。
實驗說明:
與實驗一的乘法器比較,關于時鐘的消耗多少都有改進。
實驗結論:
傳統的乘法器無論如何改進也好,當遇見如127 x 127 的乘數和被乘數,咋也看不出什么優化......
1.4 補碼君存在的意義
每一個人都有存在的意義,有的人用一生的時間去尋找自己的存在意義,有的人則是經過生活的大反轉,看到了自己存在意義,有的人則不聞不問... 當然補碼也有存在的意義,只是在前面的實驗被筆者濫用而已。
補碼不僅可以執行正值和負值轉換,其實補碼存在的意義,就是避免計算機去做減法的操作。
? ?? 1101 ??? -3補
+ ?? 1000??? 8
????? 0101??? 5
?
假設-3 + 8,只要將-3 轉為補碼形式,亦即0011 => 1101,然后和8,亦即1000相加
就會得到5,亦即0101。至于溢出的最高位可以無視掉。
1101 ??? -3補
+???? 1110???? -2補
?? 1011??? -5補
其實你知道嗎,如Quartus II 綜合器 ,當我們使用“-”算術操作符的時候,其實就是使用補碼的形式,具體如下:
A = 8'd5;
B = 8'd9;
A -B 等價于A + ( ~B + 1'b1 );
在實際的操作中,綜合器都會如上優化。
?
1.5:Booth算法乘法器
傳統的乘法器是有極限的,因此位操作乘法器就出現了。筆者在網上沖浪找資源的時候,還常常撞到許多稀奇古怪的位操作乘法器。但是有一種位操作乘法器,吸引了筆者的眼球,它就是Booth算法乘法器。實際上Booth 算法是一種“加碼”乘法運算。
Booth 算法的概念也很簡單,我們先從數學的角度去理解看看:
?
B[0] | B[-1] | 加碼結果 |
0 | 0 | 0(無操作) |
0 | 1 | 1(+被乘數) |
1 | 0 | 1(-被乘數) |
1 | 1 | 0(無操作) |
?
B[-1] 是什么?先假設B是2的,然而B的最低位的右邊后一個“負一位”那就是B[-1]。
0010 0? // LSB 右邊出現的就是-1位
那么上面那個加碼表和乘法又有什么關系呢?其實要加碼的目標是“乘數”,假設乘數為2, 那么乘數2的加碼過程會是如下。
一開始的時候在乘數2的“負一位”加上一個默認0值 | 0010?0 |
先判斷[0: -1],結果是2'b00,表示“0”亦即沒有操作 | 0010 0 |
判斷[2: 1],結果是2'b01,表示“1”亦即“-被乘數”操作 | 0010 0 |
判斷[1: 0],結果是2'b10,表示“1”亦即“+被乘數”操作 | 0010 0 |
判斷[3: 2],結果是2'b00,表示“0”亦即沒有操作 | 0010 0 |
舉個例子,被乘數為7,0111; 乘數為2,0010;結果會是什么?
????? 0111? ???? - A被乘數 ????? 0010?0? - B乘數 ? ========== ????? 0110? ??? - 乘數加碼 ? ========== ????? 0000???? 0 ?? 111001??????1?(- 7) ??? 0111?????? 1 (+7) ?? 0000 ?????? 0 ? ========== ?? 0001110???? 14??? ? ==========???????? | ? ? ? ? 從左邊的操作過程中,我們可以看到乘數被加碼以后, 操作的結果是14。 |
從數學的角度看來,確實Booth算法是麻煩的存在,但是在位操作的角度來看就不是這么一回事。實際上在千奇百怪的位操作乘法中,Booth算法其中可以容納“補碼”亦即“負數”來執行操作。
B[0] | B[-1] | 加碼結果 |
0 | 0 | 無操作,右移一位 |
0 | 1 | +被乘數,右移一位 |
1 | 0 | -被乘數,右移一位 |
1 | 1 | 無操作,右移一位 |
?
上面的圖表是位操作時候的Booth 算法。Booth算法在位操作的時候,它使用一個很有個性的空間,就是P空間。
?
先假設:被乘數A 為7 (0111),乘數B為2 (0010) ,它們n均為4位,所以P空間的容量是n x 2 + 1 , 亦即9 位。
_ _ _ _ _ _ _ _? _? // P空間
那么P空間如何實現乘法的位操作呢?
一開始先求出-1 (被乘數) | ?A = 0111,A= 1001 |
然后初始化P 空間, 默認為0 | ?P = 0000 0000 0 |
P空間的[4..1] 填入乘數 | ?P = 0000 0010 0 |
判斷P[1:0],是2'b00 亦即“無操作” | ?P = 0000 0010 0 |
判斷P[8], 如果是邏輯0右移一位補0,反之補1。 | ?P = 0000 0001 0 |
判斷P[1:0],是2'b10 亦即“-被乘數”。 | ?P = 0000 0001 0 |
P空間的[8..5] 和 被乘數?A相加。 | ?P = 0000 0001 0 ?+? 1001????? ?P = 1001 0001 0 |
判斷P[8], 如果是邏輯0右移一位,補0,反之補1 | ?P = 1100 1000 1 |
判斷P[1:0],是2'b01 亦即“+被乘數”。 | ?P = 1100 1000 1 |
P空間的[8..5] 和 被乘數 A 相加。 | ?p = 1100 1000 1 ?+? 0111????? ?P = 0011 1000 1?無視最高位溢出 |
判斷P[8], 如果是邏輯0右移一位補0,反之補1 | ?P = 0001 1100 0 |
判斷P[1:0],是2'b00 亦即“無操作” | ?P = 0001 1100 0 |
判斷P[8], 如果是邏輯0右移一位,補0,反之補1 | ?P = 0000 1110 0 |
最終P空間的[8..1] 就是最終答案。 | ?P =?0000 1110?0 |
從上面的操作看來,由于乘數和被乘數均為n 位所以 “判斷P[1:0],后操作,之后移位”的操作僅執行四次而已。
?
? 如左邊的循環圖。A為被乘數,A為被乘數補碼形式(-1(A) ),B為乘數,n為乘數和被乘數的位寬,P為操作空間。 ? 一開始P空間會初始化,然后P空間的[4..1] 位會填入B。然后進入P[1:0]的判斷。每一次的判斷過后的操作都會導致P空間右移一次,至于右移過后的最高位是補0還是補1,是由當時P[8]說了算。 ? ? 當循環n 次以后,最終結果會是P[8:1]。 |
?
實驗三:Booth算法乘法器
實驗中建立的Booth算法乘法器大致的步驟正如1.5章節所描述的那樣。
booth_multiplier_module.v
第13~15行是仿真的輸出(S - Simulation , Q - Output)。第20~25行定義了該模塊所使用的寄存器。a寄存器用來寄存A 值,s寄存器用來寄存-1(A) 的值,p寄存器是P空間。輸入信號A和B均為8位位寬,所以p寄存器是17位位寬。至于X寄存器是用來表示n位,用來指示n 次循環。
步驟0(40~41行),初始化了a,s寄存器。p[8:1]填入B值,亦即乘數,其余的位均為0值。
步驟1(43~51行)是用來判斷p[1:0] 的操作。步驟2(53~55行)是執行右移一位,是補0還是補1,完全取決于p[16]。步驟1~2會重復交替執行,直到X的值達到8次,就會進入下一步步驟。
步驟3~4(57~61行)是用來產生完成信號。第68行輸出信號product 是由p空間的[16..1]來驅動。第72~74行是仿真用的輸出信號,功能如字面上的意思。
booth_multiplier_module.vt
?
在仿真中,從步驟0~3(59~73行),激勵了不同A和B的值(被乘和數乘數)。
仿真結果:
P空間的詳細操作過程,自己代開modelsim看吧,界面有限的關系。從仿真結果上可以看到,4次的乘法操作所使用的時間都一樣,尤其是-127 x -127 的情形,不像傳統乘法器那樣累加127次,才能得到結果。(p空間的[ Width :1]是用來填入乘數B,然而p空間的[Width * 2 : Width + 1 ] 是用來執行和被乘數A的操作)
實驗結論:
按常理來說8位的乘數和被乘數,位操作會是使用8個時鐘而已,但是實驗3的乘法器,需要先操作后移位的關系,所以多出8個時鐘的消耗......
?
1.6 筆者情有獨鐘的步驟i
在筆者初學Verilog HDL語言,筆者老是捉不好Verilog HDL 語言和時序的關系,吃了不少苦頭。世界就是很巧妙,腦子里就忽然間冒出步驟i。
?
步驟i是什么?
有關《Verilog HDL 那些事兒》那本筆記,雖然筆者的實例都和“它”有關。但是在筆記中,筆者只是微微的帶過“步驟i是仿順序操作相關的寫法... ”。但是要探討步驟i是什么,那不是初學Verilog HDL 的任務。步驟i的用法很簡單,從概念上和“順序操作”很類似,它可以補助初學者不必過度依賴功能仿真,也能“從代碼中看到時序”。
如果從低級建模的角度去探討步驟i,低級建模里面有一個準則,就是“一個模塊一個功能”,步驟i好比這個準則的支持者。步驟i從0開始,表示了這個模塊開始工作,直到i被清理,這也表示了這個模塊已經結束工作。或者可以這樣說“一個模塊不會出現兩個步驟i”。
?
具體上,步驟i的“值”是指示著“第幾個時鐘沿”發生,然而Verilog HDL語言里的“步驟”和C語言里的“步驟”是不一樣。C語言里的“步驟”就好比“把大象放進冰箱需要幾個步驟... ”。相反的Verilog HDL 語言里的“步驟”,有如“時間點”的觀念。
如上面的示意圖所示, 在這個時間點里所發生的“決定”會產生不一樣的未來。然而在這個時間點里“可以允許不同的決定在這一刻存在”。舉一個例子:A的初值是4,B的初值是0。
?
case( i )
0:
begin A <= A + 2'd2; B <= B + 2'd3; i <= i + 1'b1; end
1:
if( A > 3 ) begin B <= A; A = 0; i <= i + 1'b1; end
else if i <= i + 1'b1;
?
咋看是一個簡單的代碼,但是你知道里邊包含的秘密嗎?
在i = 0的時候,A 累加2,B 累加3。
在i = 1的時候,如果A大于3,就B寄存A的值將A清零。
無論是i等于0還是等于1,它們“只是這一時間點發生的決定”,結果會在這個時間點的過后發生。如果用“生動”的話來描述的話。
在時間點0的時候,這個模塊決定A累加2,B累加3。然后在時間點0過后,結果就產生。直到迎來下一個時間點,這個模塊才能再一次作決定。
在時間點1的時候,這個模塊判斷A是否大于3。那么,問題來了“這個模塊是以什么作為基礎,判斷A大于3呢?”。答案很簡單就是“時間點1的過去的結果”或者說“在時間點0過后所產生的結果”。
上圖完全將上述的內容表達了出來。在這里筆者有一個很在意的問題,那就是"<=" 賦值操作符。在眾多的參考書中“<=”賦值操作符被解釋為“時間沿有效的賦值操作符”。筆者初學的時候的,完全不知道它是蝦米... 如果換做時間點的概念來說“<=”的操作符,表示了“在這個時間點下決定”專用的賦值操作符。
與“=”賦值操作符不一樣,它是沒有時間點的概念的賦值操作符。所以在always @ ( posedge CLK ... ) 有效區域內,它是不適合使用,因為它會破壞這個模塊的時間和結果。
我們的人生,下錯了決定只要知錯,吸取教訓還有從來的機會。但是模塊下錯了決定,就影響它的一生,所以我們在編輯的時候要特別小心,不然會可能因我們的疏忽,導致了這個模塊的一生悲劇。
小時候,筆者讀道德教育的時候,有一句話是筆者一生受用,那就是“先三思而后行”。
這個又和上述的內容有什么關系呢?
我們知道“時間點”的概念就是“就是在這個時間點決定了什么,這個時間點的過后會產生什么”。難道模塊的世界就是那么現實, 就連三思的機會也沒有嗎?這是一個很好的問題......
舉個例子,有一個模塊他有A ,B和C三個寄存器,它們的初值都是0:
case( i )
?? 0:
?? begin A <= 3; B <= 4; C <= 0; i <= i + 1'b1; end
?? 1:
?? begin
?????? C <= A + B;
?????? if( C > 0 ) begin A <= 0; B <= 0 ; end
?????? else begin A <= 1; B <= 1; end
?????? i <= i + 1'b1;
?? end
從上面的代碼,我們可以知道。在時間點0,該模塊決定了A 等于3,B等于4,C等于0。然后到了時間1, 問題來了“在時間點1,該模塊是以什么作為基礎去判斷C 的值呢?是時間點1過去的C值,還是在這一個瞬間A + B 所產生的值?”。
答案如上圖所示,if是以時間點1過去的C值作為判斷的基礎。所以說模塊的現實是很殘忍的,它們不但沒有重來的機會,就連“思考”的時間也不給它。它們"只能以當前時間點過去的值,作為當前時間點下決定的參考......? ( 寫到這里, 筆者流淚了! )
實際上“=”不是不可以在always @ ( posedge CLK ... ) 里出現,只不過它比較危險。
case( i )
?? 0:
?? begin A <= 3; B <= 4; C <= 0; i <= i + 1'b1; end
?? 1:
?? begin
?????? C = A + B;
?????? if( C > 0 ) begin A <= 0; B <= 0 ; end
?????? else begin A <= 1; B <= 1; end
?????? i <= i + 1'b1;
?? end
筆者將上面的代碼稍微修改了一下, 在步驟1 變成了C = A + B。
如果把步驟i按照“時間點”的概念,結果會是如上圖。在時間點1,“=”造成了一個而外的時間停止空間,在這個空間里C 不但可以“作決定”,而且“即時得到結果”。在某種程度上,它的存在會破壞和諧,如果沒有步驟i的控制,它很容易暴走。筆者在設計模塊中,除非出現“不得已”的情況,否則筆者在always @ ( posedge CLK ... )區域內,絕對不會使用它。
?
1.7 Booth算法乘法器的改進
在實驗三中所建立的Booth算法乘法器,要完成一次乘法計算,至少要消耗16個時鐘,而且其中8個時間就是消耗在移位的方面上。那么有什么辦法改進 實驗三中的Booth算法乘法器呢?
在1.6章節,筆者說了步驟i有如時間點的概念,假設我這樣修改實驗三的Booth乘法器 :
case ( i )
???
?? 0: ... 初始化
??
?? 1,2,3,4,5,6,7,8:
?? begin
?????? if( p[1:0] == 2'b01 ) p <= { p[16] , p[16:9] + a , p[8:1] };
?????? else if( p[1:0] == 2'b10 ) p <= { p[16] , p[16:9] + s , p[8:1]};
?????? else p <= { p[16] , p[16:1]};
?????? i <= i + 1'b1;
?? end
從上面的代碼,讀者能看出什么破綻嗎?我們嘗試回憶Booth算法的流程圖,先判斷p[1:0] 的操作,然后右移一位,最高位補0還是補1,是取決與 經p[1:0]操作之后的p[16]。
那么問題來了,從上面的代碼看來p <= { p[16] , p[16:9] + a , p[8:1]}; 其中的p[16] 是以當前時間點的過去值作為基礎,而不是p[1:0]操作過后的值, 所以上面的代碼不行!
case( i )
?
0: ... 初始化
?
1,2,3,4,5,6,7,8:
begin
??? Diff1 = p[16:9] + a;? Diff2 = p[16:9] +s;
???
??? if( p[1:0] == 2'b01 ) p <= { Diff1[7] , Diff1 , p[8:1]};
??? else if( p[1:0] == 2'b10 ) p <= { Diff2[7] , Diff2 , p[8:1]};
??? else p <= { p[16] , p[16:1]};
?
??? i <= i + 1'b1;
end
?
上面的代碼表示了,在步驟1~8里Diff1 寄存了p[16:9] + a 的結果,反之Diff2 寄存了p[16:9] + s的結果。然后判斷p[1:0] 再來決定p 的結果是取決于Diff1 ,Diff2 或者其他。和第一段的代碼不同,第二段代碼的p輸出值是一致的。在這里有一個重點是,Diff1 和Diff2 沒有使用“<=”而是使用“=”,換一句話說,Diff1 和Diff2 結果的產生在“該時間點作決定的時候”,亦即“取得即時的結果”,而不是該時間點過后,才產生結果。
實驗四:Booth算法乘法器改進
基于實驗三的Booth算法乘法器,從原先的一次乘法需要16次、個時鐘,優化至8個時鐘。
booth_multiplier_module_2.v
?
同樣是Booth 算法的原理,和實驗三不同的是在55~67行,是步驟1~8的循環操作。不再使用X寄存器作為循環計數,而是直接使用步驟來指示8個循環操作。在55~67行,這樣的寫法有一個好處,就是可以使得p的值輸出一致,因此可以減少8個時鐘。
仿真結果:
實驗四所使用的.vt 文件和實驗三的一樣。
從仿真結果看來,一次的乘法操作只消耗8個時鐘而已(步驟0初始化,和步驟9~10完成信號產生除外)。現在我們把上面的仿真結果切成一塊一塊的來看。
?
? ? | 00000000 10000001 0 值左邊上升沿開始,即是第一個時間點i = 0,亦即步驟0。步驟0之后就是初始化的結果。S是取反過后的a值,并且填充在p空間的[8:1]。 |
? ? | 00000000 10000001 0 值右邊的上升沿,亦即步驟1。此時: Diff1 寄存過去的p[16:9] + a ,亦即00000000 + 10000001, 結果為10000001。Diff2 寄存過去的p[16:9] + s,亦即00000000 + 01111111, 結果為01111111。經步驟1的“決定”,過去p[1:0]是 2'b10 ,所以p值的未來是{ Diff2[7] , Diff2 , p過去[8:1] }, 亦即 0 01111111 10000001。 |
? ? | 00111111 11000000 1 值右邊的上升沿,亦即步驟2。此時: Diff1 寄存過去的p[16:9] + a ,亦即00111111 + 10000001, 結果為11000000。Diff2 寄存過去的p[16:9] + s,亦即00111111 + 01111111, 結果為10111110。經步驟2的“決定”,過去p[1:0]是 2'b01 ,所以p值的未來是{ Diff1[7] , Diff1 , p過去[8:1] }, 亦即 1 11000000 11000000。 |
? ? | 11100000 01100000 0 值右邊的上升沿,亦即步驟3。此時: Diff1 寄存過去的p[16:9] + a ,亦即11100000 + 10000001, 結果為01100001。Diff2 寄存過去的p[16:9] + s,亦即11100000 + 01111111, 結果為01011111。經步驟3的“決定”,過去p[1:0]是2'b00 ,所以p值的未來是{ p過去[16] , p過去[16:1] }, 亦即 1 11100000 01100000。 |
11110000 00110000 0 值右邊的上升沿,亦即步驟4。此時: Diff1 寄存過去的p[16:9] + a ,亦即11110000 + 10000001, 結果為01110001。Diff2 寄存過去的p[16:9] + s,亦即11110000 + 01111111, 結果為01101111。經步驟4的“決定”,過去p[1:0]是2'b00 ,所以p值的未來是{ p過去[16] , p過去[16:1] }, 亦即 1 11110000 00110000。 ? ? |
11111000 00011000 0 值右邊的上升沿,亦即步驟5。此時: Diff1 寄存過去的p[16:9] + a ,亦即11111000 + 10000001, 結果為01111001。Diff2 寄存過去的p[16:9] + s,亦即11111000 + 01111111, 結果為01110111。經步驟5的“決定”,過去p[1:0]是2'b00 ,所以p值的未來是{ p過去[16] , p過去[16:1] }, 亦即 1 11111000 00011000。 ? ? | |
11111100 00001100 0 值右邊的上升沿,亦即步驟6。此時: Diff1 寄存過去的p[16:9] + a ,亦即11111100 + 10000001, 結果為01111101。Diff2 寄存過去的p[16:9] + s,亦即11111100 + 01111111, 結果為01111011。經步驟6的“決定”,過去p[1:0]是2'b00 ,所以p值的未來是{ p過去[16] , p過去[16:1] }, 亦即 1 11111100 00001100。 ? ? | |
11111110 000001100 0 值右邊的上升沿,亦即步驟7。此時: Diff1 寄存過去的p[16:9] + a ,亦即11111110 + 10000001, 結果為01111111。Diff2 寄存過去的p[16:9] + s,亦即11111110 + 01111111, 結果為01111101。經步驟7的“決定”,過去p[1:0]是2'b00 ,所以p值的未來是{ p過去[16] , p過去[16:1] }, 亦即 1 11111110 00000110。 ? ? | |
11111111 00000011 0 值右邊的上升沿,亦即步驟8。此時: Diff1 寄存過去的p[16:9] + a ,亦即11111111 + 10000001, 結果為10000000。Diff2 寄存過去的p[16:9] + s,亦即11111111 + 01111111, 結果為 01111110。經步驟8的“決定”,過去p[1:0]是2'b10 ,所以p值的未來是{Diff2[7] , Diff2, p過去[8:1] }, 亦即 0 01111110 00000011。 ? ? 最終結果取值未來p[16:1] ,00111111 00000001 亦即16129。 |
實驗說明:
如果以“大象放進冰箱”這樣的概念去理解步驟i,在實驗四中可能會產生許多思考邏輯上的矛盾。換一個想法,如果以“時間點”的概念去理解步驟i的話,從仿真圖看來是絕對邏輯的。(再嘮叨的補充一下,p空間的[ Width :1]是用來填入乘數B,然而p空間的[Width * 2 : Width + 1 ] 是用來執行和被乘數A的操作)
實驗結論:
這一章節筆記的重點不是要“如何如何實現一個算法”,而是“以不同概念的理解去完成乘法器的改進”。
1.8 LUT乘法器
從1.8章節以前的乘法器都可以歸納為“慢速乘法器”,當然它們不是真正意義上的慢,只不過它們無法達到急性一族人的任性而已。LUT乘法器,又成為查表乘法器。用傻瓜的話來說,就是先吧各種各樣的結果儲存在一個表中,然后將輸入資源以“查表”的方式,許對比“等于的結果”。
舉個例子,筆者先建立一個16 x 16 正值的查表:
?
? ? | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
2 | 0 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 |
3 | 0 | 3 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 |
4 | 0 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 | 52 | 56 | 60 |
5 | 0 | 5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 |
6 | 0 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60 | 66 | 72 | 78 | 84 | 90 |
7 | 0 | 7 | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 70 | 77 | 84 | 91 | 98 | 105 |
8 | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 |
9 | 0 | 9 | 18 | 27 | 36 | 45 | 54 | 63 | 72 | 81 | 90 | 99 | 108 | 117 | 126 | 135 |
10 | 0 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100 | 110 | 120 | 130 | 140 | 150 |
11 | 0 | 11 | 22 | 33 | 44 | 55 | 66 | 77 | 88 | 99 | 110 | 121 | 132 | 143 | 154 | 165 |
12 | 0 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 | 108 | 120 | 132 | 144 | 156 | 168 | 180 |
13 | 0 | 13 | 26 | 39 | 52 | 65 | 78 | 91 | 104 | 117 | 130 | 143 | 156 | 169 | 182 | 195 |
14 | 0 | 14 | 28 | 42 | 56 | 70 | 84 | 98 | 112 | 126 | 140 | 154 | 168 | 182 | 196 | 210 |
15 | 0 | 15 | 30 | 45 | 60 | 75 | 90 | 105 | 120 | 135 | 150 | 165 | 180 | 195 | 210 | 225 |
假設A x B,它們均為4位,A為10,B為2,那么結果會是20。查表乘法器之所以被稱為快速乘法器,就是上面的原因( 實際上許多硬件乘法器都是使用查表的方式)。
如果A x B ,它們均為8位,那么應該如何呢?難道再建立一個256 x 256 乘法器!?這樣會死人的。
不知道讀者有沒有聽過Quarter square 乘法查表呢?
上邊是該算法的公式,在公式的結束得到ab = ( ( a + b )2?)/4 - ( ( a - b )2?)/4 。
如果再進一步細分的話,無論是( a + b )2/4? 或者( a - b )2/4,經過冪運算后,得到的結果都是正值。
假設a 和b的位寬都是8 位的短整數話( 127 + 127 )2?/ 4 = ( -127 - 127 )2?/ 4。那么我們可以得到一個結論“( a + b )2/4? 或者( a - b )2/4? 使用同樣的(C)2/4 查表”。
那么我們建立一個C = 0 ~ 255 ,并且內容是(C)2/4 的查表。
?
這個查表的尋址雖然是0~255,但是實際上下限是254而已。因為我們知道兩個短整數最大值相加僅有-127 + -127 = -254 或者127 + 127 = 254 。
那么問題來了, 短整數的最大取值范圍是-127 ~ 127 而已,何來寄存-254 ~ 254 呢?
這里我們就涉及了“不同容量空間的相互賦值”。假設C 是9位位寬的不正規整數
,然而A 和B 都是8位位寬的正規整數,那么C = A + B ?
?
??? C = A + B | 等價于 | ??? C = { A[7], A } + { B[7], B } |
??? A = 127 (0111 1111) ??? B = 127 (0111 1111) ? ?A? ?? 0111 1111 ?B? ? ?0111 1111? + ?C? ? 01111 1110 | 等價于 | ??? ??? A = 127 (00111 1111) ??? B = 127 (00111 1111) ? ?A? ? 00111 1111 ?B? ? 00111 1111? + ?C? ? 01111 1110 ? ? |
??? ??? A = -127 (1000 0001) ??? B = -127 (1000 0001) ??? ?A? ?? 1000 0001 ?B? ?? 1000 0001? + ?C? ? 10000 0010 ? ? | ? ? ? ? ? ? 等價于 | ? ? ??? A = -127 (11000 0001) ??? B = -127 (11000 0001) ??? ?A? ? 11000 0001 ?B? ? 11000 0001? + ?C? ? 10000 0010 ? ? |
接下來,我們來看一看下面的代碼:
reg [8:0]I1,I2;
case( i )
0:
begin
??? I1 <= { A[7], A } + { B[7], B };??????????? // C =A + B;
??? I2 <= { A[7], A } + { ~B[7], ( ~B + 1'b1 ) };? // C = A - B;
??? i <= i + 1'b1;
end
1:? // 取正值
begin
??? I1 <= I1[8] ? ( ~I1 + 1'b1 ) : I1;
??? I2 <= I2[8] ? ( ~I2 + 1'b1 ) : I2;
??? i <= i + 1'b1;
end
?
上面的I1 和I2 均為9位位寬。在步驟0,I1表示了C = A + B,相反的I2 表示了C = A - B。由于短整數的賦值采用補碼的表示方式,所以大大簡化了正負轉換的操作。
假設A = -1 ( 1111 1111 ) , B = -3 ( 1111 1101 ), 經過上面步驟0的操作:
I1 = { 1 11111111 } + { 1 1111 1101 } = 1 1111 1100 (-4) 等價于I1 = -1 + -3 = -4
I2 = { 1 11111111 } + { 0 0000 0011 } = 0 0000 0010 ( 2) 等價于I2 = -1 - (-3) = -1 + 3 = 2
步驟1是I1 和I2 從負值轉換為正值。
假設I1 = -4 (1 111 1100) ,I2 = 2 (0 0000 0010), 經過步驟1的操作:
I1 = 0 0000 0011 + 1 = 0 0000 0100;
I2 = 0 0000 0010;
為什么在步驟1中,要特意將負值轉換為正值呢?筆者在前面已經說過,無論是(-C)2?還是(C)2?取得的結果都是一至。為了兩者I1 和I2 共用相同的查表這是必須的步驟。
如果用I1和I2 來表達Quarter square 公式,那么:
?( | I1 |2?/ 4 ) - ( | I2 |2?/ 4 )
實驗五:基于Quarter square 的查表乘法器
首先筆者手動建立0~255 關于(C)2/4 結果的lut_module.v ,因為用Quartus II建立的rom 仿真不給力,很別扭。
?
lut_module.v
這是我目前,貼過最長的.v 文件了...
lut_multiplier_module.v
這個模塊的功能很簡單。首先取得I1 = A + B ,I2 = A - B,然后I1 和I2 都正值值,將I1 和I2 送至各自的查表,然后將得出的結果Q1_Sig (I1的結果) 和Q2_Sig
(I2的結果) , 執行相減。實際上是補碼形式的相加,Q1_Sig + ( ~Q2_Sig + 1'b1 ) ,以致符合Quarter square的公式:
?( a + b )2/4? - ( a - b )2/4 = ( |I1| )2/4? + [ ( |I2| )2/4]補
???????????? ????????????????? = Q1_Sig + [Q2_Sig]補
?
?
第15~18行是仿真的輸出。第26~27行建立Q1_Sig 和Q2_Sig ,實際上這兩個線型數據是U1(81~87行)和U2(91~97行) 實例前申明的,可是modelsim 那么混蛋,偏偏就不給我通過。
從37~77行是該模塊的主功能。步驟0(49~54行)是取I1 和I2 的值。步驟1(56~61行)是I1和I2的正值化操作。步驟2(63~64行)是延遲一個時鐘,給予足夠的時間從lut_module.v讀出結果。步驟3(66~67行),是Quarter square公式操作的最后一步。
89~99行是lut_module.v 的實例化 ,U1是給I1使用 ,U2是給I2使用,它們的輸出連線分別是Q1_Sig 和Q2_Sig 。102行的Product 輸出信號由Data寄存器驅動。然而106~109行是仿真輸出的驅動,分別有I1 , I2 ,Q1_Sig 和Q2_Sig 的仿真輸出。
?
lut_multiplier_module.vt
.vt 文件的寫法和之前的實驗都一樣,如果真的不知道筆者在寫什么,就得好好看筆者之前寫的筆記。
仿真結果:
看吧!一次的乘法操作僅需4個時鐘的而已。比起改進的Booth算法減少了一半的時鐘消耗。真不愧是查表式的乘法器,佩服佩服。
實驗結論:
說實話查表式的乘法器是“以空間換時間”的乘法器,所以說查表式的乘法器是很消耗空間。到底有什么乘法器“可以節約空間,又節省時鐘”呢?
你知道嗎?傳統查表的乘法器都有一個僵局,假設A(B),那么其中一個變量需要是“恒數”,否則建立查表的工作是非常的勞動。但是Quarter square 公式的出現把這個僵局給打破。感謝前人的努力吧,我們后人才能乘涼......
1.9 Modified Booth 算法乘法器
事先聲明modified booth 算法 和 改進的booth 算法乘法器(實驗四)是沒有任何關系的。如字面上的意思modified booth 算法是booth 算法的升級版。我們稍微來回味一下booth 算法。
?
假設B是4位位寬的乘數,那么booth 算法會對B[0: -1] , B[1:0], B[2:1], B[3:2] 加碼,而使得乘法運算得到簡化。booth 算法有典型數學做法,也有位操作的做法。Modified booth 算法比起booth 算法,對于4位位寬B乘數的加碼返回會更廣,而使得n/2 乘法運算的優化。再假設B是4微微款的倍數,那么modified booth 算法會對B[1:-1] , B[3:1] 執行加碼。
如果站在位操作的角度上:
?
B[1] | B[0] | B[-1] | 操作結果 |
0 | 0 | 0 | 無操作,右移兩位 |
0 | 0 | 1 | +被乘數,右移兩位 |
0 | 1 | 0 | +被乘數,右移兩位 |
0 | 1 | 1 | 右移一位,+被乘數,右移一位 |
1 | 0 | 0 | 右移一位,-被乘數,右移一位 |
1 | 0 | 1 | -被乘數,右移兩位 |
1 | 1 | 0 | -被乘數,右移兩位 |
1 | 1 | 1 | 無操作,右移兩位 |
?
Modified booth 算法同樣也有使用p空間,假設乘數A,和被乘數B,均為4位,那么p空間的大小n x 2 + 1 ,亦即9位。乘數A為7 (0111),被乘數B為2 (0010)。
?
先求出+被乘數 和 -被乘數,亦即A 和?A。 | ?? A = 0111 ,?A= 1001 |
P空間初始化為0,然后P空間的[4..1] 填入乘數 亦即B。 | ?? P = 0000 0000 0 ?? P = 0000 0010 0 |
先判斷p[2:0],結果是3'b100 亦即“右移一位,-被乘數,右移一位”。 | ?? P = 0000 0010 0 |
右移一位 ? ? | ?? P = 0000 0001 0 ?? |
p[8:5] 加上?A | ?? P = 0000 0001 0 ???+? 1001????? ???P = 1001 0001 0 |
右移一位 | ?? p = 1100 1000 1 |
判斷p[2:0],結果是3'b001 亦即“+被乘數,右移二位”。 | ?? p = 1100 1000 1 |
?
p[8:5] 加上 A | ?? P = 1100 1000 1 ???+? 0111????? ???P = 0011 1000 1 |
右移二位 | ?? P = 0000 1110 0 |
最終取出p[8:1] 就是最終答案8'b00001110 ?,亦即14。 | ?? P =?0000 1110?0 |
?
關于4 位為位寬的乘數和被乘數操作流程圖如下:
說實話modified booth 算法的位操作是很不規則,從上面的流程圖可以看到,不同的p[2:0]操作都有“不同的步驟次數”,這也使得它非常不適合作為運用。
?
實驗六:Modified Booth 乘法器
?
這個模塊大致的操作如上述的流程圖。
modified_booth_module.v
?
15~17行是仿真輸出。43~94行是該模塊的主功能。在步驟0(45~51行)取得被乘數A并且寄存在a寄存器,此外取得-1(被乘數A) 并且寄存在s寄存器。在初始化p空間的同時,將乘數B填入p[8:1]。
(由于被乘數A和乘數B的位寬為8,所以p空間是n x 2 + 1 亦即9。我知道我很長氣,但是還是容許筆者補充一下:p空間的[ Width :1]是用來填入乘數B,然而p空間的[Width * 2 : Width + 1 ] 是用來執行和被乘數A的操作)。
步驟1和2(53~62行)是p[2:0] 等于3'b000 | 111 | 001 | 010 | 101 | 110 的操作。相反的,由于modified booth 算法當p[2:0] 等于3'b011 和3'b100 所執行的步驟次數是不一樣(56~57行)。
所以在步驟3~5(66~73行)針對 p[2:0] 等于3'b011 的操作(56行)。反之步驟6~8 (77~84行)針對p[2:0] 3'b100 的操作(57行)。
?
步驟9~10產生完成信號。第102行的product輸出信號是由p[16:1]來驅動。第106~109的仿真輸出信號,分別由寄存器a ,s 和p來驅動。
modified_booth_multiplier_module.v
?
這是激勵文件,在寫這個文件的時候,筆者心情很糟糕,所以在步驟5加入了類似for嵌套循環的東西。其他的和之前的.vt 文件都是大同小異~ 自己看著吧。
?
仿真結果:
在仿真結果中,可以很明顯的看到當2(4) 和127(-127)有明顯的時鐘消耗差異。
實驗結論:
如果Modified booth 算法用在“位操作”,雖然它是快速的乘法操作,但是很多時候它還是很別扭。換句話說,用它還要圖運氣,因為不同的乘數和被乘數都有不同的時鐘消耗......
?
1.10 Modified Booth 乘法器·改
如果要把Modified Booth 乘法器別扭的性格去掉,我們不得站在“數學的角度”去看modified booth 算法。下表是從數學的角度去看modified booth 針對乘數B的加碼。
?
B[1] | B[0] | B[n-1] | 操作結果 |
0 | 0 | 0 | 無操作 |
0 | 0 | 1 | +被乘數 |
0 | 1 | 0 | +被乘數 |
0 | 1 | 1 | +2(被乘數) |
1 | 0 | 0 | -2(被乘數) |
1 | 0 | 1 | -被乘數 |
1 | 1 | 0 | -被乘數 |
1 | 1 | 1 | 無操作 |
?
我們假設A被乘數和乘數B均為4位位寬 :A=7(0111),B=2(0010)。
?
A = (7) 0000 0111;2A = (14) 0000 1110;-2A = (-14) 1111 0010。
?
在這里我們必須注意一下當B[1:-1] 等于011 或者100 的時候,4位的被乘數A的取值范圍最大是-7 ~ 7 然而,+2(被乘數) 或者 -2(被乘數) 都會使得A的最大值突破取值范圍。所以需要從4位位寬的空間向更大的位位寬哦空間轉換。這里就選擇向8位位寬的空間轉換吧。
?
B乘數加碼為B[1:-1] = 3'b100?,亦即 -2(被乘數) 和B[3:1] = 3'b100 ,亦即 +被乘數。
?
??? A????? 0 1 1 1
?? ?B????? 0 0 1 0??0
??? ==============
?????????? +1? -2?????? B乘數加碼
??? ==============
? 1 1 1 1 0 0 1 0
?+ 0 0 0 0 0 1 1 1????????? << 2 左移兩位
?? ===============
??? 10 0 0 0 1 1 1 0????? 無視超過8位最高位的益處
?? ===============
?
還記得booth算法在數學角度上的運算嗎?4位的乘數和被乘數相乘,乘數必須加碼n次,而且乘積也是n 位的次數,亦即4次哦加碼操作,和4次的乘積操作。相反的modified booth 算法在數學的角度上運算的話,4位的乘數和被乘數相乘,乘數加碼為n位/ 2 次,而且乘積也是n位/2 的次數,亦即2次加碼操作,和2次的乘積操作
實驗七:Modified Booth 乘法器·改
modified_booth_multiplier_module_2.v
?
第29~27行是該模塊所使用的寄存器。a是用來寄存A,a2是用來寄存2A,s是用來寄存-A,s2是用來寄存-2A。M是用來表示每次乘積的偏移量。
由于這個實驗不是站在位操作的角度上,所以P空間僅是作為累加空間的存在。作為補償寄存器N用來判別booth 加碼操作,所以寄存器N用于寄存乘數B的值。乘數B是8位位寬,所以N空間的大小是 “乘數B的大小+ 1”。多出來的1個空間是用來寄存B[-1]的值。”
在步驟0(54~65行),是用來初始化所有相關的寄存器。寄存器a,a2,s,s2 在初始化的同時也進行8位 向16位 空間轉換。寄存器p和M都清零,至于寄存器N[8:1]是用來填充乘數B,N[0] 填入零值。
步驟1~4(67~79),也就是4次的乘積次數,因為受到n/2 的關系。每一次的乘積操作都是先判別N[2:0],然后累加相關的值。
我們知道傳統的乘法,每一次的乘積操作,都有偏移量 ,打個比方。
?? 123
?? 111
=====
?? 123? <= 十進制的第一個乘積是 偏移0,沒有左移位操作。
? 123?? <= 十進制的第二個乘積是 偏移10,也就是左移1位。
?123??? <= 十進制的第三個乘積是 偏移100,也就是左移2位。
=====
???????? ?
同樣的道理寄存器M 是用于記錄二進制的每一次乘積偏移量,但是modified booth乘法的乘積偏移量是普通2進制乘法乘積偏移量的2被。所以每一次乘積操作結束都左移+2。
至于寄存器N它寄存了B[7:1] + B[-1] 的值。然而每一次用于的判別都是N[2:0],所以每一次的乘積之后,N都需要右移兩位。
假設B = 1101 0010 ,N 必然是1101 0010 0。
乘積1 ? ? B[1:-1] = 100 N = 1101 0010 0 | 乘積2 ? ? B[3:1] = 001 N = 0011 0100 1 | 乘積3 ? ? B[5:3] = 010 N = 0000 1101 0 | 乘積4 ? ? B[7:5] = 110 N = 0000 0011 0 |
為什么說8 位位寬的數據相乘,乘積運算次數是n / 2 ,亦即4。這是Modified booth算法的一個特點。如果站在數學的角度上,他可以節省“乘積次數/ 2”。
第92行的product 輸出是由寄存器p驅動。前面筆者說過了,如果站在數學的角度,p空間只是累加空間的作用而已。然而p空間的大小是“乘數和被乘數位寬大小的相加”。
第96~101行是仿真輸出信號的被驅動。有一點很特別,除了寄存a, a2, s, s2 和N 以外,筆者還故意將該模塊的i 引出,這是為了觀察 “Modified booth 乘法使得乘積次數減半”這一事實。在仿真中,SQ_i 從1~4經過,如果輸出的結果是真確,那么可以證明Modified booth 算法確實何以減少一半的乘積。
modified_booth_multiplier_module_2.vt
?
仿真結果:
?
?
從仿真結果上,我們可以看到,每一個乘法操作都消耗同樣數目的時鐘。此外還有一點, 當SQ_i 等于4 之后,就會得到正確的答案。
實驗結論:
實驗七和實驗六相比,不僅每一次乘法操作時鐘消耗都一致,而且這樣結果帶來一個好處,就是- 實驗七和實驗六相比比起乘法運算更快。此外,從SQ_i信號等于4之后,product 就輸出正確的結果,所以我們可以證明modified booth算法是可以減半乘積的次數。
總結:
從實驗一到實驗七當中,筆者詳細描述出四種乘法器的各有千秋,其中還有幾種乘法器筆者還特意去優化和提升它們。從四種乘法器之中,傳統乘法器,Booth 乘法器,LUT查表乘法器,和Modified Booth乘法器。LUT乘法器擁有最少的時鐘消耗(最快的運算速度),但是LUT乘法器卻暴露出消耗資源的弱點。
如果將LUT乘法器排外,自然而然Modified Booth 乘法器成為第二候選人,但是要建立Modified Booth 乘法器需要很好的理論基礎,故很多新手都很怕它。至于Booth乘法和是最受歡迎的,如果設計的要求不像DSP那么任性,估計會有很多人喜歡它,因為它中庸,簡單,容易親近。
剩 下的傳統的乘法器,它什么都不比上后者,難道我們就要鄙視它嗎?這個不然,筆者接觸各種各樣的乘法,還是托它的副,不然我是不可能如此深入研究整數乘法 器。傳統的乘法器,最主要的功能是傳達“乘法運算”的概念。正如筆者贊同的一句話:“前人造路,后人走路”,前者們的辛苦應該受到尊敬。
整數乘法器所涉及的知識可真不小,Verilog HDL語言掌握的成熟性姑且不說,而且還涉及諸如補碼,整數的表示方法,不同位空間的整數轉換等等... 都是一些非常基礎的知識。我們所使用的高級語言,如C語言:
int C;
short int A,B;
C = A * B;
假設筆者輸入如同上述的代碼,實際上我們是不知道和不被允許窺看它里邊是如何操作(有傳言說,C語言的乘法就是傳統的乘法概念... (-_-!))。
雖然這本只有短短50多頁的筆記,故事也只是圍繞著著“整數乘法器”發展,顯然還有很多地方都不給力。但是你知道嗎,關于網上“Verilog HDL 整數乘法器”的求救貼已經達到很恐怖的數量,此外還有很多源碼和實例都非常不給力,真是非常蛋疼!故筆者才有編輯這本筆記的初衷,雖然這本筆記不是什么非常給力的東西,但是作為參考已經切切有余。
不知道讀者們看完這本筆記后又會萌出什么奇怪的想法呢?
總結
以上是生活随笔為你收集整理的verilog乘法器以及booth编码改进的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 麻将胡牌算法带癞子 python实现
- 下一篇: 半导体精密划片机行业介绍及市场分析