SystemCとVerilatorとGoogle 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ポイントはVerilatorの-LDFLAGSオプションでGoogle Testのライブラリをリンクさせているくらいかな。昨日書いた通り、現状では-LDLIBSオプションは使えないので、-LDFLAGSで代用している。
% cd obj_dir
% make -f Vdut.mk % ./Vdut
0 件のコメント:
コメントを投稿