Introduction
ここではインターコネクトコンポーネントを通じてトランザクションが伝搬していくのを見ていく。すなわち、ルーターがイニシエータといくつかのターゲットメモリの間に配置される。
ルーターはトランザクション、DMI、デバッグトランザクションをターゲットに転送する必要があり、関数呼び出しやトランザクションがイニシエータに戻っていくリターンパスも管理する必要がある。
ここでは2つの課題に取り組む必要がある。
・1つのターゲットソケットと複数のイニシエータソケットを持つインターコネクトコンポーネントをどのようにモデリングするか?
・ルーターを介してfoward/backwardトランザクションでアドレスをどのように処理するか?
An Interconnect Component
イニシエータとターゲットメモリはTutorial 2の例ととても似ているが、ここではそれらの間にルーターを追加する。ルーターはTLM-2インタコネクトコンポーネントとしてモデリングされる。つまり、そのコンポーネントではターゲットソケットから入ってくるトランザクションをイニシエータソケットに出力する。この例では4つのイニシエータソケットが4つのメモリインスタンスに接続される。
トップ階層の接続は以下の通り。
SC_MODULE(Top) { Initiator* initiator; Router<4>* router; Memory* memory[4]; SC_CTOR(Top) { initiator = new Initiator("initiator"); router = new Router<4>("router"); for (int i = 0; i < 4; i++) { char txt[20]; sprintf(txt, "memory_%d", i); memory[i] = new Memory(txt); } initiator->socket.bind( router->target_socket ); for (int i = 0; i < 4; i++) router->initiator_socket[i]->bind( memory[i]->socket ); } };
この例ではイニシエータのソケットはルーターのターゲットソケットにバインドされ、ルーターのそれぞれ4つのイニシエータソケットは別々のターゲットメモリのソケットにバインドされる。initiator-to-tagetソケットはそれぞれpoint-to-point接続である。1つのイニシエータソケットを複数のターゲットソケットにバインドすることはできないし、逆方向についても同様である。(ただし、実際のところはできる。そのためにはmulti-passthroughソケットと呼ばれる別の便利なソケットを使用する必要がある。)
それぞれのメモリはイニシエータからは異なるアドレススペースに配置される。そのため、ルーターはトランザクションに埋め込まれたアドレスに依存して、メモリのローカルアドレスへのアドレス変換をしつつ、適したメモリにトランザクションをルーティングしなければならない。このアドレス変換処理は通常転送だけでなくDMIやデバッグ転送でも行う必要がある。
ルーターは一つのシンプルターゲットソケットのインスタンスを持ち、4つのシンプルイニシエータソケットを持ち、それらはブロッキング転送をサポートする。
template<unsigned int N_TARGETS> struct Router: sc_module { tlm_utils::simple_target_socket<Router> target_socket; tlm_utils::simple_initiator_socket_tagged<Router> initiator_socket[N_TARGETS]; ... };
ターゲット数はテンプレート引数によって指定可能となっている。すべてのソケットはデフォルトで32ビット幅でベースプロトコルを使う。このルーターは汎用ペイロードトランザクションしかルーティングできない(Extensionには対応しない)。
また、イニシエータソケットは simple_initiator_socket_tagged を使用する。タグ付きソケットを使うことでどのソケットからメソッド呼び出しが行われたかがわかるようになり、イニシエータソケットが複数ある場合には必須のものである。以下がルータのコンストラクタである。
SC_CTOR(Router) : target_socket("target_socket") { target_socket.register_b_transport( this, &Router::b_transport); target_socket.register_get_direct_mem_ptr( this, &Router::get_direct_mem_ptr); target_socket.register_transport_dbg( this, &Router::transport_dbg); for (unsigned int i = 0; i < N_TARGETS; i++) { char txt[20]; sprintf(txt, "socket_%d", i); initiator_socket[i] = new tlm_utils::simple_initiator_socket_tagged<Router>(txt); initiator_socket[i]->register_invalidate_direct_mem_ptr( this, &Router::invalidate_direct_mem_ptr, i); } }
それぞれのインタフェースメソッドの実装はターゲットソケットかイニシエータソケットかの適したソケットに登録される。
イニシエータソケットへの登録メソッドの最後の引数が整数タグになっていることに着目してほしい。このタグはコールバック時に invalidate_direct_mem_ptr メソッドの引数として渡される。次に、ブロッキング転送インタフェース、DMI、デバッグ転送インタフェースを見ていく。
Routing the b_transport method
ブロッキング転送メソッドはfoward方向の際にのみ渡される。したがって、ターゲットから返される b_transport 呼び出しのハンドリングについては考慮しなくてよい。以下はルーターの b_transport の実装である。
virtual void b_transport( tlm::tlm_generic_payload& trans, sc_time& delay ) { sc_dt::uint64 address = trans.get_address(); sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( address, masked_address); trans.set_address( masked_address ); ( *initiator_socket[target_nr] )->b_transport( trans, delay ); }
ルーターはアドレス属性を調べ、どのソケットにトランザクションを受け渡すか決定します。そのための変数として target_nr を使っている。この例では以下のアドレスデコーディング関数を使っている。
inline unsigned int decode_address( sc_dt::uint64 address, sc_dt::uint64& masked_address ) { unsigned int target_nr = static_cast<unsigned int>( (address >> 8) & 0x3 ); masked_address = address & 0xFF; return target_nr; } inline sc_dt::uint64 compose_address( unsigned int target_nr, sc_dt::uint64 address) { return (target_nr << 8) | (address & 0xFF); }
ルーターは汎用ペイロードトランザクションのアドレス属性をマスクしたアドレス、すなわちターゲットメモリのローカルアドレスで上書きする。
アドレス属性はインターコネクトコンポーネント内で修正することが許される数少ない属性の一つである。ほかの書き換え可能な属性としてはDMIヒントとExtensionがある。インターコネクトとターゲットはほとんどの属性値をリードオンリーとして扱う必要がある。
b_transportの最後ではトランザクションを適切なイニシエータソケットに転送している。ターゲットモジュールのb_transportはイニシエータモジュールのスレッドプロセスのコンテキストで実行される。関数終了時は関数コールチェーン全体が巻き戻りイニシエータに戻されることになる。
Routing DMI and Debug Transactions
DMIとデバッグ転送のルーティングで重要な原則は、通常転送と全く同じアドレス変換を行うことであり、その変換はfoward/backward双方で行うことが必要である。まずはfoward DMIインタフェースから見ていく。
virtual bool get_direct_mem_ptr(tlm::tlm_generic_payload& trans, tlm::tlm_dmi& dmi_data) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); trans.set_address( masked_address ); bool status = ( *initiator_socket[target_nr] )-> get_direct_mem_ptr( trans, dmi_data ); ...
コードは上述の b_transport の実装と似ている。要求された DMI アドレスはターゲットでのアドレス空間に変換され、トランザクションは適切なターゲットにルーティングされる。
ターゲットはアドレス範囲を含むDMIデータオブジェクトによってDMI要求に返答するが、そのオブジェクトについてもイニシエータでのアドレス空間に変換する必要がある。
dmi_data.set_start_address( compose_address( target_nr, dmi_data.get_start_address() )); dmi_data.set_end_address ( compose_address( target_nr, dmi_data.get_end_address() )); return status; }
DMIデータオブジェクトに組み込まれている開始アドレスと終了アドレスは上書きされる。重要な点はここでは逆アドレス変換が行われている点である。ターゲットでDMIポインターを無効化する際もこのbackwardパスと同じ処理が必要である。
virtual void invalidate_direct_mem_ptr(int id, sc_dt::uint64 start_range, sc_dt::uint64 end_range) { sc_dt::uint64 bw_start_range = compose_address( id, start_range ); sc_dt::uint64 bw_end_range = compose_address( id, end_range ); target_socket->invalidate_direct_mem_ptr(bw_start_range, bw_end_range); }
ここでの invalidate_direct_mem_ptr 実装はタグ付きソケットからのコールバックの例となっている。
ソケットはどのソケットからメソッド呼び出しが行われたか判別するための int id引数を追加している。
ターゲットと同じIDを設定しているため、このメソッドで逆アドレス変換が実行できる。
最後にデバッグ転送メソッドのルーティングを見ていくが、今までの例と同様の実装となっている。
virtual unsigned int transport_dbg(tlm::tlm_generic_payload& trans) { sc_dt::uint64 masked_address; unsigned int target_nr = decode_address( trans.get_address(), masked_address ); trans.set_address( masked_address ); return ( *initiator_socket[target_nr] )->transport_dbg( trans ); }
補注:
Tutorial 4, 5, 6もあるが、サンプルコードのみで解説がないため、本日記での紹介はいったんここで終わりとする。