😀

コンピュータアーキテクチャ

2024/05/18に公開

コンピュータシステムの理論と実践の学習内容のダンプ。

プログラム内蔵方式

コンピュータがプログラムのコードをデータのようにメモリに格納して操作される方式。同じハードウェアでも別のプログラムを読み込むと全くことなる動作をすることが可能

ノイマン型アーキテクチャ

抽象化された論理的なコンピュータをuniversal Turing machineと呼び、実用的なコンピュータアーキテクチャは von Neumann machine とよばれる。
ノイマン型アーキテクチャの登場人物は4つ

  • CPU: ALU、レジスタ、制御ユニットからなり、計算をおこなう
  • メモリ: データと命令を格納し、CPUからRead / Writeされる
  • 入力デバイス: データのinput
  • 出力デバイス: データのoutput

ポイントは、メモリに命令が格納されていること

メモリ

データと命令は別のメモリ空間に保存される。両者ともにバイナリで表現され、RAMに保存される。RAMはレジスタの集合である。
例えば、「メモリアドレスjのデータをフェッチせよ」という命令がある時、以下のようにレジスタは使われる

  • CPUからRAMのaddress入力へjが送られる。
  • RAMのタイレクトアクセス回路が対象のレジスタを選ぶ
  • RAM[j]のデータをCPUに送る

HDLで記述すると以下のようになる

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

    PARTS:
    DMux(in=load, sel=address[14], a=ramload, b=screenload);
    RAM16K(in=in, load=ramload, address=address[0..13], out=ramOut);
    Screen(in=in, load=screenload, address=address[0..12], out=screenOut);
    Keyboard(out=KbdOut);

    Mux4Way16(a=ramOut, b=ramOut, c=screenOut, d=KbdOut, sel=address[13..14], out=out);
}

address[14]でロードしてくる情報の先を決定し、inで入力データを決定する

データメモリ

データを格納するメモリ。
一般的にRAMと呼ばれる

命令メモリ

コンピュータが命令を実行するたびに、CPUが命令メモリから命令をFetchし、デコードする。
一般的にROMと呼ばれる

CPU

中央演算装置。メモリから命令やデータをロードしたりwriteしたりする
HDLで記述すると以下のようになる


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:

    And(a=instruction[15], b=true, out=Corder);
    Not(in=Corder, out=Aorder);

    /*
     Destination
    */
    // saveToA
    And(a=instruction[5], b=instruction[15], out=saveToA);

    // saveToD
    And(a=instruction[4], b=instruction[15], out=saveToD);

    // saveToM
    And(a=instruction[3], b=instruction[15], out=writeM); // write to M


    /*
        Jump
    */
    Not(in=zr, out=notZr);
    Not(in=ng, out=notNg);

    And(a=notZr, b=notNg, out=GT); // out > 0
    And(a=notZr, b=ng, out=LT); // out < 0
    And(a=zr, b=true, out=EQ); // out == 0
    Not(in=zr,out=NE); // out != 0
    Or(a=GT, b=EQ, out=GE); // out >= 0
    Or(a=LT, b=EQ, out=LE); // out =< 0

    And(a=instruction[0], b=true, out=J3);
    And(a=instruction[1], b=true, out=J2);
    And(a=instruction[2], b=true, out=J1);

    Not(in=J3, out=notJ3);
    Not(in=J2, out=notJ2);
    Not(in=J1, out=notJ1);



    // No jump 000
    And(a=notJ3, b=notJ2, out=maybeNojump);
    And(a=maybeNojump, b=notJ1, out=NoJump1);
    And(a=NoJump1, b=instruction[15], out=NoJump);


    // JGT 001
    And(a=J3, b=notJ2, out=maybeJGT);
    And(a=maybeJGT, b=notJ1, out=JGT);
    And(a=JGT, b=GT, out=jump1);

    // JEQ  010
    And(a=notJ3, b=J2, out=maybeJEQ);
    And(a=maybeJEQ, b=notJ1, out=JEQ);
    And(a=JEQ, b=EQ, out=jump2);

    // JGE  011
    And(a=J3, b=J2, out=maybeJGE);
    And(a=maybeJGE, b=notJ1, out=JGE);
    And(a=JGE, b=GE, out=jump3);

    // JLT  100
    And(a=notJ3, b=notJ2, out=maybeJLT);
    And(a=maybeJLT, b=J1, out=JLT);
    And(a=JLT, b=LT, out=jump4);

    // JNE  101
    And(a=J3, b=notJ2, out=maybeJNE);
    And(a=maybeJNE, b=J1, out=JNE);
    And(a=JNE, b=NE, out=jump5);

    // JLE  110
    And(a=notJ3, b=J2, out=maybeJLE);
    And(a=maybeJLE, b=J1, out=JLE);
    And(a=JLE, b=LE, out=jump6);

    // JMP 111
    And(a=J3, b=J2, out=maybeJMP);
    And(a=maybeJMP, b=J1, out=jump7);

    Or8Way(
        in[0]=jump1,
        in[1]=jump2,
        in[2]=jump3,
        in[3]=jump4,
        in[4]=jump5,
        in[5]=jump6,
        in[6]=jump7,
        in[7]=false,
        out=mayBejump
    );
    And(a=instruction[15], b=mayBejump, out=jump);

    Not(in=jump, out=inc);

    PC(in=ARegisterOut, load=jump, inc=inc, reset=reset, out[0..14]=pc);

    /*
        Data Flow
    */

    Mux16(a=instruction, b=aluOut, sel=instruction[15], out=ToARegister);
    Not(in=instruction[15], out=noti15);
    Or(a=noti15, b=saveToA, out=loadA);
    ARegister(in=ToARegister, load=loadA, out=ARegisterOut, out[0..14]=addressM);

    Mux16(a=ARegisterOut, b=inM, sel=instruction[12], out=AorMOut);
    ALU(x=DRegisterOut, y=AorMOut, zx=instruction[11], nx=instruction[10], zy=instruction[9], ny=instruction[8], f=instruction[7], no=instruction[6], out=outM, out=aluOut, zr=zr, ng=ng);

    DRegister(in=aluOut, load=saveToD, out=DRegisterOut);

}

