2013年10月12日土曜日

SystemCはじめました、その5

SystemCとVerilatorGoogle Testを組み合わせて使う話。

ググったらテスト毎にforkして別プロセスでシミュレーションする話も見つかったけれど、もう少し穏便(?)に行く。具体的には、シミュレーションとしては途中で止めることなく、途中途中でリセットをかけながら全テストを一気に流す。

まず、DUTとそのテストベンチと実行バッチをまとめたファイルはこちら。SystemCとVerilatorとGoogle Testがインストール済みの環境なら、解凍して出来たディレクトリの下で

% ./run
すれば動くはず。以下、SystemCで書いたテストベンチをコピペ。DUTはここには貼らないけど、「レイテンシ1クロックの符号無し8ビットのクリップ付き加減算機」とだけ書いておく。上のリンク先のtar ballの中に含まれているけど、以下のテストをパスするものを書いてみるのもありかも。

#include "Vdut.h"
#include "verilated_vcd_sc.h"
#include <gtest/gtest.h>
#define CLK_PERIOD 10
#define VCD_FILENAME "test_sc.vcd"
// test bench
SC_MODULE(TestBench) {
    sc_in<bool> clk;
    sc_signal<bool> xrst;
    sc_signal<bool> addsub;
    sc_signal<uint32_t> a, b, c;
    Vdut dut;
    SC_CTOR(TestBench) : dut("dut") {
        xrst = 0;
        dut.clk(clk);
        dut.xrst(xrst);
        dut.i_addsub(addsub);
        dut.i_a(a);
        dut.i_b(b);
        dut.o_c(c);
    }
    void cy(unsigned int n = 1) {
        for (int i = 0; i < n; i++) {
            wait(clk.posedge_event());
        }
        wait(1, SC_NS); // #1ns
    }
    void reset() {
        xrst = 0;
        wait(CLK_PERIOD * 5.5, SC_NS);
        xrst = 1;
        cy();
    }
};
// test thread
SC_MODULE(TestThread) {
    void (*body)();
    sc_event e_run;
    bool done;
    SC_CTOR(TestThread) : body(0), e_run(), done(false) {
        SC_THREAD(test);
        sensitive << e_run;
    }
    void test() {
        while (true) {
            wait();
            if (body) body();
            done = true;
        }
    }
    bool run(void (*func)(), uint32_t timeout = 0) {
        body = func;
        done = false;
        e_run.notify(CLK_PERIOD, SC_NS);
        if (timeout) {
            while (!done) {
                sc_start(CLK_PERIOD, SC_NS);
                if (--timeout == 0) return false;
            }
        } else {
            while (!done)
                sc_start(CLK_PERIOD, SC_NS);
        }
        return true;
    }
};
// instance
TestBench tb("tb");
TestThread th("th");
// tests
TEST(AddTest, Under255) {
    struct dummy {
        static void test() {
            tb.reset();
            tb.cy(10);
            tb.addsub = 0;
            tb.a = 0;
            tb.b = 0;
            tb.cy();
            EXPECT_EQ(0, tb.c);
            tb.a = 1;
            tb.b = 2;
            tb.cy();
            EXPECT_EQ(3, tb.c);
            tb.a = 3;
            tb.b = 4;
            tb.cy();
            EXPECT_EQ(7, tb.c);
            tb.a = 100;
            tb.b = 100;
            tb.cy();
            EXPECT_EQ(200, tb.c);
        }
    };
    ASSERT_TRUE(th.run(dummy::test));
}
TEST(AddTest, Clip) {
    struct dummy {
        static void test() {
            tb.reset();
            tb.cy(10);
            tb.addsub = 0;
            tb.a = 127;
            tb.b = 127;
            tb.cy();
            EXPECT_EQ(254, tb.c);
            tb.a = 128;
            tb.b = 127;
            tb.cy();
            EXPECT_EQ(255, tb.c);
            tb.a = 127;
            tb.b = 128;
            tb.cy();
            EXPECT_EQ(255, tb.c);
            tb.a = 128;
            tb.b = 128;
            tb.cy();
            EXPECT_EQ(255, tb.c);
            tb.a = 255;
            tb.b = 255;
            tb.cy();
            EXPECT_EQ(255, tb.c);
        }
    };
    ASSERT_TRUE(th.run(dummy::test));
}
TEST(SubTest, AgeB) {
    struct dummy {
        static void test() {
            tb.reset();
            tb.cy(10);
            tb.addsub = 1;
            tb.a = 0;
            tb.b = 0;
            tb.cy();
            EXPECT_EQ(0, tb.c);
            tb.a = 2;
            tb.b = 1;
            tb.cy();
            EXPECT_EQ(1, tb.c);
            tb.a = 7;
            tb.b = 4;
            tb.cy();
            EXPECT_EQ(3, tb.c);
            tb.a = 100;
            tb.b = 30;
            tb.cy();
            EXPECT_EQ(70, tb.c);
            tb.a = 255;
            tb.b = 254;
            tb.cy();
            EXPECT_EQ(1, tb.c);
            tb.a = 255;
            tb.b = 255;
            tb.cy();
            EXPECT_EQ(0, tb.c);
        }
    };
    ASSERT_TRUE(th.run(dummy::test));
}
TEST(SubTest, AltB) {
    struct dummy {
        static void test() {
            tb.reset();
            tb.cy(10);
            tb.addsub = 1;
            tb.a = 0;
            tb.b = 1;
            tb.cy();
            EXPECT_EQ(0, tb.c);
            tb.a = 2;
            tb.b = 3;
            tb.cy();
            EXPECT_EQ(0, tb.c);
            tb.a = 100;
            tb.b = 200;
            tb.cy();
            EXPECT_EQ(0, tb.c);
            tb.a = 254;
            tb.b = 255;
            tb.cy();
            EXPECT_EQ(0, tb.c);
            tb.a = 0;
            tb.b = 255;
            tb.cy();
            EXPECT_EQ(0, tb.c);
        }
    };
    ASSERT_TRUE(th.run(dummy::test));
}
// main
int sc_main(int argc, char *argv[]) {
    Verilated::commandArgs(argc, argv);
    testing::InitGoogleTest(&argc, argv);
    sc_clock clk("clk", CLK_PERIOD, SC_NS);
    tb.clk(clk);
#ifdef VCD_FILENAME
    Verilated::traceEverOn(true);
    VerilatedVcdSc *vcd = new VerilatedVcdSc();
    tb.dut.trace(vcd, 99);
    vcd->open(VCD_FILENAME);
    int r = RUN_ALL_TESTS();
    vcd->close();
    delete vcd;
    return r;
#else
    return RUN_ALL_TESTS();
#endif // #ifdef VCD_FILENAME
}

