LoginSignup
0
0

RISC-VをHDLで手書きしてLチカ(PYNQ-Z2シリーズ)

Posted at

初めに

著者はCPUの自作が初めてなので嘘を書いているかもしれません.RISC-Vに用意されている命令をすべて実装したわけではありません.
また,実装したすべての命令について動作確認を行ったわけではないため,動作は保証しません.
もし間違いがありましたら指摘してくださると幸いです.

ハードとソフト

  • ハード
    • PYNQ-Z2
  • ソフト
    • Vivado v2022.2

visc vとは

ざっっっっくり言うとCPUの一種です.
OSSです.
ISAが何種類かあって,公開されています.

rv32iとは

RISC-VのISAのひとつ.もっとも単純なものです.
機械語は32ビットです.

全体の構成

回路構成をクラス図っぽく書くとこんな感じです.
romにプログラムが保存され,pc(プログラムカウンタ)に応じた機械語を出力します.
decorderは機械語を翻訳してオペコードやイミディエイトデータなどを取り出します.
aluでは受け取ったオペコードやイミディエイトデータなどから処理内容を判断して,自身が持つレジスタから値を読み取ったり書き込んだりします.

コード

ブロックデザインとファイル構成は以下のようになっています.
なおarはデバッグ用にpcを出力しているだけです.

image.png

image.png

System Verilog Header

データ型とかインターフェースとかいろいろ定義.
ちなみにインターフェースは名前空間内に定義できないそうです.私はこれ知らなくてしばらくハマりました.

register.svh

レジスタとプログラムカウンタに関するデータ型や定数の定義をしてます.
RISC-Vではレジスタが32個用意されますので,レジスタのアドレスは5ビットになります.
また,符号付き変数の大小を比較するために最上位ビットに1を足した結果を返す関数を定義しています.

`ifndef REGISTER_SVH
`define REGISTER_SVH

package register;
    // レジスタのアドレス
    typedef logic[4:0] addr_t;
    // レジスタのデータ型
    typedef logic[31:0] register_t;

    // プログラムカウンタのデータ型
    typedef logic[31:0] pc_t;

    // 初期化する値
    localparam register_t REGISTER_ZERO = 32'b0;

    // プログラムカウンタのリセット値
    localparam pc_t PC_ZERO = 32'b0;
    // 一度にインクリメントされるプログラムカウンタのサイズ
    localparam pc_t PC_RUG = 32'd4;

    // 符号付き比較のため,最上位ビットに1を足す
    function register_t remove_sign(
        input register_t in
    );
        remove_sign = 32'h8000_0000 + in;
    endfunction
endpackage

`endif

led.svh

Lチカに関する定義.
Lチカのために使用する変数値を保存するレジスタのアドレスを定義しています.

`ifndef LED_SVH
`define LED_SVH

`include "register.svh"

// Lチカのための定義
package led;
    // 出力するLEDの値を保存するアドレス
    localparam register::addr_t LED_ADDR = 5'h1f;
    // Lチカの周期を保存するアドレス
    localparam register::addr_t CYCLE_ADDR = 5'h01;
    // 現在のカウントを保存するアドレス
    localparam register::addr_t CNT_ADDR = 5'h02;
endpackage

`endif

decorde.svh

オペコードやイミディエイトデータなどのデータ型や,取り出したデータを伝達するためのインターフェースを定義しています.
また,符号付きイミディエイトデータを作成するために2の補数を取得する関数を定義しています.
RISC-Vにはfunct3funct7というデータがあり,命令によってはオペコードとこれらの値で動作が決定します.なおRISC-VにはR typeなど命令がタイプ分けされていますが,回路を作成するうえでは重要な知識ではないのでcommand_type_enumとかいらなかったかも知れないです.あった方がソースは分かりやすくなると思いますが.

`ifndef DECORDE_SVH
`define DECORDE_SVH

`include "register.svh"

