ラベル systemc の投稿を表示しています。 すべての投稿を表示
ラベル systemc の投稿を表示しています。 すべての投稿を表示

2014年8月5日火曜日

SystemC 2.3.1が出ていた

SystemC 2.3.1がリリースされていたことに3か月以上遅れて気付いた。そう言えば最近、使ってないなぁ。

で、変更点はざっくりこんなもん

* SystemC 2.3.1
  Release of SystemC 2.3.1
  - Bug fixes.
  - Code cleanup.
  - Some experimental new features.
だそうな。Some experimental new featuresもあるけど、無理に使わなければ、機能的には単なる2.3.0のバグフィックス版。プライベートのMacにも、そのうち入れておこう。

2.3.0のときは騙されたけど、systemc-2.3.1.tgzはサフィックス通りtar ballだった。

2014年4月22日火曜日

SystemCのインストール

新しい仮想マシンを立ち上げて久々にSystemCをインストールしたら、プチはまりしたところ。

ファイル名のサフィックスが.tgzなのにgzipで圧縮してないという罠は覚えていたのだが、configureで--prefixオプションを使う際、指定したディレクトリが存在しないとインストール先がカレントディレクトリになる。なので、--prefix=/usr/local/systemc-2.3.0とか指定する場合には、configure実行前に/usr/local/systemc-2.3.0を作っておかなければならない。

よくよく見ればconfigureがエラーメッセージを吐いているのだけれど、makeまでは通ってしまうから見逃しがち。

2013年10月16日水曜日

SystemCはじめました、その6

VerilatorとGoogle Testを組み合わせる話の続き。

いつのまにやら、UbuntuのレポジトリからGoogle Testのバイナリパッケージがなくなっていた。適当にググってみると、Google Testはいつからか、プロジェクト毎にビルドすることを推奨しているらしい。

ならば、Verilatorが吐き出すMakefileにGoogle Testをビルドしてもらおう。Verilatorを実行するディレクトリにGoogleが配布しているgtest-1.7.0.zipを展開する場合、以下のようにすればGoogle Testも含めて、test_xx.cppでxx.vをテストするバイナリをビルドしてくれる。

% unzip gtest-1.7.0.zip
% export SYSTEMC_CXX_FLAGS=-pthread
% verilator --sc xx.v --exe test_xx.cpp gtest-1.7.0/src/gtest-all.cc -CFLAGS '-isystem ../gtest-1.7.0/include -I ../gtest-1.7.0'
% cd obj_dir
% make -f Vxx.mk

環境変数SYSTEMC_CXX_FLAGSの設定は、使っているシェルに合わせて適宜読み替えること。.profileとか.cshrcとか.zshenvとかに書いてしまってもいい。

注意点として、libgtest-devのようなソースパッケージを入れている状態でオプションを間違えると、 libgtest-devのヘッダをインクルードしながらgtest-1.7.0.zipの.ccファイルをコンパイルしてしまったりする。このポストの時点でUbuntuのlibgtest-devは1.6.0なので、gtest-1.7.0.zipと混ぜるのはよろしくない。libgtest-devは切ってしまおう。

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で代用している。

2013年10月11日金曜日

SystemCはじめました、その4

VerilatorとSystemCでRTLをテストしていて、ふと思った。結局C++のソフトを書いているのだから、既存のC++テストフレームワークを使えないだろうか。

早速Google Testを使ってみようとしてみたところ、Verilatorが吐き出すMakefileにlibgtestをリンクするよう指定させるところで、Verilatorの-LDLIBSオプションを使おうとしてプチ引っかかった。

結論から言うと、-LDLIBSオプションはマニュアルにはあるけど、ざっとソースを見た限り実装されてない模様(v3.853)。ちょっと気持ち悪いけど-LDFLAGSオプションで代用するか、環境変数でLDLIBSを指定するか、だな。

SystemCとGoogle Testの組み合わせの話は後日。

2013年9月21日土曜日

SystemCはじめました、その3

放置気味だったSystemCの話を回収。前回は合成可能なVerilogをSystemCで書き直したけれど、そもそも本気で石にするシステムをSystemCで書く気なんて全くない。単にVerilatorを使ってVerilogの検証環境を構築したかっただけなのだ。

