HDL/VivadoでAXIバスを利用 のバックアップの現在との差分(No.10)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

#contents

* AXI バス [#zf8590a4]

Xilinx の資料によれば、

> AXI は、[[AMBA (Advanced Microcontroller Bus Architecture) 4 仕様>http://www.amba.com/]] 
> に基づいて標準化 された IP インターフェイスプロトコルです。
> AXI は、[[AMBA (Advanced Microcontroller Bus Architecture) 4 仕様>http://www.amba.com/]] に基づいて標準化 された IP インターフェイスプロトコルです。

とのことで、例えば Zynq に内蔵された ARM プロセッサと、ユーザーロジックと、の間などが
AXI バスで繋がれています。すなわち IP を自作したならば、AXI バスに繋がなければ、
CPU から利用できません。
AXI バスで繋がれています。すなわち、何か IP を自作したならば、AXI バスに繋げられるようにしなければ
その IP を CPU から利用することができません。

でも逆に、一旦 IP を AXI バス互換にしてしまえば Vivado 上の GUI を用いて
IP 通しを容易に接続できるなど、利点も多いようです。
IP 同士を容易に接続できるなど、利点も多いようです。

- http://www.em.avnet.com/en-us/design/trainingandevents/Documents/X-Tech%202012%20Presentations/XTECH_B_AXI4_Technical_Seminar.pdf
ということで、AXI バスを一通り使えるようになるよういろいろ調べました。

自作 IP を AXI-4 Lite バスに繋ぐための汎用コードは [[こちらです。>#m1b39907]]
* 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 バスはマスターとスレーブとを繋ぐ、1対1のバスだそうです。
複数を繋ぐときには Interconnect の IP を間に挟むことになります。
- AXI(-Full): マスターからスレーブのメモリやレジスタを読み書きするためのフルスペックプロトコル
- AXI-Lite: マスターからスレーブのレジスタを読み書きするための低速だが軽量なプロトコル
- AXI-Stream: 単純にマスターからスレーブへデータを受け渡す高速かつ軽量なプロトコル

&ref(axi-bus-handshake.png,,50%);
細かい仕様は 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

※その後 AXI5 が策定され、現在 AXI4 は徐々に古いものになっていっているようです。

#twitter(1760112468788174982); 

** 基礎となるプロトコル [#v357bba4]

AXI バスを用いた通信では、マスターとスレーブとの間で様々なデータがやりとりされますが、
その基本となるのは DATA ライン、VALID ライン、READY ラインを用いた以下のようなプロトコルになります。
AXI ではデータ転送要求をするのは常にマスターですが、
実際のデータはマスターからスレーブへ送られることも、
スレーブからマスターへも送られることもあります。

AXI プロトコルにおけるデータ転送の基本となるのは、
DATA ライン、VALID ライン、READY ラインを用いた以下のようなプロトコルになります。

&ref(axi-bus-handshake.png,,50%,ogp);

- 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
注意点として、Valid が上がるのを見てから Ready を上げるのは問題ないですが、
Ready が上がるのを待って Valid を上げようとしてはいけません。
両者で待ち合いになってデッドロックが発生しかねません。

** AXI4-Lite と AXI4-Stream [#k2147da0]
** AXI4-Lite Slave と AXI4-Stream Master/Slave [#k2147da0]

CPU からレジスタを設定・読取するための AXI4-Lite と、~
IP と CPU との間でデータを転送するための AXI4-Stream を使いたいです。
CPU から IP のレジスタを設定・読取するための AXI4-Lite と、~
IP と CPU との間でデータを転送するための AXI4-Stream とを使いこなしたいです。

どちらも、上記の基本プロトコルを知っていれば動作の理解は難しくないようです。
あと、AXI4-Stream は最後に DMA を使って SDRAM を読み書きすることになるので、
DMA も使いこなしたい。

ちょっとかじったところ、AXI4-Lite と AXI4-Stream は、上記の基本プロトコルさえ知っていれば動作の理解は難しくないようです。

** AXI4-Lite における読み取り動作 [#taa54914]

AXI4-Lite ではバースト転送機能がないため、アドレスを送るとデータが帰ってくる、という簡単なプロトコルになっています。

+ マスターからアドレスを送ります
-- araddr[?:0], arvalid, arready を使います
-- 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 ______~~______
arready ______[~~]______
);~
&tchart(
rdata   =?=====X=0xbeef==X==?=
rresp   ====?==X00===X=?==
rvalid  ______~~~~____
rresp   ====?==X00===X=?==
rready  ________~~____
rready  ________[~~]____
);

** AXI4-Lite における書き込み動作 [#qb99af77]

こちらもバースト転送はないので、
マスターからアドレスとデータをそれぞれ別々のチャンネルから送ると、
スレーブから成否を返すという単純なプロトコルです。

+ マスターからアドレスを送ります
-- awaddr[?:0], awvalid, awready を使います
-- awaddr[?:0], awprot[2:0], awvalid, awready を使います
+ マスターからデータとストローブ信号を送ります
-- wdata[?:0], wstrb[?:0], wvalid, wready を使います
-- wstrb は wdata のうち実際に書き込む部位をバイト単位で指定します
-- アドレスと同時に送られることもあります
-- アドレスと同時に送られることもあります~
 → むしろデータが先に来る場合もあるそうです((2017-11-01 ZYNQ勉強会で教えていただきました))
+ スレーブから結果の成否を送ります
-- 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 ______~~______
awready ______[~~]______
);~
&tchart(
wdata   =?===X=0xbeef==X==?===
tstrb   =?===X=1111==X==?===
wvalid  ____~~~~______
wready  ______~~______
wready  ______[~~]______
);~
&tchart(
bresp   ====?==X00=X=?====
bvalid  ______~~______
bvalid  ______[~~]______
bready  ____~~~~______
);

** AXI4-Stream によるデータ転送 [#r768d77f]

アドレス指定も何もなく、単にマスターからスレーブへデータだけが送られます。
データをパケットに分けるための区切り情報も表すことができます。

+ マスターからスレーブへストローブ信号、データ区切り信号と共にデータを送ります。
- tdata[?:0], tstrb[?:0], tlast, tvalid, tready を使います
- tlast は1まとまりのデータの区切りを表します
- tlast はひとまとまりのデータの区切りを表します

例:

&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;

とすれば良いことになります。

配線やロジックの遅延が無視できないときは、
1クロックの遅延を入れるなどの工夫が必要になるかもしれません。

tuser などのオプショナルな線もあるので、
tlast で区切られるパケットを束ねて1まとまりにする際に
1まとまりの始めに立てるなどの使い方ができます。

ビデオ画像の送信では、
-画像の頭に tuser を立てる
-1ラインの終わりに tlast を立てる

というのが普通だそうです。((2017-11-01 ZYNQ勉強会で教えていただきました))

* Vivado の IP ひな形生成機構を利用する [#u6d63940]

AXI バス自身の細かい仕様は AMBA で定められています。
AXI バスに繋げられる IP を作るには IP 動作をバス仕様に合わせるだけでなく、
その IP を Vivado で扱えるようにするための作法に合わせる必要もあります。

- 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
すべてを一から作るのはいろんな仕様をすべて頭に入れなければならず大変なので、
手っ取り早く使うためには Vivado を使って IP のひな形を作成するのが良いようです。

いきなり仕様通りの IP を書くのは大変ですが、
手っ取り早く使うために、Vivado を使って IP のひな形を作成できるようです。
以下、その手順を追ってみます。

以下、まずはその手順を追ってみます。
** プロジェクトを作成 [#j4df389e]

* プロジェクトを作成 [#j4df389e]
IP を利用する側となるメインプロジェクトを作成します。

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]
** 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 用のプロジェクトが別途作成されます。
後々使うことになるのでぜひ作成しておくと良いです。
[Edit IP] とすることで IP 編集用のプロジェクトが別途作成されます。
後々使うことになるのでぜひ作成しておくと良い・・・のですが・・・

&ref(lite_slave_project.png,,50%);
これを選ぶと Vivado は ip_repo の直下にやたらとたくさんフォルダを
作ってしまうので、あとで非常にごちゃごちゃして嫌な感じです。
保存場所を選択できるようになっていれば良かったのに、
と思いました。

ここで作成したのような IP 用のプロジェクトでは、
むしろここでは 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]
** AXI4 Lite マスターとなる IP のひな形を作成する [#oc593fbf]

上と同様にして、"test_lite_master" という IP を作成しました。

[Flow Navigator]-[Project Manager] で [IP Catalog] を選ぶと、

&ref(lite_sm_generated.png,,50%);

2つの IP が追加されていることを確認できます。

* GUI を使って配置する [#n430d13d]
** 自動生成される 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]
** "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]
** 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]
** シミュレーションしてみる [#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 を作成しました。

始め、ひな形として作成した 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;
 	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]
* vivado が自動で作る AXI-Lite スレーブの動作を理解する [#h589575e]

AXI4-Lite スレーブ作成の例として、汎用 DIO モジュールを作成してみます。
** 読み出し動作 [#eadc9b69]

[Tool]-[Create and Package IP] から [new AXI4 peripheral] で
"AXI4_Lite_Slave_DIO" を作り、
AXI4_Lite_Slave_DIO_v1_0_S_AXI.v を次のように変更しました。
AXI4 の仕様で AXI4-Lite 読み出し時のスレーブ動作は次のように規定されています。

まず、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
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 を待たなくてもよい

 LANG:verilog
     assign odata0 = slv_reg0;
     assign odata1 = slv_reg1;
     assign odata2 = slv_reg2;
     assign odata3 = slv_reg3;
前の読み出しデータに rready を発行するタイミングと
次の読み出し用に arvalid を発行するタイミングが規定されていないので、
このあたりがコードでどのように書かれているか、興味があります。

として出力を繋ぎ、
自動作成されるのと同じ動作をするコードを掲載すると、

 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
   always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      S_AXI_ARREADY <= 0;
    end else 
    if (~S_AXI_ARREADY && S_AXI_ARVALID) begin
      S_AXI_ARREADY <= 1;
      axi_araddr  <= S_AXI_ARADDR;
    end else begin
      S_AXI_ARREADY <= 0;
    end 
 
  assign S_AXI_RRESP = 0;  // 'OKAY' response
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      S_AXI_RVALID <= 0;
    end else
    if (~S_AXI_RVALID && S_AXI_ARREADY && S_AXI_ARVALID) begin
      S_AXI_RVALID <= 1;
    end else 
    if (S_AXI_RVALID && S_AXI_RREADY) begin
      S_AXI_RVALID <= 0;
    end
 
  assign slv_reg_rden = S_AXI_ARREADY & S_AXI_ARVALID & ~S_AXI_RVALID;
 
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      S_AXI_RDATA  <= 0;
    end else 
    if (slv_reg_rden) begin
      S_AXI_RDATA <= 
        axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] == 0 ? slv_reg0 :
        axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] == 1 ? slv_reg1 :
        axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] == 2 ? slv_reg2 :
        axi_araddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] == 3 ? slv_reg3 :
                                                               0;
    end

として入力を繋げば完了です。
状態遷移図を書いてみると次のようになります。

さらにこれを IP の外に引き出さなければならないので、
module AXI4_Lite_Slave_DIO_v1_0 の方も、ポートに
&uml(
@startuml
skinparam state {
  BackgroundColor<<Error>> Pink
}

 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
[*] -right-> Idle
Idle : arready = 0
Idle : rvalid = 0
Idle : raddr = ?
Idle : rdata = ?
Idle --> Idle : [ ~arvalid ]
Idle -down-> Address_Ready : [ arvalid ]
Address_Ready: arready = 1
Address_Ready: rvalid = 0
Address_Ready: raddr = <valid>
Address_Ready: rdata = ?
Address_Ready: (arvalid = 1)
Address_Ready -right-> Data_Ready : [ 1 ]
Data_Ready: arready = 0
Data_Ready: rvalid = 1
Data_Ready: raddr = ?
Data_Ready: rdata = <valid>
Data_Ready --> Data_Ready : [ ~rready & ~arvalid ]
Data_Ready --> Idle : [ rready & ~arvalid ]
Data_Ready --> Address_Ready : [ rready & arvalid ]
Data_Ready --> Erroneous : [ ~rready & arvalid ]
state Erroneous<<Error>>
Erroneous: arready = 1
Erroneous: rvalid = 1
Erroneous: raddr = <new>
Erroneous: rdata = <old>
Erroneous: (arvalid = 1)
Erroneous --> Data_Ready : [ ~rready ]
Erroneous --> Idle : [ rready ]
);

を加え、
 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),
通常は Idle → Address_Ready → Data_Ready の3クロックで1回の読み出し動作になりますが、
注目すべきは Data_Ready から [ rready & arvalid ] のコンディションで直接
Address_Ready へ飛べることです。

のように繋ぎます。
実際に利用されることがあるのか不明ですが、原理的には下記タイミング図後半のように、
2クロックに1回の頻度で連続してデータを読み出せるのだと思います。
ただしこの場合にもレイテンシーは3クロックになります。

これで AXI4-Lite 経由で idataX, odataX へアクセスできるようになりました。
数では赤線がアドレス発行タイミング、黄色がデータ読み取りタイミングです。

** ドライバファイルについて [#led644e3]
&tchart(
clock   ~_~_~_~_~_~_~_~_~_~_~_~_
arvalid __|~~~~______|~~~~~~~~____
raddr   =?=X==1=X=?====X2===|X3===X?===
arready ____~~________~~__~~____
raddr_buf   ===?=X=1========X2===X3=====
rvalid  ______~~~~______~~__~~__
rden    ____~~________~~__~~____
rdata   =====?=X=d1========Xd2===Xd3===
rready  ________[~~]____~~[~~]~~[~~]__
);

AXI4_Lite_Slave_DIO.h
 LANG:C
 #include "xil_types.h"
 #include "xstatus.h"
Data_Ready で [ ~rready & arvalid ] としてしまうとエラーになります。
その後 Data_Ready, Idle どちらに遷移したとしても、正しいデータの読み出しは行えません。

つまり、前のデータ読み取りと次のアドレス送信を同時に行うことはできますが、
前のデータ読み取りに先行して次のアドレスを送ってはいけない、ということになります。

** 書き込み動作 [#qf781aeb]

AXI4 の仕様によれば、AXI4-Lite 書き込み時のスレーブ動作は以下の要求を満たす必要があります。

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 を立ててはいけない、つまり、立てっぱなしにはできないそうです。

もう一つ、データが送られるのとアドレスが送られるのとは独立で、
どちらが先に届くかわからない点です。とはいえ、どちらかに ready を立てないと
もう一方が送られないことはないのでしょうから、両方 valid が立ってから
ready を立てる動作で問題ないことになります。

 LANG:verilog
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      S_AXI_AWREADY <= 0;
    end else begin    
      if (!S_AXI_AWREADY && S_AXI_AWVALID && S_AXI_WVALID) begin
        S_AXI_AWREADY <= 1;
      end else begin
        S_AXI_AWREADY <= 0;
      end
    end 
 
 #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
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      axi_awaddr <= 0;
    end else begin    
      if (!axi_awready && S_AXI_AWVALID && S_AXI_WVALID) begin
        axi_awaddr <= S_AXI_AWADDR;
      end
    end 
 
 #define AXI4_LITE_SLAVE_DIO_mWriteReg(BaseAddress, RegOffset, Data) \
   	Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
        axi_wready <= 0;
    end else begin    
      if ( !axi_wready && S_AXI_WVALID && S_AXI_AWVALID) begin
        axi_wready <= 1;
      end else begin
        axi_wready <= 0;
      end
    end 
 
 #define AXI4_LITE_SLAVE_DIO_mReadReg(BaseAddress, RegOffset) \
     Xil_In32((BaseAddress) + (RegOffset))
  assign wen = S_AXI_READY && S_AXI_WVALID && axi_awready && S_AXI_AWVALID;
 
  integer byte_index;
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      slv_reg0 <= 0;
      slv_reg1 <= 0;
      slv_reg2 <= 0;
      slv_reg3 <= 0;
    end else
    if (wen) begin
      for ( byte_index = 0; byte_index <= (C_S_AXI_DATA_WIDTH/8)-1; byte_index = byte_index+1 ) begin
        case ( axi_awaddr[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB] )
          2'h0: if ( S_AXI_WSTRB[byte_index] == 1 )
                  slv_reg0[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
          2'h1: if ( S_AXI_WSTRB[byte_index] == 1 ) 
                  slv_reg1[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
          2'h2: if ( S_AXI_WSTRB[byte_index] == 1 ) 
                  slv_reg2[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
          2'h3: if ( S_AXI_WSTRB[byte_index] == 1 ) 
                  slv_reg3[(byte_index*8) +: 8] <= S_AXI_WDATA[(byte_index*8) +: 8];
          default : ;
        endcase
      end
    end
  end
 
  assign S_AXI_BRESP = 0;  // 'OKAY' response 
  always @( posedge S_AXI_ACLK )
    if ( !S_AXI_ARESETN ) begin
      S_AXI_BVALID  <= 0;
    end else 
    if (~S_AXI_BVALID && S_AXI_AWREADY && S_AXI_AWVALID && S_AXI_WREADY && S_AXI_WVALID) begin
      S_AXI_BVALID <= 1;
    end else 
    if (S_AXI_BVALID && S_AXI_BREADY) begin
      S_AXI_BVALID <= 0;
    end  

こんなマクロが定義されており、CPU から簡単にデータの読み書きができる雰囲気です。
(未確認)
&uml(
state Write {
[*] -right-> Idle
Idle: awready = 0
Idle: wready = 0
Idle: awaddr = ?
Idle: bvalid = 0
Idle: (wdata = ?)
Idle --> Idle : [ ~awvalid | ~wvalid ]
Idle --> Writing : [ awvalid & wvalid ] 
Writing: awready = 1
Writing: wready = 1
Writing: awaddr = <valid>
Writing: bvalid = 0
Writing: (awvalid = 1)
Writing: (wvalid = 1)
Writing: (wdata = <valid>)
Writing --> Idle
--
[*] -right-> Idle2
state "Idle" as Idle2
Idle2: bvalid = 0
Idle2 --> Idle2 : [ state != Writing ]
Idle2 --> Done : [ state == Writing ] 
Done: bvalid = 1
Done --> Done : [ ~bready ]
Done --> Idle2 : [ bready ]
}
);

* AXI4-Lite のスレーブを作る [#e32ac5c2]
動作は単純で、
- awvalid & wvalid が揃えば awaddr をバッファして Writing へ
~ Writing にてバッファされた awaddr と生の wdata, wstrb を使って書き込み
- Writing したら bvalid を立て、bready で下げる。
- strb により書き込みはバイト単位、ハーフワード単位などでも可能
-- 読み出しは常にワード単位でよい

vivado がはき出す AXI4-Lite スレーブのひな形コードがあまり分かりやすくなく、
無駄に遅延が入っているようにも思えるので、AXI4-Lite スレーブを一から作るために
タイミングと基本動作をおさらいしたいと思います。
となっている。

** ごめんなさい、現状では動作未確認です [#n7886c2c]
最短で2クロックに1データを書き込めることがわかります。

以下の記述はもっともらしく書いてありますが、実記での動作は未確認です。

追って動作確認を行います。

** AXI4-Lite 読み出し時のスレーブ動作 [#g12e8fbc]
** 少し単純化 [#m1b39907]

AXI4 の仕様では次のように規定されています。
マスター側のお行儀が良いことを前提とすれば、

A3.3.1 Dependencies between channel handshake signals
実際に読み出しを行う条件は arready だけ見れば十分。

- arvalid と arready
-- マスターは arvalid を立てるのに arready を待ってはならない
-- スレーブは arready を立てるのに arvalid を待ってよい
-- スレーブは arready を立てるのに arvalid を待たなくてもよい
- rvalid と rready
-- スレーブは rvalid を立てる前に arvalid と arready を待たなければならない
-- スレーブは rvalid を立てるのに rready を待ってはならない
-- マスターは rready を立てるのに rvalid を待ってよい
-- マスターは rready を立てるのに rvalid を待たなくてもよい
また書き込みについても

スレーブ側が必ず次のクロックでデータを返せる場合の例。
- awready と wready は同じ動作
- awaddr だけでなく wdata, wstrb もキャッシュすれば ready だけ見て書き込んで構わない
- !ready && bready で bvalid をクリアする

&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
 
     localparam integer ADDR_LSB = $clog2(C_S_AXI_DATA_WIDTH/8);
     localparam integer OPT_MEM_ADDR_BITS = 2;
   
     // 読み出し動作
   
     reg [OPT_MEM_ADDR_BITS-1 : 0]  S_AXI_ARADDR_reg;
     reg [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_RDATA_reg;
     reg S_AXI_ARREADY_reg;
     reg S_AXI_RVALID_reg;
    
     assign S_AXI_ARREADY = S_AXI_ARREADY_reg;
     assign S_AXI_RDATA   = read_data;
     assign S_AXI_RVALID  = S_AXI_RVALID_reg;
     assign S_AXI_RRESP   = 0;
    
     always @(posedge S_AXI_ACLK)
       if (!S_AXI_ARESETN) begin
         S_AXI_ARREADY_reg <= 0;
         S_AXI_RVALID_reg  <= 0;
       end else begin
         if (!S_AXI_ARREADY_reg && S_AXI_ARVALID) begin
           S_AXI_ARREADY_reg <= 1;
           S_AXI_ARADDR_reg <= S_AXI_ARADDR[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB];
         end else begin
           S_AXI_ARREADY_reg <= 0;
         end
    
         if (S_AXI_ARREADY_reg) begin
           set_read_addr(S_AXI_ARADDR_reg);
           S_AXI_RVALID_reg <= 1;
         end else
         if (S_AXI_RREADY) begin
           S_AXI_RVALID_reg <= 0;
         end
       end
 

 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;
 LANG:verilog
     // 書き込み動作
   
     reg [OPT_MEM_ADDR_BITS-1 : 0] S_AXI_AWADDR_reg;
     reg S_AXI_READY_reg;
     reg S_AXI_BVALID_reg;
     reg [C_S_AXI_DATA_WIDTH-1 : 0] S_AXI_WDATA_reg;
     reg [(C_S_AXI_DATA_WIDTH/8)-1 : 0] S_AXI_WSTRB_reg;
     
     assign S_AXI_AWREADY = S_AXI_READY_reg;
     assign S_AXI_WREADY  = S_AXI_READY_reg;
     assign S_AXI_BVALID  = S_AXI_BVALID_reg;
     assign S_AXI_BRESP   = 0;
    
     always @(posedge S_AXI_ACLK)
       if (!S_AXI_ARESETN) begin
         S_AXI_READY_reg  <= 0;
         S_AXI_BVALID_reg <= 0;
         initialize_data();
       end else begin
         if (!S_AXI_READY_reg && S_AXI_AWVALID && S_AXI_WVALID) begin
           S_AXI_READY_reg <= 1;
           S_AXI_AWADDR_reg <= S_AXI_AWADDR[ADDR_LSB+OPT_MEM_ADDR_BITS:ADDR_LSB];
           S_AXI_WDATA_reg <= S_AXI_WDATA;
           S_AXI_WSTRB_reg <= S_AXI_WSTRB;
         end else begin
           S_AXI_READY_reg <= 0;
         end
    
         write_data(S_AXI_AWADDR_reg, S_AXI_WDATA_reg, S_AXI_READY_reg ? S_AXI_WSTRB_reg : 0);
         if (S_AXI_READY_reg) begin
           S_AXI_BVALID_reg <= 1;
         end else
         if (S_AXI_BREADY) begin
           S_AXI_BVALID_reg <= 0;
         end
       end
 
     // ここより上は汎用コード

のようにすれば最短2クロックで読み出し動作を繰り返せるスレーブになります。
 LANG:verilog
     // 下の部分だけ変えればほとんどの用途に対応可能?
 
     reg [C_S_AXI_DATA_WIDTH-1:0]  reg0 = 0;
     reg [C_S_AXI_DATA_WIDTH-1:0]  reg1 = 0;
     reg [C_S_AXI_DATA_WIDTH-1:0]  reg2 = 0;
     reg [C_S_AXI_DATA_WIDTH-1:0]  reg3 = 0;
 
     reg [C_S_AXI_DATA_WIDTH-1:0]  read_buff;
     task set_read_addr;
       input [OPT_MEM_ADDR_BITS : 0] addr;
       begin
         read_buff <= addr == 0 ? reg0 :
                      addr == 1 ? reg1 :
                      addr == 2 ? reg2 :
                      addr == 3 ? reg3 : 0;
       end
     endtask
 
     assign read_data = read_buff;
 
     task initialize_data;
       begin
         reg0 <= 0;
         reg1 <= 0;
         reg2 <= 0;
         reg3 <= 0;
       end
     endtask
 
     task write_data;
       input [OPT_MEM_ADDR_BITS : 0] addr;
       input [C_S_AXI_DATA_WIDTH-1:0] data;
       input [(C_S_AXI_DATA_WIDTH/8)-1 : 0] strb;
       integer i;
       begin
         for ( i = 0; i < C_S_AXI_DATA_WIDTH/8; i = i+1 )
           if (strb[i])
             case (addr)
               0: reg0[(8*i) +: 8] <= data[(8*i) +: 8];
               1: reg1[(8*i) +: 8] <= data[(8*i) +: 8];
               2: reg2[(8*i) +: 8] <= data[(8*i) +: 8];
               3: reg3[(8*i) +: 8] <= data[(8*i) +: 8];
             endcase
       end
     endtask

ただし、
*** レジスタの実際のビット幅が 8 bit の倍数でない場合 [#lb2a6544]

write_data の

 LANG:verilog
       rdata <= register[araddr];
         for ( i = 0; i < C_S_AXI_DATA_WIDTH/8; i = i+1 )
           if (strb[i])
             case (addr)
               0: reg0[(8*i) +: 8] <= data[(8*i) +: 8];
               1: reg1[(8*i) +: 8] <= data[(8*i) +: 8];
               2: reg2[(8*i) +: 8] <= data[(8*i) +: 8];
               3: reg3[(8*i) +: 8] <= data[(8*i) +: 8];
             endcase

この部分はマスターから送られてくる araddr 
をデータ選択回路にそのまま繋いでいるため、配線遅延が大きくなる可能性があります。
のところを、

この遅延のためにバスクロックが制限されてしまうようであれば、
一旦 araddr をバッファするために、arvalid から rready 
の間を1クロック空ける必要があります。
 LANG:verilog
         for ( i = 0; i < C_S_AXI_DATA_WIDTH; i = i+1 )
           if (strb[i >> 3])
             case (addr)
               0: if(i < DATA_WIDTH1) reg0[i] <= data[i];
               1: if(i < DATA_WIDTH2) reg1[i] <= data[i];
               2: if(i < DATA_WIDTH3) reg2[i] <= data[i];
               3: if(i < DATA_WIDTH4) reg3[i] <= data[i];
             endcase

&tchart(
aclk    ~_~_~_~_~_~_~_~_~_
araddr  ==?X=A1X===?X=A2X=?======
arvalid __~~____~~________
arready ~~~~____~~______~~
rdata   ======?X=D1X=?==X===D2X=?
rvalid  ______[~~]____~~[~~]__
rready  __~~~~~~~~____~~__
);
のようにするといいみたい。

この場合、rready と rvalid の時間変化は
*** BRAM とつなぐ場合 [#e6dd2a5a]

|rready|1|0|0|1|0|0|
|rvalid|0|0|1|0|0|1|
 LANG:verilog
     // 下の部分だけ変えればほとんどの用途に対応可能?
 
     assign mem_addr = S_AXI_READY ? S_AXI_AWADDR_reg : S_AXI_ARADDR_reg;
     assign mem_in   = S_AXI_WDATA_reg;
     assign mem_we   = S_AXI_WSTRB_reg;
     assign mem_en   = S_AXI_ARREADY_reg || S_AXI_READY_reg;
 
     task set_read_addr;
       input [OPT_MEM_ADDR_BITS : 0] addr;
       begin
         ;  // do nothing
       end
     endfunction
 
     assign read_data = mem_out;
 
     task initialize_data;
       begin
         ;  // do nothing
       end
     endtask
 
     task write_data;
       input [OPT_MEM_ADDR_BITS : 0] addr;
       input [C_S_AXI_DATA_WIDTH-1:0] data;
       input [(C_S_AXI_DATA_WIDTH/8)-1 : 0] strb;
       begin
         ;  // do nothing
       end
     endtask

のようになるため、
こうかな?

 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
     rvalid <= 1;
     rdata <= get_register(araddr_buf);
   end else begin
 //  if (rready) begin
     if (rready || arready) begin // recovery from erroneous state
       arready <= 1;
       rvalid <= 0;
     end
   end
後で試す。
** ドライバファイルについて [#led644e3]

とするのが良さそうです。
AXI4_Lite_Slave_test.h
 LANG:C
 #include "xil_types.h"
 #include "xstatus.h"
 
 #define AXI4_LITE_SLAVE_TEST_S_AXI_SLV_REG0_OFFSET 0
 #define AXI4_LITE_SLAVE_TEST_S_AXI_SLV_REG1_OFFSET 4
 #define AXI4_LITE_SLAVE_TEST_S_AXI_SLV_REG2_OFFSET 8
 #define AXI4_LITE_SLAVE_TEST_S_AXI_SLV_REG3_OFFSET 12
 
 #define AXI4_LITE_SLAVE_TEST_mWriteReg(BaseAddress, RegOffset, Data) \
   	Xil_Out32((BaseAddress) + (RegOffset), (u32)(Data))
 
 #define AXI4_LITE_SLAVE_TEST_mReadReg(BaseAddress, RegOffset) \
     Xil_In32((BaseAddress) + (RegOffset))

万一 arready && rvalid になってしまったときのことを考えて、
最後の判定を if (rready) ではなく if (rready || arready) 
としてみましたが、マスター側の状態が分からないので、
必ずしもこれが最良かどうか自信がありません。
こんなマクロが定義されており、CPU から簡単にデータの読み書きができるようになっています。

この回路では最高で3クロックに一回読み込み動作が可能になります。
しかも Xil_Out32 や Xil_In32 の中身は単なるメモリアクセスなんですね。

** AXI4-Lite 書き込み時のスレーブ動作 [#af224058]
プロジェクト中で IP を利用した場合、これらのファイルは正しく SDK に引き継がれるようでした。
このあたりの仕組みもあとで勉強したいところです。

AXI4 の仕様によれば、以下の要求を満たす必要があります。
** 汎用ドライバを使い Linux 上からスレーブの動作を確認する [#f99a0ee5]

A3.3.1 Dependencies between channel handshake signals
[[電気回路/zynq/AXI4-LiteスレーブIPの動作テスト]] 
の手順で Linux 上のソフトから読み書きできることをテストしました。

+ マスターは 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 を待たなくてもよい
* AXI4-Stream を試す [#maf47dc5]

気をつけなければならないのは 4. ですね。
wvalid と wready の両方を確認してからでないと
bvalid を立ててはいけないそうです。
http://www.googoolia.com/wp/category/zynq-training/page/2/

データが送られる前に必ずアドレスが送られるのかどうか、
つまり wvalid が立つ前に必ず awvalid が立つのかどうか、
上では明記されていないのですが、
A1.3 AXI Architecture のところには
によれば、AXI4-Stream を CPU モジュールに繋ぐには、
間に DMA コントローラを置いて、受け取ったデータを SDRAM へ流し込むのが定石のようです。

>The AXI protocol permits address information to be issued ahead of the actual data transfer
DMA コントローラとしては AXI DataMover という IP が汎用性の高いモジュールで、
AXI Central Direct Memory Access (AXI CDMA) というのはお手頃なモジュールのようです?

と書かれていますし、Figure A1-2 でも Address and control が Write data に先行していますので、
恐らく awvalid の前に wvalid が立つことは無いと思われます。
** AXI-Stream マスター IP [#m1c45640]

すると、スレーブ側が書き込み要求をいつでも受け取れる場合、
下図のように wvalid が立つまで awready を上げないようにして、
awvalid && wvalid を待ってから処理すると実装が楽そうです。
IP 側で生成したデータを CPU の管理するメモリに流し込む方向に連続データを転送する IP です。

&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  ~~~~~~~~____~~~~~~~~~~
*** カスタム IP 作成 [#sf57016a]

bvalid_hold ____________~~________
);
[Tools]-[Create and Package New IP...] から smastertest という IP を作り、
AXI-Stream マスターポートを持たせました。

この場合、awready, wready は wvalid に連動して動かせばよく、
書き込み動作も wvalid に同期します。
&ref(create-smastertest.png,,66%);

bvalid は bready を待たなければなりませんので、
bvalid && !bready の条件で値を保持します。
- 0 から順にインクリメントする
- 下位8ビットが 0xff の時に tlast を立てる

という動作をさせます。

 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;
    reg [C_M_AXIS_TDATA_WIDTH-1 : 0] M_AXIS_TDATA_reg;
    assign M_AXIS_TSTRB = 4'b1111;
    reg M_AXIS_TLAST_reg;
    reg M_AXIS_TVALID_reg;
    
    assign M_AXIS_TDATA = M_AXIS_TDATA_reg;
    assign M_AXIS_TLAST = M_AXIS_TLAST_reg;
    assign M_AXIS_TVALID = M_AXIS_TVALID_reg;
 
    always @(posedge M_AXIS_ACLK)
      if (!M_AXIS_ARESETN) begin
        M_AXIS_TDATA_reg <= 0;
        M_AXIS_TLAST_reg <= 0;
        M_AXIS_TVALID_reg <= 0;
      end else begin
        // データ読み取り可能
        M_AXIS_TVALID_reg <= 1;
             
        // 読まれたら更新する
        if (M_AXIS_TREADY) begin
          M_AXIS_TDATA_reg <= M_AXIS_TDATA_reg + 1'b1;
          // 256ワードごとに tlast を立てる
          M_AXIS_TLAST_reg <= (M_AXIS_TDATA_reg & 'hff) == 'hfe;
        end
      end

ただ、この回路ではマスターからバスラインを通って届いた wvalid 
をバスラインを通じてマスターに返すことになるため、配線遅延が大きくなる心配があります。
*** コネクション [#u356e205]

また、registers への書き込みにマスターから届いた awaddr や wdata 
をそのまま使っているのも配線遅延的に不利になります。
デザインに追加し、M00_AXIS ポートから S_AXI_HP0 へドラッグし線で結ぶと、
直接はつなげないので DMA コントローラを追加して良いか、と聞かれます。

この遅延が問題になるようであれば、
バッファを入れて、1クロックの遅延を許容する必要があります。
&ref(smaster-added.png);

wvalid, awaddr, wdata をバッファすることにより、これらの遅延が影響しなくなります。
OK すると自動的に必要な IP (axi_dma, axi_mem_intercon) を追加してくれますが、
さらに左上にアシスタンスが出るので、Run Connection Automation します。

&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  ~~~~~~~~____~~~~~~~~~~
&ref(connection-automation.png,,66%);

ready   ______~~__~~______~~__
awaddr_buf  ====?X=A1====X=A2==X=?X=A3====
wdata_buf   ======?X=D1==X=D2==X=?==X=D3==
);
すると axi_periph から axi_dma の AXI-Lite ポートとへの接続が追加されました。
さらにリセット線も追加されています。

 LANG:verilog(linenumber)
 always @(posedge aclk)
   if (!resetn) begin
     ready <= 0;
     bvalid <= 0;
   end else begin
     ready <= !ready && awvalid && wvalid;
     bvalid <= (!ready && awvalid && wvalid) ||
               (bvalid && !bready);
     awaddr_buf <= awaddr;
     wdata_buf <= wdata;
     if (ready)
       set_register(awaddr_buf, wdata_buf);
   end
 assign awready = ready;
 assign wready = ready;
 assign bresp = 0;
Regenerate Layout した後の図がこちらです。

実は2行目は、本来なら次のように wvalid だけを待てば良いのですが、
&ref(dma-registers-are-accessible.png);

 LANG:verilog
   ready <= !ready && wvalid;
*** 割り込み設定 [#d317205c]

前者のようにすることで、万一 !awvalid のまま wvalid が立ったとしても、
不用意な書き込みをしてしまわないようになっています。
転送終了を表すために割り込みを設定します。

この回路は最短で2クロックに1回データを書き込めます。
Processing System の IRQ_F2P につながっている xlconcat をダブルクリックして
[Numbers of Ports] を 4 から 5 に増やします。
IRQ_F2P ポートの幅はすぐには反映されませんが、いずれかのタイミングで勝手に広がってくれるので特に編集の必要はありません。

** wstrb について [#i5343809]
&ref(numbers-of-ports.png,,66%);

ストローブ信号は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
増えたポートに axi_dma の s2mm_intout をつなぎます。
s2mm は stream to memory のことでしょうけれど、余分な m は何だろう???

とすることで、wstrb_buf を広げて strobe_expended を作成できます。
&ref(connect-dma-intout.png,,66%);

これを使って、

 LANG:verilog
 register <= (wdata & strobe_expended) | (register & ~strobe_expended);
*** Bitstream 生成 [#a8bfd7e5]

とすれば、必要なバイトだけを更新できるはずです。
Validate Design すると、これで問題ないと言われました。~
[Generate Block Design] の後、[Generate Bitstream] します。

** 合わせると [#qbc45efb]
*** アドレス確認 [#v2f4710c]

例えば AXI4-Lite に繋げられる汎用 DIO を以下のように作れます。
&ref(smaster-address-editor.png,,66%);

get_register と set_register を書き換えるだけで
様々な AXI4-Lite スレーブを作成できるようになっています。
dma コントローラは 0x40400000 にある。

 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	= 4
 ) (
   // 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 == 0 ? idata0 :
       addr == 4 ? idata1 :
       addr == 8 ? idata2 :
       addr == 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_ADDR_WIDTH-1 : 0] strobe;
   begin
     case(addr)
     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
 
 //////////////////////////////////////////////////////
 
 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 (rready) begin
     if (S_AXI_RREADY || S_AXI_ARREADY) begin // recovery from erroneous state
       S_AXI_ARREADY <= 1;
       S_AXI_RVALID <= 0;
     end
   end
   
 reg valid_buf = 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
     valid_buf <= 0;
     S_AXI_BVALID <= 0;
   end else begin
     valid_buf <= !valid_buf && S_AXI_AWVALID && S_AXI_WVALID;
     S_AXI_BVALID <= (!valid_buf && S_AXI_AWVALID && S_AXI_WVALID) ||
                     (S_AXI_BVALID && !S_AXI_BREADY);
     awaddr_buf <= S_AXI_AWADDR;
     wdata_buf <= S_AXI_WDATA;
     wstrb_buf <= S_AXI_WSTRB;
     if (valid_buf)
       set_register(awaddr_buf, wdata_buf, strobe_expended);
   end
 
 assign S_AXI_AWREADY = valid_buf;
 assign S_AXI_WREADY = valid_buf;
 assign S_AXI_BRESP = 0;
 
 endmodule
[[電気回路/z-turn/基本事項#r4d5943a]] によれば、
0x00000000-0x3fffffff は SDRAM の全範囲をカバーしている。

** イレギュラーな条件からの復帰について [#gfeafa5c]
*** ソフトウェアからアクセスするには [#m3fe86f6]

ちゃんと考えると、結局 vivado が生成するような回路になる可能性があります・・・
サードパーティー製のドライバを使い、Linux 上のアプリケーションで
DMA によりデータを受け取ることができました。

→ [[電気回路/zynq/DMA処理]]

* コメント・質問 [#r43d5138]

#article_kcaptcha
**s2mmのmm [#t2cdd2e9]
>[[7of9]] (&timetag(2021-12-01T23:52:55+09:00, 2021-12-02 (木) 08:52:55);)~
~
> s2mm は stream to memory のことでしょうけれど、余分な m は何だろう???~
~
すでにご存知かもしれませんが、memory mapのことでしょうね。~
~
記事参考にさせていただいています。~
ありがとうございます。~

//

#comment_kcaptcha

**AXI_Full [#od34dd23]
>[[伊藤康彦]] (&timetag(2019-02-22T02:48:27+09:00, 2019-02-22 (金) 11:48:27);)~
~
とてもいい記事ですね。AXIに自作IPを接続するのに悩んでいる方々には、~
一気に問題が解決する素晴らしい内容です。~
~
当方も数年前に同様の内容で悩んでおりまして、AXI-Fullのひな型をかなり時間をかけて修正して AXI_mem_intercon に自作IPを直接繋いでいましたが、AXI-stream で user IP を作成して、DMA を自動生成させるというのは、大幅な設計時間短縮になりますね。~
~
たいへん参考になりました。 ~
~
ありがとうございました。~

//

#comment_kcaptcha

**ドライバファイルについて [#i617e606]
>[[とんとん]] (&timetag(2016-05-09T10:58:09+09:00, 2016-05-09 (月) 19:58:09);)~
~
きっとご存知かもしれませんが、SDKに関してポインタでアドレスを指定すれば、ドライバファイルのAPI関数を使わなくても独自で作ったAXI IPのレジスタにアクセスできます。~

//
- はい、そのようですね。「こういう風にドライバを作ったらいいのでは?」というサジェスチョンと捕えるのが良いのでしょうか。 -- [[武内 (管理人)]] &new{2016-05-09 (月) 20:02:50};
- 貴重なレポートありがとうございます。とても参考になります。 -- [[伊藤康彦]] &new{2019-02-22 (金) 11:37:37};

#comment_kcaptcha


Counter: 105080 (from 2010/06/03), today: 11, yesterday: 0