AXI4-Stream总线流控握手实战经验总结
总述
AXI4-Stream总线(后简称AXIS)广泛应用于Xilinx中的各IP核之间,学习使用Xlinx的FPGA时AXI4总线接口这道坎儿是不可跳过的。当然其他厂家的FPGA学习虽然不一定使用AXI4(如Intel的FPGA使用Avalon-ST总线),但每种不同的总线的协议实则相似,学好AXIS其余的流式传输总线也能够触类旁通。
在使用AXIS总线连接IP核与自己编写的逻辑,或使用AXIS开发自己的逻辑时,流控握手的处理是很烦人的。当多级使用AXIS接口的模块连接时后级拉低s_axis_tready信号停止接收的事件实则需要经过各级通过AXIS相连接的模块最终传递到产生数据的模块,让产生数据的模块不再产生新的数据。这个过程看似简单,实则若真的使用最简单的方式,即用组合逻辑贯穿每一级模块,当其中一级或多级模块不满足接收条件时数据产生模块侧可以立即得知并不继续产生数据。这种方式功能仿真没有问题,但是上板跑往往会出各种奇怪的问题,毕竟对于器件而言此时的axis_tready是个不小的扇出。
解决
每一级模块在设计时确保s_axis_tready信号是使用D触发器输出,而不是与m_axis_tready有组合逻辑关系。这样一来AXIS接口的流控信号axis_tready就不会因为过多级的组合逻辑而导致时序违例。虽然这样处理后从末级到数据源之间的流控信息传递延迟变大为若干个时钟,但是让出了时钟频率的提升空间和模块级数的限制。
(AXIS接口在Vivado中的open block design编辑器中有特殊优化,无需在编写代码时增加任何修饰语句即可在Vivado中识别,并提供类似于官方IP核的显示形式,如下图所示)。
经验
由于s_axis_tready的输出由D触发器直接输出,因此s_axis_tready信号仅能在上升沿时改变。我们以从AXIS接口输入数据暂存至ram等待后续处理为例,本例中如果暂存数据至ram且还未被处理,此ram地址对应的空状态信号(ram_empty)将会拉低。下图中s_axis_tvalid和s_axis_tready为AXIS的握手信号其余AXIS信号省略,addr_write为当前选中的ram地址,ram_empty为当前选中的ram地址是否为空状态。
通过观察可得,这个时序图存在问题,因为addr_write=2时该ram单元不为空,应该此时的s_axis_tready为低,而非在addr_write=3时拉低。为什么会错呢?
因为s_axis_tready没有选择使用组合逻辑将ram_empty信号直接连接在s_axis_tready上,而是判断ram_empty并在上升沿改变s_axis_tready的输出寄存器,但是addr_write由2变为3时的上升沿才可以采样到ram_empty为低电平,因此s_axis_tready输出寄存器的变化迟到,没有起到效果,且导致时序混乱。
解决这个问题的经验一句话概括为:
s_axis_tready低电平查看当前位置是否空闲,s_axis_tready高电平时查看下一个位置是否空闲。
下面的论述中将s_axis_tready的输出寄存器称作TEMP_s_axis_tready。并按s_axis_tready的两种情况分别论述。
此时分两种情况看待,第一种是s_axis_tready为低电平,此时上升沿判断ram_empty并锁存对应状态至TEMP_s_axis_tready。然而这种情况与上述会导致时序混乱的判断方式相同,为何此情况下又是可以用的了呢?请看addr_write为0时的情况,addr_write=0之后ram_empty迅速拉高,虽然s_axis_tready耽搁了一个时钟才拉高,但由于s_axis_tvalid与s_axis_tready在耽搁的那个周期内不全为1,因此并不会产生实质的数据传输,因此仅产生了一个时钟的延迟而没有导致破坏数据及时序混乱。
第二种情况是 s_axis_tready已经为高电平,此种情况与本章节开头举的反例情况相同,此时增加addr_write_next信号和ram_empty_next信号,意义分别为下一个写地址,以及下一个写地址的空满状态。s_axis_tready自addr_write选择0后拉高,拉高后每次上升沿判断ram_empty_next,在addr_write为1变2时的上升沿即可采样到ram_empty_next提供的超前1个时钟的空满状态信息,因此s_axis_tready此次在正确的位置拉低。