自作CPU Any% RTA 2:28:17.13

自作CPU Any% RTA

夏休みが9/30に終わるCra2yPierr0tです、こんばんは。タイトル通りです、始めましょう。いや間違えた、はーじまーるよー

speedrun.comを確認しましたが、なぜか自作CPU RTAを走ってる人がいなかったので、今回は私がレギュレーションを決めたいと思います。

ISAを考え始めるところからスタートで、FPGAボードに載ってるLEDで1~10の総和が計算出来てる事が確認できた時点でタイマーストップ 、ということでどうでしょうか。

レギュレーション

ISA検討から自作CPUで1~10の総和を計算まで、確認はオンボードLED

チャート

ところで今回走るチャートの説明がまだでしたね。

  1. ISA策定
  2. アーキテクチャ策定
  3. レジスタ作成
  4. デコーダ作成
  5. コントローラ作成
  6. ALU作成
  7. RAM作成
  8. Quartusプロジェクト作成
  9. CPU作成
  10. ROM作成
  11. CPU, RAM, ROM結合
  12. プログラミング(バイナリ書く方)
  13. ピンアサイ
  14. 合成
  15. プログラミング(FPGAに書き込む方)

以上です、時刻は23時くらい、タイマースタート

経過

経過を写真と説明で報告します

1. ISA策定

大体6分でISAを決定しました。

ISA
命令長は16ビット、命令はmov, add, li, load, storeのみに決めたようです。即値を示すimmediateのスペルが間違っていて既に駄目そうですが応援しましょう。

2. アーキテクチャ策定

約1分でマイクロアーキテクチャを決めました。

アーキテクチャ
死ぬほど焦っていたみたいで10文字でマイクロアーキテクチャの仕様を決めたみたいです。かわいいですね。

3. レジスタ作成

大体5分で書きました。

module regfile(
        input logic       clk,
        input logic [3:0] a_rs1,
        input logic [3:0] a_rs2,
        input logic [3:0] a_rd,
        input logic [15:0] d_rd,
        input logic       we_rd,
        output logic [15:0] d_rs1,
        output logic [15:0] d_rs2
    );

    logic [15:0] mem[0:15];

    assign d_rs1 = mem[a_rs1];
    assign d_rs2 = mem[a_rs2];

    always_ff @(posedge clk) begin
        if(we_rd) begin
            mem[a_rd] = d_rd;
        end
    end
endmodule

特にトリッキーな事はやってないですね。読み出しは組み合わせ回路になっていて、we_rdがhighの場合書き込みが出来る。因みに今後出てくるRTL記述の不可解な点や非効率的な点等は全て死ぬほど焦っている事によるものだと解釈して下さい。

4. デコーダ作成

脳がバグってるのか手がバグってるのか知りませんがデコーダの作成に約6分掛かっています。

module decoder(
        input  logic [15:0] instr,
        output logic [3:0] opcode,
        output logic [3:0] a_rs1,
        output logic [3:0] a_rs2,
        output logic [3:0] a_rd,
        output logic [7:0] immediate
    );

    assign opcode = instr[15:12];
    assign a_rd  = instr[11:8]; 
    assign a_rs1 = instr[7:4];
    assign a_rs2 = instr[3:0];
    assign immediate = instr[7:0];

endmodule

instrを各フィールド毎にバラすだけの組み合わせ回路ですね、これをデコーダと呼んでいいのかは非常に怪しいですがまあいいでしょう。

5. コントローラ作成

約3分で書きました

