組成原理第三章(上)——ALU與整數的四則運算
參考資料
- 《計算機組成與設計:硬件/軟件接口》——機械工業出版社
- 《Arithmetic for Computers》——Jiang Zhong
- 授課內容 —— 吳長澤老師
ALU的構建
ALU就是運算器,這些內容都可以在書中的附錄中找到,我們接下來看一個簡單的例子
邏輯的與、或
可以看到,這裏有兩個輸入,a、b,他們分別連接到了一個與門和一個或門,他們分別與和或的結果又連接到了一個多路選擇器上,Select用於控制到底是做與運算還是做或運算,Select = 0時,做與,Select = 1的時候我們就做或運算。
迴歸正題,加法——半加器
爲了達到計算的目的,我們需要構建最基礎的運算單元,那就是半加器。半加器是什麼?半加器可以接收兩個相加的數,然後給出他們相加的和以及他們的進位,但是他沒有接受低位的進位,所以它叫半加器。
我們先來構建一個真值表:
a | b | result | carry |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
根據真值表可以構建出表達式:
根據表達式可以構建出電路:
全加器
真值表
表達式
電路
有了加法器之後,我們就可以做很多的運算了,以加法爲例,只需要將很多全加器串在一起就可以構成一個多位整數的加法器。
1 bit ALU
有了上面的這些積累之後,我們就可以實現一個有以下功能的ALU:
- AND
- OR
- ADD
其實實現的方法也非常簡單,就是對剛纔的多路選擇器做一個拓展,把全加器加進來就可以了。
Basic 32 bit ALU
那麼有了一個1 bit ALU之後,就可以把32個1 bit ALU級聯在一起,就可以形成一個32位的ALU
但是值得注意的是,級聯的運算器的數目越多,它的體積、成本以及出錯概率都會相應的上升,他的可靠性、成本都會有所下降,所以往往不會以1bitALU爲基礎進行級聯,而是會以多位的ALU爲基礎在進行級聯,這樣能夠降低成本、增加準確率。
擴展1 bit ALU
我們還希望我們的這個ALU可以完成減法操作,那麼應該怎麼做呢?我們都知道,假如說要完成的操作,那麼實際上完成的是加上的補碼。的補碼就是對進行取反,然後加一。那麼這一系列的操作反應在電路中實際上就是:
如果我們要進行減法運算,那麼我們就需要將設置爲1這樣就完成了對b的取反操作,同時,我們需要將最低位的的值改爲1,就可以完成上面說的加一的操作。這樣我們在沒有添加過多的邏輯單元的情況下就完成了從加法器到加法、減法器的擴展。
那麼經過擴展後,我們的功能有:
- AND
- OR
- ADD
- Subtract
我們發現缺少了比較操作:
- Slt rd, rs, rt; 也就是比較功能
- 如果rs < rt, rd = 1 否則 爲0
那麼怎麼去做呢?我們可以是用減法來完成這一功能,我們可以做的操作,如果結果是負數,那麼就表示。接下來看電路:
這裏的less是負責接收高位信息的,因爲我們在判斷兩個多位整數的大小的時候,是不能通過低位去判斷的,所以我們需要這種機制來進行。大家覺得沒講透的話可以繼續往下看,一會會講到級聯後的這個,到時候會好理解一些。
那麼除了設置比較之外,我們還需要去做一個溢出探測。溢出是指有限的符號位無法表示我們的結果了。溢出是通過最高位和符號位進行判斷。
這裏也是先挖個坑,等下再來填。
Compute ALU
輸入:
- A
- B
控制量:
- Binvert:是否反轉
- Operation:選擇做什麼運算的,其實就是那個選擇器
- Carryin:上一個數的進位
輸出:
- Result
- Overflow
噠噠噠敲黑板開始填坑了:觀察這個電路我們可以發現,高位的結果連接到了最低位的Less上,如果是加法而且是1也就是進行反轉,而且是1的話,就代表着我們在做A-B的操作。A-B後,如果高位是1的話,那麼就說明A>B,否則就說明,這樣就完成了簡單的判斷大小的操作。
如果到這裏你還是看不懂,那麼不慌,我們詳細的講一下,假如說我們現在有兩個4位有符號數A = 3,B=4
我們要對比它們的大小,那麼就需要先進行A-B的操作,爲執行該操作,我們先對B進行取補碼的操作:
- B =
- 最高位爲1,說明A<B
接下來看第二種情況,,判斷A、B大小
- B =
- ,最高位爲0,說明
相信大家一定看懂了,如果看不懂,那就把上面的部分再看一遍,還是看不懂建議再去複習一下二進制表示部分的內容。接下來還需要增加一個功能,因爲我們時常需要判斷相加的結果是否爲0,所以我們想要添加一個判斷是否爲0的門,原理也很簡單,如果每一位都是0的話,那麼結果也一定爲0,把人話翻譯成機器語言就是所有Result進行或運算,如果是0的話那麼就說明都是0,反之就說明不是0,那麼對結果取反後,如果不是0那麼就輸出1,反之就是0,電路如下。
ALU symbol & control
我們將上面的級聯電路進行封裝,就得到了一個ALU,示意圖如下:
ALU operation的值與其對應的操作如下:
ALU的構建部分到此結束了。
整數加減法
數的表示範圍:
有一個n位的二進制整數
- 有符號:
- 正數:
- 負數:
- 無符號:
什麼樣子的數進行運算會溢出呢?
- 正數負數相加:不會發生溢出
- 兩個正數相加:可能溢出
- 兩個負數相加:可能溢出
整數的減法
很簡單,我們上面也有所提及,,在這裏我們主要討論在什麼情況下會發生溢出。
什麼時候溢出?
-
兩個正數相減或兩個負數相減:不會溢出,因爲結果的絕對值一定比兩者絕對值一定比兩者絕對值的最大值小,人話聽不懂,但是用數學語言描述就看得懂了:
KaTeX parse error: Undefined control sequence: \and at position 13: if (A\geq0 \̲a̲n̲d̲ ̲B\geq0)\or(A\le… -
正數減負數:可能發生溢出
-
負數減正數:可能發生溢出
溢出處理
發生溢出的時候,通常的做法是:將PC的值存到EPC中,其中PC的值代表當前指令,EPC代表發生錯誤的指令,執行這步操作,實際上在乾的事情就是:記錄下來,這個語句出錯了。
指令可以進行異常處理並且返回到當前指令繼續執行。
加法器的速度問題
問題
常用的方法是使用串行的處理方式,也就是先計算低位,然後從低位到高位依次計算,如下圖所示:
這樣計算的效率很低,這是由於後方的運算需要等待前方的進位完成才能夠進行計算。如果我們能夠預先知道上一位的進位的話,就可以極大地加快效率。
解決
我們可以將這個串行的電路改成一個兩層的邏輯,這樣可以極大的提升他的計算速度,但是這樣的改動是有巨大的代價的,代價就是:需要很多的輸入門,導致電路的體量很大。下面有一些解決方案:
超前進位加法器
核心思路:將需要進位的值和計算的值進行分離。
我們先來看上面的兩個符號,代表當前位的結果,代表當前位的進位。我們很容易得到當前位的結果爲,進位的結果是KaTeX parse error: Undefined control sequence: \and at position 5: a_i \̲a̲n̲d̲ ̲b_i。
接下來我們再來關注一下上一位的進位和最終結果之間的關係,我們定義符號表示來自上一位的進位
C(進位) | S(當前位) | |||
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 0 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 1 | 0 |
1 | 1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 | 0 |
接下來我們來寫一下這個表達式:
KaTeX parse error: No such environment: align at position 8:
\begin{̲a̲l̲i̲g̲n̲}̲
\begin{arr…
我們將這個表達式進行化簡,就可以得到最終的結果:
計算過程如下( 寫字爛,勿噴),由於篇幅原因這裏省略了很多中間步驟:
根據這個過程我們可以構造出相應的全加器電路:
下面的圖中給出了超前進位加法,在多位運算中的式子:
我們可以把原來串行的加法器拆分成四個一組,在每一組中進行超前進位加法,然後再將多個四位的超前進位加法器級聯在一起,就可以達到使計算速度增加,但又不過度使用計算資源的效果。
下圖所示的是一個四位的超前進位加法器:
我們可以對他進行封裝,並且進行級聯即可獲得更高位的快速加法器:
我們對比兩種方式:
-
一、組內使用超前進位,組間串行:如上方左圖所示。
-
二、組內使用超前進位,組間超前進位:如上方右圖所示。
多媒體中的飽和操作
當正向溢出時,值保留在最大可表示值。
當反向溢出時,值保留在最小可表示值。
二進制乘法
整數
二進制乘法和十進制乘法的運算是一樣的,我們可以看一個例子:
我們可以將這個過程用一個流程圖來表示:
其中,被乘數左移一位,就相當於我們在使用豎式計算十進制乘法時,每過一位都要將新的得數向左移一位一樣。而乘數向右移一位實際上就是更換將要檢測的位數,這樣就可以保證每次都檢查最低位就可以了。
定點小數的乘法
方法
假設有兩個定點小數:
我們可以將表示成下面的式子:
KaTeX parse error: No such environment: align at position 8:
\begin{̲a̲l̲i̲g̲n̲}̲
\begin{arr…
據此我們可以得到一個通項公式:
$$
\begin{align}
\begin{array}
. z_0 &=0\
z_1 &= 2^{-1}(y_nx+z_{0})\
z_2 &= 2^{-1}(y_{n-1}x+z_{1})\
…\
z_i &= 2^{-1}(y_{n-i+1}x+z_{i-1})\
…\
z_n &= x*y = 2^{-1}(y_1x+z_{n-1})
\end{array}
\end{align}
$$
所以每次只需要相加兩個數,然後向後移一位,且相加的數只有n位,因此不需要2n位的加法器。
示例
計算:
流程圖
爲了方便理解,我做了一個流程圖
假設有問題n位定點小數乘法:
乘法優化
我們看到剛纔的計算過程,y有多少位就需要做多少個輪次的加法,這樣的效率是非常之低的。這裏優化的主要思想是:並行。
第一種:陣列乘法
我個人認爲,這種乘法器的思想和超前進位的思想有一點相似,它將我們豎式中需要計算的都先計算出來,然後再對他們進行加法即可。
第二種:樹型乘法器
樹形乘法器,就是將32位的結果分別算出來,然後再用樹形結構進行歸併相加,這樣就只用級的輪次就可以算出結果。
乘法在MIPS中
在MIPS中,我們將乘法的結果放到了兩個專有的寄存器中:
- HI:存儲高32位
- LO:存儲低32位
mult rs, rt
multu rs, rt
# 這兩條指令是將 rs*rt的結果存到我們剛纔說的HI、LO寄存器中
mfhi rd # 讀取HI到rd
mflo rd # 讀取LO到rd
mul rd, rs, rt # 將rs*rt的低32位賦給rd
除法
除法及其硬件結構
第一種方法:恢復餘數法
算法流程
硬件結構
講解
回覆餘數法是一種非常暴力的方法,爲什麼暴力呢,聽我慢慢道來:
上面框圖一大堆,看起來很麻煩的樣子。單核心的操作非常簡單,那就是:從高位的解開始嘗試,先把餘數給減了,如果不行的話,再恢復回來,如果行的話,那就保留。
優化
與優化乘法的方式相似,只用左移餘數的方法就可以完成操作,方法如下:
更快速的除法
因爲除法中存在減法操作,所以不能像之前的乘法一樣優化,但是我們可以通過同時生成多位商的方法進行操作,或是查表法、不執行除法等方法進行優化。
MIPS在除法中
和乘法一樣,除法也用到了HI和LO兩個寄存器,他們分別用於儲存餘數、除數。
div rs, rt
divu rs, rt
mfhi s1
mflo s1
今天就講到這裏,明天再繼續吧。