CPU的設計與實現(2)--邏輯電路設計

在上一篇博文CPU的設計與實現(1)--方案設計中,較爲詳細地講解了我將要設計實現的Gater8這個基於自制CPU的自制計算機的設計方案。

 
這是本系列第二篇博文,我將詳細分析具體設計完成的Gater8的數字邏輯電路。最初計劃本系列博文的第二篇應該是打算用各種純二進制門(與門、非門、或門等)設計數字電路,然後在第三篇博文再講本文的內容。這樣安排是想讓即使沒有數字電路基礎的讀者也能順利無障礙的理解文章的內容,並逐步從基礎原理深入到後續的實現。可是由於以下幾個原因,我想先不講解原先的第二篇,並將後續博文都提前:(1)我最近太忙,基本沒什麼業餘時間;(2)設計寄存器和三態控制芯片都不難,就是寫文章特別花時間,於是還是因爲忙;(3)基本的數字電路原理應該很多書和教程都有詳細介紹;(4)從自己設計的數字電路版CPU轉換到基於7400芯片的原型電路也需要花時間,而且可能需要額外電路或略微改動原先設計才能轉換。
 
其實這被擠掉的第二篇博文我主要是想把以下幾個東西的工作原理講清楚,這對於理解和設計自己的CPU非常重要:(1)D flip flop,即D觸發器的工作原理,(2)K-map簡化布爾表達式的方法,(3)8位並行加法器的設計,(4)用flip flop和K-map(或布爾代數)設計出給定要求的時序電路,比如特定的計數器。其中(3)是組合電路,而(4)是時序電路。
 
一、自制CPU計算機Gater8原型的概要說明
 
首先,下圖1爲Gater8的模塊結構圖,而圖2爲完成後的基於7400系列芯片的Gater8具體原型圖。圖1和圖2在結構佈局對應。
 
CPU的設計與實現(2)--邏輯電路設計圖1. Gater8的模塊結構圖。
 
 
CPU的設計與實現(2)--邏輯電路設計
圖2. 基於7400芯片的Gater8原型電路(查看高清大圖: 右擊->顯示圖片)。
 
圖1和圖2中的設計佈局大致爲:左邊部分是ROM和RAM電路,中間部分爲各寄存器和ALU,右上部分爲I/O,右下角部分爲控制器。具體的每個部分的詳細說明將在以下章節給出。
 
Gater8的邏輯電路設計在軟件LogicWorks 5下完成,並測試運行通過。其設計總體硬件參數如下:
 
數據線位寬:8 位
地址線位寬:12位

控制器類型:硬佈線邏輯

運算速度:待定,預計1MHz以上
指令數:11條
存儲架構:哈佛半馮諾依曼架構
RAM:4KB
ROM:4KB
輸入能力:1個8位輸入設備,或8個1位輸入設備
輸出能力:2個8位輸出設備,或16個1位輸出設備
 
Gater8雖然是8位的處理能力,即每個數據寄存器均爲8bit,但是它的設計過程和設計原理和16位,32位的計算機是一樣的。因此完全可以在Gater8的基礎上只花一兩個小時就能設計出32位的CPU和計算機的原型。之所以Gater8只是8位,是因爲16位或32位CPU需要太多的7400系列芯片,我需要考慮最後用實際7400系列芯片搭建時不會有幾千上萬根杜邦線,這樣就太麻煩了。
 
關於運算速度:因爲CPU由兩大部件構成,即運算器和控制器,因此CPU的每個原子操作(也叫微指令,子操作)運算速度(即時鐘週期)由(1)運算時間,(2)控制時間兩部分構成。由於不同的子操作的執行時間長度是不同的,因此時鐘週期的長度一定要足夠大到能完成任何一個子操作。Gater8中每條指令由3至4個子操作組成,執行每條指令需要3至4個時鐘週期。這些子操作包括從ALU到內部寄存器賦值、輸入設備到內部寄存器賦值、ROM到內部寄存器賦值、RAM到內部寄存器賦值(讀RAM)、內部寄存器賦值到RAM(寫RAM)、內部寄存器到輸出設備寄存器賦值、程序計數器(PC)+1,等等。這些子操作的運算時間就是完成賦值的數據通信時間,而控制時間是子操作對應的控制邏輯所消耗的時間。控制邏輯本質上是一個有限狀態機,根據不同的指令操作碼、其它條件(如Z, C標誌位)以及當前狀態確定下一個狀態,同時將控制信號發送給CPU各部件的控制端口,以協調各部件的有序運作,完成一個個的子操作。
 
