SPI Slave to Avalon Master Bridge Coresを試す

#はじめに
Intel製FPGAを活用していくと、レジスタ制御などのソフト制御を行いたい場面が多々あります。簡単に思いつくのは内部にNiosIIを組込んで実現させる方法ですが、内部メモリやLEのリソースが割けなかったりして安易に実装できない時にはHDL上で工夫してなんとか凌いでいました。
そんな時にSPI Slave to Avalon Master Bridge Coreなるものの存在を知り、試してみることにしました。

###概要
簡単に云うと、NiosIIを使わずともSPI Slaveのコアを使用してAvalon-MMバスへのアクセスが可能になるバスブリッジです。これによりPlatform Designer(旧Qsys)で作成したIP群へのアクセスが外部で用意したCPUのSPI Masterから容易に行えるようになり、FPGAのリソースの消費を抑えることができます。

###試したもの
[SPI Slave to Avalon Master Bridge Design Example][link-1]というサンプルファイルがIntelのHPに用意されています。
今回はこちらを試しました。
但し、用意されているプロジェクトがCycloneV SoC用とCycloneIII用のみとなっています。
CycloneIIIはQuartus Primeでは既にサポート外ですし、CycloneV SoCもちょっと手出ししにくいです。今回は手元にあった安価なMAX10 Evalution KitにCycloneIII版を移植することにしました。
[link-1]:https://www.intel.co.jp/content/www/jp/ja/programmable/support/support-resources/design-examples/intellectual-property/embedded/nios-ii/exm-spi-bridge.html

#ハード編
サンプルのプロジェクトはVer9.1で作成されています。
この頃はPlatform Designerの前身のQsysの更に前身のSOPC Builder版となっており拡張子が.sopcで最新のPlatform Designerでは開くことができません。・・・:sob:
CycloneIII対応Quartusの最終版であるVer13.1でプロジェクトファイルを一旦アップデートする必要があります。旧QuartusはIntelのHPからダウンロードできますので、ダウンロードしてインストールします。
Ver13.1では既にQsysとなっていますが、`.sopc`ファイルも開くことができます。

+ `cpu_spi_core.sopc`
+ `spi_bridge_system.sopc`

をそれぞれ.qsysファイルとして再ジェネレートします。設定は一切変更しません。
作成したQsysファイルはプロジェクトに登録されていないので、

+ \cpu_spi_core\synthesis\cpu_spi_core.qip
+ \spi_bridge_system\synthesis\spi_bridge_system.qip

をSettings → Files にAddします。
一度フルコンパイルして、終了させます。

Quartus Ver13.1ではMAX10に対応していないため、今度は最新Quartus Prime(今回はv18.1)で開きます。CycloneIIIはサポート外だとメッセージが出ますので、MAX10を選択してOKをクリックします。
左上Project NavigatorにLaunch IP Upgrade Toolのボタンが出ますので、これをクリックして、Perform Automatic Upgradeを実行します。
ALT_PLLのUpgradeは失敗します。これは諦めてIP Catalogからマニュアルで再生成します。
IP Catalog → PLL → ALTPLL をAddします。
ファイル名は既存と同じpll.vとします。設定も既存のままinclk0=50MHz,c0=50MHzの設定と
して、ウィザード最後の生成するファイルリスト一覧のpll.bsfにチェックを入れてFinishします。
pll.qipを追加するか聞いてきますので、OKをクリックします。


Upgradeされたcpu_spi_core.qsysを開き、

+ cpu ： NiosII/e にする。
+ onchip_mem_for_cpu ： Sizeを 32768 にする。 

再Generateします。
その後、

+ Device → 「10M08SAE144C8GES」にする。 
+ Device and Pin Options → Configuration → Configuration mode を
「Single Compressed Image with Memory Initialization」に設定する。
+ Pinアサインを適宜修正する。

全て実施した後、フルコンパイルし完成です。

