前置き
「CPUの創りかた」という本があります。いくつかの部品から4ビットCPU「TD4」を作る方法について解説されていますが、現在は入手困難な部品もあり、自分のはんだ付けがあまりにも酷すぎることからなかなか手を出せませんでした。
そう諦めて3年くらい経ち、机の上にあったFPGAボードを見て、「これで作ればいいのでは?」と思いました。
というわけで、4ビットCPU「TD4」をVerilog HDLで実装した話を始めます。
TD4とは?
TD4は、「CPUの創りかた」に設計と解説が載っている4ビットCPUです。TD4は
- レジスタ4個(汎用レジスタ2個、プログラムカウンタ1個、出力用レジスタ1個)
- 演算は整数の足し算のみ
- IN、OUT命令(4ビットの入力、出力)
- 命令はROMから直接フェッチする
- Cフラグによる分岐命令
のような機能を持っています。貧弱すぎるCPUですが、その分作りやすいCPUだと思います。
実装の仕方
1. レジスタ
値を保持しておくためのレジスタです。汎用レジスタ2個(A, B)とプログラムカウンタ、プログラムカウンタ、出力レジスタの部品となるモジュールです。
module REG (
input CLK, //クロック
input LOAD, //値を保持するか計算された値に変更するか
input CNT, //カウント機能
input [3:0] IN, //入力
output reg [3:0] OUT //出力
);
always @(posedge CLK) begin
if (LOAD) // LOADが1であれば
OUT <= IN;
else //LOADが0であれば
if (CNT) //カウント機能が有効になっている場合
if (OUT == 4'b1111)
OUT <= 4'b0000;
else
OUT <= OUT + 4'b0001;
else
OUT <= OUT;
end
endmodule
2. セレクタ
4つの入力(レジスタA、レジスタB、外部入力、0)から一つを選び、ALUに値を渡すためのモジュールです。
module SELECTOR (
input [3:0] REGA, //レジスタAからの信号線
input [3:0] REGB, //レジスタBからの信号線
input [3:0] IN, //外部入力
input [1:0] SEL, //選択のための信号
output reg [3:0] OUT //出力
);
always @* begin
case (SEL)
2'b00: OUT <= REGA;
2'b01: OUT <= REGB;
2'b10: OUT <= IN;
2'b11 : OUT <= 4'b0000;
endcase
end
endmodule
3. ROM
プログラムを格納するためのモジュールです。著者もスイッチで実装していたので、プログラムは直接Verilogで書き込むことにしました。本に載っているサンプルプログラム2と同じです。
module ROM (
input [3:0] ADDR, //アドレス
output reg [7:0] COMMAND //命令の出力
);
always @* begin
case (ADDR)
4'b0000: COMMAND <= 8'b10110111; //OUT 0111
4'b0001: COMMAND <= 8'b00000001; //ADD A, 0001
4'b0010: COMMAND <= 8'b11100001; //JNC 0001
4'b0011: COMMAND <= 8'b00000001; //ADD A, 0001
4'b0100: COMMAND <= 8'b11100011; //JNC 0011
4'b0101: COMMAND <= 8'b10110110; //OUT 0110
4'b0110: COMMAND <= 8'b00000001; //ADD A, 0001
4'b0111: COMMAND <= 8'b11100110; //JNC 0110
4'b1000: COMMAND <= 8'b00000001; //ADD A, 0001
4'b1001: COMMAND <= 8'b11101000; //JNC 1000
4'b1010: COMMAND <= 8'b10110000; //OUT 0000
4'b1011: COMMAND <= 8'b10110100; //OUT 0100
4'b1100: COMMAND <= 8'b00000001; //ADD 0001
4'b1101: COMMAND <= 8'b11101010; //JNC 1010
4'b1110: COMMAND <= 8'b10111000; //OUT 1000
4'b1111: COMMAND <= 8'b11111111; //JMP 1111
endcase
end
endmodule
4. デコーダ
命令をフェッチして命令通りに動くようにするためのモジュールです。
module DECODER(
input [7:0] COMMAND, //命令
input CFLAG, //Cフラグ
output [3:0] IM, //即値
output reg [3:0] LOAD, //レジスタの選択
output reg [1:0] SEL //セレクタへの信号線
);
assign IM = COMMAND[3:0]; //命令の下位4ビットは即値
always @* begin
case (COMMAND[7:4])
4'b0011: begin SEL <= 2'b11; LOAD <= 4'b0001; end //MOV A, Im
4'b0111: begin SEL <= 2'b11; LOAD <= 4'b0010; end //MOV B, Im
4'b0001: begin SEL <= 2'b01; LOAD <= 4'b0001; end //MOV A, B
4'b0100: begin SEL <= 2'b00; LOAD <= 4'b0010; end //MOV B, A
4'b0000: begin SEL <= 2'b00; LOAD <= 4'b0001; end //ADD A, Im
4'b0101: begin SEL <= 2'b01; LOAD <= 4'b0010; end //ADD B, Im
4'b0010: begin SEL <= 2'b10; LOAD <= 4'b0001; end //IN A
4'b0110: begin SEL <= 2'b10; LOAD <= 4'b0010; end //IN B
4'b1011: begin SEL <= 2'b11; LOAD <= 4'b0100; end //OUT Im
4'b1001: begin SEL <= 2'b01; LOAD <= 4'b0100; end //OUT B
4'b1111: begin SEL <= 2'b11; LOAD <= 4'b1000; end //JMP Im
4'b1110: if (!CFLAG) begin SEL <=2'b11; LOAD <= 4'b1000; end else begin SEL <= 2'bxx; LOAD <= 4'b0000; end //JNC Im
endcase
end
endmodule
5. これらを組み合わせる
1~4のモジュールを組み合わせてTD4の完成です。
module TD4(
input CLK, //クロック
input [3:0] IN, //入力
output [3:0] OUT //出力
);
wire [1:0] SEL;
wire [3:0] LOAD, REGA_OUT, REGB_OUT, ADDR, SEL_OUT, IM, ALU_OUT;
wire [7:0] COMMAND;
reg CFLAG_IN;
wire CFLAG_OUT;
REG REG_A(.CLK(CLK), .LOAD(LOAD[0]), .IN(ALU_OUT), .CNT(0), .OUT(REGA_OUT)); //レジスタA
REG REG_B(.CLK(CLK), .LOAD(LOAD[1]), .IN(ALU_OUT), .CNT(0), .OUT(REGB_OUT)); //レジスタB
REG REG_O(.CLK(CLK), .LOAD(LOAD[2]), .IN(ALU_OUT), .CNT(0), .OUT(OUT)); //出力レジスタ
REG REG_P(.CLK(CLK), .LOAD(LOAD[3]), .IN(ALU_OUT), .CNT(1), .OUT(ADDR)); //プログラムカウンタ
SELECTOR SELECTOR (.REGA(REGA_OUT), .REGB(REGB_OUT), .IN(IN), .SEL(SEL), .OUT(SEL_OUT));
ROM ROM(.ADDR(ADDR), .COMMAND(COMMAND));
ALU ALU(.INPUT(SEL_OUT), .IM(IM), .OUTPUT(ALU_OUT), .CFLAG(CFLAG_OUT));
DECODER DECODER(.COMMAND(COMMAND), .CFLAG(CFLAG_IN), .IM(IM), .LOAD(LOAD), .SEL(SEL));
//Cフラグの保持
always @(posedge CLK) begin
CFLAG_IN <= CFLAG_OUT;
end
endmodule
テスト
TD4モジュールだけでは動作できないので、動作させるためのコードを作成してFPGAに書き込みます。クロックが高すぎると動作過程が見えないので、クロックを下げています。DE0-CVを基準として作成しました。
module TOP(
input CLOCK_50,
output [9:0] LEDR,
input [9:0] SW
);
reg [25:0] cnt;
always @(posedge CLOCK_50) begin
if (cnt==26'd4_999_999)
cnt <= 26'b0;
else
cnt <= cnt + 26'b1;
end
TD4 TD4(.CLK(cnt==26'd4_999_999), .IN(SW[3:0]), .OUT(LEDR[3:0]));
endmodule
最後に
Verilogで自分でコードを考えて書いたのはこれが初めてです。授業とか研究で触ったこともないし、本を真似してデジタル時計を作った経験はありますが、それ以外の経験は全くないのでVerilogは初心者レベルです。
僕のような初心者でもTD4は簡単に作れるCPUです。皆さんもFPGAボードさえあれば気軽に挑戦できると思います。
参考文献
渡波 郁『CPUの創りかた』、マイナビ、2003。