上面這段內容講得有點抽象,不過沒關係,這個階段是邏輯電路設計,主要關注的是電路的邏輯合理性、硬件設計和連接是否正確、程序是否能夠在所設計的電路上正確執行,具體的運算速度將在下一個階段用7400芯片搭建時調試完成。不過,理論上時鐘週期還是能夠根據查閱自己設計的數據通路上每一個部件的Data sheet獲得相應所需的時間,並累加得到。這個累加得到的是理論值,實際可能還需要再略微長一點。目前預計最長的Data path可能是ROM訪問,因爲按照常識,ROM芯片的訪問相對來說最慢,可能需要100ns(運算時間),再加上控制單元的開銷(控制時間),總計約不超過200ns,這麼一算的話,1/200ns=5MHz,這個運算速度是我預計的1MHz的5倍。提升CPU的時鐘週期的方法有很多,比如現在主流的CPU將總線分爲幾個層次,連接寄存器之間的內部總線速度最快,將ROM和RAM分離,不直接連接到該總線上,因爲ROM和RAM的訪問速度很慢,於是專們爲ROM,RAM設計的另一個總線,還有更慢的I/O總線等。另外通過增加Cache也一定程度上解決了這個問題。這樣CPU的時鐘週期就不會受到很多限制,能很好得控制到只開銷在必要的一些邏輯門電路上。
 
參見在圖2右下角控制器部分,Gater8一共提供了17個控制端口的控制,都是低電平(即0)使能對應的功能。並且,所有部件,包括所有70X825寄存器,70X161、70X163計數器,70X244三態緩衝器,70X154譯碼器,ALU,控制器等都是在時鐘信號從低電平到高電平跳變時起作用,即positive edge
 
以下詳細介紹CPU和計算機的每個部件。
 
二、寄存器Register
 
爲了使Gater8儘量少用7400系列芯片以便後期搭建,我只用了很少的寄存器。一共使用了以下寄存器:
 
2個8位運算寄存器:A, T
1個12位程序計數器:PC
1個8位指令寄存器:IR
2個8位輸出寄存器:DEV1, DEV2
 
對比之前設計的方案,省略了MA(內存地址)寄存器和B寄存器。因爲沒有MA寄存器,因此不支持間接尋址,沒有通用寄存器B,於是也暫時不能實現硬件棧結構。但是這樣可以使芯片的使用適量減少。
 
在實現上,所有8位寄存器都使用70X825芯片,825芯片內部集成了8個D flip flop。唯一的12位計數器PC由3個70X161芯片,161芯片是4位計數器,給定時鐘信號,161的輸出狀態將從0至F至0順序變化,每次遞增1。通過連接代表低4位的161芯片的RCO端口和代表高4位的161芯片的T端口,可以提共完整的8位計數器。同理,3片161芯片以這種方式連接後,就可以提供12位計數器,計數器的輸出值用於表示當前ROM中指令或常量的地址。
 
每個寄存器都可以通過個控制端口從外部獲取數據並存在內部,比如A寄存器的這個端口爲Aen,T寄存器的爲Ten,這個控制端口由70X825芯片的CLKEN引腳提供。另外寄存器輸出數據到外界由另一個控制端口控制,比如A和T寄存器的這個端口分別爲Aout和Tout,這個控制端口由70X244芯片的GA/GB引腳提供。根據用途的不同,有些寄存器是單向,比如DEV1和DEV2兩個寄存器只有輸入,無需控制輸出,因此只有DEV1en,DEV2en控制端口。
 
三、總線
 
Gater8有兩組總線:數據總線和地址總線。在上面的圖2中,最上面的8條橫向長長的線爲數據總線,而下方的12條橫向相對短一點的線爲地址總線。
 
3.1 數據總線
 
數據總線用於連接各個運算寄存器的輸入和輸出、ROM/RAM的輸出、ALU的輸出、I/O接口等。由於總線對於連接在上面的所有部件是共享的,因此同一時刻只能允許一個部件向總線輸出,因此可以看到,A,T寄存器的輸出端會通過一個70X244芯片(X代表LS或HCT等任何字符串,因爲不同公司生產的70244芯片使用不同的X代碼)連接到數據總線上。70X244芯片是8位三態緩衝器,可以控制數據是否向總線輸出。
 
3.2 地址總線
 
地址總線一共12位寬,其中低8位與數據總線連通,方便ROM輸出低8地址,而高4位地址由IR寄存器(即指令寄存器)的低4位提供。並不是所有的指令的低4位都表示地址,只有讀寫RAM的兩條指令,即LDR和STR纔是,具體機器碼格式請參見下文。
 
四、RAM與ROM
 
Gater8的RAM與ROM部分在圖2的左邊,它們的地址引腳都連接在12位地址總線,因此共享4KB地址空間。Gater8復位後,所有寄存器的值均爲0,也就是PC寄存器指向ROM的0x000地址處,從這裏開始存放第一條指令,並向高地址延申。
 
