Edited at

素人が作る超基本CPU@Verilog w/Vivado


環境

Vivado 2016/4を使用。

ザイリンクスFPGAを買うとライセンスが付いてくる。

無料のRTLコンパイラでもコンパイルできるはず。(Verilogに対応していれば)

例えば下記のソフトは無料。


Altera社model sim

http://www.altera.co.jp/products/software/quartus-ii/modelsim/qts-modelsim-index.html



きっかけ

ずっとCPUってなんか難しそうで敷居が高いなあ、、パソコンってどうやって動いてるんだ、、と思いつつX年。

筆者はアナログ回路はそこそこ分かるがデジタルはさっぱり。


東大のCPU実験で自作コア上の自作OS上で自作シェルを動かした話

http://yamaguchi-1024.hatenablog.com/entry/2018/02/27/172417


を読んでCPU設計できるなんてカッコイイ。。と思って自分もやってみた。

せっかくVivadoが入っているPCもあるし!


マイクロプロセッサの設計と実装

http://www.mtl.t.u-tokyo.ac.jp/~jikken/cpu/wiki/


と思ってもとっかかりが必要なので上記でも触れられていた東大の授業のFPGAで行うCPU設計の内容をなぞってみる。

こんな必修があるなんて東大すごい。


設計目標

とりあえずはMIPS式の足し算しかできないCPUを作るのが目標!!!

足し算とメモリアクセスするだけでも立派なCPUなハズ。

具体的なアルゴリズムは東大演習のSample1をまずは動かす。

Sample1


#0の準備

XOR r0, r0, r0

#1項めの計算

ADDI r8, r0, 1

ADD r9, r0, r8

#2項めの計算(以下繰り返し)

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

ADDI r8, r8, 1

ADD r9, r8, r9

#10項めの計算

ADDI r8, r8, 1

ADD r9, r8, r9

HALT

http://www.mtl.t.u-tokyo.ac.jp/~jikken/cpu/wiki/%E5%85%A5%E5%8A%9B%E7%94%A8%E3%82%A2%E3%82%BB%E3%83%B3%E3%83%96%E3%83%AA%E3%81%AE%E4%BE%8B%E3%81%A8%E6%9B%B8%E3%81%8D%E6%96%B9/


アルゴリズム的には:


1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55


を計算しているだけ。ただ上記を単純に書くと10種の直値を使うことになる(そうしてもいいんだけど・・・)

ここでは一種類の直値(ADDI r8, r0, 1)を使って上記アルゴリズムを実現する。


ちなみにADDIというのは直値加算


r8 = r0 + 1


が計算され、 結果保存レジスタ(r8) = 加算するレジスタ値(r0) + 定数(1)

の形になっている。この辺りは教科書読んだほうが良い。気力があったらMIPS命令の解説をする。。?


命令セット

基本的にはMIPSに従うが東大演習のは割当が本家と微妙に違う。

今回は東大演習に書いてあるものに従って作る。


ISA の仕様

http://www.mtl.t.u-tokyo.ac.jp/~jikken/cpu/wiki/ISA%20%E3%81%AE%E4%BB%95%E6%A7%98/



命令セット形式



これは本家MIPSと同等。

今回使うのはRegとImmediateのみ。


実装するALU命令



Add ImmがADDIに当たる。

Subは使わないけどなんとなく実装してみた。書くだけなら簡単だし。

Funct割当が本家MIPSと違う東大MIPS。まずはフローを捉えてからALU命令は足していくとする。


コンピュータアーキテクチャ

Verilog書く前にその前にCPUアーキテクチャを考える。

といっても素人なもんで教科書通りにしか作れない.


素人CPU回路図



基本的な単一サイクルプロセッサ。でも回路図にするとなんかコンピュータアーキテクチャぽい。

大きくはプログラムカウンタ、レジスタ、ALUと3ブロックを作成し、配線でデコーダを作る。

メリット:分岐や別メモリもないので凄く作りやすい!

デメリット:素人丸出し。動けばいいのだ。

東大演習に当てはめるとstep 4に近い?命令分岐はまだない。





http://www.mtl.t.u-tokyo.ac.jp/~jikken/cpu/wiki/%E9%96%8B%E7%99%BA%E6%89%8B%E9%A0%86/ より引用)


設計

ブロックの設計を示す。初Verilogでドキドキ。。

文法については


Verilog入門

https://qiita.com/thtitech/items/8cc898dda7a10780f495#%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90


が参考になったので共有。本とかは読まなくてもいけた。


 プログラムカウンタとデコーダ

プログラムカウンタ本体はシングルポートレジスタで記述。

そのアドレスをカウンタで指定するようにする。


PC


module PC(
input CLK,
input RST,
output [31:0] INST
);

wire write = 1'b0; // dont write..
wire [31:0] data_in = 32'b0;
wire [31:0] data_out;

assign INST = data_out;
wire [4:0] addr;

// call program counter
COUNTER5B COUNTER(
.CLK(CLK), .addr(addr), .RST(RST)
);
// call program memory
CPU_REG PMEM(
.addr(addr), .data_in(data_in), .data_out(data_out), .write(write));

endmodule

module CPU_REG(
input [4:0] addr, //32-words MIPS like
input [31:0] data_in,
output [31:0] data_out,

input CLK,
input write
);

