AXIバスインタフェースを持ったIPのテスト用に簡単なBFM (Bus Functional Model)を書きました。
こちらです:https://gist.github.com/tuttieee/e9391121cf9d47a69533
現在、Xilinx AXI DataMover を組み込んだIPのテストに使っていますが、今のところ必要十分です。
ぶっちゃけAXIの仕様書は読んでいません。AXI DataMoverの動作に合わせて書いただけです。
Queue
Readチャンネルですが、araddr
,arvalid
,arready
で読み出しアドレスを指定したあと、それに対応するデータをSlaveが出力し終わる前に、次のaraddr
が入力されることがあります。
読み出しアドレスはキューに貯めておかなければいけないみたいです。
DataMoverを使っていてこれにハマり、対応したくて、ReadチャンネルのBFMを書きました。Writeチャンネルはおまけです。
つかいかた
Read channel
// Bus parameters
parameter integer C_M_AXI_DATA_WIDTH = 32;
parameter integer C_M_AXI_ADDR_WIDTH = 32;
localparam BYTE_PER_WORD = C_M_AXI_DATA_WIDTH / 8;
// Instantiate BFM
wire [C_M_AXI_ADDR_WIDTH-1 : 0] raddr;
wire rnext;
axi_simple_read_bfm #(
.C_M_AXI_ID_WIDTH(C_M_AXI_ID_WIDTH),
.C_M_AXI_DATA_WIDTH(C_M_AXI_DATA_WIDTH)
) axi_simple_read_bfm_inst (
.m_axi_aclk(m_axi_aclk),
.m_axi_aresetn(m_axi_aresetn),
.m_axi_arvalid(m_axi_arvalid),
.m_axi_arready(m_axi_arready),
.m_axi_araddr(m_axi_araddr),
.m_axi_arlen(m_axi_arlen),
.m_axi_rready(m_axi_rready),
.m_axi_rvalid(m_axi_rvalid),
.m_axi_rlast(m_axi_rlast),
.m_axi_rid(m_axi_rid),
.m_axi_rresp(m_axi_rresp),
.raddr(raddr),
.rnext(rnext)
);
// Test data buffer parameters
parameter BUF_LEN = 128;
parameter BFM_ADDR_BASE = 32'h10000000;
parameter BFM_ADDR_HIGH = BFM_ADDR_HIGH + BUF_LEN * C_M_AXI_DATA_WIDTH / BYTE_PER_WORD;
// Test data buffer
reg [C_M_AXI_DATA_WIDTH - 1 : 0] mem [0 : BUF_LEN-1];
initial
$readmemh("some_data.txt", mem, 0, BUF_LEN-1);
// Reading buffer
wire [C_M_AXI_ADDR_WIDTH - 1:0] mem_addr = (raddr - BFM_ADDR_BASE) / BYTE_PER_WORD;
wire rnext_mem = rnext && (BFM_ADDR_BASE <= raddr) && (raddr <= BFM_ADDR_HIGH);
assign m_axi_rdata = rnext_mem ? mem[mem_addr] : 0;
mem
というバッファを用意して、AXIバス経由でそれを読み出すようなテストベンチのコードスニペットです。
(実際に私が使っているコードから一部を切り取って整理したものですが、このコード自体は動くかテストしていません。雰囲気だけ掴んでください)
AXIバスの信号のうち必要なものをaxi_simple_read_bfm
モジュールに入力します。
axi_simple_read_bfm
モジュールからはraddr
とrnext
の信号が出てきます。
raddr
はアドレス、rnext
は読み出しイネーブルです。rnext
がアサートされているとき、m_axi_rdata
に読み出したいデータを入力します。
本来AXIバスはready信号とvalid信号を持ち、readスレーブ側はvalid信号を操作することでブロッキング操作ができますが、
このBFMは両信号を勝手に操作し、それに合わせてrnext
も問答無用でアサートするので、ユーザーはブロッキング操作するようなコードは書けません。
データをメモリに読んでおいてアドレスに応じて読みだす、という操作ができれば十分だったので、簡単のためこのような仕様になっています。
Write channel
// AXI BFM for Write channel
parameter BFM_WRITE_ADDR_BASE = 32'h18000000;
parameter BFM_WRITE_ADDR_HIGH = BFM_WRITE_ADDR_BASE + RESULT_MEM_LEN * BYTE_PER_WORD;
wire [C_M_AXI_ADDR_WIDTH-1:0] waddr;
wire wnext;
axi_simple_write_bfm #(
.C_M_AXI_ID_WIDTH(C_M_AXI_ID_WIDTH)
) axi_simple_write_bfm_inst (
.m_axi_aclk(m_axi_aclk),
.m_axi_aresetn(m_axi_aresetn),
.m_axi_awready(m_axi_awready),
.m_axi_awlen(m_axi_awlen),
.m_axi_awvalid(m_axi_awvalid),
.m_axi_awaddr(m_axi_awaddr),
.m_axi_wready(m_axi_wready),
.m_axi_wvalid(m_axi_wvalid),
.m_axi_wlast(m_axi_wlast),
.m_axi_bid(m_axi_bid),
.m_axi_bresp(m_axi_bresp),
.m_axi_bvalid(m_axi_bvalid),
.m_axi_bready(m_axi_bready),
.waddr(waddr),
.wnext(wnext)
);
// 出力バッファ
integer omem_i;
reg [C_M_AXI_DATA_WIDTH-1:0] output_mem [0:RESULT_MEM_LEN-1];
initial
for (omem_i=0; omem_i<RESULT_MEM_LEN; omem_i=omem_i+1)
output_mem[omem_i] = {C_M_AXI_DATA_WIDTH{1'b0}};
wire wnext_output = wnext && (BFM_WRITE_ADDR_BASE <= waddr) && (waddr <= BFM_WRITE_ADDR_HIGH);
wire [C_M_AXI_ADDR_WIDTH-1 : 0] output_mem_addr = (waddr - BFM_WRITE_ADDR_BASE)/BYTE_PER_WORD;
always @( posedge m_axi_aclk ) begin
if ( wnext_output )
output_mem[output_mem_addr] <= m_axi_wdata;
end
こちらはWriteされたデータをアドレスに応じてバッファに貯める、という動作になっています。
貯めたデータはあとでチェックするなどしてテストします。
いいわけ
FPGA歴半年くらいの素人が書いたコードなので、指摘点などあればお願いします。