nand2tetris_hack組合語言

柠檬水请加冰發表於2024-04-05

計算機

我接觸的第一臺電腦是winXP系統,我擁有的第一臺電腦是win7,也就說一開始我理解的計算機就有著好看的介面,靈活的操作性方式,擁有許多軟體,可以做很多事情。
我們可曾想過,大部分機器都有其專屬用途,比如榨汁機只能用來榨汁、削皮刀只能用來削皮,而計算機,他可以播放影片、瀏覽網頁等等,這是一件多麼神奇的事情!那這是如何做到的
開始的理論模型圖靈機,至後來真正實現了的計算機基礎架構馮洛伊曼體系。歷史不去追溯,我們看一下,這門課程將要實現的計算機基本架構圖

圖中省略了匯流排和控制單元,大體思路是程式和資料放在記憶體中的兩塊,ALU透過從記憶體和暫存器中取值完成運算,輸出至外部裝置或記憶體和暫存器中。看起來不難是吧,可是第一步怎麼做

指令集

可以發現,此刻我們對組裝的計算機並沒有一個具象化的概念。他需要實現哪些功能,他的內部資料如何傳輸等等,我們一無所知。課程老師在這一節,先假定我們已擁有了這樣一個hack計算機,我們學習hack-CPU提供的指令集,進行簡單程式開發。這樣在實現CPU之前,我們大概知道了我們想做的是什麼
計算機中,使用者輸入的指令,將會被編譯器翻譯為二進位制編碼(後面需要實現的),繼而交給CPU執行。指令需要實現的功能有三

  1. 指令需要計算機要做什麼,比如取數還是運算
  2. 指令需要能夠控制硬體的執行,比如正常指令都是逐行執行的,有時需要跳轉
  3. 指令需要告訴硬體如何做,比如去哪裡取值,輸出放在哪裡

實現指令集的過程中,存在硬體和軟體之間的權衡。這門課裡就沒有在指令集層次支援乘法,但是我們透過軟體演算法實現。所以,硬體設計和機器語言往往是同步進行的

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指令集

這個略複雜些,分為了以下幾種情況

  1. A/D/M可以賦值立即數(常數),不過可選值只有0,-1,1三種
  2. A/D/M可以互相賦值,支援相反數
  3. A/D/M支援+-算數運算,也支援&|邏輯運算,特別的支援立即數1,可以理解為第二種情況的擴充套件
  4. 跳轉指令,課程選擇了在下文介紹,用於控制程式執行流程

    如果現在和你說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實現!!

相關文章