【Verilog自作CPU】シリーズ
| Part | タイトル | 内容 |
|---|---|---|
| Part1 | レジスタファイルとALU | 基本部品 |
| Part2 | 命令デコーダ | 命令解析 |
| Part3 | メモリとロード・ストア | 本記事 |
| Part4 | 分岐とジャンプ | BEQ/JAL命令の実装 |
| Part5 | CPU統合と動作テスト | 完成・プログラム実行 |
はじめに
前回は命令デコーダを作りました。今回はメモリモジュールを実装します。
CPUには2種類のメモリが必要です:
- 命令メモリ (I-MEM): プログラムを格納(読み取り専用)
- データメモリ (D-MEM): データを読み書き
命令メモリの実装
命令メモリは、PCの値(アドレス)を受け取って、対応する命令を返すシンプルなROMです。
// 命令メモリ (ROM)
module imem (
input [31:0] addr,
output [31:0] data
);
// 256ワード = 1KB
reg [31:0] memory [0:255];
// ワードアドレスでアクセス(下位2ビットは無視)
wire [7:0] word_addr = addr[9:2];
assign data = memory[word_addr];
endmodule
ポイント:
- ワードアドレス: RISC-Vの命令は4バイト単位なので、アドレスの下位2ビットは常に0
- 非同期読み出し: クロック不要、アドレスを与えれば即座にデータが出る
データメモリの実装
データメモリは読み書き両方できるRAMです。
// データメモリ (RAM)
module dmem (
input clk,
input [31:0] addr,
input [31:0] write_data,
input write_enable,
output [31:0] read_data
);
// 256ワード = 1KB
reg [31:0] memory [0:255];
// ワードアドレスでアクセス
wire [7:0] word_addr = addr[9:2];
// 同期書き込み
always @(posedge clk) begin
if (write_enable)
memory[word_addr] <= write_data;
end
// 非同期読み出し
assign read_data = memory[word_addr];
endmodule
ポイント:
- 書き込みは同期(クロックの立ち上がりで書き込み)
- 読み出しは非同期(組み合わせ回路)
ロード・ストア命令
LW(Load Word)
メモリからレジスタにデータを読み込む:
LW rd, offset(rs1)
rd = memory[rs1 + offset]
例:LW x5, 4(x1) → x5 = memory[x1 + 4]
SW(Store Word)
レジスタからメモリにデータを書き込む:
SW rs2, offset(rs1)
memory[rs1 + offset] = rs2
例:SW x2, 8(x1) → memory[x1 + 8] = x2
データパスの拡張
ロード・ストア命令を実行するため、データパスを拡張します:
┌─────────────────────────────┐
│ │
┌─────┐ ┌────────┐│ ┌─────────┐ ┌─────┐ │
│ PC │──▶│ I-MEM │┴─▶│ DECODER │──▶│ REG │────┤
└──┬──┘ └────────┘ └────┬────┘ └──┬──┘ │
│ │ │ │
│ │ ┌─────▼─────┐ │
│ │ │ MUX │◀─┤ alu_src
│ │ └─────┬─────┘ │
│ │ │ │
│ │ ┌─────▼─────┐ │
│ │ │ ALU │ │
│ │ └─────┬─────┘ │
│ │ │ │
│ │ ┌─────▼─────┐ │
│ └────▶│ D-MEM │──┤
│ └─────┬─────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ MUX │◀─┤ mem_to_reg
│ └─────┬─────┘ │
│ │ │
◀────────────────────────────────────┴────────┘
(writeback)
新しいマルチプレクサ:
- alu_src: ALU第2入力を rs2 か 即値 かで選択
- mem_to_reg: ライトバックデータを ALU結果 か メモリデータ かで選択
メモリテスト
データメモリのテストベンチ:
`timescale 1ns/1ps
module dmem_tb;
reg clk;
reg [31:0] addr, write_data;
reg write_enable;
wire [31:0] read_data;
dmem dut (
.clk(clk),
.addr(addr),
.write_data(write_data),
.write_enable(write_enable),
.read_data(read_data)
);
initial clk = 0;
always #5 clk = ~clk;
initial begin
$display("=== Data Memory Test ===\n");
// 初期化
write_enable = 0;
addr = 0;
write_data = 0;
// Test 1: アドレス0に書き込み
addr = 32'h00000000;
write_data = 32'hDEADBEEF;
write_enable = 1;
@(posedge clk); #1;
write_enable = 0;
$display("Test 1: Write 0xDEADBEEF to addr 0");
$display(" Read back: 0x%h", read_data);
// Test 2: アドレス4に書き込み
addr = 32'h00000004;
write_data = 32'hCAFEBABE;
write_enable = 1;
@(posedge clk); #1;
write_enable = 0;
$display("Test 2: Write 0xCAFEBABE to addr 4");
$display(" Read back: 0x%h", read_data);
// Test 3: アドレス0を再度読み出し
addr = 32'h00000000;
#1;
$display("Test 3: Read addr 0 again");
$display(" Read: 0x%h (expected: 0xDEADBEEF)", read_data);
if (read_data == 32'hDEADBEEF)
$display("\n*** TEST PASSED ***");
else
$display("\n*** TEST FAILED ***");
#20; $finish;
end
endmodule
実行結果:
=== Data Memory Test ===
Test 1: Write 0xDEADBEEF to addr 0
Read back: 0xdeadbeef
Test 2: Write 0xCAFEBABE to addr 4
Read back: 0xcafebabe
Test 3: Read addr 0 again
Read: 0xdeadbeef (expected: 0xDEADBEEF)
*** TEST PASSED ***
LW/SWプログラムのシミュレーション
メモリアクセスを含む簡単なプログラム:
# メモリアドレス0に値を保存し、読み戻す
ADDI x1, x0, 100 # x1 = 100 (ベースアドレス)
ADDI x2, x0, 42 # x2 = 42 (保存する値)
SW x2, 0(x1) # memory[100] = 42
LW x3, 0(x1) # x3 = memory[100]
# x3 = 42 になるはず
機械語に変換:
| 命令 | 機械語 |
|---|---|
| ADDI x1, x0, 100 | 0x06400093 |
| ADDI x2, x0, 42 | 0x02a00113 |
| SW x2, 0(x1) | 0x0020a023 |
| LW x3, 0(x1) | 0x0000a183 |
アドレス計算の詳細
LW/SW命令では、ALUを使ってアドレスを計算します:
実効アドレス = rs1 + 即値(符号拡張)
例:LW x3, -4(x1) の場合
- rs1 = x1 の値(例:100)
- imm = -4(符号拡張で 0xFFFFFFFC)
- 実効アドレス = 100 + (-4) = 96
デコーダでは:
-
alu_src = 1(即値を使う) -
alu_op = ADD(加算)
バイト・ハーフワードアクセス
RISC-Vには LB, LH, SB, SH 命令もありますが、今回は簡略化のためワード(32ビット)アクセスのみ実装しています。
完全な実装には:
- バイトイネーブル信号
- アライメントチェック
- 符号拡張(LB, LH)とゼロ拡張(LBU, LHU)
が必要になります。
まとめ
Part3では、メモリモジュールを実装しました:
- 命令メモリ(IMEM): 256ワードのROM
- データメモリ(DMEM): 256ワードのRAM
- LW/SW命令: ロード・ストアの動作原理
次回Part4では、分岐命令(BEQ)とジャンプ命令(JAL)を実装します。
Part1 [完了] レジスタファイル + ALU
Part2 [完了] 命令デコーダ
Part3 [完了] メモリアクセス
Part4 [次回] 分岐・ジャンプ
Part5 [予定] 統合テスト