ZYNQ搭載高速ADCボード「Cosmo-Z」のユーザ回路追加(後編)

ZYNQ搭載高速ADCボード「Cosmo-Z」にユーザ回路を追加する方法の続きです。
[前編はこちら](https://qiita.com/nahitafu/items/cd2aa24d2097ac4b9877)
# フィルタの追加
FilterブロックにはADCで計測された生データが流れているので、ここに適当なフィルタを追加します。
適当なフィルタということでCICフィルタを選びました。
## CICフィルタとは
CICフィルタというのは足し算とデシメーションだけでできている単純なローパスフィルタで、特性は急峻ではありませんが、構造は簡単です。
詳しいことは
http://nahitafu.cocolog-nifty.com/nahitafu/2015/08/cosmo-z18bit-ec.html
の記事をご覧ください。
## SLICEモジュールの改造
VivadoでIPコアとして使えるCICフィルタは、AXI Streamを前提としていて、生のデータは入れられないようです。

そこで、前回作ったSLICEモジュールを改造して、8chのデータが詰まった96bitのAXI Streamを、16bitのAXI Stream 8chに分解するようにしました。
元のADCデータは12bit×8chなのですが、これを12bitのAXI Stream 8本に分けたところ、VivadoがWarningを出してきました。
どうやら、AXI Streamはビット幅が8の倍数でないと正しく動作しないかもしれないそうなのです。
CICフィルタに入れるデータは2の補数表現であろうから、リニア→2の補数表現に変換しなければなりません。

せっかくなので、CH1をCH1,2,3,4にコピーして、CH2をCH5,6,7,8にコピーする機能もSliceモジュールに持たせましょう。

```vhdl
architecture Behavioral of cosmoz_data_slice is
  ATTRIBUTE X_INTERFACE_INFO : STRING;
  ATTRIBUTE X_INTERFACE_INFO of tdata_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata0_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT0 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid0_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT0 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata1_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT1 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid1_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT1 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata2_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT2 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid2_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT2 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata3_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT3 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid3_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT3 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata4_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT4 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid4_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT4 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata5_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT5 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid5_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT5 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata6_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT6 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid6_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT6 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata7_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT7 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid7_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT7 TVALID";
begin
    tvalid0_o  <= tvalid_i;
    tvalid1_o  <= tvalid_i;
    tvalid2_o  <= tvalid_i;
    tvalid3_o  <= tvalid_i;
    tvalid4_o  <= tvalid_i;
    tvalid5_o  <= tvalid_i;
    tvalid6_o  <= tvalid_i;
    tvalid7_o  <= tvalid_i;
    tdata0_o   <= (tdata_i(ADC_BITS * (0 + 1) - 1 downto ADC_BITS * 0) - x"800") & "0000";
    tdata1_o   <= (tdata_i(ADC_BITS * (0 + 1) - 1 downto ADC_BITS * 0) - x"800") & "0000";
    tdata2_o   <= (tdata_i(ADC_BITS * (0 + 1) - 1 downto ADC_BITS * 0) - x"800") & "0000";
    tdata3_o   <= (tdata_i(ADC_BITS * (0 + 1) - 1 downto ADC_BITS * 0) - x"800") & "0000";
    tdata4_o   <= (tdata_i(ADC_BITS * (1 + 1) - 1 downto ADC_BITS * 1) - x"800") & "0000";
    tdata5_o   <= (tdata_i(ADC_BITS * (1 + 1) - 1 downto ADC_BITS * 1) - x"800") & "0000";
    tdata6_o   <= (tdata_i(ADC_BITS * (1 + 1) - 1 downto ADC_BITS * 1) - x"800") & "0000";
    tdata7_o   <= (tdata_i(ADC_BITS * (1 + 1) - 1 downto ADC_BITS * 1) - x"800") & "0000";
```
こんなふうになりました。

## Combineモジュールの改良
Combineモジュールでは逆にこれらのデータを束ねます。

```vhdl
  ATTRIBUTE X_INTERFACE_INFO : STRING;
  ATTRIBUTE X_INTERFACE_INFO of tdata_o  : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid_o : SIGNAL is "xilinx.com:interface:axis:1.0 DOUT TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata0_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN0 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid0_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN0 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata1_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN1 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid1_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN1 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata2_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN2 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid2_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN2 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata3_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN3 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid3_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN3 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata4_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN4 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid4_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN4 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata5_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN5 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid5_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN5 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata6_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN6 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid6_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN6 TVALID";
  ATTRIBUTE X_INTERFACE_INFO of tdata7_i  : SIGNAL is "xilinx.com:interface:axis:1.0 DIN7 TDATA";
  ATTRIBUTE X_INTERFACE_INFO of tvalid7_i : SIGNAL is "xilinx.com:interface:axis:1.0 DIN7 TVALID";
begin
    tvalid_o   <= tvalid0_i;
    tdata_o(ADC_BITS * (0 + 1) - 1 downto ADC_BITS * 0)   <= tdata0_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (1 + 1) - 1 downto ADC_BITS * 1)   <= tdata1_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (2 + 1) - 1 downto ADC_BITS * 2)   <= tdata2_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (3 + 1) - 1 downto ADC_BITS * 3)   <= tdata3_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (4 + 1) - 1 downto ADC_BITS * 4)   <= tdata4_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (5 + 1) - 1 downto ADC_BITS * 5)   <= tdata5_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (6 + 1) - 1 downto ADC_BITS * 6)   <= tdata6_i(15 downto 4) - x"800";
    tdata_o(ADC_BITS * (7 + 1) - 1 downto ADC_BITS * 7)   <= tdata7_i(15 downto 4) - x"800";
```

## Block Designを配線する
SliceモジュールとCombineモジュールをRTLモジュールとして作り、間にCICフィルタを挟みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/11821c5f-45e7-5c3d-6080-87370e26906b.png)

