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

更新


公開メモ

AXI バス

Xilinx の資料によれば、

AXI は、AMBA (Advanced Microcontroller Bus Architecture) 4 仕様

に基づいて標準化 された IP インターフェイスプロトコルです。

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

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

axi-bus-variations.png

AXI バスはマスターとスレーブとを繋ぐ、1対1のバスだそうです。 複数を繋ぐときには Interconnect の IP を間に挟むことになります。

axi-bus-handshake.png

基礎となるプロトコル

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

  • 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 と AXI4-Stream

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

どちらも、上記の基本プロトコルを知っていれば動作の理解は難しくないようです。

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

  1. マスターからアドレスを送ります
    • araddr[?: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

例:

&tchart( aclk ~_~_~_~_~_~_~_ araddr =?===X=0x3000==X=?==== arvalid _~~~~___ arready _~~_ rdata =?=====X=0xbeef==X==?= rvalid ___~~~~_ rresp ====?==X00===X=?== rready _____~~_ );

AXI4-Lite における書き込み動作

  1. マスターからアドレスを送ります
    • awaddr[?: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

例:

&tchart( aclk ~_~_~_~_~_~_~_ awaddr =?===X=0x3000==X=?==== awvalid _~~~~___ awready _~~_ wdata =?===X=0xbeef==X==?=== wvalid _~~~~___ wready _~~_ bresp ====?==X00=X=?==== bvalid _~~_ bready _~~~~___ );

AXI4-Stream によるデータ転送

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

例:

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

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

Vivado の IP ひな形生成機構を利用する

AXI バス自身の細かい仕様は AMBA で定められています。

いきなり仕様通りの IP を書くのは大変ですが、 手っ取り早く使うために、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 用のプロジェクトが別途作成されます。 後々使うことになるのでぜひ作成しておくと良いです。

lite_slave_project.png

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

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

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

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

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

lite_sm_generated.png

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

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

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

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

AXI4 Lite Master を読む

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

メインのステートマシン

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 の動作

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

メインロジック

LANGUAGE:verilog
if (writes_done) begin
  mst_exec_state <= INIT_READ;
end else
if (~axi_awvalid && ~axi_wvalid && ~M_AXI_BVALID  && 
        ~start_single_write && ~write_issued && 
        ~last_write) begin
  start_single_write <= 1;
  write_issued  <= 1;
end else 
if (axi_bready) begin
  write_issued  <= 0;
end else begin
  start_single_write <= 0;
end
  • axi_bready が立てば次のクロックで write_issued が降りる
  • axi_bready が立っていなければ次のクロックで start_single_write が降りる
  • 通常は start_single_write が立った次のクロックで axi_bready が立つことは無いので、 start_single_write のパルスは必ず1クロック幅になる

write_issued はここでしか参照されておらず、 start_single_write を立てた後、 まだ axi_bready が検出されていないことを表すフラグになっている。

last_write は続きの書き込み要求があるかどうかを表すフラグ。

  • アドレス線 = AWVALID を立てて AWREADY を待つ
  • データ線 = WVALID を立てて WREADY を待つ
  • 書き込み完了 = M_AXI_BVALID が立ったら BREADY を返す

と言う動作なので、上記の大きな if は

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

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

アドレス線

awvalid を立てて awready を待つ。

awready が立ったら4増やす。

LANGUAGE:verilog
 // axi_awvalid の設定
 // 有効な書き込みアドレスが出力されていることを示す
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin
     axi_awvalid <= 0;
   end else 
   if (start_single_write) begin
     axi_awvalid <= 1;
   end else 
   if (axi_awvalid && M_AXI_AWREADY) begin
     axi_awvalid <= 0;
   end
 end
 
 // 書き込みアドレス
 assign M_AXI_AWADDR   = C_M_TARGET_SLAVE_BASE_ADDR + axi_awaddr;
 
 // axi_awaddr の設定
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0  || init_txn_pulse == 1) begin
     axi_awaddr <= 0;
   end else 
   if (axi_awvalid && M_AXI_AWREADY) begin
     axi_awaddr <= axi_awaddr + 32'h00000004;
   end
 end

データ線

WVALID を立てて WREADY を待つ

LANGUAGE:verilog
 // axi_wvalid の設定
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0  || init_txn_pulse == 1) begin
     axi_wvalid <= 0;
   end else 
   if (start_single_write) begin
     axi_wvalid <= 1;
   end else 
   if (axi_wvalid && M_AXI_WREADY) begin
     axi_wvalid <= 0;
   end
 end
 
 // axi_wdata の設定
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0 || init_txn_pulse == 1 ) begin
     axi_wdata <= C_M_START_DATA_VALUE;
   end else
   if (M_AXI_WREADY && axi_wvalid) begin
     axi_wdata <= C_M_START_DATA_VALUE + write_index;
   end
 end

