Make/Day

毎日なにか作ります

48日目 ~実装!!CPU!!~

追記2:8/7にソースコードのミスを修正いたしました。
追記:7/31にソースコードのミスを修正いたしました。


こんにちは!今日はデジタル回路の話題で更新します!

今回は!ついに!CPUの実装を行っていきます!
このコンピュータ自作プロジェクトの中でも非常に大きな制御回路になります。そう、いわば、"頂き"みたいな(`-ω-´)キリッ✧
かなり大きな回路ブロックなだけあり、いつものように回路図を作るのがかなり面倒です。オイッ!!( ´Д`)っ))Д゚)・∵.
なので、参考になりそうな画像を引用させて頂きました。

CPUの実装

参考元:architecture - What should happen in this (nand2tetris) CPU implementation, if the instruction is a c-instruction? - Stack Overflow

どうも海外ではこの本をテキストに勉強している所もあるようで、質問サイトにも多くのPreviewがついていますね。私のプロジェクトもまさに同じ本を使用しているので、実際、こんな感じのCPUを作っています。
ここで気になるのは、一番左端の謎の数字と文字列だと思います(111 a...みたいな)。これは、CPUに与える命令コードを形式毎にアルファベットでわけたものです。この命令コードを細かく刻んで意味を解釈し、図中の©と書いてある配線に突っ込んでいくのが具体的な目標となります。

命令の解釈(デコード)

CPUへ入力される命令は16bitの2進数の数列からなります。これも本で仕様が決まっており、
i xx a cccccc ddd jjj
というビットの固まりに分かれています。例えば、iは命令の種類を示します。iが0ならアドレスを指定する命令、1なら計算を実行する命令だと解釈させます。上図の矢印で指している部分はaluの計算結果を使うか、入力された値を用いるかの分岐点で、"iが0の場合入力されたアドレスを使う"となるようにマルチプレクサに回路を繋げてやればよいと考えられます。このように、それぞれのビットの役割ごとの分岐点を考えて回路を組んでいきます。。。が、説明すると更に大変なので、ここまでで実装に移っていきます。

実装

それでは、Verilogコードで実装していきます。今回は、割と本気で検証がこれからなので、自分で使ってみる際は自己責任でお願いします。

module CPU(
  input [15:0] inM, instruction,
  input reset, clk,
  output [15:0] outM,
  output [14:0] addressM, pc,
  output writeM
);
  wire nI, loadA, loadD, swalu, zrout, ngout, zror, ngor, flagout, nflagout, anotheror, zrngor, conditions, loadpc; 
  wire [15:0] swAin, aluout, outA, outD, aluin;

      // AレジスタとaddressM
    Mux16 U0(instruction, aluout, instruction[15], swAin);
    Not   G0(instruction[15], nI);
    Or    G1(i, instruction[5], loadA);
    Register A(swAin, loadA, clk, outA);
    assign addressM = outA[14:0];
    
    // Dレジスタ
    And   G2(instruction[15], instruction[4], loadD);
    Register D(aluout, loadD, clk, outD);

    // Memory[A]
    And   G3(instruction[15], instruction[3], writeM);

    // ALU
    And   G4(instruction[15], instruction[12], swalu);
    Mux16 U1(outA, inM, swalu, aluin);
    // 修正1:ALU   alu(outD, aluin, instruction[11], instruction[10], instruction[9], instruction[8], instruction[7], instruction[6], outM, aluout, zrout, ngout);
    ALU   alu(outD, aluin, instruction[11], instruction[10], instruction[9], instruction[8], instruction[7], instruction[6], aluout, zrout, ngout);
    assign outM = aluout;

    // ProgramCounter
    And G5(instruction[2], ngout, ngor);
    And G6(instruction[1], zrout, zror);
    Or  G7(zrout, ngout, flagout);
    Not G8(flagout, nflagout);
    And G9(instruction[0], nflagout, anotheror);
    Or  G10(zror, ngor, zrngor);
    Or  G11(zrngor, anotheror, conditions);
    And G12(conditions, instruction[15], loadpc);
    // 修正2:PC  pc(outA, loadpc, true, reset, pc);
 // 修正3:PC  pc0(outA, loadpc, true, reset, clk, pc);
   PC  pc0(outA, loadpc, 1`b1, reset, clk, pc);
endmodule

上の記述を細かく追う方はあまりいらっしゃらないかもしれませんが、一応モジュールごとに分けて書いてみました。
次回、メモリの記述を書いたらいよいよコンピュータの組み立てに移っていきたいと思います!(`ω´)!