- 8位二進制CPU的設計和實現
CPU基本電路的實現
本文是對B站UP躊躇月光出的8位二進制CPU的設計和實現的文字教程復現第一部分 CPU基本電路的實現
相關 github 地址:https://github.com/StevenBaby/computer
PS:有錯誤的地方請指正,謝謝!共同學習,一起進步!
概述
這個視頻主要是分享8位二進制CPU的設計和實現。
首先,解釋一下為什么做這個視頻。眾所周知,計算機專業眾多課程中有一門課是計算機組成原理,這門課程實際上並不那么好理解,因為他涉及到了硬件。但是組成原理這門課又沒有對硬件部分做充分的描述,導致這門課沒有其他課那么容易學習,特別是中央處理器(CPU)部分的實現。那么我們一起來實現一個8位二進制CPU,就可以更加自然的理解計算機組成原理究竟說了些什么。
其次,關於做CPU或者說做一台計算機,做一台什么樣的計算機的問題,圖靈(1912-1954)給出過答案,圖靈在他的論文<可計算性>
中給出了什么樣的事情是計算機可以做的,什么是做不到的,以及給出了計算機的抽象模型-圖靈機,其中就描述了計算機和機器的區別,區別就是支持任何條件轉移指令的機器就是計算機,也就是說我們要做一個支持條件轉移的計算機,在這篇論文中還介紹了另一種測試計算機的方法,這就是人們熟知的圖靈測試。最早引入條件轉移指令的人是英國數學家和經濟學家查爾斯.巴貝奇(1792-1871),大約在19世紀,巴貝奇就有了制造解析機的想法,不過巴貝奇的解析機並沒有在他有生之年實現。那么我們要做的CPU就是可以支持條件轉移指令的機器,這里推薦大家兩本書,一本是查爾斯的《編碼:隱匿在計算機軟硬件背后的語言》,也是這本書讓up有了想做此視頻的想法,第二本是李忠的《穿越計算機的迷霧》,可以作為前一本書的補充,值得大家一讀再讀。以上兩本就作為教材了,如果想盡快了解做什么,可以去下載這兩本書先讀一讀。工欲善其事必先利其器,本教程使用一個電路仿真的軟件logiccircuit
(下面會提到)來制作CPU電路。
准備工作
-
小白可以看看上面的兩本書的前面部分或者快速入門數電基礎
-
下載仿真軟件
logiccircuit
:下載地址
logiccircuit
軟件使用方法
打開軟件,主窗口由以下幾個部分組成
軟件提供模塊
:軟件自身提供的一些基本輸入輸出單元和基本模塊自建模塊
:通過下面的軟件提供模塊搭建的特殊功能模塊搭建操作窗口
:拖動模塊連線和仿真展示的窗口開啟仿真按鈕
:模塊搭建和連線之后開啟仿真的按鈕
輸入輸出單元
- 輸入單元
位寬
:可選位寬如1位,8位邊
:搭建模塊時選擇接口位置
- 輸出單元
位寬
:可選位寬如1位,8位邊
:搭建模塊時選擇接口位置
- 按鈕/切換器
符號
:名字是模塊內部用的,特別是展示真值表時顯示。符號是模塊上表示該引腳的一個代號。切換器
:切換器是鎖定狀態的,和按鈕不同,按鈕按下是一個狀態,彈開是一個狀態,切換器每按下彈起一次切換一次狀態針腳
:和邊類似,搭建模塊時選擇接口位置
- 常量:和輸入單元差不多,只不過是常量
- 傳感器:暫時不涉及
- 時鍾:提供時鍾信號
- 分路器:用來合並分路針腳為總線
分路針腳數目
:引腳多的一端的針腳數分路針腳位寬
:引腳多的一端的單個針腳的位寬組合針腳位置
:單總線的位寬=分路針腳數目x分路針腳位寬,位置決定是輸入還是輸出,有三角標志
的是低位
- LED:方便顯示邏輯電平輸出,1 亮 0滅
- 7段數碼管:顯示數字
- LED矩陣
- 圖形序列
- 蜂鳴器
- 探針:方便看某一處的電平輸出
基本元件
-
非門
邏輯表達式
:\(L=\overline{A}\)真值表
A L 0 1 1 0 -
三態門(上)
-
三態門(下)
-
與門
邏輯表達式
:\(L=A\cdot B=AB\)真值表
A B L 0 0 0 0 1 0 1 0 0 1 1 1 -
與非門
邏輯表達式
:\(L=\overline{A\cdot B}=\overline{AB}\)真值表
A B L 0 0 1 0 1 1 1 0 1 1 1 0 -
或門
邏輯表達式
:\(L=A + B\)真值表
A B L 0 0 0 0 1 1 1 0 1 1 1 1 -
或非門
邏輯表達式
:\(L=\overline{A + B}\)真值表
A B L 0 0 1 0 1 0 1 0 0 1 1 0 -
異或門
邏輯表達式
:\(L=A\overline{B} + \overline{A}B\)真值表
A B L 0 0 0 0 1 1 1 0 1 1 1 0 -
同或門
邏輯表達式
:\(L=\overline{A} \overline{B} + AB\)真值表
A B L 0 0 1 0 1 0 1 0 0 1 1 1 -
ROM:只讀存儲器
-
RAM:隨機存儲器
真值表:選中自建模塊后,確保有輸入輸出單元,然后點擊菜單欄電路
-真值表
,即可查看該模塊的真值表
操作方法
總結的一些操作方法,有的可以在遇到了再回來看看
- 新建模塊
點擊菜單欄電路
-新建邏輯電路
,即可新建模塊 - 修改自建模塊名稱
選中模塊,點擊菜單欄電路
-邏輯電路
,即可修改名字,符號,類別 - 交叉線的畫法
按住Alt
,點擊交叉點 - 旋轉元器件
ctrl+L
基本邏輯電路
半加器 Half Adder
參考工程:03 半加器
單位二進制加法:0 + 0 = 0(無進位)
、0 + 1 = 1(無進位)
、1 + 0 = 1(無進位)
、1 + 1 = 0(進位1)
寫成真值表(A,B輸入,S輸出,C進位輸出)為
A | B | S | C |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 1 | 1 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 0 | 1 |
實現這樣一個運算的邏輯電路稱為半加器
半加器
電路是指對兩個輸入數據位相加
,輸出一個結果位
和進位
,沒有進位輸入
的加法器電路。 是實現兩個一位二進制數
的加法運算電路
。
根據經驗我們得知,邏輯表達式為
打開logiccircuit
根據上面異或和與關系搭建門電路,驗證
- 將
異或(XOR)門
用與
、或
、非
門實現出來
- 然后用
異或(XOR)門
和與門
共同搭建半加器(Half Adder)
- 搭建完之后通過
按鍵
和LED
測試,符合真值表邏輯即可
全加器 Full Adder 和加法器 8Adder
參考工程:04 全加器和加法器
全加器 Full Adder
全加器英語名稱為
Full Adder
,是用門電路實現兩個二進制數相加
並求出和的組合線路,稱為一位全加器
。一位全加器可以處理低位進位
,並輸出本位加法進位。
將上面真值表補齊(CI
是輸入低位進位)
A | B | CI | S | CO |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 0 |
0 | 1 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 |
1 | 1 | 0 | 0 | 1 |
1 | 1 | 1 | 1 | 1 |
- 通過兩個
半加器
設計全加器
- 搭建完之后通過
按鍵
和LED
測試,符合真值表邏輯即可
補充:也可以通過ROM存儲器地址映射的方式實現全加器
將CI
、A
、B
當做地址的三位,S(低位)
、CO
當做數據的兩位,填入
也可以實現全加器的功能。
8位加法器 8Adder
- 將8個
全加器
級聯
- 制作測試器測試
- 8位開關
- 8位LED
- 8位開關
將其組合在一起測試
補充:這種全加器叫
串形加法器
,由於高位的運算需要等待低位的進位輸出,所以會有延遲,效率不是很高。
還有一種並行加法器可以實現同步,這里不多做介紹。事實上還可以通過前面的ROM實現方法實現低延遲運算。
補碼和減法
參考工程:05 補碼與減法
關於補碼:因為計算機采用的二進制,沒有符號位的概念,因此沒法實現減法操作,所以誕生出了補碼的概念。
這里拿時鍾舉例,時鍾有12點,12=0
,1+11=0
,1-1=0
,所以1的補碼
就是11
,1減去1
就是1加上1的補碼(11)
在二進制中,一個二進制數的補碼即為該二進制數的反碼+1,反碼則是每一位都取反,例如
0001
的反碼為1110
,補碼為1111
取反器 Invert
- 一位取反器
其實就是非門,只不過加上一位使能位E
E | I | O |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
根據以上真值表,采用異或門
- 8位取反器
將八個取反器
並聯
減法運算
- 按鍵實現了
取反
和+1
的操作 與門
和非門
是為了在減法運算時屏蔽最高位的進位。
數碼管和譯碼電路設計
數碼管簡介
數碼管本質上就是LED,不同的位控制不同的LED,如下圖,從第0位到第7位,通過控制不同的LED來組合出數字。
通過以下電路測試一下
七段十六進制數碼管
參考工程:06 七段十六進制進制數碼管的制作
通過四位二進制輸入顯示
0~F
十六個數碼符號
A3 | A2 | A1 | A0 | number |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 2 |
0 | 0 | 1 | 1 | 3 |
0 | 1 | 0 | 0 | 4 |
0 | 1 | 0 | 1 | 5 |
0 | 1 | 1 | 0 | 6 |
0 | 1 | 1 | 1 | 7 |
1 | 0 | 0 | 0 | 8 |
1 | 0 | 0 | 1 | 9 |
1 | 0 | 1 | 0 | A |
1 | 0 | 1 | 1 | b |
1 | 1 | 0 | 0 | C |
1 | 1 | 0 | 1 | d |
1 | 1 | 1 | 0 | E |
1 | 1 | 1 | 1 | F |
以上也叫段碼表
單十六進制數碼管譯碼電路
陽極段碼表
A3 | A2 | A1 | A0 | number | HEX |
---|---|---|---|---|---|
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 |
- 采用ROM映射
4路地址線
到8個數據上
將上述數字填入ROM的存儲單元中
將按鍵換成輸入即模塊的制作
雙十六進制數碼管譯碼電路
- 將兩個單十六進制數碼管並聯,即可制作雙十六進制數碼管
- 將雙數碼管放到之前制作的加法器中,即可測試數碼管
七段十進制數碼管
參考工程:07 七段十進制進制數碼管的制作
- 同理,向ROM中寫入固定數據
- 點擊
另存為
,將二進制文件保存到桌面,用vscode
打開(下載插件Hex Editor
)
這里我已經寫入了數據
- 通過Python腳本寫入數據
import os
dirname = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(dirname,'test.bin'),'wb') as file:
for var in range(256):
var=str(var)
var=int(var,base=16)
byte = var.to_bytes(2,byteorder='little')
file.write(byte)
- 然后將二進制文件載入
數碼管高位消隱
要想讓高位為零時不亮,可以考慮先給單個數碼管加一個使能位。
解決思路就是做一個8位的二選一選擇器
單位二選一選擇器
當EN為0時,輸出等於B,與A無關
當EN為1時,輸出等於A,與B無關
八位二選一選擇器
8個二選一選擇器並聯
數碼管使能
如果EN為1,則數碼管使能,如果EN為0,則數碼管熄滅
消隱電路實現
- 十六進制(雙數碼管)
將高位的輸入接入通過或門接入使能端,當全為0時,EN為0,數碼管熄滅
- 十進制(三數碼管)
同理,將兩個高位的輸入用或門接入使能端,同時,防止出現例如
208
時中間的零被消隱掉,中間一位的或門需要加入最高位。
再放到原來的電路中測試一下
前面一部分都是組合邏輯電路部分,現在開始時序邏輯電路部分,區別在於,時序邏輯電路能夠保存電路狀態。
RS觸發器
參考工程:08 R-S觸發器
我們先看一下RS觸發器的真值表
R | S | Q | Q' |
---|---|---|---|
0 | 0 | 不變 | 不變 |
0 | 1 | 1 | 0 |
1 | 0 | 0 | 1 |
1 | 1 | 0 | 0 |
該電路有以下特點:
- 剛上電時,輸出的狀態是不確定的,只確定兩個輸出是相反的。
- 當其中一個輸出為1時,輸出狀態立刻確定,見上面真值表。
- 當兩個輸出都為0時,輸出狀態保持不變。
- 當兩個輸出都為1時,輸出都為0,則無法通過單個輸出判斷狀態,此時觸發器狀態不確定(應避免此情況)。
該電路主要作用在於:如果R和S都為0時,電路狀態可以得到保存,這個功能也叫作鎖存器。
D觸發器
參考工程:09 D 觸發器
拋棄掉RS的鎖存功能,R和S總是相反的,將D接到S端,D取反接到R端,兩個輸入合成一個輸入,就構成了D觸發器。
再添加一個EN,作為鎖存功能(這樣做的目的是方便對鎖存功能進行統一管理)。具體實現如下:
D邊沿觸發器
參考工程:10 D 邊沿觸發器
上升沿觸發器:En上升沿的時候,D數據才會 寫到Q
這樣做的目的是在時鍾信號輸入時,只對上升沿信號做出動作(LED亮),忽略下降沿信號,這樣,時鍾信號一個周期完成一個動作。
解決初始狀態問題
上面的不管是RS觸發器還是D觸發器還是D邊沿觸發器,都存在剛上電時狀態不確定的情況,因此,需要添加設置其初始狀態的功能。
-
RS觸發器
-
D觸發器
-
D邊沿觸發器
D邊沿觸發器的應用
上面說到D邊沿觸發器可以讓時鍾信號一個周期完成一個動作。見下面一個應用電路:
- 首先,上電,按下按鈕,完成給第一個D邊沿觸發器置1,其他清0的動作(如上,第一個LED亮)
- 松開按鈕之后,第一個輸出=第二個輸入為1,其他輸入為0,時鍾信號一個周期將所有輸入寫入到輸出,此時變第一個輸出為1為第二個輸出為1=第三個輸入為1,其他為0,這樣一個周期,LED進行一次位移,形成跑馬燈的效果。
T觸發器和行波計數器
參考工程:11 T 觸發器和行波計數器
T觸發器的T的意思是Toggle,即翻轉,如果說D邊沿觸發器是一個周期做一個動作,T觸發器就是一個周期翻轉一次狀態(具體的動作),原理就是把輸出的非再接入到輸入。
T觸發器的應用-行波計數器
上面說到,T觸發器一個周期完成一次翻轉,兩次翻轉即構成輸出的一次周期。見下面的電路
第一個T觸發器的兩個周期構成第二個T觸發器的一個周期,第二個T觸發器的兩個周期構成第三個T觸發器的一個周期,依次類推。即構成了一個8位計數器,從0到255。
三態門和寄存器
參考工程:12 三態門和寄存器
上面提到的D邊沿觸發器可以在上升沿中將輸入寫入輸出,利用這樣的功能我們可以實現一個
單字節存儲器
。
功能:通過Clear清0 ,Pre置1,在DI端輸入8位數據,給CP一個上升沿,DI數據就被寫入到DO上
問題:
總線沖突問題:如果將多個存儲器
連到一起做數據交換,那么就會存在一個輸入會連到多個輸出的問題,就會發生數據沖突,那么有沒有辦法在需要數據交換時將兩個寄存器單獨連接在一起。這個辦法就是三態門。
三態門
三態門的作用就是當B失能時,輸入與輸出處於斷開狀態(高阻態),當B使能時,輸入與輸出處於連接狀態。
將八個三態門並聯得到八位三態門電路
這樣我們就可以解決上面說的總線沖突問題
寄存器
存儲器
加上三態門
可以構成寄存器
- 端口S僅做比較有三態門和無三態門時的區別,實際並無此端口
- 當W使能時,和時鍾信號一起作用,可以將存儲器輸入寫到存儲器輸出
- 當R使能時,三態門連通,存儲器輸出連到寄存器輸出;當R不使能時,寄存器輸出處於高阻態。
R沒有使能
R使能后
三八譯碼器和存儲器組織
參考工程:13 三八譯碼器和存儲器組織
三八譯碼器
三八譯碼器,顧名思義,就是三位向八位的一個譯碼器,其真值表如下:
A2 | A1 | A0 | O7 | O6 | O5 | O4 | O3 | O2 | O1 | O0 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 1 | |||||||
0 | 0 | 1 | 1 | |||||||
0 | 1 | 0 | 1 | |||||||
0 | 1 | 1 | 1 | |||||||
1 | 0 | 0 | 1 | |||||||
1 | 0 | 1 | 1 | |||||||
1 | 1 | 0 | 1 | |||||||
1 | 1 | 1 | 1 |
邏輯電路
存儲器
前面我們實現了一個1byte的存儲器,或者說寄存器,現在我們要實現一個8byte寄存器,基本思路就是用一個三八譯碼器實現三位地址線對8個1byte存儲器的選擇。
- 測試讀寫功能
存儲器擴展和讀寫
參考工程:14 存儲器擴展、15 關於存儲器讀寫的問題
存儲器擴展
存儲器擴展分為
字擴展
和位擴展
這里首先談一下
位擴展
,例如前面是8x1byte存儲器,現在擴展為8x2byte存儲器,只要將輸入輸出的位擴寬即可
其次是
字擴展
,例如前面是8x1byte存儲器,現在擴展為16x1byte存儲器,擴寬一位地址線(加一個二選一)即可
- 加寬高位地址線
- 加寬低位地址線
存儲器讀寫問題
這里存儲器的讀和寫是獨立開來的,這樣會在某些時刻造成沖突,所以要解決這個問題,就是讓存儲器在寫的時候沒法讀。做出以下修改:
- 單字節存儲器
- 8x1byte存儲器
- 8x2byte存儲器
- 16x1byteH存儲器
- 16x1byteL存儲器