Verilog
FPGA
AXI4
More than 1 year has passed since last update.

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が入力されることがあります。

axi_read.png

読み出しアドレスはキューに貯めておかなければいけないみたいです。
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モジュールからはraddrrnextの信号が出てきます。
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歴半年くらいの素人が書いたコードなので、指摘点などあればお願いします。