FPGAでTD4(4bitCPU)を作ってみた

この記事は最終更新日から3年以上が経過しています。

CPUの創りかた自体は発売されたとき買って読んだんだけどCPU作らずにずっときてしまったので、正月休みを利用してつくってみた。

FPGAやverilog自体に慣れるために

ココらへんをよんでた。
最初はまったくわからなかったが、回路図を表現しているとわかってから結構、読み書きできるようになった。
あと、verilogは回路図に近い表記だけじゃなくて、わりと振る舞いや手続き言語っぽくかけるところもあるので、マルチプレクサなんかはすごく楽にかける。

そもそも自分は組み込みプログラミングの知識があったのピンがどうこうの話は慣れたらとく苦労はしなかった。(設定など面倒くさいが・・・)

あと、テストベンチの書き方がよくわからなかったがある程度わかるようになってからはモジュール毎に書くようになった。
今回さくせいしたCPUもモジュール毎にテストベンチを書いてある。

最初はicarus verilogとGTKWave上でテストベンチでシミュレーション上で動かして、動いたのを確認してから実機上(DE0)でも動かしてみた。
DE0は50MHZだったので動きを追うには分周する必要があったけど分周したらちゃんと動いた。

組んでみて一番苦労したのがデコーダーで、全命令に対して書くので面倒くさかった。テストベンチもふくめて。

TD4はかなり命令の少ないCPUでかつverilogなので、まともな規模で配線とかだと相当面倒くさいことが目に浮かぶ。
あと最初はリセットを付けてなかったので初期化されずCPUがうごかなかった。

verilogは結構わかるようになったんだけど、それでもalwaysやfunctionの使いわけがよくわからなかったりはまだしているのでもっと勉強したい。

あとはもうちょっとまともなCPUつくったり、NESをFPGAでつくってみたいなと思った。

リポジトリはここです。
https://github.com/oskimura/TD4

解説

ALU

TD4のALUは足し算しかない。シンプルなALU。

module alu(ain, bin, c, out);
    input wire [3:0] ain,bin;
    output c;
    output wire [3:0] out;

    assign {c,out} = ain + bin;
endmodule

カウンター

PC(プログラムカウンター)、クロックカウンターにジャンプ命令ようにロードを追加

module counter(reset, in,ld,clk,out);
    input reset;
    input [3:0] in;
    input ld;
    input clk;
    output [3:0] out;

    reg [3:0] cnt=4'b0000;
    always @(posedge clk or negedge reset) begin
        if (!reset) begin 
            cnt<= #1 4'b0000;
        end
        else if(ld==0) begin
            cnt<= #1 in;
        end 
        else begin
            cnt <= #1 cnt+1;
        end
    end
    assign out = cnt;

endmodule

レジスタ

リセットや入力がない限り同じ値を出力し続ける

module register(reset,in,ld,clk,out);
  input reset;
  input [3:0] in;
  input ld;
  input clk;
  output [3:0] out;

  reg [3:0] mem=4'b000;
  always @(posedge clk or negedge reset) begin
    if(!reset) begin
       mem =  #1 4'b0000;
    end else if(!ld)begin
      mem =  #1 in;
    end
  end 
  assign out = mem;

endmodule

ロム

本にのっているラーメンタイマーのプログラムが実装されたROM。
1Hzで16秒カウンタを5回繰り返す

module rom(addr,out);
    input [3:0] addr;
    output reg [7:0] out;

    always @(addr) begin
        case (addr) 
            4'b0000: out=8'b10110111; // out 5

//1           
            4'b0001: out=8'b00000001; // add a 1
            //2
            4'b0010: out=8'b11100001; // jnc 1

//3
            4'b0011: out=8'b00000001; // add a 1
            //4
            4'b0100: out=8'b11100011; // jnc 3

//5
            4'b0101: out=8'b10110110; // out 6
            //6
            4'b0110: out=8'b00000001; // add a 1
            //7
            4'b0111: out=8'b11100110; // jnc 6

//8
            4'b1000: out=8'b00000001; // add a 1
            //9
            4'b1001: out=8'b11101000; // jnc 8

            //10
            4'b1010: out=8'b10110000; // out 0
            //11
            4'b1011: out=8'b10110100; // out 4
            //12
            4'b1100: out=8'b00000001; // add a 1
            //13
            4'b1101: out=8'b11101010; // jnc 10

