Learn SystemC with Examples #9 (Clock~Interrupt a thread while busy, template class)

Clock

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

sc_clockはsc_signalから派生したプリミティブチャネルで、クロックのモデリングに使われる。
クロックの値やイベントはsc_signal_in_if<bool>インタフェース経由でアクセスできる。
コンストラクタの各引数の意味は上のリンク参照。

Process: CTHREAD

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

SC_CTHREAD:

  1. SystemC 2.0 では deprecated. 2つ目の引数がイベントファインダーの場合はまだサポートされる。
  2. プロセス登録時にクロックを必要とする。
  3. SC_METHODやSC_THREADのようなセンシティビティリストを持たない
  4. 指定したクロックエッジが起こった時に起動される。

Handle trigger while busy

https://www.learnsystemc.com/pattern/trigger_when_busy

スレッドがBusy状態の時、来ているトリガーをlistenしていない。ありうるデザインパターンとして以下がある。

  1. 2つのスレッドを作る。トリガーハンドラーとタスクプロセッサ。
  2. トリガーハンドラーでのアクション
    • トリガーを受信したとき、タスクプロセッサがBusyかどうか確認し、Busyでなければタスクをディスパッチする。
    • そうでなければ、トリガーをバッファする。
  3. タスクプロセッサでのアクション
    • トリガーハンドラによってディスパッチされたタスクの実行
    • 完了後、トリガーバッファをチェックする
      • 空ならreturn。そうでなければバッファからトリガーをpopし、タスクを実行

この方式はFIFOチャネルと似ている。しかし、オリジナルのトリガー型を修正する必要がない。

Handle trigger while busy, template class

https://www.learnsystemc.com/pattern/trigger_when_busy2

前チャプターと同様の機構をテンプレートクラスによって実現している。

Interrupt a thread while busy

https://www.learnsystemc.com/pattern/interrupt_when_busy

スレッドが割り込みを受けるには以下が必要

  1. スレッド実行中に割り込みをモニタ
  2. 割り込み処理をする
    • abort もしくは
    • pause してあとで resume

Interrupt a thread while busy, template class

https://www.learnsystemc.com/pattern/interrupt_when_busy2

前チャプターと同様の機構をテンプレートクラスによって実現している。

Learn SystemC with Examples #8 (Primitive Channel~Customized Data Type)

Primitive Channel

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

sc_prim_channelとは

  1. すべてのプリミティブチャネルの基底クラス
  2. updateフェーズへの固有のアクセスを提供する
  3. 階層、ポート、プロセスを含まない
  4. 階層チャネルと同様、プリミティブチャネルはinterface method call paradigmによって呼び出されるパブリックなメンバ関数を提供する。
    • request_update(): スケジューラに対してチャネルのupdate requestをキューする
    • async_request_update():
      • ↑のスレッドセーフ版。SystemCカーネル以外のOSのスレッドから呼び出される。
      • SystemCカーネルから呼び出すことは推奨されない。
    • update()
      • request_update、async_request_update呼び出しに応じて、updateフェーズ中にスケジューラによってコールバックされる。
      • アプリケーション側でこのメンバ関数をオーバーライドしてよい。sc_prim_channel での関数定義は空である。
      • 典型的には、現オブジェクトのデータメンバを読んだり更新したり、デルタ通知を作ったりする。
      • してはいけないこと
        • sc_prim_channel のメンバ関数のコール。ただし、親クラスでoverrideされたupdate関数それ自身は除く。
        • 即時通知を作るため、引数なしでsc_eventのnotify()メンバ関数を呼び出すこと
        • プロセス制御のための sc_process_handle のメンバ関数サスペンド、killなど)
        • 現オブジェクトのデータメンバを除くstorageの状態変更
        • 現オブジェクト以外のプリミティブチャネルインスタンスのread
        • チャネルインタフェース以外のメソッド呼び出し。特に、update()はsignalに対してwriteしてはならない。
    • next_trigger()
    • wait()

チャネルは一つ以上のインタフェースを実装する必要がある。つまり、インタフェースクラスを継承する必要がある。インタフェースはチャネルに必要なメソッドを提供する。

Hierarchical Channel

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

階層チャネルとは

  1. sc_channel基底クラスから継承する。sc_channelはsc_moduleと全く同一である。つまり、階層チャネルとはモジュールである。
  2. なんらかのinterfaceから継承する。ポートに接続するためである。

モジュールと同様、階層チャネルはプロセスやポートを持つことができる。

