自作Binary Translation型RISC-Vエミュレータは、ある程度RISC-Vの命令セットをサポートできるようになってきており、最大の課題だったCompressed命令のサポートもほぼ完了しつつある。テストパタンも安定してPassできるようになってきたので、次はベンチマークプログラムを実行して、本家のQEMUとどの程度の速度差があるのか確認していきたいところである。
本家のQEMUと比較するためには、同じテストパタンを流さなければならないだろう。本家のQEMUで動かすためにはSiFive社が提供しているFreedom-E-SDKで提供されているテストパタンを使うのが良かろう。Dhrystoneをまずは実行してみたい。
Dhrystoneにはprintf()
などの関数が入っているが、これをFreedom-E-SDKではfreedom-metal
というサポートライブラリによってカバーしている。例えば以下のようにしてDhrystoneをビルドすると、U54のメモリマップを使用してDhrystoneのビルドが行われる。
$ make PROGRAM=dhrystone TARGET=qemu-sifive-u54
ちなみにQEMUの4.1.0と5.1.0ではこのあたりのメモリマップが微妙に違う。
$ ./qemu-4.1.0/build/riscv64-softmmu/qemu-system-riscv64 -nographic -machine sifive_u,dumpdtb=riscv64-sifive_u.4.1.0.dtb $ ./qemu-5.1.0/build/riscv64-softmmu/qemu-system-riscv64 -nographic -machine sifive_u,dumpdtb=riscv64-sifive_u.5.1.0.dtb
$ dtc riscv64-sifive_u.4.1.0.dtb > riscv64-sifive_u.4.1.0.dts $ dtc riscv64-sifive_u.5.1.0.dtb > riscv64-sifive_u.5.1.0.dts
riscv64-sifive_u.4.1.0.dts
uart@10013000 { interrupts = < 0x03 >; interrupt-parent = < 0x02 >; clock-frequency = < 0x1dcd6500 >; reg = < 0x00 0x10013000 0x00 0x1000 >; compatible = "sifive,uart0"; };
riscv64-sifive_u.5.1.0.dts
serial@10010000 { interrupts = < 0x04 >; interrupt-parent = < 0x06 >; clocks = < 0x05 0x03 >; reg = < 0x00 0x10010000 0x00 0x1000 >; compatible = "sifive,uart0"; };
この辺を考慮して、QEMU-5.1.0に対応したメモリマップで自作エミュレータ側を調整する。とりあえずはUARTだけ実装すればそれっぽくなるであろう。UARTはメモリアドレスとして0x10010000なのだが、現在のエミュレータは0x80000000以下のマップは用意していない。どうしようかといろいろ考えたのだが、結局はこれらのCachedされない場所はTLBに登録しないことで常にメモリ参照ミスを発生させ、手動で操作することにした。
かなり乱暴な方法だが、以下のようにして無理やりメモリマップを貼り付けることで0x1000_0xxxにアクセスする場合は特殊な処理を挿入することにする。
pub fn helper_func_store32(emu: &mut EmuEnv, rs2: u64, rs1: u64, imm: u64, guest_pc: u64) -> usize { let rs1_data = emu.m_iregs[rs1 as usize]; let rs2_data = emu.m_iregs[rs2 as usize]; let addr = rs1_data.wrapping_add(imm as i32 as u64); match emu.convert_physical_address(guest_pc, addr, MemAccType::Write) { Ok(guest_phy_addr) => { if emu.m_arg_config.mmu_debug { println!("store32 : converted address: {:016x} --> {:016x}", addr, guest_phy_addr); } if emu.m_arg_config.machine == MachineEnum::RiscvSiFiveU && (guest_phy_addr & !0xfff) == 0x1000_0000 { if emu.m_arg_config.debug { println!("UART Access : {:08x}", guest_phy_addr); } // region : 0x1000_0000 - 0x1000_0fff match guest_phy_addr { 0x1000_0000 => { eprint!("{}", (rs2_data & 0xff) as u8 as char) }, // txdata 0x1000_0004 => { }, // rxdata 0x1000_0008 => { }, // txctrl 0x1000_000c => { }, // rxctrl _ => {}, } return MemResult::NoExcept as usize;
メモリマップ0x1000_0000のTXDATAにアクセスがあると、その時の値を問答無用で出力する。一方でロードの場合は以下のようになった。これはとりあえずスタブを持っているだけで何もしない。
pub fn helper_func_load32(emu: &mut EmuEnv, rd: u64, rs1: u64, imm: u64, guest_pc: u64) -> usize { let rs1_data = emu.m_iregs[rs1 as usize]; let addr = rs1_data.wrapping_add(imm as i32 as u64); match emu.convert_physical_address(guest_pc, addr, MemAccType::Read) { Ok(guest_phy_addr) => { if emu.m_arg_config.mmu_debug { println!("load32 : converted address: {:016x} --> {:016x}", addr, guest_phy_addr); } if emu.m_arg_config.machine == MachineEnum::RiscvSiFiveU && (guest_phy_addr & !0xfff) == 0x1000_0000 { if emu.m_arg_config.debug { println!("UART Access : {:08x}", guest_phy_addr); } // region : 0x1000_0000 - 0x1000_0fff match guest_phy_addr { 0x1000_0000 => { emu.m_iregs[rd as usize] = 0; }, // txdata 0x1000_0004 => { emu.m_iregs[rd as usize] = 0; }, // rxdata 0x1000_0008 => { emu.m_iregs[rd as usize] = 0; }, // txctrl 0x1000_000c => { emu.m_iregs[rd as usize] = 0; }, // rxctrl _ => {}, } return MemResult::NoExcept as usize; } ...
ここまででDhrystoneをある程度ブートできるようになった。以下のように実行すると最初のDhrystoneのメッセージまでは表示される。
$ cargo run -- --machine sifive_u --elf-file /home/msyksphinz/work/riscv/freedom-e-sdk/software/dhrystone/release/dhrystone.elf
Dhrystone Benchmark, Version 2.1 (Language: C) Program compiled without 'register' attribute Execution starts, 20000000 runs through Dhrystone
しかしここから先がSegmentation Faultで落ちてしまったのを修正すれば、一応パタンが走り始めた。ただし長すぎて果たしてどこまで進んでいるのか良く分からない...