ざっくり解説。

TestBenchモジュールは、その名の通りテストベンチのモジュール。ここでDUTをインスタンスしている。複雑なDUTなら、ここで入出力モデルもインスタンスしてDUTに接続するところだろうけど、今回のDUTは単純なので入出力の信号を生やしただけ。cy()で1nsディレイさせているのはレーシング対策。このTestBenchを1つだけ静的にインスタンスして、各テストから突っついている。

TestThreadモジュールは、任意の関数をSystemCのスレッドで実行するためのモジュール。どうしてこんな面倒なことをするかというと、SystemCのモジュールはシミュレーション開始前に初期化しなければならないから。各テストの実行時に、そのテスト用のスレッドを含むモジュールをインスタンスしようとすると、1つ目のテストは動くのだが2つ目のテストが走らない(結構ハマった)。そこでテスト実行用のモジュールを静的にインスタンスして、各テストで使い回す形にした。

各テストTEST(...)の書き方はGoogle Testのドキュメントを見てもらうとして、テスト本体はdummyクラスのtest()の中に記述している。本当はここに直接test()の定義を書きたいのだけれど、C++の仕様上できないのでdummyクラスで包んでいる。C++11のlambdaを使えば、もうちょっと幸せになる?

sc_main()は概ねテンプレート通りだが、sc_start()を呼ぶ代わりにGoogle TestのRUN_ALL_TESTS()マクロを実行している。

本当にテストされているのか不安だったら、適当にテストの期待値を変更してこけさせてみればいい。また、とりあえず波形も取っているので見てみればいい。最後に実行バッチの中身を書いておこう。

% verilator --sc dut.v -LDFLAGS -lgtest --trace --exe sc_gtest.cpp
% cd obj_dir
% make -f Vdut.mk % ./Vdut
ポイントはVerilatorの-LDFLAGSオプションでGoogle Testのライブラリをリンクさせているくらいかな。昨日書いた通り、現状では-LDLIBSオプションは使えないので、-LDFLAGSで代用している。

0 件のコメント:

コメントを投稿