gem5を構成するSimObjectを追加するためのチュートリアルをやってみる。
- gem5記事一覧インデックス
リクエスト受信の実装
recvTimingReq
の実装はより複雑である。SimpleMemobj
がリクエストを受信できるかどうかをチェックする必要がある。SimpleMemobj
は非常にシンプルなブロック構造をしている; 1つのリクエストのみ同時に受け付けることができる。従って、あるリクエストを処理中に別のリクエストが到達すると、2番目のリクエストはブロックされる。
実装を単純にするために、CPUSidePortはポートインタフェースにすべてのフローコントロール情報を格納する。したがって、CPUSidePortに別のメンバ変数としてneedRetryを追加する必要がある。この変数はboolean変数であり、SimpleMemobjがフリーになった場合にリトライが必要であることを意味する。そして、SimpleMemobjはリクエストがブロックされ、将来的にリトライする実行する必要があることを意味する。
bool SimpleMemobj::CPUSidePort::recvTimingReq(PacketPtr pkt) { if (!owner->handleRequest(pkt)) { needRetry = true; return false; } else { return true; } }
SimpleMemobj
へのリクエストを処理するために、まずSimpleMemobj
が他のリクエストへの応答を待って既にブロックされているかどうかをチェックする。ブロックされている場合、false
を返し、呼び出されたマスタポートに今リクエストを 受け付けられないことを通知する。そうでなければ、ポートをブロックされたものとしてマークし、パケットをメモリポートから送信する。このために、SimpleMemobj
実装からフロー制御を隠すために、MemSidePort
オブジェクトにヘルパー関数を定義することができる。memPort
が全てのフロー制御を処理し、リクエストの消費に成功したので、常にhandleRequest
からtrue
を返すと仮定する。
bool SimpleMemobj::handleRequest(PacketPtr pkt) { if (blocked) { return false; } DPRINTF(SimpleMemobj, "Got request for addr %#x\n", pkt->getAddr()); blocked = true; memPort.sendPacket(pkt); return true; }
次に、MemSidePort
にsendPacket
関数を実装する必要がある。この関数は、相手のスレーブ・ポートがリクエストを受け付けられない場合のフロー制御を行う。そのために、MemSidePort
に、パケットがブロックされた場合に備えてパケットを保存するためのメンバを追加する必要がある。レシーバーがリクエスト(またはレスポンス)を受信できない場合にパケットを保存するのは、送信側の責任である。
送信に失敗した場合、このオブジェクトはパケットをblockedPacket
メンバ関数に格納し、後で(recvReqRetry
を受信したときに)パケットを送信できるようにする。この関数には、バグがないことを確認するための防御コードも含まれており、blockedPacket
変数を間違って上書きしようとすることはない。
void SimpleMemobj::MemSidePort::sendPacket(PacketPtr pkt) { panic_if(blockedPacket != nullptr, "Should never try to send if blocked!"); if (!sendTimingReq(pkt)) { blockedPacket = pkt; } }
次に、パケットを再送するコードを実装する必要がある。この関数では、上で書いたsendPacket
関数を呼び出して、パケットの再送を試みる。
void SimpleMemobj::MemSidePort::recvReqRetry() { assert(blockedPacket != nullptr); PacketPtr pkt = blockedPacket; blockedPacket = nullptr; sendPacket(pkt); }
レスポンス受信の実装
応答のコードパスは受信のコードパスに似ている。MemSidePort
が応答を取得すると、SimpleMemobj
を通して適切なCPUSidePort
に応答を転送する。
bool SimpleMemobj::MemSidePort::recvTimingResp(PacketPtr pkt) { return owner->handleResponse(pkt); }
SimpleMemobj
では、まず、オブジェクトがブロッキングされているため、応答を受信すると常にブロックされる。パケットをCPU側に送り返す前に、オブジェクトがもはやブロックされていないことをマークする必要がある。これは、sendTimingResp
を呼び出す前に行わなければならない。そうしないと、マスター・ポートが応答を受信してから別のリクエストを送信するまでの間に1つのコールチェーンを持っている可能性があるため、無限ループにはまる可能性がある。
SimpleMemobj
のブロックを解除した後、パケットが命令パケットかデータパケットかをチェックし、適切なポートに送り返す。最後に、オブジェクトのブロックが解除されたので、CPU側のポートに、失敗したリクエストの再試行が可能になったことを通知する必要があるかもしれない。
bool SimpleMemobj::handleResponse(PacketPtr pkt) { 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; }
MemSidePort
にパケット送信用の便利関数を実装したのと同様に、CPUSidePort
にsendPacket
関数を実装して、CPU側にレスポンスを送信することができる。この関数はsendTimingResp
を呼び出し、その結果、相手マスター・ポートのrecvTimingResp
が呼び出される。この呼び出しが失敗し、ピアポートが現在ブロックされている場合、後で送信するためにパケットを保存する。
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); }
最後に、CPUSidePort用の追加関数trySendRetry
を実装する必要がある。trySendRetry
はSimpleMemobj
が新しいリクエストでブロックされたときに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
を変更することで、次の章のキャッシュのようなリッチな機能を持つオブジェクトを作ることができる。