package decorde;
    // 機械語のタイプ
    typedef logic[2:0] command_type_t;

    typedef logic[6:0] opcode_t;
    typedef logic[2:0] funct3_t;
    typedef logic[6:0] funct7_t;

    // 機械語のタイプの列挙
    typedef enum command_type_t {
        NONE,
        R_TYPE,
        I_TYPE,
        S_TYPE,
        B_TYPE,
        U_TYPE,
        J_TYPE
    } command_type_enum;

    // オペコードから機械語のタイプを返す
    function command_type_t get_command_type(
        input logic[6:0] opcode
    );
        unique case (opcode)
            7'b0110011: get_command_type = R_TYPE;
            7'b1100111: get_command_type = I_TYPE;
            7'b0000011: get_command_type = I_TYPE;
            7'b0010011: get_command_type = I_TYPE;
            7'b0100011: get_command_type = S_TYPE;
            7'b1100011: get_command_type = B_TYPE;
            7'b0110111: get_command_type = U_TYPE;
            7'b0010111: get_command_type = U_TYPE;
            7'b1101111: get_command_type = J_TYPE;
            default:    get_command_type = NONE;
        endcase
    endfunction

    // 2の補数を取得
    function register::register_t get_complement(
        input register::register_t in
    );
        get_complement = ~in + 1;
    endfunction
endpackage

// 実行形式コマンドのインターフェース
interface command_if;
    decorde::opcode_t opcode;
    register::addr_t rs1;
    register::addr_t rs2;
    register::addr_t rd;
    decorde::funct3_t funct3;
    decorde::funct7_t funct7;
    register::register_t imm_raw;
    register::register_t imm_signed;
    decorde::command_type_t command_type;

    // 機械語をデコードして送る側
    modport master(
        output opcode,
        output rs1, output rs2, output rd,
        output funct3, output funct7,
        output imm_raw, output imm_signed,
        output command_type
    );
    // デコードされた機械語を受け取る側
    modport slave(
        input opcode,
        input rs1, input rs2, input rd,
        input funct3, input funct7,
        input imm_raw, input imm_signed,
        input command_type
    );
endinterface