この例では、sc_signal_inout_if<int>を実装したカスタム階層チャネルを示している。sc_signal_inout_ifで定義されている以下の関数を実装する必要がある。

  1. void write(const int&)
  2. const int& read() const
  3. const sc_event& value_changed_event() const
  4. const sc_event& default_event() const
  5. const int& get_data_ref() const
  6. bool event() const

補注:SystemC-3.0の場合、SC_HAS_PROCESSは削除する必要がある。

-   SC_HAS_PROCESS(SIGNAL);

Trace File

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

  1. シミュレーション中の一連の値の変化を時間順に記録
  2. VCDファイル書式を使う
  3. sc_create_vcd_trace_fileによってのみ作成とオープンが行われる
  4. エラボレーション中もしくはシミュレーション中にオープンされる
  5. sc_trace によってトレースされている値のみ保持する
  6. 値がトレースされる前にファイルがオープンされている必要がある。ファイルが開かれてから1デルタサイクル以上経過しないと値がトレースされない
  7. sc_close_vcd_trace_file によってファイルがクローズされる。シミュレーションの最後のデルタサイクル前にクローズされるべきでない。

Error and Message Report

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

sc_report

  1. sc_report_handler::report 関数によって精されるレポートインスタンス
  2. 与えられたseverity levelとメッセージタイプにSC_CACHE_REPORTがセットされているアプリケーションが読み取れる。
  3. レポートハンドラーによって投げられたときにアプリケーションよってキャッチされることも。

sc_report_handler

  1. テキストレポートを書きだす機能を持つ。レポート生成時のアプリケーション固有のふるまいも定義できる。

sc_severityはレポートのseverity levelを表現する。

  1. enum sc_severity {SC_INFO = 0, SC_WARNING, SC_ERROR, SC_FATAL, SC_MAX_SEVERITY};
  2. 4つのレベルがあり、SC_MAX_SEVERITY は severity levelでない。

sc_verbosity は verbosity levelsを示す。sc_report_handler::set_verbosity_level() 関数や sc_report_handler::report() 関数に渡すときの引数。

  1. enum sc_verbosity {SC_NONE = 0, SC_LOW = 100, SC_MEDIUM = 200, SC_HIGH = 300, SC_FULL = 400, SC_DEBUG = 500};

sc_actions はビット単位で異なるアクションを表現する。
enum {
SC_UNSPECIFIED = 0x0000, //is not an action, serves as the default value meaning that no action has been set.
SC_DO_NOTHING = 0x0001, // is a specified action
SC_THROW = 0x0002,
SC_LOG = 0x0004,
SC_DISPLAY = 0x0008,
SC_CACHE_REPORT = 0x0010,
SC_INTERRUPT = 0x0020,
SC_STOP = 0x0040,
SC_ABORT = 0x0080
}

それぞれのseverity levelにたいしてデフォルトのアクションが関連付けられている。set_actions関数呼び出しでオーバーライド可能。デフォルトアクションは以下の通り。
#define SC_DEFAULT_INFO_ACTIONS ( SC_LOG | SC_DISPLAY )
#define SC_DEFAULT_WARNING_ACTIONS ( SC_LOG | SC_DISPLAY )
#define SC_DEFAULT_ERROR_ACTIONS ( SC_LOG | SC_CACHE_REPORT | SC_THROW )
#define SC_DEFAULT_FATAL_ACTIONS ( SC_LOG | SC_DISPLAY | SC_CACHE_REPORT | SC_ABORT )

void report(sc_severity, const char* msg_type, const char* msg, [int verbosity], const char* file, int line)
関数は、レポートを生成し、適したアクションを起こす。

  1. 1つめの引数で渡されたseverity、2つ目の引数で渡されたメッセージタイプを使って、アクションを決定する。以前呼び出されたset_actions, stop_after, suppress, forceにも基づく。
  2. 5つすべての引数を使って sc_report オブジェクトを作り、set_handlerによって設定されたハンドラーにそのオブジェクトを渡す。
  3. report関数を呼び出した以降はオブジェクトは保持されない。ただし、アクションに SC_CACHE_REPORT が設定されている場合は get_cached_reports 関数によってオブジェクトを再び取得できる。
  4. report()関数では実行されるアクションの決定を担う。set_handler関数によって設定されるハンドラー関数はそのアクションの実行を担う。
  5. 生成されたレポート数を管理する。このカウント数はアクションが実行されようが抑制されようがインクリメントされる。ただし、verbosity levelによってレポートが無視された場合は除く。

