Learn SystemC with Examples #1 (Hello World~SC_HAS_PROCESS)

www.learnsystemc.com
を試していく。まずはHello Worldから。

Hello World

https://www.learnsystemc.com/basic/hello_world

このページのサンプルコードをhello.ccとして保存する。Makefileを用意する。

include Makefile.config

PROJECT = hello
OBJS    = $(PROJECT).o

include Makefile.rules

Makefile.config, Makefile.rules は systemc インストールディレクトリの examplesの下にあるものを使う。
これで make を実行し、 ./hello.x を実行すると、以下のように正しく動作しているようだ。

$ ./hello.x

        SystemC 3.0.0_pub_rev_20231124-Accellera --- Feb 18 2024 18:43:12
        Copyright (c) 1996-2023 by all Contributors,
        ALL RIGHTS RESERVED
Hello world using approach 1
Hello world using approach 2

ソースコードを見ていく。

hello1() については完全なC++標準なので特に特筆すべき点はない。

C++のエントリーポイントはmain関数だが、SystemCのエントリーポイントはsc_main関数である。
SystemCのモジュールはsc_moduleベースクラスを継承したものである。
骨組みのみを抜き出すと以下の通り。

#include <systemc>
using namespace sc_core;

struct HelloWorld : sc_module {};

int sc_main(int, char*[]) {
  HelloWorld helloworld("helloworld");
  sc_start();
  return 0;
}

中身については次のチャプターで議論する。

SystemC Module

https://www.learnsystemc.com/basic/module


SystemCのモジュールはsc_moduleクラスを継承するクラスであり、Verilogのモジュールと同様の機能を持つ。

SystemCモジュールの定義は以下の3通りがある。

  1. SC_MODULE(module_name) {}: マクロによる定義。2.のシンタックスシュガー
  2. struct module_name: public sc_module {}: C++文法に基づく定義であり、1よりreadableである。
  3. class module_name : public sc_module { public: }: コンストラクタをpublicにしなければならないときはこうする。

SystemCモジュールは以下の注意点がある。

  1. sc_moduleオブジェクトはエラボレーション中にのみ構築可能である。シミュレーション中にインスタンシエーションするとエラーが発生しうる。
  2. sc_moduleは少なくとも一つ以上のコンストラクタを持つ必要がある。すべてのコンストラクタはsc_module_nameパラメータを必ず持つ必要があるが、さらに別のパラメータを持つことも可能。
  3. sc_moduleインスタンス名とコンストラクタに渡す文字列値は通常同じにしておくのが慣例。
  4. モジュール間通信は通常インタフェースメソッド呼び出し、すなわち、ポートにより行う。デバッグや診断用途ではほかのメカニズムの使用も許容される。

Constructor: SC_CTOR

https://www.learnsystemc.com/basic/sc_ctor

SC_CTORマクロはコンストラクタ定義のシンタックスシュガーであり、

  SC_CTOR(MODULE_A) {}

のような使い方をする。
sc_module_nameパラメータ以外のパラメータをコンストラクタに渡したい場合はSC_CTORマクロは使えず、

  MODULE_C(sc_module_name name, int i) : sc_module(name), i(i) {}

のような書き方をする。

補注:最新のIEEE 1666-2023規格ではSC_CTORにsc_module_name以外の追加のパラメータを入れることができるようになった。

https://systemc.org/events/scef202309/ の SystemC IEEE 1666 update のプレゼンテーション p.18 を参照。

