SiFive社のCPUおよびRocketChipには、バスとしてTileLinkおよびDiplomacyが使用されている。
TileLinkはバスプロトコルなので良いとして、Diplomacyの理解は非常に難解だ。私もまだ完全に理解できていない。
Chipyardのリファレンスは比較的詳しく書いてあると思うので、この資料を読みながらDiplomacyの勉強をしていこうと思う。
9. TileLinkとDiplomacyリファレンス
TileLinkはRocketChipおよび他のChipyardジェネレータで使用されているキャッシュコヒーレンスとメモリプロトコルである。これらは、キャッシュ、メモリ、周辺機器、DMAデバイスなどの異なるモジュールとどのように互いに通信を行うかを規定している。
RocketChipのTilelinkの実装はDiplomacyとよばれる、Chiselジェネレータ内で構成情報を交換して行われる2段階のエラボレーション方式を使用している。Diplomacyの詳細に関しては、Cook, Terpstra, Leeの論文を参考にすること。
シンプルなTileLinkのウィジェットを追加する方法の概要については、アクセラレータデバイスを追加するセクションを参考にすること。このセクションでは、RocketChipにより提供されているTilelinkおよびDiplomacyの機能について説明する。
TileLink 1.7プロトコルの詳細な仕様についてはSiFiveのウェブサイトを参考にすること。
9.1. TileLinkのノードタイプ
DiplomacyはSoC内の異なるコンポーネントをDirected Acyclic Graphのノードとして表現する。TileLinkのノードは異なるタイプを持つことができる。
9.1.1. クライアントノード
TileLinkのクライアントはTileLinkのトランザクションを、まずはAチャネルを通じてリクエストを発行し、Dチャネルを通じてレスポンスを受け取る。クライアントの種類がTL-Cであれば、ProbeリクエストをBチャネルを通じて受け取り、ReleaseコマンドをCチャネルを通じて発行し、Eチャネルを通じてGrantメッセージを発行する。
RocketChip/ChipyardのL1キャッシュ及びDMAデバイスはクライアントノードである。
TileLinkクライアントを追加するためには、testchippipから、LazyModuleにTLHelperオブジェクトを以下のように追加することで実現できる:
class MyClient(implicit p: Parameters) extends LazyModule { val node = TLHelper.makeClientNode(TLClientParameters( name = "my-client", sourceId = IdRange(0, 4), requestFifo = true, visibility = Seq(AddressSet(0x10000, 0xffff)))) lazy val module = new LazyModuleImp(this) { val (tl, edge) = node.out(0) // 残りのコードをここに追加する。 } }
name
引数はDiplomacyグラフの中でのノードの名前を識別する。この引数はTLClientParametersの中で唯一必須の引数である。
SourceI
引数はこのクライアントが使用するソースIDの範囲を指定する。ここでは[0, 4)までのIDを使用しているので、このクライアントは同時に4つまでのリクエストをインフライトに発行することができる。各リクエストはsourceフィールドにおいてそれぞれ異なる値を設定される。このフィールドのデフォルト値はIdRange(0, 1)
であり、これは単一のリクエストのみがインフライトすることができることを意味する。
requestFifo
引数はBooleanのオプションでありデフォルトではfalseである。この引数をtrueに設定すると、クライアントノードはダウンストリームのマネージャに対してレスポンスをFIFOの順序で送ることを要求する(つまり、当該リクエストが送信されるのと同じ順番にリクエストが返ってくることを期待する)
visibility
引数はクライアントがアクセスするアドレス領域を指定する。デフォルトの値はすべてのアドレスを含んでいる。例えば、1つのアドレス領域AddressSet(0x10000, 0xffff)
を指定した場合、クライアントは0x10000から0x1fffffまでの領域のみアクセスできる。通常はこの設定は使用しないが、ダウンストリームのクロスバージェネレータがハードウェアを最適化するために使用することができる。つまり、クライアントとマネージャのアービトレーションにおいて、visibilityが重ならないアドレス領域におけるクロスバーの最適化を実施することができる。
Lazyモジュールの実装において、node.out
という表記はbundle/edgeのペアのリストを取得するために使用する。TLHelperを使用しているならば、単一のクライアントエッジのみが指定され、単一のペアのみが返される。
tl
バンドルはChiselのハードウェアバンドルであり、このモジュールのIOポートを接続する。このバンドルにはTL-ULおよびTL-UHの場合2種類のTileLinkチャネルのDecoupleバンドルが含まれており、TL-Cの場合は5種類である。ここまでで、TileLinkにメッセージを送受信するために必要なハードウェアを接続するための準備が整った。
edge
オブジェクトには、Diplomacyグラフのエッジが示されている。エッジにはTileLinkのエッジオブジェクトメソッドに示された様々な便利な関数が含まれている。
9.1.2. マネージャノード
TileLinkのマネージャはクライアントのAチャネルからリクエストを受け取り、Dチャネルにレスポンスを返す。マネージャノードはTLHelperを使用して以下のようにして作成できる。
class MyManager(implicit p: Parameters) extends LazyModule { val device = new SimpleDevice("my-device", Seq("tutorial,my-device0")) val beatBytes = 8 val node = TLHelper.makeManagerNode(beatBytes, TLManagerParameters( address = Seq(AddressSet(0x20000, 0xfff)), resources = device.reg, regionType = RegionType.UNCACHED, executable = true, supportsArithmetic = TransferSizes(1, beatBytes), supportsLogical = TransferSizes(1, beatBytes), supportsGet = TransferSizes(1, beatBytes), supportsPutFull = TransferSizes(1, beatBytes), supportsPutPartial = TransferSizes(1, beatBytes), supportsHint = TransferSizes(1, beatBytes), fifoId = Some(0))) lazy val module = new LazyModuleImp(this) { val (tl, edge) = node.in(0) } }
The makeManagerNode
method takes two arguments. The first is beatBytes
, which is the physical width of the TileLink interface in bytes. The second is a TLManagerParameters object.
makeManagerNode
メソッドは2つの引数を取る。1つ目はbeatBytes
であり、これはTileLinkインタフェースの物理的な幅をバイト単位で返す。2番目の引数はTLManagerParametersオブジェクトである。
TLManagerParameters
における必須の引数はaddress
のみであり、これはマネージャが管理するアドレスの領域を指定する。この情報はクライアントからのリクエストをルーティングするために使用される。この例では、マネージャは0x20000から0x20fffまでの領域からのリクエストを受け取る。AddressSet
の2番目の引数はマスクであり、サイズではない。したがってこのアドレス領域の設定は2の累乗の範囲内で行わなければならない。そうでなければ、アドレッシングの設定は設定の意図とは異なるように動作してしまう。
2番目の引数はresources
であり、Device
オブジェクトから値を設定する。この場合にはSimpleDevice
を指定している。この引数はエントリをBootROM上に書き込まれるデバイスツリーに追加したい場合には必須である。デバイスツリーの情報はLinuxのドライバにより読み込まれる。SimpleDevice
に設定されている2つの引数は名前およびデバイスツリーのエントリの互換性に関する情報のリストである。このマネージャでは、デバイスツリーの情報は以下のようになる:
L12: my-device@20000 { compatible = "tutorial,my-device0"; reg = <0x20000 0x1000>; };
次の引数はregionType
である。この引数はマネージャのキャッシュの動作について示している。この引数は、以下の7つの内どれかの値を設定できる:
CACHED
- 中間エージェントはその領域におけるキャッシュされたコピーを保持する。TRACKED
- この領域は別のマスターによりキャッシュされるが、コヒーレンスの機能は提供される。UNCACHED
- この領域はキャッシュされない。しかし可能ならばキャッシュされるべきである。IDEMOTENT
- 最近Putされた内容を返すが、内容はキャッシュされるべきではない。VOLATILE
- 内容はPut操作無しで変更される可能性があるが、PutおよびGetでは副作用は発生しない。PUT_EFFECTS
- Putにより副作用が発生するため、コマンドを複合したり、遅延させるべきではない。GET_EFFECTS
- Getは副作用を発生させるため、投機的に発行させるべきではない。
次はexecutable
引数であり、CPUがフェッチ命令をこのマネージャから発行することができるかを示している。デフォルトではfalseであり、MMIOなどの周辺機器では設定すべきである。
次の6つの引数はsupport
から始まり、異なるAチャネルのメッセージタイプについて、受付可能かを決めている。メッセージタイプの定義については、TileLinkエッジオブジェクトのメソッドを参照のこと。TransferSizes
のケースクラスには、各メッセージタイプについてマネージャが受け付けることのできる論理サイズがバイト単位で指定されている。これは内包的な領域であり、すべての論理サイズは2の累乗である必要がある。したがってこの場合には、マネージャは1, 2, 4, 8バイトのリクエストを受け付けることができる。
最後の引数はfifoId
設定であり、FIFOのドメインにマネージャが入っているかを示している。この引数をNoneに設定(デフォルトである)すると、マネージャはレスポンスの順序を保証しない。fifoId
を設定すると、同じfifoId
が設定された他のすべてのマネージャと共有され、つまりそのFIFOドメイン内のクライアントからのリクエストはすべて同じ順序で帰ってくることを意味する。
9.1.3. レジスタノード
マネージャノードを直接指定してTileLinkリクエストを処理する論理をすべて書いたとしても、通常はレジスタノードを使用した方が簡単である。このタイプのノードはregmap
メソッドを提供し制御/状態レジスタを自動的に生成しTileLinkプロトコルを処理するための論理も生成する。レジスタノードの使用方法について野依詳細についてはレジスタノードを参照のこと。