ロバストなマイクロコントローラとFPGAのSPIインターフェースの実装: パート2 - プロトコルの定義

APDahlen Applications Engineer

この記事シリーズの主な目的は、マイクロコントローラー(uC)とフィールドプログラマブルゲートアレイ(FPGA)間でデータを迅速かつ確実に転送するメカニズムを開発することです。この回では、通信プロトコルと関連フレームの属性について説明します。

uCからFPGAへのインターフェースを設計するには、さまざまな方法があります。最初に一連の一般的な設計要件を確立することで、この話を組み立ててみましょう。その設計要件とは以下です。

  • uCとFPGA間のワイヤの数を最小限に抑えること
  • uCとFPGAの間でターゲットを絞った(アドレス指定可能な)情報交換が可能であること
  • 数千のアドレス指定可能なFPGAレジスタに対応できること
  • メッセージの長さを最適化すること
  • FPGAを過度に複雑にすることなく、uCのプログラミングが容易にできること
  • 高速であること
  • データの整合性の検証を提供すること
  • クロスプラットフォームの互換性があること(例:Xilinx固有ではない等)

FPGAリソースの使用を最小限に抑えることは設計要件の1つではないことに注意してください。この明らかな省略の理由については、このシリーズのパート1で説明しています。すべてのFPGAモジュール出力がクロックドメイン内で登録され、同期していることを思い出してください。この設計規定は、FPGAファブリックの消費量と、設計の安定性およびトラブルシューティング時間の短縮とのバランスを取る、許容可能なトレードオフであると考えられます。この記事で説明するRTLは最適化されていませんが、Digilent Basys 3のXilinx Artix-7 XC7A35T-1CPG236C FPGAで利用可能な総リソースのごく一部を使います。

プロトコルのコンセプト

設計とその要件は、ハードウェアとデータプロトコルの観点から最もよく表せます。これは、Open Systems Interconnect (OSI)モデルのレイヤ1およびレイヤ2に似ています。レイヤ1の物理レベルは電気仕様を記述します。この設計で、ワイヤ数が少ないということは、 I^2C 、SPI、デュアルSPI、さらにはクアッドSPIで使用することを意味します。これらのオプションの中で、SPIは全二重通信を可能にし、uCのプログラミングが簡単であるという点で際立っています。余談ですが、FPGAはuCよりも高速にデータを処理できるため、フロー制御メカニズムの必要性がなくなると仮定します。

レイヤ2のデータリンクレベルでは、データ交換のためのプロトコルを定義する必要があります。このレイヤでは、適切なサイズのブロックにデータをフレーム化し、適切なヘッダとエラー検出を行います。レイヤ2はレイヤ1で指定された電気インターフェースに直接関係しないため、レイヤ2とレイヤ1は互いに独立していることに注意してください。これにより、将来的には、SPIインターフェースをクアッドSPIさらにオクタSPIに置き換えることができるようになります。ただし、レイヤ間のバイト幅のインターフェースが保持されている場合に限ります。

私の知る限り、SPIを使用したデータ交換の確立されたプロトコルはありません。デバイス固有の実装が一般的です。一例として、センサには、データの読み取りおよび書き込みを行うためのメーカーまたはデバイス固有のプロトコルが搭載されている場合があります。これには、単一またはレジスタのブロックに関係するデータ交換のルールが含まれます。一部のデバイスには巡回冗長検査(CRC)が組み込まれており、データの読み取りおよび書き込み操作に対してデータ整合性を確認できます。最後に、ほとんどのSPIデバイスは半二重プロトコルで動作します。一般的なSPI転送は、読み取り/書き込みフラグを含むバイトから始まります。デバイスはその後、後続のクロックオクテットに対して適切なアクションを実行します。MOSI回線とMISO回線で同時送信を行う全二重はほとんどありません。

uCの観点からは、全二重SPIマスターがそれぞれ実現できます。以下は、チップセレクト信号におけるSPI転送のフレーミングを含むArduinoの実装です。

// prepare buf for SPI transmission
// append CRC

digitalWrite(CS_PIN, LOW);
SPI.transfer(buffer, bufferSize);    // full-duplex: each uC byte in buff is replaced with an FPGA byte
digitalWrite(CS_PIN, HIGH);

// verify and handle CRC
// unpack and handle FPGA data

802.3 フレームの SPI への適合

続けて説明する前に、802.3 EthernetフレームをSPIプロトコルの出発点として検証することは有益です。図1に見られるように、Ethernetフレームはプリアンブル、MAC宛先+送信元、長さのフィールドを含むヘッダで始まります。次に、46バイトから1500バイトのペイロードが続き、これに32ビットのCRCコードが続きます。

図1:簡略化された802.3 Ethernetフレーム

