FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Binary Translation型エミュレータを作る(テスト環境の用意)

はじめに

RustでBinary Translation型エミュレータを作っている。Binary Translation型エミュレータの特徴は、ゲストのマシンコード(例えばRISC-Vなど)をホストマシン(例えばx86)で実行する場合に、RISC-Vのマシンコードをそれと等価なx86コードに変換する。一般的なインタプリタ型の命令セットシミュレータはゲストのマシンコードをデコードするとそれをC言語などの高級言語でエミュレートして実行するため冗長性が大きく実行速度はあまり良くないが、Binary Translation型は直接ゲストマシンコードをホストマシンコードに翻訳するためエミュレーション速度で有利だ。

ゲストマシンをRISC-V、ホストマシンをx86に限定してRustで実装を進めている。ある程度実装が進んでおり、いくつかの命令をすでにサポートしたのだが、いくつかのテストパタンを用意しておりこれをいちいち手動で流すのが面倒になってきた。せっかくRustにはcargoという総合ビルド環境があるのでこれでリグレッションテストを統一してしまいたい。リグレッションテストをサポートしていく。

Rustのテスト実行環境

Rustのテスト環境は、Cargoを使うと簡単に構築できる。test以下にlib.rsを配置してテストパタンを追加する。

  • tests/lib.rs
extern crate uint_execute;

#[test]
fn simple_start2() {
    assert_eq!(
        uint_execute::run(
            "rvtests/simple_start2/test.riscv".to_string(),
            &[
                0x0000000000000000,
                0x0000000000000001,
                0x0000000000000003,
                ...

何をテストするのかという話だが、対象としてはRISC-V形式でコンパイルしたELFファイルをロードして実行するようにしたい。Pass/Failの判定は、とりあえず今のところは最終的なレジスタ値をダンプして想定したレジスタ値と一致するかを比較する。32本のレジスタの最終状態をダンプして、期待値との一致比較を行う。

run()関数は以下のように定義しており、main()と同様の機能でテストバイナリを構築するようになっている。

  • src/lib.rs
pub fn run(filename: String, exp_gpr: &[u64; 32]) -> usize {
    let mut emu = EmuEnv::new();
    emu.run(&filename);
    let gpr_vec = emu.get_gpr();
    for (gpr_val, exp_val) in gpr_vec.iter().zip(exp_gpr.iter()) {
        if gpr_val != exp_val {
            print!("Failed. %08x != %08x\n");
            return 1;
        }
    }
    return 0;
}

テストの記述

ここでexp_gprは32本の64ビット型符号なし整数引数で、最終レジスタ値の期待値を格納している。一方でgpr_vecはエミュレーションを行った際の最終レジスタ値を取得したものになっており、この2つを比較する。

2つの32本配列を比較するために、zipを使って2つの配列を結合し、それぞれのペアを比較している。もし一致しなければその旨を表示して1を返す。

テストパタンの終了条件は、一致比較を行った結果すべて一致すれば0、不一致ならば1を返すという単純なものだ。このテストを、すべてのテストバイナリに対して実行する。

fn simple_start2() {
    assert_eq!(
        uint_execute::run(
            "rvtests/simple_start2/test.riscv".to_string(),       // テストパタンの場所
            &[
                0x0000000000000000,                                // 期待値の配列:32本のレジスタすべて
                0x0000000000000001,

これを現在用意しているすべてのテストパタンについて作成した。

#[test]
fn simple_start2() {
    assert_eq!(
        uint_execute::run(
            "rvtests/simple_start2/test.riscv".to_string(),
            &[....
#[test]
fn simple_start() {
    assert_eq!(
        uint_execute::run(
            "rvtests/simple_start/test.riscv".to_string(),
            &[...
#[test]
fn simple_load() {
    assert_eq!(
        uint_execute::run(
            "rvtests/load_test/test.riscv".to_string(),
            &[...
#[test]
fn branch_test() {
    assert_eq!(
        uint_execute::run(
            "rvtests/branch_test/test.riscv".to_string(),
            &[...
#[test]
fn csr_test() {
    assert_eq!(
        uint_execute::run(
            "rvtests/csr_test/test.riscv".to_string(),
            &[...
#[test]
fn long_insts() {
    assert_eq!(
        uint_execute::run(
            "rvtests/long_insts/test.riscv".to_string(),
            &[...
#[test]
fn simple_add() {
    assert_eq!(
        uint_execute::run(
            "rvtests/simple_add/test.riscv".to_string(),
            &[...
#[test]
fn simple_lui() {
    assert_eq!(
        uint_execute::run(
            "rvtests/simple_lui/test.riscv".to_string(),
            &[...

テストの実行と確認

テストを実行するためには、cargo testと入力する。これですべてのテストパタンが実行され、結果が表示される。

$ cargo test
running 8 tests
test simple_start ... ok
test csr_test ... ok
test simple_add ... ok
test branch_test ... ok
test simple_lui ... ok
test simple_start2 ... ok
test simple_load ... ok
test long_insts ... ok

問題無いようだ。リグレッションテストの環境が構築できた。

f:id:msyksphinz:20200913010729p:plain