図75.「実行2」の制御信号
図76.「実行3」の制御信号
上記の表は別ページに書いた「自作CPU-機械語命令の詳細動作」を見直しながら確認していくと分かりやすいと思います。
ここまで見てきた制御信号の動きを実現するためのcontrol_unit.vのコードが以下になります。
module control_unit(clk_3, rst, flag, ir1, alu_e, wr_e, ir1_e, ir2_e, pc_e, hr_e,
mem_e, p1_e, p2_e, p3_e, p4_e, mem_w, m1_s, m2_s, m3_s, m4_s, m5_s, alu_opcode);
input clk_3;
input rst;
input flag;
input [4:0] ir1;
output alu_e, wr_e, ir1_e, ir2_e, pc_e, hr_e, mem_e, p1_e,
p2_e, p3_e, p4_e, mem_w, m1_s, m2_s, m3_s, m4_s, m5_s;
output [3:0] alu_opcode;
reg [20:0] decode;
reg [2:0] state;
always @(posedge clk_3 or negedge rst) begin
if(rst == 1'b0)
state <= 3'b000;
else
case(state)
3'b000:begin //待機
state <= 3'b001;
decode <= 21'b000000000000000000000;
end
3'b001:begin //RAM読込1
state <= 3'b010;
decode <= 21'b000000100000011000000;
end
3'b010:begin //IR1読込
state <= 3'b011;
decode <= 21'b001010000000011000000;
end
3'b011:begin //RAM読込2
state <= 3'b100;
decode <= 21'b000000100000011000000;
end
3'b100:begin //IR2読込
state <= 3'b101;
case(ir1)
5'b00001:decode <= 21'b000100000000001000000; // JUMP
5'b00010:begin // JZC
if(flag == 1'b0)
decode <= 21'b000100000000011000000;
else
decode <= 21'b000100000000001000000;
end
5'b00011:decode <= 21'b000100000000010001000; // LD
5'b00100:decode <= 21'b000100000000010000000; // ST
5'b00101:decode <= 21'b000100000000111000000; // SET
5'b00110:decode <= 21'b000100000000010001000; // LHI
5'b00111:decode <= 21'b000100000000010000001; // ADD
5'b01000:decode <= 21'b000100000000010000010; // SUB
5'b01001:decode <= 21'b000100000000010000011; // AND
5'b01010:decode <= 21'b000100000000010000100; // OR
5'b01011:decode <= 21'b000100000000010000101; // NOT
5'b01100:decode <= 21'b000100000000010000110; // SL
5'b01101:decode <= 21'b000100000000010000111; // SR
5'b01110:decode <= 21'b000100000100010100000; // INA
5'b01111:decode <= 21'b000100000010010110000; // INB
5'b10000:decode <= 21'b000100000000010001000; // OUTA
5'b10001:decode <= 21'b000100000000010001000; // OUTB
endcase
end
3'b101:begin //実行1
state <= 3'b110;
case(ir1)
5'b00001:decode <= 21'b000010000000001000000; // JUMP
5'b00010:begin // JZC
if(flag == 1'b0)
decode <= 21'b000010000000011000000;
else
decode <= 21'b000010000000001000000;
end
5'b00011:decode <= 21'b000010100000010001000; // LD
5'b00100:decode <= 21'b100010000000010000000; // ST
5'b00101:decode <= 21'b010010000000111000000; // SET
5'b00110:decode <= 21'b000010100000010001000; // LHI
5'b00111:decode <= 21'b000010100000010000001; // ADD
5'b01000:decode <= 21'b000010100000010000010; // SUB
5'b01001:decode <= 21'b000010100000010000011; // AND
5'b01010:decode <= 21'b000010100000010000100; // OR
5'b01011:decode <= 21'b000010100000010000101; // NOT
5'b01100:decode <= 21'b000010100000010000110; // SL
5'b01101:decode <= 21'b000010100000010000111; // SR
5'b01110:decode <= 21'b000010000000010100000; // INA
5'b01111:decode <= 21'b000010000000010110000; // INB
5'b10000:decode <= 21'b000010100000010001000; // OUTA
5'b10001:decode <= 21'b000010100000010001000; // OUTB
endcase
end
3'b110:begin //実行2
state <= 3'b111;
case(ir1)
5'b00001:decode <= 21'b000000000000011000000; // JUMP
5'b00010:begin // JZC
if(flag == 1'b0)
decode <= 21'b000000000000011000000;
else
decode <= 21'b000000000000011000000;
end
5'b00011:decode <= 21'b100000000000010001000; // LD
5'b00100:decode <= 21'b000000000001010000000; // ST
5'b00101:decode <= 21'b000000000000011000000; // SET
5'b00110:decode <= 21'b100000000000010001000; // LHI
5'b00111:decode <= 21'b100000000000010000001; // ADD
5'b01000:decode <= 21'b100000000000010000010; // SUB
5'b01001:decode <= 21'b100000000000010000011; // AND
5'b01010:decode <= 21'b100000000000010000100; // OR
5'b01011:decode <= 21'b100000000000010000101; // NOT
5'b01100:decode <= 21'b100000000000010000110; // SL
5'b01101:decode <= 21'b100000000000010000111; // SR
5'b01110:decode <= 21'b000000000001010100000; // INA
5'b01111:decode <= 21'b000000000001010110000; // INB
5'b10000:decode <= 21'b100000000000010001000; // OUTA
5'b10001:decode <= 21'b100000000000010001000; // OUTB
endcase
end
3'b111:begin //実行3
state <= 3'b001;
case(ir1)
5'b00001:decode <= 21'b000000000000011000000; // JUMP
5'b00010:begin // JZC
if(flag == 1'b0)
decode <= 21'b000000000000011000000;
else
decode <= 21'b000000000000011000000;
end
5'b00011:decode <= 21'b010000000000011000000; // LD
5'b00100:decode <= 21'b000000000000011000000; // ST
5'b00101:decode <= 21'b000000000000011000000; // SET
5'b00110:decode <= 21'b000001000000011000000; // LHI
5'b00111:decode <= 21'b010000000000011000001; // ADD
5'b01000:decode <= 21'b010000000000011000010; // SUB
5'b01001:decode <= 21'b010000000000011000011; // AND
5'b01010:decode <= 21'b010000000000011000100; // OR
5'b01011:decode <= 21'b010000000000011000101; // NOT
5'b01100:decode <= 21'b010000000000011000110; // SL
5'b01101:decode <= 21'b010000000000011000111; // SR
5'b01110:decode <= 21'b000000000000011000000; // INA
5'b01111:decode <= 21'b000000000000011000000; // INB
5'b10000:decode <= 21'b000000010000011000000; // OUTA
5'b10001:decode <= 21'b000000001000011000000; // OUTB
endcase
end
endcase
end
assign { alu_e, wr_e, ir1_e, ir2_e, pc_e, hr_e, mem_e, p1_e, p2_e,
p3_e, p4_e, mem_w, m1_s, m2_s, m3_s, m4_s, m5_s, alu_opcode} = decode;
endmodule
コード自体は長めですが、ほとんどのコードが”decode”というレジスタへの代入文であり、その内容も先ほどの信号線の表がそのまま代入文になっているだけです。新しい要素としては”state”というレジスタの使い方があります。
”state”というレジスタは命令の実行に必要となる7つのステップの状態を管理するために作られたレジスタです。さらにリセットがかけられた時に移行する「待機」状態も含め、全部で8つの状態を管理します。”state”レジスタは3ビットのサイズを持っていて、各ステップの状態を次のような数値に割り当てて管理します。
待機 :3’b000
RAM読込1 :3’b001
IR1読込 :3’b010
RAM読込2 :3’b011
IR2読込 :3’b100
実行1 :3’b101
実行2 :3’b110
実行3 :3’b111
この”state”というレジスタがどのように機能するかについて理解するには、”state”レジスタを括弧の中に持つ17行目のcase文の働きに注目する必要があります。
少し分かりやすくするため17行目のcase文から”decode”への代入処理の部分を削除します。するとalways文の中の処理は以下のようになっています。
always @(posedge clk_3 or negedge rst) begin
if(rst == 1'b0)
state <= 3'b000;
else
case(state)
3'b000:begin //待機
state <= 3'b001;
end
3'b001:begin //RAM読込1
state <= 3'b010;
end
3'b010:begin //IR1読込
state <= 3'b011;
end
3'b011:begin //RAM読込2
state <= 3'b100;
end
3'b100:begin //IR2読込
state <= 3'b101;
end
3'b101:begin //実行1
state <= 3'b110;
end
3'b110:begin //実行2
state <= 3'b111;
end
3'b111:begin //実行3
state <= 3'b001;
end
endcase
end
まず、”rst”というリセット信号が「0」になっている時には強制的に”state”レジスタが3’b000の状態(待機の状態)になることがわかります。それ以外の時には”state”の状態によって処理が変わります。
リセット信号が「0」でない状態でalways文の中に入れるとすれば、それは”clk_3”というクロック信号が立ち上がった瞬間だけということになります。(posedge clk_3)
リセット信号が「0」でない時は”clk_3”が立ち上がるたびにcase文が実行されることになりますが、case文の中では全てのケースにおいて”state”レジスタの値を書き換える処理が記述されています。
例えば”state”レジスタが3’b000の状態(待機の状態)の時に”clk_3”の立ち上がりでalways文の中に入った場合には”state”レジスタの値を3’b001の状態(RAM読込1の状態)に書き換える処理が実行されます。(state <= 3’b001;)
ですので次に”clk_3”が立ち上がる時にはcase文の中の”state”が3’b001のケース(RAM読込1の状態)が実行されることになります。
”state”が3’b001のケース(RAM読込1の状態)ではさらに”state”レジスタの値が3’b010の状態(IR1読込の状態)に書き換える処理が実行されるのでさらに”state”レジスタの値が書き変わっていきます。
以降、同様にしてcase文の中を見ていくと、”state”レジスタの値が次々に書き変わっていき、値が3’b111の状態(実行3の状態)になった時に”state”が3’b001の状態(RAM読込1の状態)へ戻されることが分かります。
このようにalways文の中で”state”レジスタの値を書き換えていくことで、クロック信号が入るたびに状態が切り替わる機能を実現することができます。このような回路のことを「ステートマシン」と言います。
CPUなどの制御装置はステートマシンで実現されています。ステートマシンとは内部に複数の状態を持ち、外部からの入力信号と現在の状態により、次の状態と出力信号が決まる回路のことを言います。ある状態から別の状態への移行はクロックの立ち上がり、もしくは立ち下がりの瞬間に行われます。
この部分の回路の動きが分かれば元のcontrol_unit.vのコードも動作の内容が理解しやすくなるかと思います。CPUはリセット状態から抜け出した直後から「RAM読込1」から「実行3」までの状態をクロック信号が立ち上がるたびに移行していき、再び「RAM読込1」の状態へ戻って同じ移行を繰り返す、という動作をします。その過程でインストラクションレジスタへの命令の読み込みやCPU内の各回路への信号の出力などが行われ、プログラムが実行されていきます。
145行目にあるassign文の意味は”decode”という21ビットのレジスタの値を連接演算子で連結された各信号線の束(これも21ビット)へ代入する、という意味になります。各信号線への値の代入はソースコード上で繰り返し行われます。毎回全ての信号線の名前を記述するのは大変なので、代入処理のときには”decode”という1つの信号への代入で済むようにこのような書き方をしています。