gem5を構成するSimObjectを追加するためのチュートリアルをやってみる。
- gem5記事一覧インデックス
gem5のPythonインターフェースの最も強力な部分の1つは、Pythonからgem5のC++オブジェクトにパラメータを渡す機能である。 この章では、前章の単純なHelloObject
をベースに、SimObjectのパラメータの種類とその使い方をいくつか検討する。
簡単なパラメータ
まず、HelloObject
にイベントを発生させる待ち時間と回数のパラメータを追加する。パラメータを追加するには、SimObject Pythonファイル(src/learning_gem5/part2/HelloObject.py
)のHelloObject
クラスを修正する。パラメータは、PythonクラスにParam
型を含む新しいステートメントを追加することで設定される。
例えば、以下のコードでは "Latency "パラメータであるtime_to_wait
と、整数パラメータであるnumber_of_fires
が設定されている。
class HelloObject(SimObject): type = 'HelloObject' cxx_header = "learning_gem5/part2/hello_object.hh" time_to_wait = Param.Latency("Time before firing the event") number_of_fires = Param.Int(1, "Number of times to fire the event before " "goodbye")
Param.<TypeName>
はTypeName
型のパラメータを宣言している。一般的な型は整数ならInt
、浮動小数点数ならFloat
などである。これらの型は通常のPythonクラスのように動作する。
各パラメータ宣言は1つか2つの引数を取る。(上記のnumber_of_fires
のように)2つのパラメーターが与えられた場合、最初の引数はパラメータのデフォルト値となる。この場合、Pythonの設定ファイルでnumber_of_fires
に何も指定せずにHelloObject
をインスタンス化すると、デフォルト値の1を取ることになる。
パラメータ宣言の2番目の引数は、パラメータの短い説明である。これはPython文字列でなければならない。パラメータ宣言のパラメータを1つだけ指定した場合は、それが説明文になる(time_to_wait
の場合)。
gem5では、組み込み型以外の多くの複雑なパラメータ型もサポートしている。例えば、time_to_wait
はLatency
である。Latency
は文字列として時間としての値を受け取り、それをシミュレータのtickに変換する。例えば、デフォルトのtick rateが1ピコ秒(1秒間に1012 tick、または1THz)の場合、"1ns "は自動的に1000に変換される。他にも、Percent
、Cycles
、MemorySize
などの便利なパラメータがある。
SimObjectファイルでこれらのパラメーターを宣言したら、コンストラクタでその値をC++クラスにコピーする必要がある。次のコードは、HelloObjectコンストラクタの変更を示している。
HelloObject::HelloObject(const HelloObjectParams ¶ms) : SimObject(params), event(*this), myName(params.name), latency(params.time_to_wait), timesLeft(params.number_of_fires) { DPRINTF(Hello, "Created the hello object with the name %s\n", myName); }
ここでは、latency
とtimesLeft
のデフォルト値にパラメータの値を使用している。さらに、パラメータ・オブジェクトの名前をメンバ変数myName
に格納し、後で使用できるようにしている。各パラメータのインスタンス化には、Pythonの設定ファイルから得られる名前を持つ。
しかし、ここで名前を代入するのは、params
オブジェクトを使用する一例に過ぎない。すべてのSimObjectには、常に名前を返すname()
関数がある。したがって、上記のように名前を保存する必要はない。
HelloObjectクラスの宣言に、名前用のメンバ変数を追加する。
class HelloObject : public SimObject { private: void processEvent(); EventWrapper event; const std::string myName; const Tick latency; int timesLeft; public: HelloObject(HelloObjectParams *p); void startup(); };
上記でgem5を実行すると、以下のエラーが発生する。
gem5 Simulator System. http://gem5.org gem5 is copyrighted software; use the --copyright option for details. gem5 compiled Jan 4 2017 14:46:36 gem5 started Jan 4 2017 14:46:52 gem5 executing on chinook, pid 3422 command line: build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py Global frequency set at 1000000000000 ticks per second fatal: hello.time_to_wait without default or user set value
これはtime_to_waitパラメータにデフォルト値がないためである。そのため、Pythonの設定ファイル(run_hello.py
)を更新して、この値を指定する必要がある。
root.hello = HelloObject(time_to_wait = '2us')
あるいは、time_to_wait
をメンバ変数として指定することもできる。m5.instantiate()
が呼ばれるまでC++オブジェクトは生成されないので、どちらのオプションを選んでも全く同じである。
root.hello = HelloObject()
root.hello.time_to_wait = '2us'
この単純なスクリプトの出力は、Helloデバッグフラグを実行した場合、次のようになる。
gem5 Simulator System. http://gem5.org gem5 is copyrighted software; use the --copyright option for details. gem5 compiled Jan 4 2017 14:46:36 gem5 started Jan 4 2017 14:50:08 gem5 executing on chinook, pid 3455 command line: build/X86/gem5.opt --debug-flags=Hello configs/learning_gem5/part2/run_hello.py Global frequency set at 1000000000000 ticks per second 0: hello: Created the hello object with the name hello Beginning simulation! info: Entering event queue @ 0. Starting simulation... 2000000: hello: Hello world! Processing the event! 0 left 2000000: hello: Done firing! Exiting @ tick 18446744073709551615 because simulate() limit reached
コンフィギュレーション・スクリプトを変更して、イベントを複数回発生させることもできる。
パラメータとしての他のSimObjects
他のSimObjectをパラメータとして指定することもできる。これを示すために、新しいSimObject、GoodbyeObject
を作成する。このオブジェクトは、他のSimObjectに”Goodbye”と言う単純な関数を持っている。もう少し面白くするために、GoodbyeObject
はメッセージを書き込むためのバッファと、メッセージを書き込むための限られた帯域幅を持っている。
まず、SConscriptファイルでSimObjectを宣言する:
Import('*') SimObject('HelloObject.py', sim_objects=['HelloObject', 'GoodbyeObject']) Source('hello_object.cc') Source('goodbye_object.cc') DebugFlag('Hello')
新しいSConscriptファイルはここからダウンロードできる。
次に、SimObject Pythonファイルで新しいSimObjectを宣言する必要がある。GoodbyeObject
はHelloObject
と関係が深いので、同じファイルを使う。以下のコードをHelloObject.py
に追加する。
このオブジェクトには2つのパラメータがあり、両方ともデフォルト値になっている。最初のパラメータはバッファのサイズで、MemorySize
パラメータである。つ目はwrite_bandwidth
で、バッファを埋めるスピードを指定する。バッファが一杯になると、シミュレーションは終了する。
class GoodbyeObject(SimObject): type = 'GoodbyeObject' cxx_header = "learning_gem5/part2/goodbye_object.hh" cxx_class = "gem5::GoodbyeObject" buffer_size = Param.MemorySize('1kB', "Size of buffer to fill with goodbye") write_bandwidth = Param.MemoryBandwidth('100MB/s', "Bandwidth to fill " "the buffer")
次に、GoodbyeObject
を実装する。
#ifndef __LEARNING_GEM5_GOODBYE_OBJECT_HH__ #define __LEARNING_GEM5_GOODBYE_OBJECT_HH__ #include <string> #include "params/GoodbyeObject.hh" #include "sim/sim_object.hh" class GoodbyeObject : public SimObject { private: void processEvent(); /** * バッファを1反復分フィルする。バッファが満杯でない場合、 * この関数は別のイベントをキューに入れ、フィルを継続する */ void fillBuffer(); EventWrapper<GoodbyeObject, &GoodbyeObject::processEvent> event; /// 1tickあたりに処理するバイト数 float bandwidth; /// フィルする予定のバイトサイズ int bufferSize; /// メッセージを挿入するバッファ char *buffer; /// バッファに挿入するメッセージ std::string message; /// これまでに使用したバッファの量 int bufferUsed; public: GoodbyeObject(GoodbyeObjectParams *p); ~GoodbyeObject(); /** * Called by an outside object. Starts off the events to fill the buffer * with a goodbye message. * * @param name the name of the object we are saying goodbye to. */ void sayGoodbye(std::string name); }; #endif // __LEARNING_GEM5_GOODBYE_OBJECT_HH__
#include "learning_gem5/part2/goodbye_object.hh" #include "base/trace.hh" #include "debug/Hello.hh" #include "sim/sim_exit.hh" GoodbyeObject::GoodbyeObject(const GoodbyeObjectParams ¶ms) : SimObject(params), event(*this), bandwidth(params.write_bandwidth), bufferSize(params.buffer_size), buffer(nullptr), bufferUsed(0) { buffer = new char[bufferSize]; DPRINTF(Hello, "Created the goodbye object\n"); } GoodbyeObject::~GoodbyeObject() { delete[] buffer; } void GoodbyeObject::processEvent() { DPRINTF(Hello, "Processing the event!\n"); fillBuffer(); } void GoodbyeObject::sayGoodbye(std::string other_name) { DPRINTF(Hello, "Saying goodbye to %s\n", other_name); message = "Goodbye " + other_name + "!! "; fillBuffer(); } void GoodbyeObject::fillBuffer() { // There better be a message assert(message.length() > 0); // Copy from the message to the buffer per byte. int bytes_copied = 0; for (auto it = message.begin(); it < message.end() && bufferUsed < bufferSize - 1; it++, bufferUsed++, bytes_copied++) { // Copy the character into the buffer buffer[bufferUsed] = *it; } if (bufferUsed < bufferSize - 1) { // Wait for the next copy for as long as it would have taken DPRINTF(Hello, "Scheduling another fillBuffer in %d ticks\n", bandwidth * bytes_copied); schedule(event, curTick() + bandwidth * bytes_copied); } else { DPRINTF(Hello, "Goodbye done copying!\n"); // Be sure to take into account the time for the last bytes exitSimLoop(buffer, 0, curTick() + bandwidth * bytes_copied); } }
このGoodbyeObject
へのインターフェースは、文字列をパラメータとして受け取る関数sayGoodbye
というシンプルなものだ。この関数が呼ばれると、シミュレーターはメッセージを構築し、メンバ変数に保存する。それから、バッファへの充填を開始する。
限られた帯域幅をモデル化するために、メッセージをバッファに書き込むたびに、メッセージの書き込みにかかる待ち時間を一時停止する。この一時停止をモデル化するために、単純なイベントを使用する。
SimObject宣言でMemoryBandwidth
パラメータを使用したので、帯域幅変数は自動的に1バイトあたりのティックに変換され、待ち時間の計算は単純に帯域幅×バッファに書き込むバイト数となる。
最後に、バッファがいっぱいになったら、シミュレーションを終了する関数exitSimLoop
を呼び出す。1つ目はPythonの設定スクリプトに返すメッセージ(exit_event.getCause()
)、2つ目は終了コード、3つ目はいつ終了するかである。
HelloObjectのパラメータとしてGoodbyeObjectを追加する
まず、GoodbyeObject
をHelloObjectのパラメータとして追加する。これには、Param
のTypeName
にSimObjectのクラス名を指定するだけだ。通常のパラメータと同じように、デフォルトを指定することもできるし、指定しないこともできる。
class HelloObject(SimObject): type = 'HelloObject' cxx_header = "learning_gem5/part2/hello_object.hh" time_to_wait = Param.Latency("Time before firing the event") number_of_fires = Param.Int(1, "Number of times to fire the event before " "goodbye") goodbye_object = Param.GoodbyeObject("A goodbye object")
次に、GoodbyeObject
への参照をHelloObject
クラスに追加する。hello_object.hh
ファイルの先頭にgoodbye_object.hh
をインクルードするのを忘れないように!
#include <string> #include "learning_gem5/part2/goodbye_object.hh" #include "params/HelloObject.hh" #include "sim/sim_object.hh" class HelloObject : public SimObject { private: void processEvent(); EventWrapper event; /// Pointer to the corresponding GoodbyeObject. Set via Python GoodbyeObject* goodbye; /// The name of this object in the Python config file const std::string myName; /// Latency between calling the event (in ticks) const Tick latency; /// Number of times left to fire the event before goodbye int timesLeft; public: HelloObject(const HelloObjectParams &p); void startup(); };
次に、HelloObject
のコンストラクタとprocess
イベント関数を更新する必要がある。また、コンストラクタにgoodbye
ポインタが有効かどうかのチェックを追加する。Pythonの特殊なSimObjectであるNULLを使うことで、パラメータを通してSimObjectとしてNULLポインタを渡すことができる。これはこのオブジェクトが受け入れるようにコード化されていないため、このような場合はパニックになるべきである。
#include "learning_gem5/part2/hello_object.hh" #include "debug/Hello.hh" HelloObject::HelloObject(HelloObjectParams ¶ms) : SimObject(params), event(*this), goodbye(params.goodbye_object), myName(params.name), latency(params.time_to_wait), timesLeft(params.number_of_fires) { DPRINTF(Hello, "Created the hello object with the name %s\n", myName); panic_if(!goodbye, "HelloObject must have a non-null GoodbyeObject"); }
パラメータで指定した数のイベントを処理したら、GoodbyeObject
のsayGoodbye
関数を呼び出そう。
void HelloObject::processEvent() { timesLeft--; DPRINTF(Hello, "Hello world! Processing the event! %d left\n", timesLeft); if (timesLeft <= 0) { DPRINTF(Hello, "Done firing!\n"); goodbye->sayGoodbye(myName); } else { schedule(event, curTick() + latency); } }
configスクリプトのアップデート
最後に、GoodbyeObject
をコンフィグスクリプトに追加する必要がある。新しいコンフィグスクリプトhello_goodbye.py
を作成し、hello
オブジェクトとgoodbye
オブジェクトの両方をインスタンス化する。例えば、以下のようなスクリプトが考えられる。
import m5 from m5.objects import * root = Root(full_system = False) root.hello = HelloObject(time_to_wait = '2us', number_of_fires = 5) root.hello.goodbye_object = GoodbyeObject(buffer_size='100B') m5.instantiate() print("Beginning simulation!") exit_event = m5.simulate() print('Exiting @ tick %i because %s' % (m5.curTick(), exit_event.getCause()))
このスクリプトを実行すると、以下の出力が生成される。
gem5 Simulator System. http://gem5.org gem5 is copyrighted software; use the --copyright option for details. gem5 compiled Jan 4 2017 15:17:14 gem5 started Jan 4 2017 15:18:41 gem5 executing on chinook, pid 3838 command line: build/X86/gem5.opt --debug-flags=HelloExample configs/learning_gem5/part2/hello_goodbye.py Global frequency set at 1000000000000 ticks per second 0: hello.goodbye_object: Created the goodbye object 0: hello: Created the hello object Beginning simulation! info: Entering event queue @ 0. Starting simulation... 2000000: hello: Hello world! Processing the event! 4 left 4000000: hello: Hello world! Processing the event! 3 left 6000000: hello: Hello world! Processing the event! 2 left 8000000: hello: Hello world! Processing the event! 1 left 10000000: hello: Hello world! Processing the event! 0 left 10000000: hello: Done firing! 10000000: hello.goodbye_object: Saying goodbye to hello 10000000: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks 10152592: hello.goodbye_object: Processing the event! 10152592: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks 10305184: hello.goodbye_object: Processing the event! 10305184: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks 10457776: hello.goodbye_object: Processing the event! 10457776: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks 10610368: hello.goodbye_object: Processing the event! 10610368: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks 10762960: hello.goodbye_object: Processing the event! 10762960: hello.goodbye_object: Scheduling another fillBuffer in 152592 ticks 10915552: hello.goodbye_object: Processing the event! 10915552: hello.goodbye_object: Goodbye done copying! Exiting @ tick 10944163 because Goodbye hello!! Goodbye hello!! Goodbye hello!! Goodbye hello!! Goodbye hello!! Goodbye hello!! Goo
これら2つのSimObjectのパラメータを変更し、全体の実行時間(Exiting @ tick 10944163
)がどのように変化するかを見ることができる。これらのテストを実行するには、ターミナルへの出力が少なくなるようにデバッグフラグを外すとよい。
次の章では、より複雑で有用なSimObjectを作成し、最後に単純なブロッキングユニプロセッサキャッシュを実装する。