Applistar

4. 3 階層チャネルを使って通信をモデル化する

TOP > 4 SystemCをつかってみよう > 4. 3 階層チャネルを使って通信をモデル化する
  4. 3 階層チャネルを使って通信をモデル化する  

4. 3 階層チャネルを使って通信をモデル化する

4. 3. 1 プロセスの分離と通信プロトコルの定義

次に cpu0 では計算と記憶を同一のモジュールで表現していたモデルを すこし変えて、計算をつかさどる cpu1 とメモリ mem を分離してメモリバス membus 経由でアクセスするモデルで表現してみましょう。

プロセスを分離すると、プロセス間の通信も記述する必要があります。 ここでは、SystemC の持つ通信の強力な設計方法である、 階層チャネルと、インタフェースを使います。

4. 3. 2 シーケンスチャートによる通信の設計

CPUとメモリに分類した結果、これら 2 つのモジュール間の連携の様子をシーケンスチャートで表わすと良く理解できます。

CPU とメモリの間は、CPU から次の 2 種類の命令を出すことで通信することとします。

  • 指定したアドレスからデータを読むreadDirect関数
  • 指定したアドレスへデータを書き込むwriteDirect関数

このアクセス関数の組を interface と呼びます。 このインタフェースの具体的な記述を以下に示します。

#include <systemc.h>

class mem_if : virtual public sc_interface
{
public:
  virtual void writeDirect(int&, int *) = 0;
  virtual void readDirect(int&, int *) = 0;
};

インタフェース定義は C++ の言葉では抽象クラス() に分類されるクラスを使って行ないます。 抽象クラスでは仮想関数の宣言のみを行い、関数の実体の定義は行いません。

関数の実体の方の定義は、 インタフェースの派生クラスである階層チャネルのクラスで行います。

SystemC用語 定義の分担
インタフェース 仮想関数の宣言(関数の名前と引数を宣言する)
階層チャネル 関数の中身(実装)の定義

なぜ、そのような、まわりくどい書き方をするのでしょうか? その理由は、段階的に通信の詳細化をしていっても、 呼出側のプロトコルが一定の間は、CPU 側の記述を変更する必要がないという、利点があるからです。 「再利用」可能な記述になるわけです。

実装は階層チャネルの中の関数の実体にかかれているわけですから、 通信が詳細化されるにつれて、異なる実装の階層チャネルがCPU に接続されてもCPUは平然としていられるわけです。 これはインタフェース定義が変わらないからです。

4. 3. 3 CPU1とメモリの構成図

以下にCPUとメモリの接続構成図を示します。

4. 3. 4 メモリクラスmemをsc_channelとインタフェースの派生クラスとして定義する

つぎにメモリmemのクラスの定義を見てみましょう。

class mem : public sc_channel, public mem_if
{
public:

  void writeDirect(int& addr, int *data);
  void readDirect(int& addr, int *data);
  mem(sc_module_name name) : sc_channel(name) {}
private:
  enum {maxmem =100};
  int memdata[maxmem];
};

ここで

class mem : public sc_channel, public mem_if

で定義されるように、memは、sc_channel と先ほど定義したインタフェースmem_if を継承して作成します。

4. 3. 5  階層チャネルmemの継承関係

class memの継承関係をUMLのクラス図で見てみましょう。

これをみるとわかるように階層チャネルは

  • sc_channel(sc_module)の持つモジュールの性質を受け継いでいる
  • mem_ifで宣言されている関数を全て持つ(定義する)必要がある。

ことが分かります。

4. 3. 6 writeDirect関数の実体の定義は階層チャネルで定義する

インタフェースで持つことが義務づけられた、 writeDirect()関数、readDirect() 関数の実体の定義は階層チャネルの中で行うことになります。

その記述をみてみましょう。

void
mem::writeDirect(int &addr, int *data)
{
  if(addr > maxmem-1) {
    cout << " exceeding mem size.\n";
    return;
  }
  memdata[addr] = *data;
}
void
mem::readDirect(int &addr, int *data)
{
  if(addr > maxmem-1) {
    cout << " exceeding mem size.\n";
    *data = 0;
    return;
  }
  *data = memdata[addr];
}

4. 3. 7 階層チャネルを呼ぶときは sc_port にそのインタフェースを接続する

つぎに階層チャネルをモジュールに接続する様子を見てみましょう。 cpu モジュール記述を以下に示します。

SC_MODULE(cpu)
{
 public:
  sc_in<bool> reset;
  sc_in<bool> clk;
  sc_out<int> cpu_out;
  sc_port<mem_if> membus;

  enum {maxmem =100};
  int memdata[maxmem];
  int pc;

  void main();
  void cpureset();

  SC_CTOR(cpu) {
    SC_METHOD(cpureset);
    sensitive << reset.neg();
    SC_METHOD(main);
    sensitive << clk.pos();
  }
  int data;
};

ここで注意したいのは、次のようにして

  sc_port<mem_if> membus;

membus というポートに先ほど定義した mem_if の属性を 持たせていることです。

4. 3. 8 ポートから階層チャネルの関数を呼び出す

CPUの初期化ルーチンcpureset()を見てみると 以下のようになります。

void
cpu::cpureset(){
  cout << "simulation initialization \n";
  for (int i = 0; i < maxmem; i++){
    membus -> writeDirect(i, &i);
    pc = 0;
  }
}

ここで階層チャネルの関数の呼び出しは

membus -> writeDirect(i, &i);

のようにインタフェースを接続したポートに対して行われていることに注意してください。

class top : public sc_module
{
public:
  mem *mem1;
  cpu *cpu1;

  sc_in<bool> clk;
  sc_in<bool> reset;
  sc_out<int> cpu_out;

  top(sc_module_name name): sc_module(name)
  {
    mem1 = new mem("mem1");
    cpu1 = new  cpu("cpu1");
    cpu1->membus(*mem1);

    cpu1->clk(clk);
    cpu1->reset(reset);
    cpu1->cpu_out(cpu_out);
  }
};
int sc_main (int argc , char *argv[]) {

  sc_signal<bool> reset;
  sc_signal<int> cpu_out;

  sc_clock clk("clk", 10, 0.5 , 100, 0);
   top top1("top1");
   top1.reset(reset);
   top1.clk(clk);
   top1.cpu_out(cpu_out);

   sc_start(10 );
   reset = 1;
   sc_start(20 );
   reset = 0;

   sc_start(1000 );
   sc_stop();
   return 0;
}
  • 簡単な通信をモデル化しよう
  • チャンネルとは
  • 階層的なチャネルを使ってモデル化しよう
  • 複数のバスマスタと複数のバススレーブをモデル化する
  4. 3 階層チャネルを使って通信をモデル化する  
TOP > 4 SystemCをつかってみよう > 4. 3 階層チャネルを使って通信をモデル化する