ALU

算術演算と論理演算を行う論理回路。

レジスタ

CPUの近くにある一時記憶装置。高速に読み書きが可能。各レジスタはそのサイズに合わせて1ワードを保存する。
注目ポイント2つ

  • CPU回路の中に存在するためアクセスが爆速
  • レジスタは数えるほどしか存在しない。そのため、機械語でレジスタを指定するために必要なビットが少なくすみ、短くなる

データレジスタ

簡易的なメモリのように扱われる。計算結果の一時保存場所として使われがち

アドレスレジスタ

データの読み書きのため、メモリのどのワードにアクセスすべきかを保存するレジスタ。

プログラムカウンタレジスタ

次にフェッチすべき命令アドレスを格納するレジスタ。PCと呼ばれる。PCにはアドレスが格納されており、このアドレスは命令メモリからフェッチを行うアドレス。

  • 現在の命令にgotoがなければ、プログラムはPCをインクリメントして命令をロードする
  • goto nがあれば、PCにnを書き込みその値を次に読む。

入出力

様々な入出力デバイスの中で一番単純なのは、メモリマップドI/O。I/Oデバイスのためのメモリ領域を割り当てて、CPUからは通常のメモリ領域かのように見せることができる。
こうすることで、CPU・プラットフォーム全体の設計はI/Oデバイスとは分離できる。通常、デバイスへのメモリマップの割当とその先頭メモリの保存はOSが行う。

Hackデバイスの動き

HackデバイスをHDLで記述すると以下のようになる。

CHIP Computer {

    IN reset;

    PARTS:
    ROM32K(address=pcOut, out=romOut);

    CPU(inM=Mout, instruction=romOut, reset=reset, outM=Mvalue, writeM=writeM, addressM=addressM, pc=pcOut);

    Memory(in=Mvalue, load=writeM, address=addressM, out=Mout);

}

電源を入れると、ROMに書き込まれた制御プログラムが

クロックサイクル中の動きは以下2つの構成要素からなる

  • 実行
    現在命令を構成するビットを元に、ALUとレジスタを使用して計算を実行したり値をレジスタに保存したりする

  • フェッチ
    次にどの命令をフェッチするかをALUの出力と現在命令のジャンプbitから行う。
    ジャンプを行うなら、PCにAレジスタの値をセットする
    しないならPCの値をインクリメントするのみ

メモリ空間を2つに分けているのはHackの特徴でかなり単純化している。

CPUのパフォーマンス構造のため2つの道がとられた

  • CISC (Complex Instruction Set Computing): 複雑な命令セットを提供しパフォーマンスを上げようとする試み
  • RISC(Reduced Instruction Set Computing): 単純な命令セットでできるかぎり高速なハードウェア実装を行う試み。

制御ユニット

次にどの命令をフェッチしてデコードし実行することを担う。ループ、レジスタ・メモリのRead / Write、次の命令へのジャンプなど

Discussion