Ethernetフレームのサイズを縮小して、uCからFPGAへのインターフェースに適したものにすることができます。SPIでは、プリアンブルやアドレッシングは必要ありません。SPIのCS_not制御ラインとSPIの1対1通信の性質により、これらの機能は不要になります。フィールドのサイズと可変長のペイロードは、保持されることが望ましい機能です。これにより、uCが1つのFPGAレジスタを読み書きできるようになります。データのブロックを転送することもできます。最後に、CRCによりデータ検証が可能になります。

私たちの目的では、フィールドのサイズは小さくてもいいかもしれません。合理的な妥協案は、ペイロードの長さを識別するために1バイトを使用することです。これにより、フレームの長さはヘッダとCRC用のスペースを含めて256要素におのずと制限されます。フレーム長の短縮を考慮すると、16ビットCRCが合理的です。

メッセージタイプ要件のバランスを取る

SPIは全二重通信チャネルを提供することを思い出してください。これまでに示したように、これはuCプログラミングの観点から見てそれほど複雑ではありません。ただし、この全二重モードを活用するには、FPGA内の特定のハードウェアをアドレス指定する方法を決定する必要があります。

この方法を使用するには、FPGAハードウェアがアドレス指定可能である必要があります。たとえば、FPGAトレーニングボードのLEDは、ベースアドレス(Basys3にインストールされている16個のLEDの2バイト)に割り当てることができます。ボードの16個のスライドスイッチは、別の2つのアドレスに割り当てることができます。この構成により、FPGAはあたかもuCの別のペリフェラルであるかのように動作します。uCは、SPI経由のフレームプロトコルを使用してFPGAハードウェアの読み取りと書き込みを行います。

この記事の残りの部分では、FPGAハードウェアをペリフェラルと呼びます。これは、uCがSPIインターフェースを介してFPGAを高性能ペリフェラルとして効果的に使用する設計を反映しています。

設計要件の1つは、数個から数千個のFPGAレジスタに対応できる拡張性です。この要件を考慮すると、バイト幅のアドレスでは項目数が256に制限されるので厳しすぎます。しかし、2バイト幅のインターフェースであれば、uCがFPGA内で最大65536項目をアドレス指定できるため、受け入れられるでしょう。

この時点で話を一旦停止して、SPIフレームのヘッダを特定します。これには、コマンドのタイプ(読み取りまたは書き込み)を示す1バイト、ペイロード長の1バイト、アドレスの2バイト、可変長ペイロード、およびCRCが含まれます。対応する長さは4バイト、ペイロードは最大250バイト、CRCは2バイトです。

多くの設計ではこれで十分かもしれません。ただし、この方法には半二重という暗黙の前提があります。その理由を理解するには、SPI転送に関連する因果関係を考慮してください。ボードの16個のLEDを制御するレジスタがベースアドレス0x0200にあったとします。このレジスタ上でSPI転送を開始すると、実際にはFPGAレジスタを読み取ってから書き込むことになります。古いデータが含まれるため、読み取り操作は無意味です。ここでは、マルチバイトレジスタの更新に伴う不安定性を防ぐために、すべてのFPGAレジスタにダブルバッファリングが実装されていると仮定します。

全二重の場合は、別のアドレス指定フィールドを追加する必要があります。FPGAボードのスイッチステータスを保持するレジスタがアドレス0x0202にあるとします。別のアドレス指定フィールドをフレームに追加すると、有効な読み取りおよび書き込み操作を実行できるようになります。

これで、LEDを更新すると同時に、スイッチの状態を読み取ることができます。

新しいヘッダは5バイト、ペイロードは最大249バイト、CRCは2バイトとなりました。コマンドバイトはもはや必要ないことに注目してください。

  • ペイロード長:(1バイト)ヘッダとペイロードのバイト数
  • Toアドレス フィールド:(2バイト)書き込まれるFPGAレジスタを識別します
  • Fromアドレスフィールド:(2 バイト) 読み取られるFPGAハードウェアを識別します
  • ペイロード:(1から249バイト)
  • CRC:(2バイト)

2つのオプションをよく比較すると、1バイトの違いがあることがわかります。読み取り/書き込みコマンドバイトを含む半二重フレームでは、FPGAボードの16個のLEDに書き込むために8バイトの転送が必要になります。対応する全二重動作には9バイトが必要です。ただし、この設計ではスイッチの読み取りとLEDへの書き込みを同時に行うことができます。これをどう見るかは人によって異なりますが、プロセスで1バイトを無駄にしたか、8バイトに加えてトランザクションに関連する時間のオーバーヘッドを節約したことになります。

これらの設計上のトレードオフを考慮した後、全二重オプションのみに焦点を当てます。uCのオーバーヘッドとFPGAプログラミングのオーバーヘッドは重要ではありません。小さなペイロード転送では速度差は最小限です。ペイロードが大きい場合も、この違いは重要ではありません。

