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

更新


公開メモ

AXI バス

Xilinx の資料によれば、

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

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

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

ということで、AXI バスを一通り使えるようになるよういろいろ調べました。

AXI バスの仕様

AXI バスには3つの規格があり、それぞれ AXI(-Full), AXI-Lite, AXI-Stream と呼ばれます。

どの規格も、マスターとスレーブとを繋ぐ1対1のバスになっていますので、 複数の機器を繋ぐときには Interconnect の IP を間に挟むことになります。

axi-bus-variations.png

  • AXI(-Full): マスターからスレーブのメモリやレジスタを読み書きするためのフルスペックプロトコル
  • AXI-Lite: マスターからスレーブのレジスタを読み書きするための低速だが軽量なプロトコル
  • AXI-Stream: 単純にマスターからスレーブへデータを受け渡す高速かつ軽量なプロトコル

細かい仕様は AMBA で定められています。

基礎となるプロトコル

AXI ではデータ転送要求をするのは常にマスターですが、 実際のデータはマスターからスレーブへ送られることも、 スレーブからマスターへも送られることもあります。

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

axi-bus-handshake.png

  • 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

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

あと、AXI4-Stream は最後に DMA を使って SDRAM を読み書きすることになるので、 DMA も使いこなしたい。

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

AXI4-Lite における読み取り動作

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

  1. マスターからアドレスを送ります
    • araddr[?:0], arprot[2:0], arvalid, arready を使います
  2. スレーブが成否のステータスと共にデータを送ります
    • 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 における書き込み動作

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

  1. マスターからアドレスを送ります
    • awaddr[?:0], awprot[2:0], awvalid, awready を使います
  2. マスターからデータとストローブ信号を送ります
    • wdata[?:0], wstrb[?:0], wvalid, wready を使います
    • wstrb は wdata のうち実際に書き込む部位をバイト単位で指定します
    • アドレスと同時に送られることもあります
  3. スレーブから結果の成否を送ります
    • 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 によるデータ転送

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

  1. マスターからスレーブへストローブ信号、データ区切り信号と共にデータを送ります。
  • 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 ひな形生成機構を利用する

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

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

以下、その手順を追ってみます。

プロジェクトを作成

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

new-project.png

[Create New Project] を選んで、

project-name.png

testing_axi4_lite という名前にしました。

rtl-project.png

RTL Project として、

main-sv.png

[Create File] から "main.sv" を追加します。

[Add Existing IP]、[Add Constraint] は飛ばして、

zynq-7020.png

Zynq 7020 を選択しました。

project-summary.png

[Finish] でプロジェクトが生成されます。

no-ports.png

ここではモジュール main にはポートを定義しません。

AXI4 Lite スレーブとなる IP のひな形を作成

create-ip-menu.png

[Tools]-[Create and Package IP...] を選択

create-ip.png

AXI4 ペリフェラル専用の Wizard があると書かれています。

create-axi4-peripheral.png

そちらを選択して Next

test_lite_slave.png

"test_lite_slave" という名前にしました。

lite_slave_config.png

設定・読み出し可能なレジスタを4つ持つ AXI4 Lite Slave IP を作成します。

IP のひな形の他、IP を ARM から使うためのドライバのひな形まで生成してくれます。

lite_slave_creation.png

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] を外しておかないと、 作成したプロジェクトは自動的に削除されてしまいますので注意して下さい。

not_delete_project.png

上記どちらかの要領で作成したような IP パッケージング用のプロジェクトでは、 [Flow Navigator] の [Project Manager] から [Package IP] を選択することで 右側にいろいろな設定項目が現れます。

lite_slave_project.png

IP ソースを変更したり、これらの設定を変更したら、 最後に [Review and Package] を押すと、IP が更新されます。

AXI4 Lite マスターとなる IP のひな形を作成する

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

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

lite_sm_generated.png

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

自動生成される IP の中身