set_actions():

  1. report()メンバ関数によってとられるアクションの設定。
  2. 以前呼び出したseverity, メッセージタイプ、あるいはその組み合わせと同じ場合はアクションを上書きする。

stop_after():

  1. 引数で与えられた制限数にレポート数が到達するとsc_stop()を呼び出す。

get_count():

  1. レポート数を返す

Verbosity level:

  1. int set_verbosity_level(int): 最大verbosity levelの設定。
  2. int get_verbosity_level(): 最大verbosity levelを返す。

Customized Data Type

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

sc_signal<T> と sc_fifo<T> は様々なデータタイプが使用できる。SystemCの組み込み型はすでに対応済みである。
カスタムデータタイプを使うには、以下のメンバ関数を実装する必要がある。

  1. operator=(): read/writeメソッドに必要
  2. operator==(): sc_signal::value_changed_event() に必要
  3. ostream& operator<<(): 出力に必要
  4. sc_trace(): SystemCのトレース機構への組み込みに必要。波形ビューアでも見れるようになる。

Learn SystemC with Examples #7 (Communication: port~Communication: port array)

Communication: port

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

通信にはキーとなる概念が3つある。

  1. インタフェース
    • sc_interface から派生した抽象クラス。sc_objectからは派生していない。
    • 純粋仮想関数を持ち、それらはインタフェースを継承したチャネル内で実装される。
  2. ポート
    • ポートはインタフェースメソッドを呼び出されたとき、バインドされているチャネルにメソッド呼び出しを転送する。
    • ポートでは必要なサービスの集合を定義する。
  3. チャネル
    • sc_prim_channel はすべてのプリミティブチャネルの基底クラス。
    • チャネルはパブリックなメンバ関数を提供し、それらはinterface method call paradigmで使用される。
    • プリミティブチャネルは1つ以上のインタフェースを実装する。

つまり、

  • ポートはサービスを必要とし、インタフェースはサービスを定義(=API定義)し、チャネルはサービスを実装(=API実装)する。
  • ポートはチャネルに接続(紐づけ)できる。その場合、チャネルはポートが必要とするインタフェースを実装している必要がある。
  • ポートは基本的にチャネルへのポインタのように扱える。

ポートを使うケース

  1. モジュール内でそのモジュールの外部のチャネルのメンバ関数を呼び出したいとき。ポート経由でインタフェースメソッドコールをする。そうしないと悪いコーディングスタイルとなる。
  2. ただし、モジュール内のチャネルのメンバ関数を呼び出すときは直接呼び出すことができる。これはportless channel accessとして知られている。
  3. あるモジュールが子モジュールのチャネルのメンバ関数を呼び出したい場合、子モジュールのexportを経由して行われる。

Communication: export

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

sc_exportとは

  1. モジュールが親モジュールに対してインタフェースを提供できるようにする
  2. exportに紐づいたチャネルにインタフェースメソッドコールを転送
  3. exportを持つモジュールによって提供されるサービスを定義できる

いつ使うか

  1. exportを通じてインタフェースを提供することは、モジュールの中でインタフェースを実装することの代替となる。
  2. 明示的なexportの使用により、モジュールは構造化された方法で複数のインタフェースを提供できるようになる。
  3. あるモジュールが子モジュール内のチャネルインタフェースに属するメンバ関数を呼び出すとき、子モジュールのexportを経由すべき。

Communication: port 2 port

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

今まで見てきたケース

  1. 同じモジュール内の2つのプロセスがチャネル経由で接続
    • process1() --> channel --> process2()
  2. 別モジュールの2つのプロセスがポートとチャネル経由で接続
    • module1::process1() --> module1::port1 --> channel --> module2::port2 --> module2::process2()
  3. 別モジュールの2つのプロセスがexport経由で接続
    • module1::process1() --> module1::channel --> module1::export1 --> module2::port2 --> module2::process2()

これらのケースではすべてポートの接続にチャネルが必要だった。モジュールのポートからサブモジュールのポートに直接できる接続できる特別なケースがある。

    • module::port1 --> module::submodule::port2

補注:チャネル経由はインタフェースがin/out異なってるが、サブモジュールへの直接接続時はin同士またはout同士の接続。

Communication: specialized ports

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