內存部分設計採用了半哈佛半馮諾依曼架構。半哈佛架構是因爲RAM和ROM有各自獨立的內容空間,雖然它們共享同一地址空間。例如,Gater8的RAM和ROM都是4KB,但是對於同一地址0x000處,ROM和RAM的內容是各自獨立的,是可以不同的。
 
說它是半馮諾依曼架構是因爲,Gater8對ROM的讀取和RAM的訪問不能同時進行(因爲ROM和RAM共享數據總線),這和哈佛架構中兩者可同時進行不同,而和馮諾依曼架構一樣。
 
哈佛架構相比馮氏架構有很多優點,Gater8的內存部分也可以設計成完全的哈佛架構,不過同時也會損失一些其它的功能或自由性,而且,目前也不需要ROM和RAM同時進行操作,因此Gater8當前的內存部分設計還是挺理想的。
 
4.1 ROM部分
 
ROM部分一共有PCen, PCLD, ROMout三個核心控制端口。
 
ROM部分最重要的是PC寄存器,它由3片70X161芯片構成,並有4個控制端口,分別爲PCen, PCLD, CLK, 以及RST。CLK連接系統時鐘信號,RST連接復位信號,因此真正由控制器控制的只有PCenPCLD兩個端口。
 
當PCen端口置0(即低電平)時,經過一個CLK時鐘週期,PC寄存器內容自加1,實現必要的程序自動向下運行。
 
當PCLD端口置0(即低電平)時,經過一個CLK時鐘週期,PC寄存器會從地址總線載入12位數據作爲新的PC寄存器的內容,這用於實現JMP, JNZ等跳轉指令。
 
另外,ROM芯片採用AT28C64,輸出的8位數據由一個70X244三態緩衝芯片控制是否輸出到數據總線上。這個是否輸出到數據總線上的控制由ROMout端口的值決定。當ROMout端口爲0時,當前PC計數器指向的ROM數據輸出到數據總線上。
 
4.2 RAM部分
 
RAM部分一共有MEMen, MEMin, MEMout三個核心控制端口。
 
這三個端口一起實現兩個功能:(1)數據從數據總線寫入到的RAM芯片,以及(2)數據從RAM芯片輸出到數據總線上。
 
(1)數據寫入RAM
由於RAM芯片採用CY62256,這是一個SRAM芯片,速度快(單次讀寫可在70ns以內完成),而且無需像DRAM安芯片需要刷新機制(這會使電路更復雜)。查閱它的Data Sheet,可知它由三個控制端口:CE片選、OE輸出使能、和WE寫入使能,我將其分別重命名爲MEMen、MEMout、以及MEMin三個新名字,符合我自己的命名習慣。
 
當需要寫入數據到RAM芯片內時,必須在MEMen和MEMin這兩個端口上置0,然後連接在RAM芯片8位數據端口DIO0~DIO7的數據就會被寫入到在由12位A0~A11端口決定的地址處。
 
由於寫入時,待寫入的8位數據來源於數據總線,用一片70X244,它的輸入端口連接數據總線,輸出端口連接RAM的數據端口,它的使能端口GA/GB由MEMen和MEMin這兩個信號布爾與(即OR運算)的結果決定,即當且僅當MEMen和MEMin這兩個信號都爲0時,GA/GB也爲0,這時這片70X244就導通了,數據可以從數據總線傳輸到RAM芯片的數據端口。
 
(2)數據從RAM讀出
當需要讀取RAM某處的一個字節時,必須在MEMen和MEMout這兩個端口上置0,然後連接在RAM芯片的一個字節就會從8位數據端口DIO0~DIO7輸出,這個輸出的字節位於由12位A0~A11端口決定的地址。
 
由於讀取操作會把數據從RAM芯片輸出到數據總線上,另一片70X244控制這個輸出。這個70X244的GA/GB使能端口也由MEMen和MEMout這兩個信號的布爾與的結果決定。
 
(3)關於兩片70X244芯片
由於即使當CS端口爲1時,這個RAM芯片也會從DIO端口默認輸出8個0,這才需要兩片70X244芯片控制只在必要時開通一個方向的傳輸,一片控制寫,一片控制讀。如果有類似的RAM芯片,能在CS端口爲1時,變爲第三態,不輸出也不輸入,這樣便可省掉這兩片70X244芯片和兩個與門,電路便可簡化。
 
五、算術邏輯單元ALU
 
ALU部分一共有ALUCn, ALUM, ALUout三個核心控制端口。
 
Gater8的ALU直接採用兩片70X181芯片並聯構成。70X181芯片提供了加、減、與、或等若干基本算術邏輯運算。單片70X181芯片只能提供2個4位數的運算,通過連接低4位181芯片的Cn+4端口到高4位的181芯片Cn,可實現2個8位數的運算,Cn+4會根據實際運算向高4位進行借位或進位。
 