…と言うわけで、適当な回路のテストベンチを書いてみる。まず、テスト対象の回路はこのローパスフィルタ(lpf.v)

// low pass filter
module lpf (
    input           clk,
    input           xrst,
    input           i_vld,
    input   [7:0]   i_d,
    output          o_vld,
    output  [7:0]   o_d
);
reg     [7:0]   r_d_1z;
reg     [7:0]   r_d_2z;
reg     [7:0]   r_d_3z;
reg             r_vld_1z;
reg             r_vld_2z;
reg             r_vld_3z;
reg     [7:0]   r_od;
reg             r_ovld;
wire    [7:0]   w_d0;
wire    [7:0]   w_d1;
wire    [7:0]   w_d2;
wire    [9:0]   w_sum;
wire    [8:0]   w_round;
wire            w_ovld;
// valid
always @(posedge clk or negedge xrst) begin
    if (!xrst) begin
        r_vld_1z <= 1'b0;
        r_vld_2z <= 1'b0;
        r_vld_3z <= 1'b0;
    end else begin
        r_vld_1z <= i_vld;
        r_vld_2z <= r_vld_1z;
        r_vld_3z <= r_vld_2z;
    end
end
// taps
always @(posedge clk or negedge xrst) begin
    if (!xrst) begin
        r_d_1z[7:0] <= 8'd0;
        r_d_2z[7:0] <= 8'd0;
        r_d_3z[7:0] <= 8'd0;
    end else begin
        r_d_1z[7:0] <= i_d[7:0];
        r_d_2z[7:0] <= r_d_1z[7:0];
        r_d_3z[7:0] <= r_d_2z[7:0];
    end
