12 casez(op) 13 /* MOV */ 5'b00zzz: regs[op[2:0]]=regs[sss]; 14 /* ADD */ 5'b01000: if (regs[0]+regs[sss] > 15) c_flag=1;else begin regs[0]=regs[0]+regs[sss];c_flag=0;end 15 /* OR */ 5'b01001: regs[0]=regs[0]|regs[sss]; 16 /* AND */ 5'b01010: regs[0]=regs[0]®s[sss]; 17 /* XOR */ 5'b01011: regs[0]=regs[0]^regs[sss]; 18 /* INC */ 5'b01100: if (regs[sss]+1 > 15) c_flag=1;else begin regs[sss]=regs[sss]+1;c_flag=0;end 19 /* NOT */ 5'b01101: regs[sss]=!regs[sss]; 20 /*RROTATE*/ 5'b01110: regs[sss]=regs[sss]>>1| (regs[sss]<<3 & 4'b1000); 21 /*LROTATE*/ 5'b01111: regs[sss]=regs[sss]<<1| (regs[sss]>>3 & 4'b0001); 22 /* JNC */ 5'b1000z: regs[7]= (c_flag)?regs[7]+1:{op[0],sss}; 23 /* JMP */ 5'b1001z: regs[7]= {op[0],sss}; 24 /* MVI */ 5'b1010z: regs[0]= {op[0],sss}; 25 endcase
12〜25行目までがcasez(op)のクローズです。2行目で定義して読み込んだインストラクションセットの上位5ビットのopの値によりケース分岐します。
13行目はMOV命令を実装するコードが記述されています。MOV命令はレジスター間の値の移動です。5'b00zzz: はopの値がこの値と一致するとその行が実行されます。opの5ビットのうち上位2ビットが00で、後はzzzと記述していますが、zzzの部分はどんな値でもopと一致します。この行の動作はopの下位3ビットで示したレジスターにsssで示したレジスターの値を代入します。
14行目はADD命令を実装するコードを記述しています。このインストラクションはアキュムレータのR0とsssで指定したレジスターの加算です。演算結果はR0に残ります。加算結果が15を上回ればc_flagを1にセットして、それ以外はc_flagをゼロにして演算結果はR0に保存します。
15〜17行目はsssで示したレジスターとR0の論理和、論理積、排他的論理和の演算結果がR0に保存されます。
18行目はsssで指定したレジスターの値を1プラスします。加算結果が15を上回ればc_flagを1にセットしてそれ以外はc_flagをゼロにします。
19行目はNOTインストラクションの実装です。sssで指定したレジスターの値の否定を取って同レジスターに保存します。
20行目と21行目はローテート命令です。20行目のRROTATEはsssで指定したレジスターを下位ビットに向けて1ビット分シフトする命令セットです。それで最下位ビットが落ちこぼれた場合はそれを最上位ビットに転生させます。21行目のLROTATEは先ほどとは逆に上位ビット方向に1ビット分シフトします。最上位ビットが浮きこぼれた場合は最下位ビットに転生します。後はRROTATEとほぼ同じですからコードを見てロジックは想像してみてください。
22行目のJNCですが、これはキャリーフラグc_flagの値によってインストラクションが保存されている外部メモリのアドレスを変更する命令です。c_flagが1の場合は実行アドレスのアドレスが1加算されます。そうでない場合はdoutの下位4ビットで指定されたアドレスに実行が移ります。
ここで幾つか初登場の表現がありますので少し説明しましょう。“?”と“:”で区切られた式ですが“?”の前にあるのが条件式です。これが真ならば“?”から“:”までの間に記述された式がこの式の値となりR7に代入されます。レジスターR7はプログラムカウンターの役割を担っていて外部メモリのアドレスに接続されています。
また、条件式が偽の場合はその後に続く“:”から“;”までがこの式の値となります。C言語などでは三項演算子として知られていますね。それともう1つの新登場の表現は“{}”です。これはビットの結合で、この例だとsssが下位3ビットでその上にopの最下位ビットが結合され4ビットの値が生成されます。今までsssは8個のレジスターを指定する3ビット長の値として使われてきましたが、今回はプログラムカウンターの値の下位3ビットとして使われています。Verilog-HDLはビット操作が簡単に書けるのがうれしいですね。
23行JMP命令はdoutで指定した下位4ビットで指定したアドレスに実行が移ります。JNCと同様sssとopの最下位を結合してアドレスを生成しプログラムカウンターR7に代入します。
24行目はMVI命令です。レジスターR0に即値を代入する命令です。opの最下位ビットと3ビット長のsssを結合した値がR0に代入されます。この命令が使えるのはアキュムレータR0に対してのみです。
25行目でcaseクローズを抜けます。これで外部メモリからフェッチしたインストラクションのデコードとその実行は終わりです。
26 /* PC++ */ if(op[4:1]!=4'b1000 && op[4:1]!=4'b1001) regs[7]=regs[7]+1; 27 end 28 endmodule
26行目ではプログラムカウンターを1つ進めます。ただし、JMPとJNC以外です。これらのインストラクションが実行された場合、プログラムカウンターの値はインクリメントされません。
今回はVerilog-HDLという言語で記述されたDL166の内部を見てきました。4ビットCPUではありますが、Verilog-HDLという言語を使えばCPUを28行で記述できることをどう思われたでしょうか。もし読者の皆さんの中に「CPUなんて怖くない」とか「僕も、私もCPUを創れるかも」と気付いた方がいらっしゃれば筆者の思うツボです。
次回は、このDL166が動作しているところをPC上のシミュレーターで観察してみます。お楽しみに。
Copyright © ITmedia, Inc. All Rights Reserved.