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. ポイント解説
-
ステージごとにレジスタを持たせる
-
IF/ID, ID/EX, EX/MEM, MEM/WB
-
命令の進行を段階的に保存
-
-
割り込みはIFステージで検出
-
割り込み中は PC をハンドラに変更
-
ハンドラからの復帰は別途 MRET 命令で実装可能
-
-
メモリマップ
-
MEMステージで LW/SW だけ mem_addr にアクセス
-
他は単純に演算結果をWBに渡す
-
-
簡略化点
-
バイパス無し → データハザードに対応していない
-
分岐はパイプラインでは未実装(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. 進化ポイント
-
データハザード対応
-
EXステージで
forwardA/forwardBにより前段・後段の最新値を使用 -
フォワーディングのみで大半のRAWハザードを回避
-
-
分岐ハザード対応
-
BEQ命令はEXステージで判定
-
分岐確定時に
if_flush信号でIF/IDステージをフラッシュ
-
-
割り込み
-
IFステージでIRQ検出 → ハンドラPCにジャンプ
-
パイプラインフラッシュで中途半端な命令を破棄
-
-
メモリマップ
-
MEMステージで LW/SW
-
外部メモリやIOへのアクセスが明示的
-
💡次のステップとしては、さらに LOAD/STORE バブル挿入 や MRET命令による割り込み復帰処理 を加えて、FPGAで安全に動くレベルに近づけることもできます。