//14
            4'b1110: out=8'b10111000; // out 8
            //15
            4'b1111: out=8'b11111111; // jmp 15
        endcase
    end

endmodule

デコーダー

アセンブラの命令をひたすら回路出力に変換していく

module decorder(op,c,sel,ld);
    input [3:0] op;
    input c;
    output [1:0] sel;
    output [3:0] ld;

    reg [3:0] load;
    reg [1:0] select;

    always @(op or c) begin      
        case (op) 
            // ADD,A,Im
            4'b0000 : load<=4'b1110; 
            // MOV A,B
            4'b0001: load=4'b1110; 
            // IN A
            4'b0010: load=4'b1110;
            // MOV A,Im
            4'b0011: load=4'b1110;

            // MOV B,A
            4'b0100: load=4'b1101; 
            // MOV B,Im
            4'b0101: load=4'b1101; 
            // IN B
            4'b0110: load=4'b1101; 
            // MOV B,Im
            4'b0111: load=4'b1101; 

            // OUT B
            4'b1001: load=4'b1011; 
            // OUT Im
            4'b1011: load=4'b1011; 

            // JMC
            4'b1110:  if(c==0) begin 
                        // JMC(C=0) 
                            load=4'b0111; 
                        end 
                        else begin
                        // JMC(C=1) 
                            load=4'b1111; 
                        end
            // JMP
            4'b1111: load=4'b0111;
        endcase
    end

    always @(op) begin      
        case (op) 
            // ADD,A,Im
            4'b0000 : select<=2'b00;
            // MOV A,B
            4'b0001: select=2'b01;
            // IN A
            4'b0010:  select=2'b10;
            // MOV A,Im
            4'b0011: select=2'b11;

            // MOV B,A
            4'b0100:  select=2'b00;
            // MOV B,Im
            4'b0101:  select=2'b01;
            // IN B
            4'b0110:  select=2'b10;
            // MOV B,Im
            4'b0111: select=2'b11;

            // OUT B
            4'b1001: select=2'b10;
            // OUT Im
            4'b1011: select=2'b11;

            // JMC
            4'b1110:  if(c==0) begin 
                            // JMC(C=0) 
                            select=2'b11;
                        end 
                        else begin
                            // JMC(C=1) 
                            select=2'bxx;
                        end
            // JMP
            4'b1111: select=2'b11;
    endcase end

    assign ld=load;
    assign sel=select;
endmodule

TD4

これまでのモジュールを繋いだもの。キャリーレジスタは忘れていたので後から追加。

module td4(reset,clk,inp,outp);
    input reset;
    input clk;
    input [3:0] inp;
    output [3:0] outp;

    wire [3:0] ch0,ch1,ch2,ch3;
    wire [3:0] addr;

    wire [3:0] a;

    wire [7:0] memdata;

    wire [3:0] alu_out;
    wire [3:0] ld;

    reg cflag;
    wire cflga_r;

    wire [1:0] sel;

    assign outp = ch2;
    wire [3:0] op,im;

    mem mem_u(.addr(addr),.out(memdata));
    assign ch3=4'b0000;

    register areg(.reset(reset),.in(alu_out),.ld(ld[0]),.clk(clk),.out(ch0));
    register breg(.reset(reset),.in(alu_out),.ld(ld[1]),.clk(clk),.out(ch1));
    register creg(.reset(reset),.in(alu_out),.ld(ld[2]),.clk(clk),.out(outp));
    counter pc(.reset(reset),.in(alu_out),.ld(ld[3]),.clk(clk),.out(addr));

    assign op = memdata[7:4];
    assign im = memdata[3:0];
    dataselector dataselector_u(.sel(sel),.c0(ch0),.c1(ch1),.c2(inp),.c3(ch3),.y(a));

    alu alu_u(.ain(a), .bin(im), .c(cflag_r), .out(alu_out));

    always @(posedge clk  or reset) begin
        if (!reset) begin
            cflag = 1'b0;
        end
        else begin   
            cflag = cflag_r;
        end
    end

    decorder decorder_u(.op(op),.c(cflag),.sel(sel),.ld(ld));


endmodule
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした