Packet to Transactionコアの動作について [IPコア]

QsysにはSPIやJTAGを使ってAvalonMMペリフェラルを操作できるAvalonMMバスブリッジモジュールがある。

SPI Slave/JTAG to Avalon Master Bridge Cores(pdf)
SPI Slave to Avalon Master Bridge Design Example

これを利用すると、NiosIIや自作AvalonMMマスタを使わなくてもQsysの中身を自在に操ることができる。
特にリソースの少ないMAX II/Vにちょっとしたペリフェラルを組み込んで制御するとか、FPGAの中身にテストでレジスタ操作をしたいというときには非常に便利な機能なのだけど、どうにも資料が揃っていない。
なので、ちょっと調べてみた。


・トランザクションの本体

SPI Slave/JTAG to AvalonMM Bridgeは単体のコアではなく、物理層・パケット層・トランザクション層の3つのレイヤからなる。このうちAvalonMMマスタとして振る舞うのはトランザクション層のコア(Packets to Transaction)になる。

このコアが処理できるパケットはコマンドヘッダにより4種類ある。
実際にはリード/ライトの2種類に対してそれぞれシングルとアドレスインクリメントの2つがあるので4種類ということになる。
基本的には

CMD 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD)

という共通の8バイトのパケットを流す。ライトの場合はこの後に書き込むデータを続ける。
SIZEはデータのサイズを指示する16bit長のデータ、ADRはアドレスを指示する32bit長のデータで、この2つのフィールドだけはネットワークバイトオーダーで指定する。

ただしQuartusII 13.0時点で、SIZEフィールドはリード時しか使われておらず、ライト時は後続のデータをカウントして書き込みサイズを取得するような実装になっている。

(1)シングルライトパケット

0x00 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD) D0 D1 … Dn

を送る。DATAは4バイト以下でなければならない。バイトアドレスの若い順から並べる(※アドレスフィールドとはバイトオーダーが逆になる)
また、アドレスは書き込むデータ長が32bit境界を跨ぐような指定をしてはならない。
下のような4バイトのライトレスポンスを返す。

0x80 0x00 SIZE(U) SIZE(D)

データは32bit単位で書き込まれるので、デスティネーションにバイトイネーブル機能がない場合、32bitデータ幅が全て書き換えられてしまうことに注意する。

(2)インクリメンタルライトパケット

0x04 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD) D0 D1 … Dn

を送る。DATAは最長65535バイトまで可能。バイトアドレスの若い順から並べる(※アドレスフィールドとはバイトオーダーが逆になる)
アドレスは開始バイト位置を指定する。
下のような4バイトのライトレスポンスを返す。

0x84 0x00 SIZE(U) SIZE(D)

インクリメンタルライトではデータは自動的に32bitにアラインメントされて書き込みが行われるため、これでレジスタに連続書き込みをする場合は、バイト単位でデータを送っていても32bit単位にまとめられて書き込みが発行されることに注意しなければならない。
特に非アライメントの転送をする場合にデスティネーションがバイトイネーブル機能を持っていないと、余計な部分まで書き込みがされる。

(3)シングルリードパケット

0x10 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD)

を送る。SIZEは読み込みバイト数で1,2,4のどれかを指示する。アドレスは読み込みバイト数が32bitアライメントを跨がないようにする。
下のようなリードレスポンスを返す。

SIZE = 0x0001の場合 : D0
SIZE = 0x0002の場合 : D0 D1
SIZE = 0x0004の場合 : D0 D1 D2 D3

AvalonMMの仕様として、リードは必ずデータビット幅(32bit幅)単位で行われるため、リードで状態が変わるソースに対してバイト/ハーフワードのリードをした場合、読んでいない部分のレジスタもリード扱いになるので注意すること。

(4)インクリメンタルリードパケット

0x14 0x00 SIZE(U) SIZE(D) ADR(UU) ADR(UD) ADR(DU) ADR(DD)

を送る。SIZEは読み込みバイト数で65535バイトまで指定。アドレスは開始バイト位置を指定する。
下のようなリードレスポンスを返す。

D0 D1…Dn

開始アドレスからのバイトを返す。ただしシングルリード同様に、内部では32bit単位のリードを行っているため、アライメントに合わないアドレスあるいはデータ数を指示した場合、指定されていないアドレスのデータバイトをリードする。
リードによって状態が変わるソースにアクセスする場合には注意が必要。



パケット層と物理層のバイトエンコードはデータシート見れば分かるので省略。

Packet to Transactionの動作はなかなかよく出来ているので、結構トリッキーな使い方もできそう。
実はインクリメンタルとシングルの差はアドレスのカウントアップをするかしないかだけで、ステートマシンは同じなので、シングルライト/リードのパケットに32bit以上のデータを付けても動作するし、32bit非アラインメントから32bitデータの読み書きをしても、一応2つのトランザクションに分割してアクセスする。

ただしアドレスは増えないので非常に怪しいデータが書かれる。ついでに、シングルリードで非アラインで複数リードをすると、2回目以降のバイトイネーブルが正しく動かないようだ。

シングルリード/ライトで連続データを扱うと、うまくすればFIFOなんかの揮発性ペリフェラルから一度にデータを転送できるが、はまるとドツボなので、デバッグの手段がさっと思いつかないレベルの人はやらない方が賢明ではある。