ひとりNand2Tetris(5b) ーハードウェア編完遂!

Posted by Hiraku on 2015-09-27

Nand2Tetrisもくもくの続き。やっとComputerという回路が完成し、ハードウェアが完成になります。

次のアセンブラからは好きな言語を使って実装して良いとのことなので、専用の環境ともお別れですね。


コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方

CPUの実装メモ

これまでの集大成とも言えるCPUという回路。この本で言うところの「Hack」という名前の機械語が直接動作するように作らなければいけません。

基本的に、本に大体の設計図が書いてあるので、それの欠けている部分を自分で想像しつつ、作っていく感じになります。

instructionと呼ばれるバスがこのHack機械語に対応するので、その各ビットを見つつ、マルチプレクサで条件分岐をしてうまくAレジスタとDレジスタにデータを保存すればOK。

ちょっとハマったところとしては、PC(プログラムカウンタ)をj1,j2,j3ピンを見て分岐するところ。案外一番複雑になりました。

計算部分は、もともとのALUがこの機械語向けに設計されていたこともあり、素直にピンを繋げば大丈夫でした。

命令がA命令(Aレジスタに数字をセットするだけの命令)とC命令(ALUに計算を支持する命令)でinstructionの意味が全く異なるところも大変です。おかげで、常にinstruction[15]が0か1か、各所で判定する部位が必要になっていました。

メモを書きながら実装していたのでコメント文が多め。

nand2tetris-memo/05 at master ・ hirak/nand2tetris-memo

// 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/05/CPU.hdl

/**
 * The Hack CPU (Central Processing unit), consisting of an ALU,
 * two registers named A and D, and a program counter named PC.
 * The CPU is designed to fetch and execute instructions written in 
 * the Hack machine language. In particular, functions as follows:
 * Executes the inputted instruction according to the Hack machine 
 * language specification. The D and A in the language specification
 * refer to CPU-resident registers, while M refers to the external
 * memory location addressed by A, i.e. to Memory[A]. The inM input 
 * holds the value of this location. If the current instruction needs 
 * to write a value to M, the value is placed in outM, the address 
 * of the target location is placed in the addressM output, and the 
 * writeM control bit is asserted. (When writeM==0, any value may 
 * appear in outM). The outM and writeM outputs are combinational: 
 * they are affected instantaneously by the execution of the current 
 * instruction. The addressM and pc outputs are clocked: although they 
 * are affected by the execution of the current instruction, they commit 
 * to their new values only in the next time step. If reset==1 then the 
 * CPU jumps to address 0 (i.e. pc is set to 0 in next time step) rather 
 * than to the address resulting from executing the current instruction. 
 */

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:
    // instruction
    // A命令: 0vvv vvvv vvvv vvvv
    // C命令: 111a cccc ccdd djjj
    // a=instruction[12]
    // c=instruction[6..11]
    // d1=instruction[5]
    // d2=instruction[4]
    // d3=instruction[3]
    // j1=instruction[2]
    // j2=instruction[1]
    // j3=instruction[0]

    // aIn: Aレジスタへの入力選択
    // A命令ならばinstructionをそのままAに渡して保存させる
    Mux16(a=instruction, b=aluout, sel=instruction[15], out=aIn);

    // aLoad: Aが保存するかどうか。
    // - instructionがA命令の時
    // - d1ビットが1の時
    // この2パターン存在するのでOrでつなぐ
    // instruction[15]==0 or instruction[5](=d1)==1
    Not(in=instruction[15], out=aCmd);
    Or(a=aCmd, b=instruction[5], out=aLoad);

    // Aレジスタ。出力はa[16]
    ARegister(in=aIn, load=aLoad, out=a, out[0..14]=addressM);

    // am: ALUのyへの入力選択。aかmか。
    // aビット(instruction[12])が1ならばMを。0ならばAを。
    Mux16(a=a, b=inM, sel=instruction[12], out=am);

    // Dレジスタ。出力はd
    // ここに保存するのは
    // d2(instruction[4])==1 and i=1
    And(a=instruction[4], b=instruction[15], out=dLoad);
    DRegister(in=aluout, load=dLoad, out=d);

    // ALU
    ALU(
        x=d,
        y=am,

        zx=instruction[11], //c1
        nx=instruction[10], //c2
        zy=instruction[9],  //c3
        ny=instruction[8],  //c4
        f=instruction[7],   //c5
        no=instruction[6],  //c6

        out=aluout,
        out=outM,
        zr=zr,
        ng=ng
    );

    // writeMは、d3(instruction[3])かつc命令の時(instruction[15])だけ
    And(a=instruction[3], b=instruction[15], out=writeM);

    // pcInc: カウンタをインクリメントする
    // pcLoad: カウンタアドレスを上書きする。

    // j1かつout < 0のとき1
    And(a=ng, b=instruction[2], out=j1true);
    // j2かつout = 0のとき1
    And(a=zr, b=instruction[1], out=j2true);
    // j3かつout > 0のとき1
    // out>0は!(ng+zr)で判定
    Or(a=ng, b=zr, out=notplus);
    Not(in=notplus, out=plus);
    And(a=plus, b=instruction[0], out=j3true);

    // A命令の時は無条件でpcInc=1。
    // 全て0ならば、条件が成立せずpcIncへ。
    // どれか成立していたらpcLoadへ。
    Or(a=j1true, b=j2true, out=j12true);
    Or(a=j12true, b=j3true, out=j123true);
    And(a=j123true, b=instruction[15], out=pcLoad);
    Not(in=pcLoad, out=pcInc);

    PC(
        in=a,
        inc=pcInc,
        load=pcLoad,
        reset=reset,
        out[0..14]=pc
    );
}

これでヒーヒー言ってたのですが、現実のCPUは浮動小数演算などもこなしてくれるわけで、もっと複雑なんでしょうね。このCPUは15bit同士の足し算しかできません。掛け算すらないので、四則演算もアセンブラより上のレイヤーで実装することになります。

あとのComputerの組み立ては簡単でした。

この調子でどんどん行こう!

etcの最新記事