module controller(
        input logic [3:0] opcode,
        output logic we_ram,
        output logic we_rd
    );

    assign we_ram = (opcode == 4'b0011) ? 1'b1 : 1'b0;
    assign we_rd = (opcode == 4'b0000) ? 1'b1 
                 : (opcode == 4'b0001) ? 1'b1
                 : (opcode == 4'b0010) ? 1'b1 : 1'b0;
endmodule

入力のopcodeの値に応じて、レジスタに書き込む命令ならwe_rdをhighに、ramに書き込む命令ならwe_ramをhighに、それ以外ならlowにする組み合わせ回路ですね。魅せポイントは3項間演算子です。許して。

6. ALU作成

これも大体3分で書きました

module ALU(
        input logic [3:0] opcode,
        input logic [15:0] d_rs1,
        input logic [15:0] d_rs2,
        input logic [7:0] immediate,
        output logic [15:0] d_rd
    );

    always_comb begin
        case(opcode)
            4'b0000: d_rd = d_rs1 + d_rs2;
            4'b0001: d_rd = d_rs1;
            4'b0010: d_rd = {8'h00, immediate};
            default: d_rd = 16'h0000;
        endcase
    end
endmodule

入力のopcodeの値に応じてd_rdに入力する値を変える組み合わせ回路ですね。immediateのスペルが間違ってなくてえらい。ここらへんで1~10の総和を計算するのにload命令をmov命令は要らない事に気が付きました。

7. RAM作成

約3分使いました。今回のレギュレーションだと正直RAMは要らないんですが(そもそもload命令を実装してない)、RAMが無いCPUとか笑えるので渋々書きました。RTAやぞ

module RAM(
        input logic        clk,
        input logic [15:0] d_rs1,
        input logic [15:0] d_rs2,
        input logic        we_ram
    );

    logic [15:0] mem[0:1024];

    always_ff @(posedge clk) begin
        if(we_ram) begin
            mem[d_rs1] = d_rs2;
        end
    end
endmodule

we_ramがhighの場合にd_rs1の値をアドレスにしてd_rs2の値をストアする仕組みですね。

8. Quartusのプロジェクト作成

Quartusの起動とプロジェクト作成に若干手間取って9分使いました。初心者かな? 手元にあるMAX1000を対象にしています。

9. CPU作成

やることはレジスタデコーダとALUとコントローラをまとめるだけです、9分。

module rta_cpu(
        input logic        clk,
        input logic [15:0] instr,
        output logic       we_ram,
        output logic [15:0] d_rs1,
        output logic [15:0] d_rs2,
        output logic [7:0] pc = 8'h00
    );

    logic [3:0] opcode;
    logic [3:0] a_rs1;
    logic [3:0] a_rs2;
    logic [3:0] a_rd;
    logic [7:0] immediate;
    logic [15:0] d_rd;
    logic we_rd;

    always_ff @(posedge clk) begin
        if(pc != 8'h30) begin
            pc <= pc + 8'h1;
        end else begin
            pc <= pc;
        end
    end
    
    decoder deocoder(
            .instr(instr),
            .opcode(opcode),
            .a_rs1(a_rs1),
            .a_rs2(a_rs2),
            .a_rd(a_rd),
            .immediate(immediate)
        );

    controller controller(
            .opcode(opcode),
            .we_ram(we_ram),
            .we_rd(we_rd)
        );

    ALU ALU(
            .opcode(opcode),
            .d_rs1(d_rs1),
            .d_rs2(d_rs2),
            .immediate(immediate),
            .d_rd(d_rd)
        );

    regfile regfile(
            .clk(clk),
            .a_rs1(a_rs1),
            .a_rs2(a_rs2),
            .a_rd(a_rd),
            .d_rd(d_rd),
            .we_rd(we_rd),
            .d_rs1(d_rs1),
            .d_rs2(d_rs2)
        );
endmodule

書き始めたあたりでチャートにプログラムカウンタ(PC)の作成を組み込んでなかった事に気が付きました。再走しろ。とりあえずアドレスが0x30になったあたりで停止するようにしました。分岐命令が無いのでループで停止が作れない。仕方ないね。

10. ROM作成

Quartusではinital文でメモリの初期化をしてくれないのでROMのIPを生成します。Vivadoを見習え。1分。

11. CPU, RAM, ROM結合

ここでLED出力のMMIOのアドレスを決めていなかった事に気が付きました。やる気あんのか。とりあえず0xFFにストアすると書き込んだ値がLEDに出力されるようにした。3.5分掛かった。

module rta_computer(
        input logic clk,
        output logic [7:0] led = 8'h00
    );
    logic [15:0] instr;
    logic [15:0] d_rs1;
    logic [15:0] d_rs2;
    logic [15:0] pc;
    logic we_ram;
    
    rta_cpu rta_cpu(
            .clk(clk),
            .instr(instr),
            .we_ram(we_ram),
            .d_rs1(d_rs1),
            .d_rs2(d_rs2),
            .pc(pc)
        );
    
    always_ff @(clk) begin
        if(we_ram && (d_rs1 == 16'hff)) begin
            led <= d_rs2;
        end else begin
            led <= led;
        end
    end
    RAM RAM(
        .clk(clk),
        .d_rs1(d_rs1),
        .d_rs2(d_rs2),
        .we_ram(we_ram)
    );

    rom rom(
        .address(pc),
        .clock(clk),
        .q(instr)
    );

endmodule
12. プログラミング(バイナリ書く方)

なぐり書きして人力アセンブル、勢いがあっていいですね。

バイナリ

mifファイルに書き込んでIPのROMに読み込ませます。4分位掛かった気がする。

13. ピンアサイ

MAX1000のマニュアルを読んでPin Plannerポチポチ、1分。

14. 合成

一回の合成に54秒掛かりました。

15. プログラミング(FPGAに書き込む方)

合成が完了し、わくわくしながらFPGAに書き込み!

眩しい!!!!

え????なんで???1から10の総和って0xFFなの?????

デバッグ

なんでや シミュレーターだと正常に動いとるやん

デバッグ

ここらへんで日付が変わる、様々頑張る

デバッグ

1時間07分53秒後....

確認

動いた。なんでや、原因全く判明してへんぞ。 ご覧下さい、LEDが00110111と光ってますね、これは10進数で55です。計算出来ていますね。嬉しいですね。嬉しい!!!!

結果

結果は2時間28分17秒13でした。デバッグに1時間割いたうえに原因不明のまま動いたのが酷いですね。最早RTAと言うべきなのか怪しいレベル。いや言うべきではないでしょうね。まあええ、動けばばええんや。(朝1:30)

感想

完走した感想ですがガバガバレギュレーションにガバガバチャート、酷いものですが完動して感動しました。これもう僕が自作CPU RTA世界王者自称していいですよね。世界最速は2:28:17.13で暫定一位です。

一体何処に急いでCPUを作る需要があるのか知りませんが、レギュレーションはもう少し詰めたら楽しめそうです。じゃああなたも走りましょう、

僕も走ったんだからさ。

勧誘

ところで自作CPU研究会はいつでも参加者を募集してます。入りたい方は手段は問わないので僕を含むメンバーの誰かに参加希望の旨を伝えて下さい。

失礼しました。

cpu-dev.github.io