OCamlでもできるRISC-Vシミュレータの作り方:1日目「メモリ」
OCamlの練習に、以前 Ruby で作成した RISC-V シミュレータを OCaml へ移植してみる。
プロジェクト作成
Duneで rvsim プロジェクトを作成する。(Duneについてはこちらを参照 → Dune) code:sh
$ cd ~/src
$ dune init project rvsim
$ cd rvsim
メモリ
Memoryモジュールを作成する。
code:memory.ml
type t = { data : bytes }
let create n = { data = Bytes.create n }
let make n c = { data = Bytes.make n c }
let of_string str = { data = Bytes.of_string str }
let read_word memory addr =
let b0 = Bytes.get_uint8 memory.data (addr + 0)
and b1 = Bytes.get_uint8 memory.data (addr + 1)
and b2 = Bytes.get_uint8 memory.data (addr + 2)
and b3 = Bytes.get_uint8 memory.data (addr + 3) in
let word = b0 lor (b1 lsl 8) lor (b2 lsl 16) lor (b3 lsl 24) in
Int32.of_int word
let write_word memory addr word =
let wd = Int32.to_int word in
let b0 = wd land 0xff
and b1 = (wd land 0xff00) lsr 8
and b2 = (wd land 0xff0000) lsr 16
and b3 = (wd land 0xff000000) lsr 24 in
ignore (Bytes.set_uint8 memory.data (addr + 3) b3);
ignore (Bytes.set_uint8 memory.data (addr + 2) b2);
ignore (Bytes.set_uint8 memory.data (addr + 1) b1);
ignore (Bytes.set_uint8 memory.data (addr + 0) b0)
対話環境からの呼び出し
作成したMemoryモジュールを対話環境から呼び出してみる。
code:ocaml
$ dune utop
utop # Rvsim.Memory.make 10 '\x41';;
- : Rvsim.Memory.t = {Rvsim.Memory.bytes = Bytes.of_string "AAAAAAAAAA"}
テスト
次はMemoryモジュールのテストを作成する。実行するテスト本体(*_test.ml)とテストが出力するはずのテキスト(*_test.expected)を用意する。
code:test/memory_test.ml
(* メモリを初期化 *)
let memory = Rvsim.Memory.of_string "\x93\x00\x01\x01\x94\x00\x01\x01"
(* アドレスを指定して読み込み *)
let test = Rvsim.Memory.read_word memory 0
let _ = Printf.printf "%08x\n" (Int32.to_int test)
let test = Rvsim.Memory.read_word memory 4
let _ = Printf.printf "%08x\n" (Int32.to_int test)
(* アドレス0番地へ書き込み *)
let test = (Rvsim.Memory.write_word memory 0 0x00ABCDEFl; Rvsim.Memory.read_word memory 0)
let _ = Printf.printf "%08x\n" (Int32.to_int test)
(* アドレス4番地へ書き込み *)
let test = (Rvsim.Memory.write_word memory 4 (-1l); Rvsim.Memory.read_word memory 4)
let _ = Printf.printf "%d\n" (Int32.to_int test)
code:test/memory_test.expected
01010093
01010094
00abcdef
-1
追加したテストを実行できるよう test/dune を以下のように修正。
code:test/dune
;; 変更前
(test
(name rvsim))
;; 変更後
(test
(name memory_test) ; 追加したテストのファイル名(.mlは省略)
(libraries rvsim)) ; Rvsim.Memoryを参照できるよう、ライブラリにrvsimを追加
最後に、デフォルトで作成される test/rvsim.rb は使用しないので削除しておく。残しておくとテスト時に以下のエラーを引き起こす。
code:sh
$ dune build
File "test/memory_test.ml", line 2, characters 13-35:
2 | let memory = Rvsim.Memory.of_string "\x93\x00\x01\x01\x94\x00\x01\x01"
^^^^^^^^^^^^^^^^^^^^^^
Error: Unbound module Rvsim.Memory
テストの実行は以下のとおり。okが出れば成功。
code:sh
$ dune test && echo ok
ok
test/memory_test.ml の出力と test/memory_test.expected の間に差異があると以下のようなエラーが出力される。
code:sh
$ dune test && echo OK
File "test/memory_test.expected", line 1, characters 0-0:
diff --git a/_build/default/test/memory_test.expected b/_build/default/test/memory_test.output
index 975d42a..7c3611d 100644
--- a/_build/default/test/memory_test.expected
+++ b/_build/default/test/memory_test.output
@@ -1,4 +1,4 @@
01010093
01010094
00abcdef
-hogehoge
+-1
(おまけ)
テストが複数になった時は test/dune を以下のように修正する。
(新たに test/decoder_test.ml を追加した場合の差分)
code:diff
diff --git a/test/decoder_test.expected b/test/decoder_test.expected
new file mode 100644
index 0000000..b14df64
--- /dev/null
+++ b/test/decoder_test.expected
@@ -0,0 +1 @@
+Hi
diff --git a/test/decoder_test.ml b/test/decoder_test.ml
new file mode 100644
index 0000000..cb31a83
--- /dev/null
+++ b/test/decoder_test.ml
@@ -0,0 +1 @@
+let () = print_endline "Hi"
diff --git a/test/dune b/test/dune
index 06f5b4f..2c46831 100644
--- a/test/dune
+++ b/test/dune
@@ -1,3 +1,3 @@
-(test
- (name memory_test) ; 追加したテストのファイル名(.mlは省略)
- (libraries rvsim)) ; Rvsim.Memoryを参照できるよう、ライブラリにrvsimを追加
+(tests
+ (names memory_test decoder_test)
+ (libraries rvsim))