Vivado下FIFO IP核除了文章FIFO IP核使用(一) ,FIFO IP核使用(二),FIFO IP核使用(三)中介绍的标准接口(empty,full,rd_en,wr_en等)之外还可以选择配置集成了AXI4总线的接口。AXI4总线分为三种:AXI4,AXI4-Lite和AXI4-Stream。关于更多AXI总线的知识,参见文章AXI总线基础 。FIFO IP核集成了AXI4-Stream总线,特点是没有地址线,允许无限制的数据突发传输规模。顾名思义,Stream是指数据像水流一样连续不断,用固定的速度发送给接口。
在使用上,标准FIFO和AXI4-Stream FIFO也有区别。举例说明:相对于标准接口FIFO中读使能信号rd_en置位后,还需要一个时钟周期的延迟,数据才会出现在读数据端,AXI4-Stream FIFO在读操作握手后,读数据立马可用。从这点上看,AXI4-Stream FIFO读操作时更简单。在设计实现工程时,设计者可以考虑设计需要,进而选择使用哪种FIFO。本节课将结合使用AXI4-Stream FIFO的实例,带领大家一起学习有AXI4-Stream接口的FIFO使用方法。
1.AXI4-Stream FIFO接口
作为AXI4总线家族的一员,AXI4-Stream肯定有valid/ready/data接口。在AXI4-Stream中,分为主(master),从(slave)两个部分,如下表所示。AXI master接口用来读操作,AXI slave接口用来写操作,如图1所示。
表1:
| 名称 | 方向 | 描述 |
| 从: | / | / |
| s_axis_tvalid | 输入 | 对应标准FIFO接口中的wr_en |
| s_axis_tready | 输出 | 对应标准FIFO接口中的~full |
| s_axis_tdata | 输入 | 对应标准FIFO接口中的din |
| / | / | / |
| 主: | / | / |
| m_axis_tvalid | 输出 | 对应标准FIFO接口中的~empty |
| m_axis_tready | 输入 | 对应标准FIFO接口中的rd_en |
| m_axis_tdata | 输出 | 对应标准FIFO接口中的dout |

图1 AXI4-Stream FIFO和 标准FIFO之间的联系
注意无论是s_axis_tdata还是m_axis_tdata都是以8-bit/一个字节为基本单位。
读操作和写操作中m_axis_tvalid,m_axis_tready,s_axis_tvalid和s_axis_tready的握手如图2所示:

图2 AXI4-Stream FIFO 握手
图1所示的读操作过程:AXI FIFO slave提供information和valid(m_axis_tvalid),一旦检测到ready(m_axis_tready)置位,AXI FIFO slave就会把下一个 准备好的information输出到总线端口上。当FIFO中没有数据(为空)时,valid信号被置低。
图1所示的写操作过程:AXI FIFO master 提供 information和 valid(s_axis_tvalid)信号; AXI FIFO slave通过置位 ready(s_axis_tready)信号来接受 information。 只有当 FIFO 已满时,ready信号才会被置低。
可以看出,只有当valid和ready同时有效时,数据传输(读或写)才会发生,广泛的Valid信号和Ready信号握手如图2所示:

图2 Valid和Ready同时为高
除了表1中的信号外,AXI4-Stream FIFO 还有其它的一些信号接口可供选择和配置,这里列出一些常用信号,如表2所示:
表2:
| 名称 | 方向 | 描述 |
| 从: | / | / |
| s_axis_tstrb | 输入 | 字节使能,高电平为对应数据字节有效。对于 64 位 数据,位 0 对应 DATA 的最低有效字节,位 7 对应最高有效字节。 例如: • strb[0] = 1b,DATA[7:0] 为 NULL 字节 • strb [7] = 0b,DATA[63:56] 不是 NULL 字节 |
| s_axis_tkeep | 输入 | 字节使能,指示数据的相关字节是否作为数据流的一部分。为低时表示数据相关字节是空字节,可以从数据流中删除。 对于 64 位 数据,位 0 对应 DATA 的最低有效字节,位 7 对应最高有效字节。 例如: • keep[0] = 1b,DATA[7:0] 为 NULL 字节 • keep[7] = 0b,DATA[63:56] 不是 NULL 字节 |
| s_axis_tlast | 输入 | 指示数据包结束 |
| s_axis_tid | 输入 | 指示不同数据流的数据流标识符 |
| s_axis_tdest | 输入 | 为数据提供路由信息 |
| s_axis_tuser | 输入 | 可以与数据流一起传输的用户自定义信息 |
| / | / | / |
| 主: | / | / |
| m_axis_tstrb | 输出 | 字节使能,高电平为对应数据字节有效。对于 64 位 数据,位 0 对应 DATA 的最低有效字节,位 7 对应最高有效字节。 例如: • strb[0] = 1b,DATA[7:0] 为 NULL 字节 • strb [7] = 0b,DATA[63:56] 不是 NULL 字节 |
| m_axis_tkeep | 输出 | 字节使能,指示数据的相关字节是否作为数据流的一部分。为低时表示数据相关字节是空字节,可以从数据流中删除。 对于 64 位 数据,位 0 对应 DATA 的最低有效字节,位 7 对应最高有效字节。 例如: • keep[0] = 1b,DATA[7:0] 为 NULL 字节 • keep[7] = 0b,DATA[63:56] 不是 NULL 字节 |
| m_axis_tlast | 输出 | 指示数据包结束 |
| m_axis_tid | 输出 | 指示不同数据流的数据流标识符 |
| m_axis_tdest | 输出 | 为数据提供路由信息 |
| m_axis_tuser | 输出 | 可以与数据流一起传输的用户自定义信息 |
2. AXI4-Stream FIFO实例
-
AXI4-Stream FIFO IP核设置
在Vivado的IP Catalog的搜索栏中输入 FIFO ,在结果中选择 FIFO Generator ,如图3所示。

