前面我們介紹了所有的基礎零件,接下來就可以構建CPU了,這節的內容會比較多。
既然開始構建CPU,就少不了程序,因為CPU就是用來執行程序的。我們知道任何編程語言編寫的程序最終都會轉換為二進制,所以這里我們直接使用二進制的編程語言(機器語言)。比如我們讓CPU計算一個加法3 14,這個加法運算用機器語言來描述的話就類似于下面這段二進制:
看著挺亂是吧,沒關系,后面我們會講解。大家只要清楚這段二進制的程序需要保存在內存里,這樣CPU才能讀取并執行。
不僅是程序,3和14作為運算的數據也是保存在內存里的,CPU要做的動作是從內存中某個地址讀取數據3和14,然后根據程序要求(加、減……)對二個數字進行運算,運算的結果保存在內存中某個地址處。
要實現這樣的功能,必須有一個約定,要讓CPU能夠識別相應的動作,即讀取、運算、保存,所以我們建立如下約定:(稱為指令表)。
指令表可以理解為是程序的解釋器,當CPU拿到一段程序,例如00101110時,它必須知道這意味著什么,而指令表就能起到答疑解惑的作用。
具體來說每段程序(指令)的前四位對應著指令表中的操作碼,后四位對應著指令表中的地址。比如00101110,前四位0010是操作碼,它的含義是LOAD_A(見指令表),代表讀取數據放入寄存器A。再看后四位1110,指令表中描述的內容是“4位內存地址”,這其實就是要讀取的數據的內存地址。連在一起的含義就是“在1110這個地址處讀取數據保存到寄存器A”。
接下來上電路!首先我們需要一塊內存,可以直接使用上節提到的256B內存,但為了方便起見,我們假設它只有16個地址,可以保存16個8位二進制數。另外需要六個寄存器,每個可以保存一個8位二進制數,其中寄存器A-D用于臨時存儲和操作數據,指令地址寄存器用于記錄程序運行到哪里了(程序指令的地址),指令寄存器用于存放指令內容。
接下來分析工作過程,當計算機啟動時,所有寄存器初始值都是00000000,CPU開始進入第一個階段:取指令,也就是從內存中獲取指令。指令地址寄存器會連接到內存,讀取地址為00000000的數據,即00101110,這個數據會保存在指令寄存器,第一個階段結束。
第二個階段:解碼,即弄清楚指令要做什么。其實前面我們已經做了鋪墊,指令內容是00101110,其中前四位0010是操作碼,在指令表中對應的就是LOAD_A,指令后四位是1110,對應的是4位內存地址,整體意思就是從1110的位置讀取數據保存在寄存器A。但現在還沒有現成的電路能夠做解碼工作,所以我們需要添加一部分電路,如圖。
這部分新添加的電路我們暫且稱為解碼電路,指令寄存器的前四位數據0010作為解碼電路的輸入,經過這些門電路的處理,最終會輸出1。換句話說,解碼電路的作用就是識別指令是否是0010(LOAD_A),只有指令是0010時,輸出才是1,否則就是0。
第三個階段:執行。解碼電路的輸出會連接到內存的允許讀取端口,而指令寄存器的后四位1110會連接到內存的地址端口,這樣就相當于允許讀取內存地址為1110的數據,這個數據是00000011(十進制3)。
接下來這個數據要如何保存到寄存器A呢?我們需要讓解碼電路的輸出同時連接到寄存器A的允許寫入端口,而四個寄存器的數據輸入端口要連接到內存的數據端口。當數據00000011被讀取出來時,會同時發給四個寄存器,但只有寄存器A是允許寫入的,所以數據就被保存在寄存器A中了。
接下來將指令地址寄存器 1,變成00000001,以便取下一條指令,后面的步驟與前面類似。需要注意的是,前面的解碼電路只能識別第一條指令LOAD_A,后面每一條指令都需要單獨的解碼電路支持。我們把所有指令對應的解碼電路和指令寄存器、指令地址寄存器等部分統一叫做控制單元。
接下來我們快速分析剩余的指令,現在指令地址是00000001,所以從內存中取出00011111存入指令寄存器。前四位0001對應的指令就是LOAD_B,后四位1111是要讀取的內存地址,對應的數據是00001110(十進制14),這個數據會被存放到寄存器B中。然后指令地址寄存器 1(00000010),繼續取下一條指令。指令內容是10000100,前四位1000在指令表中對應的是ADD,后四位0100分別是二個寄存器的地址01和00(因為寄存器A-D只有四個,用二位二進制數就可以描述),其中00是寄存器A,01是寄存器B,所以這個指令的作用就是將寄存器A、寄存器B的值相加。涉及到加法,就要用到之前講過的算術邏輯單元,也稱運算單元(ALU)了,我們簡化一下電路如下:
寄存器A、B會通過控制單元連接到運算單元的二個輸入端,同時控制單元會將操作符也傳遞給運算單元,這樣就可以計算了。計算的結果必須保存起來才行,但指令本身并未明確保存在哪里,所以這里面有個約定,運算結果會保存在指令地址中最后一個寄存器里,二個寄存器地址分別是01和00,后面就是00,也就是寄存器A,所以最終結果會通過控制單元保存到寄存器A中,這個結果是00010001(十進制17)。
指令地址寄存器 1(00000011),繼續取下一條指令。指令內容是01000111,前四位0100表明指令是STORE_A,即將寄存器A的數據寫入內存,內存地址就是指令的后四位0111,控制單元會發送給寄存器A允許讀取信號,發送給內存允許寫入信號,將寄存器A的值保存在內存中對應的位置。
終于我們完成了一句簡單的程序任務,兩數相加,并成功保存結果。我們會發現每個指令的讀取、解碼、執行,相當于一個周期,CPU就是不斷的重復這個周期,進而完成各類任務。在每個周期中算術邏輯單元、控制單元、存儲單元(內存)都需要密切配合,節奏不能亂,才能保證最后的結果正確。但如何確保這個節奏是恰當的呢?既不能太快,因為即使是電信號的處理也需要時間,也不能太慢,造成計算效率太低。所以有一個單獨的電路在控制這個節奏,就好像一臺時鐘,精確的指揮各個部分有條不紊的運行。CPU都有一個重要的指標:主頻,例如2.6GHz,相當于26億次周期/秒,意味著一秒鐘內CPU會執行26億次周期,主頻越高的CPU代表速度越快。我們把帶有時鐘電路的算術邏輯單元、控制單元、6個寄存器封裝成一個相對獨立的部分,這就是CPU!
至此,我們從一個簡單的晶體管開關開始,一路添磚加瓦,終于打造了一個完整的CPU,當然也是最基礎的CPU。相信這個系列文章能讓我們對硬件與軟件的結合點有了清晰的認知,我們日常所使用的各類軟件都是程序指令編寫出來的,每個程序的每條指令在CPU內部都會經歷眾多晶體管開關的處理,最終完成我們希望的任務。
來源: 孫老師聊人工智能