2018-09-23
半導体チップを作り放題な夢のデバイス「FPGA」で動かしてみた4bitCPU TD4
せっかくなので手で配線するのは大変な32bitCPUのツインコアに挑戦!
シンプル設計なオレオレCPUを創って、MAX10評価ボード上で、2コア動作させてみました。


90MHzで動作している32bit CPU 2コアの様子。
単にエルチカしているだけですが、毎秒1.8億回計算しています。

オレオレCPUづくりの手順
1. 32bit CPUをつくろう(汎用レジスタが32bit)
2. マシン語は32bit固定長にしよう
3. 命令は、ロード、ストア、代入、加算、減算、or、and、xor、左シフト、右シフト、掛け算の11種類
4. 命令に4bit、レジスタ指定に4bit x 2、16bitを値に使おう
5. レジスタは4bitなので16コ、最後レジスタをArmっぽくプログラムカウンタにしよう
6. 条件判定用のフラグはゼロフラグだけ
7. 残り4bit、Arm32っぽいフラグによるスキップ、フラグ更新するか否か、16bitの値の符号としよう
8. 出力はメモリにマッピングしておこう(RAMは32bit x 32word、最後の31番目を出力に接続)

9. Verilogを勉強する

初めて使うハードウェア記述言語、なんとなくサンプルいじっていてもよくわからないので書籍を購入。
わかるVerilog HDL入門―文法の基礎から論理回路設計、論理合成、実装まで (トランジスタ技術SPECIAL)
購入の決め手は、著書の木村真也さんが群馬高専の先生なこと。
しかも、函館高専の出身でした!

Verilogのコツは、組み合わせ回路と順序回路、シミュレーション用と論理合成用をきちんと区別すること。

組み合わせ回路:wireでassignしたりfunctionでつなぐ(フリップフロップなど、フィードバックなしの回路)
順序回路:alwaysでclk立ち上がりとn_reset立ち下がりでのregの変化をtaskなどで記述

シミュレーション用記述:#10などの遅延、initialはシミュレーション用(initialでも合成されてたけど??)

10. Verilogで実装する(名付けて、TF32CPU r1 - Taisuke Fukuno's 32 bit CPU release 1)

module tf32cpu_r1( input clk, input n_reset, output [3:0] outport); reg [31:0] r[0:15]; // register reg [31:0] mem[0:31]; // 32 word reg zeroflg; wire [31:0] code; wire skipflgnz; wire skipflgz; wire flgchg; wire minusim; wire [3:0] op; // operation code wire [3:0] rega; wire [3:0] regb; wire [31:0] opland; wire [15:0] im; // immediate data assign code = mem[r[15]]; assign skipflgnz = code[31]; assign skipflgz = code[30]; assign flgchg = code[29]; assign minusim = code[28]; assign op = code[27:24]; assign rega = code[23:20]; assign regb = code[19:16]; assign im = code[15:0]; assign opland = r[regb] + (minusim ? -im : im); always @(posedge clk, negedge n_reset) begin if (!n_reset) begin // program init_blink; init_reg; end else begin if (skipflgnz && !zeroflg || skipflgz && zeroflg) r[15] <= r[15] + 1; else begin case (op) 4'h0: r[rega] <= mem[opland]; 4'h1: mem[opland] <= r[rega]; 4'h2: r[rega] <= opland; 4'h3: r[rega] <= r[rega] + opland; 4'h4: r[rega] <= r[rega] - opland; 4'h5: r[rega] <= r[rega] | opland; 4'h6: r[rega] <= r[rega] & opland; 4'h7: r[rega] <= r[rega] ^ opland; 4'h8: r[rega] <= r[rega] << opland; 4'h9: r[rega] <= r[rega] >> opland; 4'hA: r[rega] <= r[rega] * opland; endcase if (flgchg) zeroflg <= r[rega] == 0; if (rega != 15) r[15] <= r[15] + 1; end end end assign outport = mem[31][3:0]; endmodule

- Verilogの特徴
短く書ける!(32bit CPUが、64行)
moduleが回路のかたまりを表す。(inputやoutputでインターフェイスを定義)
ブロックの表記方法は、begin / end({ }はbitの括りを表すのに使っちゃっている)
a[3:0]で、aのbit3からbit0までの4bitを表す。(何もつけないと1bit)
bitの長さを意識することは大事だけど、割といい感じにキャストされる
posedgeが立ち上がった時、negedgeが立ち下がった時
<= が、順序回路で次のタイミングへの切り替えを表す(同じタイミングで同じregに入れるとエラー)

11. レジスタの初期化とプログラムを task でメモリにセットするコードを追記

task init_blink; // for slowclock begin mem[ 0] <= 32'h0_2_3_0_0001; // R3=1 mem[ 1] <= 32'h0_1_3_0_001F; // [31]=R3 mem[ 2] <= 32'h0_2_3_0_0002; // R3=2 mem[ 3] <= 32'h0_1_3_0_001F; // [31]=R3 mem[ 4] <= 32'h0_2_F_0_0000; // PC=0 mem[31] <= 32'h00000000; // outport end endtask task init_reg; begin r[ 0] <= 32'h0; r[ 1] <= 32'h0; r[ 2] <= 32'h0; r[ 3] <= 32'h0; r[ 4] <= 32'h0; r[ 5] <= 32'h0; r[ 6] <= 32'h0; r[ 6] <= 32'h0; r[ 7] <= 32'h0; r[ 8] <= 32'h0; r[ 9] <= 32'h0; r[10] <= 32'h0; r[11] <= 32'h0; r[12] <= 32'h0; r[13] <= 32'h0; r[14] <= 32'h0; r[15] <= 32'h0; zeroflg <= 1'b0; end endtask

12. クロックやリセットとつないでオレオレCPUが動いた!
(MAX10評価ボードの場合、クロック clk=27、LED outport=132,134,135,140,141)

module tf32cpu( input clk, output [4:0] outport); wire cpuclk; assign cpuclk = clk; wire n_reset; poweronreset por(cpuclk, n_reset); reg [3:0] exout; tf32cpu_r1 cpu1(cpuclk, n_reset, exout); assign outport = { cpuclk, ~exout }; endmodule

poweonreset.v (起動後に少し待って n_reset を0にする回路)

module poweronreset( input clk, output n_reset); reg [7:0] counter; always @(posedge clk) begin if (counter != 8'b11111111) counter <= counter + 8'b1; end assign n_reset = counter == 8'b11111111; endmodule

13. ALTPLL(50MHzを9逓倍5分周)でクロックアップ!(90MHzが限界でした、それ以上だと沈黙)

module tf32cpu( input clk, output [4:0] outport); wire cpuclk; pll pll1(clk, cpuclk); // 略 endmodule

14. CPUのインスタンスを足して、ゆっくりCPUとの2コア(ツインCPU!)構成にしてみる

module tf32cpu( input clk, output [4:0] outport); wire cpuclk; pll pll1(clk, cpuclk); wire n_reset; poweronreset por(cpuclk, n_reset); reg [3:0] exout; tf32cpu_r1 cpu1(cpuclk, n_reset, exout); reg [3:0] exout2; wire slowclk; clockdivider cdiv2(cpuclk, n_reset, 2, slowclk); tf32cpu_r1 cpu2(slowclk, n_reset, exout2); assign outport = { slowclk, ~(exout | (exout2[1:0] << 2)) }; endmodule

クロックを遅くする clockdivider.v

`define LEN_CLOCK 50_000_000 module clockdivider( input clk, input n_reset, input [3:0] div, output reg outclk); reg [31:0] counter; always @(posedge clk, negedge n_reset) begin if (!n_reset) begin outclk <= 0; counter <= 0; end else if (counter == (`LEN_CLOCK >> div) - 1) begin counter <= 0; outclk <= ~outclk; end else counter <= counter + 1'b1; end endmodule

