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
を変更することで、次の章のキャッシュのようなリッチな機能を持つオブジェクトを作ることができる。