nand2tetris_hack計算機

柠檬水请加冰發表於2024-07-28

終於來到了這一步!!
前文裡,我們學習了hack程式語言,大概知道需要實現的hack計算機是什麼樣子,需要實現哪些功能。同時在更早的時候,我們建造了ALU和RAM元件,加上老師內建的ROM和鍵盤螢幕外設,那麼開幹!
等一下,在開始之前,我想先聊聊當今通用計算機的體系結構,也就是大名鼎鼎的馮諾依曼體系,我們的hack計算機也是依據於此

馮諾依曼體系介紹

“有道無術,術尚可求;有術無道,止於術”,三體裡秦始皇欲建造人型計算機,b站裡的大神用excel實現的cpu,當然還有我們這門課程裡將要在模擬器裡實現的hack計算機,他們都遵循著馮諾依曼體系框架。從b站評論裡看來的,“理論上,只要是可以實現與非門和時鐘的,都可以用來用來實現計算機”

這張圖,我記得在大一的計算機組成原理裡看見過,哈哈哈,只記得馮諾依曼這四個字了
我們通常有三種資訊型別在系統間傳遞。資料,當我們進行加法運算時,輸入需要從記憶體移動到暫存器中,再到實際的運算單元,進行計算後返回;地址,實現執行的是什麼指令和需要訪問記憶體中的哪些資料,這些都在地址裡的;控制,系統的每一個組成部分需要知道,在特定的時間點做什麼事情
下面,讓我們看看計算機裡面的不同元件,他們可以接收什麼資訊和發出什麼資訊

CPU

我們的CPU分為兩個元件,一是算術邏輯單元ALU,二是暫存器。算術邏輯單元能夠加減數字,也能夠進行邏輯運算;暫存器則是暫存一些資料,這些資料,在後續的計算中將要使用
ALU需要接入資料匯流排,因為需要拉取資料計算,然後返回;同時,他需要控制匯流排的資訊,告訴他要執行的操作型別是什麼,可能是相加,可能是取反;另一方面,根據計算結果,alu會返回標誌位資訊,如 結果是否大於0,這些需要放在控制匯流排上,告訴系統的其他部分做什麼操作,比如下一個指令是否跳轉
暫存器在概念上很簡單,作為cpu的緩衝單元,理所當然的需要接入資料匯流排;同時暫存器在設計上,還支援指定地址這種來取/設值,這實際是間接定址到RAM或者ROM上,這就需要暫存器連線到地址匯流排上
綜上,cpu需要透過資料匯流排/地址匯流排獲取輸入資料,透過控制匯流排得到運算函式,計算得到的結果輸出至資料/地址匯流排上,同時計算結果標誌位反饋到控制匯流排,等待下一次計算

記憶體

標準的馮洛伊曼體系,應該是使用一塊記憶體,資料和指令均放入其中,這裡簡化處理為資料和指令獨立放在一塊記憶體中(哈佛結構),還有一些別的原因,不在這裡說明了,仍以馮洛伊曼體系介紹
記憶體最直接的需求就是資料讀寫,需要連線在資料匯流排上;同時上面提到的CPU的輸入和輸出均可以是地址,記憶體同樣需要連線到地址匯流排上;其次指令記憶體需要知道下一條待執行的指令內容,這裡cpu輸出的指令地址被歸納進控制匯流排裡
這裡貼兩張資料記憶體和指令記憶體的資料流轉圖,幫助理解記憶體在整個鏈路扮演的角色


綜上,記憶體透過控制控制匯流排取出指令地址,透過地址匯流排取出輸入資料,然後經cpu計算後,透過地址匯流排和資料匯流排寫回輸出資料至記憶體中,同時控制匯流排上返回下一次待執行的地址

這裡,描述的有點亂。三條匯流排只是一個概念模型,理解每個元件做什麼的就好了。知道大概是這樣的思路,重要的還是引出下面的CPU和RAM的輸入輸出引數設計

計算機設計

總體架構


看起來複雜,實際對於計算機來說,只是單純的取指令,執行再取

CPU


想從白紙實現出來,我感覺難度相當高,課程老師估計也知道,直接給了實現結構圖,照著連線就好。我這裡如果說你問我,為什麼這個結構可以實現cpu,我是知道的,但若你問我cpu還有沒有別的實現結構,我就懵了

白板

實現結構圖

實現思路

這裡照著圖實現思路,就很清晰了。把指令裡的每一位取出來,然後進行不同的判斷,稍微複雜一點的就是現在ALU的返回值也需要用到了;這部分詳細可以參照影片

