OCamlでもできるRISC-Vシミュレータの作り方:3日目「CPU」
3日目は「CPU」モジュールを作成。今回作成するCPUは主に以下の4つの部品で構成される。
メモリ(1日目に作成したMemoryモジュール)
プログラムやデータの保存領域
命令デコーダ(2日目に作成したInstructionモジュールのdecode関数)
プラグラムカウンタ(PC)
メモリ中の次に実行する命令のアドレスを指し示す
x0からx31まで32個のレジスタ
RV32Iはx0からx31までの32ビットの整数レジスタを持つ
RISC-Vの「x0レジスタ」は常に0を返す特殊なレジスタとなる
Cpu.run が実行されるたびにプログラムカウンタが指し示すアドレスから命令が読み出され、命令デコーダから返ってきた値を元に適切な命令が実行される。
code:lib/cpu.ml
type t = { mutable pc : int32; memory : Memory.t; x_registers : int32 array }
let create =
{ pc = 0l; memory = Memory.make 512 '\x00'; x_registers = Array.make 32 0l }
(* レジスタの値を取得する *)
(* x0は常に0を返す *)
let get_x_register cpu n = if n = 0 then 0l else Array.get cpu.x_registers n
(* 指定したレジスタに値をセットする *)
(* x0への値のセットは無視される *)
let set_x_register cpu n v = if n = 0 then () else Array.set cpu.x_registers n v
let init_memory cpu data = Memory.init cpu.memory data
(* ADD命令 *)
let exec_add cpu rd rs1 rs2 =
let rs1v = Array.get cpu.x_registers rs1
and rs2v = Array.get cpu.x_registers rs2 in
Array.set cpu.x_registers rd (Int32.add rs1v rs2v);
cpu.pc <- Int32.add cpu.pc 4l
(* SUB命令 *)
let exec_sub cpu rd rs1 rs2 =
let rs1v = Array.get cpu.x_registers rs1
and rs2v = Array.get cpu.x_registers rs2 in
Array.set cpu.x_registers rd (Int32.sub rs1v rs2v);
cpu.pc <- Int32.add cpu.pc 4l
(* OR命令 *)
let exec_or cpu rd rs1 rs2 =
let rs1v = Int32.to_int (Array.get cpu.x_registers rs1)
and rs2v = Int32.to_int (Array.get cpu.x_registers rs2) in
Array.set cpu.x_registers rd (Int32.of_int (rs1v lor rs2v));
cpu.pc <- Int32.add cpu.pc 4l
(* AND命令 *)
let exec_and cpu rd rs1 rs2 =
let rs1v = Int32.to_int (Array.get cpu.x_registers rs1)
and rs2v = Int32.to_int (Array.get cpu.x_registers rs2) in
Array.set cpu.x_registers rd (Int32.of_int (rs1v land rs2v));
cpu.pc <- Int32.add cpu.pc 4l
(* ADDI命令 *)
let exec_addi cpu rd rs1 i_imm =
(* 12ビット符号付き整数を32ビットに符号拡張 *)
let sign_ext i = if i land 0x800 = 0x800 then 0xFFFFF000 lor i else i in
let rs1v = Array.get cpu.x_registers rs1
and imm = Int32.of_int (sign_ext i_imm) in
Array.set cpu.x_registers rd (Int32.add rs1v imm);
cpu.pc <- Int32.add cpu.pc 4l
(* SLLI命令 *)
let exec_slli cpu rd rs1 i_imm =
let rs1v = Array.get cpu.x_registers rs1 in
Array.set cpu.x_registers rd (Int32.shift_left rs1v i_imm);
cpu.pc <- Int32.add cpu.pc 4l
(* BEQ命令 *)
let exec_beq cpu rs1 rs2 b_imm =
(* 13ビット符号付き整数を32ビットに符号拡張 *)
let sign_ext i = if i land 0x1000 = 0x1000 then 0xFFFFE000 lor i else i in
let rs1v = Int32.to_int (Array.get cpu.x_registers rs1)
and rs2v = Int32.to_int (Array.get cpu.x_registers rs2) in
let imm = sign_ext b_imm in
if rs1v = rs2v then cpu.pc <- Int32.add cpu.pc (Int32.of_int imm)
else cpu.pc <- Int32.add cpu.pc 4l
(* LW命令 *)
let exec_lw cpu rd rs1 i_imm =
(* 12ビット符号付き整数を32ビットに符号拡張 *)
let sign_ext i = if i land 0x800 = 0x800 then 0xFFFFF000 lor i else i in
let rs1val = Int32.to_int (Array.get cpu.x_registers rs1) in
let imm = sign_ext i_imm in
let addr = rs1val + imm in
let data = Memory.read_word cpu.memory addr in
Array.set cpu.x_registers rd data;
cpu.pc <- Int32.add cpu.pc 4l
(* SW命令 *)
let exec_sw cpu rs1 rs2 s_imm =
(* 12ビット符号付き整数を32ビットに符号拡張 *)
let sign_ext i = if i land 0x800 = 0x800 then 0xFFFFF000 lor i else i in
let rs1val = Int32.to_int (Array.get cpu.x_registers rs1) in
let rs2val = Array.get cpu.x_registers rs2 in
let imm = sign_ext s_imm in
let addr = rs1val + imm in
Memory.write_word cpu.memory addr rs2val;
cpu.pc <- Int32.add cpu.pc 4l
let fetch cpu =
let word = Memory.read_word cpu.memory (Int32.to_int cpu.pc) in
Instruction.decode word
let exec cpu (inst : Instruction.t) =
match inst with
| { opcode = 0b0110011; funct3 = 0x0; funct7 = 0x00; _ } ->
exec_add cpu inst.rd inst.rs1 inst.rs2
| { opcode = 0b0110011; funct3 = 0x0; funct7 = 0x20; _ } ->
exec_sub cpu inst.rd inst.rs1 inst.rs2
| { opcode = 0b0110011; funct3 = 0x6; funct7 = 0x00; _ } ->
exec_or cpu inst.rd inst.rs1 inst.rs2
| { opcode = 0b0110011; funct3 = 0x7; funct7 = 0x00; _ } ->
exec_and cpu inst.rd inst.rs1 inst.rs2
| { opcode = 0b0010011; funct3 = 0x0; _ } ->
exec_addi cpu inst.rd inst.rs1 inst.i_imm
| { opcode = 0b0010011; funct3 = 0x1; _ } ->
exec_slli cpu inst.rd inst.rs1 inst.i_imm
| { opcode = 0b1100011; funct3 = 0x0; _ } ->
exec_beq cpu inst.rs1 inst.rs2 inst.b_imm
| { opcode = 0b0000011; funct3 = 0x2; _ } ->
exec_lw cpu inst.rd inst.rs1 inst.i_imm
| { opcode = 0b0100011; funct3 = 0x2; _ } ->
exec_sw cpu inst.rs1 inst.rs2 inst.s_imm
| _ -> failwith "invalid instruction"
let run cpu = exec cpu (fetch cpu)
CPUの使い方はこんな感じ。
code:test/cpu_test.ml
(* ADD命令組み立てのためのユーティリティメソッド *)
let _add rd rs1 rs2 =
let _op = 0b0110011
and _rd = rd lsl 7
and _f3 = 0b000
and _rs1 = rs1 lsl 15
and _rs2 = rs2 lsl 20
and _f7 = 0b0000000 in
Int32.of_int (_op lor _rd lor _f3 lor _rs1 lor _rs2 lor _f7)
let print_int32 x = print_int (Int32.to_int x)
let print_int32_as_bits x len =
let nth_bit_str x n =
Int32.to_string (Int32.logand (Int32.shift_right_logical x n) 1l)
in
let rec f x n acc =
if n = 0 then acc ^ nth_bit_str x n else f x (n - 1) (acc ^ nth_bit_str x n)
in
print_string (String.sub (f x 31 "") (32 - len) len)
(* 簡単な実行のテスト *)
let test_run () =
let data =
let data = Bytes.create 8 in
Bytes.set_int32_le data 0 (_add 4 1 2);
(* x4 = x1 + x2 *)
Bytes.set_int32_le data 4 (_add 5 4 3);
(* x5 = x4 + x3 *)
data
in
let cpu = Rvsim.Cpu.create in
(* 初期化 *)
Array.set cpu.x_registers 1 10l;
(* x1 = 10 *)
Array.set cpu.x_registers 2 20l;
(* x2 = 20 *)
Array.set cpu.x_registers 3 30l;
(* x3 = 30 *)
Rvsim.Cpu.init_memory cpu data;
(* 実行前 *)
print_int32 cpu.pc;
(* => 0 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 1);
(* => 10 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 2);
(* => 20 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 3);
(* => 30 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 4);
(* => 0 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 5);
(* => 0 *)
print_newline ();
(* 1つめの命令を実行 *)
Rvsim.Cpu.run cpu;
(* 2つめの命令を実行 *)
Rvsim.Cpu.run cpu;
(* 実行後 *)
print_int32 cpu.pc;
(* => 8 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 4);
(* => 30 *)
print_newline ();
print_int32 (Array.get cpu.x_registers 5);
(* => 60 *)
print_newline ()
let test_exec_add () =
let cpu = Rvsim.Cpu.create in
Array.set cpu.x_registers 1 10l;
(* x1 = 10 *)
Array.set cpu.x_registers 2 20l;
(* x2 = 20 *)
Array.set cpu.x_registers 3 (-10l);
(* x3 = -10 *)
Rvsim.Cpu.exec_add cpu 4 1 2;
print_int32 (Array.get cpu.x_registers 4);
(* => 30 *)
print_newline ();
Rvsim.Cpu.exec_add cpu 5 0 3;
(* x5 = x0 + x3 *)
print_int32 (Array.get cpu.x_registers 5);
(* => -10 *)
print_newline ()
let test_exec_sub () =
let cpu = Rvsim.Cpu.create in
Array.set cpu.x_registers 1 10l;
(* x1 = 10 *)
Array.set cpu.x_registers 2 20l;
(* x2 = 20 *)
Array.set cpu.x_registers 3 (-10l);
(* x3 = -10 *)
Rvsim.Cpu.exec_sub cpu 4 2 1;
(* x4 = x2 - x1 *)
print_int32 (Array.get cpu.x_registers 4);
(* => 10 *)
print_newline ();
Rvsim.Cpu.exec_sub cpu 5 2 3;
(* x5 = x2 - x3 *)
print_int32 (Array.get cpu.x_registers 5);
(* => 30 *)
print_newline ()
let test_exec_and () =
let cpu = Rvsim.Cpu.create in
Rvsim.Cpu.set_x_register cpu 1 0b0011l;
Rvsim.Cpu.set_x_register cpu 2 0b0110l;
Rvsim.Cpu.exec_and cpu 3 1 2;
print_int32_as_bits (Rvsim.Cpu.get_x_register cpu 3) 4;
(* => 0010 *)
print_newline ()
let test_exec_or () =
let cpu = Rvsim.Cpu.create in
Rvsim.Cpu.set_x_register cpu 1 0b1010l;
Rvsim.Cpu.set_x_register cpu 2 0b0101l;
Rvsim.Cpu.exec_or cpu 3 1 2;
print_int32_as_bits (Rvsim.Cpu.get_x_register cpu 3) 4;
(* => 1111 *)
print_newline ()
let test_exec_addi () =
let cpu = Rvsim.Cpu.create in
(* x3 = x1 + 20 *)
Rvsim.Cpu.set_x_register cpu 1 10l;
Rvsim.Cpu.exec_addi cpu 3 1 20;
print_int32 (Rvsim.Cpu.get_x_register cpu 3);
(* => 30 *)
print_newline ();
(* x3 = x0 + (-1) *)
Rvsim.Cpu.exec_addi cpu 3 0 0xFFF;
print_int32 (Rvsim.Cpu.get_x_register cpu 3);
(* => -1 *)
print_newline ()
let test_exec_slli () =
let cpu = Rvsim.Cpu.create in
(* 1ビット左シフト *)
Rvsim.Cpu.set_x_register cpu 1 0b1111_0000_1111_0000_1111_0000_1111_0000l;
Rvsim.Cpu.exec_slli cpu 3 1 1;
print_int32_as_bits (Rvsim.Cpu.get_x_register cpu 3) 32;
(* => 11100001111000011110000111100000 *)
print_newline ()
let test_exec_beq () =
let cpu = Rvsim.Cpu.create in
(* x1 = x2 の場合 *)
cpu.pc <- 0l;
Rvsim.Cpu.set_x_register cpu 1 1l;
Rvsim.Cpu.set_x_register cpu 2 1l;
Rvsim.Cpu.exec_beq cpu 1 2 12;
print_int32 cpu.pc;
(* => 12 *)
print_newline ();
(* x1 != x2 の場合 *)
cpu.pc <- 0l;
Rvsim.Cpu.set_x_register cpu 1 1l;
Rvsim.Cpu.set_x_register cpu 2 2l;
Rvsim.Cpu.exec_beq cpu 1 2 12;
print_int32 cpu.pc;
(* => 4 *)
print_newline ();
(* 負数の分岐 *)
(* 4100 + (-4096) = 4 へ飛ぶこと *)
cpu.pc <- 4100l;
Rvsim.Cpu.set_x_register cpu 1 1l;
Rvsim.Cpu.set_x_register cpu 2 1l;
Rvsim.Cpu.exec_beq cpu 1 2 (0b1_0000_0000_0000);
print_int32 cpu.pc;
(* => 4 *)
print_newline ()
let test_sw_lw () =
let cpu = Rvsim.Cpu.create in
(* x1 = x2 の場合 *)
Rvsim.Cpu.set_x_register cpu 1 0x16l;
Rvsim.Cpu.set_x_register cpu 2 0xffl;
Rvsim.Cpu.exec_sw cpu 1 2 0;
Rvsim.Cpu.exec_lw cpu 3 1 0;
print_int32 (Rvsim.Cpu.get_x_register cpu 3);
(* => 255 *)
print_newline ()
let _ =
test_run ();
test_exec_add ();
test_exec_sub ();
test_exec_and ();
test_exec_or ();
test_exec_addi ();
test_exec_slli ();
test_exec_beq ();
test_sw_lw ()
上記のテストが予期するテスト結果はこちら。
code:test/cpu_test.expected
0
10
20
30
0
0
8
30
60
30
-10
10
30
0010
1111
30
-1
11100001111000011110000111100000
12
4
4
255