二十、內存
所謂飯前便后要洗手,一個完整的衛生間,除了有若干坑位,還得有洗手池配套。類似的,學習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#版》中作了分析和總結,現在終于算是從繼電器一路走到面向對象編程了。