HDL/VivadoでAXIバスを利用 の履歴(No.2)
更新- 履歴一覧
- 差分 を表示
- 現在との差分 を表示
- ソース を表示
- 電気回路/HDL/VivadoでAXIバスを利用 へ行く。
プロジェクトを作成†
[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" という IP を作成します。
AXI4 Lite Slave として設定可能なレジスタを4つ持つ IP を作成します。
AXI4 BFM Simulation はライセンスを別途購入しないと使えません。
[Edit IP] とすることで IP 用のプロジェクトが別途で作成されます。
このような IP 用のプロジェクトでは、 [Flow Navigator] の [Project Manager] から [Package 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; ...
のようになっているので、これを 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] すると、
正しくシミュレーションできていることが分かります。
自動生成される IP の中身†
AXI4 Lite Slave†
4つのレジスタ slv_reg1, slv_reg2, slv_reg3, slv_reg4 を含んでいて、 AXI4 Lite インタフェースを用いて内容を読み書きできます。
これらのレジスタの値を既存の IP で読み取れば、 既存 IP への設定値を AXI4 バス経由で書き込めることになります。
レジスタの値の代わりに既存 IP からの出力を繋げば、 既存 IP の状態を AXI4 バス経由で読み出せることになります。
AXI4 Lite Master†
AXI4 バス経由で、Slave のレジスタに順に値を書き込むコードが生成されています。
書き込み手順を変えれば任意の IP と繋げられるはずです。
AXI4 Lite Master を読む†
シミュレーション時に使いたいのでちゃんと読んでみる
LANGUAGE:verilog(linenumber) `timescale 1 ns / 1 ps
メインのステートマシン†
- IDLE
- init_txn_pulse で INIT_WRITE へ
- INIT_WRITE
- writes_done で INIT_READ へ
- INIT_READ
- reads_done で INIT_COMPARE へ
- INIT_COMPARE
- 無条件に IDLE へ
という簡単なもの
INIT_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
改造指針†
last_write に write_command_empty を割り当てる
アドレスとデータを適切に切り替える
とすれば良さそう。
INIT_READ の動作†
メインロジック†
LANGUAGE:verilog INIT_READ: if (reads_done) begin mst_exec_state <= INIT_COMPARE; end else if (~axi_arvalid && ~M_AXI_RVALID && ~start_single_read && ~read_issued && ~last_read) begin start_single_read <= 1; read_issued <= 1; end else if (axi_rready) begin read_issued <= 0; end else begin start_single_read <= 0; end