3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

TD4をVerilog HDLで実装した話

前置き

「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。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
3
Help us understand the problem. What are the problem?