AXI4-Stream VIPをMasterとして試してみる

FPGA
スポンサーリンク

はじめに

AMD Vivadoで、AXI4-Stream VIPをAXI StreamのMasterとして使ってみたので、使い方を残しておきます。
(Slaveはこの記事では扱いません。利用予定はあるので、今後使い方をのせるかもしれません)

対象読者は、以下を想定しています。

  • AXI4-StreamのSlaveモジュールを作ったけど、検証の時にAXI4-Stream Masterのモデルを作るのが面倒な方
    →AXI4-Stream VIPを使うことで、少しは楽になるかもしれません。
  • AXI4-Streamから、ランダムなトランザクションを発行する(AXI4-Streamデータ & サイドバンド信号 TUSER/TLASTをランダムに発行する)ではなく、指定して出力したい方
    (自分はこの動機で調べ始めました。他のブログ記事でも紹介されてるものもありますが、ランダムにトランザクションを生成する方法は書いている一方で、サイドバンド信号とかを自分で指定して行う方法がすぐに出てきませんでしたので、今回書いておきます。)

言語はSystem Verilogを使います。

Vivadoのバージョンは2023.2を用いています。(現時点2024/06/08で最新版)

準備

AXI4-Stream VIPをBlock Designへ追加する

まずは、Block Designを作ります。今回は、「AXIS_VIP」という名前でBlock Designを作りました。
作り方の詳細は割愛しますが、Vivadoの左側の「Flow Navigator」上「IP INTEGRATOR」の「Create Block Design」から生成できます。この辺りの使い方が微妙な方は、ここ(外部ブログ記事)とかここ(公式ドキュメント)を参考にしてみてください。

次に、Block DesignにAXI4-Stream VIPを追加します。(右クリックの「Add IP」から選択できます。)

次は、IPの設定です。
今回は下記の通りにしました。VIDEO用の信号を想定していますので、最低限の信号のみを付けています。
TDATA幅は96bit(例えばRGB888×4pixelなどを想定)で、USER幅は1bitにしています(TUSERのbit[0]はVideo信号の場合、Frame Startの信号にあたります)。

ちなみに、ここまでのTclコマンドはこちらです。

# Block Designの作成
create_bd_design "AXIS_VIP"

# Block DesignへAXI4-Stream VIPを作成
startgroup
create_bd_cell -type ip -vlnv xilinx.com:ip:axi4stream_vip:1.1 axi4stream_vip_0
endgroup

# AXI4-Stream VIPのコンフィグ設定
set_property -dict [list CONFIG.HAS_TLAST.VALUE_SRC USER CONFIG.HAS_TREADY.VALUE_SRC USER CONFIG.TDATA_NUM_BYTES.VALUE_SRC USER CONFIG.TUSER_WIDTH.VALUE_SRC USER CONFIG.HAS_TSTRB.VALUE_SRC PROPAGATED] [get_bd_cells axi4stream_vip_0]
set_property -dict [list \
  CONFIG.HAS_TLAST {1} \
  CONFIG.HAS_TUSER_BITS_PER_BYTE {0} \
  CONFIG.INTERFACE_MODE {MASTER} \
  CONFIG.TDATA_NUM_BYTES {12} \
  CONFIG.TUSER_WIDTH {1} \
  CONFIG.TDEST_WIDTH {0} \
  CONFIG.TID_WIDTH {0} \
  CONFIG.HAS_TREADY {1} \
] [get_bd_cells axi4stream_vip_0]

DUT(Device Under Test、検証する自作モジュール等)をBlock Designへ追加する

検証したいモジュール(自作RTLやブロックデザインで作った回路など)を現在のBlock Designへと追加します。

今回は以下のコードを接続します。tdatas_axis_tready && s_axis_tvalid で更新するだけのモジュールです。これと接続し、AXI Stream VIPの出力から1Cycle遅れてaxis_rxモジュールの出力に見えてくるかを確認します。

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/06/05 21:00:13
// Design Name: 
// Module Name: axis_rx
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module axis_rx (
    input wire aclk,
    input wire aresetn, // アクティブローリセット信号

    // AXI4-Stream 入力インターフェース
    input wire s_axis_tvalid,
    output reg s_axis_tready,
    input wire [95:0] s_axis_tdata,
    input wire s_axis_tlast,
    input wire s_axis_tuser,

    // AXI4-Stream 出力インターフェース
    output reg m_axis_tvalid,
    output reg [95:0] m_axis_tdata,
    output reg m_axis_tlast,
    output reg m_axis_tuser
);

    always @(posedge aclk) begin
        if (~aresetn) begin
            s_axis_tready <= 1'b0;
        end else begin
            s_axis_tready <= 1'b1;
        end
    end

    always @(posedge aclk) begin
        if (~aresetn) begin
            m_axis_tdata <= 96'h0;
        end else begin
            if (s_axis_tready && s_axis_tvalid) begin
                m_axis_tdata <= s_axis_tdata;
            end else begin
                m_axis_tdata <= m_axis_tdata;
            end
        end
    end

    always @(posedge aclk) begin
        if (~aresetn) begin
            m_axis_tvalid <= 1'b0;
            m_axis_tlast  <= 1'b0;
            m_axis_tuser  <= 1'b0;
        end else begin
            m_axis_tvalid <= s_axis_tvalid;
            m_axis_tlast  <= s_axis_tlast;
            m_axis_tuser  <= s_axis_tuser;
        end
    end

