CPUの創りかた自体は発売されたとき買って読んだんだけどCPU作らずにずっときてしまったので、正月休みを利用してつくってみた。
ココらへんをよんでた。
最初はまったくわからなかったが、回路図を表現しているとわかってから結構、読み書きできるようになった。
あと、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
````