
gem5を構成するSimObjectを追加するためのチュートリアルをやってみる。
SimpleMemobjを使った簡単なブリッジの実装が良く分からなかったので、もう一度よく読みなおしてみることにした。
- gem5記事一覧インデックス
リクエスト受信の実装 (recvTimingReq())
recvTimingReq()は、CPU側のポートからリクエストを受け付けるための関数である。これのために必要な要件は以下のようになる:
SimpleMemobjがリクエストを受信できるかどうかをチェックするCPUSidePortはポートインタフェースにすべてのフローコントロール情報を格納する。CPUSidePortに別のメンバ変数としてneedRetryを追加するboolean変数であり、SimpleMemobjがフリーになった場合にリトライが必要であることを意味する。SimpleMemobjはリクエストがブロックされ、将来的にリトライする実行する必要がある
以下がその実装となる
// CPU側からリクエストを受け付ける実装 bool SimpleMemobj::CPUSidePort::recvTimingReq(PacketPtr pkt) { if (!owner->handleRequest(pkt)) { // handleRequest()を実行して答えがFalseだと、リクエストを処理中である // ポート自体はfalseを返してリクエストを拒否し、retryが必要であることを覚える needRetry = true; return false; } else { // handleRequest()を実行して答えがTrueだと、そのままリクエストを受け付ける return true; } }
次にhandleRequest()の実装は以下のようになる:
SimpleMemobjが他のリクエストへの応答を待って既にブロックされているかどうかをチェックする (handleRequest())- ブロックされている場合
falseを返す(今リクエストを 受け付けられないことを通知する)
- ブロックされていない場合
- ポートをブロックされたものとしてマークする (
block = true) - パケットをメモリポートから送信する (
memPort.sendPacket())
- ポートをブロックされたものとしてマークする (
// CPUからのリクエストを処理するサポート関数 bool SimpleMemobj::handleRequest(PacketPtr pkt) { // 現在他のリクエストを処理しているためブロックされていることを意味する if (blocked) { return false; } DPRINTF(SimpleMemobj, "Got request for addr %#x\n", pkt->getAddr()); // 他のリクエストがないため処理を行う。 // blocked = trueとしてブロック中にする // メモリ・バスにパケットを送信する blocked = true; memPort.sendPacket(pkt); return true; }
次に、MemSidePortにsendPacket関数を実装する:
- 相手のスレーブ・ポートがリクエストを受け付けられない場合のフロー制御を行う。
- パケットがブロックされた場合に備えてパケットを保存するためのメンバを追加する(
blockedPacket)
void SimpleMemobj::MemSidePort::sendPacket(PacketPtr pkt) { panic_if(blockedPacket != nullptr, "Should never try to send if blocked!"); // パケットをメモリ・バス側に送信しようとして拒否された場合 // そのパケットを保存しておく // パケットを送信するときに、保存用のblockedPacketは必ずemptyの状態である必要がある if (!sendTimingReq(pkt)) { blockedPacket = pkt; } }
パケットを再送するコードを実装する必要がある:
上で書いたsendPacket関数を呼び出して、パケットの再送を試みる。
void SimpleMemobj::MemSidePort::recvReqRetry() { assert(blockedPacket != nullptr); PacketPtr pkt = blockedPacket; blockedPacket = nullptr; sendPacket(pkt); }
レスポンス受信(recvTimingResp())の実装
recvTimingResp()は、MemSidePort側が、リクエストに対するレスポンスを受信したときに駆動する関数である。
MemSidePortが応答を取得すると、SimpleMemobjを通して適切なCPUSidePortに応答を転送する。
bool SimpleMemobj::MemSidePort::recvTimingResp(PacketPtr pkt) { return owner->handleResponse(pkt); }
handleResponse()の実装を行う。ここでは、すでにCPU側の別のパケットがブロックしていた際の挙動も含まれている:
- 最初のアサーションはCPUからリクエストを受け付けたため、レスポンスが戻った際には
blocked=trueになっていることを確認するための実装である。 - blockedを解除する
- これは、
sendTimingRespを呼び出す前に行わなければならない。 - そうしないと、マスター・ポートが応答を受信してから別のリクエストを送信するまでの間に1つのコールチェーンを持っている可能性があるため、無限ループにはまる可能性がある。
- これは、
- パケットが命令パケットかデータパケットかをチェックし、適切なポートに送り返す。
- オブジェクトのブロックが解除されたので、CPU側のポートに、失敗したリクエストの再試行が可能になったことを通知する必要があるかもしれない。
bool SimpleMemobj::handleResponse(PacketPtr pkt) { // CPUからリクエストを受け付けたため、レスポンスが戻った際には // blocked=trueになっていることを想定 assert(blocked); DPRINTF(SimpleMemobj, "Got response for addr %#x\n", pkt->getAddr()); // ブロックを解除 blocked = false; // Simply forward to the memory port if (pkt->req->isInstFetch()) { instPort.sendPacket(pkt); } else { dataPort.sendPacket(pkt); } instPort.trySendRetry(); dataPort.trySendRetry(); return true; }
CPUSidePort側でこれらに関して新たに実装しなければならない関数は以下である:
CPUSidePort::sendPacket()- CPU側にレスポンスパケットを送信する
CPUSidePort::recvRespRetry()- 純粋仮想関数でオーバライドする必要がある。ブロックされているパケットがある場合に再送信を行うための関数
CPUSidePort::trySendRetry()- CPU側でブロックされたパケットを、新たに送信するための要求を行う。
まずはsendPacket()の実装:
sendTimingRespを呼び出し、その結果、相手マスター・ポートのrecvTimingRespが呼び出される。- 呼び出しが失敗した場合
- CPU側のポートが現在ブロックされていることを意味する。
- あとで送信するためにパケットを保存する (
blockedPacket)
void SimpleMemobj::CPUSidePort::sendPacket(PacketPtr pkt) { panic_if(blockedPacket != nullptr, "Should never try to send if blocked!"); if (!sendTimingResp(pkt)) { blockedPacket = pkt; } }
次にrecvRespRetry()の実装である:
メモリ・バス側のrecvReqRetry()とまったく同じで、単にパケットを再送しようとするだけである。
void SimpleMemobj::CPUSidePort::recvRespRetry() { assert(blockedPacket != nullptr); PacketPtr pkt = blockedPacket; blockedPacket = nullptr; sendPacket(pkt); }
次に、trySendRetry()の実装:
SimpleMemobjがCPU側の新たなリクエストをrecvTimingReq()でブロックした場合、ブロックを解除するためにリトライが必要かどうかをチェックする。
リトライが必要な場合、この関数はsendRetryReqを呼び出し、次に相手マスターポート(この場合はCPU)のrecvReqRetryを呼び出す。
void SimpleMemobj::CPUSidePort::trySendRetry() { if (needRetry && blockedPacket == nullptr) { needRetry = false; DPRINTF(SimpleMemobj, "Sending retry req for %d\n", id); sendRetryReq(); } }
この関数に加えて、ファイルを完成させるためにSimpleMemobjのcreate関数を追加する。
SimpleMemobj* SimpleMemobjParams::create() { return new SimpleMemobj(this); }
以下の図は、CPUSidePort、MemSidePort、SimpleMemobj の関係を示している。この図は、ピアポートがSimpleMemobjの実装とどのように相互作用するかを示している。太字の関数はそれぞれ実装しなければならないもので、太字でない関数はピアポートへのポートインターフェースである。色は、オブジェクトを通るAPIパスの1つ(例えば、リクエストの受信やメモリ範囲の更新)をハイライトしている。
この単純なメモリー・オブジェクトでは、パケットはCPU側からメモリー側に転送されるだけだ。しかし、handleRequestとhandleResponseを変更することで、次の章のキャッシュのようなリッチな機能を持つオブジェクトを作ることができる。