AXI4 Lite Slave

4つのレジスタ slv_reg1, slv_reg2, slv_reg3, slv_reg4 を含んでいて、 AXI4 Lite インタフェースを用いて内容を読み書きできます。

これらのレジスタの値を既存の IP で読み取ったり、 レジスタの値の代わりに既存 IP からの出力を書き出すようにしたりすれば、 既存 IP を AXI4 Lite バスに接続することができます。

AXI4 Lite Master

AXI4 バス経由で、Slave のレジスタに対して順に値を書き込むコードが生成されています。

GUI を使って配置する

[Flow Navigator]-[IP Integrator]-[Create Block Design] から、

create_block_design.png

design_1 という Design を作成します。

add-lite-ips.png

[Add IP] から2つの IP を選んで Enter を押すと、

lite-ips-placed.png

並びが逆な感じですが、2つの IP が配置されました。

S_AXI から M_AXI までマウスカーソルをドラッグすると、 2つのポートの間を配線できます。

Ctrl+K で aclk, arstn, init を入力ポートとして、 done を出力ポートとして作成し、それぞれ適切に配線します。

design-routed.png

ごちゃごちゃしているので、 [Regenerate Layout] したところ、

layout-regenerated.png

見やすく配置されました。

"Address block is not mapped" というエラー

ところが、これで [Validate Design (F6)] したところ、

CRITICAL WARNING: [BD 41-1356] Address block </test_lite_slave_0/S_AXI/S_AXI_reg> 
is not mapped into </test_lite_master_0/M_AXI>. Please use Address Editor to either 
map or exclude it.

というエラーが出てしまいました。

言われたとおり、Address Editor でアドレスを割り当てます。

assign-address.png

右クリックから [Assign Address] を呼び、Range を最小の 4k にしました。

address-assigned.png

Diagram に戻り、 F6 したところ、

validation-successful.png

うまくいきました。

Verilog ソースとの統合

[Flow navigator]-[IP Integrator]-[Generate Block Design] から、

generate-block-design.png

[Out of context per Block Design] を選ぶと、

verilog-source-generated.png

design_1.v が生成されます。

中身は、

design_1.v

LANGUAGE:verilog
module design_1
   (aclk,
    arstn,
    done,
    init);
  input aclk;
  input arstn;
  output done;
  input init;
...

のように、Ctrl+K で作成した aclk, arstn, init, done などのポートが見えています。

これを main.v でインスタンス化することで利用できます。

シミュレーションしてみる

[Flow Navigator]-[Project Manager]-[Add Sources] から

add-simulation-source.png

[Add or create simulation sources] を選んで、

create-design_1_test.png

"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

このファイルをトップレベルに指定して、

set-as-top.png

[Run Simulation] すると、

axi4-lite-simulated.png

正しくシミュレーションできていることが分かります。

連続する書き込みに6クロックかかっているようです。

シミュレーション用の Master を作成する

シミュレーション時に 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 に繋ぐと、

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

実行結果は次のようになりました。

axi4_lite_master_bfm-test.png

1回の書き込みに6クロック掛かっており、これは Vivado で自動生成した AXI4 Lite Master IP と同じ動作になっています。

標準で作られる AXI-Lite スレーブの動作を理解する

読み出し動作

AXI4 の仕様で AXI4-Lite 読み出し時のスレーブ動作は次のように規定されています。

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

前の読み出しデータに rready を発行するタイミングと 次の読み出し用に arvalid を発行するタイミングが規定されていないので、 このあたりがコードでどのように書かれているか、興味があります。

自動作成されるのと同じ動作をするコードを掲載すると、

LANG:verilog
  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

状態遷移図を書いてみると次のようになります。

&uml( @startuml skinparam state {

 BackgroundColor<<Error>> Pink

}

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

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

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

数では赤線がアドレス発行タイミング、黄色がデータ読み取りタイミングです。

&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 _____[~~]_~~[~~]~~[~~]__ );

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

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

