OCamlでもできるRISC-Vシミュレータの作り方:5日目「入出力」
4649は動いたのでHello Worldも動くようにしたい。
code:sample/hello.S
# hello
#
# $ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o hello.elf hello.S
# $ riscv64-unknown-elf-objcopy -O binary hello.elf hello.rom
# $ ruby rv32sim.rb hello.rom
.text
.globl _start
.type _start, @function
_start:
// シリアル通信の送受信レジスタのアドレス ( 0x10000000 ) を gp レジスタにセット
// 1024を18ビットシフトさせて 0x10000000 を作成する
addi gp, zero, 1024
slli gp, gp, 18
addi t0, zero, 'H'
sw t0, 0(gp)
addi t0, zero, 'E'
sw t0, 0(gp)
addi t0, zero, 'L'
sw t0, 0(gp)
addi t0, zero, 'L'
sw t0, 0(gp)
addi t0, zero, 'O'
sw t0, 0(gp)
addi t0, zero, ' '
sw t0, 0(gp)
addi t0, zero, 'W'
sw t0, 0(gp)
addi t0, zero, 'O'
sw t0, 0(gp)
addi t0, zero, 'R'
sw t0, 0(gp)
addi t0, zero, 'L'
sw t0, 0(gp)
addi t0, zero, 'D'
sw t0, 0(gp)
addi t0, zero, '!'
sw t0, 0(gp)
addi t0, zero, '!'
sw t0, 0(gp)
addi t0, zero, '\n'
sw t0, 0(gp)
nop
nop
nop
nop
nop
現在はメモリマップトIOによるシリアル通信に対応していないため、以下のようなエラーが出る。
code:sh
$ dune exec rvsim sample/hello.rom
Fatal error: exception Invalid_argument("index out of bounds")
標準出力への出力
0x10000000番地にswが行われたらメモリマップトIOでシリアルへ出力(今回はシミュレータなので出力先は標準出力でOK)するようにしたい。
Charモジュールとprint_chrを使えば良さそう。
Charモジュール
code:ocaml
utop # Char.code 'a';;
- : int = 97
utop # Char.chr 97;;
- : char = 'a'
utop # Char.chr (Int32.to_int 97l);;
- : char = 'a'
utop # print_char (Char.chr (Int32.to_int 97l));;
a- : unit = ()
標準入力の入力
input_byte stdin でいい感じに入力バイトを読み込めるみたい。
code:diff
diff --git a/lib/cpu.ml b/lib/cpu.ml
index 762cc42..05de680 100644
--- a/lib/cpu.ml
+++ b/lib/cpu.ml
@@ -82,7 +82,11 @@ let exec_lw cpu rd rs1 i_imm =
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
+ let data =
+ (* 読み込み元のアドレスが 0x10000000 の場合は標準入力からデータを読み込む *)
+ if addr = 0x10000000 then Int32.of_int (input_byte stdin)
+ else Memory.read_word cpu.memory addr
+ in
Array.set cpu.x_registers rd data;
cpu.pc <- Int32.add cpu.pc 4l
@@ -94,7 +98,11 @@ let exec_sw cpu rs1 rs2 s_imm =
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;
+ (* 書き込み先のアドレスが 0x10000000 の場合は標準出力へデータを出力する *)
+ if addr = 0x10000000 then (
+ print_char (Char.chr (Int32.to_int rs2val));
+ flush stdout)
+ else Memory.write_word cpu.memory addr rs2val;
cpu.pc <- Int32.add cpu.pc 4l
let fetch cpu =
ちょっと修正
code:diff
diff --git a/lib/cpu.ml b/lib/cpu.ml
index 05de680..6e6131e 100644
--- a/lib/cpu.ml
+++ b/lib/cpu.ml
@@ -20,8 +20,11 @@ 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
+let is_serial_io addr = addr = 0x10000000
+
(* ADD命令 *)
let exec_add cpu rd rs1 rs2 =
let rs1v = Array.get cpu.x_registers rs1
@@ -83,8 +86,7 @@ let exec_lw cpu rd rs1 i_imm =
let imm = sign_ext i_imm in
let addr = rs1val + imm in
let data =
- (* 読み込み元のアドレスが 0x10000000 の場合は標準入力からデータを読み込む *)
- if addr = 0x10000000 then Int32.of_int (input_byte stdin)
+ if is_serial_io addr then Serial.read ()
else Memory.read_word cpu.memory addr
in
Array.set cpu.x_registers rd data;
@@ -98,10 +100,7 @@ let exec_sw cpu rs1 rs2 s_imm =
let rs2val = Array.get cpu.x_registers rs2 in
let imm = sign_ext s_imm in
let addr = rs1val + imm in
- (* 書き込み先のアドレスが 0x10000000 の場合は標準出力へデータを出力する *)
- if addr = 0x10000000 then (
- print_char (Char.chr (Int32.to_int rs2val));
- flush stdout)
+ if is_serial_io addr then Serial.write rs2val
else Memory.write_word cpu.memory addr rs2val;
cpu.pc <- Int32.add cpu.pc 4l
diff --git a/lib/serial.ml b/lib/serial.ml
new file mode 100644
index 0000000..0764994
--- /dev/null
+++ b/lib/serial.ml
@@ -0,0 +1,5 @@
+let write word = (
+ print_char (Char.chr (Int32.to_int word));
+ flush stdout)
+
+let read () = Int32.of_int (input_byte stdin)