181芯片的運算功能選擇是由S0~S3、M、Cn這6個端口決定的,具體可查閱它的Data Sheet。爲了簡化電路設計,我直接採用相應運算對應的S0~S3四位值作爲我的操作碼,比如想讓181芯片執行A-B運算,S0~S3的值須分別爲0110,同時M和Cn的值須爲0和0,這樣A-B的值就會從181芯片的F端口輸出。我於是對我的指令集中的減法指令SUB的4位操作碼定爲0110,這樣我可以直接連接IR寄存器的高4位到兩片181芯片的S0~S3端口就行了,無需額外的轉換或映射電路。
 
Gater8一共使用了181芯片的4個運算功能,分別如下:
 
CPU的設計與實現(2)--邏輯電路設計
 
表1. Gater8採用的4條70X181芯片的運算。
 
表1中,前兩條爲算術運算,後兩條爲邏輯運算,X表示可以爲任意值,對結果不影響。
 
我將M和Cn這兩個端口重命名爲ALUM和ALUCn。
 
ALUout端口由一片70X244芯片的GA/GB提供,負責數據從ALU向數據總線的輸出。
 
另外,70X181芯片還有一些其它輸出端口,比如高位181芯片的Cn+4表示最終運算結果的進位/借位,A=B端口可在181執行減法狀態下表示結果是否爲0。因此,高位181芯片的Cn+4端口可用來提供進行/借位標誌位,即Cflag。而A=B端口在特定情形時用來表示零標誌位,即Zflag,不過這個特定情形往往不太容易達到,因此我沒有用A=B端口,而用了一個8位的或門連聯A寄存器來得到Zflag。另外Cflag在Gater8中也沒有用到。
 
六、輸入輸出I/O
 
I/O部分一共有DEV0en, DEV1en, DEV2en三個核心控制端口。
 
其中DEV1en和DEV2en爲兩個輸出設備的控制端口,寄存器小節已講解過了,不再贅述。
 
DEV0en是用來控制輸入設備向數據總線傳輸數據的控制端口,由一片70X244芯片提供。
 
圖1中,兩個輸出設備分別連接了8個LED燈,輸入設備連接了兩個Hex Keyboard,用於測試程序是否能在電路上正確執行。後期用實際芯片搭建時,將使用以下設備:
 
輸出設備:一個1602 LCD、一個蜂鳴器、若干LED燈。也會考慮連接PWM驅動的電機驅動板,嘗試程序實現PWM脈衝,並控制電機轉動。
輸入設備:若干按鍵、一些數字傳感器,比如數字式土壤溼度傳感器、DHT11數字式溫溼度傳感器、數字式光敏傳感器等。
 
七、控制器CU
 
CPU沒有控制器也能正常完成各條指令,得到相應的運算結果或操作輸入輸出設備,只不過CPU內那麼多部件的控制端口都需要人爲置0或置1才能讓它們協調工作。這也就是早期計算機有那麼多開關需要人工撥動來控制計算機的原因。有了控制器,CPU就可以擺脫人工干預,自動有序得執行程序,速度當然也比人工操作快成千上萬倍。
 
但是控制器(Control Unit)的設計是相對最複雜的部分。它的設計完成需要考慮以下幾大內容:
 
(1)數據通道(即Data Path)和子操作
(2)指令集的設計
(3)採用微程序還是硬佈線實現CU
 
這三大部分內容並不是獨立的,而是相互關聯的,往往一部分內容的微調都會影響另兩部分的設計。下面分別針對以上幾點進行詳細分析。
 
7.1 數據通道(Data Path)和子操作
 
數據通道就是CPU內相連的各部件之間的數據傳輸通路。比如在數據總線上連接有ROM的輸出端口和IR寄存器的輸入端口,這就形成了一個數據通道,用ROM->IR表示,有了這個通道,ROM就可以輸出數據進IR寄存器。但沒有通道連接IR寄存器的輸出到A寄存器的輸入,因此這個操作無法在硬件上完成。執行每個數據通道就夠成了一個最基本的硬件子操作,這個子操作不可再被拆分,是CPU的原子操作。
 
爲了順利完成一個ROM->IR子操作的執行,控制器需要發送不同的控制信號給各部件的控制端口。首先,ROMout端口必須置0,以允許ROM輸出數據到數據總線上,同時IRen端口也需要置0,使得IR寄存器在下一個時鐘週期上升沿把數據總線上的數據存入IR寄存器。ROMout和IRen是這個子操作的核心端口,但僅設置兩個0在這兩個端口上還不夠,因爲數據總線是被很多CPU內的部件所共享的,當ROM佔用數據總線時,我們同時還得讓其它任何共享數據總線的部件不能輸出數據到這個總線上,不然就會發生衝突,子操作就無法完成。因此,除了ROMout和IRen置0外,同時PCLD, Aen, Aout, Ten, Tout, ALUout, DEV1en, DEV2en, DEV0en, MEMen, MEMin, MEMout都需要置1,而ALUM和ALUCn這兩個屬於ALU的控制端口的值可以是任意值,即0或1均可,因爲這個子操作不關心ALU運算。
 
