FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

Gem5のチュートリアル "Learning Gem5"をやってみる(12. メモリシステムへのSimObjectの作成)

gem5を構成するSimObjectを追加するためのチュートリアルをやってみる。

www.gem5.org

  • gem5記事一覧インデックス

msyksphinz.hatenablog.com


この章では、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(書き込み応答)である。また、キャッシュ用のライトバック要求(WritebackDirtyWritebackClean)、その他多くのコマンドタイプがある

パケットはまた、リクエストのデータか、データへのポインタを保持する。パケットを作成するときに、データが動的(明示的に割り当てられ、割り当て解除される)か、静的(パケットオブジェクトによって割り当てられ、割り当て解除される)かのオプションがある。

最後に、パケットはコヒーレンシを追跡する単位として古典的なキャッシュで使用される。したがって、パケット・コードの多くは、古典的なキャッシュ・コヒーレンシ・プロトコルに特有のものである。しかし、パケットは、コヒーレンシに直接関係しないメモリ・オブジェクト(例えば、DRAMコントローラとCPUモデル)であっても、gem5のすべてのメモリ・オブジェクト間の通信に使用される。

すべてのポートインターフェース関数は、パラメータとしてPacketポインタを受け取る。このポインタは非常に一般的なので、gem5には型定義が含まれている: PacketPtrである。

ポート・インターフェース

gem5にはmasterポートとslaveポートの2種類がある。メモリオブジェクトを実装するときは、必ずこの2種類のポートのうち少なくとも1つを実装することになる。そのためには、masterポート用にMasterPortを、slaveポート用にSlavePortを継承した新しいクラスを作成する。masterポートはリクエストを送信し(レスポンスを受信し)、slaveポートはリクエストを受信する(レスポンスを送信する)。

下図は、masterポートとslaveポートの最も単純な相互作用の概要である。この図はタイミングモードでの相互作用を示す。他のモードはもっと単純で、masterとslave間で単純なコールチェーンを使用する。

上述したように、全てのポートインターフェースは、パラメータとしてPacketPtrを必要とする。これらの各関数(sendTimingReqrecvTimingReqなど)は、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も同じコールスタックで呼び出すことができる。そのため、無限再帰のバグやその他のバグを誤って発生させやすい。メモリオブジェクトがリトライを送る前に、その瞬間に別のパケットを受け入れる 準備ができていることが重要である。