次に cpu0 では計算と記憶を同一のモジュールで表現していたモデルを すこし変えて、計算をつかさどる cpu1 とメモリ mem を分離してメモリバス membus 経由でアクセスするモデルで表現してみましょう。
プロセスを分離すると、プロセス間の通信も記述する必要があります。 ここでは、SystemC の持つ通信の強力な設計方法である、 階層チャネルと、インタフェースを使います。
CPUとメモリに分類した結果、これら 2 つのモジュール間の連携の様子をシーケンスチャートで表わすと良く理解できます。

CPU とメモリの間は、CPU から次の 2 種類の命令を出すことで通信することとします。
このアクセス関数の組を 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は平然としていられるわけです。 これはインタフェース定義が変わらないからです。
以下にCPUとメモリの接続構成図を示します。

つぎにメモリ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 を継承して作成します。
class memの継承関係をUMLのクラス図で見てみましょう。

これをみるとわかるように階層チャネルは
ことが分かります。
インタフェースで持つことが義務づけられた、 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];
}
つぎに階層チャネルをモジュールに接続する様子を見てみましょう。 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 の属性を 持たせていることです。
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;
}