本記事の概要
Xilinx社では、IPコアのインタフェースをAXI4プロトコルで共通化することによって、システム開発での生産性・互換性・柔軟性を高めています。
つまり、AXI4はXilinx社のIPを使う上での共通言語の役割を果たしています。
ただし、自作でRTLモジュールを作成した場合に、既存のIPと接続するためには、自作RTLモジュールにAXIプロトコルの入出力インターフェースを追加しなくてはいけません。
しかし、実際にゼロからIPにAXIプロトコルの入出力インターフェースを追加するのは大変です。
そこで、前回の記事から、Xilinx社が提供するVivadoのIPパッケージャーという機能を用いて、自作のカスタムIPにAXIプロトコルの入出力インターフェースを追加する方法について解説しています。
今回は、IPパッケージャーを使用して作成した、カスタムIPの内部構成を解説し、具体的にカスタマイズする方法を解説します。
次回の記事では、このカスタムIPを動かすドライバAPIの使用方法を解説し、Zynq上のプロセッサで動作確認を行います。
AXI4プロトコルの動作について学ぶには以下の書籍がおすすめです。入門者向けにAXI4プロトコルの動作は、「FPGAプログラミング大全」に非常にわかりやすく解説されています。
目標
本記事全体の目標は次の通りです:
自作のRTLモジュールにAXI4-Liteインタフェース(S側)を追加する
カスタムIPに実装するRTL回路は比較的簡単なLED点滅回路とします。
プロセッサが書き込んだ制御レジスタの値によって、LEDの点滅位置を変更します。
カスタムIPの変更方法
カスタムIPの内部構成の確認
前回の記事で作成したカスタムIPの内部を見ていきましょう。
作成したIPのフォルダ内部に雛形となるHDL記述のファイルが格納されています。
今回は使用言語にVerilogを指定したので、これらのソースコードはVerilogで記述されています。
この2つのファイルの関係は次の図のようになっています。
led_config_v1_0.vはラッパーファイルで、led_config_v1_0_S00_AXI.vに本質的な回路のHDL記述が用意されています。
内部のIP構成について理解するには、ある程度Verilogの文法を理解しておく必要があります。
Verilogの文法について、以下の書籍が網羅的に説明されています。
led_config_v1_0.v
まずは、ラッパーファイルの中身を見ておきましょう。
適宜、略していますので、ご注意ください
module led_config_v1_0 #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Parameters of Axi Slave Bus Interface S00_AXI
parameter integer C_S00_AXI_DATA_WIDTH = 32,
parameter integer C_S00_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
// User ports ends
// Do not modify the ports beyond this line
// -- 略 --
);
// Instantiation of Axi Bus Interface S00_AXI
led_config_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
) led_config_v1_0_S00_AXI_inst (
// -- 略 --
);
// Add user logic here
// User logic ends
endmodule
ところどころに、“Users to add parameters here”、“Users to add ports here”、”Add user logic here”というコメントアウトがあると思います。
このコメントアウトの直後に、HDL記述を追加していきます。
上記のコードを図解すると、次のような感じです。
ユーザー用のパラメータ、ポート、ロジックの3箇所それぞれにカスタムで記述することが可能です。
led_config_v1_0_S00_AXI.v
次に、本質的な回路部分が書かれている、led_config_v1_0_S00_AXI.vというファイルの中身を確認しましょう。
適宜、略していますので、ご注意ください
module led_config_v1_0_S00_AXI #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Width of S_AXI data bus
parameter integer C_S_AXI_DATA_WIDTH = 32,
// Width of S_AXI address bus
parameter integer C_S_AXI_ADDR_WIDTH = 4
)
(
// Users to add ports here
// User ports ends
// Do not modify the ports beyond this line
// -- 略 --
);
// AXI4-Lite用の自動生成されるロジック
// -- 略 --
// Add user logic here
// User logic ends
endmodule
こちらのソースコードにも、“Users to add parameters here”、“Users to add ports here”、”Add user logic here”というコメントアウトがあると思います。同様に、コメントアウトの直後に、HDL記述を追加していきます。
上記のコードを図解すると、次のような感じです。
自動生成ロジックの部分で、AXI4-Liteのプロトコルを満足するように、ロジックが構成されます。
ユーザー用のパラメータ、ポート、ロジックの3箇所それぞれにカスタムで記述することが可能です。
また、AXI4-Lite用に自動生成されたロジックの部分も、内容が理解できれば、適宜修正することが可能です。
自動生成されたロジックについて
自動生成されたロジックについて、図解すると下の図のようになります。
詳細はコード自体をご覧いただいて、自分で理解するのがよいと思いますが、その一助として下の図を活用いただけるとよいかと思います。
自動生成されているロジック内部の動作の大まかな流れを確認しましょう。
自動生成されたロジック内では4つのレジスタslv_regで構成されるレジスタ空間が生成されます。
先程、IPパッケージャーで[Number of Registers]を4のままにしておきましたが、この値によって生成されるレジスタ空間のサイズを変えることが可能です。
レジスタslv_regにデータを読み書きするために、AXI4-Liteには、5種類のチャネルが設けられています。
書き込むときのイメージ
S側IPのレジスタslv_regにデータを書き込むときには、5つのチャネルのうち、AWチャネル、Wチャネル、Bチャネルの3つを使います。
M側IPが操作・制御するときのイメージは下の図のようになります。
M側のIPが、AWチャネルに書き込むレジスタのアドレス、Wチャネルに書き込むデータを乗せて、S側のIPに送信します。
S側のIPが無事にアドレスとデータを受信したら、Bチャネルに無事に受信したかどうかを示す成否判定信号を乗せて、M側のIPに送信します。
読み出すときのイメージ
次に、M側IPがS側IPのレジスタのデータを読み出したいときには、5つのチャネルのうち、ARチャネル、Rチャネルの2つを使います。
先程同様に、読み出し操作のイメージはこのような感じです。
M側のIPが、ARチャネルに読み出したいレジスタのアドレスを乗せて、S側のIPに送信します。
S側のIPはアドレスを受信したら、Rチャネルに読みだしたデータを乗せて、M側のIPに送信します。
どこをカスタマイズするとよいか?
LED出力回路の構成
今回のカスタマイズでは、レジスタslv_regを参照し、その値に応じてLEDの点灯位置を修正していきます。
そこで、レジスタslv_regに値を書き込む自動生成されたロジックはそのまま用いて、別のユーザーロジックを追加したいと思います。
最終的に作りたいIPのイメージを共有しておきます。
図のように、レジスタslv_reg1の値をそのまま外部に出力して、LEDの入力にするような非常に簡単な回路を作ってみましょう。
led_config_v1_0_S00_AXI.v
まず、led_config_v1_0_S00_AXI.vを変更しましょう。
ソースコードでは、「①出力側のユーザーポートを追加する」「②ユーザーロジックで、ユーザーポートとレジスタslv_reg1とを接続する」の2つを行います。
まず、①のLEDと接続するための出力ポート(4bit)を追加します。以下のようなコードを記述します。
// Users to add ports here
output wire [3:0] led_op,
// User ports ends
次に、②のロジックについてです。
非常に簡単ですが、slv_reg0の下位4bitのレジスタをLEDに出力します。
slv_reg0の下位4bitのレジスタ[3:0]を、assignを使って出力ポートled_opと配線しました。
// Add user logic here
assign led_op = slv_reg0[3:0];
// User logic ends
led_config_v1_0.v
次に、ラッパーファイルを変更しましょう。
ソースコードでは、「①出力側のユーザーポートを追加する」「②参照しているインスタンスにユーザーポートを追加する」の2つを行います。
①のLEDと接続するための出力ポート(4bit)を追加します。以下のようなコードを記述します。
// Users to add ports here
output wire [3:0] led_op,
// User ports ends
次に、②の参照しているインスタンスled_config_v1_0_S00_AXI.vにユーザーポートを追加します。
非常に簡単ですが、slv_reg0の下位4bitのレジスタをLEDに出力します。
一番はじめに.led_opというユーザーポート同士をつなぐための記述を追加しています。
led_config_v1_0_S00_AXI # (
.C_S_AXI_DATA_WIDTH(C_S00_AXI_DATA_WIDTH),
.C_S_AXI_ADDR_WIDTH(C_S00_AXI_ADDR_WIDTH)
) led_config_v1_0_S00_AXI_inst (
.led_op (led_op),
/*略*/
);
カスタムIPのパッケージング
では、変更したカスタムIPをパッケージングしていきましょう。
前回の記事で、カスタムIPを作成すると、下の図のような新しいVivadoのウィンドウが立ち上がっていると思います。
Verilogファイル(led_config_v1_0.vとled_config_v1_0_S00_AXI.v)を修正すると、[Package IP]というタブの、いくつかのアイコンがチェックマークから図のようなマークに変わります。
この変更されたマークが付いている項目を一つクリックし、画面上の[インフォメーション]のアイコンをクリックすると、自動的にIPが修正されます。
一番下の[Review and Package]以外のアイコンがすべてチェックマークに戻ったら、[Review and Package]を選択し、[Re-Package IP]をクリックします。
これにより、修正後のカスタムIPがパッケージングされます。
パッケージングが完了すると、Vivadoプロジェクトを閉じるかどうか選択できます。
ここでは、[Yes]を選択しておきました。
Vivadoプロジェクトをそのまま立ち上げたままにしておきたい場合は、[No]を選択します。
以上で、カスタムIPのカスタマイズが完了しました。
次回は、いよいよ自作のIPを動かしていきたいと思います!
最後までご覧いただきありがとうございました。
コメント