計算機
我接觸的第一臺電腦是winXP系統,我擁有的第一臺電腦是win7,也就說一開始我理解的計算機就有著好看的介面,靈活的操作性方式,擁有許多軟體,可以做很多事情。
我們可曾想過,大部分機器都有其專屬用途,比如榨汁機只能用來榨汁、削皮刀只能用來削皮,而計算機,他可以播放影片、瀏覽網頁等等,這是一件多麼神奇的事情!那這是如何做到的
開始的理論模型圖靈機,至後來真正實現了的計算機基礎架構馮洛伊曼體系。歷史不去追溯,我們看一下,這門課程將要實現的計算機基本架構圖
圖中省略了匯流排和控制單元,大體思路是程式和資料放在記憶體中的兩塊,ALU透過從記憶體和暫存器中取值完成運算,輸出至外部裝置或記憶體和暫存器中。看起來不難是吧,可是第一步怎麼做
指令集
可以發現,此刻我們對組裝的計算機並沒有一個具象化的概念。他需要實現哪些功能,他的內部資料如何傳輸等等,我們一無所知。課程老師在這一節,先假定我們已擁有了這樣一個hack計算機,我們學習hack-CPU提供的指令集,進行簡單程式開發。這樣在實現CPU之前,我們大概知道了我們想做的是什麼
計算機中,使用者輸入的指令,將會被編譯器翻譯為二進位制編碼(後面需要實現的),繼而交給CPU執行。指令需要實現的功能有三
- 指令需要計算機要做什麼,比如取數還是運算
- 指令需要能夠控制硬體的執行,比如正常指令都是逐行執行的,有時需要跳轉
- 指令需要告訴硬體如何做,比如去哪裡取值,輸出放在哪裡
實現指令集的過程中,存在硬體和軟體之間的權衡。這門課裡就沒有在指令集層次支援乘法,但是我們透過軟體演算法實現。所以,硬體設計和機器語言往往是同步進行的
hack彙編
介紹課程hack彙編語法之前,先引入幾段大眾認知的標準組合語言
我的前幾篇部落格裡,簡單的介紹瞭如何編寫8086彙編程式碼。相比於高階語言裡複雜的語法,組合語言更接近程式設計的實質,只提供基礎的算數運算和邏輯運算。寫彙編程式碼其實很受限,腦子裡只能有加加減減、選址賦值這些操作,不過課程老師也說了,“simple people are impressed by sophisticated thing, sophisticated people are impressed by simple thing”
hack計算機記憶體和暫存器架構
在通用計算機中,程式碼其實也是資料,只是我們將他放在了程式碼段內,這裡hack計算機為了簡便處理,不在一整塊記憶體上劃分程式碼與資料,而是設計了兩塊記憶體,分別存放程式碼和資料
hack計算機有兩個暫存器,存放地址的A暫存器和存放資料的D暫存器。我們可以設定A暫存器的值,去記憶體中取值,然後將值儲存在D暫存器中,同時提供了一個變數M,M代表此刻資料區選中的值,可以對他進行賦值和運算
hack計算機指令集
有了記憶體和暫存器存放資料/程式碼之後,此時需要一套指令來讀取/寫入資料,並交給CPU運算。hack計算機提供了兩種指令集,依據指令集編寫而成的程式碼被載入入ROM中,而後逐行執行;至於指令怎麼被載入,ROM又是如何執行的,暫且先不討論。現在可以理解就是有了這樣一個hack計算機,提供了這樣的指令集,請為他編寫程式碼。其實這和我們學一門新的語言一樣,學語法,寫功能,再去了解其本質
A指令集
這個比較簡單A-address取址,並自動賦值A暫存器;至於圖中提示的如何確認,隨後而來的取值是到資料區還是程式碼區,這是後面實現CPU時考慮的事情
C指令集
這個略複雜些,分為了以下幾種情況
- A/D/M可以賦值立即數(常數),不過可選值只有0,-1,1三種
- A/D/M可以互相賦值,支援相反數
- A/D/M支援+-算數運算,也支援&|邏輯運算,特別的支援立即數1,可以理解為第二種情況的擴充套件
- 跳轉指令,課程選擇了在下文介紹,用於控制程式執行流程
如果現在和你說hack計算機就是由這兩種指令集做為基石搭建而成,你肯定不相信。其實兩種指令集配合起來使用,可以實現對任何一個記憶體單元的取值和賦值,看例子會很容易理解了
這裡直接將A暫存器當作立即數來使用了
這裡是透過M這個變數,方便的實現記憶體地址賦值操作,並總結了一種基礎正規化,A指令取值,C指令賦值
來一個稍難一點的例子,這裡可以實際去CPU模擬器中執行,看一看記憶體和暫存器在執行過程中,值的變化
hack計算機 Symbolic programming
標題我不知道怎麼翻譯,實際這裡是介紹了使用一些人類可讀的方式來影響程式執行
分支
高階語言裡的if else以及for迴圈,需要透過這種方式實現,不知道為什麼,習慣了前幾節使用選擇器來判斷,感覺這種jump方式,也不是那麼難受了。跳轉指令實際上是包含在C指令集內的,下文會再對C指令集綜述
再貼一個稍微複雜一點的,一般是這樣配合使用的
變數
之前在介紹A指令集的時候,@x 這裡x可以用來表示立即數,也可以用來表示實際記憶體地址,當混在一起的時候,就看不懂這裡x是代表什麼的了;所以我們需要一種區分,約定,如果這裡是表示實際記憶體地址的,我們可以使用一個小寫英文來代替,相當於高階語言裡申請了一個變數,記憶體地址自動從16號開始,前面被內建變數名R0..R15佔據了
這有一個立即數和實際記憶體地址混合使用的例子,很容易區分這兩者
標籤
這個主要是為了配合跳轉指令來使用的,畢竟跳轉的時候還要數在第幾行,修改程式碼之後都要調整一下受影響的跳轉程式碼,這也太痛苦了
實戰
這裡主要介紹了一種思路,編寫彙編程式碼時,先嚐試思考虛擬碼,再將虛擬碼翻譯為組合語言。執行時彙編器再翻譯為機器指令,可以發現變數和標籤這些語法糖都被抹去了
這裡提到了一個問題,因為計算機通電之後是不會停止的(這裡我不明白CPU佔有率是怎麼一回事),程式會一直往下執行的。就像C語言裡的陣列越界,我們需要這個程式執行結束之後,“停”在最後位置
順便說一句,上面的程式碼可以這樣最佳化。我在平時寫程式碼,也會盡量寫if 不寫else
hack計算機低階程式設計
迭代
指標
實戰
其實學到這裡,基本已經把計算機程式裡的三種結構理解了,大部分的功能都可以參考於此
hack計算機指令集-補充
標題有點奇怪,主要開始的時候如果介紹完整,很難解釋,現在學習了這麼多例子,已經對hack組合語言有了完整的認識,再來談一談指令集
A指令集的兩種用法
C指令集的完整概述
這裡貼兩張圖感受一些,C指令集的組成規則
稍複雜些的跳轉指令,也被容納在內了,是不是有種統一美
注意事項
這裡的意思是,不要混用jump指令和M賦值,因為二者都依賴A指令
輸入與輸出
這裡hack計算機內建了顯示器和鍵盤晶片,並將他們對映到記憶體的某一塊區域;可以透過讀取/寫入這一塊區域內的值,來顯示內容或讀取當前正在操作的鍵值
顯示器
@SCREEN為內建變數
可以將一個16位數設定為-1,快速設定一大塊畫素點為黑色
鍵盤
鍵盤簡單一點,這裡的值來自於被按下鍵的Unicode編碼值,沒有鍵被按下時,0
使用時,也內建了一個變數@KBD
課程作業
這節課程的基礎知識到這裡就結束了,留下了兩個作業,貼一下程式碼加深理解
實現乘法
這裡實現的很簡單,簡單的++,而且沒有判斷R0和R1誰大誰小尋找更少的迭代次數
// This file is part of www.nand2tetris.org
// and the book "The Elements of Computing Systems"
// by Nisan and Schocken, MIT Press.
// File name: projects/4/Mult.asm
// Multiplies R0 and R1 and stores the result in R2.
// (R0, R1, R2 refer to RAM[0], RAM[1], and RAM[2], respectively.)
// The algorithm is based on repetitive addition.
@R2
M=0
@R0
D=M
@END
D;JEQ
@R1
D=M
@END
D;JEQ
@i
M=0
(LOOP)
@i
D=M
@R1
D=D-M
@END
D;JEQ
@R0
D=M
@R2
M=M+D
@i
M=M+1
@LOOP
0;JMP
(END)
@END
0;JMP
白屏/黑屏
這個程式裡就能看出hack計算機的侷限性了,因為只有兩個暫存器,想把黑屏和白屏程式碼封裝起來,就很有難度了。最後是黑屏寫了一套程式碼,白屏寫了一套程式碼,二者就在大迴圈裡跑,也沒有設定子迴圈,讓程式完成渲染後處於監聽鍵盤的狀態。不過,至多就是按鍵的時間長一點就好了..
// Runs an infinite loop that listens to the keyboard input.
// When a key is pressed (any key), the program blackens the screen,
// i.e. writes "black" in every pixel. When no key is pressed,
// the screen should be cleared.
(MAIN)
@KBD
D=M
@BLACK
D;JNE
@WHITE
0;JMP
@MAIN
0;JMP
(BLACK)
@i
M=0
@j
M=0
@head
M=0
(BLACK-LOOP1)
@i
D=M
@255
D=D-A
@MAIN
D;JGT
@j
M=0
(BLACK-LOOP2)
@j
D=M
@31
D=D-A
@BLACK-LOOP1-END
D;JGT
@head
D=M
@j
D=D+M
@SCREEN
A=A+D
M=-1
@j
M=M+1
@BLACK-LOOP2
0;JMP
(BLACK-LOOP1-END)
@32
D=A
@head
M=M+D
@i
M=M+1
@BLACK-LOOP1
0;JMP
(WHITE)
...
M=0
...
(END)
@END
0;JMP
總結
這一節貼了好多圖,不過想想還是很有成就感的,我在學會一門語言之後,還可以把他表述出來。這節看懂了課程上的例子之後,難度就不大了,最後的白屏/黑屏作業也只是複雜,而不是困難。期待下節課的CPU實現!!