reg [31:0] PROGRAM[31:0];

initial $readmemb("バイナリファイルの置き場所", PROGRAM);

assign data_out = PROGRAM[addr];
always @(posedge CLK) if(write) PROGRAM[addr] <= data_in;

endmodule



initial $readmemb("バイナリファイルの置き場所", PROGRAM);


ここでバイナリファイルをロードすることで命令を読み込んでいる。

詳しくは


http://www.mtl.t.u-tokyo.ac.jp/~jikken/cpu/wiki/RAM%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9/

のRAMの初期化を参照。



ちなみにカウンタ回路

グローバルリセットじゃないと上手く動かなかった。


counter


module COUNTER5B(
input CLK,
input RST,
output [4:0] addr);

// 2^5 (32-state) counter
reg [4:0] count;
assign addr = count;

always @(posedge CLK) begin
if(RST == 1) begin
count = 5'b0;
end
else begin
count = count + 1;
end
end

endmodule



 レジスタ

2R1Wレジスタを


Verilogでプロセッサを作ろう

https://qiita.com/thtitech/items/78d6ac9ef48d242d873d#instruction-fetch%E3%82%B9%E3%83%86%E3%83%BC%E3%82%B8


を参考に作成。

デジタル設計では自動生成してくれるの有り難い。手で設計するの面倒くさい

参考設計よろしくREGアドレス0を読み出そうとすると(r0)ゼロが読み出されるようにしている。

そうすると何かと有用だ。

書き込みはCLK rise edgeで起きる。


Register


module GPR(
input CLK,
input [4:0] REGNUM0, REGNUM1, REGNUM2,
input [31:0] DIN0,
input WE0,
output [31:0] DOUT0, DOUT1
);

reg [31:0] r[15:0];

assign DOUT0 = (REGNUM0==0) ? 0 : r[REGNUM0];
assign DOUT1 = (REGNUM1==0) ? 0 : r[REGNUM1];
always @(posedge CLK) if (WE0) r[REGNUM2] <= DIN0;

endmodule



 ALU

ALUsrc: 直値を使うかレジスタ値を読み出すか選択。

命令から得る直値は16bしかないため、ALU内で32bに拡張している。

もっと綺麗に書ける気はする


ALU


module ALU(
input [5:0] opcode,
input [31:0] ina,
input [31:0] inb,
input ALUsrc,
input [5:0] funct,
input [31:0] INST,

output [31:0] out, ALUA, ALUB,
output flag

);

// 直値を使うかレジスタ値を使うかALUsrcによってswitch
always @(*) begin
if(ALUsrc) begin
ALUB2 = imm_i;
end
else begin
ALUB2 = inb;
end
end

assign flag = tout[32]; //Overflow Flag
assign out = tout[31:0];

// ALU function
always@(*) begin
if(opcode==6'd0) begin
case(funct)
6'd0: // add
tout = ALUA + ALUB;
6'd2: // sub
tout = ALUA - ALUB;
6'd10: // XOR
tout = ALUA ^ ALUB;
default:
tout = ALUA + ALUB;
endcase
end
else begin //直値の場合
tout = ALUA + ALUB;
end
end
endmodule



テストベンチ

結果:



綺麗に出るね!!

そしてVivadoめちゃ使いやすい。デバッグもやりやすいっす。

FPGAに書いてみたいけど今は手元にない。。



目標であるレジスタ9に55が書き込まれ、正常動作を確認。

やったね。


testbench


module tb_mips_v2();
// CLK
reg clk;
reg RST;

// REG
reg write;

// for INST
//reg [31:0] INST;
wire [31:0] INST;

// monitor
wire flag;
wire [31:0] out, reg_out, ina, inb, ALUA, ALUB;
wire [31:0] DOUT0;
wire [4:0] addr;

// top circuit
top_module_mips_v2 t0(
.CLK(clk), .write(write), .flag(flag), .out(out),
.ina(ina), .inb(inb),.ALUA(ALUA),.ALUB(ALUB), .INST(INST), .addr(addr), .RST(RST)
);

initial begin
clk <= 1'b0;
write <= 1'b1;
RST <= 1'b1;
end

always #5 begin
clk <= ~clk;
end

task wait_posedge_clk;
input n;
integer n;

begin
for(n=n; n>0; n=n-1) begin
@(posedge clk)
;
end
end
endtask

initial begin

wait_posedge_clk(2);
RST <= 1'b0;

wait_posedge_clk(50);

$finish;
end

endmodule


命令セットは東大演習のsimple1.binをそのまま書き込んでます。


Github code

https://github.com/arutema47/mips_cpudesign/tree/developing/v1

tb_mips_v2.vがテストベンチファイル。


 完走した感想

足し算しているだけだけど動作した時はかなり感動した。

CPUに及び腰になってる人は是非設計してみてほしい。

こんなCPUだけど書いてみるとアーキテクチャの基本を理解するのに役立つ。

でもARM CPUも命令セットは基本的にはこれなのでもうちょっと発展させて理解を深めたい。

なんか設計報告書書いてる気分


参考図書

デジタル回路設計とコンピュータ・アーキテクチャ 7章

コンピュータの構成と設計 上


TODO

単サイクルプロセッサ完成バージョンについて記事執筆

パイプラインプロセッサの実装