HDL/VivadoでAXIバスを利用 のバックアップソース(No.17)

更新

[[公開メモ]]

#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 同士を容易に接続できるなど、利点も多いようです。

- "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: 単純にマスターからスレーブへデータを受け渡す高速かつ軽量なプロトコル

** 基礎となるプロトコル [#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 が同時に立った時(上の図で黄色くハイライトされたクロック)
に送受信が成立する、という規則が重要です。

このあたり、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 Slave と AXI4-Stream Master/Slave と AXI4-Full Master [#k2147da0]

CPU からレジスタを設定・読取するための AXI4-Lite と、~
IP と CPU との間でデータを転送するための AXI4-Stream と、~
AXI4-Stream の結果を SDRAM へ読み書きする AXI4-Full Master を使いたいです。

SDRAM の読み書きに DMA コンポーネントを使うなら
AXI4-Full Master は必要ないかもしれません。

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 はアクセス許可情報を表すための信号です。
特にアクセス制限の必要がなければ 0b000 で良いようです。

例:

&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 はアクセス許可情報を表すための信号です。
特にアクセス制限の必要がなければ 0b000 で良いようです。


例:

&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 は1まとまりのデータの区切りを表します

例:

&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 バス自身の細かい仕様は 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

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 が追加されていることを確認できます。

** 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クロックかかっているようです。

* 自動生成される 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 のレジスタに順に値を書き込むコードが生成されています。

書き込み手順を変えれば任意の IP と繋げられるはずですが、
ちょっと複雑なので理解するのに時間が掛かります。。。

* AXI4 Lite Master を読む [#za72c469]

シミュレーション時に AXI4 Lite バス経由で IP を操作したいので、
AXI4 Lite Master の動作を理解するためソースをしっかり読んでみようと思います。

** メインのステートマシン [#a85b463d]

: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 の動作 [#lbcf8890]

READ の動作も WRITE の動作と似ているので、
WRITE が理解できればほぼ全てを理解できるようでした。

*** メインロジック [#t562517a]

 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 は
- アドレス線出力中でなく
- データ選出力中でなく
- 書き込み完了待ちでなく
- 書き込み開始フラグが立っておらず
- 引き続きの書き込み要求がない

という条件を表している。

*** アドレス線 [#icb83e02]

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

*** データ線 [#p0fb2a6a]

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

*** 書き込み完了 [#f996255d]

 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]);

*** 書き込みデータ数のカウント [#v349921b]

 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 を作成する [#h72e26ea]

シミュレーション時に 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'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 の時間変化は

|rready|1|0|0|1|0|0|
|rvalid|0|0|1|0|0|1|

のようになるため、

 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]

z-turn ボードの Hello World サンプルコードを元にして、

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: 104341 (from 2010/06/03), today: 33, yesterday: 0