endmodule

このVerilogファイルをプロジェクトへ追加(+マーク「Add Source」)し、その後Block Designに追加(右クリックから「Add Module」)します。(ここで、もしあなたのソースがSystem VerilogだとBlock Designに追加できないと思いますが、System Verilogファイルはそのままだと追加できません。追加したければ、VerilogかVHDLでWrapperファイルを作成する必要があります。)

ここまでできれば、以下と同じようなBlock Designになるはずです。

Create Block Designもしておきましょう。

これで準備は終わりです。

ここまでのTclコマンドは下記です。

# ファイルの追加 <path-to-src>は各々のソースファイルまでのパス
add_files -norecurse <path-to-src>/axis_rx.v
update_compile_order -fileset sources_1

# Block Designの生成
create_bd_cell -type module -reference axis_rx axis_rx_0

# AXIS I/Fの接続
connect_bd_intf_net [get_bd_intf_pins axis_rx_0/s_axis] [get_bd_intf_pins axi4stream_vip_0/M_AXIS]

# aclk, areset_nの外部ポートの生成と接続
startgroup
make_bd_pins_external  [get_bd_pins axi4stream_vip_0/aclk]
endgroup
startgroup
make_bd_pins_external  [get_bd_pins axi4stream_vip_0/aresetn]
endgroup
startgroup
make_bd_intf_pins_external  [get_bd_intf_pins axis_rx_0/m_axis]
endgroup
connect_bd_net [get_bd_ports aclk_0] [get_bd_pins axis_rx_0/aclk]
connect_bd_net [get_bd_ports aresetn_0] [get_bd_pins axis_rx_0/aresetn]

# ネット名等についた _0 等を削除
set_property name aclk [get_bd_ports aclk_0]
set_property name aresetn [get_bd_ports aresetn_0]
set_property name m_axis [get_bd_intf_ports m_axis_0]
set_property name aclk [get_bd_nets aclk_0_1]
set_property name aresetn [get_bd_nets aresetn_0_1]

# Block Designを再レイアウト
regenerate_bd_layout

# Wrapperファイルの生成とファイルの追加
make_wrapper -files [get_files D:/Xilinx/my_projects/AXI_VIP_TEST/AXI_VIP_TEST.srcs/sources_1/bd/AXIS_VIP/AXIS_VIP.bd] -top
add_files -norecurse d:/Xilinx/my_projects/AXI_VIP_TEST/AXI_VIP_TEST.gen/sources_1/bd/AXIS_VIP/hdl/AXIS_VIP_wrapper.v
update_compile_order -fileset sources_1

論理シミュレーションでAXI Stream VIPを動かしてみる

テストベンチの作成

以下のテストベンチを作成しました。以下の順でTransactionを発行するように記載しています。

  • Master VIP のInitialize
  • Random Transaction (Write) 1回目
  • Random Transaction (Write) 2回目
  • API Transaction (Write) 1回目
  • API Transaction (Write) 2回目
  • 3Cycle連続でAPI Transaction (Write)
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2024/06/08 07:54:17
// Design Name: 
// Module Name: tb_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


import axi4stream_vip_pkg::*;
import AXIS_VIP_axi4stream_vip_0_0_pkg::*;

