HDL/VivadoでAXIバスを利用 のバックアップソース(No.21)
更新- バックアップ一覧
- 差分 を表示
- 現在との差分 を表示
- バックアップ を表示
- 電気回路/HDL/VivadoでAXIバスを利用 へ行く。
[[公開メモ]] #contents * AXI バス [#zf8590a4] Xilinx の資料によれば、 > AXI は、[[AMBA (Advanced Microcontroller Bus Architecture) 4 仕様>http://www.amba.com/]] に基づいて標準化 された IP インターフェイスプロトコルです。 とのことで、例えば Zynq に内蔵された ARM プロセッサと、ユーザーロジックと、の間などが AXI バスで繋がれています。すなわち、何か IP を自作したならば、AXI バスに繋げられるようにしなければ その IP を CPU から利用することができません。 でも逆に、一旦 IP を AXI バス互換にしてしまえば Vivado 上の GUI を用いて IP 同士を容易に接続できるなど、利点も多いようです。 ということで、AXI バスを一通り使えるようになるよういろいろ調べました。 * AXI バスの仕様 [#v002e43c] - "AXI4 Overview" by Xilinx~ http://www.em.avnet.com/en-us/design/trainingandevents/Documents/X-Tech%202012%20Presentations/XTECH_B_AXI4_Technical_Seminar.pdf AXI バスには3つの規格があり、それぞれ AXI(-Full), AXI-Lite, AXI-Stream と呼ばれます。 どの規格も、マスターとスレーブとを繋ぐ1対1のバスになっていますので、 複数の機器を繋ぐときには Interconnect の IP を間に挟むことになります。 &ref(axi-bus-variations.png,,50%); - AXI(-Full): マスターからスレーブのメモリやレジスタを読み書きするためのフルスペックプロトコル - AXI-Lite: マスターからスレーブのレジスタを読み書きするための低速だが軽量なプロトコル - AXI-Stream: 単純にマスターからスレーブへデータを受け渡す高速かつ軽量なプロトコル 細かい仕様は AMBA で定められています。 - AXI4-Full, -Lite 仕様 : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0022e/index.html - AXI4-Stream 仕様 : http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ihi0051a/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 ** 基礎となるプロトコル [#v357bba4] AXI ではデータ転送要求をするのは常にマスターですが、 実際のデータはマスターからスレーブへ送られることも、 スレーブからマスターへも送られることもあります。 AXI プロトコルにおけるデータ転送の基本となるのは、 DATA ライン、VALID ライン、READY ラインを用いた以下のようなプロトコルになります。 &ref(axi-bus-handshake.png,,50%); - 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 が同時に立った時(上の図で黄色くハイライトされたクロック) に送受信が成立する、という規則が重要です。 注意点として、Valid が上がるのを見てから Ready を上げるのは問題ないですが、 Ready が上がるのを待って Valid を上げようとしてはいけません。 両者で待ち合いになってデッドロックが発生しかねません。 ** AXI4-Lite Slave と AXI4-Stream Master/Slave [#k2147da0] CPU から IP のレジスタを設定・読取するための AXI4-Lite と、~ IP と CPU との間でデータを転送するための AXI4-Stream とを使いこなしたいです。 あと、AXI4-Stream は最後に DMA を使って SDRAM を読み書きすることになるので、 DMA も使いこなしたい。 ちょっとかじったところ、AXI4-Lite と AXI4-Stream は、上記の基本プロトコルさえ知っていれば動作の理解は難しくないようです。 ** AXI4-Lite における読み取り動作 [#taa54914] AXI4-Lite ではバースト転送機能がないため、アドレスを送るとデータが帰ってくる、という簡単なプロトコルになっています。 + マスターからアドレスを送ります -- araddr[?:0], arprot[2: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 arprot はアクセス許可情報を表すための信号です。 特にアクセス制限の必要がなければ 3'b000 で良いようです。 例: &tchart( aclk ~_~_~_~_~_~_~_ araddr =?===X=0x3000==X=?==== arprot =?===X=000==X=?==== arvalid ____~~~~______ arready ______[~~]______ );~ &tchart( rdata =?=====X=0xbeef==X==?= rresp ====?==X00===X=?== rvalid ______~~~~____ rready ________[~~]____ ); ** AXI4-Lite における書き込み動作 [#qb99af77] こちらもバースト転送はないので、 マスターからアドレスとデータをそれぞれ別々のチャンネルから送ると、 スレーブから成否を返すという単純なプロトコルです。 + マスターからアドレスを送ります -- awaddr[?:0], awprot[2: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 アドレスとデータの転送は平行して行えます。データを送るのにアドレスの転送終了を待つ必要はありません。 awprot はアクセス許可情報を表すための信号です。 特にアクセス制限の必要がなければ 3'b000 で良いようです。 例: &tchart( aclk ~_~_~_~_~_~_~_ awaddr =?===X=0x3000==X=?==== awprot =?===X=000==X=?==== awvalid ____~~~~______ awready ______[~~]______ );~ &tchart( wdata =?===X=0xbeef==X==?=== tstrb =?===X=1111==X==?=== wvalid ____~~~~______ wready ______[~~]______ );~ &tchart( bresp ====?==X00=X=?==== bvalid ______[~~]______ bready ____~~~~______ ); ** AXI4-Stream によるデータ転送 [#r768d77f] アドレス指定も何もなく、単にマスターからスレーブへデータだけが送られます。 データをパケットに分けるための区切り情報も表すことができます。 + マスターからスレーブへストローブ信号、データ区切り信号と共にデータを送ります。 - tdata[?:0], tstrb[?:0], tlast, tvalid, tready を使います - tlast はひとまとまりのデータの区切りを表します 例: &tchart( aclk ~_~_~_~_~_~_~_ tdata =?=X===D1X=?==X=D2X=? 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 ひな形生成機構を利用する [#u6d63940] AXI バスに繋げられる IP を作るには IP 動作をバス仕様に合わせるだけでなく、 その IP を Vivado で扱えるようにするための作法に合わせる必要もあります。 すべてを一から作るのはいろんな仕様をすべて頭に入れなければならず大変なので、 手っ取り早く使うためには Vivado を使って IP のひな形を作成するのが良いようです。 以下、その手順を追ってみます。 ** プロジェクトを作成 [#j4df389e] IP を利用するプロジェクトを作成します。 &ref(new-project.png,,50%); [Create New Project] を選んで、 &ref(project-name.png,,50%); testing_axi4_lite という名前にしました。 &ref(rtl-project.png,,50%); RTL Project として、 &ref(main-sv.png,,50%); [Create File] から "main.sv" を追加します。 [Add Existing IP]、[Add Constraint] は飛ばして、 &ref(zynq-7020.png,,50%); Zynq 7020 を選択しました。 &ref(project-summary.png,,50%); [Finish] でプロジェクトが生成されます。 &ref(no-ports.png,,50%); ここではモジュール main にはポートを定義しません。 ** AXI4 Lite スレーブとなる IP のひな形を作成 [#cc35574c] &ref(create-ip-menu.png,,50%); [Tools]-[Create and Package IP...] を選択 &ref(create-ip.png,,50%); AXI4 ペリフェラル専用の Wizard があると書かれています。 &ref(create-axi4-peripheral.png,,50%); そちらを選択して Next &ref(test_lite_slave.png,,50%); "test_lite_slave" という名前にしました。 &ref(lite_slave_config.png,,50%); 設定・読み出し可能なレジスタを4つ持つ AXI4 Lite Slave IP を作成します。 IP のひな形の他、IP を ARM から使うためのドライバのひな形まで生成してくれます。 &ref(lite_slave_creation.png,,50%); AXI4 BFM Simulation はライセンスを別途購入しないと使えません。 [Edit IP] とすることで IP 編集用のプロジェクトが別途作成されます。 後々使うことになるのでぜひ作成しておくと良い・・・のですが・・・ これを選ぶと Vivado は ip_repo の直下にやたらとたくさんフォルダを 作ってしまうので、あとで非常にごちゃごちゃして嫌な感じです。 保存場所を選択できるようになっていれば良かったのに、 と思いました。 むしろここでは Edit IP をせず、後から IP Catalog 上で右クリックから Edit in IP Packager として、自分の好きな場所にプロジェクトを作成するのが 良いかもしれません。ただその場合には、Project Settings の [IP]-[Packager] で、[Delete project after package] を外しておかないと、 作成したプロジェクトは自動的に削除されてしまいますので注意して下さい。 &ref(not_delete_project.png,,50%); 上記どちらかの要領で作成したような IP パッケージング用のプロジェクトでは、 [Flow Navigator] の [Project Manager] から [Package IP] を選択することで 右側にいろいろな設定項目が現れます。 &ref(lite_slave_project.png,,50%); IP ソースを変更したり、これらの設定を変更したら、 最後に [Review and Package] を押すと、IP が更新されます。 ** AXI4 Lite マスターとなる IP のひな形を作成する [#oc593fbf] 上と同様にして、"test_lite_master" という IP を作成しました。 [Flow Navigator]-[Project Manager] で [IP Catalog] を選ぶと、 &ref(lite_sm_generated.png,,50%); 2つの IP が追加されていることを確認できます。 ** 自動生成される IP の中身 [#h0e78358] *** AXI4 Lite Slave [#w41173ad] 4つのレジスタ slv_reg1, slv_reg2, slv_reg3, slv_reg4 を含んでいて、 AXI4 Lite インタフェースを用いて内容を読み書きできます。 これらのレジスタの値を既存の IP で読み取ったり、 レジスタの値の代わりに既存 IP からの出力を書き出すようにしたりすれば、 既存 IP を AXI4 Lite バスに接続することができます。 *** AXI4 Lite Master [#s82d83fb] AXI4 バス経由で、Slave のレジスタに対して順に値を書き込むコードが生成されています。 ** GUI を使って配置する [#n430d13d] [Flow Navigator]-[IP Integrator]-[Create Block Design] から、 &ref(create_block_design.png,,50%); design_1 という Design を作成します。 &ref(add-lite-ips.png,,50%); [Add IP] から2つの IP を選んで Enter を押すと、 &ref(lite-ips-placed.png,,50%); 並びが逆な感じですが、2つの IP が配置されました。 S_AXI から M_AXI までマウスカーソルをドラッグすると、 2つのポートの間を配線できます。 Ctrl+K で aclk, arstn, init を入力ポートとして、 done を出力ポートとして作成し、それぞれ適切に配線します。 &ref(design-routed.png); ごちゃごちゃしているので、 [Regenerate Layout] したところ、 &ref(layout-regenerated.png); 見やすく配置されました。 ** "Address block is not mapped" というエラー [#ebb49d4a] ところが、これで [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 でアドレスを割り当てます。 &ref(assign-address.png,,50%); 右クリックから [Assign Address] を呼び、Range を最小の 4k にしました。 &ref(address-assigned.png,,50%); Diagram に戻り、 F6 したところ、 &ref(validation-successful.png,,50%); うまくいきました。 ** Verilog ソースとの統合 [#i7215ace] [Flow navigator]-[IP Integrator]-[Generate Block Design] から、 &ref(generate-block-design.png,,50%); [Out of context per Block Design] を選ぶと、 &ref(verilog-source-generated.png,,50%); 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 でインスタンス化することで利用できます。 ** シミュレーションしてみる [#o8d662e3] [Flow Navigator]-[Project Manager]-[Add Sources] から &ref(add-simulation-source.png,,50%); [Add or create simulation sources] を選んで、 &ref(create-design_1_test.png,,50%); "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 このファイルをトップレベルに指定して、 &ref(set-as-top.png,,50%); [Run Simulation] すると、 &ref(axi4-lite-simulated.png); 正しくシミュレーションできていることが分かります。 連続する書き込みに6クロックかかっているようです。 * シミュレーション用の Master を作成する [#h72e26ea] シミュレーション時に AXI4-Lite Slave となる IP と容易にデータをやりとりするために、 合成不可能な、シミュレーション専用の Master を作成しました。 始め、ひな形として作成した 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'b000; 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 に繋ぐと、 &ref(axi4_lite_master_bfm-design.png); 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 実行結果は次のようになりました。 &ref(axi4_lite_master_bfm-test.png); 1回の書き込みに6クロック掛かっており、これは Vivado で自動生成した AXI4 Lite Master IP と同じ動作になっています。 * 汎用 DIO スレーブを作成する [#e54cebe2] 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 へアクセスできるようになりました。 ** ドライバファイルについて [#led644e3] 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 から簡単にデータの読み書きができる雰囲気です。 しかも Xil_Out32 や Xil_In32 の中身は単なるメモリアクセスなんですね。 プロジェクト中で IP を利用した場合、これらのファイルは正しく SDK に引き継がれるようでした。 このあたりの仕組みもあとで勉強したいところです。 * AXI4-Lite のスレーブを作る [#e32ac5c2] vivado がはき出す AXI4-Lite スレーブのひな形コードがあまり分かりやすくなく、 無駄に遅延が入っているようにも思えるので、AXI4-Lite スレーブを一から作るために タイミングと基本動作をおさらいしたいと思います。 ** ごめんなさい、現状では動作未確認です [#n7886c2c] 以下の記述はもっともらしく書いてありますが、実記での動作は未確認です。 追って動作確認を行います。 ** AXI4-Lite 読み出し時のスレーブ動作 [#g12e8fbc] AXI4 の仕様では次のように規定されています。 A3.3.1 Dependencies between channel handshake signals - arvalid と arready -- マスターは arvalid を立てるのに arready を待ってはならない -- スレーブは arready を立てるのに arvalid を待ってよい -- スレーブは arready を立てるのに arvalid を待たなくてもよい - rvalid と rready -- スレーブは rvalid を立てる前に arvalid と arready を待たなければならない -- スレーブは rvalid を立てるのに rready を待ってはならない -- マスターは rready を立てるのに rvalid を待ってよい -- マスターは rready を立てるのに rvalid を待たなくてもよい スレーブ側が必ず次のクロックでデータを返せる場合の例。 &tchart( aclk ~_~_~_~_~_~_~_~_~_ araddr ==?X=A1X=?X=A2X=?==X=A3X=?== arvalid __~~__~~____~~____ arready ~~~~__~~____~~__~~ rdata ====?X=D1X=?X=D2==X=?X=D3X=? rvalid ____[~~]__~~[~~]__[~~]__ rready __~~~~____~~~~~~__ ); arready = !rvalid の関係があるので、 LANG: verilog always @(posedge aclk) if (!resetn) begin arready <= 1; end else if (arready) begin if (arvalid) begin rdata <= get_registers(araddr); arready <= 0; end end else begin if (rready) begin arready <= 1; end end assign rvalid = !arready; のようにすれば最短2クロックで読み出し動作を繰り返せるスレーブになります。 ただし、 LANG:verilog rdata <= get_registers(araddr); この部分はマスターから送られてくる araddr をデータ選択回路にそのまま繋ぐことになるため、配線遅延が大きくなる可能性があります。 この遅延のためにバスクロックが制限されてしまうようであれば、 一旦 araddr をバッファするために、arvalid から rready の間を1クロック空ける必要があります。 &tchart( aclk ~_~_~_~_~_~_~_~_~_ araddr ==?X=A1X===?X=A2X=?====== arvalid __~~____~~________ arready ~~~~____~~______~~ rdata ======?X=D1X=?==X===D2X=? rvalid ______[~~]____~~[~~]__ rready __~~~~~~~~____~~__ ); この場合、rready と rvalid に注目すると、 ¨( skinparam handwritten true [*] -> コマンド待ち コマンド待ち: rready = 1 コマンド待ち: rvalid = 0 コマンド待ち --> 読み出し: arvalid & \narready 読み出し: rready = 0 読み出し: rvalid = 0 読み出し-right--> 結果送信: 読み出し終了 結果送信: rready = 0 結果送信: rvalid = 1 結果送信 --> コマンド待ち : rready ); のようになるため、 LANG: verilog always @(posedge aclk) if (!resetn) 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 // Indeed, we can use as many as clocks we need // before finishing preparation of rdata and // assertion of rvalid in this block. rdata <= get_register(araddr_buf); rvalid <= 1; end else begin if (rready || arvalid) begin // recovery from erroneous state arready <= 1; rvalid <= 0; end end とするのが良さそうです。 万一、マスターとの歩調がずれてしまい、 rready を待っている間に次の arvalid が来てしまった場合に 正常動作へ戻れるよう、 最後の判定を if (rready) ではなく if (rready || arvalid) としてみました。マスター側の状態が分からないので、 必ずしもこれが最良かどうか自信がありませんが。。。 この回路では最短で3クロックに一回読み込み動作が可能になります。 コメントでも入れたとおり、rdata にデータを入れて rvalid を立てる部分は、必要なら何クロックかかかっても構いません。 rvalid が立つまでマスターは待ってくれます。 ** AXI4-Lite 書き込み時のスレーブ動作 [#af224058] AXI4 の仕様によれば、以下の要求を満たす必要があります。 A3.3.1 Dependencies between channel handshake signals + マスターは awvalid や wvalid を立てるのに awready や wready を待ってはならない + スレーブは awready や wready を立てるのに awvalid や wvalid を待ってもよい + スレーブは awready や wready を立てるのに awvalid や wvalid を待たなくてもよい + スレーブは bvalid を立てる前に awvalid, awready, wvalid, wready を待たなければならない~ (AXI4-Full のバースト転送では wlast も待たなければならない) + スレーブは bvalid を立てるのに bready を待ってはならない + マスターは bready を立てるのに bvalid を待ってもよい + マスターは bready を立てるのに bvalid を待たなくてもよい 気をつけなければならないのは 4. ですね。 wvalid と wready の両方を確認してからでないと bvalid を立ててはいけないそうです。 もう一つ、データが送られる前に必ずアドレスが送られるのかどうか、 つまり wvalid が立つ前に必ず awvalid が立つのかどうか、 上では明記されていません。でも、A1.3 AXI Architecture のところには >The AXI protocol permits address information to be issued ahead of the actual data transfer と書かれていますし、Figure A1-2 でも Address and control が Write data に先行していますので、 恐らく awvalid の前に wvalid が立つことは無いと思われます。 すると、スレーブ側が書き込み要求をいつでも受け取れる場合、 下図のように wvalid が立つまで awready を上げないようにして、 awvalid && wvalid を待ってから処理すると実装が楽そうです。 &tchart( @w_caption 50 aclk ~_~_~_~_~_~_~_~_~_~_~_ awaddr ==?X=A1==X=A2X=A3==X=?X=A4==X=A5X=? awvalid __~~~~~~~~~~__~~~~~~__ awready ____[~~~~]__[~~]____[~~~~]__ wdata ====?X=D1X=D2X=?X=D3X=?==X=D4X=D5X=? wvalid ____~~~~__~~____~~~~__ wready ____~~~~__~~____~~~~__ bresp ====00================== bvalid ____~~~~__~~~~__~~~~__ bready ~~~~~~~~____~~~~~~~~~~ bvalid_hold ____________~~________ ); この場合、awready, wready は wvalid に連動して動かせばよく、 書き込み動作も wvalid に同期します。 bvalid は bready を待たなければなりませんので、 bvalid && !bready の条件で値を保持します。 LANG:verilog always @(posedge aclk) begin if (!resetn) begin bvalid_hold <= 0; end else begin bvalid_hold <= bvalid && !bready; end if (wvalid) set_register(awaddr, wdata); end assign awready = wvalid; assign wready = wvalid; assign bresp = 0; assign bvalid = wvalid || bvalid_hold; ただ、この回路ではマスターからバスラインを通って届いた wvalid をバスラインを通じてマスターに返すことになるため、配線遅延が大きくなる心配があります。 また、registers への書き込みにマスターから届いた awaddr や wdata をそのまま使っているのも配線遅延的に不利になります。 この遅延が問題になるようであれば、 バッファを入れて、1クロックの遅延を許容する必要があります。 wvalid, awaddr, wdata をバッファすることにより、これらの遅延が影響しなくなります。 &tchart( @w_caption 60 aclk ~_~_~_~_~_~_~_~_~_~_~_ awaddr ==?X=A1====X=A2==X=?X=A3====X=? awvalid __~~~~~~~~~~__~~~~~~__ awready ______[~~]__[~~]______[~~]__ wdata ====?X=D1==X=D2==X=?==X=D3==X=? wvalid ____~~~~~~~~____~~~~__ wready ______~~__~~______~~__ bresp ====00================== bvalid ______~~__~~~~____~~__ bready ~~~~~~~~____~~~~~~~~~~ ready ______~~__~~______~~__ awaddr_buf ====?X=A1====X=A2==X=?X=A3==== wdata_buf ======?X=D1==X=D2==X=?==X=D3== ); LANG:verilog(linenumber) always @(posedge aclk) if (!resetn) begin ready <= 0; bvalid <= 0; end else begin ready <= !ready && awvalid && wvalid; bvalid <= (!ready && awvalid && wvalid) || // raise (bvalid && !bready); // hold awaddr_buf <= awaddr; wdata_buf <= wdata; if (ready) set_register(awaddr_buf, wdata_buf); end assign awready = ready; assign wready = ready; assign bresp = 0; 実は2行目は、本来なら次のように wvalid だけを待てば良いのですが、 LANG:verilog ready <= !ready && wvalid; 前者のようにすることで、万一 !awvalid のまま wvalid が立ったとしても、 不用意な書き込みをしてしまわないようになっています(あまり自信なし)。 この回路は最短で2クロックに1回データを書き込めます。 書き込み動作に時間が掛かるためクロックを引き延ばす必要がある場合には、 bvalid を立てる条件を変えて、書き込みが完了するクロックで bvalid が 立つようにする必要があります。 ** wstrb について [#i5343809] ストローブ信号は1バイトにつき1ビットで表されているので、 書き込みのマスクに使うには1ビットを8ビットに広げる必要があります。 LANG:verilog wire [C_S_AXI_DATA_WIDTH-1 : 0] strobe_expended; generate genvar i; for(i=0; i<C_S_AXI_DATA_WIDTH/8; i=i+1) begin:strobe_bits assign strobe_expended[i*8+7:i*8] = {8{wstrb_buf[i]}}; end endgenerate とすることで、wstrb_buf を広げて strobe_expended を作成できます。 これを使って、 LANG:verilog register <= (wdata & strobe_expended) | (register & ~strobe_expended); とすれば、必要なバイトだけを更新できるはずです。 ** 合わせると [#qbc45efb] 例えば AXI4-Lite に繋げられる汎用 DIO を以下のように作れます。 get_register と set_register を書き換えるだけで 様々な AXI4-Lite スレーブを作成できるようになっています。 LANG:verilog(linenumber) `timescale 1 ns / 1 ps module AXI4_Lite_Slave_DIO_v1_0_S_AXI #( parameter integer C_S_AXI_DATA_WIDTH = 32, parameter integer C_S_AXI_ADDR_WIDTH = 2 ) ( // 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 reg [C_S_AXI_DATA_WIDTH-1:0] odata0, output reg [C_S_AXI_DATA_WIDTH-1:0] odata1, output reg [C_S_AXI_DATA_WIDTH-1:0] odata2, output reg [C_S_AXI_DATA_WIDTH-1:0] odata3, // User ports ends input wire S_AXI_ACLK, input wire S_AXI_ARESETN, input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_AWADDR, input wire [2 : 0] S_AXI_AWPROT, input wire S_AXI_AWVALID, output wire S_AXI_AWREADY, input wire [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA, input wire [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB, input wire S_AXI_WVALID, output wire S_AXI_WREADY, output wire [1 : 0] S_AXI_BRESP, output reg S_AXI_BVALID, input wire S_AXI_BREADY, input wire [C_S_AXI_ADDR_WIDTH-1 : 0] S_AXI_ARADDR, input wire [2 : 0] S_AXI_ARPROT, input wire S_AXI_ARVALID, output reg S_AXI_ARREADY, output reg [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA, output wire [1 : 0] S_AXI_RRESP, output reg S_AXI_RVALID, input wire S_AXI_RREADY ); function [C_S_AXI_DATA_WIDTH-1 : 0] get_register; input [C_S_AXI_ADDR_WIDTH-1 : 0] addr; begin get_register = (addr & 'h0c) == 0 ? idata0 : (addr & 'h0c) == 4 ? idata1 : (addr & 'h0c) == 8 ? idata2 : (addr & 'h0c) == 12 ? idata3 : 0; end endfunction task set_register; input [C_S_AXI_ADDR_WIDTH-1 : 0] addr; input [C_S_AXI_DATA_WIDTH-1 : 0] data; input [C_S_AXI_DATA_WIDTH-1 : 0] strobe; begin case(addr & 'h0c) 0: odata0 <= (data & strobe) | (odata0 & ~strobe); 4: odata1 <= (data & strobe) | (odata1 & ~strobe); 8: odata2 <= (data & strobe) | (odata2 & ~strobe); 12: odata3 <= (data & strobe) | (odata3 & ~strobe); endcase end endtask ////////////////////////////////////////////////////// code for read action reg [C_S_AXI_ADDR_WIDTH-1 : 0] araddr_buf; always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) begin S_AXI_ARREADY <= 1; S_AXI_RVALID <= 0; end else if (S_AXI_ARREADY && !S_AXI_RVALID) begin if (S_AXI_ARVALID) begin araddr_buf <= S_AXI_ARADDR; S_AXI_ARREADY <= 0; end end else if (!S_AXI_ARREADY && !S_AXI_RVALID) begin S_AXI_RVALID <= 1; S_AXI_RDATA <= get_register(araddr_buf); end else begin if (S_AXI_RREADY || S_AXI_ARVALID) begin // recovery from erroneous state S_AXI_ARREADY <= 1; S_AXI_RVALID <= 0; end end ////////////////////////////////////////////////////// code for write action reg ready = 0; reg [C_S_AXI_ADDR_WIDTH-1 : 0] awaddr_buf; reg [C_S_AXI_DATA_WIDTH-1 : 0] wdata_buf; reg [(C_S_AXI_DATA_WIDTH/8)-1 : 0] wstrb_buf; wire [C_S_AXI_DATA_WIDTH-1 : 0] strobe_expended; generate genvar i; for(i=0; i<C_S_AXI_DATA_WIDTH/8; i=i+1) begin:strobe_bits assign strobe_expended[i*8+7:i*8] = {8{wstrb_buf[i]}}; end endgenerate always @(posedge S_AXI_ACLK) if (!S_AXI_ARESETN) begin ready <= 0; S_AXI_BVALID <= 0; end else begin ready <= !ready && S_AXI_AWVALID && S_AXI_WVALID; S_AXI_BVALID <= (!ready && S_AXI_AWVALID && S_AXI_WVALID) || // raise (S_AXI_BVALID && !S_AXI_BREADY); // hold awaddr_buf <= S_AXI_AWADDR; wdata_buf <= S_AXI_WDATA; wstrb_buf <= S_AXI_WSTRB; if (ready) set_register(awaddr_buf, wdata_buf, strobe_expended); end assign S_AXI_AWREADY = ready; assign S_AXI_WREADY = ready; assign S_AXI_BRESP = 0; endmodule ** イレギュラーな条件からの復帰について [#gfeafa5c] ちゃんと考えると、結局 vivado が生成するような回路になる可能性があります??? * 上記 AXI4-Lite スレーブを試してみる [#h4200e41] AR# 56609: 2013.2 Vivado IPI、Zynq-7000 - IPI 外のカスタム AXI HDL を Zynq AXI インターフェイスに接続する方法~ http://japan.xilinx.com/support/answers/56609.html を参考に、design_1 から M_AXI ポートを外部へ引き出し、そこに上記スレーブを繋いでみますが・・・ むむ?なんか、うまく動きません。 Address Editor の動作あたり、ちゃんと理解しないと駄目かな??? * AXI4-Stream を試す [#maf47dc5] http://www.googoolia.com/wp/category/zynq-training/page/2/ によれば、AXI4-Stream を CPU モジュールに繋ぐには、 間に DMA コントローラを置いて、受け取ったデータを SDRAM へ流し込むのが定石のようです。 DMA コントローラとしては AXI DataMover という IP が汎用性の高いモジュールで、 AXI Central Direct Memory Access (AXI CDMA) というのはお手頃なモジュールのようです? * コメント・質問 [#r43d5138] #article_kcaptcha
Counter: 105211 (from 2010/06/03),
today: 33,
yesterday: 0