VOOZH about

URL: https://qiita.com/oskimura/items/83b0173bd11ef773ea84

⇱ FPGAでTD4(4bitCPU)を作ってみた #FPGA - Qiita


👁 Image
27

Go to list of users who liked

24

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 5 years have passed since last update.

@oskimura(きむら おさむ)

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

27
Last updated at Posted at 2017-01-12

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

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

ココらへんをよんでた。
最初はまったくわからなかったが、回路図を表現しているとわかってから結構、読み書きできるようになった。
あと、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
````

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

`````verilog
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
````
27

Go to list of users who liked

24
0

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27

Go to list of users who liked

24