この全二重プロトコルには、認識しなければならない欠点が1つあります。それは、各データ転送が書き込み操作を伴うようになったという事実です。そうすることが不便な場合もあるでしょう。書き込み操作を混乱させるのではなく、0xFF00から0xFFFFの位置にあるFPGAアドレスの空で非機能なブロックを予約します。読み取り専用メモリ操作が必要な場合、uCはこの空の場所に「書き込み」ます。この簡単な変更により、全二重動作を半二重読み取り動作として動作させることができます。

FPGAへの書き込みとFPGAからの読み出し

全二重uCからFPGAへのプロトコルのコマンドフレームと応答フレームを図2に示します。ヘッダの内容はフレーム間で密接に対応しています。読み取りアドレスと長さのヘッダ情報が応答にエコーされていることがわかります。応答には因果関係の問題があることに注意してください。フィールドはバイトごとの同時転送でストリーミングされるため、応答は遅れます。たとえば、最初に送信されるバイトである長さフィールドを応答の最初のフィールドにすることはできません。これは、FPGAが送信される最初のバイトをロックする時点ではまだ受信されていないためです。書き込みアドレスは応答フレームにとって重要ではないため、これは問題ではありません。代わりに、uCが最初のバイトの長さをストリーミングしている間に、FPGAはプログラム固有のフラグを送信できます。

フレームフィールドの残りの部分も同様です。FPGA応答は、ベース読み取りアドレスから始まるデータをストリーミングします。一方、FPGAは同時にuCデータを一時バッファに移動します。それぞれのCRCが計算され、フレームに追加されます。

図2: コマンドフレームと応答フレームの関係

コマンドフレームのデータ整合性

データの整合性の尺度はCRCによって提供されます。CRCはフレームコンテンツに適用されることを思い出してください。uCの観点から3段階のプロセスを考えてみましょう。uCはまず、関連データを入れたフレームバッファを準備します。次に、CRCを計算してバッファに追加する関数を呼び出します。最後に、uCは関数を呼び出してSPI経由でデータを転送します。

FPGAはストリーミングデータをバッファに配置し、同時にコマンドフレームのCRC を計算します。フレームが完了すると、FPGAは受信したコマンドフレームのCRCと計算されたコマンドフレームのCRCを比較します。それらが一致すると、FPGAはデータをバッファから対応するハードウェアレジスタに迅速に転送します。これは、全てのペイロードに約5uSを必要とする比較的高速な操作です。受信したCRCと計算したCRCが異なる場合、FPGAはフレームを破棄します。 また、フレームエラー処理も起動されます。

図2の応答フレームに見られるように、トラブルシューティングのためにフレームエラーカウンタが含まれています。また、FPGAからuCにつながる物理的な配線を追加することもできます。これにより、uCがフレームエラーを高速で検出し、応答できるようになります。

FPGAの一時フレームバッファの重要性は、どれだけ強調してもしすぎることはありません。FPGAは、CRCによってエラー無しが検証されるまで、新しいデータに対して動作しません。 この重要なトピックについては、別の記事で取り上げます。

応答フレームのデータ整合性

応答フレームの整合性を確保するには、uCでの慎重なプログラミングが必要です。まず、コマンドフレームが検証されるずっと前に、FPGAがuCの読み取りリクエストに即座に応答することを認識する必要があります。その結果、FPGAがコマンドを理解するだけでなく、FPGAが生成したデータの整合性を検証するのはuCの責任になります。これは、応答フレーム内のフレーム長とベース読み取りアドレスのエコーによって行われます。uCは、応答フレームとコマンドフレーム内のデータを比較することでエラーを識別できます。

たとえば、コマンドフレームがuCからFPGAに送信されるときに、コマンドフレームの読み取りアドレスフィールドにビットエラーがあったとします。FPGAのCRC機構によってエラーが検出される可能性が非常に高いため、uCからFPGAへの書き込み操作の整合性は十分に評価されます。FPGAはフレームを無視します。同時に、FPGAの並列構造に関連する因果関係も考慮する必要があります。

FPGAは、コマンドフレームが破損していることを認識するずっと前に、読み取りデータをストリーミングします。実際、律儀に、誤った読み取りアドレスからデータをストリーミングします。有効なCRCもフレームに追加します。

トランザクションが完了すると、uCは応答フレームの完全なコピーを保持します。動作する前に、データを検証する必要があります。これは2段階のプロセスです。

  1. uCは最初に、送信フィールドと受信フィールドの長さと読み取りアドレスの一致を検証する必要があります。
  2. 次に、応答フレームのCRCを検証する必要があります。

両方の操作が完了すると、uCは通信エラーを処理するために適切なアクションを実行できます。

パート3が掲載されました

ご意見やご提案をお待ちしております。高レベルのRTLシステム設計方法論に関するさらなる議論は特に歓迎致します。

ご健闘をお祈りします。

APDahlen




オリジナル・ソース(English)