計算機的每一條指令都是由若干個這樣的子操作構成,Gater8也不例外,但不同的硬件設計,即使相同的功能需要的子操作集合也不一樣。比如,對於指令:
IN T
這條指令將從輸入設備DEV0讀取一個字節到T寄存器,它需要以下3個子操作完成:
 
(i) ROM->IR
(ii) PC+1->PC
(iii) DEV0->T
 
(i)完成從ROM中取指令到指令寄存器IR,(ii)完成PC寄存器自加1,(iii)完成從輸入設備DEV0讀取1字節到T寄存器。這3個子操作,每個都能在一個時鐘週期內完成,所以一條IN T指令需要3個時鐘週期完成。
 
上面(i)和(ii)兩個子操作是取指操作,它們是每一條指令的最前面兩個子操作,(iii)是執行子操作。不同的指令可能有1個或2個執行子操作。比如LDR指令(詳見下文)就有2個執行子操作,因此它是4週期指令。
 
很重要一點是,每一個子操作必須在一個時鐘週期內完成,同時要求這個子操作對應的數據通路在硬件上必須是存在的。對於不能在同一時鐘週期完成的操作必須拆分爲幾個子操作按順序完成。
 
7.2 指令集的設計
 
在已完成的硬件上,我們可以設計相應的指令集。指令集的挑選和設計是需要精心考慮的,特別是將用7400系列芯片搭建出來的Gater8,因爲如果設計過於複雜的指令集,電路就會變得相對複雜,所需7400芯片就會增多,同時設計一些用不上或可以用其它指令表示的一些很少用得上的指令也是一種硬件資源浪費。
 
由於IR寄存器爲8位,Gater8的指令長度爲8位,於是我打算用高4位表示操作碼(Opcode),低4位表示其它功能或不用。經過慎重考慮最後爲Gater8實現以下11條指令:
 
(1)OUT: 用於輸出A或T寄存器的值到輸出設備DEV1或DEV2,語法格式爲: OUT DEV1, A;
(2)IN: 從輸入設備DEV0讀取1字節到A或T寄存器,語法:IN A;
(3)LDR: 從指定地址的RAM處讀取1字節內容到A寄存器,語法:LDR 0x123; 0x123爲12位地址,下同。
(4)SUB: 執行減法運算,並將結果存入A或T寄存器,語法:SUB A;
(5)LDI: 從ROM內讀取1字節立即數到A或T寄存器,語法:LDI A, #0xAB; '#'符號表示立即數。
(6)ADD: 執行加法運算,並將結果存入A或T寄存器,語法:ADD A;
(7)JMP: 無條件跳轉,語法:JMP 0x123;
(8)AND: 執行布爾與運算,並將結果存入A或T寄存器,語法:AND A;
(9)STR: 將A寄存器的內容寫入指定地址的RAM中,語法:STR 0x123;
(10)OR: 執行布爾或運算,並將結果存入A或T寄存器,語法:OR A;
(11)JNZ: 當A寄存器值不爲0時跳轉,否則不跳轉,語法:JNZ 0x123;
 
注:在編寫實際Gater8的彙編程序時,上述指令中出現的地址,比如0x123可以用匯編程序中的符號地址代替。如此便可實現變量的定義、運算,以及循環程序的編寫。
 
上述每條指令對應的子操作和相應的控制端口的置值情況見表2:
 
CPU的設計與實現(2)--邏輯電路設計
表2. Gater8的詳細控制邏輯(查看高清大圖: 右擊->顯示圖片)。
 
表2中,所有空白格子內爲省略的數字1,'X'表示可以爲任何值,即0或1均可。
 
CPU的控制器本質上是一個有限狀態機。在任意狀態下,符合一定條件就會進入下一個不同的狀態,下一個狀態由當前狀態以及給出的條件決定,不一定唯一。每一個狀態可用於表達一個數據通路或子操作,比如上面7.1小節分析的IN T指令,一共由三個子操作構成,每一個子操作的不同控制端口輸出可以對應到一個狀態,假設當前狀態爲表中的S3,那麼就控制器就對17個控制端口分別置S3那行對應的值。
 
上面提到狀態變化由不同條件引起,所有條件由所有輸入到控制器的信號構成。Gater8的控制器使用以下一共7位輸入信號來確定狀態的變化
 