sc_portクラスでポートを宣言する以外に、様々な特殊化されたポートクラスがある。異なったチャネルタイプや追加の機能がある。

  1. sc_in: signalを使う
  2. sc_fifo_in: fifo読み込み
  3. sc_fifo_out: fifo書き込み
  4. sc_in&ly;bool>, sc_in<sc_dt::sc_logic>: value_changed(), pos(), neg()
  5. sc_inout: signalを使う。value_changed(), initialize()
  6. sc_inout<bool>, sc_inout<sc_dt::sc_logic>: value_changed(), initialize(), pos(), neg()
  7. sc_out: sc_inoutの派生クラス。
  8. sc_in_resolved: sc_in<sc_dt::sc_logic> の派生クラス。
  9. sc_inout_resolved: sc_inout<sc_dt::sc_logic> の派生クラス。
  10. sc_out_resolved: sc_inout_resolvedの派生クラス。
  11. sc_in_rv: sc_in<sc_dt::sc_lv<W>>の派生クラス。
  12. sc_inout_rv: sc_inout<sc_dt::sc_lv<W>>の派生クラス。
  13. sc_out_rv: sc_inout_rvの派生クラス。

sc_port<sc_signal_inout_if<int>> はsignalが提供するメンバ関数のみにアクセスできる。
1. read()
2. write()
3. default_event()

  • sc_sensitiveの<<演算子によって静的センシティビティを定義するためにポートが使われた場合に呼び出される。

4. event()

  • イベントが発生したかをチェックする。true/falseを返す。

5. value_changed_event()

  • 値が変化したイベント

sc_port<sc_signal_inout_if<bool>> は追加で signal<bool>が提供するメンバ関数にアクセスできる。
6. posedge()
7. posedge_event()
8. negedge()
9. negedge_event()

sc_inout<> は追加のメンバ関数がある。
10. initialize()

  • チャネルに紐づけられる前に初期値を定義

11. value_changed()

  • チャネルに紐づけられる前にセンシティビティを設けるために使う

bool, sc_logic, sc_inout<bool> チャネルはさらに以下のメンバ関数がある。
12. pos()
13. neg()

1~9のメンバ関数はsignal channelによって提供される。port->method()で呼び出す。
10~13のメンバ関数は特殊化ポートによって提供される。port.method()で呼び出す。

補注:
クラスの親子関係は以下が参考になる。
https://eda-playground.readthedocs.io/en/latest/_static/systemc-2.3.1/sysc/inherits.html

Communication: port array

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

ポートを宣言するとき、

  1. 一つ目のテンプレート引数はインタフェース名を指定する。ポートの種別でもある。
    • ポートはポート種別に応じたチャネル、port, export にのみ紐づけできる。
  2. 二つ目のテンプレート引数はOptionalでチャネルインタフェースの最大数を整数値で指定できる。
    • デフォルトは1
    • もし値が0なら、チャネルインタフェースと無限に紐づけできる
    • 許容される数以上のチャネルと紐づけするとエラーになる
  3. 三つ目のテンプレート引数はOptionalでsc_port_policy型のポートポリシーを指定できる。複数のポートを紐づけしたときのルールや紐づいてないポートのルールが決定される。
    • デフォルト SC_ONE_OR_MORE_BOUND: ポートは一つ以上のチャネルと紐づけされる。最大値は2つ目の引数で指定される。エラボレーションの最後で紐づけされていないままならエラー。
    • SC_ZERO_OR_MORE_BOUND: ポートは0以上のチャネルと紐づけされる。最大値は2つ目の引数で指定される。エラボレーションの最後で紐づけされていないままでもよい。
    • SC_ALL_BOUND: ポートは2つ目の引数で指定される1以上の値とちょうど同じだけのチャネルインスタンスと紐づけされる必要がある。
      • 2つ目の引数が0の場合、SC_ONE_OR_MORE_BOUNDと同じ意味になる。
      • 紐づけされたチャネル数が不足する場合はエラーになる。

ポートを同じ(?)チャネルに複数回紐づけするのはエラーになる。

C++の構文を使ってポートアレイを定義することもできる。
sc_port<IF> p[10] or vector<sc_port<IF>> p(10);

Learn SystemC with Examples #6 (Signal: read and write~Buffer)

Signal: read and write

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

sc_signalとは

  1. プリミティブチャネルであり、電気信号を運ぶワイヤーをモデリングするために使う
  2. evaluate-update方式を使う。これにより、同時read/writeのふるまいが一意になる。現在の値と新しい値を維持する。
  3. write() メソッドは現在の値と新しい値が異なるときにupdateリクエストを出す。
  4. sc_signal_inout_if<T> インタフェースがある。

