HDL/VivadoでAXIバスを利用 の履歴(No.9)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/HDL/VivadoでAXIバスを利用 へ行く。
AXI バス†
Xilinx の資料によれば、
AXI は、AMBA (Advanced Microcontroller Bus Architecture) 4 仕様
に基づいて標準化 された IP インターフェイスプロトコルです。
とのことで、例えば Zynq に内蔵された ARM プロセッサと、ユーザーロジックと、の間などが AXI バスで繋がれています。すなわち IP を自作したならば、AXI バスに繋がなければ、 CPU から利用できません。
でも逆に、一旦 IP を AXI バス互換にしてしまえば Vivado 上の GUI を用いて IP 通しを容易に接続できるなど、利点も多いようです。
AXI バスはマスターとスレーブとを繋ぐ、1対1のバスだそうです。 複数を繋ぐときには Interconnect の IP を間に挟むことになります。
基礎となるプロトコル†
AXI バスを用いた通信では、マスターとスレーブとの間で様々なデータがやりとりされますが、 その基本となるのは DATA ライン、VALID ライン、READY ラインを用いた以下のようなプロトコルになります。
- DATA はデータを提示するための信号線です
- VALID は送信側が DATA に有効なデータを提示していることを示す信号線です
- READY は受信側が DATA を受け取れることを示す信号線です
- VALID と READY が同時に立った時点で送受信が成立したことになります
受け取りに時間の掛かる IP であれば、次のように valid が立ったのを見て ready を立てるのも良いですし、
&tchart( clock _~_~_~_~_~_~_~_~_~_~_ data =?====*=DATA========*=?==== valid ~~~~~~~~~~_ ready ________[~~]_ );
ready をフロー制御のように使いたければ、 次のように受け取り可能な間 ready を上げっぱなしにしても構いません。
&tchart( clock _~_~_~_~_~_~_~_~_~_~_ data =?============*D=*=?==== valid ________[~~]_ ready ~~~~~~~~~~_ );
とにかく、valid と ready が同時に立った時(上の図で黄色くハイライトされたクロック) に送受信が成立する、という規則が重要です。
このあたり、Silica の Designing a Custom AXI-lite Slave Peripheral
の説明がとても分かりやすかったです。
http://silica.com/wps/wcm/connect/71b10b18-9c9c-44c6-b62d-9a031b8f3df8/SILICA_Xilinx_Designing_a_custom_axi_slave_rev1.pdf?MOD=AJPERES
AXI4-Lite と AXI4-Stream†
CPU からレジスタを設定・読取するための AXI4-Lite と、
IP と CPU との間でデータを転送するための AXI4-Stream を使いたいです。
どちらも、上記の基本プロトコルを知っていれば動作の理解は難しくないようです。
AXI4-Lite における読み取り動作†
- マスターからアドレスを送ります
- araddr[?:0], arvalid, arready を使います
- スレーブが成否のステータスと共にデータを送ります
- rdata[?:0], rresp[1:0], rvalid, rready を使います
- rresp[1:0] は上位1ビットがゼロなら成功、1なら失敗になります
- 00 : OKAY
- 01 : Exclusive Access OK
- 10 : Slave Error
- 11 : Decode Error
例:
&tchart( aclk ~_~_~_~_~_~_~_ araddr =?===X=0x3000==X=?==== arvalid _~~~~___ arready _~~_ rdata =?=====X=0xbeef==X==?= rvalid ___~~~~_ rresp ====?==X00===X=?== rready _____~~_ );
AXI4-Lite における書き込み動作†
- マスターからアドレスを送ります
- awaddr[?:0], awvalid, awready を使います
- マスターからデータとストローブ信号を送ります
- wdata[?:0], wstrb[?:0], wvalid, wready を使います
- wstrb は wdata のうち実際に書き込む部位をバイト単位で指定します
- アドレスと同時に送られることもあります
- スレーブから結果の成否を送ります
- bresp[1:0], bvalid, bready を使います
- bresp[1:0] は上位1ビットがゼロなら成功、1なら失敗になります
- 00 : OKAY
- 01 : Exclusive Access OK
- 10 : Slave Error
- 11 : Decode Error
例:
&tchart( aclk ~_~_~_~_~_~_~_ awaddr =?===X=0x3000==X=?==== awvalid _~~~~___ awready _~~_ wdata =?===X=0xbeef==X==?=== wvalid _~~~~___ wready _~~_ bresp ====?==X00=X=?==== bvalid _~~_ bready _~~~~___ );
AXI4-Stream によるデータ転送†
- マスターからスレーブへストローブ信号、データ区切り信号と共にデータを送ります。
- tdata[?:0], tstrb[?:0], tlast, tvalid, tready を使います
- tlast は1まとまりのデータの区切りを表します
例:
&tchart( aclk ~_~_~_~_~_~_~_ tdata =?=X===D1X=?==X=D2X=? tstrb =?=X==1111=X==?=X=1111X=? tlast ________~~ tvalid ~~~~[~~] tready __[~~]~~~~__ );
FIFO の出口を AXI4-Stream マスターとして動作させるのであれば、例えば
LANG:verilog assign axis_tvalid = !fifo_empty; assign fifo_re = axis_tvalid & axis_tready; assign axis_tdata = fifo_o;
とすればよく、FIFO の入り口を AXI4-Stream スレーブとして動作させるのであれば、例えば
LANG: verilog assign axis_tready = !fifo_full; assign fifo_we = axis_tready & axis_tvalid; assign fifo_i = axis_tdata;
とすれば良いことになります。
Vivado の IP ひな形生成機構を利用する†
AXI バス自身の細かい仕様は AMBA で定められています。
- AXI4-Full, AXI4-Lite : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0022e/index.html 細かいところまでは載っていない?
- AXI4-Lite : http://silica.com/wps/wcm/connect/71b10b18-9c9c-44c6-b62d-9a031b8f3df8/SILICA_Xilinx_Designing_a_custom_axi_slave_rev1.pdf?MOD=AJPERES
- AXI4-Stream : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0051a/index.html
いきなり仕様通りの IP を書くのは大変ですが、 手っ取り早く使うために、Vivado を使って IP のひな形を作成できるようです。
以下、まずはその手順を追ってみます。
プロジェクトを作成†
IP を利用するプロジェクトを作成します。
[Create New Project] を選んで、
testing_axi4_lite という名前にしました。
RTL Project として、
[Create File] から "main.sv" を追加します。
[Add Existing IP]、[Add Constraint] は飛ばして、
Zynq 7020 を選択しました。
[Finish] でプロジェクトが生成されます。
ここではモジュール main にはポートを定義しません。
AXI4 Lite スレーブとなる IP のひな形を作成†
[Tools]-[Create and Package IP...] を選択
AXI4 ペリフェラル専用の Wizard があると書かれています。
そちらを選択して Next
"test_lite_slave" という名前にしました。
設定・読み出し可能なレジスタを4つ持つ AXI4 Lite Slave IP を作成します。
IP のひな形の他、IP を ARM から使うためのドライバのひな形まで生成してくれます。
AXI4 BFM Simulation はライセンスを別途購入しないと使えません。
[Edit IP] とすることで IP 用のプロジェクトが別途作成されます。 後々使うことになるのでぜひ作成しておくと良いです。
ここで作成したのような IP 用のプロジェクトでは、 [Flow Navigator] の [Project Manager] から [Package IP] を選択することで 右側にいろいろな設定項目が現れます。
IP ソースを変更したり、これらの設定を変更したら、 最後に [Review and Package] を押すと、IP が更新されます。
AXI4 Lite マスターとなる IP のひな形を作成する†
上と同様にして、"test_lite_master" という IP を作成しました。
[Flow Navigator]-[Project Manager] で [IP Catalog] を選ぶと、
2つの IP が追加されていることを確認できます。
GUI を使って配置する†
[Flow Navigator]-[IP Integrator]-[Create Block Design] から、
design_1 という Design を作成します。
[Add IP] から2つの IP を選んで Enter を押すと、
並びが逆な感じですが、2つの IP が配置されました。
S_AXI から M_AXI までマウスカーソルをドラッグすると、 2つのポートの間を配線できます。
Ctrl+K で aclk, arstn, init を入力ポートとして、 done を出力ポートとして作成し、それぞれ適切に配線します。
ごちゃごちゃしているので、 [Regenerate Layout] したところ、
見やすく配置されました。
"Address block is not mapped" というエラー†
ところが、これで [Validate Design (F6)] したところ、
CRITICAL WARNING: [BD 41-1356] Address block </test_lite_slave_0/S_AXI/S_AXI_reg> is not mapped into </test_lite_master_0/M_AXI>. Please use Address Editor to either map or exclude it.
というエラーが出てしまいました。
言われたとおり、Address Editor でアドレスを割り当てます。
右クリックから [Assign Address] を呼び、Range を最小の 4k にしました。
Diagram に戻り、 F6 したところ、
うまくいきました。
Verilog ソースとの統合†
[Flow navigator]-[IP Integrator]-[Generate Block Design] から、
[Out of context per Block Design] を選ぶと、
design_1.v が生成されます。
中身は、
design_1.v
LANGUAGE:verilog module design_1 (aclk, arstn, done, init); input aclk; input arstn; output done; input init; ...
のように、Ctrl+K で作成した aclk, arstn, init, done などのポートが見えています。
これを main.v でインスタンス化することで利用できます。
シミュレーションしてみる†
[Flow Navigator]-[Project Manager]-[Add Sources] から
[Add or create simulation sources] を選んで、
"design_1_test.sv" を作成します。
中身を次のようにして、クロック、リセット、開始トリガを供給しました。
design_1_test.sv
LANGUAGE:verilog `timescale 1ns / 1ps module design_1_test(); reg aclk = 0; reg arstn = 0; wire done; reg init = 0; design_1 uut (.*); always #10 aclk <= !aclk; initial begin repeat(10) @(posedge aclk); arstn <= 1; repeat(10) @(posedge aclk); init <= 1; @(posedge aclk); init <= 0; @(posedge done); @(posedge aclk); $stop; end endmodule
このファイルをトップレベルに指定して、
[Run Simulation] すると、
正しくシミュレーションできていることが分かります。
連続する書き込みに6クロックかかっているようです。
自動生成される IP の中身†
AXI4 Lite Slave†
4つのレジスタ slv_reg1, slv_reg2, slv_reg3, slv_reg4 を含んでいて、 AXI4 Lite インタフェースを用いて内容を読み書きできます。
これらのレジスタの値を既存の IP で読み取ったり、 レジスタの値の代わりに既存 IP からの出力を書き出すようにしたりすれば、 既存 IP を AXI4 Lite バスに接続することができます。
AXI4 Lite Master†
AXI4 バス経由で、Slave のレジスタに順に値を書き込むコードが生成されています。
書き込み手順を変えれば任意の IP と繋げられるはずですが、 ちょっと複雑なので理解するのに時間が掛かります。。。
AXI4 Lite Master を読む†
シミュレーション時に AXI4 Lite バス経由で IP を操作したいので、 AXI4 Lite Master の動作を理解するためソースをしっかり読んでみようと思います。
メインのステートマシン†
- IDLE
- init_txn_pulse で INIT_WRITE へ
- INIT_WRITE
- writes_done で INIT_READ へ
- INIT_READ
- reads_done で INIT_COMPARE へ
- INIT_COMPARE
- 無条件に IDLE へ
という簡単なものでした。
INIT_WRITE フェーズで書き込んだデータと同じ値が INIT_READ フェーズで読み出せることを確認するようになっています。
INIT_WRITE の動作†
READ の動作も WRITE の動作と似ているので、 WRITE が理解できればほぼ全てを理解できるようでした。
メインロジック†
LANGUAGE:verilog if (writes_done) begin mst_exec_state <= INIT_READ; end else if (~axi_awvalid && ~axi_wvalid && ~M_AXI_BVALID && ~start_single_write && ~write_issued && ~last_write) begin start_single_write <= 1; write_issued <= 1; end else if (axi_bready) begin write_issued <= 0; end else begin start_single_write <= 0; end
- axi_bready が立てば次のクロックで write_issued が降りる
- axi_bready が立っていなければ次のクロックで start_single_write が降りる
- 通常は start_single_write が立った次のクロックで axi_bready が立つことは無いので、 start_single_write のパルスは必ず1クロック幅になる
write_issued はここでしか参照されておらず、 start_single_write を立てた後、 まだ axi_bready が検出されていないことを表すフラグになっている。
last_write は続きの書き込み要求があるかどうかを表すフラグ。
- アドレス線 = AWVALID を立てて AWREADY を待つ
- データ線 = WVALID を立てて WREADY を待つ
- 書き込み完了 = M_AXI_BVALID が立ったら BREADY を返す
と言う動作なので、上記の大きな if は
- アドレス線出力中でなく
- データ選出力中でなく
- 書き込み完了待ちでなく
- 書き込み開始フラグが立っておらず
- 引き続きの書き込み要求がない
という条件を表している。
アドレス線†
awvalid を立てて awready を待つ。
awready が立ったら4増やす。
LANGUAGE:verilog // axi_awvalid の設定 // 有効な書き込みアドレスが出力されていることを示す always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin axi_awvalid <= 0; end else if (start_single_write) begin axi_awvalid <= 1; end else if (axi_awvalid && M_AXI_AWREADY) begin axi_awvalid <= 0; end end // 書き込みアドレス assign M_AXI_AWADDR = C_M_TARGET_SLAVE_BASE_ADDR + axi_awaddr; // axi_awaddr の設定 always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin axi_awaddr <= 0; end else if (axi_awvalid && M_AXI_AWREADY) begin axi_awaddr <= axi_awaddr + 32'h00000004; end end
データ線†
WVALID を立てて WREADY を待つ
LANGUAGE:verilog // axi_wvalid の設定 always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin axi_wvalid <= 0; end else if (start_single_write) begin axi_wvalid <= 1; end else if (axi_wvalid && M_AXI_WREADY) begin axi_wvalid <= 0; end end // axi_wdata の設定 always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1 ) begin axi_wdata <= C_M_START_DATA_VALUE; end else if (M_AXI_WREADY && axi_wvalid) begin axi_wdata <= C_M_START_DATA_VALUE + write_index; end end
書き込み完了†
LANGUAGE:verilog // axi_bready の設定 always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin axi_bready <= 0; end else if (~axi_bready && M_AXI_BVALID) begin axi_bready <= 1; end else begin axi_bready <= 0; end end // Flag write errors assign write_resp_error = (axi_bready & M_AXI_BVALID & M_AXI_BRESP[1]);
書き込みデータ数のカウント†
LANGUAGE:verilog // write_index の設定 always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin write_index <= 0; end else if (start_single_write) begin write_index <= write_index + 1; end end // last_write の設定 always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin last_write <= 0; end else if ((write_index == C_M_TRANSACTIONS_NUM) && M_AXI_AWREADY) begin last_write <= 1; end end // writes_done の設定 // 書き込み完了は M_AXI_BVALID && axi_bready で判定する always @(posedge M_AXI_ACLK) begin if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin writes_done <= 0; end else if (last_write && M_AXI_BVALID && axi_bready) begin writes_done <= 1; end end
ふむ、これを変更して何かするのは大変そうですね・・・
シミュレーション用の Master を作成する†
シミュレーション時に AXI4-Lite Slave となる IP と容易にデータをやりとりするために、 合成不可能な、シミュレーション専用の Master を作成しました。
まず、上記と同様の手順で AXI4_Lite_Master_BFM を作成しました。
AXI4_Lite_Master_BFM_v1_0.v の中身をごっそり書き換えて、 task のみを含む形にしてしまいます。
LANG:verilog `timescale 1 ns / 1 ps module AXI4_Lite_Master_BFM_v1_0 #( parameter integer C_M_AXI_ADDR_WIDTH = 32, parameter integer C_M_AXI_DATA_WIDTH = 32 ) ( output reg error = 0, input wire m_axi_aclk, input wire m_axi_aresetn, output reg [C_M_AXI_ADDR_WIDTH-1 : 0] m_axi_awaddr, output wire [2 : 0] m_axi_awprot, output reg m_axi_awvalid = 0, input wire m_axi_awready, output reg [C_M_AXI_DATA_WIDTH-1 : 0] m_axi_wdata, output wire [C_M_AXI_DATA_WIDTH/8-1 : 0] m_axi_wstrb, output reg m_axi_wvalid = 0, input wire m_axi_wready, input wire [1 : 0] m_axi_bresp, input wire m_axi_bvalid, output reg m_axi_bready = 0, output reg [C_M_AXI_ADDR_WIDTH-1 : 0] m_axi_araddr, output wire [2 : 0] m_axi_arprot, output reg m_axi_arvalid = 0, input wire m_axi_arready, input wire [C_M_AXI_DATA_WIDTH-1 : 0] m_axi_rdata, input wire [1 : 0] m_axi_rresp, input wire m_axi_rvalid, output reg m_axi_rready = 0 ); assign m_axi_awprot = 3'b000; assign m_axi_wstrb = 4'b1111; assign m_axi_arprot = 3'b001; task write( input [C_M_AXI_ADDR_WIDTH-1:0] addr, input [C_M_AXI_DATA_WIDTH-1:0] data ); begin m_axi_awvalid = 0; m_axi_wvalid = 0; m_axi_bready = 0; @(posedge m_axi_aclk) #1 fork begin // アドレスを出力し awvalid を立てて awready を待つ m_axi_awaddr = addr; m_axi_awvalid = 1; while(!m_axi_awready) @(posedge m_axi_aclk) #1; @(posedge m_axi_aclk) #1; m_axi_awvalid = 0; end begin // データを出力し wvalid を立てて wready を待つ m_axi_wdata = data; m_axi_wvalid = 1; while(!m_axi_wready) @(posedge m_axi_aclk) #1; @(posedge m_axi_aclk) #1; m_axi_wvalid = 0; end begin // bvalid が立ったら bready を返しエラーを読む while(!m_axi_bvalid) @(posedge m_axi_aclk) #1; @(posedge m_axi_aclk) #1; m_axi_bready = 1; error = m_axi_bresp[1]; @(posedge m_axi_aclk) #1; m_axi_bready = 0; @(posedge m_axi_aclk) #1; end join end endtask task read( input [C_M_AXI_ADDR_WIDTH-1:0] addr, output [C_M_AXI_DATA_WIDTH-1:0] data ); begin m_axi_arvalid = 0; m_axi_rready = 0; @(posedge m_axi_aclk) #1 fork begin // アドレスを出力し arvalid を立てて arready を待つ m_axi_araddr = addr; m_axi_arvalid = 1; while(!m_axi_arready) @(posedge m_axi_aclk) #1; @(posedge m_axi_aclk) #1; m_axi_arvalid = 0; end begin // rvalid が立ったら rready を返しエラーとデータを読む while(!m_axi_rvalid) @(posedge m_axi_aclk) #1; @(posedge m_axi_aclk) #1; m_axi_rready = 1; error = m_axi_rresp[1]; if(!error) data = m_axi_rdata; @(posedge m_axi_aclk) #1; m_axi_rready = 0; @(posedge m_axi_aclk) #1; end join end endtask task verify( input [C_M_AXI_ADDR_WIDTH-1:0] addr, input [C_M_AXI_DATA_WIDTH-1:0] expected ); reg [C_M_AXI_DATA_WIDTH-1:0] data; reg read_error; begin read(addr, data); error = data !== expected; if(error) $display("ERROR"); end endtask endmodule
これを test_lite_slave に繋ぐと、
write や verify のタスクを使って次のようなテストベンチを書くことができます。
LANG:verilog `timescale 1ns / 1ps module design_2_test(); reg aclk = 0; reg arstn = 0; design_2 uut (.*); // detect error always @(posedge uut.AXI4_Lite_Master_BFM_0.inst.error) $display("AXI4 error @ %t", $time); // generate clock always #5 aclk <= !aclk; initial begin repeat(10) @(posedge aclk); arstn <= 1; repeat(10) @(posedge aclk); uut.AXI4_Lite_Master_BFM_0.inst.write(0, 'h1234); uut.AXI4_Lite_Master_BFM_0.inst.verify(0, 'h1234); uut.AXI4_Lite_Master_BFM_0.inst.verify(0, 'h1235); // generates error uut.AXI4_Lite_Master_BFM_0.inst.write(0, 'h5678); uut.AXI4_Lite_Master_BFM_0.inst.verify(0, 'h5678); uut.AXI4_Lite_Master_BFM_0.inst.write(0, 'h0001); uut.AXI4_Lite_Master_BFM_0.inst.write(4, 'h0002); uut.AXI4_Lite_Master_BFM_0.inst.write(8, 'h0003); uut.AXI4_Lite_Master_BFM_0.inst.write(12, 'h0004); repeat(10) @(posedge aclk); $stop; end endmodule
実行結果は次のようになりました。
1回の書き込みに6クロック掛かっており、これは Vivado で自動生成した AXI4 Lite Master IP と同じ動作になっています。
汎用 DIO スレーブを作成する†
AXI4-Lite スレーブ作成の例として、汎用 DIO モジュールを作成してみます。
[Tool]-[Create and Package IP] から [new AXI4 peripheral] で "AXI4_Lite_Slave_DIO" を作り、 AXI4_Lite_Slave_DIO_v1_0_S_AXI.v を次のように変更しました。
まず、module AXI4_Lite_Slave_DIO_v1_0_S_AXI のポート設定に以下を加えます。
LANG:verilog // Users to add ports here input wire [C_S_AXI_DATA_WIDTH-1:0] idata0, input wire [C_S_AXI_DATA_WIDTH-1:0] idata1, input wire [C_S_AXI_DATA_WIDTH-1:0] idata2, input wire [C_S_AXI_DATA_WIDTH-1:0] idata3, output wire [C_S_AXI_DATA_WIDTH-1:0] odata0, output wire [C_S_AXI_DATA_WIDTH-1:0] odata1, output wire [C_S_AXI_DATA_WIDTH-1:0] odata2, output wire [C_S_AXI_DATA_WIDTH-1:0] odata3, // User ports ends
そして、
LANG:verilog assign odata0 = slv_reg0; assign odata1 = slv_reg1; assign odata2 = slv_reg2; assign odata3 = slv_reg3;
として出力を繋ぎ、
LANG:verilog always @(*) case (axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB]) 0: reg_data_out <= idata0; 1: reg_data_out <= idata1; 2: reg_data_out <= idata2; 3: reg_data_out <= idata3; endcase
として入力を繋げば完了です。
さらにこれを IP の外に引き出さなければならないので、 module AXI4_Lite_Slave_DIO_v1_0 の方も、ポートに
LANG:verilog // Users to add ports here input wire [C_S_AXI_DATA_WIDTH-1:0] idata0, input wire [C_S_AXI_DATA_WIDTH-1:0] idata1, input wire [C_S_AXI_DATA_WIDTH-1:0] idata2, input wire [C_S_AXI_DATA_WIDTH-1:0] idata3, output wire [C_S_AXI_DATA_WIDTH-1:0] odata0, output wire [C_S_AXI_DATA_WIDTH-1:0] odata1, output wire [C_S_AXI_DATA_WIDTH-1:0] odata2, output wire [C_S_AXI_DATA_WIDTH-1:0] odata3, // User ports ends
を加え、
LANG:verilog AXI4_Lite_Slave_DIO_v1_0_S_AXI_inst ( .idata0(idata0), .idata1(idata1), .idata2(idata2), .idata3(idata3), .odata0(odata0), .odata1(odata1), .odata2(odata2), .odata3(odata3),
のように繋ぎます。
これで AXI4-Lite 経由で idataX, odataX へアクセスできるようになりました。
ドライバファイルについて†
AXI4_Lite_Slave_DIO.h
LANG:C #include "xil_types.h" #include "xstatus.h" #define AXI4_LITE_SLAVE_DIO_S_AXI_SLV_REG0_OFFSET 0 #define AXI4_LITE_SLAVE_DIO_S_AXI_SLV_REG1_OFFSET 4 #define AXI4_LITE_SLAVE_DIO_S_AXI_SLV_REG2_OFFSET 8 #define AXI4_LITE_SLAVE_DIO_S_AXI_SLV_REG3_OFFSET 12 #define AXI4_LITE_SLAVE_DIO_mWriteReg(BaseAddress, RegOffset, Data) \ Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data)) #define AXI4_LITE_SLAVE_DIO_mReadReg(BaseAddress, RegOffset) \ Xil_In32((BaseAddress) + (RegOffset))
こんなマクロが定義されており、CPU から簡単にデータの読み書きができる雰囲気です。 (未確認)
AXI4-Lite のスレーブを作る†
vivado がはき出す AXI4-Lite スレーブのひな形コードがあまり分かりやすくなく、 無駄に遅延が入っているようにも思えるので、AXI4-Lite スレーブを一から作るために タイミングと基本動作をおさらいしたいと思います。
AXI4-Lite 読み出し時のスレーブ動作†
スレーブ側が必ず次のクロックでデータを返せる場合の例。
&tchart(
aclk ~_~_~_~_~_~_~_~_
araddr ==?X=A1X=?X=A2X=?X=A3X=?==
arvalid ~~~~~~__
arready ~~~~~~~~__~
rdata ====?X=D1X=?X=D2X=?X=D3X=?
rvalid __[~~][~~][~~]
rready ~~~~~~~~~~~~
);
arready = !rvalid の関係があるので、
LANG: verilog always @(posedge aclk) if (rst) begin arready <= 1; end else if (arready) begin if (arvalid) begin rdata <= registers[araddr]; arready <= 0; end end else begin if (rready) begin arready <= 1; end end assign rvalid = !arready;
のようにすれば最短2クロックで読み出し動作を繰り返せるスレーブになります。
LANG:verilog rdata <= register[araddr];
この部分の遅延のためにバスクロックが制限されてしまうようであれば、 一旦 araddr をバッファするために、arvalid から rready の間を1クロック空けることになるかもしれません。
&tchart( aclk ~_~_~_~_~_~_~_~_ araddr ==?X=A1X===?X=A2X=?==== arvalid __~~_~~___ arready ~~~~_~~___ rdata ======?X=D1X=?==X=D2X=? rvalid ___[~~]_[~~]__ rready ~~~~~~~~~~~~ );
この場合、rready と rvalid との関係は
rready | 1 | 0 | 0 | 1 | 0 | 0 |
rvalid | 0 | 0 | 1 | 0 | 0 | 1 |
のようになるため、
LANG: verilog always @(posedge aclk) if (rst) begin arready <= 1; rvalid <= 0; end else if (arready && !rvalid) begin if (arvalid) begin araddr_buf <= araddr; arready <= 0; end end else if (!arready && !rvalid) begin rvalid <= 1; rdata <= registers[araddr_buf]; end else begin // if (rready) begin if (rready || arready) begin // recovery from erroneous state arready <= 1; rvalid <= 0; end end
とするのが良さそうです。
万一 arready && rvalid になってしまったときのことを考えて、 最後の判定を if (rready) ではなく if (rready || arready) としてみましたが、マスター側の状態が分からないので、 必ずしもこれが最良かどうか自信がありません。
この形であればロジック遅延の大きなパスはありませんので、 バスクロックを高速化したい場合にもそのまま使えると思います。
この回路では最高で3クロックに一回読み込み動作が可能になります。
AXI4-Lite 書き込み時のスレーブ動作†
スレーブ側が書き込み要求をいつでも受け取れる場合、 awready, wready, bresp, bvalid を定数とすることができそうに思えるのですが、
&tchart(
aclk ~_~_~_~_~_~_~_~_
awaddr ==?X=A1X=A2X=A3X=A4X=A5X=?==
awvalid [~~~~~~~~~~]__
awready ~~~~~~~~~~~~~~~
wdata ==?X=D1X=D2X=D3X=D4X=D5X=?==
wvalid ~~~~~~~~~~__
wready ~~~~~~~~~~~~~~~
bresp ====00============
bvalid ~~~~~~~~~~~~~~~1
bready ~~~~~~~~~~~~~~~1
);
wvalid は常に awvalid と同時に立つとは限らない気がするので実際には、 awready は定数にすることができません。
awvalid が必ず wvalid より先に立つのであれば、下図のように wvalid が立つまで awready を上げないようにして、awvalid && wvalid を待ってから処理するのが良さそうです。
&tchart(
aclk ~_~_~_~_~_~_~_~_~_~_
awaddr ==?X=A1==X=A2X=A3==X=A4==X=A5X=?
awvalid ~~~~~~~~~~~~~~~~
awready __[~~~~][~~][~~~~]
wdata ====?X=D1X=D2X=?X=D3X=?X=D4X=D5X=?
wvalid __~~~~~~~~~~
wready __[~~~~][~~][~~~~]
bresp ====00================
bvalid ~~~~~~~~~~~~~~~~1~~~
bready ~~~~~~~~~~~~~~~~1~~~
);
この場合、awready や wready は wvalid に連動し、 書き込み動作も wvalid に同期します。
bvalid も wvalid に連動させても良いですが、 恐らく 1 に固定でも良いのだと思います。
LANG:verilog always @(posedge aclk) if (wvalid) registers[awaddr] <= wdata; assign awready = wvalid; assign wready = wvalid; assign bresp = 0; assign bvalid = 1;
ただ、awvalid が必ず wvalid より先に立つ、というのがバスの仕様上保障されているのかどうか 今ひとつ自信が持てません。。。
そうでなければ awready や wready は awvalid && wvalid で上げることにして、
&tchart(
aclk ~_~_~_~_~_~_~_~_~_~_
awaddr ==?X=A1==X=A2X=A3==X==?=X=A4X=?
awvalid ~~~~~~~~~~~~
awready __[~~~~][~~]__[~~]
wdata ====?X=D1X=D2X=?X=D3X=?X=D4==X=?
wvalid __~~~~~~~~~~
wready __[~~~~][~~]__~~
bresp ====00================
bvalid ~~~~~~~~~~~~~~~~~1~~
bready ~~~~~~~~~~~~~~~~~~1~
);
LANG:verilog always @(posedge aclk) if (awvalid && wvalid) registers[awaddr] <= wdata; assign awready = awvalid && wvalid; assign wready = awvalid && wvalid; assign bresp = 0; assign bvalid = 1;
とすれば良さそうです。
この回路で配線遅延が最も大きくなると思われるのは次の部分です。
LANG:verilog assign awready = awvalid && wvalid; assign wready = awvalid && wvalid;
この遅延が問題になるようであれば、 バッファを入れて、1クロックの遅延を許容する必要があります。
&tchart(
@w_caption 60
aclk ~_~_~_~_~_~_~_~_~_~_~_~_~_~_
awaddr ==?X===A1==X=A2==X=A3====X==?=X=A4==X=?=
awvalid __~~~~~~~~~~~~~~~~_~~~~
awready ____[~~][~~]_[~~][~~]
wdata ====?X=D1==X=D2==X=?X=D3==X=?X=D4====X=?=
wvalid __~~~~~~~~~~~~~~~~~~_
wready ____~~~~_~~~~
bresp ====00=========================
bvalid ~~~~~~~~~~~~~~~~~~~1~~~~~~~~~
bready ~~~~~~~~~~~~~~~~~~~~1~~~~~~~~
valid_buf ____~~~~_~~~~ awaddr_buf ====?X===A1==X=A2==X=A3====X==?=X=A4==X? wdata_buf ======?X=D1==X=D2==X=?X=D3==X=?X=D4====X? );
awvalid && wvalid をバッファして、次のようにすれば良いはずです。
awaddr や wdata もバッファすることにより、これらの遅延も影響しなくなりますね。
LANG:verilog always @(posedge aclk) valid_buf <= !valid_buf && awvalid && wvalid; awaddr_buf <= awaddr; wdata_buf <= wdata; if (valid_buf) registers[awaddr_buf] <= wdata_buf; assign awready = valid_buf; assign wready = valid_buf; assign bresp = 0; assign bvalid = 1;
この回路であれば、最短で2クロックに1回データを書き込めることになります。