LoginSignup
2
4

More than 5 years have passed since last update.

簡易AXI BFM

Last updated at Posted at 2016-01-12

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歴半年くらいの素人が書いたコードなので、指摘点などあればお願いします。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4