FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

Rocket Chip Generatorのバス生成技術を読み解く(2. Diplomacyの論文を読む2)

Diplomacyの論文を読むその2.前回の記事は以下。

msyksphinz.hatenablog.com

carrv.github.io

後半はDiplomacyのデザインパタンについて。


f:id:msyksphinz:20190619222846p:plain
TileLinkプロトコル

4. Design Patterns

このセクションではDiplomacyとTileLinkをどのようにして有効化し、ハードウェアデザインパタンとして、ジェネレータを使用したデザインに組み込むかについて解説する。

4.1 DRYing Out Parameterization

"Don't Repeat Yourself(DRY)"という考え方を踏襲する。つまり、デザインを冗長に定義しない、そしてシステム全体、情報やドキュメントなども冗長化しないという考え方である。デザインを変更すると、マニュアルなどのドキュメントも変更する、という考え方である。しかし、パラメタライズしたハードウェアの生成はDRYに基づいたハードウェア設計の素晴らしいソースとなるが、パラメタライズ自身が、直行しない複雑性の根源となってしまうのが問題である。

パラメタライズされたアービタのジェネレータの例を考える。アービタはN個の入力を受け取り、Muxで1つだけを外に渡す。そして\log_2(N)の整数をソースフィールドからアービトレーションされたメッセージに追加し、レスポンスを正しいポートに返すことができるようにする。Non-Diplomaticなコードベースでは、カーディナリティNを様々な場所でバインドしなければならない。

  1. アービターモジュールの構築時
  2. すべての拡張されたソースフィールドの配線をダウンストリームに伝搬させる

これにより、デザインの中のNというパラメータのために様々モジュールのインスタンスと配線のコンフィグレーションに変更が加わる。デザイン中の様々な場所でこのNという要素が絡み、これらの要素について正しく接続できていることがすべて確認できないと、ミスコンフィグレーションとして問題が発生することになる。

Diplomacyによりこれらの問題を解決することができる。Diplomatic Arbiterのジェネレータの場合、我々がN個のソースノードをArbiterのノードに接続したという事実は、単一の、明白な、独占的な表現である。Nというカーディナリティをコードベース上のどこにも宣言する必要なく、Nという値はデザインのグラフトポロジから自動的に算出され、アービタのジェネレータに伝えられ、さらにすべてのダウンストリームのエッジに対して伝えられる。

4.2 Viewを用いたハードウェア生成

カーディナリティの正確な情報を提供すること以上に、Diplomaticなグラフはシステム中のインターコネクトに必要な他のノードの情報をジェネレータに提供することができる。

  • マスターノード : 全てのスレーブの情報を確認する
    • 特定のメモリ操作が有効であるか
      • Rocket ChipのTLB : 与えられたページについて、Read/Write/Execute操作がサポートされているかどうか, 安全にキャッシュすることができるか、そして特定のアトミック操作が実行できるか
      • 特定のページテーブルエントリについてパーミッションが記録されているか
    • Chiselのコンパイル時にTLBがキャッシュされたメモリの転送がすべてのスレーブで実行可能かを確認する。
  • スレーブノード : すべてのマスターの操作が到達できるかどうかを確認する
    • すべてのマスターの数を数えて、ディクショナリの作成時に必要なビット数を計算する。
    • 特定のマスターに対して順序を記録する。
  • アダプタノード : 相互接続ジェネレータに、マスタとスレーブの両方の濃度と機能に関する情報を提供する。

全体的に見ると、このハードウェア生成のアプローチにより、設計者はシステムのグローバルな観点から見ることができるようになる。これは例えばローカルのコンポーネントに対して生成的なパラメータを定義しても同様である。

4.3 Correct by Composition

TileLinkの特性により、現状のDiplomaticグラフに対して特定の変換を行っても、別の正しいグラフwお確実に生成することができるという保証を持っている。これにより、デザインの等価性を維持したまま、サブグラフの挿入が可能となる。ここでは、Rocket Chipのコードベースの構造を形作る3つの変換について説明する。

  1. Combinational Composition : 複数のアダプタノードが線形シーケンスで構成されている。
    • TileLinkプロトコルのReady/Validを使用し、各チャネルの信号は内側から外側へ向けてハンドシェークで進んでいく。
    • これらのモジュールは、なるべく役割を分担したほうが良く、アダプタはそれぞれ直行した機能を持っているべきである。例えば:
      • 制御信号を修正する
        • バーストメッセージを一連のシングルビートメッセージに分割する、など。
      • メッセージフィールドの幅を調整する(データバスプレーンの幅を広げるなど)
      • メッセージ全体にわたるトランザクションレベルの要件を管理する
        • 例えば、一連のメッセージ要求および応答がFIFO順序に従うことを確実にする。
    • Figure-1では、TileLinkからAXI4への変換の例を示している。これらは、個別の直行された機能を実装したアダプタを連結させる。これらの操作はゼロレイテンシのモジュールが挿入され、機能的に等価である。
  2. Sequential Composition : チャネルに対して機能を挿入する。

    • キューの挿入など。
      • キューの深さだけでなくフロー制御パラメータも提供する。
        • 例えば、連続したメッセージのエンキューのキューイングや空のキューを通るメッセージのフローなど
    • これにより、タイミングデカップリングポイントなどの問題を簡単に解決できることを目的とする。
  3. Hierarchical Composition : TileLinkはデッドロックしないことが保証されているため、SoCのサブ蔵hうを等価性を維持しながら別のサブグラフに変換する。

あるタイプのノードが、ジェネレータによって特に制約もなく接続することができると宣言されると、Scala(とChisel)の多重継承により、トレイとを介してサブグラフコンポーネントを構築する。Figure-4では抽象的なプロセッサとメモリの間を接続する機能が記述されている。

// Trait containing attachment points common to all systems
trait ConnectsToMemory {
  val processorMasterNode: TLSourceNode // abstract member
  val memorySlaveNode: TLSinkNode = LazyModule(new TLRAM).node
}

// No coherence manager provided, only a single cache is safe
trait ConnectsIncoherently extends ConnectsToMemory {
  require(processorMasterNode.masters.size <= 1)
  memorySlaveNode := processorMasterNode
}

// Make some coherence manager and insert it in the node graph:
trait ConnectsViaBroadcastHub extends ConnectsToMemory {
  val hub = LazyModule(new TLBroadcastHub)
  hub.node := processorMasterNode
  memorySlaveNode := hub.node
}

trait ConnectsViaL2Cache extends ConnectsToMemory {
  val l2 = LazyModule(new TLCache)
  l2.node := processorMasterNode
  memorySlaveNode := l2.node
}

// Add some master nodes and reify the attachment point:
trait HasOneCore extends ConnectsToMemory {
  val core = LazyModule(new Rocket)
  val processorMasterNode = core.node
}

trait HasTwoCores extends ConnectsToMemory {
  val cores = List(2).fill(LazyModule(new Rocket))
  val xbar = LazyModule(new TLXbar)
  val processorMasterNode = xbar.node
  cores.foreach { c => xbar.node := c.node }
}

// Compile and elaborate correct hardware:
class SingleCoreSystem extends HasOneCore with ConnectsIncoherently
class DualCoreSystem extends HasTwoCores with ConnectsViaBroadcastHub
class DualCoreL2System extends HasTwoCores with ConnectsViaL2Cache
// Fails at Scala compile time due to missing TLSourceNode instance:
class IncompleteSystem extends ConnectsViaL2Cache
// Fails during elaboration due to failed requirement:
class UnsafeSystem extends HasTwoCores ConnectsIncoherently