コンストラク

  1. sc_signal()
    • ベースクラスの sc_prim_channel(sc_gen_unique_name("signal")) 呼び出し
  2. sc_signal(const char* name_)
    • ベースクラスの sc_prim_channel(name_) 呼び出し

メンバ関数

  1. T& read() or operator const T& ()
    • 現在の値の参照を返す。信号の状態を変化させない。
  2. void write(const T&)
    • 次デルタ時刻に値を更新する。
  3. operator=
    • write() と等価
  4. sc_event& default_event(), sc_event& value_changed_event()
    • 値が変化したときのイベントの参照を返す。
  5. bool event()
    • 同一シミュレーション時刻の直前のデルタ時刻のupdateフェーズでちょうど値の更新が行われた場合のみ true

sc_fifoとの比較

  1. sc_signal のスロット数は1
  2. sc_signal は現在の値と新しい値が異なる場合のみupdateリクエストをトリガーする
  3. sc_signal の read は値を削除しない。

executionフェーズ以外のsc_signalについて

  1. 初期値をエラボレーション中に書き込める
  2. sc_mainからエラボレーション中あるいはシミュレーション停止中に書き込める。sc_startの呼び出し前でも呼び出し後でも可能。

補注:要するにVerilogでのノンブロッキング代入と同じ。

Signal: detect event

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

  1. sc_event& default_event(), sc_event& value_changed_event()
    • value-changed eventの参照を返す。
  2. bool event()
    • 同一シミュレーション時刻の直前のデルタ時刻のupdateフェーズでちょうど値の更新が行われた場合のみ true

Signal: many writers

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

sc_signalのクラス定義
template <class T, sc_writer_policy WRITER_POLICY = SC_ONE_WRITER> class sc_signal: public sc_signal_inout_if<T>, public sc_prim_channel {}

  1. WRITER_POLICY == SC_ONE_WRITER の場合、複数のプロセスから信号が書き込まれるとエラー
  2. WRITER_POLICY == SC_MANY_WRITERS の場合、
    • 複数のプロセスが同時に信号に書き込むとエラーになる
    • 異なるデルタ時刻であれば異なるプロセスが書き込むことは可能

デフォルトはSC_ONE_WRITER。
読み込みは同一時刻か異なる時刻かにかかわらず複数のプロセスからread可能。

補注:
サンプルコード中、writer2() スレッドの s1.write(v); のコメントアウトを有効化すると以下のようにランタイムエラーになる。
信号s1に対して複数のドライバが存在するためである。
一方、s2に対する複数ドライバは SC_MANY_WRITERS 指定によりエラーにならない。
s2でも同時書き込みが発生するとエラーになるが、writer2内で同時書き込みが起こらないようデルタ遅延が挿入されているためエラーは起きない。
同時書き込みが起きてもエラーにならない別の機構については次のチャプターで紹介されている。