(i) IR寄存器的高6位,其中高4位是指令的操作碼,相對低的2位是條件碼(JNZ, LDR, STR, JMP指令除外);
(ii) Zflag標誌位,即零標誌位; 
 
其中IR寄存器的高4位是操作碼,其值決定了具體的指令,第Zflag標誌位信號只有JNZ指令用到,IR寄存器高6位中的最低位(表中名爲OPC2)只有OUT指令用到,IR寄存器的高6位中倒數第二位(表中名爲OPC)用於決定保存到或讀取到A(OPC=0時)或T(OPC=1時)寄存器。
 
我們對IN T指令的例子再重新詳細分析一下就是:(i) 最初是S0狀態,對應取指令子操作ROM->IR,(ii) 然後無條件轉換到下一個狀態S1(其實是有條件的,就是發生一個時鐘週期),對應的子操作是PC+1->PC,即PC計數器自加1,(iii) 然後查看以下條件:IR寄存器的高4位操作碼爲0011,對應爲IN指令,同時查看高4位後的那一位(取名OPC位)爲1,於是對應的子操作是DEV0->T,如果OPC位值爲0,對應的子操作爲DEV0->A,這兩個子操作都對應到狀態S3,因爲它們都可以在一個時鐘週期內完成。
 
 
Gater8的指令集設計採用了不定長格式,LDI, JNZ, LDR, STR, 和JMP這五條爲2字節指令,其它均爲1字節指令。
 
對於不同的操作碼,IR寄存器的低4位含意是不同的。一共有兩種情況,下面分別圖示加說明。
 
第一種情況:對於OUT, IN, SUB, LDI, ADD, AND, OR這七條指令,其機器碼結構如圖3所示:
 
CPU的設計與實現(2)--邏輯電路設計
圖3. OUT, IN, SUB, LDI, ADD, AND, OR七條指令的機器碼結構。
 
在圖3中,'X'表示不使用,其中OPC2只有OUT指令用到,用於表示設備DEV1(OPC2=0時)或DEV2(OPC2=1時)。這7條指令中,除了LDI外,都是單字節指令,LDI的完整指令格式如圖4所示:
 
CPU的設計與實現(2)--邏輯電路設計
圖4. LDI指令機器碼結構。
 
在圖4中,第1字節和其它6條指令結構一致(LDI指令不使用OPC2),第二字節爲無符號立即數,等價於C語言中的unsigned char類型的常量。
 
第二種情況:對於JNZ, LDR, STR, 和JMP這4條指令,由於它們都需要涉及地址操作,因此它們都是2字節指令,其機器碼結構如圖5所示:
 
CPU的設計與實現(2)--邏輯電路設計
圖5. JNZ, LDR, STR, 和JMP這4條指令的機器碼結構。
 
這4條指令的第1字節高4位爲操作碼,低4位提供12位地址的高4位地址,第2字節提供12位地址的低8位地址。爲了完整提供12位地址,第1字節的低4位都用於表示地址,不能用於其它目的,也就沒有OPC位的存在,因此LDR和STR這兩條指令不能選擇讀取或寫入A還是T寄存器,都是固定操作一個默認的寄存器(默認爲A)。
 
7.3 Gater8的硬佈線控制器實現
 
在控制器設計階段最重要的成果就是完成7.2小節中的表2。表2完整提供了每條指令的所需要的子操作、每個子操作對應的各部件控制端口的控制信號,以及狀態轉變的條件。
 
控制器的實現主有兩種方式:
(1)微程序方式:需要微程序ROM,用各個條件作爲該ROM的地址,對應的輸出就是各部件的控制信號。
(2)硬佈線方式:完全用布爾電路實現整個狀態機。
 
對於Gater8的控制器而言,一共有7個輸入和17個輸出。如果我去掉一個輸出設備,就可以使所需控制的端口數變爲16個,這樣我就可以用2片8位地址輸入8位數據輸出的ROM芯片,7個控制器輸入位做爲地址(最高地址位不用,置0)存儲每個狀態對應的16個控制信號,就完成了微程序式的控制器實現。Nibbler自制CPU就是這麼做的。
 
但我本人更偏向用硬佈線實現,也是Gater8的實現方式。因爲我一共用了4位操作碼,共可產生16個狀態,每個狀態按上面表2中輸出相應的各控制信號。狀態轉變可以通過計數器(Counter)實現,對4位操作碼對應16個狀態中的哪一個可以通過譯碼器(Decoder)實現,而剩下的每個狀態輸出相應的17個控制信號需要自己設計邏輯電路實現。硬佈線的邏輯結構如下圖所示:
 
CPU的設計與實現(2)--邏輯電路設計
圖6. 硬佈線控制器結構圖,圖片引用自John D. Carpinelli的《Computer Systems Organization & Architecture》第228頁。
 
