« 「Cosmo-Z」にユーザ回路を追加する方法(前編) | トップページ | 内層クリアランス »

2020.01.06

「Cosmo-Z」にユーザ回路を追加する方法(後編)

ZYNQ搭載高速ADCボード「Cosmo-Z」にユーザ回路を追加する方法の続きです。

フィルタの追加

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モジュールに持たせましょう。


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モジュールでは逆にこれらのデータを束ねます。


  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フィルタを挟みます。

1_20200109041201

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フィルタの設定は以下のとおりです。

ゆるめ フィルタ

2_20200109041201

ふつう フィルタ

3_20200109041301

きつめ フィルタ

4_20200109041301

入出力ビット幅

フィルタの設定がきつくなると力バスのビット幅も増えてきます。

同じ条件で評価したいので、いずれのフィルタも、入出力データは16bit幅でTruncationの設定にしています。

5_20200109041301

ちょっと失敗したこと

SliceとCombineをRTLモジュールにしたことが失敗でした。

なぜなら、RTLモジュールでAXI Streamを作ると、Varidateをしたときにバスの速度が自動的に推定されないようで、全部手作業で設定しなければいけないようでした。ポートをクリックして、Block Interface PropertyをつっついてCONFIGの中のFREQ_HZを全部設定しなおさなければなりません。

IPとして作れば、おそらく自動で設定されます。

RTLモジュールはIPコアと違い、ツールがラッパを作ってアトリビュートを設定することができないのでしょう。

論理合成して実行

これを論理合成して、計測を行ってみました。

結果は次の図の波形のとおり。

7_20200109041301

フィルタがきつくなるにつれ、遅延が増えていくのがわかります。

時間軸的に拡大してみると、フィルタを通したほうはすべてカクカクしています。

これはCICフィルタがデシメーションをしているためで、4回に1回しかデータを出力しないからです。

8_20200109041301

灰色の線(CH8)は矩形波の反射による鋭いヒゲを除去できているのでLPFとしての効果はありそうです。

CICフィルタの周波数特性とノイズ除去性能

LPFなので、どのくらいノイズが除去できるか気になるところです。

今回使っている正弦波の発振器は、安物なので、波形が歪んでいます。

9_20200109041301

そのため、FFTをすると結構な高調波が乗ってしまうのですが・・・

10_20200109041301

CICフィルタを通すと、

(ゆるめ)

11_20200109041301

(ふつう)

12_20200109041301

(きつめ)

13_20200109041301

全体的にノイズは減っているのでしょうが、CICフィルタ自体が緩すぎて、あまり効果は感じられません。

サンプリング周波数の1/8付近(10MHz付近)のノイズは落ちていますが、20MHz付近ではかえってノイズが増えているように見えます。

矩形波もフィルタした結果のFFT

トリガの矩形波もCICフィルタに掛けているので、そのFFTでスペクトラムを見てみます。

矩形波のスペクトラムはくし型になります。

14_20200109041301

まずは「ゆるめ」のフィルタ。fs/4の20MHzで急峻に切れています。

15_20200109041401

次は「ふつう」のフィルタ。10MHz付近の減衰が少しだけ大きくなっています。

16_20200109041401

最後は「きつめ」のフィルタ。

10MHzと30MHz付近で大きくカットできました。

17_20200109041401

元が幅が広がったスペクトラムのほうがフィルタの特性は把握しやすいですね。

きっと、10MHz、20MHz、30MHz、40MHzのすべての部分で急峻に切れているのでしょう。

まとめ

いかがでしたでしょうか。

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

 

|

« 「Cosmo-Z」にユーザ回路を追加する方法(前編) | トップページ | 内層クリアランス »

コメント

SLICEモジュールのクロックが推論されない件ですが、内部で使っていないダミーでいいのでクロック入力のポートを追加してクロックを追加しておくと、SLICEのAXI Stream SlaveとMasterがそのクロックのドメインであると認識するので、手動でFREQ_HZ設定しなくても良くなると思います。

このあたり、RTLモジュールでも、IP PackagerでIPにした場合でも同じですので、クロック入力要らなくても付けとくと便利です。

以前、AXI Stream経由でRGBのピクセル・データを入力して、YUVで出力するモジュールを作ったときも、クロック要らないのですが、クロックドメインを指定するためにダミーのクロックを付けたりしてました。

投稿: Kenta IDA (@ciniml) | 2020.01.09 05:18

なるほど!
クロックがないからクロック推論できないわけですね。
ありがとうございます。

投稿: なひたふ | 2020.01.09 18:00

コメントを書く



(ウェブ上には掲載しません)




« 「Cosmo-Z」にユーザ回路を追加する方法(前編) | トップページ | 内層クリアランス »