本記事の概要
VerilogやVHDLなどのハードウェア言語を用いて作成したRTLモジュールの入出力に、IPパッケージャーを使用してAXI4-Liteインタフェースを追加する方法を解説。
本記事では、M側のAXI4-Liteインタフェースを追加したカスタムIPのロジック構成を具体的に解説しました。
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を出力させてみようと思います。
- 自作RTLモジュールの接続先IPにAXI4インタフェースしか用意されていないような場合
- カスタムIPに制御レジスタやステータスレジスタを設け、プロセッサからそれらを参照したい場合
AXI4プロトコルの動作について学ぶには以下の書籍がおすすめです。入門者向けにAXI4プロトコルの動作は、「FPGAプログラミング大全」に非常にわかりやすく解説されています。
目標
本記事全体の目標は次の通りです:
自作のRTLモジュールにAXI4-Liteインタフェース(M側)を追加する

今回は、カスタムIPからAXI4-Liteを通じて、XilinxのIP”AXI UART Lite”のデータレジスタに値を書き込み、文字列“Hello world”を外部PCに出力させてみましょう。
UART (Universal Asynchronous Receiver/Transmitter, ユーアート)は調歩同期式のシリアル信号をパラレル信号に変換したり、逆にパラレル信号をシリアル信号に変換するための集積回路です。
※調歩同期式とは、データチャネル以外のクロック用のチャネルを設けずに、データ自体にデータ信号の開始と終了を表すスタート/ストップビットを加えて送受信を行う方式
AXI UART LiteはAXI4-Liteプロトコルのパラレル信号をシリアル信号に変換するUARTの役割を果たすIPです。
AXI4-Liteインタフェースを通じて、IP内部のレジスタ(UART Lite Registers)にFPGAからアクセスすることができます。
このUART Lite Registersは、IPを設定するコントロールレジスタやステータスレジスタに加えて、シリアル信号に変換するデータや受信したシリアル信号のバッファとなるFIFOとしての役割も果たします。


UARTなどのシリアル通信に関する解説はネット上に数多くあるので、まずはそちらで勉強をしました。初学者がまず手始めに書籍で勉強しようとしたときに、以下の書籍が参考になりました。
I2CやUARTなどの汎用的に使われる通信規格は、以下の書籍を参考にしています。組み込みマイコン周りのメモリやペリフェラルについて広く浅く記載されているので、網羅的に組み込みシステムを理解する初学者向けの書籍です。
細かいところまでは記載されていないので、より通信規格について学ぶにはWebや別の教科書が必要にはなりましたが、まず持っておいても損はないかと思います。
カスタム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状態になります。
- 入力
- “m00_axi_aclk”ポート:クロック信号
- “m00_axi_aresetn”ポート:リセット信号
- “m00_axi_init_axi_txn”ポート:AXIトランザクション開始を要求するトリガー信号
- 出力
- M00_AXIバス:追加したAXI4-Liteインタフェース(Master)
- “m00_axi_txn_done”:AXIトランザクションの完了を表す
- “m00_axi_error”:M側からS側に書き込んだ信号と実際に信号が一致していなかったり、AXIトランザクションが成立しなかった場合に、High状態になる。
カスタムIPの内部構成
前回の記事で作成したカスタムIPの内部を見ていきましょう。
作成したIPのフォルダ内部に雛形となるHDL記述のファイルが格納されています。
- パス:[..\IP\ip_repo\UART_OUT_1.0\hdl]
- ソースコード:
- UART_OUT_v1_0.v
- UART_OUT_v1_0_M00_AXI.v
今回も使用言語に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チャネルをそれぞれ使います。
- AWチャネル:書き込み先のアドレス
- Wチャネル:書き込むデータ
- Bチャネル:書き込みをできたかどうかの成否を判定
- ARチャネル:読み出しを行うアドレス
- Rチャネル:読みだしたデータ
ユーザーロジックでは大きく3つの処理がなされています。
「S側IPへの書き込み処理」、「S側IPからの読み出し処理」、「書き込み値と読み出し値の判定処理」の3つです。
ユーザーロジックでは、この3つの処理をステートマシンを使って順次実行しています。
状態遷移図と各処理におけるタイミングチャートを見て、内部の動作の大まかな流れを確認しましょう。
状態遷移図
ユーザーロジックの内部では、上記の3つの処理を行う状態に加えて、トリガー信号の入力を待つ待機状態の、合計4状態が定義されています。
- IDLE状態:INIT_AXI_TXNの入力待機状態
- INIT_WRITE状態:S側IPへの書き込み実行処理状態
- INIT_READ状態:S側IPからの読み出し実行処理
- COMPARE状態:書き込み値と読み出し値の判定処理状態
下の状態遷移図のように、順にループの構造でこの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への送信を行うには、書き込み処理を行っているロジックを流用できそうですね。
最後までご覧いただきありがとうございました!
次回の記事のリンク
参考:開発環境
- 開発用PC: Windows 10, 64bit
- Vivado Design Suite – HLx Edition – 2020.2
- Vitis コア開発キット – 2020.2
- 開発用基板: Zybo Zynq-7010評価ボード(Board Rev.4)
- Zynq XC7Z010-1CLG400C
コメント