開放電腦計畫
如果您是資工系畢業的學生,必然會上過「計算機結構、編譯器、作業系統、系統程式」等等課程, 這些課程都是設計出一台電腦所必需的基本課程。但是如果有人問您「您是否會設計電腦呢?」,相信大部分人 的回答應該是:「我不會,也沒有設計過」。
光是設計一個作業系統,就得花上十年的工夫,遑論還要自己設計「CPU、匯流排、組譯器、編譯器、作業系統」 等等。因此,我們都曾經有過這樣的夢想,然後在年紀越大,越來越瞭解整個工業結構之後,我們就放棄了這樣 一個夢想,因為我們必須與現實妥協。
但是,身為一個大學教師,我有責任教導學生,告訴他們「電腦是怎麼做出來的」,因此我不自量力的提出了 這樣一個計畫,那就是「開放電腦計畫」,我們將以「千里之行、始於足下」的精神,設計出一台全世界最簡單 且清楚的「電腦」,包含「軟體與硬體」。
從 2007 年我開始寫「系統程式」這本書以來,就有一個想法逐漸在內心發酵, 這個想法就是:「我想從 CPU 設計、組譯器、虛擬機、編譯器到作業系統」,自己打造一台電腦,於是、「開放電腦計畫」就誕生了!
那麼、開放電腦計畫的「產品」會是什麼呢?
應該有些人會認為是一套自行編寫的軟硬體程式,當然、這部份是包含在「開放電腦計畫」當中的。
但是、更重要的事情是,我們希望透過「開放電腦計畫」讓學生能夠學會整個「電腦的軟硬體設計方式」,並且透過這個踏腳石瞭解整個「電腦軟硬體工業」,進而能夠達到「以理論指導實務、以實務驗證理論」的目標。
為了達成這個目標,我們將「開放電腦計畫」分成三個階段,也就是「簡單設計 (程式) => 理論闡述 (書籍) => 開源實作 (工業軟硬體與流程)」,整體的構想說明如下:
- 簡單設計(程式): 採用 Verilog + C 設計「CPU、組譯器、編譯器、作業系統」等軟硬體,遵循 KISS (Keep It Simple and Stupid) 原則,不考慮「效能」 與「商業競爭力」等問題,甚至在實用性上進行了不少妥協,一律採用「容易理解」為最高指導原則,目的是清楚的展現整個「軟硬體系統」的架構。
- 理論闡述(書籍): 但是、要瞭解像「處理器、系統軟體、編譯器、作業系統」這些領域,只有程式是不夠的。因為程式通常不容易懂,而且對於沒有背景知識的人而言,往往難如天書。所以我們將撰寫一系列書籍,用來說明上述簡單程式的設計原理,然後開始進入「計算機結構、編譯器、作業系統、系統程式」的理論體系中,導引出進一步的設計可能性與工業考量等議題。
- 開源實作(工業):一但有了前述的理論與實作基礎之後,我們就會採用「開放原始碼」來進行案例研究。舉例而言、在「計算機結構」上我們會以 ARM 為實務核心、「編譯器」領域則以 gcc, LLVM 為研究標的,「作業系統」上則會對 FreeRTOS、Linux 等進行案例研究,「虛擬機」上則會以 QEMU、V8 等開源案例為研究對象。
根據以上規劃,本書乃為一系列書籍中的一本,完整的書籍架構如下:
開放電腦計畫書籍 簡易程式 工業實作
系統程式 as0, vm0, cc0, os0 gcc/llvm 計算機結構 mcu0, cpu0 ARM/OpenRISC 編譯器 c0c, j0c gcc/llvm 作業系統 os0, XINU, MINIX FreeRTOS, Linux
這些書籍分別描述不同的面向,其涵蓋範圍如下圖所示:
硬體:計算機結構
在硬體方面,我們將自行設計兩款處理器,一款是用來展示簡單「微處理器」設計原理的16 位元微控制器 MCU0,而另一款則是用來展示「高階處理器」設計原理的 32 位元處理器 CPU0。
透過 MCU0,我們希望展示一顆「最簡易微處理器」的設計方法,我們將採用「流程式」與「區塊式」的方法分別實作一遍,讓讀者可以分別從「硬體人」與「軟體人」的角度去體會處理器的設計方式。由於「流程式」的方法比較簡單,因此我們會先用此法進行設計,當讀者理解何謂「微處理器」之後,在將同樣的功能改用「區塊式的方法」實作一遍,這樣應該就能逐漸「由易至難、由淺入深」了。
在 MCU0 當中,我們採用「CPU 與記憶體」合一的設計方式,這種方式比較像「系統單晶片」(SOC) 的設計方法,其記憶體容量較小,因此可以直接用 Verilog 陣列宣告放入 FPGA 當中使用,不需考慮外部 DRAM 存取速度較慢的問題,也不用考慮「記憶階層」的速度問題,因此設計起來會相對容易許多。
接著,我們將再度設計一個 32 位元的處理器 – CPU0。並透過 CPU0 來討論「當 CPU 速度比 DRAM 記憶體快上許多」的時候,如何能透過快取 (cache) 與記憶體管理單元 (MMU) 達到「又快又大」的目的,並且討論如何透過「流水線」架構 (Pipeline) 達到加速的目的,這些都屬於「高階處理器」所需要討論的問題。
軟體:系統程式
有了 MCU0 與 CPU0 等硬體之後,我們就可以建構運作於這些硬體之上的軟體了,這些軟體包含「組譯器、虛擬機、編譯器、作業系統」等等。
我們已經分別用 C 與 JavaSript 建構出簡易的「組譯器、虛擬機、編譯器」工具了,讓我們先說明一下在 CPU0 上這些程式的使用方法,以下示範是採用 node.js+Javascript 實作的工具版本,因此必須安裝 node.js 才能執行。
組合語言 (Assembly Language)
接著、讓我們從組合語言的角度,來看看 CPU0 處理器的設計,以下是一個可以計算 1+2+...+10
的程式,
計算完成之後會透過呼叫軟體中斷 SWI 程序 (類似 DOS 時代的 INT 中斷),在螢幕上印出下列訊息。
1+...+10=55
以下的檔案 sum.as0 正是完成這樣功能的一個 CPU0 組合語言程式。
檔案:sum.as0
LD R1, sum ; R1 = sum = 0
LD R2, i ; R2 = i = 1
LDI R3, 10 ; R3 = 10
FOR: CMP R2, R3 ; if (R2 > R3)
JGT EXIT ; goto EXIT
ADD R1, R1, R2 ; R1 = R1 + R2 (sum = sum + i)
ADDI R2, R2, 1 ; R2 = R2 + 1 ( i = i + 1)
JMP FOR ; goto FOR
EXIT: ST R1, sum ; sum = R1
ST R2, i ; i = R2
LD R9, msgptr ; R9= pointer(msg) = &msg
SWI 3 ; SWI 3 : 印出 R9 (=&msg) 中的字串
MOV R9, R1 ; R9 = R1 = sum
SWI 4 ; SWI 4 : 印出 R9 (=R1=sum) 中的整數
RET ; return 返回上一層呼叫函數
i: RESW 1 ; int i
sum: WORD 0 ; int sum=0
msg: BYTE "1+...+10=", 0 ; char *msg = "sum="
msgptr: WORD msg ; char &msgptr = &msg
組譯器 (Assembler)
我們可以用以下指令呼叫「組譯器 AS0」對上述檔案進行組譯:
node as0 sum.as0 sum.ob0
上述的程式經過組譯之後,會輸出組譯報表,如下所示。
sum.as0 的組譯報表
0000 LD R1,sum L 00 001F003C
0004 LD R2,i L 00 002F0034
0008 LDI R3,10 L 08 0830000A
000C FOR CMP R2,R3 A 10 10230000
0010 JGT EXIT J 23 2300000C
0014 ADD R1,R1,R2 A 13 13112000
0018 ADDI R2,R2,1 A 1B 1B220001
001C JMP FOR J 26 26FFFFEC
0020 EXIT ST R1,sum L 01 011F001C
0024 ST R2,i L 01 012F0014
0028 LD R9,msgptr L 00 009F0022
002C SWI 3 J 2A 2A000003
0030 MOV R9,R1 A 12 12910000
0034 SWI 2 J 2A 2A000002
0038 RET J 2C 2C000000
003C i RESW 1 D F0 00000000
0040 sum WORD 0 D F2 00000000
0044 msg BYTE "1+...+10=",0 D F3 312B2E2E2E2B31303D00
004E msgptr WORD msg D F2 00000044
最後「組譯器 AS0」會輸出機器碼到目的檔 sum.ob0 當中,其內容如下所示。
sum.as0 的機器碼 (以 16 進位顯示)
001F003C 002F0034 0830000A 10230000
2300000C 13112000 1B220001 26FFFFEC
011F001C 012F0014 009F0022 2A000003
12910000 2A000002 2C000000 00000000
00000000 312B2E2E 2E2B3130 3D000000
0044
虛擬機 (Virtual Machine)
如果我們用「虛擬機 VM0」去執行上述的目的檔 sum.ob0,會看到程式的執行結果,是在
螢幕上列印出 1+...+10=55
,以下是我們的操作過程。
1+...+10=55
編譯器 (Compiler)
當然、一個完整的現代電腦應該包含比組譯器更高階的工具,不只支援組合語言,還要支援高階語言。
因此、我們設計了一個稱為 J0 的高階語言,語法有點像 JavaScript,但卻是經過簡化的版本。
然後、我們又設計了一個可以用來編譯 J0 語言的編譯器,稱為 J0C (J0 Compiler),可以用來將 J0 語言編譯成中間碼, 也可以直接將中間碼轉換為 CPU0 的組合語言。
以下是一個 J0 語言的範例,
檔案:sum.j0
s = sum(10);
return s;
function sum(n) {
s = 0;
i=1;
while (i<=10) {
s = s + i;
i++;
}
return s;
}
當我們使用 j0c 編譯器將上述程式編譯之後,會輸出兩個檔案,一個是 sum.ir,是編譯器中間格式 (Intermediate Representation, 虛擬碼 pcode) 的輸出檔, 其內容如下:
D:\Dropbox\Public\web\oc\code>node j0c sum
arg 10
call T1 sum
= s T1
return s
sum function
param n
= s 0
= i 1
L1
<= T2 i 10
if0 T2 L2
+ T3 s i
= s T3
++ i
goto L1
L2
return s
endf
另一個是將上述中間格式轉換成轉換成 CPU0 組合語言之後的結果,如下所示:
sum
POP n
LDI R1 0
ST R1 s
LDI R1 1
ST R1 i
L1
LD R1 i
LDI R2 10
LDI R3 0
CMP R1 R2
JLE else1
LDI R3 1
else1
ST R3 T1
LDI R1 T1
CMP R1 0
JEQ L2
LD R1 s
LD R2 i
ADD R3 R1 R2
ST R3 T2
LDI R1 T2
ST R1 s
LD R1 i
ADDI R1 R1 1
ST R1 i
JMP L1
L2
LD R1 s
RET
LDI R1 10
PUSH R1
CALL sum
ST R1 T3
LDI R1 T3
ST R1 s
s WORD 0
i WORD 0
T1 WORD 0
T2 WORD 0
T3 WORD 0
上述由 j0c 所編譯產生的組合語言,感覺相對冗長,是因為這個編譯器是最簡版本,完全沒有做任何優化動作,甚至連暫存器都是 每次重新載入的,所以效率並不會很好。
作業系統 (Operating System)
當然囉!一個完整的電腦還必須要有作業系統,不過如果是嵌入式系統的話,沒有作業系統也沒關係,只要將全部的程式連結在一起, 就可以形成一台電腦了,目前開放電腦計畫的「作業系統」還在研究開發當中,希望很快就能提供大家一個最簡單的作業系統版本。
目前我們已經寫了一個可以進行兩個行程切換 「Task Switching」 範例,接著我們將參考 UNIXv6, L4 等作業系統,以建構更 完整的簡易作業系統。
當然、即使我們從 CPU 硬體一路設計到組譯器、虛擬機、編譯器、作業系統等,未來仍然有更多領域等待我們去探索,例如「網路模組、TCP/IP、 Ethernet、無線 RF 的硬體模組、繪圖卡、OpenGL、…..」等等,希望我們能夠用最簡單的話語,將這些電腦的原理說明清楚,並用簡單的方式 實作得更完整。