はじめに
AXIバスを使った回路を簡単に検証(シミュレーション)したい・・・
今回はXilinxのAXI Verification IP (AXI VIP)を使ってBRAMにデータを読み書きするシミュレーションをしたので,使い方をまとめてみようと思います.
XilinxのFPGA開発環境であるVivadoにはAXIインタフェースを持つ回路を検証するためのAXI Verification IP (AXI VIP)が用意されています。これは、AXI BFM (Bus Functional Model)の後継に当たります。詳細は省きますので、気になる方は文末の参考文献やネットで検索してみてください。
環境
- Vivado System Edition 2018.3 (Linux)
- AXI Verification IP v1.1
回路
今回テストするために作成した回路は以下のとおりです。
なお、実際に合成して石に焼く時はVIPの代わりに何か外部の信号が接続されたりします(たぶん)。
左から順に以下のモジュールを配置しています。
- AXI Verification IP
- AXI BRAM Controller
- Block Memory Generator (BRAM)
全てブロックデザイン上で右クリックし、”Add IP”から追加しています。
また、iclkとirstnは同じくブロックデザイン上で右クリックし、”Create Port”からinputポートをつくりました。
それぞれのモジュールの設定は以下のようになっています。
まずは、AXI VIPから。今回はMasterデバイスとして動いてもらいます。
AXI BRAM Controllerの設定は以下のとおりです。
Block Memory Generatorの設定は以下のようにしました。
Modeを”BRAM Controller”に設定しています。
モジュールを配置して接続し終わったら、
- Generate HDL Wrapperでラッパーを作成
- Generate Output ProductsでVIPなどのパッケージを生成
を実行してください。Sourceタブでブロックデザインを右クリックするとできます。
テストベンチ
テストベンチはSystem Verilogで書く必要があります。
今回のテストベンチでは32bitの値をBRAMの0番地から順番に6回書き込みを行った後、同じく0番地からデータの読み込みを6回行なっています。
`timescale 1ns / 1ps
import axi_vip_pkg::*;
import design_1_axi_vip_0_0_pkg::*;
module tb();
// 適当な値を設定
parameter STEP = 100000;
bit iclk, irstn;
design_1_wrapper dut(.*);
// クロック生成
task clk_gen();
iclk = 0;
forever #(STEP/2) iclk = ~iclk;
endtask
// リセット信号生成
task rst_gen();
irstn = 0;
#(STEP*10);
irstn = 1;
endtask
// agentとtransactionを宣言
design_1_axi_vip_0_0_mst_t vip_agent;
axi_transaction wr_transaction, rd_transaction;
initial begin
fork
clk_gen();
rst_gen();
join_none
#(STEP*100);
// Init AXI agent
vip_agent = new("my vip agent", tb.dut.design_1_i.axi_vip_0.inst.IF);
vip_agent.start_master();
//---------------
// Write Transactions
//---------------
// Create a write transaction
wr_transaction = vip_agent.wr_driver.create_transaction("write transaction");
// 以下6回書込み
wr_transaction.set_write_cmd(0, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
wr_transaction.set_data_block(32'd1);
vip_agent.wr_driver.send(wr_transaction);
wr_transaction.set_write_cmd(4, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
wr_transaction.set_data_block(32'd2);
vip_agent.wr_driver.send(wr_transaction);
wr_transaction.set_write_cmd(8, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
wr_transaction.set_data_block(32'd3);
vip_agent.wr_driver.send(wr_transaction);
wr_transaction.set_write_cmd(12, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
wr_transaction.set_data_block(32'd4);
vip_agent.wr_driver.send(wr_transaction);
wr_transaction.set_write_cmd(16, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
wr_transaction.set_data_block(32'd5);
vip_agent.wr_driver.send(wr_transaction);
wr_transaction.set_write_cmd(20, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
wr_transaction.set_data_block(32'd6);
vip_agent.wr_driver.send(wr_transaction);
#(STEP*100);
//---------------
// Read Transactions
//---------------
// Create a read transaction
rd_transaction = vip_agent.rd_driver.create_transaction("read transaxtion");
// 以下6回読み込み
rd_transaction.set_read_cmd(0, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
vip_agent.rd_driver.send(rd_transaction);
rd_transaction.set_read_cmd(4, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
vip_agent.rd_driver.send(rd_transaction);
rd_transaction.set_read_cmd(8, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
vip_agent.rd_driver.send(rd_transaction);
rd_transaction.set_read_cmd(12, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
vip_agent.rd_driver.send(rd_transaction);
rd_transaction.set_read_cmd(16, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
vip_agent.rd_driver.send(rd_transaction);
rd_transaction.set_read_cmd(20, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
vip_agent.rd_driver.send(rd_transaction);
#(STEP*100);
$finish;
end
endmodule
AVI VIPの使い方
使い方は、Xilinxの製品ガイド1および、XilinxのAPIガイド2を主に参考にしています。
また、テストベンチの書き方を中心に、Qiitaのこちらの記事3も参考にさせていただきました。
1: AXI VIPを使うのに必要なパッケージをインポートする
import axi_vip_pkg::*;
import <component name>_pkg::*;
一つ目のpkgはVivadoと一緒にインストールされる共通のファイルです。
一方、二つ目のファイルはブロックデザインで配置したVIPに付随して生成されるファイルですので、ファイル名が人それぞれの環境で変わってきます。
2: AXI VIPエージェントとトランザクションを宣言する
テストベンチでAXI VIPのエージェントとトランザクションを宣言します。wr_transactionは書き込み用,rd_transactionは読み込み用で使います.
// <component name>_mst_tでマスター用agent
design_1_axi_vip_0_0_mst_t vip_agent;
axi_transaction wr_transaction, rd_transaction;
3: agentとtransactionの準備
newでVIP agentにIFのパスを渡し,agentを起動します.
ここで渡すパスは、からのテストベンチを実行するとVivadoのコンソールに表示されるようです。
また、書き込みトランザクションを作成します。
vip_agent = new("my vip agent", tb.dut.design_1_i.axi_vip_0.inst.IF);
vip_agent.start_master();
wr_transaction = vip_agent.wr_driver.create_transaction("write transaction");
4: データの転送
AXI VIPから書き込みトランザクションを生成するためには,トランザクションに書き込みコマンドと書き込みデータを指定し,送信します.
set_write_cmdの第一引数は書き込み先のアドレス(バイト単位)、第4引数はバースト長、最後の引数のxil_axi_size_t'(xil_clog2((32)/8))は書き込みデータのバイト数を2のn乗で表したものです.Xilinxの資料1を参考にしています。
(他の引数はまだ把握できていません・・・これでとりあえず動きました)。
続いて、set_data_blockで送りたいデータを設定し、送信します。
// 書き込みコマンド設定
wr_transaction.set_write_cmd(0, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
// 書き込みデータ設定
wr_transaction.set_data_block(32'd1);
// 送信
vip_agent.wr_driver.send(wr_transaction);
5: データの読み込み
書き込み時と同様に読み込みトランザクションを生成し,コマンドの設定を行ってagentで送信します.set_read_cmdの第一引数がアドレスです。
設定値は書き込み時と同じです.
// 読み込みトランザクション生成
rd_transaction = vip_agent.rd_driver.create_transaction("read transaxtion");
// コマンド設定
rd_transaction.set_read_cmd(0, XIL_AXI_BURST_TYPE_INCR, 0, 0, xil_axi_size_t'(xil_clog2((32)/8)));
// トランザクション送信(AXIによる読み込み)
vip_agent.rd_driver.send(rd_transaction);
シミュレーション結果
ちゃんと書き込みと読み込みが6回ずつ行われている様子が分かります。
信号は上から、以下のようにグループ分けしています。
- AXI書込みアドレスチャネル
- AXI書込みデータチャネル
- AXI読み込みアドレスチャネル
- AXI読み込みデータチャネル
- BRAM
おわりに
AXI VIPは便利なわりに使い方があまり出回っていないのでまとめて見ました。これを使えばAXIバスを使った検証もだいぶやりやすくなりそうです。今回はAXI MasterとしてVIPを使いましたが、Slaveやパススルーもできるようです。
また、AXI-Streamについては専用のVIPが用意されていますので、そのうちご紹介できればと思います。
ここでご紹介したのはVIPの機能のほんの一部ですし、私もよくわかっていない部分もありますので、お気づきのことがあればご指摘ください・・・