15. ツインCPUで動いた!
論理合成に2分半かかり、MAX10-08のLE使用率87%なのでトリプルにするにはきっとROM/RAMの切り離しが必要。(1コアだと合成1分14秒、46%)
* ツインCPUという懐かしい変な言葉。CPUという部品はなく、あるコンピューターの中心的役割を果たすコンピューターをCPU(中央処理装置 / Central Processing Unit)と呼ぶ。なので、正しくはツインコアのCPU。

16. マシン語プログラムを書き換える

mem[ 0] <= 32'h0_2_1_0_2FAF; // R1=#2FAF (50M=2FAF080) mem[ 1] <= 32'h0_8_1_0_0009; // R1=R1<<9 // mem[ 0] <= 32'h0_2_1_0_0003; // R1=3 for slowclock // mem[ 1] <= 32'h0_2_1_0_0003; // R1=3 mem[ 2] <= 32'h0_3_3_0_0001; // R3=R3+1 mem[ 3] <= 32'h0_1_3_0_001F; // [31]=R3 // LED1 mem[ 4] <= 32'h0_2_2_1_0000; // R2=R1 mem[ 5] <= 32'h2_4_2_0_0001; // R2=R2-1 flg mem[ 6] <= 32'h4_4_F_0_0001; // if !Z PC=PC-1 mem[ 7] <= 32'h0_1_4_0_001F; // [31]=R0 // LED0 mem[ 8] <= 32'h0_2_2_1_0000; // R2=R1 mem[ 9] <= 32'h2_4_2_0_0001; // R2=R2-1 flg mem[10] <= 32'h4_4_F_0_0001; // if !Z PC=PC-1 mem[11] <= 32'h0_2_F_0_0002; // PC=2

4つのLEDを2進数でカウントアップするデモプログラム(マシン語 for TF32CPU r1)

links
- 憧れのCPUづくりが超簡単!あの4bitコンピューターTD4マシン語プログラム on FPGA MAX10
- FPGAはじめのいっぽ、MAX10 FPGA 評価キットでエルチカ成功!
- 最大動作周波数468MHz!? 書籍の付録とUSB BlasterではじめるFPGA「MAX10-FB」の組み立て方

Tweet
クリエイティブ・コモンズ・ライセンス
本ブログの記事や写真は「Creative Commons — CC BY 4.0」の下に提供します。記事内で紹介するプログラムや作品は、それぞれに記載されたライセンスを参照ください。
CC BY / @taisukef / アイコン画像 / プロフィール画像 / 「一日一創」画像 / RSS