`endif

machine.svh

レジスタのアドレスなどから生の機械語を作成する関数.
romモジュールで使用します.
実際に使用したのはこの中のいくつかだけですがたくさんの命令について関数を作りましたので結構長いです.

`ifndef MACHINE_SVH
`define MACHINE_SVH

`include "decorde.svh"
`include "register.svh"

// 命令と変数から機械語を生成する
package machine;
    // 生の機械語
    typedef logic[31:0] machine_t;

    // R type
    // rs1とrs2で指定したアドレスから値を二つ取り出し,演算結果をrdで指定したアドレスに格納する
    function machine_t ADD(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        ADD = {7'b000_0000, rs2, rs1, 3'b000, rd, 7'b0110011};
    endfunction
    function machine_t SUB(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        SUB = {7'b010_0000, rs2, rs1, 3'b000, rd, 7'b0110011};
    endfunction
    function machine_t SLL(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        SLL = {7'b000_0000, rs2, rs1, 3'b001, rd, 7'b0110011};
    endfunction
    function machine_t SLT(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        SLT = {7'b000_0000, rs2, rs1, 3'b010, rd, 7'b0110011};
    endfunction
    function machine_t SLTU(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        SLTU = {7'b000_0000, rs2, rs1, 3'b011, rd, 7'b0110011};
    endfunction
    function machine_t XOR(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        XOR = {7'b000_0000, rs2, rs1, 3'b100, rd, 7'b0110011};
    endfunction
    function machine_t SRL(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        SRL = {7'b000_0000, rs2, rs1, 3'b101, rd, 7'b0110011};
    endfunction
    function machine_t SRA(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        SRA = {7'b010_0000, rs2, rs1, 3'b101, rd, 7'b0110011};
    endfunction
    function machine_t OR(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        OR = {7'b000_0000, rs2, rs1, 3'b110, rd, 7'b0110011};
    endfunction
    function machine_t AND(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::addr_t rd
    );
        AND = {7'b000_0000, rs2, rs1, 3'b111, rd, 7'b0110011};
    endfunction

    // I type
    // rs1で指定したアドレスの値とイミディエイトデータからの演算結果をrdで指定したアドレスに格納する
    function machine_t JALR(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        JALR = {imm[11:0], rs1, 3'b000, rd, 7'b1100111};
    endfunction
    function machine_t LB(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        LB = {imm[11:0], rs1, 3'b000, rd, 7'b0000011};
    endfunction
    function machine_t LH(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        LH = {imm[11:0], rs1, 3'b001, rd, 7'b0000011};
    endfunction
    function machine_t LW(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        LW = {imm[11:0], rs1, 3'b010, rd, 7'b0000011};
    endfunction
    function machine_t LBU(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        LBU = {imm[11:0], rs1, 3'b100, rd, 7'b0000011};
    endfunction
    function machine_t LHU(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        LHU = {imm[11:0], rs1, 3'b101, rd, 7'b0000011};
    endfunction
    function machine_t ADDI(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        ADDI = {imm[11:0], rs1, 3'b000, rd, 7'b0010011};
    endfunction
    function machine_t SLTI(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        SLTI = {imm[11:0], rs1, 3'b010, rd, 7'b0010011};
    endfunction
    function machine_t SLTIU(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        SLTIU = {imm[11:0], rs1, 3'b011, rd, 7'b0010011};
    endfunction
    function machine_t XORI(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        XORI = {imm[11:0], rs1, 3'b100, rd, 7'b0010011};
    endfunction
    function machine_t ORI(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        ORI = {imm[11:0], rs1, 3'b110, rd, 7'b0010011};
    endfunction
    function machine_t ANDI(
        input register::addr_t rs1,
        input register::addr_t rd,
        input register::register_t imm
    );
        ANDI = {imm[11:0], rs1, 3'b111, rd, 7'b0010011};
    endfunction

    // S type
    // 使ってない.メモリに関する命令.
    function machine_t SB(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        SB = {imm[11:5], rs2, rs1, 3'b000, imm[4:0], 7'b0100011};
    endfunction
    function machine_t SH(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        SH = {imm[11:5], rs2, rs1, 3'b001, imm[4:0], 7'b0100011};
    endfunction
    function machine_t SW(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        SW = {imm[11:5], rs2, rs1, 3'b010, imm[4:0], 7'b0100011};
    endfunction

    // B type
    // rs1とrs2の値を色んな手段で比較して,その結果で次のプログラムカウンタが決まる
    function machine_t BEQ(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        BEQ = {imm[12], imm[10:5], rs2, rs1, 3'b000, imm[4:1], imm[11], 7'b1100011};
    endfunction
    function machine_t BNE(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        BNE = {imm[12], imm[10:5], rs2, rs1, 3'b001, imm[4:1], imm[11], 7'b1100011};
    endfunction
    function machine_t BLT(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        BLT = {imm[12], imm[10:5], rs2, rs1, 3'b100, imm[4:1], imm[11], 7'b1100011};
    endfunction
    function machine_t BGE(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        BGE = {imm[12], imm[10:5], rs2, rs1, 3'b101, imm[4:1], imm[11], 7'b1100011};
    endfunction
    function machine_t BLTU(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        BLTU = {imm[12], imm[10:5], rs2, rs1, 3'b110, imm[4:1], imm[11], 7'b1100011};
    endfunction
    function machine_t BGEU(
        input register::addr_t rs1,
        input register::addr_t rs2,
        input register::register_t imm
    );
        BGEU = {imm[12], imm[10:5], rs2, rs1, 3'b111, imm[4:1], imm[11], 7'b1100011};
    endfunction

    // U type
    // rdに,immで指定した値を代入する.
    function machine_t LUI(
        input register::addr_t rd,
        input register::register_t imm
    );
        LUI = {imm[31:12], rd, 7'b0110111};
    endfunction
    function machine_t AUIPC(
        input register::addr_t rd,
        input register::register_t imm
    );
        AUIPC = {imm[31:12], rd, 7'b0010111};
    endfunction

    // J type
    // ジャンプ命令.
    function machine_t JAL(
        input register::addr_t rd,
        input register::register_t imm
    );
        JAL = {imm[20], imm[10:1], imm[11], imm[19:12], rd, 7'b1101111};
    endfunction
endpackage

`endif

