Applistar

4. 2 簡単な例題

TOP > 4 SystemCをつかってみよう > 4. 2 簡単な例題
  4. 2 簡単な例題  

4. 2 簡単な例題

ここでは、簡単な例題をとおしてSystemCの使い方を学びます。

4. 2. 1  例題の説明

ここでは、単純化された cpu モデルを SystemC で記述します。 この cpu をここでは cpu0 と呼びます。 この cpu0には、次の 2 つの機能を持たせます。

  • メモリの内容を初期化する
  • プログラムカウンタpcの指すアドレスからメモリの内容を読み出す。PCを1つ進める。

4. 2. 2 UMLによる分析

SystemCではC++をベースにしているため、 オブジェクト指向の考え方でシステムをとらえることにします。 オブジェクト指向のソフトウェア設計で、システムの要求分析と設計に UML(Unified Modeling Language) が注目されています。

4. 2. 3  ユースケース

まず、上記の問題設定から、 システムのもつべき機能をユースケース図で表わします。

ここではシステムが、 楕円のなかに記述されている機能を持つことを表わしています。

4. 2. 4 シーケンス図

システムの動的な連携を表現するにはシーケンス図を用います。

ここでは

  • 初期化の要求に対して、内部関数cpureset()が呼ばれること、
  • メモリ呼び出し要求に対して、関数main()が呼ばれる

ことを表わしています。

4. 2. 5 cpu0と外界とのインタフェースを規定する。

ここで cpu0 と外部との信号のやりとりを整理します。

  • 初期化信号 reset
  • メモリを呼び出す clk
  • メモリの内容を出力する cpu_out

このブロック図は以下のようになります。

4. 2. 6 cpu0のコンストラクタを定義する

ここでモジュール cpu0 の定義をする準備ができたので、 さっそく SystemC で定義してみましょう。

まずモジュールの cpu0 のモジュールクラスとコンストラクタを定義します。 コンストラクタとは、C++ でオブジェクトを初期化生成する関数のことです。 モジュールの定義の方法については詳しくは後述しますが、 例えば次のような形になります。

#include <systemc.h>

SC_MODULE(cpu)             // cpu というモジュールクラスを定義する
{
 public:                   // 公開部分
  sc_in<bool> reset;
  sc_in<bool> clk;
  sc_out<int> cpu_out;

  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();
  }
};

4. 2. 7 cpu の初期化関数を定義する

初期化関数cpureset()の定義は以下のようになります。 ここでは、memdata[i]に i を書き込むという単純な動作として定義することにします。

void
cpu::cpureset(){
  cout << "simulation initialization \n";
  for (int i=0; i<maxmem; i++){
    memdata[i]=i;
    pc = 0;
  }
}

4. 2. 8 メモリ読み出し関数 main()を定義する

メモリ読み出し関数は以下のようになります。

void
cpu::main()
{
  cpu_out = memdata[pc];
  cout << "pc = " << pc <<" memdata[pc]=" << memdata[pc] <<endl;
  pc = pc + 1;
  if (pc == maxmem-1)
    sc_stop();
}

4. 2. 9 メイン関数 sc_main()を定義する。

SystemCにおいては、ユーザが定義するメイン関数は sc_main()関数です。

ここでは、

  • テストを行う補助的な信号を定義する
  • 信号の接続を定義する
  • 信号の時系列的な変化を定義する。
  • SystemCのシミュレーションの開始と停止を指示する

という内容を記述しています。

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);
   cpu cpu1("cpu1");
   cpu1.reset(reset);
   cpu1.clk(clk);
   cpu1.cpu_out(cpu_out);

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

   sc_start(1000 );
   sc_stop();
   return 0;
}

4. 2. 10 シミュレーションを実行してみる

シミュレーションを実行するとつぎの結果を得ます。

      (run result)
simulation initialization
pc = 1 memdata[pc]=1
simulation initialization
pc = 1 memdata[pc]=1
pc = 2 memdata[pc]=2
pc = 3 memdata[pc]=3
pc = 4 memdata[pc]=4
pc = 5 memdata[pc]=5
pc = 6 memdata[pc]=6
pc = 7 memdata[pc]=7
pc = 8 memdata[pc]=8
pc = 9 memdata[pc]=9
質問 resetの立下りとclkの最初の立ち上がりのタイミングが同時刻の場合、初期化とメモリの読み出しはどのように行われますか?

つぎの A1?A4のうち正しいのはどれでしょうか?

A1  cpureset()が実行されてからmain()が実行され正常終了する
A2  main()が先に実行され初期化ができていないため、異常終了する
A3  正常に終了するか、異常終了するかシステムに依存するがいつもどちらか一方に決まる
A4  正常終了するか、異常終了するか決まらない

正解は A3、およびA4です。

このような状態を非決定性(Non Deterministic)と呼びます。 システムを設計するうえで、 最終的なシステムの振舞いが非決定的にならないよう注意する必要があります。

SystemCのシミュレータは、 時としてこのようにシステムの最終的な振舞いが非決定的な振舞いになってしまう場合があります。

これを避けるためには

  • 同一の変数を複数のプロセスで参照、変更するときお互いに確認を取って行う
  • 複数のプロセスを1つのプロセスにまとめる

などの方法が考えられます。

では、答えがなぜA3、A4と、二つあるのでしょうか? これは、 振舞いに非決定的な部分があるかどうか調べるため、 SystemCのシミュレータには、 同時刻に起動されるプロセスの実行順序を意図的にランダムにするという機能があります。 このため、 通常はA3ですが、A4も可能になります。

質問 この場合、2つのプロセス cpureset()とmain()は並行して同時に走行できますか?

答えは"No"です。 これはこれら2つのプロセスは SC_METHOD として定義されているからです。 並行して走るプロセスとして定義するには SC_THREAD を用いてプロセスを登録します。

  • 簡単なD-Flipを動かしてみる(DflipFlop)
  • 信号をトレースする(TracingSignals)
  4. 2 簡単な例題  
TOP > 4 SystemCをつかってみよう > 4. 2 簡単な例題