其中計數器有三個動作:LD、INC、CLR,分別表示加載一個新狀態值、狀態值加1、 狀態值清0。這三個動作包含了控制器這個狀態機的全部可能的動作。在表2中,我用S0~S15分別表示狀態0至狀態16,當計算機復位時,計數器值爲0,即爲S0狀態,執行子操作ROM->IR,然後在一個時鐘週期後需要進入一下個狀態S1,執行PC+1->PC,從S0狀態變換到S1狀態只需要向計數器觸發INC動作便可,它就會順序變化下一個狀態。然後下一步就需要加載IR寄存器的高4位操作碼進入計數器,識別是什麼指令,這就需要將IR寄存器的高4位連到計數器的輸入口(圖中Input處),並向計數器觸發LD動作便可,計數器就會順利加載操作碼。計數器當前的狀態值會從右側輸出口輸出至譯碼器,譯碼器會將4位二進制值變爲對應的16位二進制值,這個16位二進制值只有其中1個位爲0,其餘15位均爲1。比如當計數器當前狀態值爲1001,即十進制數字9時,譯碼器輸出端第9位就會輸出0,其餘15位均輸出1。對應的這個第9位,我們就可以根據表2中設計的各部件控制信號進行輸出。當一條指令的最後一個子操作執行完成後,比如表2中IN T指令最後子操作對應的是狀態S3,那麼下一個狀態就應該回到狀態S0,以便進行下一輪取指和譯碼執行。因此,在S3狀態執行完後,我們需要觸發計數器的CLR動作,讓其清0,回到狀態S0。
 
Gater8的設計中計數器用一片70X163芯片,譯碼器爲一片4位至16位的70X154芯片。剩下的生成邏輯控制信號部分是完全是根據Gater8的具體特點設計的,不可能有現成芯片可用,我們需要用一些與門和或門來實現,這可以用70X08芯片提供與門和70X32芯片提供或門。
 
下面分析這個邏輯控制電路的設計。我們從一個簡單的控制信號ALUM入手,在前面已經說明,因爲我們的電路設計都是0使能部件,1不使能(禁止)部件。查閱表2中ALUM那一列,發現只有S6和S9兩個狀態下時ALUM爲0,因此我們只要當前狀態爲S6和S9兩者之一時就需要向ALUM這個控制端口發送一個0,其它時候可以不發送,因爲X可以當作1也禁用部件。這樣我們只需用一個與門連接譯碼器的S6和S9端口,這個與門的輸出連接ALUM控制端口就行了,這樣就完成了ALUM的控制信號的設計。用同樣的方法,根據表2,可以完成其它16個控制端口的邏輯設計。其中Aen這個端口很多狀態都會使能它(即給Aen端口置0),而且有些是有條件使能,這時我們可以用K-map或布爾邏輯計算出某一狀態下使能Aen的電路,並將它與其它狀態下的使能Aen的電路與在一起,最終得到完整的Aen的使能電路。例如表2中,狀態S3下使能Aen的電路是S3+OPC,這裏'+'表示布爾或,而狀態S4時是無條件直接使能Aen,它們都一起與在一起共同輸出Aen控制信號。
 
另外Gater8中,在且僅在狀態S1後就需要加載操作碼,於是只有譯碼器的S1輸出連接到了計數器的LOAD口。而11條指令分別在狀態S2, S3, S5, S6, S8, S9, S10, S11, S13, S14, S15後執行結束,於是這些指令與在一起並連接到了計數器的CLR端口。一旦這11個狀態中有一個值爲0,就表示當前指令執行完畢,需要清計數器爲0,準備處理下一條指令。
 
至此,複雜的控制器的設計講解完了。
 
八、關於復位、時鐘信號
 
關於Gater8的復位,我設計了RST信號,它用於連接在每一個寄存器、計數器的CLR端口,一旦RST信號爲0,這些連接的部件都會清0。不過我對於控制器裏使用的計數器芯片不同於PC計數器的芯片,前者是70X163,它是同步清0,而後者是70X161芯片,它是異步清0。我其它寄存器用的都是70X825芯片,它也是異步清0的。所謂異步清0是指我只要在這些芯片的CLR端口置0,它們就會立即清0生效,無需等待下一個CLK時鐘週期讓其清0生效。而同步清0則需要一個在CLK端口的時鐘信號才能讓清0生效。
 
我用了163芯片作爲控制器的計數器,讓它清0,需要先置RST爲0,然後產生一個CLK週期,這樣163芯片清0的同時,其它所有的寄存器和計數器也清0了。因爲RST信號未來將連接到一個實體按鈕上,我將設計按鈕按下去產生RST信號0,放開時產生RST信號1,由於人工按鍵的時延,再加上時鐘信號的高速,在人按下和放開中間,必然會有至少1個CLK時鐘週期,因此所有的部件將都能成功清0,完成復位的功能。
 