書き込み動作

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

A3.3.1 Dependencies between channel handshake signals

  1. マスターは awvalid や wvalid を立てるのに awready や wready を待ってはならない
  2. スレーブは awready や wready を立てるのに awvalid や wvalid を待ってもよい
  3. スレーブは awready や wready を立てるのに awvalid や wvalid を待たなくてもよい
  4. スレーブは bvalid を立てる前に awvalid, awready, wvalid, wready を待たなければならない
    (AXI4-Full のバースト転送では wlast も待たなければならない)
  5. スレーブは bvalid を立てるのに bready を待ってはならない
  6. マスターは bready を立てるのに bvalid を待ってもよい
  7. マスターは 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 

 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 

 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 

 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  

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

動作は単純で、

  • awvalid & wvalid が揃えば awaddr をバッファして Writing へ

    Writing にてバッファされた awaddr と生の wdata, wstrb を使って書き込み

  • Writing したら bvalid を立て、bready で下げる。
  • 書き込みはバイト単位、ハーフワード単位などでも可能なので、上記のようなアドレス計算が必要
    • 読み出しは常にワード単位でよい

となっている。

最短で2クロックに1データを書き込めることがわかります。

少し単純化

マスター側のお行儀が良いことを前提とすれば、

実際に読み出しを行う条件は arready だけ見れば十分。

また書き込みについても

  • awready と wready は同じ動作
  • awaddr だけでなく wdata, wstrb もキャッシュした方が良い
  • ready だけ見て書き込んで構わない
  • !ready && bready で bvalid をクリアする

ということで、以下のように簡略化できます。

