簡易AXI BFM

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](https://qiita-image-store.s3.amazonaws.com/0/65141/f3cfc925-1a09-c2f7-8802-bad4fd029dc8.png)

読み出しアドレスはキューに貯めておかなければいけないみたいです。
DataMoverを使っていてこれにハマり、対応したくて、ReadチャンネルのBFMを書きました。Writeチャンネルはおまけです。

# つかいかた

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