爲了設計時測試電路的方使,圖1中Gater8的時鐘信號我是連着一個二進制開關的。這意味着,產生一個時鐘週期,需要人工撥動開關兩次。
 
你也可以不用二進制開關,換用時鐘信號直接連上去,除了速度變得很快以外,效果一模一樣。但測試電路階段,不利於觀察。
 
至此,整個邏輯電路也分析完畢。下面進行編程測試Gater8這個自制CPU是否能正確運行程序。
 
 
九、編寫程序測試CPU
 
終於到了寫程序的階段了, 這一部分我們設計一小段程序,並將對應的機器碼導入到ROM芯片內,讓Gater8運行,看是否能正確執行得到預期的結果。
 
測試程序如下:
 
LDR 0XFFF   ;從RAM的0XFFF地址處讀取1字節到A寄存器
LDI T, #0xFF   ;從ROM中讀取立即數0xFF到T寄存器,相當於賦值 T=0xFF
IN A   ;從輸入設備DEV0讀取一個字節到A寄存器
OUT DEV1, A   ;輸出A的值到DEV1
ADD A   ;計算A=A+T
LDI T, #0x01   ;賦值T=0x01
Loop:
SUB A   ;計算A=A-1
JNZ Loop   ;如果A≠0就跳到Loop處執行
 
LDI A, #0x55   ;A=0x55
LDI T, #0x0E   ;T=0x0E
ADD A   ;A=A+T=0x55+0x0E=0x63
OR T   ;T=A | T=0x63 | 0x0E=0x6F
OUT DEV2, T   ;輸出T的值0x6F到DEV2
STR 0x010   ;保存A的值0x63到RAM的地址0x010處
Stop:
JMP Stop   ;程序執行結束,執行無限循環
 
由於我還沒有編寫好彙編編譯器程序,先用手工將上面這段程序翻譯成機器碼,並導入到ROM芯片中,如下圖所示:
CPU的設計與實現(2)--邏輯電路設計
 
圖7. 編譯好的程序導入ROM芯片內。
然後復位Gater8(即RST置0,然後CLK撥動兩次,再RST置1),不斷撥動CLK二進制開關產生時鐘信號,並觀察每個寄存器、Zflag、控制器狀態等值的變化,以及輸出設備DEV1和DEV2的LED點亮情況。前面圖2給出了當輸入設備DEV0值爲0x04時,程序最終運行狀態截圖,可以看到DEV1的LED燈顯示值爲0x04,而DEV2的LED燈顯示值爲0x6F。圖8給出了輸入輸出局部運行結果截圖。
CPU的設計與實現(2)--邏輯電路設計圖8. 輸入輸出部分運行結果截圖。
 
並且,程序最後停在最後這條機器碼爲A0的指令(就是程序中最後的那條JMP指令)處進行無限循環,一切如預期一樣正常,中途觀察各寄存器的值變化也都正確。最後STR寫入RAM處的值如圖9顯示,也都正確:
 
CPU的設計與實現(2)--邏輯電路設計
 
圖9. 程序中A寄存器的值0x63正確寫入RAM的0x010處。
上面的演示程序雖然簡單,但演示了所設計的指令。雖然指令集不大,但它是精心設計的,可以充分利用這些指令寫出功能更加強大的程序。
 
 
十、小結
 
這個自己設計的Gater8,前前後後被不斷修改了很多次,至少目前的這是第3個版本。指令集也是精心挑選,最終確定了11條。整個過程不但可以用來設計8位自己的CPU,同時也可以用來設計16位和32位,甚至64位CPU,它們的原理是一樣的。
 
設計邏輯電路本身並不太複雜,可以輕鬆根據Gater8設計出另一個Gater16或Gater32,但後期用過多的芯片,擔心會很難搭建出來。畢竟在軟件裏設計實驗成功的Gater8在實際搭建階段還可能會遇到一些不確定的問題。所以在邏輯設計階段,我已儘可能精簡電路,並同時保障一定的功能的自由性。
 
本來考慮用5位操作碼,提供32個狀態,這樣可以實現更多的指令。而且控制器中的計數器和譯碼器的連接部分也完成了設計,見圖10。後來也是考慮到(1)過多與門芯片和或門芯片問題,(2)內存地址線相應少一根,4KB的地址空間將變爲2KB,所以暫時還是保持當前4位操作碼的設計。
 
CPU的設計與實現(2)--邏輯電路設計
圖10. 用5位操作碼錶示32個狀態。
 
 
接下來就是關鍵的7400系列芯片搭建,當然還有彙編器的編寫,敬請期待。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章