書き込み完了

LANGUAGE:verilog
 // axi_bready の設定
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin
     axi_bready <= 0;
   end else 
   if (~axi_bready && M_AXI_BVALID) begin
     axi_bready <= 1;
   end else begin
     axi_bready <= 0;
   end
 end
 
 // Flag write errors
 assign write_resp_error = (axi_bready & M_AXI_BVALID & M_AXI_BRESP[1]);

書き込みデータ数のカウント

LANGUAGE:verilog
 // write_index の設定
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin
     write_index <= 0;
   end else 
   if (start_single_write) begin
     write_index <= write_index + 1;
   end
 end
 
 // last_write の設定
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin
     last_write <= 0;
   end else 
   if ((write_index == C_M_TRANSACTIONS_NUM) && M_AXI_AWREADY) begin
     last_write <= 1;
   end
 end
 
 // writes_done の設定
 // 書き込み完了は M_AXI_BVALID && axi_bready で判定する
 always @(posedge M_AXI_ACLK) begin
   if (M_AXI_ARESETN == 0 || init_txn_pulse == 1) begin
     writes_done <= 0;
   end else 
   if (last_write && M_AXI_BVALID && axi_bready) begin
     writes_done <= 1;
   end
 end

ふむ、これを変更して何かするのは大変そうですね・・・

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

シミュレーション時に 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'b001;

  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 と同じ動作になっています。

汎用 DIO スレーブを作成する

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 へアクセスできるようになりました。

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

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 から簡単にデータの読み書きができる雰囲気です。 (未確認)

** AXI4-Lite のスレーブを作る

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

AXI4-Lite 読み出し時のスレーブ動作

スレーブ側が必ず次のクロックでデータを返せる場合の例。

&tchart( aclk ~_~_~_~_~_~_~_~_ araddr ==?X=A1X=?X=A2X=?X=A3X=?== arvalid ~~~~~~__ arready ~~~~~~~~__~
rdata ====?X=D1X=?X=D2X=?X=D3X=? rvalid __[~~][~~][~~] rready ~~~~~~~~~~~~ );

arready = !rvalid の関係があるので、

LANG: verilog
always @(posedge aclk)
  if (rst) begin
    arready <= 1;
  end else
  if (arready) begin
    if (arvalid) begin
      rdata <= registers[araddr];
      arready <= 0;
    end
  end else begin
    if (rready) begin
      arready <= 1;
    end
  end
assign rvalid = !arready;

のようにすれば最短2クロックで読み出し動作を繰り返せるスレーブになります。

LANG:verilog
      rdata <= register[araddr];

この部分の遅延のためにバスクロックが制限されてしまうようであれば、 一旦 araddr をバッファするために、arvalid から rready の間を1クロック空けることになるかもしれません。

&tchart( aclk ~_~_~_~_~_~_~_~_ araddr ==?X=A1X===?X=A2X=?==== arvalid __~~_~~___ arready ~~~~_~~___ rdata ======?X=D1X=?==X=D2X=? rvalid ___[~~]_[~~]__ rready ~~~~~~~~~~~~ );

この場合、rready と rvalid との関係は

rready100100
rvalid001001

のようになるため、

LANG: verilog
always @(posedge aclk)
  if (rst) 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 <= registers[araddr_buf];
  end else begin
//  if (rready) begin
    if (rready || arready) begin // recovery from erroneous state
      arready <= 1;
      rvalid <= 0;
    end
  end

とするのが良さそうです。

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

この形であればロジック遅延の大きなパスはありませんので、 バスクロックを高速化したい場合にもそのまま使えると思います。

AXI4-Lite 書き込み時のスレーブ動作

スレーブ側が書き込み要求をいつでも受け取れる場合、 awready, wready, bresp, bvalid を定数とすることができそうに思えるのですが、

