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