LANG:verilog

    localparam integer ADDR_LSB = (C_S_AXI_DATA_WIDTH/32) + 1;
    localparam integer OPT_MEM_ADDR_BITS = 1;
  
    // 読み出し動作
  
    reg [OPT_MEM_ADDR_BITS : 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   = S_AXI_RDATA_reg;
    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
          S_AXI_RDATA_reg <= read_data(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
    // 書き込み動作
  
    reg [OPT_MEM_ADDR_BITS : 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
   
        if (S_AXI_READY_reg) begin
          write_data(S_AXI_AWADDR_reg, S_AXI_WDATA_reg, S_AXI_WSTRB_reg);
          S_AXI_BVALID_reg <= 1;
        end else
        if (S_AXI_BREADY) begin
          S_AXI_BVALID_reg <= 0;
        end
      end

    // ここより上は汎用コード
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;

    function [C_S_AXI_DATA_WIDTH-1:0] read_data;
      input [OPT_MEM_ADDR_BITS : 0] addr;
      begin
        read_data = addr == 0 ? reg0 :
                    addr == 1 ? reg1 :
                    addr == 2 ? reg2 :
                    addr == 3 ? reg3 : 0;
      end
    endfunction

    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

メモリとつなぐ場合

LANG:verilog
    // 下の部分だけ変えればほとんどの用途に対応可能?

    assign mem_addr = S_AXI_READY ? S_AXI_AWADDR_reg : S_AXI_ARADDR_reg;
    assign mem_strb = S_AXI_WSTRB_reg;
    assign mem_in   = S_AXI_WDATA_reg;
    assign mem_we   = S_AXI_READY;

    function [C_S_AXI_DATA_WIDTH-1:0] read_data;
      input [OPT_MEM_ADDR_BITS : 0] addr;
      begin
        read_data = mem_out;
      end
    endfunction

    task initialize_data;
      begin
        ;
      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
        ;
      end
    endtask

こうかな?

後で試す。

ドライバファイルについて

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

こんなマクロが定義されており、CPU から簡単にデータの読み書きができるようになっています。

しかも Xil_Out32 や Xil_In32 の中身は単なるメモリアクセスなんですね。

プロジェクト中で IP を利用した場合、これらのファイルは正しく SDK に引き継がれるようでした。 このあたりの仕組みもあとで勉強したいところです。

上記コードでスレーブの動作を確認する

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

AXI4-Stream を試す

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) というのはお手頃なモジュールのようです?

AXI-Stream マスター IP

IP 側で生成したデータを CPU の管理するメモリに流し込む方向に連続データを転送する IP です。

カスタム IP 作成

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

create-smastertest.png

  • 0 から順にインクリメントする
  • 下位8ビットが 0xff の時に tlast を立てる

という動作をさせます。

LANG:verilog
   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

コネクション

デザインに追加し、M00_AXIS ポートから S_AXI_HP0 へドラッグし線で結ぶと、 直接はつなげないので DMA コントローラを追加して良いか、と聞かれます。

smaster-added.png

OK すると自動的に必要な IP (axi_dma, axi_mem_intercon) を追加してくれますが、 さらに左上にアシスタンスが出るので、Run Connection Automation します。

connection-automation.png

すると axi_periph から axi_dma の AXI-Lite ポートとへの接続が追加されました。 さらにリセット線も追加されています。

Regenerate Layout した後の図がこちらです。

dma-registers-are-accessible.png

割り込み設定

転送終了を表すために割り込みを設定します。

Processing System の IPR_F2P につながっている xlconcat をダブルクリックして [Numbers of Ports] を 4 から 5 に増やします。

numbers-of-ports.png


増えたポートに util_vector_logic_2 を複製してできた util_vector_logic_3 をつなぎます。

&ref(): File not found: "copy-vector-logic-ip.png" at page "電気回路/HDL/VivadoでAXIバスを利用";


axi_dma の s2mm_intout から 上記 vector logic へつなぎます。 s2mm は stream to memory のことでしょうけれど、余分な m は何だろう???

connect-dma-intout.png

Bitstream 生成

Validate Design すると、これで問題ないと言われました。
[Generate Block Design] の後、[Generate Bitstream] します。

アドレス確認

smaster-address-editor.png

dma コントローラは 0x40400000 にある。

ソフトウェアに関する情報

http://www.fpgadeveloper.com/2014/08/using-the-axi-dma-in-vivado.html
にリンクのあった、
C:\Xilinx\SDK\(version)\data\embeddedsw\XilinxProcessorIPLib\drivers\axidma_v(ver)\examples\xaxidma_example_sg_poll.c
を探してみると、

SDK 2016.4 には axidma の v8_1, v9_0 ~ v9_3 用の examples があるのだけれど、
axi_dma IP のバージョンは 7.1 (Rev. 12) となっている・・・

えーと、exapmples のバージョンは Linux ドライバのバージョンかな???

xaxidma.h の冒頭に History があって、

* 9.0 adk 27/07/15 Added support for 64-bit Addressing.

* 9.0 adk 19/08/15 Fixed CR#873125 DMA SG Mode example tests are failing on

* HW in 2015.3.

* 9.1 sk 11/10/15 Used UINTPTR instead of u32 for Baseaddress CR# 867425.

* 9.3 adk 26/07/16 Reduce the size of the buffer descriptor to 64 bytes.

となっていて、特に 9.2 → 9.3 の変更は大きそう。

DMA ドライバの説明書き日本語訳

以下は AXI DMA エンジンドライバの API です。

DMA 機能の全詳細はハードウェアのスペックを参照してください。 このドライバでは次の機能を利用できます。

  • Scatter-Gather DMA (SGDMA)
  • Simple DMA
  • Interrupts
  • Programmable interrupt coalescing for SGDMA
  • APIs to manage Buffer Descriptors (BD) movement to and from the SGDMA engine

[Simple DMA]

Simple DMA API を使うと DMA とデバイスとの間の単一の転送処理を設定できます。 2つのチャンネルを利用可能で、1つは DMA からデバイスへ、もう1つはデバイスから DMA へのチャンネルになります。転送処理を開始するには対応するチャンネルに対応する バッファーアドレスとバッファー長さの2つのフィールドを設定する必要があります。

[転送処理]

転送処理を記述するのに使われる構造体は Buffer Descriptor (BD) と呼ばれます。 ユーザーアプリケーションは BD を malloc し、転送処理に用いるバッファーアドレス、 転送長、制御情報を設定します。制御情報は SOF や EOF を含みます。 これらの定数は xaxidma_hw.h に定義されています。

[Scatter-Gather DMA (SGDMA)]

SGDMA ではユーザーアプリケーションがメモリ上に次善に準備した一連の転送処理設定を、 ハードウェアが順に処理します。この処理中にユーザーアプリケーションが介在する 必要はありません。また、処理中に新たな処理内容を追加することも可能であり、 ハードウェアを常に働かせておくことができます。

ユーザーはハードウェアをポーリングする、あるいは割り込みを使うことで 転送処理の完了を知ることができます。

SGDMA はすべてのパケットを処理します。パケットは一続きのバイトデータで、1つの メッセージを表します。SGDMA を使えば1つのパケットを複数の転送処理に分割できます。 例えば、14バイトのヘッダーと、それに続く1バイト以上のペイロードからなるIP パケットを思い浮かべてください。SGDMA を使えばアプリケーションはヘッダーに対応する BD と、ペイロードに対応する BD とを指定して、それらを1つのメッセージとして転送処理 することができます。この方法を使うと TCP/IP スタックをより効率的に作成できます。 なぜならパケットヘッダーとデータを異なるメモリ領域においたまま処理できるからです。 そうでなければパケットを一つの連続するメモリブロック上に組み立て直さなければなりません。

[BD リングの管理]

BD リングはソフトとハードとで共用されます。

ハードウェアは BD が連結リストの形で提供されることを期待します。DMA ハードウェアは 1つの BD の処理が終わるごとにその next pointer フィールドの値をたどることでリストを 次々に処理します。そして、ハードウェアの Tail Ptr レジスタに指定された BD を処理したら 停止します。

BD リングでは最終 BD は最初の BD へリンクされています(ので、処理は自動では停止しません)。

BD の管理はすべてドライバ内で行われます。ユーザーアプリケーションは BD のフィールドを 直接変更すべきではありません。BD フィールドの変更は常に対応する API 関数を通じて 行うようにしてください。

BD リング中ではドライバーは4つの BD グループを管理します。それぞれのグループは 0 個以上の連続する BD から構成されます。

Free
アプリケーションが XAxiDma_BdRingAlloc() で確保可能な(現在使われていない)BD
Pre-process
XAxiDma_BdRingAlloc() で確保済みの BD。 これらの BD はアプリケーションの管理下にあります。 アプリケーションはドライバ API を用いて BD の内容を編集し、DMA 転送の準備をします。
Hardware
XAxiDma_BdRingToHw() でハードウェアに渡され、処理待ちになっている BD。 これらの BD はハードウェアの管理下にあり、処理を待ち、処理中、処理済み、 のいずれかの状態にあります。 この状態の BD をアプリケーションが変更することはできません。 変更した場合にはデータが壊れたり、システムが不安定になったりしかねません。
Post-process
XAxiDma_BdRingFromHw() により Hardware グループから取り出された処理済みの BD。 これらの BD はアプリケーションの管理下にあります。アプリケーションは BD の転送処理結果を確認することができます。アプリケーションは XAxiDma_BdRingFree() することで BD を Free グループに戻せます。

連続して転送処理を行うには、BD は下記のような状態変化をすることが期待されています。

&uml( [*]-r->Free Free: under Driver control Free: to be alloc for use Free-r->Preprocess : XAxiDma_BdRingAlloc() state "Pre-process" as Preprocess Preprocess: under Application control Preprocess: setup for Transaction Preprocess-d->Free :  XAxiDma_BdRingUnAlloc() Preprocess-d->Hardware :  XAxiDma_BdRingToHw() state Hardware { [*]-r->Queuing Queuing-->InProcess InProcess-->Processed Processed-l->[*] } Hardware-l->PostProcess :  XAxiDma_BdRingFromHw() state "Post-process" as PostProcess PostProcess: under Application control PostProcess: Transaction status can be checked PostProcess-u->Free :  XAxiDma_BdRingFree() );

Pre-process 段階で、DMA 転送をハードウェアのキューに入れる前にキャンセルするには、 アプリケーションは XAxiDma_BdRingUnAlloc() を呼んで BD を Free に戻すことができます。

API は BD リストを渡り歩くため、以下の関数を提供しています。

  • XAxiDma_BdRingNext()
  • XAxiDma_BdRingPrev()

これらの関数はどこでグループが終了し、次のグループが始まるかを認識しないため、 使う際には注意が必要です。

[SGDMA デスクリプタリングの作成]

BD リングは XAxiDma_BdRingCreate() で作成されます。BD リングのためのメモリは アプリケーションが割り当てます。この領域は連続していなければなりません。 BD リングの設定には物理アドレスが必要となります。

アプリケーションは XAxiDma_BdRingMemCalc() で所望の数の BD を用意するのに必要な メモリの大きさを計算できます。逆に、XAxiDma_BdRingCntCalc() により指定のメモリサイズに 何個の BD を格納可能か計算できます。

XAxiDma_BdRingClone() というヘルパ関数を使うことで、同じタイプの指示、例えば SOF や EOF を持つ、複数の BD を含む BD リングを高速に準備できます。 XAxiDma_BdRingClone() した後、アプリケーションはバッファアドレスと転送長のみ 設定すれば良いことになります。1つのパケットのうちいくつかの BD、例えば 最初と最後のものは、特別な制御情報を設定する必要があるかもしれません。

[デスクリプタリングのステートマシン]

BD リングには2つの状態があります。

  • HALTED (H) : ハードウェアは停止中です。
  • NOT HALTED (NH) : ハードウェアは動作中です。

DMA エンジンの状態遷移は次のようになります。

&uml( [*]-->HALTED HALTED-r->NOT_HALTED : StartBdRingHw() or BdRingStart() or Resume() HALTED: 停止中 NOT_HALTED-l->HALTED : Pause() or Reset() NOT_HALTED: 動作中 );

[割り込みの併合]

SGDMA は割り込み頻度を制御するために割り込みの併合機能を提供します。 DMA エンジンは割り込み併合を最適化するために2種類の方法を提供しています。

  • パケット臨界値カウンタ: エンジンにより指定された数のパケットが処理されるごとに1回割り込みを発生させます。
  • パケット遅延時間カウンタ: 最後のパケットが処理された後、新しいパケットが処理されないまま指定の時間が 経過したら1回割り込みを発生させます。最低1つのパケットが処理されないと 割り込みは発生しないことに注意してください。

[割り込み]

割り込みはユーザーアプリケーションで処理されます。 DMA チャンネルごとに割り込みIDを持っています。 ドライバは割り込みを許可・拒否し、また、パケット処理の頻度に応じて割り込み頻度を 最適化するための API を提供します。

[ソフトウェア初期化]

Simple mode DMA エンジンを使い転送を行うには、次の設定手順に従います。

  1. XAxiDma_CfgInitialize() により DMA を初期化します。 これにより指定された DMA エンジンに対するドライバインスタンスを初期化し、 エンジンをリセットします。
  2. 割り込みモードを使う場合、割り込みを許可します。 割り込みシステムを適切に設定するのはアプリケーションの責任です。 少なくとも、エンジンが実際に割り込みを発行する前に割り込みハンドラを用意し、 適切に登録することが必要です。
  3. 該当のチャンネルのバッファアドレスと転送長フィールドを設定し、DMA 転送を開始します。

SG モードで DMA エンジンを使い転送処理を行うには、次の手順に従います。

  • XAxiDma_CfgInitialize() にて DMA を初期化します。
  • BD リングを作成します。DMA チャンネルごとに XAxiDma_BdRingCreate(). により BD リングを作る必要があります。
  • 割り込みモードを使うのであれば割り込みを許可します。
  • DMA 転送を開始します: 一番始め、あるいはリセット後には XAxiDma_BdRingStart() により転送を開始します。チャンネルがすでに転送を開始しているなら、 XAxiDma_BdRingToHw() を使います。DMA チャンネルが動作していないときに XAxiDma_BdRingToHw() しても BD はハードウェアに送られません。かわりに、 後に DMA チャンネルが XAxiDma_BdRingStart() で開始されてから処理されます。

[DMA 転送処理の開始方法]

ユーザーアプリケーションは XAxiDma_BdRingToHw() により BD をハードウェアに送り、 DMA 転送を開始します。

どちら向きのチャンネルにおいても、DMA エンジンが(XAxiDma_Pause() により) 現在停止中であれば、新たに追加された BD は受理されるものの、 XAxiDma_BdRingStart() により DMA エンジンが転送を開始する、あるいは XAxiDma_Resume() により転送を再開するまで、処理されることはありません。

[DMA 転送が終了した後の処理]

割り込みが設定され、許可されたなら、DMA チャンネルは 転送終了をソフトウェアへ通知するのために割り込みを行います。 割り込みを使わない場合、ユーザーアプリケーションは BD の完了を XAxiDma_BdRingFromHw() あるいは XAxiDma_BdHwCompleted() によりポーリングすることもできます。

  • BD が DMA チャンネルにより処理済みとなったら、 アプリケーションはまずそれらを XAxiDma_BdRingFromHw() により回収する必要があります。
  • TX 側では、その時点でアプリケーションはその BD に関連づけられていた データバッファーを解放することができます。なぜならバッファー中のデータは すでに転送されたからです。
  • RX 側では、その時点でアプリケーションは BD に関連づけられたデータ バッファーのデータを利用可能になります。
  • どちらのチャンネルにおいても、処理済みの BD は XAxiDma_BdRingFree() により Free 状態に戻してやる必要があります。そうすることで以降の 転送処理で再利用することが可能になります。
  • RX 側では、アプリケーションは常にいずれかの DB がデータ受け入れ 可能な状態にあるよう手配する責任があります。そうでないと、その RX チャンネルはデータを受け入れられなくなってしまいます。

[使用例]

ドライバ API の使用方法を示すため、5つの例が挙げられています。

  • SG 割り込みモード (xaxidma_example_sg_intr.c)
    複数の BD/パケットを転送します
  • SB ポーリングモード (xaxidma_example_sg_poll.c)
    単一 の BD を転送をします
  • SB ポーリングモード (xaxidma_poll_multi_pkts.c)
    複数の BD/パケットを転送します
  • Simple ポーリングモード (xaxidma_example_simple_poll.c)
  • Simple 割り込みモード (xaxidma_example_simple_intr.c)

[アドレス変換]

ハードウェアに与えるすべてのバッファーアドレスおよび BD アドレスは物理アドレスです。

[キャッシュの整合性]

コメント・質問




ドライバファイルについて

とんとん? ()

きっとご存知かもしれませんが、SDKに関してポインタでアドレスを指定すれば、ドライバファイルのAPI関数を使わなくても独自で作ったAXI IPのレジスタにアクセスできます。

  • はい、そのようですね。「こういう風にドライバを作ったらいいのでは?」というサジェスチョンと捕えるのが良いのでしょうか。 -- 武内 (管理人)?

Counter: 105093 (from 2010/06/03), today: 24, yesterday: 0