スポンサーリンク

prescaler.v

prescaler.vは分周回路という外部から入力されるクロック信号の周波数を数分の1に下げるための回路です。この回路はCPUにとって必要不可欠なものではなく、外部から入力されるクロック周波数がCPU回路の動作可能な限界速度を超えた場合への対策と、FPGAに内蔵されているメモリ機能を使うために必要なクロック信号を作る目的で作りました。機能としては入力された外部クロック(clk)周波数を16分の1にした信号をCPU回路用のクロック信号(clk_3)として出力し、入力された外部クロック(clk)周波数を4分の1にした信号をメモリ回路用のクロック信号(clk_2)として出力します。

module prescaler(clk, clk_2, clk_3);
	input 		clk;
	output		clk_2;
	output		clk_3;
	reg	[3:0] count;
	
	always @(posedge clk) begin
		if(count == 4'b1111) begin
			count	<= 4'b0000;
		end else begin
			count <= count + 4'b0001;
		end
	end
	
	assign clk_2 = count[1];
	assign clk_3 = count[3];
	
endmodule

 

上記のコードでは”clk”の立ち上がりのタイミングで”count”レジスタが+1ずつカウントアップされていきます。そしてちょうど”count”レジスタが4ビット2進数表現で1111になった時、”count”レジスタを0に戻しています。カウントアップと0クリアの動作が繰り返されるので”count”レジスタはカウンタ回路と同じような動作を実現します。

そしてこの4ビットカウンタの3ビット目と1ビット目の信号を取り出すと、ちょうど”clk”の16分の1と4分の1の信号となります。(4ビットカウンタの最下位ビットを0ビット目、最上位ビットを3ビット目と表します。)

ソースコードの15行目と16行目の角括弧は括弧の中に数値を書き込むことでレジスタ内の特定のビットを抜き出す機能を果たします。例えば”count[1]”は”count”レジスタの1ビット目、”count[3]”は”count”レジスタの3ビット目、”count[0]”は”count”レジスタの0ビット目(最下位ビット)、という意味になります。括弧の中には特定のビット幅を指定することも可能であり、その場合には「:」を用いて次のように記述されます。例えば”count”レジスタの0ビット目から2ビット目までを抜き出す場合の記述は”count[2:0]”となります。

wrapper_memory.v

wrapper_memory.vFPGAが内蔵するメモリ機能を呼び出すコードです。QuartusというFPGA開発用ソフトウェアのメガファンクションという機能を用いてメモリ回路を作成するのですが、具体的な方法については後ほど紹介したいと思います。

module wrapper_memory(clk_2, mem_e, mem_w, mem_address, mem_in, mem_out);
	input		clk_2;
	input 		mem_e;
	input 		mem_w;
	input 	[15:0]	mem_address;
	input	[7:0]	mem_in;
	output	[7:0]	mem_out;
	reg	[15:0]	reg_address;
	reg	[7:0]	reg_in;
	
	
	always @(posedge mem_e or posedge mem_w) begin
		reg_address <= mem_address;
		reg_in <= mem_in;
	end
	
	ram01 ram01_0(.address(reg_address[12:0]), .clock(clk_2), .data(reg_in), 
	.wren(mem_w), .q(mem_out));
	
endmodule

 

スポンサーリンク

ソースコードの17行目は外部のソースコードに記述されている回路を読み込むための記述です。先頭の”ram01”というのが外部のソースコードのmodule文で定義されている回路の名前になります。その後に続く”ram01_0”というのは読み込んだ回路を現在のソースコード上で使用するために付けられる名前です。こちらの名前はmodule文で定義された名前とは関係なく自由に決めてしまって大丈夫です。

“ram01”という名前は私がQuartusのメガファンクションという機能を使ってメモリ回路を生成した時に付けた名前です。このコードをそのまま使用する場合は、後ほど紹介するメガファンクションの生成の時にメモリ回路に付ける名前を“ram01”になるようにしてください。

“ram01_0”の後に続く丸括弧の中には“ram01”の持つ入出力端子名とその端子へ接続する信号名が入ります。ドットで始まる方の名前は“ram01”の持つ入出力端子名を表し、入出力端子が複数存在する場合には端子名の間はコンマで区切られて記述されます。入出力端子名の後ろに続く丸括弧の中にはその端子へ接続する信号の名前が入れられます。例えば17行目の記述の”.wren(mem_w),”の部分が持つ意味は“ram01_0”の持つ4つ目の端子である”wren”に対し、”wrapper_memory”の持つ入力端子である”mem_w”が接続されていることを意味します。

“ram01_0”の持つ1つ目の端子である”address”に対して”wrapper_memory”の持つレジスタである”reg_address[12:0]”が接続されていますが、ここで”reg_address”レジスタの0ビット目から12ビット目までしか”address”端子へ接続されていない理由は、“ram01””address”端子のビットサイズが13ビットしかないためです。

“ram01””address”端子のビットサイズは、使用するFPGAのメモリ容量によって上限が決められてしまいます。一般的なFPGAが持つメモリ容量でも余裕を持って生成できるように、”address”端子のビットサイズは13ビットにしてあります。このコードをそのまま使用する場合は後ほど紹介するメガファンクションの生成の時に、メモリ回路の”address”端子のビットサイズを13ビットになるようにしてください。

cpu.v

cpu.vはこれまで紹介してきた回路を全てつないでCPUという1つの回路として機能させるためのコードです。CPUの設計図と見比べながらコードを読んでいくと分かりやすいと思います。

module cpu(clk, rst, port_1, port_2, port_3, port_4);
	input 		clk;
	input 		rst;
	input 	[7:0] port_3;
	input 	[7:0] port_4;
	output 	[7:0] port_1;
	output 	[7:0] port_2;
	wire 		clk_2, clk_3;
	wire 		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;
	wire 	[3:0] alu_opcode;
	wire	[7:0] wr;
	wire	[7:0] ir1;
	wire	[7:0] ir2;
	wire	[7:0] hr;
	wire	[7:0] p3;
	wire	[7:0] p4;
	wire	[15:0] pc;
	wire	[15:0] pc_inc;
	wire	[7:0] m1;
	wire	[15:0] m2;
	wire	[15:0] m3;
	wire	[7:0] m4;
	wire	[7:0] m5;
	wire	[7:0] mem_out;
	wire	[7:0] alu_out;
	wire 		flag;
	wire	[15:0] address;
	
	prescaler prescaler_0(.clk(clk), .clk_2(clk_2), .clk_3(clk_3));
	
	control_unit control_unit_0(.clk_3(clk_3), .rst(rst), .flag(flag), .ir1(ir1[4:0]), 
	.alu_e(alu_e), .wr_e(wr_e), .ir1_e(ir1_e), .ir2_e(ir2_e), .pc_e(pc_e), .hr_e(hr_e), 
	.mem_e(mem_e), .p1_e(p1_e), .p2_e(p2_e), .p3_e(p3_e), .p4_e(p4_e), .mem_w(mem_w), 
	.m1_s(m1_s), .m2_s(m2_s), .m3_s(m3_s), .m4_s(m4_s), .m5_s(m5_s), 
	.alu_opcode(alu_opcode));
	
	register_8 wr_0(.reg_e(wr_e), .rst(rst), .reg_in(m1), .reg_out(wr));
	
	register_8 ir1_0(.reg_e(ir1_e), .rst(rst), .reg_in(mem_out), .reg_out(ir1));
	
	register_8 ir2_0(.reg_e(ir2_e), .rst(rst), .reg_in(mem_out), .reg_out(ir2));
	
	register_8 hr_0(.reg_e(hr_e), .rst(rst), .reg_in(alu_out), .reg_out(hr));
	
	register_8 p1_0(.reg_e(p1_e), .rst(rst), .reg_in(alu_out), .reg_out(port_1));
	
	register_8 p2_0(.reg_e(p2_e), .rst(rst), .reg_in(alu_out), .reg_out(port_2));
	
	register_8 p3_0(.reg_e(p3_e), .rst(rst), .reg_in(port_3), .reg_out(p3));
	
	register_8 p4_0(.reg_e(p4_e), .rst(rst), .reg_in(port_4), .reg_out(p4));
	
	program_counter pc_0(.pc_e(pc_e), .rst(rst), .pc_in(m2), .pc_out(pc), 
	.pc_inc_out(pc_inc));
	
	multiplexer_8 m1_0(.mux_s(m1_s), .mux_in_a(alu_out), .mux_in_b(ir2), .mux_out(m1));
	
	multiplexer_16 m2_0(.mux_s(m2_s), .mux_in_a(address), .mux_in_b(pc_inc), 
	.mux_out(m2));
	
	multiplexer_16 m3_0(.mux_s(m3_s), .mux_in_a(address), .mux_in_b(pc), .mux_out(m3));
	
	multiplexer_8 m4_0(.mux_s(m4_s), .mux_in_a(alu_out), .mux_in_b(m5), .mux_out(m4));
	
	multiplexer_8 m5_0(.mux_s(m5_s), .mux_in_a(p3), .mux_in_b(p4), .mux_out(m5));
	
	wrapper_memory memory_0(.clk_2(clk_2), .mem_e(mem_e), .mem_w(mem_w), 
	.mem_address(m3), .mem_in(m4), .mem_out(mem_out)); alu alu_0 (.alu_e(alu_e), 
	.rst(rst), .opcode(alu_opcode), .alu_in_a(wr), .alu_in_b(mem_out), 
	.alu_out(alu_out), .flag(flag));

	assign address = {hr, ir2};

	endmodule 

 

<戻る       次へ>

 

スポンサーリンク