文章進行了更新,請移步:編程
http://lianera.com/articles/20141027-cpu-step-by-step架構
目錄異步
開始前的話. 3工具
整體組成. 3學習
運算器. 3編碼
補碼... 3spa
本人大學黨一個,計算機科學與技術專業,平時對電子技術、計算機科學和軟件技術有所研究,在此只是分享一些本身平時學習、遊戲中的電子和計算機科學方面的經驗,但願可以給一樣學習中的人帶來啓發,另外若是有任何意見、建議、批評,歡迎提出來。
因爲對於大部分學電子和信息的同窗來講,數電已能熟練掌握,因此在此略過數電的知識,若是對數電有些不明白的,能夠參考其餘數電教程。
本教程開始以前要求至少要掌握:基本邏輯門、組合邏輯電路分析設計方法、譯碼器、編碼器、數據選擇器、比較器、加法器、三態門、基本觸發器、鎖存器、寄存器、移位寄存器、計數器。
在這裏採用的工具是Logisim,這是一個較爲簡易的仿真工具,不會涉及具體的芯片,而是純粹從原理的角度來分析仿真,對於簡單的CPU仿真最合適不過了。
此外,本人水平有限,文中不免出現紕漏和錯誤,歡迎指出!
這裏會從零開始,一步一步搭建電路,闡述原理,最終實現一個相似MIPS架構的CPU。另外我會採用儘量簡潔的語言。
CPU通常包括寄存器組、運算器和控制器。
另外對於現代的CPU,大都配置了Cache。
首先,讓咱們來構造運算器
通常簡單CPU實現的運算操做有算術運算、邏輯運算和移位。算術運算主要包括加法、減法、曾一、減1等,邏輯運算主要包括與、或、非、異或,移位實現左移右移或者不變。
算術運算器實際上就是一個加法器和其餘幾個運算的結合體,那麼減法怎麼辦呢?
衆所周知,A-B=A+(-B),就這樣,減法就轉換成了加法,那麼負數怎麼表示?答案是用補碼!
實際上爲了方便,咱們把參與運算的數都用補碼錶示,定義正數和零的補碼爲自己,負數的補碼爲其反碼加一(反碼就是按位取反)。
補碼錶示的時候,正數、零和負數能夠很方便區分出來,最高位爲0,則爲正數或零,最高位爲1,則爲負數。
不過在這裏,咱們還須要對運算結果按2^n取模(n爲位數),寫成通式就是這樣:
[A-B]補=([A]補+[-B]補)mod 2^n,(A、B>=0)
其實mod 2^(n+1)的意思就是捨棄最高位的進位。
舉個例子,若是要計算八位二進制運算5-3,能夠這樣作:
[5]補=5=00000101B,
[-3]補=[3]反+1=[0000011]反+1=11111100B+1=11111101B
而後作加法運算
若是第一個是負數呢?狀況也同樣的,由於加法知足交換律,加法器的兩個輸入端也是對稱的。
若是小數減大數呢?例如15-35(八位字長):
[15-35]補=([15]補+[-35]補)mod 2^8=(00001111B+11011101B)mod 2^8=11101100B=[-20]補
由此能夠看出,當用補碼錶示整數的時候,加減法能夠統一爲加法運算。
有了以上的理論就能夠構造算術單元啦~
實際上,兩數相減時,還須要對第二個數求補碼,求補的話,至少須要求反,另外咱們不只僅須要加法、減法,還須要增一、減1。增1能夠經過低位進位端實現,減一能夠經過加負一的補碼實現,也就是加全一,
因此綜合以上,還須要在輸入端加一個實現取反、全0、全1或保持原樣的模塊。
取反能夠用非門實現,而後用數據選擇器選擇不一樣的通路,便可實現上述功能。
一個簡單的4選一數據選擇器和一個加法器就構建出了一個算術單元,神奇吧!
其中的A和B是8路數據輸入,S一、S0和Cin進行功能選擇,右側輸出結果。
實現的功能表很容易寫出來:
那麼接下來就是邏輯單元了
邏輯單元最簡單了,與或非和異或就夠用了吧!
原理仍是同樣的,由兩路信號輸入,經過選擇功能,輸出結果!
原理圖So easy!
功能表So easy!
接下來就是最最最重要的ALU即算術邏輯單元了
說白了就是把算術單元和邏輯單元組合起來~
分析:算術邏輯單元要實現的功能是對A和B進行特定操做,而進行哪一種操做是須要選擇的,因此輸入端A、B和功能選擇端是應該共用的,對於輸出端,應該對算術和邏輯二者進行選擇。
ALU電路~
功能表~
除了算術和邏輯運算,咱們一般還須要一種移位運算,在這裏,由於速度要求很高,且不須要暫存,因此不採用移位寄存器,而是用組合邏輯電路的移位器。
移位的原理是使用不一樣的接線,錯位N位就實現了移動N位,因此須要用到數據選擇器。
對於一次一位的移位,只須要用二選一數據選擇器。
若是須要雙向移動,則須要四選一。
若是要一次移多位的話,須要用到桶形移位器,其複雜度會大大增長。
八位桶形向左移位器
能夠一次移動0~7位
須要注意的是連線
在以上各個部件的基礎上,就能組成一個運算器了,固然這樣的運算器還很簡單,不過基本的運算可以知足了。
固然,單有數據輸入/輸出和功能選擇仍是不夠的,不少時候咱們都但願瞭解運算的狀態,如溢出/進位/結果是否爲零等,所以還須要添加一些標誌的輸出。
當運算的結果超過最大表示範圍的時候,就會發生溢出。
分析能夠得出:
對於加法,溢出只有在兩數都爲正,或者兩數都爲負的狀況下才能發生;
對於減法,只有正數減負數或者負數減正數的狀況下才可能溢出。
統一爲加法以後,上述狀況就是[正]補+[正]補和[負]補+[負]補,[正]補+[正]補溢出時致使從低位進位到符號位,[負]補+[負]補致使符號位變爲0,所以能夠得出結論:
當兩數符號相同,運算結果與原數符號不一樣時,發生溢出。
狀態標誌這裏設置了溢出OV、符號SI、進位CA、零ZO。
其中OV和SI只對算術運算有效,CA對算術和移位運算有效(移出位放入CA),ZO對算術和邏輯運算有效。
(無效並不意味着輸出必定爲0)
另外,將移位器的方向選擇線IL,IR併入了S0,S1中,而且添加了一個算術左移(與邏輯左移實質是同樣的)和算術右移(最高位不變)。
溢出OV檢測:上面說過能夠經過比較源操做數和結果的符號位斷定,可是這樣電路不容易實現,在這裏,採用了對加法器的符號進位和最高進位進行異或運算以實現溢出斷定,所以在佈線時將加法器拆成了7bit+1bit。1表示溢出。
符號SI檢測:經過檢測加法器輸出的符號位便可得出符號,爲1時表示負數。
進位CA檢測:移位操做移出的位或者是加法器最高位進位,所以須要在不一樣的操做時選用不一樣的線路,1表示進位。
零ZO檢測:當輸出爲零時,全部位必定爲零,所以邏輯就是全零得一,可用或非門實現。爲1時,表示結果爲0。
功能表:
至此,一個完整的運算器已經完成好了!
運算器能夠看作一個多功能的加工廠,輸入兩個數,輸出一個結果。要採起哪一種功能,必需要經過一些功能選擇信號來進行選擇。
因此設計時,首先要考慮須要進行哪些運算,而後將這些運算的輸入端合併到一塊兒,而後經過選擇器選擇哪一個做爲輸出,固然也能夠採用其餘方法,好比用三態門代替數據選擇器、經過選擇輸入從而實現功能選擇等,總的原則就是輸入-選擇功能-輸出。
這裏沒有加入乘法器和除法器,由於這些的體積很大,也會大大增長運算器的複雜性,在現實當中,早期微處理器因爲集成度低,規模小,幾乎都不設置乘法器。但若是要作乘法怎麼辦呢?有兩種方法,一種是用CPU的微指令實現乘法(微指令之後會講),一種是靠軟件編程實現。例如8086的MUL、DIV實際是經過微指令實現的,CPU並無專門的乘法器。隨着集成度的提升,纔有專門的乘法器,甚至有的處理器還配置了陣列乘法器。
PS:各個組件名字叫法可能各類資料上不相同,但通常容易辨別的~
首先考慮通用寄存器
對於寄存器,須要實現的基本功能就是寫數、儲存、讀數,通常至少要有輸入端、時鐘端(異步也有叫送數端)、輸出端,另外根據具體狀況,可能還須要使能端和清零端。
注意寄存器的時鐘信號(送數信號)必需要是邊沿觸發的!
不能用鎖存器代替觸發器,由於鎖存器會有空翻現象,控制時鐘必須保持高度一致,對於毛刺(冒險競爭)現象很難處理。
這裏須要區分寄存器和鎖存器,最主要的區別就是寄存器是邊沿觸發,鎖存器是根據電平來送數或保持(也即同步觸發)。
另外還須要區分的是觸發方式,多是因爲翻譯的緣由,國內不少資料對觸發方式的叫法不一致,這裏列出不一樣名稱的對應關係:
Latch----鎖存器--同步觸發器(電平信號激活)
Flip flop-觸發器--邊沿觸發器(邊沿信號激活)
另外RS觸發器=基本RS鎖存器
同步RS觸發器=門控RS鎖存器
另外Flip flop在臺灣叫正反器。
對於寄存器組,因爲寄存器共用輸入線,須要對寄存器進行選擇,選擇的寄存器容許送數,其餘寄存器不容許送數,因此這裏的寄存器單元必需要帶有使能端,使能端能夠這樣構造:
在EN=0時,老是保持不變,在EN=1時容許送數。
(Logisim的D觸發器自己帶有使能端,這裏爲了演示不使用EN端)。
留下個思考題:
使能端能夠這樣構造嗎?爲何?
有了以上的基礎就能夠開始構建寄存器組了!
首先,咱們考慮對寄存器的寫入。
須要對寄存器寫入,這就須要數據輸入端,此外,還須要指定對哪一個寄存器寫,這能夠把多個寄存器編址,用地址來表示一個具體的寄存器,好比四個寄存器能夠用兩位地址指定一個寄存器,八個須要三位。
但若是一個都不寫怎麼辦呢?時鐘是不會變的,一旦時鐘來了就會寫,這時候,使能端就發揮做用了,咱們利用使能端結合地址實現指定一個寄存器,利用讀寫端和使能端結合實現要不要寫入。
而後考慮對寄存器的讀取
寄存器的輸出端是一個持續不斷的輸出,而根據前面的運算器,咱們須要從寄存器取兩個操做數輸入運算器,所以,寄存器組須要有兩個輸出,這兩個輸出要可以分別選擇鏈接到兩個寄存器,也就是要實現選擇兩個寄存器分別輸出到A、B端,這個好解決,兩個通道分別設置兩個數據選擇器,每一個選擇器可以選擇任意的寄存器。
補充:寄存器的時鐘端是隻針對寫操做而言的,一旦上升沿到來而且讀寫端爲1且寄存器被選中,那麼輸入端的數就會送到寄存器中。對於輸出端,無論時鐘怎樣,也無論讀寫端是0仍是1,輸出端時時刻刻都是寄存器的值。
實際上,這裏構建的只是一個通用寄存器組,還有一些特殊功能的寄存器爲了方便,我把它放到了後面的控制器中。
至此,通用寄存器組就構建完成啦。
控制器顧名思義,是整個CPU的控制中心,用來控制數據的流向,程序的轉移。
通常來講,控制器包括程序計數器(PC),指令譯碼器(ID),有時也將指令寄存器(IR)放到控制器中。
總的控制功能是這樣實現的:
程序計數器負責保存程序執行到的位置,指令寄存器保存當前指令,指令譯碼器把指令轉化成控制字(控制字後面會將)。
首先須要知道指令在哪裏:
通常來講,程序包括三種結構:順序、分支和循環,其中分支和循環實際上能夠由跳轉實現,因此程序的執行方式能夠概括爲順序和跳轉,對於順序執行,只須要對程序計數器每次加一個固定的數便可實現,對於跳轉,有兩種方式實現,一種是絕對跳轉,一種是相對跳轉,絕對跳轉直接給出下一條指令的地址,對程序計數器直接置數,對於相對跳轉,則經過程序計數器加上或者減去某個值實現,通常來講大多數狀況下用的是相對跳轉。
程序計數器能夠用寄存器、加法器和數據選擇器實現
若是要順序執行,則MUX選擇常數01,指令所在地址每次加一
若是要跳轉,則MUX選擇下方的通路,下一條指令地址爲當前地址加上偏移量。
程序計數器給定了地址就能夠把指令從存儲器取過來了
指令從存儲器取來放入指令寄存器中,指令寄存器與通常的寄存器徹底同樣。
取得了指令還須要對指令進行譯碼,因爲譯碼後的控制字與數據通路緊密相關,因此必須先肯定好數據通路。
數據通路是關於CPU內部多個組件直接數據的流向通道。
數據通路的類型多種多樣,有的程序和數據分開,有的不分開,有的全部數據共用一條總線,也就是CPU的內部總線,也有的數據總線分多條。
在這裏咱們採用一種相似於MIPS的數據通路,其中程序、數據的數據線和地址線全都有專用的線,數據在寄存器與運算器、存儲器都存在直接的通路,這樣可以使得控制較爲方便,效率比較高,可是連線比較多,尋址不夠靈活。
一個大體的通路以下圖所示(存儲器就是RAM):
(上圖的通路中,存儲器接受的兩路信號分別是數據線和地址線)
仔細觀察上圖不難發現,這種通路沒法在一個週期裏面實現寄存器簡介尋址並參與運算,由於一個週期要麼訪問存儲器,要麼進行運算。
此外上圖沒法實現當即數尋址,這是沒法忍受的,因此必需要在通路中加入當即數通路。
那麼問題來了,兩個或多個數據聚集到一塊兒怎麼辦?就像上圖紅圈的地方。
辦法是在每一個交叉的地方設置「閘門」,也就是數據選擇器,從而選擇不一樣的通路。
對於存儲器,只要提供「是否寫入"的信號就能間接肯定是否走存儲器這條通路。
相似於這中閘門的還有許多,就像前面寄存器上面的A、B選擇線、寄存器選擇線、是否寫入寄存器等。
把全部的這些控制通路的信號聚集到一塊兒,組合成一個二進制數,就能夠經過一個二進制數來控制通路了,這就是控制字!
經過控制字,能夠決定在這個週期裏面,用哪些寄存器作爲待運算的數、是進行運算仍是送入存儲器、進行何種運算、是把運算結果仍是存儲器讀出的值送入寄存器、送入哪一個寄存器。
經過肯定以上的信號,就能肯定CPU要作的事情了!這就是控制字的做用,而指令能夠看作是控制字的壓縮碼!
對於ADD R3,R2,R1,(即R3=R2+R1)這一條指令,實際CPU作的事情就是:
在A通道選擇R2輸出,在B通道選擇R1輸出
不選擇當即數
存儲器不寫入
運算器選擇加法運算
選擇運算器輸出的結果
選擇R3做爲待寫入寄存器
寄存器組寫入信號激活
時鐘信號上升沿到來!
R3的值成功變爲R2+R1!
這裏是主要的控制字(其中標誌位和跳轉後面會講)
雖然控制字可以選擇全部的通路,實現全部的功能,可是有些通路是無心義的,咱們並不須要,此外,控制字一般比較長,須要多個字節才能存放,這樣大大浪費了資源,因此就得想個辦法!
辦法就是採用指令譯碼器!
首先咱們能夠對控制字的根據通路分類,好比運算功能的通路基本一致,都是經過運算器運算,只是選擇的運算功能不一樣,跳轉通路同樣,只是根據不一樣的標誌位跳轉,這裏分析能夠獲得如下分類(固然不是最佳的,須要在壓縮率和靈活性直接權衡)
其中V的意思是有效,X的意思是無效(無關),IM指的是當即數。
在上圖中,能夠對W/CI/RM/WM/SF/JP/JW進行編碼,編碼成000,001,010,011,100,這樣只須要三個信號就能肯定六個控制信號了,也就是這樣:
加上其餘的信號,好比運算選擇信號、寄存器選擇信號、當即數,就是一個完整的指令格式了!
至於怎麼編碼,須要用到組合邏輯分析的知識,若是實在以爲麻煩,也能夠藉助Logisim等工具自動生成
這裏是我設計的譯碼器:
從上圖能夠看出,選擇功能須要3bit,選擇運算須要5bit,選擇目標寄存器2bit,源寄存器A2bit,源寄存器B2bit,已經有14bit了,即便是16bit的指令,剩下的當即數和跳轉方式幾乎沒有容身之地了,而加大指令長度又會形成浪費,這時能夠採用複用技術,某些位在不一樣的狀況下行使不一樣的功能。例如這裏當寄存器與當即數運算時,B寄存器佔用的資源複用做當即數,當跳轉時,運算選擇信號其中的S二、S一、S0複用做跳轉方式,全部的寄存器選擇信號複用爲當即數。
此外還有狀態寄存器還沒弄,狀態寄存器本應放在寄存器裏,這裏爲了方便,放入控制器。
因爲狀態寄存器只用來作跳轉依據,因此只有在JP爲1時才能查看狀態位肯定是否進行跳轉,這裏須要一個與門和數據選擇器,與門用來實現JP的總控制功能,數據選擇器用來選擇跳轉方式。
綜合以上的程序計數器、狀態寄存器、指令寄存器、指令譯碼器
一個完整的控制器就構建完成了!
有了寄存器、運算器、控制器,設計好了數據通路和指令集,就能夠構建CPU了!
可是若是一大堆電路所有弄到一塊兒的話,很容易弄亂的,因此須要把幾個模塊封裝一下。
Logisim裏封裝仍是很容易的。
運算器:
寄存器組:
控制器:
這裏咱們須要考慮一下指令週期。
首先須要從ROM中取指令,也就是用時鐘上升沿把數送入指令寄存器。
而後指令通過譯碼,或者通過運算器,或者通過RAM,因此RAM須要第二個時鐘上升沿。
而後須要把運算器的結果或者RAM讀出的數送入寄存器組,因此寄存器組須要第三個時鐘上升沿。
最後須要修改程序計數器,因此把第四個時鐘上升沿給程序計數器。
綜合以上,一個指令週期須要四個機器週期,可使用時鐘發生器、一個四進制計數器和一個譯碼器實現。
綜合以上,就能夠鏈接CPU的模塊間線路了!
首先鏈接三個模塊的通路(依照體系結構和數據通路):
而後添加時鐘信號(須要注意時鐘的順序):
好啦,一個完整的CPU就構建完成了!
存儲器
可是有了CPU仍是不夠的,還要有存儲器才行,ROM和RAM的鏈接方式前面已經講過,這裏就再也不贅述。
最終連出的就是這樣子:
重啓啓動時鐘,機器就可以根據ROM中的指令(初始地址爲0)運行了!
雖然咱們設計的CPU可以跑起來了,可是要讓它運行指定的程序仍是很困難的,由於程序都是二進制的機器碼,這樣編程效率過低了,也容易出錯,因此咱們須要一個可以直觀、高效編程方法,那就是彙編。
編譯器的開發須要有編譯原理的知識,因爲編譯原理較爲複雜,這裏再也不闡述。
這裏我用Java寫了個簡單的彙編編譯器,可以將彙編源代碼轉化成機器碼(很簡單的IF ELSE結構,編譯原理沒用上,詳見附件)。
這是一個求斐波那契數列並存入RAM的程序:
用編譯器編譯成的機器碼(十六進制表示):
把編譯出來的機器碼導入ROM中,運行計算機,在RAM中成功獲得了斐波那契數列!
這只是一個極其簡單的模型機,實際的現代計算機結構要複雜得多,可是總的說來思路和方法都是差很少的。
因爲時間倉促,設計方面可能會許多BUG,找到的同窗歡迎及時提出來!
全部的設計、截圖、工具我都會上傳上來。
這篇文章陸陸續續寫了一段時間了,全部的文字和圖片都是本人本人手打和截圖的,雖然耗費了很多時間,可是這個過程同時也是充滿樂趣的。
我本人是計科學生,發現周圍許同窗學了計算機組成原理或微機原理以後,仍是不知CPU的具體構造過程,寫此文的目的不敢妄稱教學,只是一些經驗,另外本人水平有限,對於不許確的地方請還以其餘參考資料爲準!但願此貼可以給須要的人帶來一些啓發。
《Logic and Computer Design Fundamentals》
——M. Morris Mano, Charles R. Kime
《Computer Organization and Design, Fourth Edition: The Hardware/Software Interface》
——Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman
附件