《自己動手寫CPU》一書的7.11節到7.14節實現了DIV和DIVU指令。
書中通過“試商法”實現除法,並在原有的流水線結構之外另加了狀態機進行計算。
照抄書上的實現方法需要另外添加個.v,我實在有點懶,不想在運算指令實現上再另外加個文件。
而且按照書上的實現,DIV和DIVU指令跟其他的運算指令差別很大,不符合我對結構的審美。
那么就必須想想辦法自己寫一個別的實現方法。
於是找到了一個結構化比較好的實現方法:
http://www.cnblogs.com/AndyJee/p/4575152.html
參照這個方法,使用verilog實現需要解決2個問題:
(1)文章是無符號數的除法,對應DIVU,而DIV是有符號數除法。
這里又要分兩種情況討論。
a、DIV指令的被除數和除數是有符號數
有符號數運算時,輸入的被除數和除數都是補碼,做除法時需要先將補碼轉成原碼。
正數的補碼就是原碼,那么只需要在負數時轉成原碼即可。
b、DIV指令的商和余數正負符號判斷
按照初等數學的定義,商的正負取決於被除數和除數的正負,而余數取決於除數的符號。
而計算機語言中,商的正負沒有變化,余數符號取決於被除數。
該問題的討論:
http://blog.sina.com.cn/s/blog_956c84bb0101j31f.html
DIV指令下,被除數/除數為負數,則轉換成原碼:
assign div_dividend = ((aluop_i == `EXE_DIV_OP) && (reg1_i[31] == 1'b1)) ? (~reg1_i + 1) : reg1_i;
assign div_divisor = ((aluop_i == `EXE_DIV_OP) && (reg2_i[31] == 1'b1)) ? (~reg2_i + 1) : reg2_i;
DIV指令下,將商和余數轉回為補碼:
assign div_quotient = ((aluop_i == `EXE_DIV_OP) && (reg1_i[31] ^ reg2_i[31] == 1'b1)) ? {1'b1, (~div_quo_o[30:0] + 1)} : div_quo_o;
assign div_remainder = ((aluop_i == `EXE_DIV_OP) && (reg1_i[31] == 1'b1)) ? {1'b1, (~div_rem_o[30:0] + 1)} : div_rem_o;
(2)參考的方法通過高級語言描述,需要轉化成適合verilog實現的方式。
該方法的核心是通過減法來實現除法,使用中文通俗描述的步驟如下:
基本方法
a、比較被除數和除數的大小;
b、如果被除數比較大,將被除數減去除數,商加1;
c、循環步驟a和b,直到被除數比除數小,這時候被除數剩下的就是余數,商就是結果(初值為0)。
基本方法比較“笨”,效率是最低的。
加速方法
a、比較被除數和n*除數的大小;
b、如果被除數比較大,將被除數減去n*除數,商加n;
c、循環步驟a和b,直到被除數比除數小,這時候被除數剩下的就是余數,商就是結果(初值為0)。
加速方法適合使用移位來實現(verilog中,左移1位數值乘於2)。
但是上面文章中描述的方法不適合直接用verilog實現(2層while循環,verilog實現很麻煩)。
可以通過以下幾個小技巧避免在verilog中出現類似2層while循環的結構:
a、考慮DIV和DIVU指令的除數位數有限,那么可以先判斷最大需要移位幾次進行加速方法步驟a(數一數除數第一個1之前有幾個0);
b、在最大移位范圍內,按照移位次數從大到小的順序進行操作(使n*除數中的n盡量大),以盡快收斂結果。
最后實現時,在流水線的寄存器之間進行的運算如下(核心代碼):
//core method
if (div_rem_i < (div_divisor << div_shift_cnt_i))
begin
div_quo_o = div_quo_i;
div_rem_o = div_rem_i;
end
else
begin
div_quo_o = div_quo_i + (1 << div_shift_cnt_i);
div_rem_o = div_rem_i + (~(div_divisor << div_shift_cnt_i)) + 1;
end
end
這樣DIV和DIVU運算所需的周期受除數影響,除數越大則運算周期越短,除數越小則運算周期越長(移位操作次數多)。
例如,當除數是1時,需要31次移位操作(31個時鍾);除數是10'b10_0000_0000時,需要22次移位(22個時鍾)。
這種實現方法最大運算周期為32*時鍾周期,而《自己動手寫CPU》中給的例子對於32位的除法,至少需要32個時鍾周期。
另外,這種實現方法與累乘加和累乘減(MADD/MADDU/MSUB/MSUBU)的結構類似,不需要另外再添加.v文件,符合懶人“初始結構以外,最好不做任何結構變動”的習慣。
最后,這種方法可以簡單實現加速,即通過比較被除數和除數從bit31開始0的數量,可以在被除數和除數絕對值都較小的情況下節省很多運算周期。