跳轉實現思路


RAM

這裡很簡單,想想怎麼區分不同的地址段即可

ROM

這是內建晶片,提供的介面就是取指

計算機

計算機就是把上面的幾個晶片,組裝起來

hdl實現

這裡還是貼一下程式碼,不要以後自己想看的時候,忘記了 哈哈

cpu

CHIP CPU {

    IN  inM[16],         // M value input  (M = contents of RAM[A])
        instruction[16], // Instruction for execution
        reset;           // Signals whether to re-start the current
                         // program (reset==1) or continue executing
                         // the current program (reset==0).

    OUT outM[16],        // M value output
        writeM,          // Write to M? 
        addressM[15],    // Address in data memory (of M)
        pc[15];          // address of next instruction

    PARTS:
	Mux16(a=instruction[0..15],b=aluout,sel=instruction[15],out=ain);

    // allow `A` @x -> set A 
    Not(in=instruction[15],out=opnot);
    Or(a=opnot,b=instruction[5], out=ac);
    ARegister(in=ain,load=ac,out=aout,out[0..14]=addressM);

    // D only load from `C`
    And(a=instruction[15],b=instruction[4], out=dc);
    DRegister(in=aluout,load=dc,out=aluin1);
    Mux16(a=aout,b=inM,sel=instruction[12],out=aluin2);
    ALU(x=aluin1,y=aluin2,zx=instruction[11],nx=instruction[10],zy=instruction[9],ny=instruction[8],f=instruction[7],no=instruction[6],out=aluout,out=outM,zr=zr,ng=ng);
    
    And(a=instruction[15],b=instruction[3],out=writeM);

    // PC
    Not(in=zr,out=notzr);
    Not(in=ng,out=notng);
    // great!
    DMux8Way(in=instruction[15], sel=instruction[0..2], a=null, b=jgt, c=jeq, d=jge, e=jlt, f=jne, g=jle, h=jmp);

    And(a=notzr,b=notng,out=jgt1);
    And(a=jgt,b=jgt1,out=jgt2);

    And(a=jeq,b=zr,out=jeq1);

    And(a=jge,b=notng,out=jge1);

    And(a=jlt,b=ng,out=jlt1);

    And(a=jne,b=notzr,out=jne1);

    Or(a=zr,b=ng,out=jle1);
    And(a=jle,b=jle1,out=jle2);

    Or8Way(in[0]=false, in[1]=jgt2, in[2]=jeq1, in[3]=jge1, in[4]=jlt1, in[5]=jne1, in[6]=jle2, in[7]=jmp, out=jumpout);
    PC(in=aout,inc=true,load=jumpout,reset=reset,out[0..14]=pc);
}

memory

CHIP Memory {
    IN in[16], load, address[15];
    OUT out[16];

    PARTS:
	DMux(in=load, sel=address[14], a=m1, b=m2);
    RAM16K(in=in, load=m1, address=address[0..13], out=out1);
    DMux(in=m2, sel=address[13], a=r1, b=r2);
    Screen(in=in, load=r1, address=address[0..12], out=out2);
    Keyboard(out=out3);

    Mux16(a=out2,b=out3,sel=address[13],out=out4);
    Mux16(a=out1,b=out4,sel=address[14],out=out);
}

computer

CHIP Computer {

    IN reset;

    PARTS:
    CPU(inM=inM,instruction=instruction,reset=reset,outM=outM,writeM=writeM,addressM=addressM,pc=pc);
    Memory(address=addressM,in=outM,load=writeM,out=inM);
    ROM32K(address=pc,out=instruction);
}

總結

到這裡,這門課程其實就結束了,後面還有一章是編譯器,我當時是直接用筆完成的,也沒有寫程式碼。哈哈哈,程式碼寫多了,反而喜歡樸素的方式來實現,還是懶吧。這章是4月做完的,已經過去了5,6,7,三個月了。這學習啊,一放下就會不經意過去好久了。
記得當時學完課程就開始寫這個部落格了,並沒有預期的那麼興奮,總覺得少了什麼,所以一直沒有寫完,這次只是草草貼了幾張圖收尾。現在想來,大概是時鐘這個概念還是沒有理解,這門課程最大的收穫可能就是cpu不再那麼神秘了,它真的是由與非門實現的,這裡還是有很多細節的,不過還是儘快開始下一門課吧。再放下去,今年就過去了。

好讀書,不求甚解;每有會意,便欣然忘食。

相關文章