CPU的工作原理1——為什么芯片只能識(shí)別二進(jìn)制
我們可能都聽說過這樣的話:“CPU是計(jì)算機(jī)的大腦,所有運(yùn)算都在這里完成”,“CPU是由數(shù)十億個(gè)晶體管組成的”,“CPU只能識(shí)別二進(jìn)制,類似于1001101100這樣的數(shù)據(jù)”,“所有的編程語言最終都會(huì)被轉(zhuǎn)換為二進(jìn)制進(jìn)行處理,因?yàn)镃PU只識(shí)別二進(jìn)制”……。時(shí)間久了,我們會(huì)把這些話當(dāng)做約定俗成的公理來看待,很少思考這是為什么。
我有著20年編程經(jīng)驗(yàn),雖然日常工作中使用編程解決各類問題在我看來都是信手拈來,但有幾個(gè)問題一直困擾我,CPU為什么能識(shí)別二進(jìn)制?它的工作原理是什么?數(shù)十億個(gè)晶體管起到什么作用?這些問題不明白雖然不會(huì)影響我的生活、工作,但會(huì)讓我無法全面認(rèn)知計(jì)算機(jī)運(yùn)行的原理,總感覺缺少點(diǎn)什么,接下來我就帶著大家一起看看CPU是如何工作的。
先來看CPU的構(gòu)成,其實(shí)不只是CPU,也包括GPU、NPU……等等所有的芯片都是一樣的,他們都是由很多個(gè)“開關(guān)”組成的,對(duì),就是開關(guān),你沒看錯(cuò)!
來源|1688.com
就好像家里的水龍頭,打開,水流出來,關(guān)上,水沒了。當(dāng)然芯片里不是真的有水龍頭,只是個(gè)比喻。早期的計(jì)算機(jī)使用的是繼電器開關(guān),在一些工業(yè)電氣設(shè)備中還能看到這種器件。
來源|1688.com
典型的外觀就是一大卷銅線,旁邊有幾個(gè)彈簧片,彈簧片上有金屬觸點(diǎn),當(dāng)銅線圈通電時(shí)會(huì)產(chǎn)生磁場,吸引彈簧片產(chǎn)生位移,從而使金屬觸點(diǎn)連接或斷開,本質(zhì)上說就是一個(gè)開關(guān),只不過它控制的是電流,而不是水流。
后來人們發(fā)現(xiàn)這種繼電器開關(guān)故障率太高,哪怕是一只蟲子爬進(jìn)去,也可能造成開關(guān)短路,軟件編程中的“bug”一詞就來源于此。所以人們又研究出了電子管開關(guān),這種開關(guān)摒棄了機(jī)械運(yùn)動(dòng)部件,故障率大大降低。
來源|zhe2.com
現(xiàn)在逛一些舊家電市場,可能還會(huì)看到使用老式電子管的收音機(jī)。但電子管由于體積大,功耗高,后來基本被晶體管替代了,除了一些專業(yè)領(lǐng)域,比如音響功放,因?yàn)殡娮庸苤瞥傻墓β史糯笃骺梢员WC高品質(zhì)的原聲,所以音響發(fā)燒友,都會(huì)以擁有一臺(tái)優(yōu)質(zhì)的電子管功率放大器為榮。
最后我們看看晶體管,它也是一個(gè)電流開關(guān),相較于電子管,它可以做得非常小,功耗非常低,所以現(xiàn)在的芯片都是基于晶體管開關(guān)的。
來源|電子通
當(dāng)然這張圖片是普通的晶體管,并不是芯片中微型晶體管的樣子,晶體管可以比較大,在一些家用電器的電路板上經(jīng)常能看到這樣的東西,外觀樣式很豐富。同時(shí)也可以做的非常小,在一個(gè)指甲蓋大小的區(qū)域里能集成上百億個(gè)晶體管。
我們說過晶體管就是一個(gè)開關(guān),類似于水龍頭,水龍頭控制的是水流,之所以打開開關(guān),有水流出,是因?yàn)橛兴畨旱拇嬖凇?/p>
同樣晶體管開關(guān)控制的是電流,之所以打開開關(guān),有電流通過,是因?yàn)橛须妷旱拇嬖凇N覀冇眠@個(gè)符號(hào)來表示一個(gè)晶體管開關(guān)。
上面是控制端,相當(dāng)于水龍頭的開關(guān),當(dāng)控制端施加一個(gè)高電位(可以簡單理解為有電),輸入端和輸出端之間的通道打開,電流由輸入端流向輸出端。如果控制端是低電位(相當(dāng)于沒電),通道關(guān)閉,輸出端沒有電流流出。為了描述方便,我們使用0代表沒電,1代表有電,一個(gè)晶體管開關(guān)會(huì)形成下面二種狀態(tài):
也就是控制端有電(1),輸出端就有電(1),控制端沒電(0),輸出端沒電(0)。所以一個(gè)晶體管開關(guān)只能有二種狀態(tài),要么是1要么是0,而1和0就是二進(jìn)制,這也就是CPU只能識(shí)別二進(jìn)制的道理。
本節(jié)我們介紹了芯片為什么只能識(shí)別二進(jìn)制,下節(jié)開始我們會(huì)介紹由多個(gè)晶體管開關(guān)組合形成的基本門電路,若干門電路組合又能形成更復(fù)雜的存儲(chǔ)電路、運(yùn)算電路,然后是半加器、全加器……直到形成整個(gè)CPU!
CPU的工作原理2——基本門電路
上節(jié)我們介紹了CPU的基本單元是晶體管開關(guān),接下來我們就看看這一堆開關(guān)組合在一起能做什么?首先我們需要用晶體管開關(guān)制作幾種最基本的建筑材料——門電路。
這有一個(gè)晶體管開關(guān),我們?cè)谳斎攵颂砑右粋€(gè)通道(細(xì)線),這條線比其他線更細(xì),留意這點(diǎn)。
然后我們?nèi)藶橐?guī)定控制端為輸入端,新加的細(xì)線為輸出端。
請(qǐng)注意,雖然晶體管本身有固定的輸出、輸出、控制端,但作為使用者我們完全可以根據(jù)需要調(diào)整輸入端、輸出端的定義,只要輸入端的變化能引發(fā)輸出端變化即可。
接下來我們分析輸入和輸出端之間的關(guān)系,當(dāng)輸入端是0(0代表沒電,1代表有電),主通道關(guān)閉,從左邊過來的電流無法到達(dá)右邊,但可以經(jīng)這條細(xì)線流出,所以輸出是1。即輸入是0,輸出是1。
接下來輸入是1的話,主通道打開,電流從左邊流到右邊,因?yàn)橹魍ǖ劳?,輸出端(?xì)線)就沒有電流了。
我們可以做個(gè)類比,某天早晨打開水龍頭,發(fā)現(xiàn)沒水,一看新聞,原來市政主管道被挖斷了,路面上都能游泳了,所以家里就沒水了,這是同樣的道理,因?yàn)樗紡闹鞴艿懒髯吡?,家里的小管道自然就沒水了。所以輸入是1,輸出是0。
我們用一個(gè)表格記錄輸入、輸出之間的關(guān)系,會(huì)發(fā)現(xiàn)它們之間存在相反的關(guān)系,所以這個(gè)簡單的電路稱為非門電路,非就是相反的意思,門是一個(gè)形象的解釋,就好像一個(gè)開關(guān),門打開,人能進(jìn)出,門關(guān)上,誰也不能進(jìn)出。這個(gè)非門電路以后我們經(jīng)常用到,為了簡單起見,我用一個(gè)符號(hào)代表這個(gè)電路。其中的NOT就是非的意思。
然后我們看第二個(gè)門電路,這有二個(gè)晶體管開關(guān),把他們首尾相連,然后將兩個(gè)晶體管的控制端當(dāng)做輸入,分別叫輸入A、輸入B,最后一個(gè)晶體管的輸出端當(dāng)做輸出。
然后分析下輸入和輸出的關(guān)系,當(dāng)輸入A=0,輸入B=1時(shí),第一個(gè)開關(guān)主管道關(guān)閉,第二個(gè)開關(guān)主管道打開,電流無法從左邊流到右邊,輸出=0;當(dāng)輸入A=1,輸入B=0時(shí),也是類似,輸出=0;當(dāng)輸入A=0,輸入B=0時(shí),輸出當(dāng)然=0;而當(dāng)輸入A=1,輸入B=1時(shí),二個(gè)開關(guān)都打開,輸出=1。
用表格整理一下,會(huì)發(fā)現(xiàn)一個(gè)規(guī)律,只有當(dāng)A、B都是1的時(shí)候輸出才是1,可以用一句話描述“當(dāng)輸入A=1并且輸入B=1時(shí),輸出才=1”,其他的情況輸出都是0。這個(gè)電路稱為與門電路,與就是并且的意思。這個(gè)門電路以后會(huì)經(jīng)常用到,也用一個(gè)符號(hào)代表,其中AND就是與的意思。
接下來再用二個(gè)晶體管彼此相連,但不是串聯(lián),而是并聯(lián),也就是兩個(gè)輸入端連在一起,兩個(gè)輸出端連在一起。
注意,圖中那個(gè)圓弧代表二條線不是相連,因?yàn)樵谄矫嫔蠠o法畫出立體圖,大家可以想象一下,二條線一個(gè)在上一個(gè)在下,沒有任何接觸。現(xiàn)在我們將兩個(gè)晶體管的控制端分別當(dāng)做輸入A、輸入B,輸出端不變,接下來分析輸入輸出之間的關(guān)系。
當(dāng)輸入A=0,輸入B=1時(shí),下面這個(gè)晶體管打開,電流可以走下面通道從輸出端流出,輸出=1;當(dāng)輸入A=1,輸入B=0時(shí),上面這個(gè)晶體管打開,電流可以走上面通道從輸出端流出,輸出=1;當(dāng)輸入A=0,輸入B=0時(shí),二個(gè)晶體管都關(guān)閉,輸出=0;當(dāng)輸入A=1,輸入B=1時(shí),兩個(gè)通道都打開,輸出=1。
使用表格總結(jié)一下,會(huì)發(fā)現(xiàn)這樣一個(gè)規(guī)律:“只要輸入A或者輸入B有一個(gè)為1,輸出就=1”,這個(gè)門電路稱為或門電路,為了方便描述,使用這個(gè)符號(hào)代替或門電路,其中or就是或者的意思。
有了上面三種最基本的門電路,我們就可以組合出更復(fù)雜的門電路(姑且稱為二階門電路,與基本門電路區(qū)分),種類很多,比如與非門、或非門、異或門等等,我們舉其中一個(gè)例子——異或門。電路是這樣的,由二個(gè)與門、一個(gè)非門、一個(gè)或門組成。
電路分析用下面四張圖來表示,大家可以自己推理一下,看結(jié)果是否正確。
最后用表格統(tǒng)計(jì)出來:
它的規(guī)律是:當(dāng)輸入A和輸入B不同時(shí),輸出=1。我們用這個(gè)符號(hào)來描述異或門。
其他二階門電路我們不一一介紹了,有興趣的讀者可以自己查閱資料,下節(jié)我們來看看由這些門電路如何制作加法器,實(shí)現(xiàn)加法運(yùn)算。
CPU的工作原理3——如何加減乘除
我們知道CPU的主要功能就是運(yùn)算,加減乘除運(yùn)算的基礎(chǔ)是加法運(yùn)算,所以我們先來看如何制作一個(gè)加法器。
我們所說的加法器當(dāng)然是指二進(jìn)制的加法,請(qǐng)看下圖:
左邊表格是二個(gè)一位二進(jìn)制數(shù)相加的情況分析(二進(jìn)制沒有2,只能通過進(jìn)位用10描述)。目標(biāo)有了,怎么實(shí)現(xiàn)呢?之前介紹過的異或門的邏輯與此很像(右邊的表格),我們看到前三種情況的加法,異或門可以直接實(shí)現(xiàn),而最后一種情況1+1的結(jié)果是0而不是10,這里面其實(shí)缺少了一個(gè)進(jìn)位,我們可以增加一個(gè)與門,解決進(jìn)位的問題。
電路圖是這樣的,其中異或門的輸出作為“和的個(gè)位數(shù)”,與門的輸出作為進(jìn)位。最終實(shí)現(xiàn)的結(jié)果如下表:
可能有人會(huì)想,1+1=10沒問題,但0+0=00,0+1=01很是奇怪,能否把前面的0去掉呢?其實(shí)我們不用擔(dān)心,這只是與我們的日常思維習(xí)慣有些沖突,并不影響結(jié)果的正確性,畢竟在一個(gè)數(shù)字前面加0和不加0的結(jié)果都一樣。
這樣我們就實(shí)現(xiàn)了一位二進(jìn)制數(shù)的加法電路,使用這個(gè)符號(hào)表示。
為什么叫“半加器”呢?因?yàn)檫€不完善,因?yàn)橐粋€(gè)完整的加法必然要考慮多位數(shù)相加,剛剛分析的只是一位數(shù)相加,如果有多位數(shù),必然要考慮前一位數(shù)字相加后可能有進(jìn)位,這個(gè)進(jìn)位也要加進(jìn)來,所以還可以對(duì)這個(gè)電路繼續(xù)完善,直至滿足多位數(shù)相加,這個(gè)完善的加法電路就稱為全加器,用這個(gè)符號(hào)表示。
有了半加器、全加器,就可以實(shí)現(xiàn)多位二進(jìn)制相加了,例如二個(gè)八位二進(jìn)制數(shù)相加的電路如下:
可簡化為:
減法器的過程稍顯復(fù)雜,因?yàn)闇p法不考慮進(jìn)位,但需要考慮借位,而借的這一位如何記錄,如何歸還都是問題,所以我們要想辦法將借位化解掉。我們先從熟悉的十進(jìn)制減法入手,例如35-16,用借位法很容易得出結(jié)果19,但現(xiàn)在我們要避免借位,怎么做呢?我們可以把式子變換一下:
35-16=35-16+100-100=35-16+99+1-100=35+(99-16)+1-100
這樣99-16就不涉及到借位,因?yàn)閷?duì)于二位數(shù)來說99是最大的。我們繼續(xù)變換式子:
35+(99-16)+1-100=35+83+1-100=118+1-100=119-100
這時(shí)又遇到減法,但是不涉及借位,只需把百位的1去掉即可,結(jié)果是19。雖然看起來繞了很大一個(gè)彎路,但我們成功地避免了借位。
接下來我們來看二個(gè)八位二進(jìn)制數(shù)相減的例子:
參考前面十進(jìn)制減法的例子,把這個(gè)式子轉(zhuǎn)換為:
這其中涉及到二次減法,其中:
觀察一下結(jié)果會(huì)發(fā)現(xiàn),差和減數(shù)是按位取反的,也就是每位0、1正好相反,這個(gè)邏輯可以使用如下電路實(shí)現(xiàn):
但問題是,該電路只會(huì)對(duì)輸入取反,而我們要做的是既能做加法也能做減法的電路,所以應(yīng)該在減法時(shí)實(shí)現(xiàn)反轉(zhuǎn),改造電路如下:
當(dāng)做減法時(shí),取反端=1,才對(duì)輸入取反,如果是加法,取反端=0,輸入不取反。我們將這個(gè)電路簡化一下,稱為求補(bǔ)器:
有了求補(bǔ)器,我們就可以將第一個(gè)減法變?yōu)榧由先》春蟮慕Y(jié)果:
這樣就只剩下最后一個(gè)減法,減去100000000,這個(gè)邏輯很簡單,只需將首位變?yōu)?即可,當(dāng)然是在做減法的時(shí)候。終于我們將所有的減法都化解掉了,現(xiàn)在我們使用加法器,再配合異或門就可以實(shí)現(xiàn)加減法運(yùn)算。
圖中有三個(gè)SUB端,這就是加減法的切換開關(guān),當(dāng)SUB=0時(shí),進(jìn)行加法運(yùn)算,當(dāng)SUB=1時(shí),進(jìn)行減法運(yùn)算。在減法中,輸入B的數(shù)據(jù)會(huì)先通過求補(bǔ)器進(jìn)行取反,然后再和輸入A相加。另外通過加法器的CI(進(jìn)位輸入)可以實(shí)現(xiàn)結(jié)果+1(因?yàn)镾UB=1),最后加法器的CO進(jìn)位輸出(也就是結(jié)果的首位數(shù)據(jù))通過一個(gè)異或門處理,減去1,最終得到最后的結(jié)果。
至此我們的電路已經(jīng)能做加減法了,而乘法就是多次加法,除法就是多次減法,所以我們也就能實(shí)現(xiàn)乘除的運(yùn)算了。
CPU的工作原理4——邏輯運(yùn)算及ALU
日常生活中有很多涉及到邏輯運(yùn)算的地方,例如“如果這個(gè)周末天氣好,我們也有空,就會(huì)去郊游”,這句話里就有邏輯運(yùn)算,“周末天氣是否好”、“我們是否有空”這是二個(gè)條件,只有當(dāng)二個(gè)條件都滿足時(shí),“是否去郊游”這個(gè)結(jié)果才是肯定的,如果有一個(gè)條件不滿足,郊游就泡湯了。很明顯,這個(gè)邏輯與之前介紹的與門電路非常像,所以用門電路非常適合做邏輯運(yùn)算,換句話說CPU不僅能做算術(shù)運(yùn)算,邏輯運(yùn)算更是它的拿手好戲。例如“判斷一個(gè)數(shù)字是否是負(fù)數(shù)”、“判斷所有輸入是否為0”等等。
下面這個(gè)電路就可以判斷所有輸入A1-A8是否都是0,只有全部是0,最終輸出才是1,否則輸出就是0。
目前為止,我們將晶體管開關(guān)進(jìn)行巧妙的組合,已經(jīng)能夠?qū)崿F(xiàn)算術(shù)運(yùn)算、邏輯運(yùn)算了,按照慣例,我們將這樣一堆電路進(jìn)行封裝,簡化為一個(gè)符號(hào),它被稱為算術(shù)邏輯單元(或運(yùn)算單元),簡稱ALU。
它具有二個(gè)8位二進(jìn)制作為輸入信號(hào),同時(shí)還要告訴它做什么運(yùn)算(加、減……),所以我們用一個(gè)4位二進(jìn)制表示操作運(yùn)算符(例如1000代表加法,1100代表減法……),輸出結(jié)果也是8位二進(jìn)制。與此同時(shí),ALU還要輸出一些標(biāo)記,這些標(biāo)記只有1位二進(jìn)制,代表某種狀態(tài)。例如如果輸出結(jié)果為0,是否為0標(biāo)志位就是1,如果輸出結(jié)果為負(fù)數(shù),是否負(fù)數(shù)標(biāo)志位就是1,如果運(yùn)算出現(xiàn)溢出(進(jìn)位產(chǎn)生的),溢出標(biāo)志位就是1。
至此我們已經(jīng)創(chuàng)建了CPU的核心之一ALU,接下來我們還會(huì)了解計(jì)算機(jī)是如何存儲(chǔ)數(shù)據(jù)的,最后我們會(huì)完整搭建一顆CPU!
CPU的工作原理5——如何存儲(chǔ)數(shù)據(jù)
上一節(jié)我們創(chuàng)建了算術(shù)邏輯單元ALU,它可以進(jìn)行算術(shù)、邏輯運(yùn)算,但計(jì)算出來的結(jié)果如何保存呢?這就需要用到存儲(chǔ)單元,接下來我們看看用什么電路能夠保存數(shù)據(jù)。
由淺入深,我們先來存儲(chǔ)1位二進(jìn)制數(shù)。先看這個(gè)改造過的或門電路。
它的特殊之處在于其輸出信號(hào)會(huì)作為輸入信號(hào)之一,看起來很奇怪是吧。一開始A、B都為0,輸出=0,當(dāng)A=1時(shí)或門輸出=1,那么B=1,這時(shí)A、B都是1,輸出還是1,保持不變,接下來即使將A設(shè)為0,輸出依然是1。于是這個(gè)電路可以保存信號(hào)1。但我們也會(huì)發(fā)現(xiàn)一個(gè)問題,即這個(gè)保存是永久性的,不論A的值是什么,輸出都是1,這顯然不符合實(shí)際需求,保存的數(shù)據(jù)應(yīng)該可以修改才對(duì),這個(gè)問題暫時(shí)放一放,后面再解決。
接下來我們?cè)倏催@個(gè)改造過的與門電路,同樣它的輸出信號(hào)也作為輸入信號(hào)之一。
一開始將A、B都設(shè)為1,輸出=1,當(dāng)A設(shè)為0時(shí),輸出=0,進(jìn)而B=0,輸出還是0,保持不變。接下來不論A如何變化,輸出始終保持為0,所以這個(gè)電路可以保存0。
然后我們開始著手解決如何修改保存內(nèi)容的問題,設(shè)計(jì)如下電路:
其中“輸入”端的信號(hào)就是這個(gè)電路要保存的值,“允許寫入”代表是否可以修改保存值,如果是0,代表不能修改,如果是1,代表可以修改,“輸出”端的信號(hào)就是保存的值。這個(gè)電路大家可以自行分析,我將四種不同的情況列在下面供大家參考。
允許寫入情況,輸入端信號(hào)1會(huì)保存起來
允許寫入情況,輸入端信號(hào)0會(huì)保存起來
禁止寫入情況,電路原本保存1,不論輸入端是什么都沒有影響
禁止寫入情況,電路原本保存0,不論輸入端是什么都沒有影響
我們用一個(gè)簡化的符號(hào)代替這個(gè)電路,這個(gè)電路可以保存1位二進(jìn)制數(shù),并且可以隨意修改,我們把它叫做鎖存器。
當(dāng)我們將8個(gè)鎖存器放在一起,就可以保存8位二進(jìn)制數(shù)了,這樣一組鎖存器被稱為寄存器。早期電腦使用8位寄存器,即由8個(gè)鎖存器組成,能保存8位二進(jìn)制數(shù),后來出現(xiàn)了16位寄存器、32位寄存器,到現(xiàn)在的電腦基本都是64位寄存器了。
接下來我們?cè)O(shè)計(jì)一個(gè)能存儲(chǔ)256位二進(jìn)制數(shù)的電路,如下圖:
我們共需要16*16=256個(gè)鎖存器,將他們?cè)O(shè)計(jì)成16行16列的矩陣,為了能準(zhǔn)確存取數(shù)據(jù),我們需要知道每個(gè)鎖存器的位置,所以需要一個(gè)額外的電路確定要操作的鎖存器的地址。如果要確定地址,必須知道行數(shù)、列數(shù),一共16行、16列,所以行和列各用4位二進(jìn)制數(shù)表示就行(0000代表第1行,0001代表第2行……,1111代表第16行,列也是一樣的),這樣就需要8位的地址輸入信號(hào)。受篇幅所限,細(xì)節(jié)的電路不再畫了,最終我們有了一個(gè)能存取256位二進(jìn)制數(shù)的電路,用這個(gè)圖形表示。
“8位地址”用于定位鎖存器的位置,“允許寫入”=1時(shí),“數(shù)據(jù)”被存入某個(gè)鎖存器,“允許讀取”=1時(shí),某個(gè)鎖存器的數(shù)據(jù)可以被讀取出來。
一個(gè)這樣的電路還是沒什么卵用的,還需要繼續(xù)擴(kuò)大規(guī)模,我們將8個(gè)相同的電路如圖所示連接在一起。
這樣我們一次就可以讀寫8位二進(jìn)制數(shù)了,8位也叫做一個(gè)字節(jié)(Byte,簡稱B)。同樣,我們簡化一下,使用下圖表示。
這個(gè)電路有256個(gè)地址,每個(gè)地址可以讀寫一個(gè)8位二進(jìn)制數(shù),一共可以保存256個(gè)字節(jié)(256B)的數(shù)據(jù)。這個(gè)容量的存儲(chǔ)空間能夠保存什么呢?很遺憾,現(xiàn)在的文檔、圖片動(dòng)輒以KB、MB為單位,似乎什么也干不了。
補(bǔ)充一下小知識(shí),1GB=1024MB,1MB=1024KB,1KB=1024B。
至此我們使用多個(gè)鎖存器構(gòu)建出了可以存取數(shù)據(jù)的內(nèi)存,雖然只有256B,更大的內(nèi)存也是同樣的原理,下節(jié)我們就開始打造完整的CPU了!
CPU的工作原理6——組建CPU
前面我們介紹了所有的基礎(chǔ)零件,接下來就可以構(gòu)建CPU了,這節(jié)的內(nèi)容會(huì)比較多。
既然開始構(gòu)建CPU,就少不了程序,因?yàn)镃PU就是用來執(zhí)行程序的。我們知道任何編程語言編寫的程序最終都會(huì)轉(zhuǎn)換為二進(jìn)制,所以這里我們直接使用二進(jìn)制的編程語言(機(jī)器語言)。比如我們讓CPU計(jì)算一個(gè)加法3+14,這個(gè)加法運(yùn)算用機(jī)器語言來描述的話就類似于下面這段二進(jìn)制:
看著挺亂是吧,沒關(guān)系,后面我們會(huì)講解。大家只要清楚這段二進(jìn)制的程序需要保存在內(nèi)存里,這樣CPU才能讀取并執(zhí)行。
不僅是程序,3和14作為運(yùn)算的數(shù)據(jù)也是保存在內(nèi)存里的,CPU要做的動(dòng)作是從內(nèi)存中某個(gè)地址讀取數(shù)據(jù)3和14,然后根據(jù)程序要求(加、減……)對(duì)二個(gè)數(shù)字進(jìn)行運(yùn)算,運(yùn)算的結(jié)果保存在內(nèi)存中某個(gè)地址處。
要實(shí)現(xiàn)這樣的功能,必須有一個(gè)約定,要讓CPU能夠識(shí)別相應(yīng)的動(dòng)作,即讀取、運(yùn)算、保存,所以我們建立如下約定:(稱為指令表)。
指令表可以理解為是程序的解釋器,當(dāng)CPU拿到一段程序,例如00101110時(shí),它必須知道這意味著什么,而指令表就能起到答疑解惑的作用。
具體來說每段程序(指令)的前四位對(duì)應(yīng)著指令表中的操作碼,后四位對(duì)應(yīng)著指令表中的地址。比如00101110,前四位0010是操作碼,它的含義是LOAD_A(見指令表),代表讀取數(shù)據(jù)放入寄存器A。再看后四位1110,指令表中描述的內(nèi)容是“4位內(nèi)存地址”,這其實(shí)就是要讀取的數(shù)據(jù)的內(nèi)存地址。連在一起的含義就是“在1110這個(gè)地址處讀取數(shù)據(jù)保存到寄存器A”。
接下來上電路!首先我們需要一塊內(nèi)存,可以直接使用上節(jié)提到的256B內(nèi)存,但為了方便起見,我們假設(shè)它只有16個(gè)地址,可以保存16個(gè)8位二進(jìn)制數(shù)。另外需要六個(gè)寄存器,每個(gè)可以保存一個(gè)8位二進(jìn)制數(shù),其中寄存器A-D用于臨時(shí)存儲(chǔ)和操作數(shù)據(jù),指令地址寄存器用于記錄程序運(yùn)行到哪里了(程序指令的地址),指令寄存器用于存放指令內(nèi)容。
接下來分析工作過程,當(dāng)計(jì)算機(jī)啟動(dòng)時(shí),所有寄存器初始值都是00000000,CPU開始進(jìn)入第一個(gè)階段:取指令,也就是從內(nèi)存中獲取指令。指令地址寄存器會(huì)連接到內(nèi)存,讀取地址為00000000的數(shù)據(jù),即00101110,這個(gè)數(shù)據(jù)會(huì)保存在指令寄存器,第一個(gè)階段結(jié)束。
第二個(gè)階段:解碼,即弄清楚指令要做什么。其實(shí)前面我們已經(jīng)做了鋪墊,指令內(nèi)容是00101110,其中前四位0010是操作碼,在指令表中對(duì)應(yīng)的就是LOAD_A,指令后四位是1110,對(duì)應(yīng)的是4位內(nèi)存地址,整體意思就是從1110的位置讀取數(shù)據(jù)保存在寄存器A。但現(xiàn)在還沒有現(xiàn)成的電路能夠做解碼工作,所以我們需要添加一部分電路,如圖。
這部分新添加的電路我們暫且稱為解碼電路,指令寄存器的前四位數(shù)據(jù)0010作為解碼電路的輸入,經(jīng)過這些門電路的處理,最終會(huì)輸出1。換句話說,解碼電路的作用就是識(shí)別指令是否是0010(LOAD_A),只有指令是0010時(shí),輸出才是1,否則就是0。
第三個(gè)階段:執(zhí)行。解碼電路的輸出會(huì)連接到內(nèi)存的允許讀取端口,而指令寄存器的后四位1110會(huì)連接到內(nèi)存的地址端口,這樣就相當(dāng)于允許讀取內(nèi)存地址為1110的數(shù)據(jù),這個(gè)數(shù)據(jù)是00000011(十進(jìn)制3)。
接下來這個(gè)數(shù)據(jù)要如何保存到寄存器A呢?我們需要讓解碼電路的輸出同時(shí)連接到寄存器A的允許寫入端口,而四個(gè)寄存器的數(shù)據(jù)輸入端口要連接到內(nèi)存的數(shù)據(jù)端口。當(dāng)數(shù)據(jù)00000011被讀取出來時(shí),會(huì)同時(shí)發(fā)給四個(gè)寄存器,但只有寄存器A是允許寫入的,所以數(shù)據(jù)就被保存在寄存器A中了。
接下來將指令地址寄存器+1,變成00000001,以便取下一條指令,后面的步驟與前面類似。需要注意的是,前面的解碼電路只能識(shí)別第一條指令LOAD_A,后面每一條指令都需要單獨(dú)的解碼電路支持。我們把所有指令對(duì)應(yīng)的解碼電路和指令寄存器、指令地址寄存器等部分統(tǒng)一叫做控制單元。
接下來我們快速分析剩余的指令,現(xiàn)在指令地址是00000001,所以從內(nèi)存中取出00011111存入指令寄存器。前四位0001對(duì)應(yīng)的指令就是LOAD_B,后四位1111是要讀取的內(nèi)存地址,對(duì)應(yīng)的數(shù)據(jù)是00001110(十進(jìn)制14),這個(gè)數(shù)據(jù)會(huì)被存放到寄存器B中。然后指令地址寄存器+1(00000010),繼續(xù)取下一條指令。指令內(nèi)容是10000100,前四位1000在指令表中對(duì)應(yīng)的是ADD,后四位0100分別是二個(gè)寄存器的地址01和00(因?yàn)榧拇嫫鰽-D只有四個(gè),用二位二進(jìn)制數(shù)就可以描述),其中00是寄存器A,01是寄存器B,所以這個(gè)指令的作用就是將寄存器A、寄存器B的值相加。涉及到加法,就要用到之前講過的算術(shù)邏輯單元,也稱運(yùn)算單元(ALU)了,我們簡化一下電路如下:
寄存器A、B會(huì)通過控制單元連接到運(yùn)算單元的二個(gè)輸入端,同時(shí)控制單元會(huì)將操作符也傳遞給運(yùn)算單元,這樣就可以計(jì)算了。計(jì)算的結(jié)果必須保存起來才行,但指令本身并未明確保存在哪里,所以這里面有個(gè)約定,運(yùn)算結(jié)果會(huì)保存在指令地址中最后一個(gè)寄存器里,二個(gè)寄存器地址分別是01和00,后面就是00,也就是寄存器A,所以最終結(jié)果會(huì)通過控制單元保存到寄存器A中,這個(gè)結(jié)果是00010001(十進(jìn)制17)。
指令地址寄存器+1(00000011),繼續(xù)取下一條指令。指令內(nèi)容是01000111,前四位0100表明指令是STORE_A,即將寄存器A的數(shù)據(jù)寫入內(nèi)存,內(nèi)存地址就是指令的后四位0111,控制單元會(huì)發(fā)送給寄存器A允許讀取信號(hào),發(fā)送給內(nèi)存允許寫入信號(hào),將寄存器A的值保存在內(nèi)存中對(duì)應(yīng)的位置。
終于我們完成了一句簡單的程序任務(wù),兩數(shù)相加,并成功保存結(jié)果。我們會(huì)發(fā)現(xiàn)每個(gè)指令的讀取、解碼、執(zhí)行,相當(dāng)于一個(gè)周期,CPU就是不斷的重復(fù)這個(gè)周期,進(jìn)而完成各類任務(wù)。在每個(gè)周期中算術(shù)邏輯單元、控制單元、存儲(chǔ)單元(內(nèi)存)都需要密切配合,節(jié)奏不能亂,才能保證最后的結(jié)果正確。但如何確保這個(gè)節(jié)奏是恰當(dāng)?shù)哪兀考炔荒芴?,因?yàn)榧词故请娦盘?hào)的處理也需要時(shí)間,也不能太慢,造成計(jì)算效率太低。所以有一個(gè)單獨(dú)的電路在控制這個(gè)節(jié)奏,就好像一臺(tái)時(shí)鐘,精確的指揮各個(gè)部分有條不紊的運(yùn)行。CPU都有一個(gè)重要的指標(biāo):主頻,例如2.6GHz,相當(dāng)于26億次周期/秒,意味著一秒鐘內(nèi)CPU會(huì)執(zhí)行26億次周期,主頻越高的CPU代表速度越快。我們把帶有時(shí)鐘電路的算術(shù)邏輯單元、控制單元、6個(gè)寄存器封裝成一個(gè)相對(duì)獨(dú)立的部分,這就是CPU!
至此,我們從一個(gè)簡單的晶體管開關(guān)開始,一路添磚加瓦,終于打造了一個(gè)完整的CPU,當(dāng)然也是最基礎(chǔ)的CPU。相信這個(gè)系列文章能讓我們對(duì)硬件與軟件的結(jié)合點(diǎn)有了清晰的認(rèn)知,我們?nèi)粘K褂玫母黝愜浖际浅绦蛑噶罹帉懗鰜淼?,每個(gè)程序的每條指令在CPU內(nèi)部都會(huì)經(jīng)歷眾多晶體管開關(guān)的處理,最終完成我們希望的任務(wù)。