bambooflow Note

simple_fifo

最終更新:

bambooflow

- view
メンバー限定 登録/ログイン

サンプル --シンプルFIFO--


systemcのディレクトリにexamplesがあって、その中のsimple_fifoをみてみた。
これは、おおよそsc_fifoを自分で作るようなサンプルとなっているようだ(若干簡単にしている)。
このサンプルが理解できればsc_fifoの動作もわかるようだ。
インターフェース、チャネル、ポートの関係はSystemCを理解する上でかなり重要なところ。


simple_fifoサンプル場所

SystemCのパッケージを展開した以下の場所。
systemc-2.2.0/examples/sysc/simple_fifo/

FIFO作成について

このサンプルで作成するFIFOは階層チャネルになる(sc_channelを継承する)。
通信に必要なものはつぎのとおり。
  1. インターフェース(write_if, read_if)
  2. チャネル(fifo)
また、送受信するモジュールはポート(sc_port<write_if>, sc_port<read_if>)を準備する必要がある。

インターフェースとチャネルの関係

  • クラス図
    sc_interface
      ↑
   ┌──┴──┐
  write_if   read_if
   ↑     ↑   sc_channel
   └──┬──┘     ↑
      ├────────┘
      │
     fifo

インターフェースとチャネルとポートの接続関係

  《送信側》             《受信側》
sc_port<write_if> <====> fifo  <====> sc_port<read_if>


インターフェース作成

インターフェースは次の2つのルールで作成する。
  1. sc_interfaceを継承する
  2. 純粋仮想関数(virtual=0)のみをpublicで宣言する


出力側インターフェース(write_if)

値をFIFOへ書き込む側のインターフェース。
class write_if : virtual public sc_interface
{
   public:
     virtual void write(char) = 0;
     virtual void reset() = 0;
};
 

出力側はwriteとresetメソッドを準備している。sc_fifoでは、ノンブロッキングで書き込むnb_writeメソッドやFIFOが満杯かどうか取得するためのnum_freeメソッドがあるが、このサンプルは最小限のものだけにしているようだ。

入力側インターフェース(read_if)

値をFIFOから取り出すインターフェース。
class read_if : virtual public sc_interface
{
   public:
     virtual void read(char &) = 0;
     virtual int num_available() = 0;
};
 
入力側ではreadとnum_availableメソッドを準備している。num_availableはFIFOに溜っている数を返す。sc_fifoにあるnb_fifoメソッド等は宣言していない。

チャネル作成(fifo)

class fifo : public sc_channel, public write_if, public read_if
{
public:
     fifo(sc_module_name name) : sc_channel(name), num_elements(0), first(0) {}
 
     void write(char c) {
         wait(read_event);
 
       data[(first + num_elements) % max] = c;
       ++ num_elements;
       write_event.notify();
     }
     void read(char &c){
       if (num_elements == 0)
         wait(write_event);
 
       c = data[first];
       -- num_elements;
       first = (first + 1) % max;
       read_event.notify();
     }
     void reset() { num_elements = first = 0; }
 
private:
     enum e { max = 10 };
     char data[max];
     int num_elements, first;
     sc_event write_event, read_event;
 
 

チャネルfifoはインターフェースであるwrite_ifとread_if、ならびにsc_channelを継承する。
そして、write_ifとread_ifを継承したので、インターフェースの純粋仮想関数の中身を書く必要がある。
このチャネルのFIFO数は、最大10個固定のようだ。(sc_fifoの場合は、コンストラクタの第2引数にFIFO数を設定できる)

writeとreadのブロッキング動作

fifoに記述されたwrite、readメソッドはwait()が含まれているのでブロッキング・メソッドである。
writeの動作は、FIFOが満杯のとき、送信側の処理を一時停止する。
readの動作は、FIFOが空のとき、受信側の処理を一時停止する。

以下にwriteとreadのブロッキング動作を説明するために必要な部分を抜き出してみた。
sc_event write_event, read_event;
int num_elements;

    void write(char c) {
      if (num_elements == max)
        wait(read_event);         // FIFOが満杯のとき、readイベント通知を待つ

       ++ num_elements;
      write_event.notify();       // writeイベントを通知する
    }

    void read(char &c){
      if (num_elements == 0)
        wait(write_event);        // FIFOが空のとき、writeイベント通知を待つ

      -- num_elements;
      read_event.notify();        // readイベントを通知する
    }

write側では、readイベントを待ち、writeイベントを通知する。
read側では、writeイベントを待ち、readイベントを通知する。
この処理では時間概念はない。デルタ遅延で処理される。

ポート作成、接続、アクセス

送信側[producer](sc_port<write_if>)

  • producerモジュール記述を部分的に抜粋
class producer : public sc_module
{
public:
     sc_port<write_if> out;          // ポート作成
         ...
     void main() // SC_THREAD
     {
         ...
       while (*str)
         out->write(*str++);         // writeアクセス
     }
};
 

インターフェースwrite_ifのAPIにアクセスするためには"->"を使う。
writeを呼ぶと、実際にはfifoチャネルのwriteが呼ぶことになる。

受信側[consumer](sc_port<read_if>)

  • consumerモジュール記述を部分的に抜粋
class consumer : public sc_module
{
   public:
     sc_port<read_if> in;            // ポート作成
         ...
     void main()  // SC_THREAD
     {
       while (true) {
         in->read(c);                // readアクセス
       }
     }
};
 
インターフェースread_ifのAPIにアクセスするためには"->"を使う。
readを呼ぶと、実際にはfifoチャネルのreadが呼ぶことになる。

トップ接続

class top : public sc_module
{
   public:
     fifo *fifo_inst;
     producer *prod_inst;
     consumer *cons_inst;
 
     top(sc_module_name name) : sc_module(name)
     {
       fifo_inst = new fifo("Fifo1");
 
       prod_inst = new producer("Producer1");
       prod_inst->out(*fifo_inst);
 
       cons_inst = new consumer("Consumer1");
       cons_inst->in(*fifo_inst);
     }
};
 

接続はsc_fifoやsc_signalのやりかたと変わらない。
モジュール->ポート( チャネル )

write,read動作説明

シミュレーションが開始されると、prod_inst(producer)とcons_inst(consumer)のプロセスが同時に処理を始める。
はじめに、cons_inst側のプロセスが動作したとすると、readが呼ばれ、FIFO数がゼロのためwaitで一時停止する。
*1
次に、prod_inst側のプロセスが動作をはじめ、writeが呼ばれる。
writeはFIFOが満杯になるまで繰り返し、満杯(10回書込み)になるとwait()で一時停止する。
このとき、writeイベントを(何度か)通知する。
*2
const_inst側のプロセスでwriteイベントの通知を受けることで、動作を再開する。
FIFOはゼロでないので、ゼロになるまでreadを繰り返す。
FIFOがゼロになるとread内のwait()で一時停止する。
このとき、readイベントを(何度か)通知する。
*3
  繰り返し  ・・・


まとめ

インターフェースとチャネルとポートの関係はSystemCの特徴の1つ。
SystemCでTLMを覚える一歩は、このsimple_fifoの動作を理解することから始まると思う。
記事メニュー
目安箱バナー
注釈

*1 カーネル処理

*2 カーネル処理

*3 カーネル処理