System Verilog

CPUの動作となる部分.

cpu_sv.sv

デコーダーとaluを繋げてます.

`include "decorde.svh"
`include "register.svh"
`include "machine.svh"

module cpu_sv(
    input clk,
    input resetn,

    output register::pc_t pc,
    input machine::machine_t machine,
    output [3:0] led
    );

    // インターフェースの定義
    command_if command();

    // モジュールの宣言
    decorder decorder (
        .clk(clk), .resetn(resetn),
        .machine(machine), .command(command)
    );
    alu alu (
        .clk(clk), .resetn(resetn),
        .command(command),
        .pc(pc), .led(led)
    );

endmodule

decorder.sv

生の機械語からオペコードやイミディエイトデータなどを取り出します.

`include "decorde.svh"
`include "machine.svh"

module decorder(
    input clk,
    input resetn,

    input machine::machine_t machine,
    command_if.master command
    );

    always_comb begin
        command.opcode = machine[6:0];
        command.command_type = decorde::get_command_type(machine[6:0]);
        unique case (decorde::get_command_type(machine[6:0]))
            decorde::R_TYPE: begin
                command.rs1        = machine[19:15];
                command.rs2        = machine[24:20];
                command.rd         = machine[11:7];
                command.funct3     = machine[14:12];
                command.funct7     = machine[31:25];
                command.imm_raw    = 32'b0;
                command.imm_signed = 32'b0;
            end

            decorde::I_TYPE: begin
                command.rs1        = machine[19:15];
                command.rs2        = 5'b0;
                command.rd         = machine[11:7];
                command.funct3     = machine[14:12];
                command.funct7     = 7'b0;
                command.imm_raw    = {20'b0, machine[31:20]};
                if (machine[31]) begin
                    command.imm_signed = {20'hfffff, machine[31:20]};
                end else begin
                    command.imm_signed = {20'b0, machine[31:20]};
                end
            end

            decorde::S_TYPE: begin
                command.rs1        = machine[19:15];
                command.rs2        = machine[24:20];
                command.rd         = 5'b0;
                command.funct3     = machine[14:12];
                command.funct7     = 7'b0;
                command.imm_raw    = {20'b0, machine[31:25], machine[11:7]};
                if (machine[31]) begin
                    command.imm_signed = {20'hfffff, machine[31:25], machine[11:7]};
                end else begin
                    command.imm_signed = {20'b0, machine[31:25], machine[11:7]};
                end
            end

            decorde::B_TYPE: begin
                command.rs1        = machine[19:15];
                command.rs2        = machine[24:20];
                command.rd         = 5'b0;
                command.funct3     = machine[14:12];
                command.funct7     = 7'b0;
                command.imm_raw    = {19'b0, machine[31], machine[7], machine[30:25], machine[11:8], 1'b0};
                if (machine[31]) begin
                    command.imm_signed = {19'h7ffff, machine[31], machine[7], machine[30:25], machine[11:8], 1'b0};
                end else begin
                    command.imm_signed = {19'b0, machine[31], machine[7], machine[30:25], machine[11:8], 1'b0};
                end
            end

            decorde::U_TYPE: begin
                command.rs1        = 5'b0;
                command.rs2        = 5'b0;
                command.rd         = machine[11:7];
                command.funct3     = 3'b0;
                command.funct7     = 7'b0;
                command.imm_raw    = {machine[31:12], 12'b0};
                command.imm_signed = {machine[31:12], 12'b0};
            end

            decorde::J_TYPE: begin
                command.rs1        = 5'b0;
                command.rs2        = 5'b0;
                command.rd         = machine[11:7];
                command.funct3     = 3'b0;
                command.funct7     = 7'b0;
                command.imm_raw    = {11'b0, machine[31], machine[19:12], machine[20], machine[30:21], 1'b0};
                if (machine[31]) begin
                    command.imm_signed = {11'h7ff, machine[31], machine[19:12], machine[20], machine[30:21], 1'b0};
                end else begin
                    command.imm_signed = {11'b0, machine[31], machine[19:12], machine[20], machine[30:21], 1'b0};
                end
            end

            default: begin
                command.rs1        = 5'b0;
                command.rs2        = 5'b0;
                command.rd         = 5'b0;
                command.funct3     = 3'b0;
                command.funct7     = 7'b0;
                command.imm_raw    = 32'b0;
                command.imm_signed = 32'b0;
            end
        endcase
    end

endmodule

alu.sv

decorder.svから受け取ったデータで指定された命令を実行します.
なお,rではアドレス0の値は0固定なので,こちらでもrdが0ならレジスタには0を入れるようにしています.

`include "decorde.svh"
`include "led.svh"

module alu(
    input clk,
    input resetn,

    command_if.slave command,

    output register::pc_t pc,
    output [3:0] led
    );

    // 内部レジスタ
    register::register_t [31:0] x = {
        register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,
        register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,
        register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,
        register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,
        register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,register::REGISTER_ZERO,
        register::REGISTER_ZERO,register::REGISTER_ZERO
    };

    always_ff @(posedge clk) begin
        if (!resetn) begin
            pc <= register::PC_ZERO;

            for (logic [5:0] i = 0; i < 32; i++) begin
                x[i] <= register::REGISTER_ZERO;
            end
        end else begin
            unique case (command.command_type)
                decorde::R_TYPE: begin
                    pc <= pc + register::PC_RUG;
                    unique case (command.opcode)
                        7'b0110011: begin
                            unique case (command.funct3)
                                3'b000: begin
                                    // ADD
                                    if (command.funct7 == 7'b0) begin
                                        x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] + x[command.rs2];
                                    end
                                    // SUB
                                    else if (command.funct7 == 7'b0100000) begin
                                        x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] - x[command.rs2];
                                    end
                                    // fail
                                    else begin
                                        ;
                                    end
                                end

                                3'b001: begin
                                    // SLL
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] << x[command.rs2];
                                end

                                3'b010: begin
                                    // SLT
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : register::remove_sign(x[command.rs1]) < register::remove_sign(x[command.rs2]) ? 32'b1 : 32'b0;
                                end

                                3'b011: begin
                                    // SLTU
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] < x[command.rs2] ? 32'b1 : 32'b0;
                                end

                                3'b100: begin
                                    // XOR
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] ^ x[command.rs2];
                                end

                                3'b101: begin
                                    // SRL
                                    if (command.funct7 == 7'b0) begin
                                        x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] >> x[command.rs2];
                                    end
                                    // SRA
                                    else if (command.funct7 == 7'b0100000) begin
                                        if (x[command.rs1][31] && command.rd != 5'b0) begin
                                             for (logic [5:0] i = 0; i < 31; i++) begin
                                                 if (i < x[command.rs2]) begin
                                                     x[command.rd][31 - i - 1] <= 1'b1;
                                                 end else begin
                                                     x[command.rd][31 - i - 1] <= x[command.rs1][31 - i - x[command.rs2] + 1];
                                                 end
                                             end
                                             x[command.rd][31] <= 1'b1;
                                        end else begin
                                            x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] >> x[command.rs2];
                                        end
                                    end
                                    // fail
                                    else begin
                                        ;
                                    end
                                end

                                3'b110: begin
                                    // OR
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] | x[command.rs2];
                                end

                                3'b111: begin
                                    // AND
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] & x[command.rs2];
                                end

                                default: begin
                                    // fail
                                    ;
                                end
                            endcase
                        end

                        default: begin
                            // fail
                            ;
                        end
                    endcase
                end

                decorde::I_TYPE: begin
                    unique case (command.opcode)
                        7'b1100111: begin
                            // JALR
                            x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : register::REGISTER_ZERO;
                            pc <= x[command.rs1] + command.imm_signed;
                        end

                        7'b0000011: begin
                            pc <= pc + register::PC_RUG;
                            unique case (command.funct3)
                                3'b000: begin
                                    // LB
                                    if (command.rd != 5'b0) begin
                                        x[command.rd][7:0] <= x[command.rs1][7:0];
                                        for (logic [5:0] i = 31; i > 7; i--) begin
                                            x[command.rd][i] <= x[command.rs1][7];
                                        end
                                    end else begin
                                        x[command.rd] <= register::REGISTER_ZERO;
                                    end
                                end

                                3'b001: begin
                                    // LH
                                    if (command.rd != 5'b0) begin
                                        x[command.rd][15:0] <= x[command.rs1][15:0];
                                        for (logic [5:0] i = 31; i > 15; i--) begin
                                            x[command.rd][i] <= x[command.rs1][15];
                                        end
                                    end else begin
                                        x[command.rd] <= register::REGISTER_ZERO;
                                    end
                                end

                                3'b010: begin
                                    // LW
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1];
                                end

                                3'b100: begin
                                    // LBU
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : {24'b0, x[command.rs1][7:0]};
                                end

                                3'b101: begin
                                    // LHU
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : {16'b0, x[command.rs1][15:0]};
                                end

                                default: begin
                                    // fail
                                    ;
                                end
                            endcase
                        end

                        7'b0010011: begin
                            pc <= pc + register::PC_RUG;
                            unique case (command.funct3)
                                3'b000: begin
                                    // ADDI
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] + command.imm_signed;
                                end

                                3'b010: begin
                                    // SLTI
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : register::remove_sign(x[command.rs1]) < register::remove_sign(command.imm_signed) ? 32'b1 : 32'b0;
                                end

                                3'b011: begin
                                    // SLTIU
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] < command.imm_signed ? 32'b1 : 32'b0;
                                end

                                3'b100: begin
                                    // XORI
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] ^ command.imm_signed;
                                end

                                3'b110: begin
                                    // ORI
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] | command.imm_signed;
                                end

                                3'b111: begin
                                    // ANDI
                                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : x[command.rs1] & command.imm_signed;
                                end

                                default: begin
                                    // fail
                                    ;
                                end
                            endcase
                        end

                        7'b1110011: begin
                            // ?
                            ;
                        end

                        default: begin
                            // fail
                            ;
                        end
                    endcase
                end

                decorde::S_TYPE: begin
                    // メモリに関する命令なのでオミット
                    ;
                end

                decorde::B_TYPE: begin
                    x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : register::REGISTER_ZERO;
                    unique case (command.opcode)
                        7'b1100011: begin
                            unique case(command.funct3)
                                3'b000: begin
                                    // BEQ
                                    if (x[command.rs1] == x[command.rs2]) begin
                                        pc <= pc + command.imm_signed;
                                    end else begin
                                        pc <= pc + register::PC_RUG;
                                    end
                                end

                                3'b001: begin
                                    // BNE
                                    if (x[command.rs1] != x[command.rs2]) begin
                                        pc <= pc + command.imm_signed;
                                    end else begin
                                        pc <= pc + register::PC_RUG;
                                    end
                                end

                                3'b100: begin
                                    // BLT
                                    if (register::remove_sign(x[command.rs1]) < register::remove_sign(x[command.rs2])) begin
                                        pc <= pc + command.imm_signed;
                                    end else begin
                                        pc <= pc + register::PC_RUG;
                                    end
                                end

                                3'b101: begin
                                    // BGE
                                    if (register::remove_sign(x[command.rs1]) >= register::remove_sign(x[command.rs2])) begin
                                        pc <= pc + command.imm_signed;
                                    end else begin
                                        pc <= pc + register::PC_RUG;
                                    end
                                end

                                3'b110: begin
                                    // BLTU
                                    if (x[command.rs1] < x[command.rs2]) begin
                                        pc <= pc + command.imm_signed;
                                    end else begin
                                        pc <= pc + register::PC_RUG;
                                    end
                                end

                                3'b111: begin
                                    // BGEU
                                    if (x[command.rs1] >= x[command.rs2]) begin
                                        pc <= pc + command.imm_signed;
                                    end else begin
                                        pc <= pc + register::PC_RUG;
                                    end
                                end

                                default: begin
                                    // fail
                                    ;
                                end
                            endcase
                        end

                        default: begin
                            // fail
                            ;
                        end
                    endcase
                end

                decorde::U_TYPE: begin
                    unique case (command.opcode)
                        7'b0110111: begin
                            // LUI
                            x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : command.imm_raw;
                            pc <= pc + register::PC_RUG;
                        end

                        7'b0010111: begin
                            // AUIPC
                            x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : command.imm_signed + pc;
                            pc <= pc + register::PC_RUG;
                        end

                        default: begin
                            // fail
                            ;
                        end
                    endcase
                end

                decorde::J_TYPE: begin
                    unique case (command.opcode)
                        7'b1101111: begin
                            // JAL
                            x[command.rd] <= command.rd == 5'b0 ? register::REGISTER_ZERO : pc + register::PC_RUG;
                            pc <= command.imm_signed + pc;
                        end

                        default: begin
                            // fail
                            ;
                        end
                    endcase
                end

                default: begin
                    // fail
                    ;
                end
            endcase
        end
    end

    assign led = x[led::LED_ADDR][3:0];

endmodule

rom_sv.sv

プログラムを格納したファイルです.
プログラムカウンタのインクリメントは4ずつ行われるので,2ビットずらしています.

`include "register.svh"
`include "decorde.svh"
`include "machine.svh"
`include "led.svh"

module rom_sv(
    input clk,
    input resetn,

    output machine::machine_t machine,
    input register::pc_t pc
    );

    // Lチカの周期
    localparam integer CYCLE = 50_000_000;

    // 命令の数
    localparam integer COMMAND_NUM = 7;

    machine::machine_t machines[0:COMMAND_NUM - 1] = {
        // 各変数の初期化
        machine::ORI(5'b0, led::LED_ADDR, 32'h5),  // LEDの初期値は0101
        machine::LUI(led::CYCLE_ADDR, CYCLE),      // 周期は上で定義したもの(下位12ビットは捨てられる)
        machine::ORI(5'b0, led::CNT_ADDR, 32'b0),  // カウンタの初期値
        // カウントアップ
        machine::ADDI(led::CNT_ADDR, led::CNT_ADDR, 32'h2),  // 2カウント(下の比較で1クロック使うため)
        machine::BGEU(led::CYCLE_ADDR, led::CNT_ADDR, decorde::get_complement(register::PC_RUG)),  // 周期とカウントを比較し,ひとつ前の命令に戻るか一つ進むか決める
        // 点灯状態反転
        machine::XORI(led::LED_ADDR, led::LED_ADDR, 32'hf),  // NOT命令がないので1とXORさせる
        // カウントを元に戻す
        machine::JAL(5'b0, decorde::get_complement(register::PC_RUG * 4))  // 4つ戻って,カウンタに0を代入する
    };

    always_comb begin
        if (pc >= {COMMAND_NUM, 2'b0}) begin
            machine = register::REGISTER_ZERO;
        end else begin
            machine = machines[{2'b0, pc[31:2]}];
        end
    end

endmodule

Verilog HDL

RTLモジュールをブロックデザインに追加するためのラッパーなので大したコードはないです.

cpu.v

module cpu(
    input clk,
    input resetn,

    output [31:0] pc,
    input [31:0] machine,
    output [3:0] led
    );

    cpu_sv cpu (
        .clk(clk), .resetn(resetn),
        .pc(pc), .machine(machine), .led(led)
    );

endmodule

rom.v

module rom(
    input clk,
    input resetn,
    
    input [31:0] pc,
    output [31:0] machine
    );

    rom_sv rom (
        .clk(clk), .resetn(resetn),
        .pc(pc), .machine(machine)
    );

endmodule

実行結果

ちなみにタイミングバイオレーションが出ますが一応動作します.

image.png

0
0
0

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
0
0