本記事の概要
Xilinx社では、IPコアのインタフェースをAXI4プロトコルで共通化することによって、システム開発での生産性・互換性・柔軟性を高めています。
つまり、AXI4はXilinx社のIPを使う上での共通言語の役割を果たしています。
ただし、自作で作成したRTLモジュールを既存のIPと接続するためには、自作RTLモジュールにAXIプロトコルの入出力インターフェースを追加しなくてはいけません。
しかし、実際にゼロからIPにAXIプロトコルの入出力インターフェースを追加するのは大変です。
そこで、前回の記事から、Xilinx社が提供するVivadoのIPパッケージャーという機能を用いて、自作のカスタムIPにAXIプロトコルの入出力インターフェースを追加する方法について解説しています。
前回の記事では、S側のAXI4-Liteインタフェースを追加しました。
今回の記事から、M側、すなわち制御する側のAXI4-LiteインタフェースをカスタムIPに追加していきたいと思います。
本記事では、M側のAXI4-Liteインタフェースを追加したカスタムIPのロジック構成について、まず理解しようと思います。
次回以降の記事では、ロジックをカスタマイズしていき、最終的にはProgrammable Logic (PL)のみでHello Worldを出力させてみようと思います。
AXI4プロトコルの動作について学ぶには以下の書籍がおすすめです。入門者向けにAXI4プロトコルの動作は、「FPGAプログラミング大全」に非常にわかりやすく解説されています。
目標
本記事全体の目標は次の通りです:
自作のRTLモジュールにAXI4-Liteインタフェース(M側)を追加する
今回は、カスタムIPからAXI4-Liteを通じて、XilinxのIP”AXI UART Lite”のデータレジスタに値を書き込み、文字列“Hello world”を外部PCに出力させてみましょう。
カスタムIPの作成
IPパッケージャーを使ったカスタムIPの作成方法は、以前の記事と基本的に同じです。
Add Interfacesのウィンドウで、[Interface Mode]にMasterを選択します。
Data Widthはそのままの値を指定しました。
また、カスタムIPの名前を、今回の記事では[UART_OUT]としています。
作成されたカスタムIPのブロックデザインはこのようになります。
入力にクロックとリセット信号に加えて、“m00_axi_init_axi_txn”というポートが追加されています。
そして、出力にはM側のAXI-Liteインタフェースの他に、“m00_axi_error”と”m00_axi_txn_done“というポートが追加されています。
次の節で説明する内部構成を見るとわかるのですが、”m00_axi_init_axi_txn”はM側のAXIトランザクションを開始するトリガーを入力するためのポートで、”m00_axi_txn_done”はトランザクションの完了を伝えるためのポートです。
カスタムIPに用意されているロジック回路には、S側に書き込んだ信号と、書き込みが終わったあとに読みだした信号とを比較し、正しく書き込みがなされたかを確認するためのロジック回路が用意されています。
“m00_axi_error”は正しく書き込みがなされていればLow状態、なされていない場合はHigh状態になります。
それ以外にも、AXIトランザクションが不成立の場合もHigh状態になります。
カスタムIPの内部構成
前回の記事で作成したカスタムIPの内部を見ていきましょう。
作成したIPのフォルダ内部に雛形となるHDL記述のファイルが格納されています。
今回も使用言語にVerilogを指定したので、これらのソースコードはVerilogで記述されています。
この2つのファイルの関係は次の図のようになっています。
前回の記事同様に、UART_OUT_v1_0.vはラッパーファイルで、UART_OUT_v1_0_M00_AXI.vに本質的な回路のHDL記述が用意されています。
内部のIP構成について理解するには、ある程度Verilogの文法を理解しておく必要があります。
Verilogの文法について、以下の書籍が網羅的に説明されています。
UART_OUT_v1_0.v
まずは、ラッパーファイルの中身を見ておきましょう。
適宜、略していますので、ご注意ください
module UART_OUT_v1_0 #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// Parameters of Axi Master Bus Interface M00_AXI
parameter C_M00_AXI_START_DATA_VALUE = 32'hAA000000,
parameter C_M00_AXI_TARGET_SLAVE_BASE_ADDR = 32'h40000000,
parameter integer C_M00_AXI_ADDR_WIDTH = 32,
parameter integer C_M00_AXI_DATA_WIDTH = 32,
parameter integer C_M00_AXI_TRANSACTIONS_NUM = 4
)
(
// Users to add ports here
// User ports ends
// Do not modify the ports beyond this line
// -- 略 --
);
// Instantiation of Axi Bus Interface M00_AXI
UART_OUT_v1_0_M00_AXI # (
.C_M_START_DATA_VALUE(C_M00_AXI_START_DATA_VALUE),
.C_M_TARGET_SLAVE_BASE_ADDR(C_M00_AXI_TARGET_SLAVE_BASE_ADDR),
.C_M_AXI_ADDR_WIDTH(C_M00_AXI_ADDR_WIDTH),
.C_M_AXI_DATA_WIDTH(C_M00_AXI_DATA_WIDTH),
.C_M_TRANSACTIONS_NUM(C_M00_AXI_TRANSACTIONS_NUM)
) UART_OUT_v1_0_M00_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箇所それぞれにカスタムで記述することが可能です。
UART_OUT_v1_0_M00_AXI.v
次に、本質的な回路部分が書かれている、UART_OUT_v1_0_S00_AXI.vというファイルの中身を確認しましょう。
適宜、略していますので、ご注意ください
module UART_OUT_v1_0_M00_AXI #
(
// Users to add parameters here
// User parameters ends
// Do not modify the parameters beyond this line
// -- 略 --
)
(
// Users to add ports here
// User ports ends
// Do not modify the ports beyond this line
// -- 略 --
);
//------------------------------------------------------------
//AXI4-Lite用の自動生成されるロジック
// -- 略 --
//Write Address Channel
// -- 略 --
//Write Data Channel
// -- 略 --
//Write Response (B) Channel
// -- 略 --
//Read Address Channel
// -- 略 --
//Read Data (and Response) Channel
// -- 略 --
//------------------------------------------------------------
//--------------------------------
//User Logic
//--------------------------------
//Address/Data Stimulus
//Address/data pairs for this example. The read and write values should
//match.
//Modify these as desired for different address patterns.
// -- 略 --
//------------------------------------------------------------
// 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のトランザクションの例として、「S側への書き込みと読み出しを順に行い、それらが一致して正しく書き込みがなされているかを確認するためのロジック回路」が、User Logic部分に記述されています。
上記のコードを図解すると、次のような感じです。
AXI4-Lite用のトランザクションを行うために、AXI4-Liteのプロトコルを満足するようなロジックが構成されます。
以下では、例として記述されている、ユーザーロジック(User Logic)部分の構成を理解し、変更するときにどこをカスタマイズすべきかについて考えていきます。
ユーザーロジックについて
ユーザーロジックを図解すると下の図のようになります。
前回の記事でも解説したように、AXI4-Liteには合計5種類のチャネルがあり、書き込みではAW、W、Bチャネル、読み出しではAR、Rチャネルをそれぞれ使います。
ユーザーロジックでは大きく3つの処理がなされています。
「S側IPへの書き込み処理」、「S側IPからの読み出し処理」、「書き込み値と読み出し値の判定処理」の3つです。
ユーザーロジックでは、この3つの処理をステートマシンを使って順次実行しています。
状態遷移図と各処理におけるタイミングチャートを見て、内部の動作の大まかな流れを確認しましょう。
状態遷移図
ユーザーロジックの内部では、上記の3つの処理を行う状態に加えて、トリガー信号の入力を待つ待機状態の、合計4状態が定義されています。
下の状態遷移図のように、順にループの構造でこの4状態の間を遷移します。
S側IPにおけるレジスタへの書き込み操作と読み出し操作は、パラメータ“C_M_TRANSACTIONS_NUM”で指定した回数分だけ実行されます。
例えば、生成したカスタムIPでは、”C_M_TRANSACTIONS_NUM”が4に指定されているので、4回、アドレスとデータの書き込みと読み出しを行います。
このとき、常に同じアドレスに書き込みを行うわけではなく、下のコードのように、例えば、アドレスには4ずつ値が加算されて別のアドレスに値を書き込んで、動作確認を行っていました。
begin
axi_awaddr <= axi_awaddr + 32'h00000004;
end
書き込みのタイミングチャート(INIT_WRITE)
実際に書き込み処理を行うときのタイミングチャートは以下のようになっていました。
AWチャネルとWチャネルにそれぞれアドレスとデータをのせて、外部のS側IPに出力しています。
S側IPからBチャネルにのせたOK信号を受信したら、次の書き込みを実行します。
指定した回数分だけ書き込みが完了したら、INIT_READ状態に状態遷移します。
読み出しのタイミングチャート(INIT_READ)
読み出しも同様で、AWチャネルにアドレスをのせて、S側IPにデータを要求します。
Wチャネルにのったデータを受信したら、次の読み出しを行い、指定した回数分だけの読み出しを実行します。
書き込み値と読み出し値の判定処理(COMPARE)
判定処理(COMPARE)はシンプルで、読み出し処理中(state=INIT_READ)に、書き込みを行った値(expected_rdata;X)と読みだした値(M_AXI_RDATA;Y)が一致するかどうかを予め判定しておき、判定結果をERRORに出力する処理を1クロックで行っています。
※なお、ERRORは書き込みと読み出しの不一致だけでなく、AXIトランザクションが不成立の場合もHigh状態になるようにロジックが組まれています。本記事では、その説明を割愛しています。
本記事では、M側のAXI4-Liteのカスタマイズの手始めに、作成したカスタムIPの内部構成の理解からはじめました。
UARTへの送信を行うには、書き込み処理を行っているロジックを流用できそうですね。
最後までご覧いただきありがとうございました!
コメント