gem5を構成するSimObjectを追加するためのチュートリアルをやってみる。
- gem5記事一覧インデックス
単純なメモリ・オブジェクトの例
このセクションでは、単純なメモリ・オブジェクトを作る。最初は、単純にCPU側(単純なCPU)からメモリー側(単純なメモリーバス)にリクエストを通すだけである。下の図を見てほしい。メモリ・バスにリクエストを送るためのマスター・ポートが1つと、CPUの命令キャッシュ・ポートとデータ・キャッシュ・ポート用のCPU側ポートが2つある。次の章simplecache-chapterでは、このオブジェクトをキャッシュにするためのロジックを追加する。
SimObjectを宣言する
SimObjectを宣言する hello-simobject-chapterで単純なSimObjectを作成したときと同じように、最初のステップはSimObject Pythonファイルを作成することである。この単純なメモリオブジェクトをSimpleMemobj
と呼び、SimObject Pythonファイルをsrc/learning_gem5/simple_memobj
に作成する。
from m5.params import * from m5.proxy import * from m5.SimObject import SimObject class SimpleMemobj(SimObject): type = 'SimpleMemobj' cxx_header = "learning_gem5/part2/simple_memobj.hh" inst_port = SlavePort("CPU side port, receives requests") data_port = SlavePort("CPU side port, receives requests") mem_side = MasterPort("Memory side port, sends requests")
このオブジェクトはSimObject
を継承している。SimObjectクラスには、C++の実装で定義しなければならない純粋仮想関数getPort
がある。
このオブジェクトのパラメータは3つのポートである。CPUが命令ポートとデータ・ポートに接続するための2つのポートと、メモリー・バスに接続するためのポートだ。これらのポートにはデフォルト値はなく、簡単な説明しかない。
これらのポートの名前を覚えておくことが重要である。SimpleMemobj
を実装し、getPort
関数を定義する際に、これらの名前を明示的に使用する。
もちろん、SimObject Pythonファイルを宣言するSConscriptファイルも新しいディレクトリに作成する必要がある。
SimpleMemobjクラスを定義する
SimpleMemobjのヘッダファイルを作成する。
#include "mem/port.hh" #include "params/SimpleMemobj.hh" #include "sim/sim_object.hh" class SimpleMemobj : public SimObject { private: public: /** constructor */ SimpleMemobj(SimpleMemobjParams *params); };
slaveポート型を定義する
次に、2種類のポート(CPU側ポートとメモリー側ポート)のクラスを定義する必要がある。他のオブジェクトがこれらのクラスを使うことはないので、SimpleMemobj
クラスの中でこれらのクラスを宣言する。
まず、スレーブポート(CPU側ポート)から始めよう。SlavePortクラスを継承する。以下は、SlavePort
クラスのすべての純粋仮想関数をオーバーライドするために必要なコードである。
class CPUSidePort : public SlavePort { private: SimpleMemobj *owner; public: CPUSidePort(const std::string& name, SimpleMemobj *owner) : SlavePort(name, owner), owner(owner) { } AddrRangeList getAddrRanges() const override; protected: Tick recvAtomic(PacketPtr pkt) override { panic("recvAtomic unimpl."); } void recvFunctional(PacketPtr pkt) override; bool recvTimingReq(PacketPtr pkt) override; void recvRespRetry() override; };
このオブジェクトでは、5つの関数を定義する必要がある。
このオブジェクトはまた、1つのメンバ変数を持つので、そのオブジェクトの関数を呼び出すことができる。
masterポート型を定義する
次に、マスター・ポート・タイプを定義する必要がある。これは、CPU側からのリクエストをメモリー・システムの残りの部分に転送するメモリー側のポートになる。
class MemSidePort : public MasterPort { private: SimpleMemobj *owner; public: MemSidePort(const std::string& name, SimpleMemobj *owner) : MasterPort(name, owner), owner(owner) { } protected: bool recvTimingResp(PacketPtr pkt) override; void recvReqRetry() override; void recvRangeChange() override; };
このクラスには、必ずオーバライドしなければならない純粋仮想関数が3つ用意されている。
SimObjectインタフェースを定義する
ここまでで、2つの新しい型CPUSidePort
とMemSidePort
を定義し、SimpleMemobj
で使用する3つのポートを宣言できるようになった。SimPbjectクラスの純粋仮想関数についても定義する必要があり、getPort
である。この関数はポートを通じてメモリオブジェクトを接続る初期化フェーズで実行される。
class SimpleMemobj : public SimObject { private: <CPUSidePort declaration> <MemSidePort declaration> CPUSidePort instPort; CPUSidePort dataPort; MemSidePort memPort; public: SimpleMemobj(SimpleMemobjParams *params); Port &getPort(const std::string &if_name, PortID idx=InvalidPortID) override; };
基本的なSimObject関数の実装
SimpleMemobjのコンストラクタでは、単純にSimObjectコンストラクタが呼ばれるだけである。また、すべてのポートを初期化する必要がある。それぞれのポートのコンストラクタは2つの引数を持つ:ポートの名前と、その所持者のポインタであり、これらはヘッダファイルに定義したものを使用する。名前はどのような文字列でも良いが、利便性のためにPythonのSimObjectファイルの名前と同一にする。また、blocked
変数をfalse
とする。
#include "learning_gem5/part2/simple_memobj.hh" #include "debug/SimpleMemobj.hh" SimpleMemobj::SimpleMemobj(SimpleMemobjParams *params) : SimObject(params), instPort(params->name + ".inst_port", this), dataPort(params->name + ".data_port", this), memPort(params->name + ".mem_side", this), blocked(false) { }
getPort
を実装するために、if_name
を比較してmem_side
という名前であるかをチェックする。もしそうであれば、memPort
オブジェクトを返す。名前がinst_port
であれば、instPort
を返し、data_port
であればdataPort
を返す。そうでなければ、新たに上位のgetPort()
を返すことになる。
Port & SimpleMemobj::getPort(const std::string &if_name, PortID idx) { panic_if(idx != InvalidPortID, "This object doesn't support vector ports"); // This is the name from the Python SimObject declaration (SimpleMemobj.py) if (if_name == "mem_side") { return memPort; } else if (if_name == "inst_port") { return instPort; } else if (if_name == "data_port") { return dataPort; } else { // pass it along to our super class return SimObject::getPort(if_name, idx); } }
slaveポートとmasterポート関数の実装
slaveとmasterポートの実装はシンプルである。ほとんどの場合、ポートのか縫うは情報をメインメモリオブジェクト(SimpleMemobj
)にフォワードするだけである。
2つのシンプルな関数から始める:getAddrRanges()とrecvFunctional()は、単純にSimpleMemobjのものを呼び出すだけである
AddrRangeList SimpleMemobj::CPUSidePort::getAddrRanges() const { return owner->getAddrRanges(); } void SimpleMemobj::CPUSidePort::recvFunctional(PacketPtr pkt) { return owner->handleFunctional(pkt); }
これらの関数の実装はSimpleMemobjのものと同一である。これらの実装は単純にリクエストをメモリ側に転送するだけである。DPRINTF関数を使用して、どのように関数が呼ばれているのかをトラックすることも可能である。
void SimpleMemobj::handleFunctional(PacketPtr pkt) { memPort.sendFunctional(pkt); } AddrRangeList SimpleMemobj::getAddrRanges() const { DPRINTF(SimpleMemobj, "Sending new ranges\n"); return memPort.getAddrRanges(); }
MemSidePortと同様に、recvRangeChangeを実装する必要があり、SimpleMemobjを通じてスレーブポートに転送を行う必要がある。
void SimpleMemobj::MemSidePort::recvRangeChange() { owner->sendRangeChange(); }
void SimpleMemobj::sendRangeChange() { instPort.sendRangeChange(); dataPort.sendRangeChange(); }