繼電器是如何成為CPU的(2)
——《穿越計算機的迷霧》整理和總結
上一篇已經從電池、開關、燈泡和繼電器開始,畫出了設計CPU所需的基本器件。這些器件將成為設計CPU的磚瓦木料。這一篇就用這些基本器件做一個CPU的雛形。
本篇所需基礎器件
傳輸門
下圖所示的傳輸門的作用是:當左邊的"~1G"端輸入為0時,左側的1A4、1A3、1A2、1A1會直接傳輸到右側對應的1Y*,就像一條線直接從1A*連接到1Y*一樣;當左邊的"~1G"端輸入為1時,左側的1A4、1A3、1A2、1A1都不能傳輸到右側對應的1Y*,就像從1A*到1Y*的連線被剪斷了一樣。
注:本文里我做的電路圖片都是GIF格式的,你可以在瀏覽器里看到隨着開關的開閉,輸入和輸出電路上的燈泡是如何變化的。每個圖上都有(http://bitzhuwei.cnblogs.com)標識我的博客地址,不過每個GIF圖的最后一幀都去掉了這個標識。這樣,看到一幀沒有標識的時候,就知道下一幀將是GIF圖的第一幀了。
傳輸門的原理很簡單,就是在每個1A*到1Y*之間的連線上放個繼電器而已,如下圖所示。(取自《穿》)
寄存器
在上一篇已經說明了寄存器的原理,這里僅僅是為了說明"74LS194"這個帶有各種無聊管腳的四位寄存器的用法。將"~CLR"、"S1"和"S0"置為1,"SR"和"SL"置為0,然后,"74LS194"就是一個簡單的四位寄存器了。(本人在multisim12.0里只找到了這個靠譜的四位寄存器,湊合用吧。)
數值顯示器
為了更直觀地看到CPU的運算結果,我們將使用"DCD_HEX"這個東西。它能夠把輸入的"0101"顯示為"5",把"1010"顯示為"A"。
本文還要用一個四位的加法器,直接在下面這個簡陋的CPU里看就好了,不再單獨展示。
一個簡陋的CPU
現在一切就緒,可以開始設計CPU了!
CPU包括運算器和控制器兩部分。我們首先做出運算器,然后逐步實現控制器,最后感受一下用機器語言編程的過程。本文實現的CPU雖然功能及其簡陋,但是能夠傳達出當前真實CPU的原理。
運算器和手動控制器
現在我們要做的這個CPU,字長是4位,只能做兩個數的加法。實現了運算器和手動版的控制器的CPU如下圖所示。我們把這個版本稱為version1的CPU。
上圖中,"Add"是加法器,能執行兩個4位數的加法運算。"RA"和"TR"是寄存器,"GAA"和"GBA"是傳輸門。"4""3""2""1"用來准備需要相加的數據(0到15),"KTR""KRA""KGA""KGB"是用來控制傳輸門通斷和寄存器脈沖的開關。
在上圖所示的GIF動畫中,顯示了"5+1+2+4"這個過程。這個過程可以分為4個步驟:①加載一個數值(Load);②加上一個數值(Add);③加上一個數值(Add);④加上一個數值(Add)。具體來說,每一個步驟要做的事情是:
指令 |
內容 |
加載一個數(Load) |
准備數據(0101); KGB↓, KGA↑; KRA↓↑ |
加一個數(Add) |
准備數據(0001); KGB↓, KGA↑; KTR↓↑; KGA↓, KGB↑; KRA↓↑ |
加一個數(Add) |
准備數據(0002); KGB↓, KGA↑; KTR↓↑; KGA↓, KGB↑; KRA↓↑ |
加一個數(Add) |
准備數據(0004); KGB↓, KGA↑; KTR↓↑; KGA↓, KGB↑; KRA↓↑ |
你可以看到,每次執行(Add)這一步,要做的事情(搬動開關)是一樣的,規律性極強。這意味着可以用簡化的方式控制"KTR""KRA""KGA""KGB"這幾個開關的狀態。經過簡化的CPU就有一定的自動化控制的性質了,如下圖所示。我們把這個版本的CPU稱為version2的CPU。
Verison2要比剛才的verison1進化了一些。為便於理解,我們保留原來的"KTR""KRA""KGA""KGB"這四個開關(把它們挪到了右上角,因為實在沒地方放了),但讓它們永遠保持閉合的狀態。這是想說明:version2里新增的電路只是實現了更加自動化地控制"KTR""KRA""KGA""KGB"的開閉,它沒有改變version1中電路的工作流程。
Version2中的"KLoad"和"KAdd"開關分別代表了"Load"和"Add"這兩個指令。當"KLoad"閉合時,表示CPU要進行加載操作,這會把"4""3""2""1"上的數據存入寄存器"RA";當"KAdd"閉合時,表示CPU要進行相加操作,這會把"4""3""2""1"上的數據與"RA"當前的數據相加,然后相加的結果又存儲到"RA"。
Version2中的"K0"和"K1"兩個開關會依次的開閉,即兩者總有一個是斷開且另一個是閉合的(若出現其它情況那就是電路設計錯了)。所以實際上"K0"和"K1"可以用一個"2位循環移位寄存器"代替。(為便於理解,仍然保留"K0"和"K1"這兩個開關,只不過讓它們永遠保持閉合的狀態)
Version2中,只需重復"准備指令,准備數據,執行指令(K↓↑↓↑)"這樣的操作,就能完成version1中的控制功能。這需要一個從"KLoad""KAdd""K0""K1"到"KTR""KRA""KGA""KGB"的轉換電路,即version2電路圖中的上半部分,如下圖所示。
這需要一點點設計邏輯電路的知識,本文直接列出真值表,據此即可畫出轉換電路。(想知道如何推導的話,請查閱《穿》或者數字電路類書籍)
Kload |
Kadd |
K0 |
K1 |
KGA |
KRA |
KTR |
KGB |
1 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
其中"KLoad""KAdd""K0""K1"為輸入,"KTR""KRA""KGA""KGB"為輸出。只有如上三種輸入情況下,輸出部分會有1;其它的輸入情況下,輸出全部為0,所以就不需要列出來了。
舉個例子,"KGA"="KLoad""~KAdd""K0""~K1" + "~KLoad""KAdd""K0""~K1"=("KLoad" ⊕"KAdd")"K0""~K1",據此可以畫出"KGA"的轉換電路。
在version2中,CPU要做的就是重復"准備指令,准備數據,執行指令(K↓↑↓↑)"這件事。其中執行指令這一步是完全重復完全自動化的(只要用振盪器替換K就可以),而准備指令和准備數據還需要手工操作。每次要執行哪個指令、要准備的數據是多少,這都是沒有規律的,可改進的方法就是:把指令和數據按順序保存到一些特別的寄存器里,需要的時候取出來用。這些特別的寄存器,就是內存。
內存
所謂飯前便后要洗手,一個完整的衛生間,除了有若干坑位,還得有洗手池配套。類似的,學習CPU的結構原理,也得把"內存"牽出來溜溜,否則無法說明計算機編程的本質。CPU+內存才是一個完整的計算機(核心),才能展現出CPU的功能。由此也能聯系到,將鼠標鍵盤顯示器等稱為"外部"設備的道理。
最基本的內存單元能夠存儲和讀寫一個位(bit),是由一個上升沿D觸發器和傳輸門組成,如下圖所示。傳輸門電路我沒有找到只有一位的,拿這個四位GAA的湊合看吧。
內存單元的符號如下圖所示。(取自《穿》)
把8個bit單元的讀寫端分別連起來,就可以存儲一個字節(8bit)。下圖是能夠存儲5bit的內存單元。(取自《穿》)此圖所示的結構,我們稱之為"一層"。
存儲4bit的一層的符號如下圖所示。這時我學會了multisim12里的"層次塊"這個東西,下圖就是用層次塊畫的,這樣可以將復雜的電路封裝起來,省地方了,還能復用。所以說模塊化的思想在硬件設計里就有了。("用層次塊替換…"和VS里的"Extract Method…"功能是何其類似!)
用一層一層的內存單元,即可構成存儲器。對於有8層的存儲器,需要3個bit表示需要讀寫的層數(23=8)。地址譯碼器的作用是:輸入101時,輸出的第5個(從0開始計數)引腳為1,其余均為0。有了地址譯碼器,存儲器對外只需很少的地址線(例如10根)即可使用很多層(例如1024層)。這也符合了軟件設計中接口盡可能簡單的原則。本文所用的RAM存儲器的結構如下圖所示。其中左邊的"3-8translator"就是譯碼器。
其層次塊符號如下圖所示。
這個RAM有三條地址線(Addr3、Addr2和Addr1),能夠表示23=8個字(層),每個字的長度是4bit。這個小小的RAM剛好夠存儲(5+1+2+4)這個示例的指令和數據,下面就用這個RAM繼續進化CPU。
這里也順便把"3-8translator"譯碼器的電路實現貼出來吧,如下圖所示。
9位循環移位寄存器
在version2里用的"2位循環移動寄存器"只需要一個乒乓觸發器(詳見上一篇)就可以了,但是后面要做的全自動控制器,需要一個"9位循環移位寄存器",且這個寄存器要在加電時自動將第一個輸出管腳置為1,其余為0。具有這樣的功能的寄存器如下圖所示。
如上圖所示,第二行有4個D↑觸發器,其中最左邊的那個負責第一個輸出管腳,即應該在加電時就置為1的那個管腳。這是通過左下方的電路實現的,其原理大家自己琢磨吧,無非是利用反饋電路實現了只生效一次這個功能而已。"9位循環移位寄存器"的符號如下圖所示,其中"D0"是第一個輸出管腳。
還有一個3bit的計數器,在上一篇里已經提過計數器,這里直接上圖。
其層次塊表示如下圖所示。
自動控制器
有了內存,我們就要把指令和數據存進去。存儲數據很好理解,是多少就寫入多少。存指令之前,我們需要為每條指令分配一個4位的編碼,比如0000表示Load,1111表示Add,然后用這個指令碼控制"KLoad"和"KAdd"的開閉,所以這又是一個轉換電路。有了這個轉換電路,就可以只用"K"開關來完成"准備指令、准備數據、執行"這些操作了。全部自動化的CPU如下圖所示。我們將這個版本的CPU稱為version3的CPU。
這里的"ALU"是用層次塊表示的version1里的電路,因為不這樣的話,電路太大,而且不容易重點突出自動控制器的工作流程。同樣的,"ShiftRegister9bit"、"Translator"、"Counter3bit"、"RAM8F4bit"都是層次塊表示的電路。
"ShiftRegister9bit"會依次將輸出端置為1,這可以從上方的三個數值顯示器看出來。"Counter3bit"是3bit的計數器。"RAM8F4bit"是8層(每層4bit)的內存。"Translator"實現了控制信號的自動控制,"Translator"的內部實現如下圖所示。
Version3所示的下半部分展示了譯碼電路,即把代表指令的4bit信號轉換為指令信號的電路。由於我們指定Load和Add指令的代碼(0000和1111)都是很規則的,所以譯碼電路也比較簡單,如下圖所示。
Version3的CPU用"9位循環移位寄存器"等器件實現了"取指令、分析指令,取數、執行"的全部自動化,其中取指令、分析指令、取數、執行分別占用了移位寄存器的t0- t2、t3、t4-t6、t7-t8這9個階段。
注:如果用振盪器替換了"K",這個CPU會不停地運轉下去,這樣就得不到我們想要的結果0xC(十進制的12)了。所以還需要添加"停機"指令。不過到這里已經說清了CPU的控制器是如何一步步實現自動化的,不再繼續講述如何添加新的指令。
最原始的機器語言編程
在給出上文的自動控制器里,我們只說了從內存里取指令和數據,而沒有說這些指令和數據是如何寫進去的。其實寫進去的過程就是(機器語言)編程的過程。最簡單的,你可以用撥動開關的方式,調整好要寫入的位置,再調整好要寫入的數值,把指令和數據一個字一個字地寫入內存。最初的計算機編程就是用類似這樣的方式(打孔紙帶)編程的。(這個過程實在無聊,本文就不展示了,有興趣的話自己拿multisim玩玩就好~)
用助記符和一些宏來代替機器碼,這就是匯編語言。用C語言這種方式封裝了匯編語言的編程方法,就是面向過程編程。用C++\C# \Java這樣的語言封裝了面向過程的語言,就是面向對象的編程方法。用if(..){…}代替JMP指令這種東西比較容易想象,但用"封裝繼承多態"這種飄渺的概念代替面向過程編程就有點困難了。我在另一篇文章《用C表達面向對象語言的機制——C#版》中作了分析和總結,現在終於算是從繼電器一路走到面向對象編程了。
總結
本系列文章只此兩篇就結束了。貌似太少算不上一個系列,不過管他呢,反正問題寫清楚就行了。如果有誰想做個實用的CPU,不妨用VHDL。SystemC也是用於硬件設計的,是用C++寫的一個類庫。我本科的時候用過,也不錯。
感謝博客園眾多園友推薦的好書,寫本篇之前惡補了一陣,受益匪淺!現和我讀的幾本書一起陳列出來,方便查找。點擊推薦者即可跳轉到其博客上。
書名 |
作者 |
推薦者 |
《穿越計算機的迷霧》 |
李忠 |
|
《編碼:隱匿在計算機軟硬件背后的語言》(《編碼的奧秘》) |
Charles Petzold (伍衛國, 王宣政, 孫燕妮 譯) |
|
《計算機系統要素——從零開始構建現代計算機》 |
Noam Nisan, Shimon Schocken (周維, 宋磊, 陳曦 譯) |
|
《CPU自制入門》 |
水頭一壽, 米澤遼, 藤田裕士 (趙謙 譯) |
|
《CPU芯片邏輯設計技術》 |
朱子玉, 李亞民 |
|
《30天自制操作系統》 |
川合秀實 (周自恆, 李黎明, 曾祥江, 張文旭 譯) |
|
《Orange'S:一個操作系統的實現》 |
於淵 |
本文作為整理和總結性質的文章,不求面面俱到,只為整理思路。想從燈泡開始一點一點地了解計算機的構成的話,建議看《編碼的奧秘-隱匿在計算機背后的軟硬件語言》(這一本的中文翻譯也不錯)。
后續
了解了硬件的設計原理,又寫過那么多的程序,下一步就該做個操作系統了。這次要寫個真實可用的操作系統出來,不必像研究CPU一樣只能弄個超簡化的仿真模型玩了。再次感謝博客園眾多園友推薦的好書,讀了這幾本書我才相信能在個把月內做出一個有圖形界面的操作系統。如果再把網絡協議和瀏覽器整進來,豈不就可以順利暢游網絡?