Error: (E115) sc_signal<T> cannot have more than one driver:
 signal `consumers.signal_0' (sc_signal)
 first driver `consumers.writer1' (sc_thread_process)
 second driver `consumers.writer2' (sc_thread_process)
In file: ../../../src/sysc/communication/sc_signal.cpp:67
In process: consumers.writer2 @ 0 s

Resolved Signal

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

Resolved signalとは、sc_signal_resolved 型あるいは sc_signal_rv 型のオブジェクトである。
sc_signal との違いは、resolved signalは複数のプロセスからの同時書き込みを許容し、チャネル内で衝突を解決する点である。

  1. sc_signal_resolved はプリミティブチャネルであり、sc_signalの派生クラスである。
  2. sc_signal_rv はリミティブチャネルであり、sc_signalの派生クラスである。
    • sc_signal_rv は sc_signal_resolved と似ている。
    • 違いはテンプレート引数。クラス定義は以下の通り。
      • class sc_signal_resolved: public sc_signal<sc_dt::sc_logic,SC_MANY_WRITERS>
      • template <int W> class sc_signal_rv: public sc_signal<sc_dt::sc_lv<W>,SC_MANY_WRITERS>

解決テーブルは以下の通り。補注:Verilogwireと同じ。

  | 0 | 1 | Z | X |
0 | 0 | X | 0 | X |
1 | X | 1 | 1 | X |
Z | 0 | 1 | Z | X |
X | X | X | X | X |

sc_signal<bool>

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

sc_signal_in_if<bool> と sc_signal_in_if<sc_dt::sc_logic>は追加のメンバ関数を持つ。

  1. posedge_event(), negedge_event()
    • 値が変化し、新しい値が 1/0 の時にいつでも通知を出すイベントの参照を返す
  2. posedge(), negedge()
    • 直前のデルタ時刻で値が更新され、新しい値が 1/0 の時にtrueを返し、そうでなければfalseを返す。

Buffer

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

sc_bufferはプリミティブチャネルであり、sc_signalの派生クラスである。
sc_signalとの違いはvalue-changed eventが値の書き込みが行われたときに発生する点である。

  1. sc_signal の場合、現在の値が1で1が書き込まれたとき、 イベントはトリガーされない。
  2. sc_buffer の場合、現在の値が1で1が書き込まれたとき、 イベントはトリガーされる。

Learn SystemC with Examples #5 (Mutex~FIFO)

Mutex

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

Mutexとは

  1. チャネルの一種であり、排他制御ロックをモデリングするためのもの。
  2. lockedとunlockedの2つのステートがある
    • Mutexをロックできるプロセスは同時に1つまで。
    • Mutexはロックされたプロセスからしかアンロックできない。アンロックされた後は別のプロセスからもロックできる。

メンバー関数

  1. int lock()
    • unlocked状態ならMutexをロックしリターン
    • locked状態ならMutexがアンロックされるまでサスペンド
    • 複数のプロセスが同時(同デルタ時刻)にロックを試みた場合、どのプロセスがロックできるかは未定義
    • 常に0を返す
  2. int trylock()
    • unlocked状態ならMutexをロックし0を返す
    • locked状態ならすぐに-1を返す
  3. int unlock()
    • unlocked状態なら-1を返す
    • locked状態でロックしたプロセスとは別のプロセスが呼び出した場合は-1を返す。
    • locked状態でロックしたプロセスと同じプロセスが呼び出した場合はアンロックし0を返す。
      • ほかのプロセスにアンロックされたことを伝達するために即時通知が使われる。

Semaphore

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

セマフォとは

  1. チャネルの一種であり、同時アクセス数が制限された状態で共有資源をアクセスできる仕組みをモデリングするためのもの
  2. 整数値を持つ(セマフォ値と呼ぶ)。セマフォ生成時に最大同時アクセス数を設定できる。
    • 初期値1の場合、セマフォはMutexと等価である。

メンバー関数

  1. int wait()
  2. int trywait()
  3. int post()
    • セマフォ値をインクリメントする
    • 待ちプロセスに対してセマフォ値がインクリメントされたことを伝達するため即時通知を使う
    • 常に0を返す
  4. int get_value()

FIFO

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

sc_fifo

  1. プリミティブチャネルであり、FIFOモデリングするために使う。
  2. 値を蓄えるスロットがある。スロット数はオブジェクト生成時に固定される。
  3. sc_fifo_in_if, sc_fifo_out_ifのインタフェースがある。

コンストラク

  1. explicit sc_fifo(int size_ = 16)
    • 基底クラスのコンストラクタを以下のようにイニシャライザリストから呼び出す。
      • sc_prim_channel(sc_gen_unique_name( "fifo" ))
  2. explicit sc_fifo(const char* name_, int size_ = 16)
    • sc_prim_channel(name_)

スロット数size_で初期化する。スロット数は1以上でないといけない。

メンバ関数(read)

  1. void read(T&), T read():
    • FIFOからデータを取り出す(取り出したデータはFIFOから削除)
    • read順はwrite順と完全一致する
    • 現デルタ時刻に書き込まれたデータは読みだせない。読み出せるのは次デルタ時刻から
    • FIFOが空ならサスペンドする
  2. bool nb_read(T&)
    • 基本的にはreadと同じだが、FIFOが空の場合、サスペンドせずにすぐさまfalseを返す。FIFOが空でなければtrueを返す。
  3. operator T()
    • "operator T() {return read();}"と等価。

メンバ関数(write)

  1. write(const T&)
    • FIFOにデータを書き込む
    • 同一デルタ時刻に複数のデータが書き込まれることもありうる
    • 同一デルタ時刻にリードされて空きスロットが出たとしても、その空きスロットに書き込まれるようになるのは次のデルタ時刻から。
    • FIFOがFullならサスペンドする
  2. bool nb_write(const T&)
    • 基本的には write() と同じだが、FIFOがフルの時はサスペンドせずにすぐさまfalseを返す。フルでないときはtrueを返す。
  3. operator=
    • "sc_fifo& operator= (const T& a) {write(a); return *this;}" と等価

メンバ関数(event)

  1. sc_event& data_written_event()
    • データ書き込みイベントを返す。FIFOにデータが書かれたデルタサイクルの最後であるデルタ通知フェーズで通知される。
  2. sc_event& data_read_event()
    • ↑のread版

メンバ関数(有効データ、空きスロット)

  1. int num_available()
    • 現デルタサイクルでread可能なスロット数。同デルタサイクルですでにreadされたスロット数を差し引いた値を返す(writeされたスロット数は加算されない)
  2. int num_free()
    • 現デルタサイクルでwrite可能な空きスロット数。同デルタサイクルで既にwriteされたスロット数を差し引いた値を返す(readされたスロット数は加算されない)

Learn SystemC with Examples #4 (Initialization~Combined Event Queue)

Initialization

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

初期化は実行ステージの一部で、sc_start()の後に行われる。初期化中は以下の3ステップが順に行われる。

  1. updateフェーズの実行。ただし、delta notificationフェーズには移らない。
  2. 全メソッド(SC_METHOD)・スレッド(SC_THREAD)をrunnable状態リストに追加。ただし以下は除く。
    • dont_initialize() の指定があるプロセス
    • clocked thread (SC_CTHREAD)
  3. delta notificationフェーズの実行。完了後はevaluation phaseに移行。

注記

  1. update/delta notificationフェーズが必要なのは、updateリクエストがエラボレーション中に生成されるためである。これはプリミティブチャネルの初期値設定のために行われる。たとえばsc_inoutクラスのinitialize関数から呼ばれる。
  2. SystemC 1.0では
    1. スレッドプロセスはinitializationフェーズでは実行されない
    2. メソッドプロセスは入力信号/ポートにsensitiveな場合initializationフェーズで実行される。
  3. SystemC 2.0ではすべてのスレッドプロセスとすべてのメソッドプロセスがinitializationフェーズで実行される。もし1.0と2.0でスレッドプロセスの挙動が異なっている場合は、スレッドプロセス内の無限ループの直前にwait()文を一つ追加すると良い。
  4. initializationフェーズでのプロセスの実行順は不定である。
  5. dont_initlize()関数はinitializationフェーズでプロセスが実行されるのを抑制する。

以下ではdont_initlization()関数の有無による挙動の違いを見る。

#include <systemc>
using namespace sc_core;

SC_MODULE(INITIALIZATION) {
  sc_event e;
  SC_CTOR(INITIALIZATION) {
    SC_THREAD(trigger);
    SC_THREAD(catcher_3);
    sensitive << e;
    dont_initialize();
  }
  void trigger() {
    while (true) {
      e.notify(1, SC_SEC);
      wait(2, SC_SEC);
    }
  }
  void catcher_3() {
    while (true) {
      std::cout << sc_time_stamp() << ": catcher_3 triggered" << std::endl;
      wait(e);
    }
  }
};

int sc_main(int, char*[]) {
  INITIALIZATION init("init");
  sc_start(4, SC_SEC);
  return 0;
}

実行結果は以下の通り。イベントeが1秒後に起こり、その時初めて catcher_3() が実行される。直後にメッセージ出力。

1 s: catcher_3 triggered
3 s: catcher_3 triggered

上記のプログラムからdont_initialize() 文を取り除いて実行すると以下のようになる。catcher_3はinitializationフェーズで起動されるため、時刻0に実行される。

0 s: catcher_3 triggered
1 s: catcher_3 triggered
3 s: catcher_3 triggered

Process: Method

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

メソッドは

  1. 静的センシティビティを持つ
  2. next_trigger関数を使って動的センシティビティを作れるのはメソッドだけ
  3. 自分自身で即時通知を使ってrunnable状態にすることはできない。

next_trigger()は

  1. sc_module/sc_prim_channelクラスのメンバ関数、あるいはnon-member関数
  2. 以下から呼ばれる

注記

  • メソッドプロセス終了後はすべてのローカル変数は破棄される。メソッドプロセスに関連する状態を保存したい場合はモジュールのメンバ変数に退避する。

SC_METHOD, SC_THREADの再実行の違い

  1. SC_METHOD: 自身のスレッドがない。シミュレーション時間を消費できない。サスペンドできない。wait()を呼べない。
  2. SC_THREAD: 自身のスレッドがある。シミュレーション時間を消費できる。サスペンドできる。wait()を呼べる。

Event Queue

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

イベントキューは

  1. メンバ関数notify()を持つ。これはイベントと同じ。
  2. 階層的チャネルであり、複数のnotification待ちを持てる。これは一つのnotification待ちしか持てないイベントとは異なる。
  3. エラボレーション中にのみ生成できる。
  4. 即時通知をサポートしない。

メンバー関数

  1. void notify(double, sc_time_unit) or void notify(const sc_time&):
    • SC_ZERO_TIME: デルタ通知
    • non-zero time: notifyが呼ばれてからの指定時間後
  2. void cancel_all()
    • 全notification待ちを削除。デルタ通知も含む。

Combined Event Queue

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

  1. イベントキューは静的センシティビティで OR することができるが、ANDはできない。
  2. イベントキューは wait()の引数としては使えないため、動的センシティビティでは使用できない。

補注:
実行結果が少し興味深い。時刻2ではeq1, eq2 がそれぞれトリガ(計2回)するのでなく、トリガが1回だけになっている。
時刻指定でイベントが複数来るときはΔ=0の1回だけトリガが行われるらしい。

Learn SystemC with Examples #3 (Event~Sensitivity)

Event

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

イベントとはsc_event型のオブジェクトであり、プロセス間の同期に使われる。
イベント通知が起こると、プロセスインスタンスはトリガーもしくはレジュームされる。

sc_eventは以下のメソッドを持つ。

  1. void notify(): 即時通知
  2. void notify(const sc_time&), void notify(double, sc_time_unit)
    • ゼロ時間: デルタ(遅延後の)通知
    • non-zero時間: 一定時間後の通知
  3. cancel()
    • 通知待ちのイベントを削除
    • 即時通知はキャンセルできない

制約

  1. sc_eventオブジェクトはエラボレーション時でもシミュレーション時でも生成できる。
  2. イベントはエラボレーション時でもシミュレーション時でも通知できる。ただし、エラボレーション中もしくはコールバック(before_end_of_elaboration, end_of_elaboration, start_of_simulation)内で即時通知をするのはエラーとなる。

イベントは2つ以上の待ち状態の通知をもつことはできない。

  1. もし通知待ち状態のイベントオブジェクトからnotify()関数が呼ばれた場合、最も早い通知時間のnotify()のみが生き残る。
  2. それ以外のスケジュール済み通知はキャンセルされる
  3. 即時通知はデルタ通知より優先され、デルタ通知はnon-zero時間後通知より優先される。この優先順は関数の呼び出し順とは関係ない。

Combined Events

https://www.learnsystemc.com/basic/event_combined
元記事が十分簡潔なのでここで特筆すべき点はない。

Delta Cycle

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

いわゆるデルタ遅延に関する説明がされている。
(説明が面倒なので省略。VHDL, Verilogの本の方に詳しく説明されていると思う)
request_update(), update()関数のことにも触れられているがサンプルコードがないためここでは詳しい挙動がわからない。(Primitive Channelのチャプターで説明されてるっぽい)

Sensitivity

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

センシティビティとは、プロセスをレジュームしたりトリガーしたりするイベントもしくはタイムアウトの集合である。
センシティビティには静的と動的の2種類がある。

  1. 静的センシティビティ: エラボレーション中に固定される。モジュール内のそれぞれのプロセスのセンシティビティリストによって設定される。
  2. 動的センシティビティ: wait(), next_trigger() などによるもの

動的センシティビティと静的センシティビティの比較がサンプル中にある。
静的センシティビティは SC_THREAD直後に sensitive << e1 << e2; などのように書いて設定する。
感想:初めてこの文法を見たときは驚いた。正直今でも強い違和感を感じる。。。

SC_MODULE(SENSITIVITY) {
  sc_event e1, e2;
  SC_CTOR(SENSITIVITY) {
    SC_THREAD(catch_1or2_dyn);
  }
  void catch_1or2_dyn() {
    while (true) {
      wait(e1 | e2);
      std::cout << "Dynamic sensitivty: e1 or e2 @ " << sc_time_stamp() << std::endl;
    }
  }
};
SC_MODULE(SENSITIVITY) {
  sc_event e1, e2;
  SC_CTOR(SENSITIVITY) {
    SC_THREAD(catch_1or2_static);
    sensitive << e1 << e2;
  }
  void catch_1or2_static(void) {
    while (true) {
      wait();
      std::cout << "Static sensitivity: e1 or e2 @ " << sc_time_stamp() << std::endl;
    }
  }
};