RISC-Vシミュレータ作成メモ
リポジトリ
Rubyでできる!RISC-Vシミュレータの作りかた
ここで作ったRISC-Vシミュレータのことを会社の開発者ブログに書いたよ
命令フォーマット
opcode
https://gyazo.com/b421eb85b24d600656563d853ee983cb
命令
あらいぐま本で実装してる命令。I形式, S形式, R形式, B形式の4つ。
add, R形式, opcode = 0110011, funct3 = 0x0, funct7 = 0x00
sub, R形式, opcode = 0110011, funct3 = 0x0, funct7 = 0x20
and, R形式, opcode = 0110011, funct3 = 0x7, funct7 = 0x00
or, R形式, opcode = 0110011, funct3 = 0x6, funct7 = 0x00
lw, I形式, opcode = 0000011, funct3 = 0x2
sw, S形式, opcode = 0100011, funct3 = 0x2
beq, B形式, opcode = 1100011, funct3 = 0x0
レジスタ
x0 (zero): ゼロレジスタ
x1 (ra): Return address
x2 (sp): Stack pointer
x3: (gp): Global pointer
x4: (tp): Thread pointer
x5-x7 (t0-t2): Temporaries
x8 (s0/fp): SAved register / Frame pointer
x9 (s1): Saved register
x10-x11 (a0-a1): Function arguments / Return values
x12-x17 (a2-a7): Function arguments
x18-x27 (s2-s11): Saved registers
x28-x31 (t3-t6): Temporaries
f0-f7 (ft0-ft7): FP Temporaries
f8-f9 (fs0-fs1): FP Saved registers
f10-f11(fa0-fa1): FP Function arguments / Return values
f12-f17 (fa2-fa7): FP Function arguments
f18-f27 (fs2-fs11): FP Saved registers
f28-f31 (ft8-ft11): FP Temporaries
RISC-Vのレジスタは、t は0から6まで、sは0から11まで、aは0から7まで、あとは zero ra sp gp fp を覚えておけば良さげ。ただし fp = s0 なので注意が必要。
min-rt-cで必要な命令
add (済)
addi (済)
andi
beq (済)
bge
bgt
ble
blt
bne
call
fld
flw
fsd
j
jr
lbu
li
lui
lw (済)
mul
mv
nop (済)
rem
sb
seqz
slli
snez
srai
srli
sub (済)
sw (済)
R4で実装した命令
lw, I形式, opcode = 0000011, funct3 = 0x2
sw, S形式, opcode = 0100011, funct3 = 0x2
add, R形式, opcode = 0110011, funct3 = 0x0, funct7 = 0x00
addi, I形式, opcode = 0010011, funct3 = 0x0
sub, R形式, opcode = 0110011, funct3 = 0x0, funct7 = 0x
and
or
slt (未実装)
beq
jal (未実装)
jalr (未実装)
lui (未実装)
ori (未実装)
sll (未実装)
srl (未実装)
sra (未実装)
RISC-V Card
https://gyazo.com/3a764871ea0da0c13a460c03799f9e30
https://gyazo.com/228561b1b3d5fe3c9ed6c0d2b4a091c4
実行イメージ
https://gyazo.com/c9e0db14309231b5920b48664636dbc0
data = cpu.fetch → inst = cpu.docode(data) → cpu.exec(inst)
rvemuの構成
rvemuの構成が参考になりそう
呼び方を確認
lib/rvemu-cli/src/main.rs
code:main.rs
emu = Emulator::new();
emu.initialize_dram(kernel_data);
emu.initialize_disk(img_data);
emu.initialize_pc(DRAM_BASE);
emu.sart();
dump_registers(&emu.cpu); // レジスタをダンプ
dump_count(&emu.cpu); // 実行されたインストラクションを表示
DRAM_BASE
pub const DRAM_BASE: u64 = 0x8000_0000;
モジュール
Emulator
property
cpu
method
new
reset
initialize_dram
initialize_disk
initialize_pc
start
Cpu
property
xregs
fregs
pc
state
mode
bus
method
new
reset
check_pending_interrupt
update_paging
translate
read
write
inst = fetch
execute(inst)
コード片
code:simulator.rb
require "cpu"
class Simulator
def initialize
@cpu = Cpu.new
end
def initialize_ram(data)
@cpu.bus.initialize_ram(data)
end
def initialize_pc(pc)
@cpu.pc = pc
end
def start(step: nil)
s = 0
while true
@cpu.execute
# step指定があれば、指定したステップ数実行後に処理を抜ける
s = s + 1
break if step && s >= step
end
end
def dump_registers
puts "#dump_registers"
end
end
code:cpu.rb
require "bus"
class Cpu
attr_accessor :pc
attr_reader :bus
def initialize
@pc = 0
@bus = Bus.new
end
def execute
# data = cpu.fetch
# inst = cpu.docode(data)
# cpu.exec(inst)
end
def fetch
@bus.read(@pc)
end
end
フィボナッチ数列
code:fibonacci
# fibonacci for RV32
#
# $ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o fibonacci fibonacci.s
# $ riscv64-unknown-elf-objcopy -O binary fibonacci fibonacci.rom
# $ ruby rv32sim.rb fibonacci.rom
.text
.globl _start
.type _start, @function
_start:
addi x5, x0, 10 # n = 10
addi x4, x0, 0 # i = 0
addi x1, x0, 0 # f(i) = 0
addi x2, x0, 1 # f(i+1) = 1
loop:
beq x4, x5, break # goto break if i == n
add x3, x1, x2 # temp = f(i) + f(i+1)
add x1, x2, x0 # f(i) = f(i+1)
add x2, x3, x0 # f(i+1) = temp
addi x4, x4, 1 # i = i + 1
beq x0, x0, loop # goto loop
break:
nop
nop
nop
nop
nop
覚え書き
addi x1, x2, 16 を機械語に翻訳すると 01 01 00 93
RISC-Vはリトルエンディアンなので、上記の機械語をメモリに書き込む時は 93 00 01 01 となる
code:ruby
[0b0000_0001_0000_0001_0000_0000_1001_0011,
0b0000_0001_0000_0001_0000_0000_1001_0011].pack("V*")
#=> "\x93\x00\x01\x01\x93\x00\x01\x01" packの V* は「little endian unsigned 32bit」とのこと
命令の取り出し方
code:ruby
=> "\x93\x00\x01\x01"
irb(main):055:0> s1 = s.slice(0, 4)
=> "\x93\x00\x01\x01"
irb(main):056:0> inst = s.slice(0, 4).reverse
=> "\x01\x01\x00\x93"
2進数、10進数、16進数
code:ruby
irb(main):064:0> 0x01010093
=> 16842899
irb(main):065:0> 0b0000_0001_0000_0001_0000_0000_1001_0011
=> 16842899
irb(main):060:0> sprintf("%d", 0b0000_0001_0000_0001_0000_0000_1001_0011)
=> "16842899"
irb(main):063:0> sprintf("%08x", 0b0000_0001_0000_0001_0000_0000_1001_0011)
=> "01010093"
imm(12bit), rs1(5bit), 000(funct3), rd(5bit), 0010011(opcode)
imm = 16 = 000000010000
rs1 = sp(2番レジスタ) = 2 = 00010
rd = ra(1番レジスタ) = 1 = 00001
funct3 = 000
opcode = 0010011
imm + rs1 + func + rd + op
= 000000010000 + 00010 + 000 + 00001 + 0010011
= 0000_0001_0000_0001_0000_0000_1001_0011
= 01010093
デコード
immのマスク = 1111_1111_1111_0000_0000_0000_0000_0000 = 0xfff00000
rs1のマスク = 0000_0000_0000_1111_1000_0000_0000_0000 = 0x000f8000
funct3のマスク = 0000_0000_0000_0000_0111_0000_0000_0000 = 0x00007000
rdのマスク = 0000_0000_0000_0000_0000_1111_1000_0000 = 0x00000f80
opcodeのマスク = 0000_0000_0000_0000_0000_0000_0111_1111 = 0x0000007f
rs2のマスク = 0000_0001_1111_0000_0000_0000_0000_0000 = 0x01f00000
funct7のマスク = 1111_1110_0000_0000_0000_0000_0000_0000 = 0xfe000000
code:ruby
irb(main):073:0> inst = "\x93\x00\x01\x01".unpack("V").first
=> 16842899
irb(main):084:0> imm = (inst & 0xfff00000) >> 20
=> 16
irb(main):085:0> rs1 = (inst & 0x000f8000) >> 15
=> 2
irb(main):086:0> funct3 = (inst & 0x00007000) >> 12
=> 0
irb(main):087:0> rd = (inst & 0x00000f80) >> 7
=> 1
irb(main):088:0> opcode = (inst & 0x0000007f)
=> 19
今回実装する形式は、I形式, S形式, R形式, B形式の4つ。
読み取るフラグは
opcode (6..0) / R, I, S, B, U, J
rd (11..7) / R, I, U, J
funct3 (14..12) / R, I, S, B
rs1 (19..15) / R, I, S, B
rs2 (24..20) / R, S, B
funct7 (31..2)
I形式のImm
imm(11:0) / 31..20
S形式のImm
imm(11:5) / 31..25
imm(4:0) / 11..7
B形式のImm
imm(12|10:5) / 31..25の6bit
imm(4:1|11) / 11..7の5bit
code:ruby
inst = 0b1_100001_10111_10011_101_1001_1_1000001
((inst & 0x80000000) >> 19) | ((inst & 0x7e000000) >> 20) | ((inst & 0x00000f00) >> 7) | ((inst & 0x00000080) << 4)
lwとsw以外は実装したので、fibonacciを動かしてみるぞ
code:ruby
=> "\x93\x00\xA0\x00"
19 pry(#<CpuTest>)> sprintf "%08x", _addi(1, 0, 10) => "00a00093"
code:ruby
irb(main):027:0> "%b" % -2048
=> "..100000000000"
irb(main):028:0> "%b" % -2047
=> "..100000000001"
romファイルを作成して...
code:sh
$ ruby make_rom.rb > ./rom
作成したromファイルを指定してシミュレータを実行
code:shell
$ ruby rv32sim.rb ./rom
--------------------------------------------------------------------------------
x00 = 0x0 (0) x01 = 0xa (10) x02 = 0x14 (20) x03 = 0x1e (30)
x04 = 0x1e (30) x05 = 0x3c (60) x06 = 0x0 (0) x07 = 0x0 (0)
x08 = 0x0 (0) x09 = 0x0 (0) x10 = 0x0 (0) x11 = 0x0 (0)
x12 = 0x0 (0) x13 = 0x0 (0) x14 = 0x0 (0) x15 = 0x0 (0)
x16 = 0x0 (0) x17 = 0x0 (0) x18 = 0x0 (0) x19 = 0x0 (0)
x20 = 0x0 (0) x21 = 0x0 (0) x22 = 0x0 (0) x23 = 0x0 (0)
x24 = 0x0 (0) x25 = 0x0 (0) x26 = 0x0 (0) x27 = 0x0 (0)
x28 = 0x0 (0) x29 = 0x0 (0) x30 = 0x0 (0) x31 = 0x0 (0)
--------------------------------------------------------------------------------
pc = 0x14 (20)
フィボナッチ数 (n = 10の場合)
code:fibonacci.s
# 命令メモリ
# f(i): x1
# f(i+1): x2
# temp: x3
# i: x4
# n: x5
rom = [
_addi(5, 0, 10), # n = 10
_addi(4, 0, 0), # i = 0
_addi(1, 0, 0), # f(i) = 0
_addi(2, 0, 1), # f(i+1) = 1
_beq(4, 5, 24), # LOOP: goto BREAK if i == n
_add(3, 1, 2), # temp = f(i) + f(i+1)
_addi(1, 2, 0), # f(i) = f(i+1)
_addi(2, 3, 0), # f(i+1) = temp
_addi(4, 4, 1), # i = i + 1
_beq(0, 0, -20), # goto LOOP
_nop, # BREAK:
].pack("V*")
gccでコンパイルしたやつで試してみる
code:fibonacci.s
.text
.globl _start
.type _start, @function
_start:
addi x5, x0, 10
addi x4, x0, 0
addi x1, x0, 0
addi x2, x0, 1
loop:
beq x4, x5, break
add x3, x1, x2
add x1, x2, x0
add x2, x3, x0
addi x4, x4, 1
beq x0, x0, loop
break:
nop
$ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o fibonacci fibonacci.s
$ riscv64-unknown-elf-objcopy -O binary fibonacci fibonacci.rom
うまく動かない...
中身を見てみる
code:sh
$ riscv64-unknown-elf-objdump -S temp/fibonacci
temp/fibonacci: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <_start>:
0: 42a9 li t0,10
2: 4201 li tp,0
4: 4081 li ra,0
6: 4105 li sp,1
0000000000000008 <loop>:
8: 00520b63 beq tp,t0,1e <break>
c: 002081b3 add gp,ra,sp
10: 000100b3 add ra,sp,zero
14: 00018133 add sp,gp,zero
18: 0205 addi tp,tp,1
1a: fe0007e3 beqz zero,8 <loop>
000000000000001e <break>:
1e: 0001 nop
0: 42a9 li t0,10
16ビットのコンパクト命令のせいかも
これだとコンパクト命令が出てこない
$ riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x00 -nostdlib -o fibonacci fibonacci.s
参考
CPUエミュレータをつくる / レガシーガジェット研究所
traceが便利そう
dataとstackで分けるの良いね
メモリダンプが参考になりそう
https://gyazo.com/f76fed423422f48179c6ef67302bf81d