ADCに入ってくる信号はCH1(正弦波)とCH2(矩形波：トリガ信号)で、それを以下のような方針で8chに振り分けます。

- ADC CH1→そのまま→CH1
- ADC CH1→CICフィルタ(め)→CH2
- ADC CH1→CICフィルタ(ふつう)→CH3
- ADC CH1→CICフィルタ(きつめ)→CH4
- ADC CH2→そのまま→CH5
- ADC CH2→CICフィルタ(ゆるめ)→CH6
- ADC CH2→CICフィルタ(ふつう)→CH7
- ADC CH2→CICフィルタ(きつめ)→CH8

VivadoのIPのCICフィルタの設定は以下のとおりです。
### ゆるめ フィルタ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/ab8d4acf-a592-d4bf-ee0b-59ecd8d850de.png)
### ふつう フィルタ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/0361b1f3-d657-0b28-f827-e32b7745a4f4.png)
### きつめ フィルタ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/5609b886-07c6-0b3e-c269-aa4384ac9771.png)
### 入出力ビット幅
フィルタの設定がきつくなると出力バスのビット幅も増えてきます。
同じ条件で評価したいので、いずれのフィルタも、入出力データは16bit幅でTruncationの設定にしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/be7d9fbb-3bb7-2840-99bd-ee263ad03181.png)

### ちょっと失敗したこと
SliceとCombineをRTLモジュールにしたことが失敗でした。
なぜなら、RTLモジュールでAXI Streamを作ると、Varidateをしたときにバスの速度が自動的に推定されないようで、全部手作業で設定しなければいけないようでした。ポートをクリックして、Block Interface PropertyをつっついてCONFIGの中のFREQ_HZを全部設定しなおさなければなりません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/83693c6d-34b7-65ff-cabc-29712f97db01.png)
IPとして作れば、おそらく自動で設定されます。
RTLモジュールはIPコアと違い、ツールがラッパを作ってアトリビュートを設定することができないのでしょう。

# 論理合成して実行
これを論理合成して、計測を行ってみました。
結果は次の図の波形のとおり。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/5007163a-8245-2c7e-492e-838aef7b8291.png)
フィルタがきつくなるにつれ、遅延が増えていくのがわかります。

時間軸的に拡大してみると、フィルタを通したほうはすべてカクカクしています。
これはCICフィルタがデシメーションをしているためで、4回に1回しかデータを出力しないからです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/d289ece7-e5b3-1525-5a8a-409bb3c458a7.png)

灰色の線(CH8)は矩形波の反射による鋭いヒゲを除去できているのでLPFとしての効果はありそうです。
## CICフィルタの周波数特性とノイズ除去性能
LPFなので、どのくらいノイズが除去できるか気になるところです。

今回使っている正弦波の発振器は、安物なので、波形が歪んでいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/1f2bfee9-f497-bf53-8ee9-2f80579debd0.png)

そのため、FFTをすると結構な高調波が乗ってしまうのですが・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/78ec6261-e64f-8f6d-93ee-a666699053b5.png)

CICフィルタを通すと、
(ゆるめ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/11fc102d-b27e-b5b5-d65b-acf3ebd5210c.png)

(ふつう)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/309d75a4-2b8e-2e7c-f3f0-d4ca9b5f3468.png)

(きつめ)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/4d5e51f3-b0f4-aab1-2434-01f05652b1f3.png)

全体的にノイズは減っているのでしょうが、CICフィルタ自体が緩すぎて、あまり効果は感じられません。
サンプリング周波数の1/8付近(10MHz付近)のノイズは落ちていますが、20MHz付近ではかえってノイズが増えているように見えます。

## 矩形波もフィルタした結果のFFT
トリガの矩形波もCICフィルタに掛けているので、そのFFTでスペクトラムを見てみます。
矩形波のスペクトラムはくし型になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/776ca66e-1567-d0c2-b22b-eaf996ddc95a.png)

まずは「ゆるめ」のフィルタ。fs/4の20MHzで急峻に切れています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/38bffb73-4eca-2666-5921-6a94be5804ae.png)

次は「ふつう」のフィルタ。10MHz付近の減衰が少しだけ大きくなっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/6a4d3f67-1516-8611-4c6c-94793cbd0d8c.png)

最後は「きつめ」のフィルタ。
10MHzと30MHz付近で大きくカットできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542455/08dd968b-7e84-a7f7-95cb-995dd1950748.png)

元が幅が広がったスペクトラムのほうがフィルタの特性は把握しやすいですね。
きっと、10MHz、20MHz、30MHz、40MHzのすべての部分で急峻に切れているのでしょう。

# まとめ
いかがでしたでしょうか。
Cosmo-ZのFilterブロックでAXI Streamをほどいて再結合させ、その間にリアルタイムな信号処理を追加することができました。