実際に Systemc-3.0 で動くか試してみる。サンプルコード中、

  SC_CTOR(MODULE_C);
  MODULE_C(sc_module_name name, int i) : sc_module(name), i(i) {

を以下のように書き換えてコンパイルする。

  SC_CTOR(MODULE_C, int i) : i(i) {

make して実行すると、正しく動くことが確認できた。

$ ./hello.x

        SystemC 3.0.0_pub_rev_20231124-Accellera --- Feb 18 2024 18:43:12
        Copyright (c) 1996-2023 by all Contributors,
        ALL RIGHTS RESERVED
module_a
module_b
module_c, i = 1

SC_HAS_PROCESS

https://www.learnsystemc.com/basic/sc_has_process

SC_HAS_PROCESSはsc_module_name以外のパラメータをとるコンストラクタのためのマクロのようだ。

SC_HAS_PROCESSはSystemC v2.0で導入された。SC_CTORと比較してみよう。

#define SC_CTOR(user_module_name) typedef user_module_name SC_CURRENT_USER_MODULE; user_module_name( ::sc_core::sc_module_name )
#define SC_HAS_PROCESS(user_module_name) typedef user_module_name SC_CURRENT_USER_MODULE

これらの違いは、SC_CTORはコンストラクタ定義に関する記述が末尾にあるのに対して、SC_HAS_PROCESSにはそれがない点である。
SC_CTORとSC_HAS_PROCESSのどちらも現モジュール名をSC_CURRENT_USER_MODULEとして定義しており、SC_METHOD/SC_THREAD/SC_CTHREADを通じてシミュレーションカーネルにメンバー関数を登録する用途で使用される。

推奨される記述スタイルは以下の通り。

  1. シミュレーションプロセスでないモジュールはSC_CTORやSC_HAS_PROCESSを使用しない。
  2. コンストラクタに追加のパラメータが必要ない場合はSC_CTORを使用する。
  3. コンストラクタに追加のパラメータが必要な場合は SC_HAS_PROCESSを使用する。

補注:前チャプターとも関連するが、SC_HAS_PROCESSは最新のIEEE1666-2023ではdeprecatedとなった。

サンプルコードをコンパイルすると以下のようなエラーが出る。

systemc-3.0.0_pub_rev_20231129/include/sysc/kernel/sc_module.h:423:32: error: ‘sc_core::sc_has_process_used’ is deprecated: SC_HAS_PROCESS(user_module_name) is obsolete in IEEE 1666-2023, define SC_ALLOW_DEPRECATED_IEEE_API to suppress. [-Werror=deprecated-declarations]
  423 |         static_assert(sc_core::sc_has_process_used, "no-op to avoid stray ';'")
      |                                ^~~~~~~~~~~~~~~~~~~

Makefile.configを以下のように修正するとビルドが通る。

#FLAGS_COMMON = -g -Wall -std=c++17
FLAGS_COMMON = -g -Wall -std=c++17 -DSC_ALLOW_DEPRECATED_IEEE_API

この修正なしにビルドが通るようにサンプルコードを書き換えてみると以下のようになる。

#include <systemc>
using namespace sc_core;

SC_MODULE(MODULE_A) {
  MODULE_A(sc_module_name name) {
    std::cout << this->name() << ", no SC_CTOR or SC_HAS_PROCESS" << std::endl;
  }
};

SC_MODULE(MODULE_B1) {
  SC_CTOR(MODULE_B1) {
    SC_METHOD(func_b);
  }
  void func_b() {
    std::cout << name() << ", SC_CTOR" << std::endl;
  }
};

SC_MODULE(MODULE_C) {
  const int i;
  SC_CTOR(MODULE_C, int i) : i(i) {
    SC_METHOD(func_c);
  }
  void func_c() {
    std::cout << name() << ", additional input argument" << std::endl;
  }
};

SC_MODULE(MODULE_D1) {
  SC_CTOR(MODULE_D1);
  void func_d() {
    std::cout << this->name() << ", SC_CTOR inside header, constructor defined outside header" << std::endl;
  }
};
MODULE_D1::MODULE_D1(sc_module_name name) : sc_module(name) {
  SC_METHOD(func_d);
}

SC_MODULE(MODULE_E) {
  MODULE_E(sc_module_name name);
  void func_e() {
    std::cout << this->name() << ", SC_HAS_PROCESS outside header, CANNOT use SC_CTOR" << std::endl;
  }
};
MODULE_E::MODULE_E(sc_module_name name) {
  SC_METHOD(func_e);
}

int sc_main(int, char*[]) {
  MODULE_A module_a("module_a");
  MODULE_B1 module_b1("module_b1");
  MODULE_C module_c("module_c", 1);
  MODULE_D1 module_d1("module_d1");
  MODULE_E module_e("module_e");
  sc_start();
  return 0;
}