Architecture

module simple_riscv(
input clk,
input reset,
input [31:0] instr, // 命令入力(外部メモリ)
input [31:0] mem_rdata,
output reg [31:0] pc,
output reg [31:0] mem_addr,
output reg [31:0] mem_wdata,
output reg mem_we
);

reg [31:0] regs [0:31]; // レジスタ x0-x31

wire [6:0] opcode = instr[6:0];
wire [4:0] rd = instr[11:7];
wire [4:0] rs1 = instr[19:15];
wire [4:0] rs2 = instr[24:20];
wire [2:0] funct3 = instr[14:12];
wire [6:0] funct7 = instr[31:25];
wire [31:0] imm = {{20{instr[31]}}, instr[31:20]}; // 簡単I型即値

always @(posedge clk or posedge reset) begin
if(reset) begin
pc <= 0;
regs[0] <= 0;
end else begin
case(opcode)
7'b0010011: begin // ADDI
regs[rd] <= regs[rs1] + imm;
pc <= pc + 4;
end
7'b0110011: begin // ADD/SUB
if(funct7 == 7'b0100000)
regs[rd] <= regs[rs1] - regs[rs2]; // SUB
else
regs[rd] <= regs[rs1] + regs[rs2]; // ADD
pc <= pc + 4;
end
7'b0000011: begin // LW
mem_addr <= regs[rs1] + imm;
regs[rd] <= mem_rdata;
pc <= pc + 4;
end
7'b0100011: begin // SW
mem_addr <= regs[rs1] + imm;
mem_wdata <= regs[rs2];
mem_we <= 1;
pc <= pc + 4;
end
7'b1100011: begin // BEQ
if(regs[rs1] == regs[rs2])
pc <= pc + imm;
else
pc <= pc + 4;
end
default: pc <= pc + 4; // それ以外はPC+4
endcase
regs[0] <= 0; // x0は常に0
mem_we <= 0; // 書き込みは1クロックだけ
end
end
endmodule

Memory Map and Interrupt

1. 基本コンセプト

メモリマップ

  • アドレス範囲で機能を分ける

    • 0x0000_0000 - 0x0000_FFFF: RAM

    • 0x1000_0000 - 0x1000_0FFF: メモリマップドIO(例: LED, スイッチ)

    • 0x2000_0000: 割り込み関連レジスタ(簡易版)

割り込み

  • 外部から irq 信号を受ける

  • 割り込みベクタは固定(例えば 0x0000_0100

  • 割り込みが発生すると PC を保存してジャンプ

  • mret 命令で元のPCに戻る(RV32I の ECALL/MRET簡易化)


2. 簡略版Verilog例(memory map + interrupt付き)

module simple_riscv_irq(
input clk,
input reset,
input [31:0] instr,
input [31:0] mem_rdata,
output reg [31:0] pc,
output reg [31:0] mem_addr,
output reg [31:0] mem_wdata,
output reg mem_we,
input irq, // 外部割り込み信号
output reg irq_ack // 割り込み処理完了
);

reg [31:0] regs [0:31];
reg [31:0] mepc; // 割り込み前のPCを保存
reg in_irq;

wire [6:0] opcode = instr[6:0];
wire [4:0] rd = instr[11:7];
wire [4:0] rs1 = instr[19:15];
wire [4:0] rs2 = instr[24:20];
wire [2:0] funct3 = instr[14:12];
wire [6:0] funct7 = instr[31:25];
wire [31:0] imm = {{20{instr[31]}}, instr[31:20]};

always @(posedge clk or posedge reset) begin
if(reset) begin
pc <= 0;
regs[0] <= 0;
in_irq <= 0;
irq_ack <= 0;
end else begin
// 割り込み処理
if(irq && !in_irq) begin
mepc <= pc;
pc <= 32'h0000_0100; // 割り込みハンドラのベクタ
in_irq <= 1;
irq_ack <= 1;
end else begin
irq_ack <= 0;
case(opcode)
7'b0010011: regs[rd] <= regs[rs1] + imm; // ADDI
7'b0110011: regs[rd] <= (funct7==7'b0100000) ? regs[rs1]-regs[rs2] : regs[rs1]+regs[rs2]; // ADD/SUB
7'b0000011: begin // LW
mem_addr <= regs[rs1] + imm;
regs[rd] <= mem_rdata;
end
7'b0100011: begin // SW
mem_addr <= regs[rs1] + imm;
mem_wdata <= regs[rs2];
mem_we <= 1;
end
7'b1100011: begin // BEQ
pc <= (regs[rs1]==regs[rs2]) ? pc+imm : pc+4;
end
7'b1110011: begin // SYSTEM命令(MRET)
if(funct3==0 && instr[31:20]==32'h302) begin
pc <= mepc; // 割り込みから復帰
in_irq <= 0;
end
end
default: pc <= pc + 4;
endcase
regs[0] <= 0;
mem_we <= 0;
if(opcode!=7'b1100011 && !(irq && !in_irq)) pc <= pc+4;
end
end
end
endmodule

Pipeline

基本的に5段階を簡易化して想定します:

ステージ 役割
IF 命令フェッチ(Instruction Fetch)
ID 命令デコード、レジスタ読み出し
EX 演算/アドレス計算
MEM メモリアクセス
WB レジスタ書き込み(Write Back)

簡易化のため:

  • ハザードやバイパスは 無視 または簡易的に挿入バブル

  • 割り込みは MEM/WB の前に確認


2. 簡易パイプライン版 Verilog 構造

module simple_riscv_pipeline(
input clk,
input reset,
input irq,
output reg irq_ack,
// メモリインターフェース
output reg [31:0] mem_addr,
input [31:0] mem_rdata,
output reg [31:0] mem_wdata,
output reg mem_we
);

reg [31:0] pc;
reg [31:0] regs [0:31];
reg [31:0] mepc;
reg in_irq;

// IF/ID レジスタ
reg [31:0] if_instr;
reg [31:0] if_pc;

// ID/EX レジスタ
reg [6:0] id_opcode;
reg [4:0] id_rd, id_rs1, id_rs2;
reg [31:0] id_imm;
reg [31:0] id_rs1_val, id_rs2_val;
reg [31:0] id_pc;

// EX/MEM レジスタ
reg [6:0] ex_opcode;
reg [4:0] ex_rd;
reg [31:0] ex_alu_out, ex_rs2_val;

// MEM/WB レジスタ
reg [6:0] mem_opcode;
reg [4:0] mem_rd;
reg [31:0] mem_alu_out;
reg [31:0] mem_rdata_reg;

// IFステージ
always @(posedge clk or posedge reset) begin
if(reset) begin
pc <= 0;
if_pc <= 0;
if_instr <= 32'b0;
regs[0] <= 0;
in_irq <= 0;
irq_ack <= 0;
end else begin
if(irq && !in_irq) begin
mepc <= pc;
pc <= 32'h100; // 割り込みハンドラ
in_irq <= 1;
irq_ack <= 1;
end else begin
irq_ack <= 0;
if_instr <= 32'b0; // 実際はメモリから読み出し
if_pc <= pc;
pc <= pc + 4;
end
end
end

// IDステージ
always @(posedge clk) begin
id_opcode <= if_instr[6:0];
id_rd <= if_instr[11:7];
id_rs1 <= if_instr[19:15];
id_rs2 <= if_instr[24:20];
id_imm <= {{20{if_instr[31]}}, if_instr[31:20]};
id_rs1_val <= regs[id_rs1];
id_rs2_val <= regs[id_rs2];
id_pc <= if_pc;
end

// EXステージ
always @(posedge clk) begin
ex_opcode <= id_opcode;
ex_rd <= id_rd;
case(id_opcode)
7'b0010011: ex_alu_out <= id_rs1_val + id_imm; // ADDI
7'b0110011: ex_alu_out <= id_rs1_val + id_rs2_val; // ADD
default: ex_alu_out <= 0;
endcase
ex_rs2_val <= id_rs2_val;
end

// MEMステージ
always @(posedge clk) begin
mem_opcode <= ex_opcode;
mem_rd <= ex_rd;
mem_alu_out <= ex_alu_out;

if(ex_opcode==7'b0000011) begin // LW
mem_rdata_reg <= mem_rdata;
end else if(ex_opcode==7'b0100011) begin // SW
mem_addr <= ex_alu_out;
mem_wdata <= ex_rs2_val;
mem_we <= 1;
end else begin
mem_we <= 0;
end
end

// WBステージ
always @(posedge clk) begin
if(mem_rd != 0) begin
if(mem_opcode==7'b0000011)
regs[mem_rd] <= mem_rdata_reg;
else
regs[mem_rd] <= mem_alu_out;
end
regs[0] <= 0;
end
endmodule


3. ポイント解説

  1. ステージごとにレジスタを持たせる

    • IF/ID, ID/EX, EX/MEM, MEM/WB

    • 命令の進行を段階的に保存

  2. 割り込みはIFステージで検出

    • 割り込み中は PC をハンドラに変更

    • ハンドラからの復帰は別途 MRET 命令で実装可能

  3. メモリマップ

    • MEMステージで LW/SW だけ mem_addr にアクセス

    • 他は単純に演算結果をWBに渡す

  4. 簡略化点

    • バイパス無し → データハザードに対応していない

    • 分岐はパイプラインでは未実装(IF+EXでフラッシュ簡略)

    • 即値はI型のみ簡略

Hazard

1. ハザード対応の概念

データハザード

  • レジスタの書き込みが後段に残っている場合、前段で読み出すと古い値になる

  • 対応方法(簡略版):

    • フォワーディング(バイパス):EX/MEM/WB の値をID/EX に渡す

    • バブル挿入:読み出し直前に1クロック待機(必要に応じて)

分岐ハザード

  • 分岐命令で次PCが不確定な場合にIFで命令を読みすぎる

  • 対応方法:

    • ブランチ決定をEXステージに移動

    • 分岐が確定するまでIF/IDをフラッシュ(NOP挿入)

割り込みハンドリング

  • IFステージで IRQ 検出

  • パイプライン中の命令はフラッシュ


2. フォワーディングのイメージ

  • EXステージで演算に必要な rs1, rs2 の値を

    • EX/MEM または MEM/WB から取り出して最新化

  • 例:forwardA = (id_rs1 == ex_rd) ? ex_alu_out : regs[id_rs1] のように置換


3. 簡略Verilog(進化版)

以下は データハザード・分岐ハザード対応+割り込み+メモリマップ の概念実装です。
本格的なFPGA実装向けではさらに細かい調整が必要ですが、理解・学習用としては十分です。

module riscv_pipeline_adv(
input clk,
input reset,
input irq,
output reg irq_ack,
// メモリインターフェース
output reg [31:0] mem_addr,
input [31:0] mem_rdata,
output reg [31:0] mem_wdata,
output reg mem_we
);

reg [31:0] pc;
reg [31:0] regs [0:31];
reg [31:0] mepc;
reg in_irq;

// IF/ID
reg [31:0] if_instr, if_pc;
reg if_flush;

// ID/EX
reg [6:0] id_opcode;
reg [4:0] id_rd, id_rs1, id_rs2;
reg [31:0] id_imm;
reg [31:0] id_rs1_val, id_rs2_val;
reg [31:0] id_pc;

// EX/MEM
reg [6:0] ex_opcode;
reg [4:0] ex_rd;
reg [31:0] ex_alu_out, ex_rs2_val;

// MEM/WB
reg [6:0] mem_opcode;
reg [4:0] mem_rd;
reg [31:0] mem_alu_out, mem_rdata_reg;

// フォワーディング信号
wire [31:0] forwardA = (id_rs1 != 0 && id_rs1 == ex_rd) ? ex_alu_out :
(id_rs1 != 0 && id_rs1 == mem_rd) ? (mem_opcode==7'b0000011 ? mem_rdata_reg : mem_alu_out) :
id_rs1_val;

wire [31:0] forwardB = (id_rs2 != 0 && id_rs2 == ex_rd) ? ex_alu_out :
(id_rs2 != 0 && id_rs2 == mem_rd) ? (mem_opcode==7'b0000011 ? mem_rdata_reg : mem_alu_out) :
id_rs2_val;

// IFステージ
always @(posedge clk or posedge reset) begin
if(reset) begin
pc <= 0; if_pc <= 0; if_instr <= 0;
regs[0] <= 0; in_irq <= 0; irq_ack <= 0;
if_flush <= 0;
end else begin
if(irq && !in_irq) begin
mepc <= pc;
pc <= 32'h100; // 割り込みベクタ
in_irq <= 1;
irq_ack <= 1;
if_flush <= 1; // パイプラインフラッシュ
end else begin
irq_ack <= 0;
if_flush <= 0;
if_instr <= 0; // 実際はメモリからフェッチ
if_pc <= pc;
pc <= pc + 4;
end
end
end

// IDステージ
always @(posedge clk) begin
if(if_flush) begin
id_opcode <= 7'b0000000;
id_rd <= 0; id_rs1 <= 0; id_rs2 <= 0; id_imm <= 0;
id_rs1_val <= 0; id_rs2_val <= 0; id_pc <= 0;
end else begin
id_opcode <= if_instr[6:0];
id_rd <= if_instr[11:7];
id_rs1 <= if_instr[19:15];
id_rs2 <= if_instr[24:20];
id_imm <= {{20{if_instr[31]}}, if_instr[31:20]};
id_rs1_val <= regs[id_rs1];
id_rs2_val <= regs[id_rs2];
id_pc <= if_pc;
end
end

// EXステージ
always @(posedge clk) begin
ex_opcode <= id_opcode;
ex_rd <= id_rd;
case(id_opcode)
7'b0010011: ex_alu_out <= forwardA + id_imm; // ADDI
7'b0110011: ex_alu_out <= forwardA + forwardB; // ADD
7'b1100011: begin // BEQ
if(forwardA == forwardB) begin
pc <= id_pc + id_imm;
if_flush <= 1;
end
end
default: ex_alu_out <= 0;
endcase
ex_rs2_val <= forwardB;
end

// MEMステージ
always @(posedge clk) begin
mem_opcode <= ex_opcode;
mem_rd <= ex_rd;
mem_alu_out <= ex_alu_out;

if(ex_opcode==7'b0000011) begin // LW
mem_rdata_reg <= mem_rdata;
end else if(ex_opcode==7'b0100011) begin // SW
mem_addr <= ex_alu_out;
mem_wdata <= ex_rs2_val;
mem_we <= 1;
end else begin
mem_we <= 0;
end
end

// WBステージ
always @(posedge clk) begin
if(mem_rd != 0) begin
if(mem_opcode==7'b0000011)
regs[mem_rd] <= mem_rdata_reg;
else
regs[mem_rd] <= mem_alu_out;
end
regs[0] <= 0;
end
endmodule


4. 進化ポイント

  1. データハザード対応

    • EXステージで forwardA/forwardB により前段・後段の最新値を使用

    • フォワーディングのみで大半のRAWハザードを回避

  2. 分岐ハザード対応

    • BEQ命令はEXステージで判定

    • 分岐確定時に if_flush 信号でIF/IDステージをフラッシュ

  3. 割り込み

    • IFステージでIRQ検出 → ハンドラPCにジャンプ

    • パイプラインフラッシュで中途半端な命令を破棄

  4. メモリマップ

    • MEMステージで LW/SW

    • 外部メモリやIOへのアクセスが明示的


💡次のステップとしては、さらに LOAD/STORE バブル挿入MRET命令による割り込み復帰処理 を加えて、FPGAで安全に動くレベルに近づけることもできます。