大家新年好,我是呼嚕嚕,在上一篇[簡易加法器](https://mp.weixin.qq.com/s/ahuk_JH8iyH8bwh3VQxpOw)里我們瞭解了半加器和全加器的設計與實現,今天我們來看下CPU中減法器是如何實現的。文章比較長,大家可以收藏反覆觀看 ## 電腦為什麼利用反碼來實現減法 ...
大家新年好,我是呼嚕嚕,在上一篇簡易加法器里我們瞭解了半加器和全加器的設計與實現,今天我們來看下CPU中減法器是如何實現的。文章比較長,大家可以收藏反覆觀看
電腦為什麼利用反碼來實現減法?
我們來看一個最常見的例子,2-1 =1
這是減法,但它等同於 2+ (-1) =1
這其實是加法。從運算邏輯上來說,減法可以通過加法來實現,這是可行的。
從硬體電路層面說,我們很容易讓電子實現彙總的效果,但是將電子群拆分出多個更小的集群,是不容易的。還有一個好處是利用加法器能實現減法的效果的話,就不需要再為減法器專門設計電路了,降低了電路的複雜度。
由於電腦採用的是二進位,和我們天生熟悉的十進位還是有區別的,那麼二進位能否實現用加法來實現減法效果?
很幸運地是,當初那群電腦那群工程師大拿將二進位玩的是爐火純青,通過原碼->反碼->補碼
,一步步實現了二進位通過加法來實現減法效果。其中原理大家感興趣地,可以看看筆者之前的一篇文章 電腦中數值和字元串怎麼用二進位表示?
補碼真的是一個天生完美的奇妙存在,基於補碼的機制,減法可以轉化為加法,也就意味著電腦可以通過加法器實現減法。
看完筆者的那篇文章,我們知道了補碼產生的手動:正數原碼不變,負數的符號位不變, 其餘各位取反, 最後一位+1
減法器的實現
要實現原碼到補碼的轉換,需要一個取反器,我們先來寫出減法邏輯的真值表:
通過真值表,我們可以很容易發現這其實就是一個異或門(相同為0,不同為1)
我們來實現一個8位的取反器,由於是8位的,所以輸入選這8位輸入,還得連一個8位的分線器,輸出類似。異或門得有8個,每個都需和控制是否取反的輸入相連。
我們將之前的全加器和減法器結合起來,需要註意的是補碼需要取反再+1
,取反可以將輸入和取反器相連,+1
可以將全加器最低位的進位與控制取反的輸入相連即可,極簡單又巧妙
我們來啟動模擬,看下效果:
上圖計算結果,相當於:
1+1 =2
1-1 =0
但是上面有個問題是,1-1=0
時,雖然燈泡是0,但是旁邊的溢出標誌顯示溢出了,我們還需改造一下。我們這裡簡單地,就直接讓減法不溢出即可(這種處理方式還是比較粗暴的,但是實現起來比較簡單)
我們來寫出溢出輸入IY,是否取反輸入IF(如果取反,就代表是減法操作),溢出輸出O的真值表關係
IY | IF | O |
---|---|---|
0 | 0 | 0 |
1 | 0 | 1 |
0 | 1 | 0 |
0 | 1 | 0 |
我們可以推出公式:O=非IF * IY
,所以需要非門和與門
這樣就減法時,就不會溢出了。但其實這個加法器只能做正數的減法(也就是輸入A得大於等於輸入B),如果最後結果為負數的,還是有bug的。我們後面有機會再優化
7段數位管16進位顯示
由於用燈泡表示二進位,每次得出的結果,還要我們去換算成10進位,非常不直觀,我們接下來利用數位管,來將二進位數"翻譯"成10進位數。
我們這邊利用的是7段數位管來實現的,數位管其實就是多個LED燈,不同的位控制不同的LED。從第0位到第7位,通過控制不同的LED來組合出數字。第7位比較特殊,數位管顯示的是點
我們用上面的電路,一一將0~F16個數顯示出來,各個開關的情況記錄成下麵的表:
數值 | 開關(2進位) | HEX(16進位) |
---|---|---|
0 | 0011 1111 | 0x3F |
1 | 0011 0000 | 0x06 |
2 | 0101 1011 | 0x5B |
3 | 0100 1111 | 0x4F |
4 | 0110 0110 | 0x66 |
5 | 0110 1101 | 0x6D |
6 | 0111 1101 | 0x7D |
7 | 0000 0111 | 0x07 |
8 | 0111 1111 | 0x7F |
9 | 0110 1111 | 0x6F |
A | 0111 0111 | 0x77 |
b | 0111 1100 | 0x7C |
C | 0011 1001 | 0x39 |
D | 0101 1100 | 0x5E |
E | 0111 1001 | 0x79 |
F | 0111 0001 | 0x71 |
這其實就是7段數位管的共陰極對照表,還有共陽極對照表這裡我們就不展示了。
如果直接用組合電路來封裝8位輸入,7段數位管的16進位顯示,的確是可以的,但如果是16位,32位,64位輸入,電路會異常的複雜,我們這邊用儲存器ROM來實現這個功能
ROM只讀存儲器,是以非破壞性讀出方式工作,它非易失性存儲器,當電源被移除時,其數據內容不會被擦除,它還有個特點就是只能讀出而不能寫入信息,其所存的數據,一般是裝入整機前事先寫好的,整機工作過程中只能讀出。
需要註意的是: 雖然ROM和硬碟有一些共性,但不能簡單地說ROM就是硬碟
常常與ROM相比的還有一個RAM(隨機存取存儲器),也就是我們常說的主存,是與CPU直接交換數據的內部存儲器,它的特點:隨機存取、數據易失、對靜電敏感、訪問速度快、需要刷新。RAM在斷電以後保存在上面的數據會自動消失
我們使用ROM和7段數位管來顯示16進位的數0~F,選用地址位寬為4,數據位寬為8,只需把對應的數據提前寫入對應的地址中即可。
這裡需要註意一下,為什麼我們選用地址位寬為4,數據位寬為8的ROM?
首先我們需要明白(1111 1111)2 = (f f)16
, 7段數位管可以表示0~F 16進位數,我們可以用2個7段數位管並聯將8位二進位數解碼成16進位數。
我們就先考慮1個7段數位管和ROM的關係,單個"f"也就是第16個數,也就是說4位二進位,即4位輸入,最大值為16
- 地址位寬為4, 可以保證定址2^4=16,分別對應十六進位下的0~F
- 數據位寬為8,相當於2個7段數位管,一個7段數位管需要4位輸入,2個就是8位輸入
我們想顯示16進位數,0~F,我們需要4位二進位輸入,其最大值1111
,就是16(F),結合上面的共陰極對照表,我們就能總結下麵的表:
A3 | A2 | A1 | A0 | Number | HEX(16進位) |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0x3F |
0 | 0 | 0 | 1 | 1 | 0x06 |
0 | 0 | 1 | 0 | 2 | 0x5B |
0 | 0 | 1 | 1 | 3 | 0x4F |
0 | 1 | 0 | 0 | 4 | 0x66 |
0 | 1 | 0 | 1 | 5 | 0x6D |
0 | 1 | 1 | 0 | 6 | 0x7D |
0 | 1 | 1 | 1 | 7 | 0x07 |
1 | 0 | 0 | 0 | 8 | 0x7F |
1 | 0 | 0 | 1 | 9 | 0x6F |
1 | 0 | 1 | 0 | A | 0x77 |
1 | 0 | 1 | 1 | b | 0x7C |
1 | 1 | 0 | 0 | C | 0x39 |
1 | 1 | 0 | 1 | d | 0x5E |
1 | 1 | 1 | 0 | E | 0x79 |
1 | 1 | 1 | 1 | F | 0x71 |
根據對應關係,我們把電路和存儲器相應地址數據預先填進去
我們啟動模擬看下效果:
測試完成後,將4個開關換成4位輸入。接著我們將2個4位16進位解碼器並聯,就成了8位16進位解碼器,並封裝一下:
並將它與上文的全加器與減法器結合起來:
nice!
7段數位管10進位顯示
通過上一小節,我們成功把8位二進位數,"翻譯"成16進位數,但距離我們更熟悉的十進位還是有點距離的,我們本小節繼續改進7段數位管,實現10進位的解碼
由於(1111 1111)2 = (255)10
, 最大值為255
ROM需要8位地址位寬,2^8 = 256
,確保能夠將256個數(0 ~255)全部找到;255是3位數,我們至少需要3個數位管(也就是我們上一小節封裝的4位16進位解碼管),1個數位管需要4位輸入,所以ROM數位管的數據位寬為12
更多精彩文章在公眾號「小牛呼嚕嚕」
電路實現:我們可以使用8個開關,來表示8位輸入;選用8位地址位寬且數據位寬為12的ROM,通過8位3針腳的分線器和3個4位16進位解碼管相連即可。
由於ROM的查找表有255個數,不能像之前一樣一個個手動填寫,我們可以利用Python來實現(電腦中需要有Python3的環境)。
將其另存到桌面上為test.bin文件,用vscode打開該文件(需要安裝 hex editor插件來顯示二進位),以小端顯示:
test.py:
with open('test.bin', 'wb') as f:
for i in range(256):
var = str(i)
var = int(var, base=16) //先轉成16進位
byte = var.to_bytes(2, byteorder='little')// 再轉化成二進位,以顯示小端
print(byte)
f.write(byte)
將其放到test.bin 同級目錄後,直接運行命令python test,py
後,test.bin就變成了:
這種55 02
其實是 255
,31 02
為 231
, 像這種55 02
就是小端表示法。
將test.bin 重新載入到ROM中
我們來啟動模擬看看:
我們將輸入替換開關,然後封裝成8位10進位解碼器電路,接上之前的減法器的電路:
21選擇器 增強 10進位顯示
我們現在有個需求,001,前面的0不想要,就想要1
,我們藉助21選擇器來實現 高位為零時,數位管不亮
我們先來看一下1位21選擇器,首先有2個輸入,分別為A和B,以及一個有效位EN,一個輸出S。我們的目的是實現:有效輸出A,無效輸出B。根據目的我們可以寫出真值表:
EN | A | B | S |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 |
0 | 1 | 0 | 0 |
0 | 1 | 1 | 1 |
1 | 0 | 0 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
1 | 1 | 1 | 1 |
我們可以得出公式 S = EN與A + 非EN與B
,進而可以畫出電路圖
封裝後,模擬一下:
我們繼續畫出8位21選擇器,只需將8個1位21選擇器組合在一起:
將其封裝一下,接著模擬測試:
7段數位管10進位顯示增強的電路,我們再重新設計一下:
更多精彩文章在公眾號「小牛呼嚕嚕」
最後把其封裝一下,放到減法器和加法器的電路中,演示一下:
完美,這樣就實現了我們的目的。
尾語
本文我們將上一小篇文章中的簡易加法器進行來改進,通過補碼,讓加法器也是運算減法。接著為了讓我們觀察結果更加方便,我們使用了7段數位管實現了 16進位、10進位顯示,最終優化了10進位顯示,使其只顯示有效位的數值。
本系列到目前為止主要是組合邏輯電路的相關知識,後續我們會探究時序邏輯電路的奧秘,來看看開關究竟是如何實現CPU除計算功能外另一個重要的功能"記憶功能"。
參考資料:
- 一個8位二進位CPU的設計和實現,躊躇月光
- 《編碼:隱匿在電腦軟硬體背後的語言》
- 《穿越電腦的迷霧》
- 深入淺出電腦組成原理,徐文浩
- 運行記憶體,百度百科
全文完,感謝您的閱讀,如果我的文章對你有所幫助的話,還請點個免費的贊,你的支持會激勵我輸出更高質量的文章,感謝!
原文鏡像:https://mp.weixin.qq.com/s/QXWm-s-v3VuYV7s4-uE7yA
電腦內功、源碼解析、科技故事、項目實戰、面試八股等更多硬核文章,首發於公眾號「小牛呼嚕嚕」,我們下期再見!