Xilinx ZynqFPGAでPLからPSのGPIOを制御する

#はじめに
　＊Xilinx Zynqについての説明はここでは割愛します。Xilinx社のホームページをご覧ください。

　PSからPL側に繋がっているポートの制御を行ったり、ポートの情報を読み取ったりしたいことはあると思います。これにはXilinxもEMIOというものを用意してくれており、Qiitaにも記事を書いてくださっている方もいらっしゃいますので、実現が可能です。
　一方、PLからPSのGPIO制御はマニュアルにも書かれていません。過去にXilinx ForumでPLからPSの制御をしたいという話題がありましたが、なかなか良い解決策が見つかりませんでした。
　実際に可能なのかどうかを実験しました。
#その１．PSに仲介させて制御する方法
　これはPLがEMIOを制御し、PSがタイマーや割り込みでEMIOを見てその結果をGPIO(MIO)に反映させる、という方法です。
　メリットはEMIOを使えばいいのでPL側のリソースは少なくて済むということです。
　デメリットはPSを介してしまうのでタイマーで見ているなら最大タイマーの遅延分、割り込みであっても割り込みオーバーヘッド分の制御遅延が生じます。また割り込みを使用するとPSの処理負荷が上がりますし、優先順位の設計も必要になります。
　msecオーダーのタイミングの制御で十分ならばこちらの方が楽であり現実的だと思います。
![PL_EMIO_Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/4d2931b5-f47d-792b-6dd5-e401ecb18ad4.png)

 図の例ではBlock DesignでPLからの信号をemio_gpio_iに入れてPLの制御した信号をPSで読み取り、次のプログラムでPS配下のGPIOに反映するようにしています。

```
int pinStatus = XGpioPs_ReadPin( PLが制御しているEMIOの番号 );
XGpioPs_WritePin(制御したいPS MIO番号, pinStatus);
```

#その２．PLが直接PSのGPIOレジスタを制御する
　PLがAXIバスを使ってPSにあるGPIOレジスタを叩く方法です。
　メリットはPSを介さずにPLがポートを制御できることです。そのポートをPSが一切触らないのならばコヒーレンシの考慮も不要です。PS側はポートの初期設定のみで済みます。
　デメリットはPL側に制御回路が必要ということです。これがどのくらいになるのかを今回検証します。

　Zynq PSのGPIOレジスタには通常のDATAレジスタの他にMASK DATAレジスタというものが用意されています（MASK_DATA_n_LSW, MASK_DATA_n_MSW, n=0...5）。このレジスタを使うと特定のビット（ポート）のみに作用させることができリードモディファイライトする必要がありませんので、PLからの制御はこのレジスタを使用します。

　今回はS_AXI_LPDを使用しました。レジスタアクセスですので他の経路でも可能と思います。
　ToolsメニューのCreate and Package New IP...で新しいAXI4 peripheralを作成します。
![CreateAndIP1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/4066497f-1484-aea3-7ecd-5b7762e0ca8d.png)
![CreateAndIP2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/8a119b78-a85e-7829-045c-f6d5459317c9.png)


　PLがMasterとなります。Interface TypeはLiteで十分です。
![CreateAndIP4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/3eeb7443-53f7-66db-3a22-f46c69a9d40f.png)

　作成したIPをBlock Design画面で右クリックし、生成します。Run Connection Automationというグリーンのバーが表示されていますのでクリックすると自動でAXIにいい感じに接続してくれます。もちろん手動で任意のAXIへ接続することも可能です。
![CreateAndIP6_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/3887ba6c-745e-2c42-0b93-395e31fd444d.png)



　今回は1ポートのみを制御しますので、PLからのポートと接続します。
![CreateAndIP7_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/538dfd6a-6bad-cccb-9add-43e5aa19d465.png)

　またAXIで制御するAddress Mapを表示してオーバーラップが無いかどうかを確認します。複数のAXIを生成している場合はアドレスが重なっている場合があります。
![CreateAndIP8_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/252250/f0e8a1e9-3337-2f0c-a87c-67171e70927b.png)



　次に、GPIOレジスタアクセスのためIPを修正します。IPを右クリックしてEdit in IP Packagerを選択します。そうするとIPのソースコードを変更することができます。
　レジスタアクセスだけのIPとして動作させますので、元プログラムの以下の定義を修正します。

```
C_M_START_DATA_VALUE	: std_logic_vector	:= x"AA000000";
C_M_TARGET_SLAVE_BASE_ADDR	: std_logic_vector	:= x"FF000000";
```

また以下の出力を

```

M_AXI_AWADDR	<= std_logic_vector (unsigned(C_M_TARGET_SLAVE_BASE_ADDR) + unsigned(axi_awaddr) + unsigned(addrOfs));
axi_wdata <= regData;
init_txn_ff <= init_axi_txn_int;
```
として自分の所望のアドレス、データを送るようにモディファイします。
init_txn_ffは初期は外部入力になっていますが、内部生成するように変更しました。
この修正のためにいくつかsignal定義を追加します。

```
    signal init_axi_txn_int : std_logic;
    signal FromPL_FF : std_logic;
    signal addrOfs : std_logic_vector(31 downto 0);
    signal regData : std_logic_vector(31 downto 0);
```
　
　そして、以下のプログラムを追加し、PLで制御したい信号をAXIを経由してPS GPIOと接続します。プログラム中に、
-- Add user logic here
と書いている部分がありますので、私は折角ですのでそこに記述することにしました。

```
process(M_AXI_ACLK)                                                                               
  begin                                                                                             
    if (rising_edge (M_AXI_ACLK)) then                                                              
      if (M_AXI_ARESETN = '0') then                                                                
          init_axi_txn_int <= '0';
          FromPL_FF <= '0';
          addrOfs <= (others=>'0');
          regData <= (others=>'1');
      else                                                                                          
          FromPL_FF <= FromPL;
          if( FromPL_FF /= FromPL ) then
              addrOfs <= X"000A000C";
              regData <= X"FFF7000"&FromPL&"000";
              init_axi_txn_int <= '1';
          else
              init_axi_txn_int <= '0';
          end if;
      end if;                                                                                       
    end if;                                                                                         
end process;                                                                                      
```

　このようにすればPSはGPIOの初期設定のみであとはPLが直接PS GPIOレジスタを叩くことができるようになります。
#結果と考察
　AXIですのでPLが直接制御といっても遅延が生じます。私が実験した回路では100MHzで約20 Clockの遅延を生じていました。他のAXIが存在した場合Queueingされるため余裕をみて100 Clock掛かったとして1μsですので、マイクロ秒オーダーの制御で問題なければ使うことができます。
　またAXIの回路を追加しているため、Logicを消費します。設計する回路にも依ると思いますが、私が実験で作成した回路ではAXIを含めて1500LUTほど消費していました。かなりの容量を消費してしまいます。
　まとめますと、

・PLからPSのGPIO制御は2通りあり不可能ではない。
・ただし、PSの処理負荷もしくはPLの容量を無駄に消費するためやるべきではない。
・開発の中終盤で基板やチップを変更できず、処理負荷にまだ余裕はあるという場合は緊急避難として使用できなくはない。

　ということが解りました。

#参考文献
[Zynq Ultrascale+ MPSoC Register Reference](https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html)
[iwatake2222様の記事です：ZYBO (Zynq) 初心者ガイド (4) PLのAXI GPIOでPSからLチカ]
(https://qiita.com/iwatake2222/items/a3b935a4e4dbd41593e1)