end
assign w_d0[7:0] = (r_vld_1z)? r_d_1z[7:0] : w_d1[7:0];
assign w_d1[7:0] = (r_vld_2z)? r_d_2z[7:0] : 8'd0;
assign w_d2[7:0] = (r_vld_3z)? r_d_3z[7:0] : w_d1[7:0];
// filter
assign w_sum[9:0]   = {2'b00, w_d0[7:0]} +
                      {1'b0,  w_d1[7:0], 1'b0} +
                      {2'b00, w_d2[7:0]};
assign w_round[8:0] = w_sum[9:1] + {8'd0, w_sum[1]};
// output
always @(posedge clk or negedge xrst) begin
    if (!xrst)
        r_od[7:0] <= 8'd0;
    else
        r_od[7:0] <= w_round[8:1];
end
assign o_d[7:0] = r_od[7:0];
assign o_vld    = r_vld_3z;
endmodule

で、そのテストベンチがこれ(test_lpf.cpp)。ちと長いけど、そのままペーストしてしまおう。

#include "Vlpf.h"
#include "verilated_vcd_sc.h"
#include <cstdlib>
#include <queue>
#include <iostream>
#define DUMP_VCD
// source
SC_MODULE(Source) {
    static const int N = 10000;
    sc_in<bool> clk, xrst;
    sc_out<bool> vld;
    sc_out<uint32_t> d;
    sc_event done;
    SC_CTOR(Source) : done() {
        SC_CTHREAD(proc, clk.pos());
        async_reset_signal_is(xrst, false);
    }
    void proc() {
        int n = N;
        while (n > 0) {
            vld.write(0);
            wait(10);
            for (int i = 0; i < 100; i++, n--) {
                vld.write(1);
                int v = rand() & 0x3f;
                if (i % 10 == 5) v += 100;
                d.write(v & 0xff);
                wait();
            }
        }
        vld.write(0);
        done.notify();
    }
};
// sink
SC_MODULE(Sink) {
    sc_in<bool> clk, xrst;
    sc_in<bool> i_dut_vld, i_exp_vld;
    sc_in<uint32_t> i_dut_d, i_exp_d;
    int n_errors;
    SC_CTOR(Sink) : n_errors(0) {
        SC_CTHREAD(proc, clk.pos());
        async_reset_signal_is(xrst, false);
    }
    void proc() {
        std::queue<uint32_t> dut_d = std::queue<uint32_t>();
        std::queue<uint32_t> exp_d = std::queue<uint32_t>();
        int cnt = 0;
        wait();
        while (true) {
            if (i_dut_vld.read()) dut_d.push(i_dut_d.read());
            if (i_exp_vld.read()) exp_d.push(i_exp_d.read());
            if (!dut_d.empty() && !exp_d.empty()) {
                uint32_t d = dut_d.front();
                uint32_t e = exp_d.front();
                const char *jdg;
                if (d == e) {
                    jdg = "OK";
                } else {
                    jdg = "NG";
                    n_errors++;
                }
                std::cout << '[' << cnt++ << "] : " << jdg
                          << " dut = " << d << ", exp = " << e << std::endl;
                dut_d.pop();
                exp_d.pop();
            }
            wait();
        }
    }
};
// model
SC_MODULE(Model) {
    sc_in<bool> clk, xrst;
    sc_in<bool> i_vld;
    sc_in<uint32_t> i_d;
    sc_out<bool> o_vld;
    sc_out<uint32_t> o_d;
    SC_CTOR(Model) {
        SC_CTHREAD(proc, clk.pos());
        async_reset_signal_is(xrst, false);
    }
    void proc() {
        uint32_t d[3] = {0, 0, 0};
        uint32_t sum;
        bool vld[3] = {false, false, false};
        wait();
        while (true) {
            d[2] = d[1];
            d[1] = d[0];
            d[0] = i_d.read() & 0xff;
            vld[2] = vld[1];
            vld[1] = vld[0];
            vld[0] = i_vld.read();
            sum = d[1] * 2;
            sum += (vld[2])? d[2] : d[1];
            sum += (vld[0])? d[0] : d[1];
            o_vld.write(vld[1]);
            o_d.write((sum + 2) / 4);
            wait();
        }
    }
};
// testbench
SC_MODULE(Testbench) {
    sc_in<bool> clk;
    sc_signal<bool> xrst, i_vld, o_dut_vld, o_exp_vld;
    sc_signal<uint32_t> i_d, o_dut_d, o_exp_d;
    Source src;
    Sink snk;
    Vlpf dut;
    Model model;
    SC_CTOR(Testbench) : src("src"), snk("snk"), dut("dut"), model("model") {
        src.clk(clk);
        src.xrst(xrst);
        src.vld(i_vld);
        src.d(i_d);
        dut.clk(clk);
        dut.xrst(xrst);
        dut.i_vld(i_vld);
        dut.i_d(i_d);
        dut.o_vld(o_dut_vld);
        dut.o_d(o_dut_d);
        model.clk(clk);
        model.xrst(xrst);
        model.i_vld(i_vld);
        model.i_d(i_d);
        model.o_vld(o_exp_vld);
        model.o_d(o_exp_d);
        snk.clk(clk);
        snk.xrst(xrst);
        snk.i_dut_vld(o_dut_vld);
        snk.i_dut_d(o_dut_d);
        snk.i_exp_vld(o_exp_vld);
        snk.i_exp_d(o_exp_d);
        SC_THREAD(test);
    }
    void test() {
        xrst = 0;
        wait(25, SC_NS);
        xrst = 1;
        wait(src.done);
        for (int i = 0; i < 10; i++) wait(clk.posedge_event());
        std::cout << "#error(s) = " << snk.n_errors << std::endl;
        sc_stop();
    }
};
int sc_main(int argc, char *argv[]) {
    Verilated::commandArgs(argc, argv);
    sc_clock clk("clk", 10, SC_NS);
    Testbench *tb = new Testbench("tb");
    tb->clk(clk);
#ifdef DUMP_VCD
    Verilated::traceEverOn(true);
    VerilatedVcdSc *vcd = new VerilatedVcdSc();
    tb->dut.trace(vcd, 99);
    vcd->open("test_lpf.vcd");
    sc_start();
    vcd->close();
#else
    sc_start();
#endif // #ifdef DUMP_VCD
    return 0;
}

ざっくり説明すると、Sourceが作ったランダムデータを、検証対象とモデルModelに入力。そして、それらからの出力をSinkが受けて比較。そんな環境をTestbench階層で組み上げている。ついでに、SystemCのメイン関数であるsc_main()にて、Verilatorの波形ファイル作成機能を使用してみている。Verilatorを使ってテストを走らせるには、以下のコマンドを実行。

% verilator --sc lpf.v --trace --exe test_lpf.cpp
% cd obj_dir
% make -f Vlpf.mk
% ./Vlpf

う〜ん、何ともかったるい。Verilogなんて腐った言語は早くまともな言語に置き換えられてくれと思っているけど、SystemCはその器ではないかな。…って言うか、C++も言語仕様はかなり酷い部類だと思うし。

2013年9月4日水曜日

SystemCはじめました、その2

モジュールの話。

まずは手っ取り早く、記述例を挙げてしまおう。入力データを累積するVerilogモジュールと、そのSystemC版。SystemC版は短いので宣言も定義もごった煮にしてしまった。

Verilog版

module acc (
    input           clk,
    input           reset_n,
    input           i_en,
    input           i_clr,
    input   [7:0]   i_d,
    output  [7:0]   o_d
);
reg     [7:0]   r_d;
always @(posedge clk or negedge reset_n) begin
    if (!reset_n)
        r_d[7:0] <= 8'h00;
    else if (i_clr)
        r_d[7:0] <= 8'h00;
    else if (i_en)
        r_d[7:0] <= r_d[7:0] + i_d[7:0];
end
assign o_d[7:0] = r_d[7:0];
endmodule

SystemC版

#include <systemc>
SC_MODULE(acc) {
    sc_core::sc_in<bool> clk;
    sc_core::sc_in<bool> reset_n;
    sc_core::sc_in<bool> i_en;
    sc_core::sc_in<bool> i_clr;
    sc_core::sc_in< sc_dt::sc_uint<8> > i_d;
    sc_core::sc_out< sc_dt::sc_uint<8> > o_d;
    sc_dt::sc_uint<8> r_d;
    SC_CTOR(acc) {
        SC_METHOD(accumlate);
        sensitive << clk.pos() << reset_n.neg();
    }
    void accumlate() {
        if (!reset_n.read()) {
            r_d = 0;
        } else if (i_clr.read()) {
            r_d = 0;
        } else if (i_en.read()) {
            r_d += i_d.read();
        }
        o_d.write(r_d);
    }
};

SystemCの記述を頭からなめて行こう。

まずはSystemCのヘッダsystemcをインクルードしている。互換性のためにsystemc.hも残されているが、systemc.hはやたらめったらusingしまくるので、これから新規で書くコードではsystemcを使った方がいいかな。

モジュール宣言にはSC_MODULEを使う。これはsc_core::sc_moduleのサブクラスを作るマクロで、SystemCのモジュールとはsc_core::sc_moduleのサブクラス。

入力ポートはsc_core::sc_in<>、出力ポートはsc_core::sc_out<>のインスタンスで表す。FFは単純に、モジュールのデータメンバで表現すればよいだろう。

モジュールのコンストラクタは、SC_CTORマクロを使用して定義している。SC_CTORで宣言・定義できるのは、sc_core::sc_module_name型の引数を1つ取るコンストラクタ。引数を変えたい場合は、普通にコンストラクタを定義する。

コンストラクタの中身では、SC_METHODマクロを使用してaccumlate()メソッドをclk入力の立ち上がりかreset_n入力の立ち下がり毎に呼び出すよう、指示している。

accumlate()の中身では、入力に応じて初期化や加算を行い、その結果を出力している。見ての通り、入力ポートから入力されている値を取得するにはread()、出力ポートへ出力する値を設定するにはwrite()を用いる。

2013年9月3日火曜日

SystemCはじめました

ちょっとSystemCが必要になるかもしれないので、手を出してみることにした。

何はともあれ、まずはインストール。名前とメールアドレスを登録してこちらからsystemc-2.3.0.tgzをダウンロードして解凍。…って、何だこりゃ? .tgzなんてサフィックスを付けておきながら、tarでまとめただけでgzipでは圧縮してないじゃないか。tar zxvfで解凍できないから、何事かと思ったぞ。

さて、いきなりケチがついたけれど、解凍できてしまえば中にあるINSTALLに書いてある通りの手順でOK。昔のバージョンはあれこれ手を入れないとビルドできなかったりしたらしいけど、さすがにもう、そんなことはなくなった模様。LMDE64ビットとMountain Lionで、問題なくインストールできた。