VOOZH about

URL: https://qiita.com/tt_and_tk/items/05d93c3d72eecfec4328

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


👁 Image
1

Go to list of users who liked

0

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 1 year has passed since last update.

@tt_and_tk(tt tt)

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

1
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

1

Go to list of users who liked

0
0

Go to list of comments

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
1

Go to list of users who liked

0