本記事の概要
Xilinx社では、IPコアのインタフェースをAXI4プロトコルで共通化することによって、システム開発での生産性・互換性・柔軟性を高めています。
自作で作成したRTLモジュールを既存のIPと接続するためには、自作RTLモジュールにAXIプロトコルの入出力インターフェースを追加しなくてはいけません。
しかし、実際にゼロからIPにAXIプロトコルの入出力インターフェースを追加するのは大変です。
そこで、前回の記事では、Xilinx社が提供するVivadoのIPパッケージャーという機能を用いて、自作のカスタムIPにAXIプロトコルの入出力インターフェースを追加する方法について解説しました。
今回の記事では、前回の記事を応用し、Hello Worldをシリアル出力させるロジック回路をProgrammable Logic (PL)上に作成しました。
外部にシリアル出力させるIPとして、AXI UART LiteというIPコアが用意されています。しかし、このIPの入力には、AXI4-Liteポートしか用意されていません。
そこで、IPパッケージャーを用いて、AXI UART Liteと接続するためのカスタムIPを作成しました。
AXI4プロトコルの動作について学ぶには以下の書籍がおすすめです。入門者向けにAXI4プロトコルの動作は、「FPGAプログラミング大全」に非常にわかりやすく解説されています。
目標
本記事全体の目標は次の通りです:
自作のRTLモジュールにAXI4-Liteインタフェース(M側)を追加する
今回は、カスタムIPからAXI4-Liteを通じて、XilinxのIP”AXI UART Lite”のデータレジスタに値を書き込み、文字列“Hello world”を外部PCに出力させてみました。
カスタムIPのカスタマイズ
カスタムIPの動作を図示しました。
カスタムIPは、トリガー信号を受信したら、送信用データをIP”AXI-UART Lite”のTx FIFOに書き込みます。
問題なく書き込みが終われば、”AXI UART Lite”のIPがTx FIFOの入力をシリアル信号に変換して、外部に出力します。
前回の記事で解説したとおり、IPパッケージャーで自動生成を行った直後のカスタムIPは、「S側IPへの書き込み処理」、「S側IPからの読み出し処理」、「書き込み値と読み出し値の判定処理」の3つの処理を行うロジックで構成されています。
ユーザーロジックでは、この3つのロジックをステートマシンを使って順次切り替えて実行していました。
図解すると下の図のようになります。
一方で、今回のカスタムIPでは、IP”AXI-UART Lite”のTransmit Data FIFOにデータを書き込むだけでよいので、上記の3つのロジックのうち、「S側IPへの書き込み処理」のみを行えばよいと考えました。
また、書き込み処理は、“C_M_TRANSACTIONS_NUM”で指定した回数分だけ行っていましたが、今回は一つのレジスタに書き込むだけなので1回だけ行えばよいです。
そこで、ユーザーロジックを以下のように変更しました。
変更箇所のソースコードをトグルボックスの中に入れています。ご興味のある方は+マークをクリックしてご覧いただければと思います。
下の図における、緑色のユーザーロジックの例と青色のユーザーロジックと呼んでいるコードの部分を変更しました。
//--------------------------------
//User Logic
//--------------------------------
//Write Addresses
always @(posedge M_AXI_ACLK) begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1) begin
axi_awaddr <= 32'h00000004;
end
end
// Write data generation
always @(posedge M_AXI_ACLK) begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 ) begin
axi_wdata <= {24'b0, AXI_DATA};
end
end
//implement master command interface state machine
always @ ( posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 1'b0)
begin
// reset condition
// All the signals are assigned default values under reset condition
mst_exec_state <= IDLE;
start_single_write <= 1'b0;
write_issued <= 1'b0;
end
else
begin
// state transition
case (mst_exec_state)
IDLE:
// This state is responsible to initiate
// AXI transaction when init_txn_pulse is asserted
if ( init_txn_pulse == 1'b1 )
begin
mst_exec_state <= INIT_WRITE;
end
else
begin
mst_exec_state <= IDLE;
end
INIT_WRITE:
if (writes_done)
begin
mst_exec_state <= IDLE;//
end
else
begin
mst_exec_state <= INIT_WRITE;
if (~axi_awvalid && ~axi_wvalid && ~M_AXI_BVALID && ~last_write && ~start_single_write && ~write_issued)
begin
start_single_write <= 1'b1;
write_issued <= 1'b1;
end
else if (axi_bready)
begin
write_issued <= 1'b0;
end
else
begin
start_single_write <= 1'b0; //Negate to generate a pulse
end
end
default :
begin
mst_exec_state <= IDLE;
end
endcase
end
end //MASTER_EXECUTION_PROC
//Terminal write count
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
last_write <= 1'b0;
//The last write should be associated with a write address ready response
else if ((write_index == 1) && M_AXI_AWREADY)
last_write <= 1'b1;
else
last_write <= last_write;
end
//Check for last write completion.
always @(posedge M_AXI_ACLK)
begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1)
writes_done <= 1'b0;
//The writes_done should be associated with a bready response
else if (last_write && M_AXI_BVALID && axi_bready)
writes_done <= 1'b1;
else
writes_done <= writes_done;
end
なお、ユーザーポートには、AXI_DATAという8ビット長の入力ポートを追加しています。
// Users to add ports here
input wire [7:0] AXI_DATA,
レジスタへの書き込み
書き込み処理に関係する記述のみを残し、それ以外の読み出し処理と判定比較処理に関係する記述をすべて削除しました。
書き込み処理も、以下のコードのようにオフセットアドレス”32’h0000_0004”のみに、データを{24’b0, AXI_DATA}の1回だけ書き込むようにしています。
//Write Addresses
always @(posedge M_AXI_ACLK) begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1) begin
axi_awaddr <= 32'h00000004;
end
end
// Write data generation
always @(posedge M_AXI_ACLK) begin
if (M_AXI_ARESETN == 0 || init_txn_pulse == 1'b1 ) begin
axi_wdata <= {24'b0, AXI_DATA};
end
end
オフセットアドレス”32’h0000_0004”はIP”AXI UART Lite”のTx FIFOのオフセットアドレスです。
このレジスタの下位8ビットにAXI_DATAという1バイト分の情報を書き込むことが可能です。
今回、最終的には、”Hello World”という文字列を順次、外部に伝送したいのですが、まずは1文字(1バイト)分だけ送ることができるようにしています。
次回の記事では、このカスタムIPに順次文字列を伝送するためのIPを紹介した後、実際にZybo上で動作確認をした結果をまとめます。
状態遷移図
ステートマシンは残したままにしましたが、以下のように変更しています。
書き込み処理が完了すれば、状態INIT_WRITEから状態IDLEに戻るようにして、INIT_READやCOMPAREの状態に遷移しないようにしています。
カスタムIPの再パッケージ
ラッパーファイルにも、ユーザーポートAXI_DATAを追加した後、カスタムIPの再パッケージを行いました。
パッケージの方法は以下の記事で解説した方法と同じです。
8ビットの信号をAXI-UART Liteに出力するためのIPが出来上がりました。
AXI_DATAという、Tx FIFOに書き込む1バイト分の信号を入力するためのポートを追加しています。
RTLシミュレーション
テストベンチ環境
RTLシミュレーションを行い、カスタムIPが正しく動作しているかどうかを見てみましょう。
シミュレーションを行うために、次のようなブロックデザインを作成しました。
AXI-UART Liteの設定は以下の通りです。Zyboのクロック125MHzを分周し100MHzにして入力しました。
また、テストベンチのコードは以下のようにしました。
`timescale 1ns / 1ps
module uart_tb();
//-----------------------------------------------------------------------
// clk and reset
//-----------------------------------------------------------------------
localparam SYSSTEP = 8 ;
localparam STEP = 10 ;
reg SYSCLK ; //125MHz
reg SYSRST ;
reg TXNSTART ;
always begin
SYSCLK = 0; #(SYSSTEP / 2);
SYSCLK = 1; #(SYSSTEP / 2);
end
initial begin
SYSRST = 0; #(SYSSTEP *10);
SYSRST = 1; #(SYSSTEP);
SYSRST = 0;
end
initial begin
TXNSTART = 0; #(STEP * 10000);
TXNSTART = 1;
end
//-----------------------------------------------------------------------
// instanciation of wrppaer file
//-----------------------------------------------------------------------
design_1_wrapper design_1_wrapper_inst(
.RST (SYSRST )
,.sys_clock (SYSCLK )
,.AXI_DATA (8'd10 )
,.BTN (TXNSTART )
,.UART_RXD_OUT ( )
,.UART_TXD_IN ( )
);
endmodule
RTLシミュレーションの結果
シミュレーションの結果は以下のとおりです。
m00_axi_init_ai_txn(書き込みを開始するトリガー信号)の立ち上がりエッジで、AXIのトランザクションが開始されます。
その後、UART_TXD_INからシリアル信号(8’d10;2bit表記で00001010)がリトルエンディアン形式(下位ビットから順に送信)で出力され、問題なくTx FIFOに書き込みが行われたことが確認できます。
想定される動作を図にすると下のようになり、シミュレーションとも一致しています。
次に、AXIトランザクションの周辺を拡大してみましょう。
AWチャネルとWチャネルから、それぞれアドレス、データ信号とValid信号を送信しています。
S側IPからのready信号を受け取ってトランザクションが成立したら、今度はS側IPから返答していることがわかります。
本記事では、M側のAXI4-Liteのカスタマイズの応用例として、AXI Lite-UARTに送信するためのカスタムIPを作成しました。
次回は、実際に文字列Hello Worldを送り、実機で動作確認をしてみようと思います。
最後までご覧いただきありがとうございました!
コメント