スポンサーリンク

control_unit.v

control_unit.vCPUの設計図上には出てこないのですが、各回路へ制御信号を送る重要な回路です。この回路はCPU設計において「制御装置」と呼ばれています。制御装置は「IR1」に読み込まれた命令の識別番号を参照してどのタイミングにどの信号線を上げ下げするかを判断します。別ページの「自作CPU-機械語命令の詳細動作」で書いたとおり、各命令の動作は7つのステップを経て段階的に実行されます。まずは各命令における各ステップでの信号線の状態をまとめたものを以下に示します。

なお、各回路への信号線には以下の名前を使用しています。

ALUのクロック信号 alu_e

WRのクロック信号 wr_e

IR1のクロック信号 ir1_e

IR2のクロック信号 ir2_e

PCのクロック信号 pc_e

HRのクロック信号 hr_e

RAMの読出し用クロック信号 :mem_e

P1のクロック信号 p1_e

P2のクロック信号 p2_e

P3のクロック信号 p3_e

P4のクロック信号 p4_e

RAMの書込み用クロック信号 :mem_w

M1の経路選択信号 :m1_s

M2の経路選択信号 :m2_s

M3の経路選択信号 :m3_s

M4の経路選択信号 :m4_s

M5の経路選択信号 :m5_s

ALUの演算切替用制御信号 :alu_opcode ※4ビット

次に7つのステップごとに各命令で信号線の状態を「0」と「1」のどちらの状態にしておくべきかをまとめた表を示します。7つのステップの内「RAM読込1」から「RAM読込2」までの最初の3ステップはインストラクションレジスタに命令自体を読み込む段階ですので、全命令で同じ動きをします。それ以降のステップでは各命令ごとに異なる動作を行いますが、「JZC」命令だけはフラグの状態によって動作が異なるため、フラグが「0」の時と「1」の時の2パターンの動作が示されています。

 

RAM読込1の制御信号

図70.「RAM読込1」の制御信号

 

IR1読込の制御信号

図71.「IR1読込」の制御信号

 

RAM読込2の制御信号

図72.「RAM読込2」の制御信号

 

IR2読込の制御信号

図73.「IR2読込」の制御信号

 

実行1の制御信号

図74.「実行1」の制御信号

スポンサーリンク

 

実行2の制御信号

図75.「実行2」の制御信号

 

実行3の制御信号

図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つの信号への代入で済むようにこのような書き方をしています。

 

<戻る       次へ>

 

スポンサーリンク