#ソフト編
EDSのプロジェクトも作り直した方がよいと思います。
BSPは最新の`cpu_spi_core.sopcinfo`から生成し、appの方は、
`\software_examples\app\spi_bridge_test`
に一式入っておりますので、ソース及びライブラリ群をImportします。
通常の手順でコンパイルを実施しますが、
> undefined reference to `spi_command'	

というエラーが出ます。
`spi_command.c`の`inline`関数指定が原因であるようなのですが、これを解決する方法は私の知識では見つけられませんでした。
とりあえず、「inline」部を削除したらコンパイルが通りましたので、今回はこれでお茶を濁しました。

#実行
`Run As NiosII Hardware`を実行すると、HPの`readme.txt`にある通り、cpu_spi_coreのSPI Masterから、spi_bridge_system上にあるOn-Chip Memoryに対してメモリチェックを行い、ターミナルウィンドウに
>Creating random test data ... 
Writing data to onchip memory ...
Write transaction successful 
Reading data from onchip memory ...
Comparing data ...
Compare data completes error free

と表示がされ、エラーなしで終了します。

# 解析
 機能はなんとなく分かりましたがコレでは、外部CPUで制御する場合の仕様がさっぱ分かりません。ソースを解析すればよいのかもしれませんが、手っ取り早くSPIの信号をキャプチャーし、実際に何が行われているか確認することにします。

### キャプチャー
SPIにて１バイト書き込み、１バイト読み取りの信号です。

![SPI_Bridge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/450358/20281945-93a5-8734-0ae4-c78e95629568.png)

[Embedded Peripherals IP User Guide][link-2]の`SPI Slave/JTAG to Avalon Master Bridge Cores`の項目を参考に解析すると、下記のコマンド体型であることがわかりました。

（１）～（７）はマスター側のライトパケットデータです。
（１）[7A] - SOP(Start of Packet) 固定値
（２）[7C 00] - Ch 0 を示す
（３）[04 00] - Transaction Code    04=Write,incrementting address
（４）[00 01] - Byte Size  1byte
（５）[00 00 10 00] - Address
（６）[7B] - EOP(End of Packet) 固定値
（７）[AA] - 書込みData 例では1byte書込みの為、データは１コ

_ココで注意が必要なのは、EOPの0x7Bがパケット終了の直後ではなく、１バイト手前に出現するところです。_
また、スレーブ側アイドル中は（a）のようにIdleを示す、0x4Aを返します。
スレーブデバイスの生死が確認できます。

**このフォーマットだと、既にリザーブされた0x4A(Idle)や0x7A(SOP)がデータとして送信できません。その場合下記の様に送る必要があります。**
[02 4B 7A 40]を送る場合
[02 4B 7D 5A 40]と変換させて送信します。
0x7DがEscapeコードと認識し、その後に続く0x5Aを0x20とXORした値が採用される仕組みです。
0x5A xor 0x20 = 0x7A
となります。

[4A]を送る場合は[7D 6A]、[7D]なら[7D 5D]となりますね。ややこしい。

（８）～はライトコマンドに対するレスポンスパケットになります。
（８）[7C 00] - Ch 0 を示す
（９）[7A] - SOP
（10）[84 00] - Transaction CodeのMSBを反転させたコードを返します。
（11）[00 01] - Byte Size  1バイトを示します。
（12）[7B] - EOP  ここでもパケット終了の１バイト手前で出現します。

（13）以降はリードパケットとなります。
マスター側リードパケット送信
[7A] - SOP
[7C 00] - Ch 0
[14 00] - Transaction Code  Read,incrementing address
[00 01] - Byte Size
[00 00 10 7B 00] - "00 00 10 00"がAddress、0x7BがEOP

（14）リードコマンドに対するレスポンスパケット
[7C 00] - Ch 0
[7A] - SOP
[7B] - EOP (パケット終了１バイト前に出現)
[AA] - Read Data 

ここでも注意が必要なのは、ライトデータの時の様に0x7Dを受信した場合は、次のバイトと0x20でXORした値に変換させなければならない点です。

[link-2]:https://www.intel.co.jp/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_embedded_ip.pdf

#最後に
FPGAの制御やデバッグ時にラズパイ経由Pythonでサクサクと利用できたら便利かなと思っています。
同様にI2Cブリッジもあるようですが、I2Cは速度面や引き回しで不利なのであまりメリットないようにも思えます。