基于 Ready/Valid 握手在块 RAM 中实现 AXI FIFO
初次接触 AXI 接口设计模块时,我对其精简流控方式感到惊讶。不同于依赖 busy/valid、full/valid 或 empty/valid 对的传统协议,AXI 仅用两个信号:ready 和 valid. 最初的挫败感很快变成了着迷。
AXI 将流控直接嵌入握手,省去额外状态线。基本规则虽简单,但映射到 FPGA 硬件时需细致注意。本教程演练一个运行在块 RAM 上的 VHDL AXI FIFO 实现。
AXI 解决延迟一周期问题
两个时钟同步模块交换数据流时,防止过读和过写是常见挑战。核心问题在于每个模块只能在发生后一个时钟周期观测对方输出。
上图展示顺序模块写入使用经典 写使能/满 信号方案的 FIFO。写入方置位 wr_en 推数据入 FIFO,FIFO 满时拉高 full。问题在于 full 恰在时钟上升沿置位,写入方同周期无法再响应。解决方案是提供前瞻信号如 almost_empty,给上游逻辑提前量。
Ready/Valid 握手
AXI 流接口用双信号握手解决此时序困境:ready 和 valid。接收方驱动 ready;高电平表示准备好接收新数据。发送方驱动 valid;高电平表示总线上数据稳定可采样。仅当两信号同一上升沿均高时发生传输。
波形演示单数据项传输。数据在上升沿采样,同常规同步设计。
实现
用 VHDL 构建 AXI FIFO 有多种方式。移位寄存器虽可行,但环形缓冲区配合块 RAM 最效率且可扩展。下面呈现模块化设计,将逻辑分离到专用进程以利清晰度和综合器友好。
实体
实体定义两个泛型参数——ram_width 和 ram_depth——设定字宽和 FIFO 深度。有效容量为 ram_depth - 1,因保留一个槽位区分满/空。
entity axi_fifo is generic ( ram_width : natural; ram_depth : natural ); port ( clk : in std_logic; rst : in std_logic; -- AXI input interface in_ready : out std_logic; in_valid : in std_logic; in_data : in std_logic_vector(ram_width - 1 downto 0); -- AXI output interface out_ready : in std_logic; out_valid : out std_logic; out_data : out std_logic_vector(ram_width - 1 downto 0) ); end axi_fifo;
接口遵循 AXI 惯例:输入侧用 in_ready 和 in_valid,输出侧镜像信号但方向反转。此对称性允许多 FIFO 无缝级联。
信号声明
先定义匹配泛型宽深的 RAM 类型,再实例化数组:
-- The FIFO is full when the RAM contains ram_depth - 1 elements type ram_type is array (0 to ram_depth - 1) of std_logic_vector(in_data'range); signal ram : ram_type;
声明可表示 RAM 任意地址的索引子类型,及四个核心信号:head、tail、count、延迟副本 count_p1:
subtype index_type is natural range ram_type'range; signal head : index_type; signal tail : index_type; signal count : index_type; signal count_p1 : index_type;
保存 ready/valid 输出内部版本,避免架构内读 out 信号:
signal in_ready_i : std_logic; signal out_valid_i : std_logic;
辅助信号跟踪上周期是否同时发生读写,对正确设置近空时 out_valid 关键:
signal read_while_write_p1 : std_logic;
子程序
next_index 函数根据读/写握手完成情况决定下一地址,到达数组末尾时绕回。
function next_index( index : index_type; ready : std_logic; valid : std_logic) return index_type is begin if ready = '1' and valid = '1' then if index = index_type'high then return index_type'low; else return index + 1; end if; end if; return index; end function;
index_proc 过程封装同步更新 head 或 tail 指针的逻辑。避免重复代码,保持架构整洁。
procedure index_proc( signal clk : in std_logic; signal rst : in std_logic; signal index : inout index_type; signal ready : in std_logic; signal valid : in std_logic) is begin if rising_edge(clk) then if rst = '1' then index <= index_type'low; else index <= next_index(index, ready, valid); end if; end if; end procedure;
内部信号赋给输出
in_ready <= in_ready_i; out_valid <= out_valid_i;
更新头尾指针
实例化辅助过程两次——一次写指针 (head),一次读指针 (tail):
PROC_HEAD : index_proc(clk, rst, head, in_ready_i, in_valid); PROC_TAIL : index_proc(clk, rst, tail, out_ready, out_valid_i);
推断块 RAM
大多数 FPGA 块 RAM 为同步。为让综合器推断 RAM 原语,将读写操作置于时钟进程内,避免显式使能信号:
PROC_RAM : process(clk) begin if rising_edge(clk) then ram(head) <= in_data; out_data <= ram(next_index(tail, out_ready, out_valid_i)); end if; end process;
in_valid 高周期无条件写入,读数据每周期更新以便消费者在 FIFO 有数据时观测有效字。
统计 FIFO 元素数
count 信号反映当前占用。head 领先 tail 时计数为差值;头指针绕回时加总深度:
PROC_COUNT : process(head, tail) begin if head < tail then count <= head - tail + ram_depth; else count <= head - tail; end if; end process;
延迟副本 count_p1 用于检测上周期 FIFO 为空,影响 out_valid:
PROC_COUNT_P1 : process(clk) begin if rising_edge(clk) then if rst = '1' then count_p1 <= 0; else count_p1 <= count; end if; end if; end process;
更新 ready 输出
写入方应知 FIFO 能否接受更多数据。count 小于 ram_depth - 1 时 ready 置高:
PROC_IN_READY : process(count) begin if count < ram_depth - 1 then in_ready_i <= '1'; else in_ready_i <= '0'; end if; end process;
检测同步读写
同周期读写发生时,FIFO 占用不变,但内部状态须反映近空时值将在两周期后才可用。故在 read_while_write_p1 捕获此事件:
PROC_READ_WHILE_WRITE_P1: process(clk) begin if rising_edge(clk) then if rst = '1' then read_while_write_p1 <= '0'; else read_while_write_p1 <= '0'; if in_ready_i = '1' and in_valid = '1' and out_ready = '1' and out_valid_i = '1' then read_while_write_p1 <= '1'; end if; end if; end if; end process;
更新 valid 输出
out_valid 告诉下游逻辑何时 out_data 持有可用字。下面组合进程默认置高,两种边界情况清零:
PROC_OUT_VALID : process(count, count_p1, read_while_write_p1) begin out_valid_i <= '1'; -- If the FIFO is empty or was empty in the previous cycle if count = 0 or count_p1 = 0 then out_valid_i <= '0'; end if; -- If a simultaneous read/write occurred when the FIFO had only one item if count = 1 and read_while_write_p1 = '1' then out_valid_i <= '0'; end if; end process;
第一条件保证首次写入后的传播延迟期间 out_valid 保持低。第二条件防止 FIFO 看似保持满一周期实则无数据输出的情况。
在 Vivado 中综合
在 Xilinx Vivado 综合此设计,在 Settings 对话框 (General → Generics/Parameters) 为泛型提供具体值。选择匹配目标 Zynq 器件 RAMB36E1 原语的深度。实现后资源利用适度——一个块 RAM 加少量 LUT 和 FF。


AXI 不止 Ready/Valid
AXI (Advanced eXtensible Interface) 是 ARM AMBA 总线架构组成部分,除简单 ready/valid 握手外提供丰富特性。深入理解可参考:
- Wikipedia: AXI
- ARM AXI 介绍
- Xilinx AXI 介绍
- AXI4 规范
VHDL