gem5を構成するSimObjectを追加するためのチュートリアルをやってみる。
- gem5記事一覧インデックス
この章では、CPUとメモリバスの間に位置するシンプルなメモリ・オブジェクトを作成する。次の章では、この単純なメモリオブジェクトにいくつかのロジックを追加して、非常に単純なブロッキング・ユニプロセッサ・キャッシュにする。
gem5のmasterポートとslaveポート
メモリオブジェクトの実装に入る前に、まずgem5のmasterポートとslaveポートのインターフェイスを理解する必要がある。以前にsimple-config-chapterで説明したように、すべてのメモリオブジェクトはポートを介して接続されている。これらのポートは、これらのメモリオブジェクト間の強固なインターフェースを提供する。
これらのポートは、timing, atomic, functionalの3つの異なるメモリシステムモードを実装している。最も重要なモードはtimingモードである。timingモードは正しいシミュレーション結果が得られる唯一のモードである。他のモードは特殊な状況でのみ使用される。
atomicモードは、目的の領域までシミュレーションを早送りし、シミュレータをウォームアップするのに便利である。このモードでは、メモリシステムでイベントが発生しないことを想定している。その代わり、すべてのメモリ要求は1つの長いコールチェーンを通して実行される。ファストフォワード中やシミュレータのウォームアップ中にメモリ・オブジェクトが使用されない限り、atomicアクセスを実装する必要はない。
functionalモードは、デバッグ・モードと言った方がよい。functionalモードは、ホストからシミュレータ・メモリへのデータの読み込みなどに使用される。syscall エミュレーション・モードで多用される。例えば、process.cmd
のバイナリをホストからシミュレータのメモリにロードし、シミュレータがそれにアクセスできるようにするために、functionalモードが使われる。機能的なアクセスは、データがどこにあろうと、読み込みでは最新のデータを返すべきであり、書き込みでは有効なデータの可能性をすべて更新すべきである(例えば、キャッシュを持つシステムでは、同じアドレスを持つ有効なキャッシュブロックが複数存在する可能性がある)。
パケット
gem5では、Packet
はポート間で送信される。Packet
は、メモリリクエストオブジェクトであるMemReq
で構成される。MemReq
は、requestor、address、要求のタイプ(読み取り、書き込みなど)など、パケットを開始した元の要求に関する情報を保持する。
パケットはまた、パケットの現在のコマンドであるMemCmd
を持つ。このコマンドは、パケットの寿命を通じて変化する可能性がある(例えば、メモリコマンドが満たされると、リクエストはレスポンスに変わる)。最も一般的なMemCmd
は、ReadReq
(読み出し要求)、ReadResp
(読み出し応答)、WriteReq
(書き込み要求)、WriteResp
(書き込み応答)である。また、キャッシュ用のライトバック要求(WritebackDirty
、WritebackClean
)、その他多くのコマンドタイプがある
パケットはまた、リクエストのデータか、データへのポインタを保持する。パケットを作成するときに、データが動的(明示的に割り当てられ、割り当て解除される)か、静的(パケットオブジェクトによって割り当てられ、割り当て解除される)かのオプションがある。
最後に、パケットはコヒーレンシを追跡する単位として古典的なキャッシュで使用される。したがって、パケット・コードの多くは、古典的なキャッシュ・コヒーレンシ・プロトコルに特有のものである。しかし、パケットは、コヒーレンシに直接関係しないメモリ・オブジェクト(例えば、DRAMコントローラとCPUモデル)であっても、gem5のすべてのメモリ・オブジェクト間の通信に使用される。
すべてのポートインターフェース関数は、パラメータとしてPacket
ポインタを受け取る。このポインタは非常に一般的なので、gem5には型定義が含まれている: PacketPtr
である。
ポート・インターフェース
gem5にはmasterポートとslaveポートの2種類がある。メモリオブジェクトを実装するときは、必ずこの2種類のポートのうち少なくとも1つを実装することになる。そのためには、masterポート用にMasterPort
を、slaveポート用にSlavePort
を継承した新しいクラスを作成する。masterポートはリクエストを送信し(レスポンスを受信し)、slaveポートはリクエストを受信する(レスポンスを送信する)。
下図は、masterポートとslaveポートの最も単純な相互作用の概要である。この図はタイミングモードでの相互作用を示す。他のモードはもっと単純で、masterとslave間で単純なコールチェーンを使用する。
上述したように、全てのポートインターフェースは、パラメータとしてPacketPtrを必要とする。これらの各関数(sendTimingReq
、recvTimingReq
など)は、1つのパラメータ、PacketPtr
を受け付ける。このパケットは、送信または受信するリクエストまたはレスポンスである。
リクエストパケットを送信するために、masterはsendTimingReq
を呼び出す。 次に(同じコールチェーンで)、同じPacketPtr
を唯一のパラメータとして、関数recvTimingReq
がslave上で呼び出される。
recvTimingReq
の戻り値の型はboolである。このブール値の戻り値は、呼び出したmasterに直接返される。trueの戻り値は、パケットがslaveに受け入れられたことを意味する。一方、falseの返り値は、slaveが受け入れられなかったことを意味し、リクエストはいつか再試行されなければならない。
上図では、まずmasterがsendTimingReq
を呼び出してタイミング要求を送信し、次にrecvTimingResp
を呼び出す。 slaveは、sendTimingReq
の呼び出しから返されるrecvTimingReq
からtrueを返す。masterは実行を続け、slaveはリクエストを完了するために必要なことを行う(例えば、キャッシュであれば、リクエストのアドレスと一致するタグがあるかどうかを調べる)。
slaveがリクエストを完了すると、masterに応答を送信できる。slaveは応答パケットでsendTimingResp
を呼び出す(これはリクエストと同じPacketPtr
でなければならないが、今は応答パケットでなければならない)。次に、masterの関数recvTimingResp
が呼び出される。masterのrecvTimingResp
関数は、slaveのsendTimingResp
の戻り値であるtrueを返す。こうして、そのリクエストのインタラクションは完了する。
後のmaster-slave-example-section で、これらの関数のコード例を示す。
masterやslaveがリクエストやレスポンスを受け取るときにビジー状態になっている可能性がある。下図は、リクエストが送信されたときにslaveがビジー状態であった場合を示している。
この場合、slaveはrecvTimingReq
関数からfalseを返す。masterがsendTimingReq
を呼び出した後にfalseを受け取った場合、その関数recvReqRetry
が実行されるまで待たなければならない。この関数が呼び出されたときのみ、masterはsendTimingRequest
の呼び出しを再試行することができる。上の図は、タイミングリクエストが1回失敗したことを示しているが、何回でも失敗する可能性がある。注意:失敗したパケットを追跡するのはslaveではなくmaster次第である。slaveは失敗したパケットへのポインタを保持しない。
同様に、この図は、slaveが応答を送信しようとしたときにmasterがビジー状態だった場合を示している。この場合、slaveはrecvRespRetry
を受信するまでsendTimingResp
を呼び出すことができない。
重要なことは、これら両方のケースにおいて、リトライのコードパスは単一のコールスタックになり得るということである。例えば、masterがsendRespRetry
を呼び出すとき、recvTimingReq
も同じコールスタックで呼び出すことができる。そのため、無限再帰のバグやその他のバグを誤って発生させやすい。メモリオブジェクトがリトライを送る前に、その瞬間に別のパケットを受け入れる 準備ができていることが重要である。