图3 搜索 FIFO Generator
双击打开FIFO Generator IP核,在Interface Type中选择 AXI Stream。Clocl Type AXI选择Common Clock(同步时钟)。

图4 Interface Type
在AXI4 Stream Ports中选择TDATA NUM BYTES为1,表示数据都是以1字节(8-bit)读/写。数据中携带的其它信息设置为0,不使用TSTRB和TKEEP两个字节使能,可以看到最后计算出来的总数据宽度是8-bit,如图5所示。

图5 设置端口数据宽度
在配置选项中选择Configuration Options为FIFO,设置FIFO Depth,这里深度设置为1024。

图6 Config设置
对于Status Flags这里选择不设置。

图7 状态标志
在最后的Summary页面中可以看出前面步骤的总结。对比设置的参数,如果和图8有差异,可以返回去修改设置。在全部设置完后,点击OK结束AXI4-Stream FIFO IP核设置。

图8 AXI4-Stream FIFO设置总结
-
读写AXI4-Stream FIFO 工程
本文使用了一个简单的例子来写FIFO和读FIFO。因为在实体程序中已经把FIFO的数据写进去了,所以在仿真程序中只是例化了模块,以及生成了一个时钟,用来调度模块中读写FIFO的状态机。
实体程序如下:
`timescale 1ns / 1ps
module top(
input clk,
input s_aresetn,
output [7:0] m_axis_tdata
);
wire s_axis_tready;
reg s_axis_tvalid = 0;
wire m_axis_tvalid;
reg m_axis_tready=0;
reg [7:0] s_axis_tdata;
//wire wr_rst_busy;
//wire rd_rst_busy;
reg [1:0] st;
reg [4:0] count;
reg [7:0] data;
wire [10:0]axis_data_count;
always @ (posedge clk or negedge s_aresetn)
begin
if(~s_aresetn)
begin
s_axis_tdata <= 0;
st <= 0;
count <= 0;
data <= 0;
end
else
begin
case(st)
0://write
begin
if(s_axis_tready)
begin
if( count > 20 )
st <= 3;
else st <= 1;
end
end
1:
begin
s_axis_tdata <= data;
s_axis_tvalid <= 1;
count <= count + 1;
data <= data + 1;
st <= 2;
end
2:
begin
s_axis_tvalid <= 0;
st <= 0;
end
3://read
begin
count <= 0;
if(m_axis_tvalid)
begin
m_axis_tready <= 1;
end
else
st <= 0;
end
endcase
end
end
fifo_generator_0 fifo_axi (
.s_aclk(clk), // input wire s_aclk
.s_aresetn(s_aresetn), // input wire s_aresetn
.s_axis_tvalid(s_axis_tvalid), // input wire s_axis_tvalid
.s_axis_tready(s_axis_tready), // output wire s_axis_tready
.s_axis_tdata(s_axis_tdata), // input wire [7 : 0] s_axis_tdata
.m_axis_tvalid(m_axis_tvalid), // output wire m_axis_tvalid
.m_axis_tready(m_axis_tready), // input wire m_axis_tready
.m_axis_tdata(m_axis_tdata) // output wire [7 : 0] m_axis_tdata
);
endmodule
仿真代码如下:
`timescale 1ns / 1ps
module tb(
);
wire [7:0] m_axis_tdata;
reg s_aresetn = 0;
reg clk = 0;
always #5 clk = ~clk;
initial begin
s_aresetn = 0;
#200
s_aresetn = 1;
end
top top_inst(
.clk(clk),
.s_aresetn(s_aresetn),
.m_axis_tdata(m_axis_tdata)
);
endmodule
-
仿真波形

图9 写操作
从图9中可以看出在s_axis_tready和s_axis_tvalid同时为高时,数据被写入了FIFO。在写入一个数据之后,m_axis_tvalid为高表示FIFO里已经有一个数据可以进行读操作了。

图10 读操作
一旦FIFO里有数据,m_axis_tvalid被置位,但是一直到m_axis_tready为高时,读操作才有效,可以看到数据依次出现在m_axis_tdata端口。

图11 边写边读
因为m_axis_tready一直为高,所以一旦有数据被写入FIFO,立即被读出来,即图11所示的边写边读。
3. 扩展
在上述创建的AXI4-Stream FIFO IP核基础上,增加字节使能s_axis_tstrb(m_axis_tstrb)和指示数据包结束的s_axis_tlast(m_axis_tlast)来控制FIFO的读写操作。