&tchart( aclk ~_~_~_~_~_~_~_~_ awaddr ==?X=A1X=A2X=A3X=A4X=A5X=?== awvalid [~~~~~~~~~~]__ awready ~~~~~~~~~~~~~~~
wdata ==?X=D1X=D2X=D3X=D4X=D5X=?== wvalid ~~~~~~~~~~__ wready ~~~~~~~~~~~~~~~
bresp ====00============ bvalid ~~~~~~~~~~~~~~~
bready ~~~~~~~~~~~~~~~
);

wvalid は常に awvalid と同時に立つとは限らない気がするので実際には、 awready は定数にすることができません。

awvalid が必ず wvalid より先に立つのであれば、下図のように wvalid が立つまで awready を上げないようにして、awvalid && wvalid を待ってから処理するのが良さそうです。

&tchart( aclk ~_~_~_~_~_~_~_~_~_~_ awaddr ==?X=A1==X=A2X=A3==X=A4==X=A5X=? awvalid ~~~~~~~~~~~~~~~~ awready __[~~~~][~~][~~~~] wdata ====?X=D1X=D2X=?X=D3X=?X=D4X=D5X=? wvalid __~~~~~~~~~~ wready __[~~~~][~~][~~~~] bresp ====00================ bvalid ~~~~~~~~~~~~~~~~~~~
bready ~~~~~~~~~~~~~~~~~~~
);

この場合、awready や wready は wvalid に連動し、 書き込み動作も wvalid に同期します。

bvalid も wvalid に連動させても良いですが、 恐らく 1 に固定でも良いのだと思います。

LANG:verilog
always @(posedge aclk)
  if (wvalid)
    registers[awaddr] <= wdata;
assign awready = wvalid;
assign wready = wvalid;
assign bresp = 0;
assign bvalid = 1;

ただ、awvalid が必ず wvalid より先に立つ、というのがバスの仕様上保障されているのかどうか 今ひとつ自信が持てません。。。

そうでなければ awready や wready は awvalid && wvalid で上げることにして、

&tchart( aclk ~_~_~_~_~_~_~_~_~_~_ awaddr ==?X=A1==X=A2X=A3==X==?=X=A4X=? awvalid ~~~~~~~~~~~~ awready __[~~~~][~~]__[~~] wdata ====?X=D1X=D2X=?X=D3X=?X=D4==X=? wvalid __~~~~~~~~~~ wready __[~~~~][~~]__~~ bresp ====00================ bvalid ~~~~~~~~~~~~~~~~~~~
bready ~~~~~~~~~~~~~~~~~~~
);

LANG:verilog
always @(posedge aclk)
  if (awvalid && wvalid)
    registers[awaddr] <= wdata;
assign awready = awvalid && wvalid;
assign wready = awvalid && wvalid;
assign bresp = 0;
assign bvalid = 1;

とすれば良さそうです。

LANG:verilog
assign awready = awvalid && wvalid;
assign wready = awvalid && wvalid;

この部分で配線遅延が問題になるようであれば、 バッファを入れて、1クロックの遅延を許容する必要があります。

&tchart( aclk ~_~_~_~_~_~_~_~_~_~_~_~_~_~_ awaddr ==?X===A1==X=A2==X=A3====X==?=X=A4==X=? awvalid ~~~~~~~~~~~~~~~~~~~~ awready ____[~~][~~]_[~~]_[~~] wdata ====?X=D1==X=D2==X=?X=D3==X=?X=D4====X=? wvalid __~~~~~~~~~~~~~~~~~~ wready ____~~~~_~~_~~ bresp ====00======================== bvalid ~~~~~~~~~~~~~~~~~~~~~~~~~~~
bready ~~~~~~~~~~~~~~~~~~~~~~~~~~~
);

awvalid && wvalid をバッファして、次のようにすれば良いはずです。

LANG:verilog
always @(posedge aclk)
  valid_buf <= !valid_buf && awvalid && wvalid;
  if (valid_buf)
    registers[awaddr] <= wdata;
assign awready = valid_buf;
assign wready = valid_buf;
assign bresp = 0;
assign bvalid = 1;

コメント・質問





Counter: 105263 (from 2010/06/03), today: 31, yesterday: 0