module tb_top ();

    /*************************************************************************************************************
    * Master VIP generates transaction:
    * Driver in master agent creates transaction
    * Randomized the transaction
    * Driver in master agent sends the transaction
    *************************************************************************************************************/
    task automatic mst_gen_transaction_rand();
        axi4stream_transaction wr_transaction;
        wr_transaction =
            mst_agent.driver.create_transaction("Master VIP write transaction (Random)");
        wr_transaction.set_xfer_alignment(XIL_AXI4STREAM_XFER_RANDOM);

        WR_TRANSACTION_FAIL : assert (wr_transaction.randomize());
        // これを使うと、以下の要素が全てランダムに発行されるようです
        // - トランザクション開始(VALID発行)までの遅延サイクル数
        // - サイドバンド信号(TUSER, TLAST, TIDなど)
        // - データ(TDATA)

        mst_agent.driver.send(wr_transaction);
    endtask

    task automatic mst_gen_transaction_api(input logic [95:0] tdata, input logic tuser,
                                           input logic tlast);
        axi4stream_transaction wr_transaction;
        wr_transaction = mst_agent.driver.create_transaction("Master VIP write transaction (API)");

        wr_transaction.set_data_beat(tdata);
        wr_transaction.set_user_beat(tuser);
        wr_transaction.set_last(tlast);
        wr_transaction.set_delay(0);
        // この場合、TVALIDは勝手に発行されます
        // delayの初期値は20で、20Cycle後に発行されるようです。ここではすぐに発行したいので、0に設定しています

        $display("delay: %d", wr_transaction.get_delay());

        mst_agent.driver.send(wr_transaction);
    endtask

    task automatic wait_clk(int n);
        repeat (n) @(posedge clock);
    endtask

    /***************************************************************************************************
    * Verbosity level which specifies how much debug information to be printed out
    * 0         - No information will be printed out
    * 400       - All information will be printed out
    ***************************************************************************************************/
    xil_axi4stream_uint mst_agent_verbosity = 0;

    AXIS_VIP_axi4stream_vip_0_0_mst_t mst_agent;

    // Clock signal
    bit clock;
    // Reset signal
    bit reset;

    enum {
        INIT_AGENT,
        WR_TRAN_RAND,
        WR_TRAN_API1,
        WR_TRAN_API2,
        NOP_ST
    } state;

    // instantiate bd
    AXIS_VIP_wrapper DUT (
        .aresetn(reset),

        .aclk(clock)
    );

    always #5 clock <= ~clock;

    axi4stream_transaction wr_transaction;
    xil_axi4stream_data_beat [95:0] tdata;
    xil_axi4stream_user_beat tuser;
    bit tlast;

    // Main process
    initial begin

        reset = 1'b0;
        tdata = '0;
        tuser = '0;
        tlast = '0;
        wait_clk(100);
        reset = 1'b1;
        wait_clk(100);

        // Initialize
        state = INIT_AGENT;
        mst_agent = new("master vip agent", DUT.AXIS_VIP_i.axi4stream_vip_0.inst.IF);
        mst_agent.vif_proxy.set_dummy_drive_type(XIL_AXI4STREAM_VIF_DRIVE_NONE);
        mst_agent.set_agent_tag("Master VIP");
        mst_agent.set_verbosity(mst_agent_verbosity);
        mst_agent.start_master();
        state = NOP_ST;
        wait_clk(100);

        // Random Transaction 1
        state = WR_TRAN_RAND;
        mst_gen_transaction_rand();
        wait_clk(1);
        state = NOP_ST;
        wait_clk(99);

        // Random Transaction 2
        state = WR_TRAN_RAND;
        mst_gen_transaction_rand();
        wait_clk(1);
        state = NOP_ST;
        wait_clk(99);

        // API Transaction 1
        state = WR_TRAN_API1;
        tdata = 'hFFFF;
        tuser = 'h1;
        tlast = 'h0;
        mst_gen_transaction_api(tdata, tuser, tlast);
        wait_clk(1);
        state = NOP_ST;
        wait_clk(99);

        // API Transaction 1
        state = WR_TRAN_API2;
        tdata = 'h1111;
        tuser = 'h0;
        tlast = 'h1;
        mst_gen_transaction_api(tdata, tuser, tlast);
        wait_clk(1);
        state = NOP_ST;
        wait_clk(99);

        wait_clk(100);

        // 3Cycle連続した API Write Transaction
        state = WR_TRAN_API1;
        tdata = 'hFFFF;
        tuser = 'h1;
        tlast = 'h0;
        mst_gen_transaction_api(tdata, tuser, tlast);
        wait_clk(1);
        state = WR_TRAN_API2;
        tdata = 'h1111;
        tuser = 'h0;
        tlast = 'h1;
        mst_gen_transaction_api(tdata, tuser, tlast);
        wait_clk(1);
        state = WR_TRAN_API1;
        tdata = 'hFFFF;
        tuser = 'h1;
        tlast = 'h0;
        mst_gen_transaction_api(tdata, tuser, tlast);
        wait_clk(1);
        state = NOP_ST;
        wait_clk(100);

        $finish();

    end

endmodule

これをプロジェクトに追加して、シミュレーションを行います。

追加するTclコマンドは以下の通りです。

# Simulation fileの追加 <path-to-src>は各々のソースファイルまでのパス
add_files -fileset sim_1 -norecurse <path-to-src>/tb_top.sv
update_compile_order -fileset sim_1

シミュレーション実行

結果としては以下のような波形が得られました。シミュレーションでは、axis_rx モジュールの入出力の信号を見ています。入力に対して出力が同じように追従しているかを確認します。tvalid/tuser/tdata/tlast全てが思った通りに追従していそうに見えるので、うまくいってそうです。

特に、最後のトランザクションを拡大したのがこちらで、1サイクルごとにAXI Streamの信号を生成できており、VALIDも3Cycle立っています。また、出力も1Cycle遅れて出力できているので、良い感じです。

参考リンク

AMD 公式

リンクはないですが、AXI4-Stream VIPのExample Designも参考にしました

その他ブログ記事など

AXI VIPの方も含んでますが、上記のサイトをかなり参考にさせていただきました。ありがとうございます。

おわりに

以上、AXI-4 Stream VIPのMasterでの使い方を残しました。

コメント

タイトルとURLをコピーしました