FPGA FIFO 为什么会多写一拍、少读一拍?从指针回绕到 Gray 码讲透满空判断

发布时间:2026/8/23 9:33:16
FPGA FIFO 为什么会多写一拍、少读一拍?从指针回绕到 Gray 码讲透满空判断 前言FIFO 满空标志出错往往不是 RAM 有问题而是指针回绕、当前/下一状态、同周期读写和跨时钟域处理不严谨。本文从同步 FIFO 到异步 FIFO结合可综合代码、时序示意和排错清单讲清满空判断的正确写法与常见误区。1. 问题通常不是 FIFO 不会存数据刚开始写 FIFO 时很多人会觉得逻辑非常简单写指针指向下一个写入地址读指针指向下一个读取地址读写指针相等FIFO 为空写指针追上读指针FIFO 为满。实际调试时却经常出现下面这些现象FIFO 明明已经写满full却晚了一拍FIFO 已经没有数据仍然允许继续读一次仿真运行几百个周期正常指针回绕后突然出错单独读、单独写都正常同时读写时数据量统计异常同步 FIFO 仿真正常改成异步 FIFO 后上板偶发丢数异步 FIFO 的empty解除得比较慢被误认为判断错误。这些问题的根源通常集中在四个地方没有区分“地址相同”和“FIFO 状态相同”没有区分当前指针和下一拍指针用使能信号代替了真正完成的读写操作跨时钟域时直接传递了二进制指针。先记住一句话FIFO 满空判断的本质不是比较两个 RAM 地址而是判断读写指针之间的相对位置。2. 先分清同步 FIFO 还是异步 FIFO在写判断逻辑前必须先确认 FIFO 属于哪一种。类型写时钟读时钟满空判断方式同步 FIFO与读时钟相同与写时钟相同计数器或扩展指针异步 FIFO独立写时钟独立读时钟Gray 码指针跨时钟域同步两种 FIFO 的 RAM 结构可能很像但满空判断不能混用。同步 FIFO 中读写逻辑位于同一个时钟域可以直接使用计数器。异步 FIFO 中读写逻辑位于不同的时钟域不存在一个能够被两个时钟安全修改的公共计数器因此通常采用二进制指针负责地址计算二进制指针转换为 Gray 码Gray 码经过两级触发器同步到另一个时钟域在各自时钟域内判断full和empty。一、同步 FIFO 的满空判断3. 最容易写错的判断只比较地址位假设 FIFO 深度为 8地址范围为0、1、2、3、4、5、6、7读写指针都为 3 时可能有两种完全不同的状态。情况一FIFO 为空读指针3 写指针3 有效数据数量0情况二FIFO 为满读指针3 写指针3 有效数据数量8如果只比较 RAM 地址assign empty (wr_ptr rd_ptr); assign full (wr_ptr rd_ptr);显然无法区分满和空。问题在于RAM 地址只能表示“指向哪里”不能表示指针已经绕了多少圈。4. 方法一使用计数器判断满空同步 FIFO 最直观、最适合新人的方法是增加一个有效数据计数器count。empty (count 0); full (count DEPTH);每成功写入一个数据count count 1;每成功读出一个数据count count - 1;同一拍既读又写count 保持不变这里最关键的不是wr_en和rd_en而是“本次操作是否真的被 FIFO 接受”。因此应定义两个内部信号wr_fire wr_en !full; rd_fire rd_en !empty;其中wr_fire1本拍真正完成了一次写入rd_fire1本拍真正完成了一次读取。后面的指针更新、RAM 操作和计数器更新都应该使用wr_fire、rd_fire而不是直接使用外部使能信号。5. 一个很隐蔽的错误同时读写时计数器更新错了很多新人会这样写if (wr_fire) count count 1b1; if (rd_fire) count count - 1b1;单独写入时没有问题单独读取时也没有问题。但是当wr_fire和rd_fire同时为 1 时同一个always块内对count进行了两次非阻塞赋值。后面的赋值会覆盖前面的赋值最终执行的是count count - 1b1;而正确结果应该是写入一个数据读取一个数据有效数据数量不变。正确写法是统一处理case ({wr_fire, rd_fire}) 2b10: count count 1b1; 2b01: count count - 1b1; default: count count; endcase对应关系如下wr_firerd_fireFIFO 数据量变化00不变01减 110加 111不变6. 可直接使用的同步 FIFO 代码下面给出一个基础同步 FIFO。代码支持任意大于 1 的 FIFO 深度非 2 的整数次幂深度同周期读写满写保护空读保护有效数据数量输出。以下代码为可综合 SystemVerilog。module sync_fifo #( parameter int DATA_W 8, parameter int DEPTH 16 )( input logic clk, input logic rst_n, input logic wr_en, input logic [DATA_W-1:0] wr_data, input logic rd_en, output logic [DATA_W-1:0] rd_data, output logic full, output logic empty, output logic [$clog2(DEPTH1)-1:0] usedw ); localparam int ADDR_W (DEPTH 2) ? 1 : $clog2(DEPTH); localparam int COUNT_W $clog2(DEPTH 1); logic [DATA_W-1:0] mem [0:DEPTH-1]; logic [ADDR_W-1:0] wr_ptr; logic [ADDR_W-1:0] rd_ptr; logic [COUNT_W-1:0] count; logic wr_fire; logic rd_fire; /* * 只有 FIFO 接受操作时fire 信号才为 1。 */ assign wr_fire wr_en !full; assign rd_fire rd_en !empty; assign full (count DEPTH); assign empty (count 0); assign usedw count; /* * 显式处理指针回绕因此 DEPTH 不要求必须是 2 的整数次幂。 */ function automatic logic [ADDR_W-1:0] ptr_inc( input logic [ADDR_W-1:0] ptr ); if (ptr DEPTH - 1) ptr_inc 0; else ptr_inc ptr 1b1; endfunction always_ff (posedge clk or negedge rst_n) begin if (!rst_n) begin wr_ptr 0; rd_ptr 0; count 0; rd_data 0; end else begin /* * 写操作 */ if (wr_fire) begin mem[wr_ptr] wr_data; wr_ptr ptr_inc(wr_ptr); end /* * 读操作 * * 这里采用寄存器输出方式。 * rd_fire 有效后数据在时钟沿后更新到 rd_data。 */ if (rd_fire) begin rd_data mem[rd_ptr]; rd_ptr ptr_inc(rd_ptr); end /* * 有效数据数量更新 */ case ({wr_fire, rd_fire}) 2b10: count count 1b1; 2b01: count count - 1b1; default: count count; endcase end end endmodule7. 为什么计数器位宽是$clog2(DEPTH1)假设 FIFO 深度为 16。有效数据数量可能为016一共需要表示 17 种状态。如果写成logic [$clog2(DEPTH)-1:0] count;当DEPTH16时count只有 4 位只能表示015无法表示 16也就无法正常产生满标志。正确写法是logic [$clog2(DEPTH1)-1:0] count;对于深度 16$clog2(17) 55 位计数器可以表示 031自然能够覆盖 016。8. 满标志为什么经常晚一拍假设 FIFO 深度为 4当前已经存了 3 个数据count 3 full 0此时又写入一个数据。在时钟沿到来前wr_en 1 full 0 wr_fire 1时钟沿到来后count 4 full 1正确时序如下时钟周期 N N1 N2 count沿前 3 4 4 wr_en 1 1 0 full 0 1 1 wr_fire 1 0 0第 N 拍允许写入第 4 个数据是正确的。第 N1 拍full1后续写入被阻止。常见错误是使用当前写指针计算一个寄存器版full却没有比较写入后的下一指针。这会导致full晚一拍拉高从而错误接受第 5 次写入。判断标志时必须明确这个标志描述的是当前状态还是本次操作完成后的下一状态9. 方法二增加一位回绕标志对于深度为 2 的整数次幂的同步 FIFO也可以不用计数器而是给读写指针增加一位。假设 FIFO 深度为 8地址位宽3 位 扩展指针4 位指针结构可以理解为bit[3] 回绕标志 bit[2:0] RAM 地址判断方法如下assign empty (wr_ptr rd_ptr); assign full (wr_ptr[3] ! rd_ptr[3]) (wr_ptr[2:0] rd_ptr[2:0]);一般化写法assign empty (wr_ptr rd_ptr); assign full (wr_ptr[ADDR_W] ! rd_ptr[ADDR_W]) (wr_ptr[ADDR_W-1:0] rd_ptr[ADDR_W-1:0]);含义是地址位相同、回绕位相同读写指针位于同一圈FIFO 为空地址位相同、回绕位不同写指针领先读指针一整圈FIFO 为满。这种方法适合深度为2、4、8、16、32……如果 FIFO 深度不是 2 的整数次幂不能简单依赖指针自然溢出需要显式设计回绕规则。10. “预留一个位置”的判断法为什么少一个容量还有一种常见写法assign empty (wr_ptr rd_ptr); assign full (wr_ptr_next rd_ptr);也就是写指针的下一个地址等于读指针时认为 FIFO 已满。这种写法本身不一定错误但它会主动空出一个存储位置。例如 RAM 实际有 16 个地址015FIFO 最多只能保存 15 个数据。这种设计通常称为“预留一个空槽位”。如果规格要求 FIFO 深度为 16而实际只能存 15 个数据就会出现容量不一致。因此使用这种方法时要明确区分RAM 物理深度16 FIFO 可用深度15二、异步 FIFO 的满空判断11. 异步 FIFO 不能共用一个计数器异步 FIFO 中存在两个独立时钟写时钟 wclk 读时钟 rclk如果让写时钟执行count count 1;同时让读时钟执行count count - 1;就会出现一个寄存器被两个时钟驱动的问题。这既不能正常综合也无法保证硬件行为。所以异步 FIFO 一般不维护跨时钟域共享计数器而是分别维护写时钟域写指针 读时钟域读指针然后将指针同步到对方时钟域。12. 为什么不能直接同步二进制指针假设一个 4 位二进制指针从 7 变成 87 0111 8 1000四个位同时发生变化。真实硬件中各个位经过组合逻辑和布线的延迟不同不可能绝对同时到达。另一个时钟域可能采样到0000 0011 1010 1111这些值可能既不是旧值 7也不是新值 8。即使每一位都经过两级触发器同步也不能保证多位总线在同一个周期内保持一致。两级触发器主要解决的是单比特信号的亚稳态传播问题并不能自动保证多位二进制总线的一致性。13. Gray 码为什么适合传递 FIFO 指针Gray 码相邻两个数之间只有一位发生变化。二进制转 Gray 码的公式为gray (binary 1) ^ binary;例如二进制Gray 码000000001001010011011010100110101111110101111100从一个合法状态变化到下一个合法状态时只有一位改变。因此即使目标时钟域刚好在跳变附近采样通常只可能采到旧 Gray 码新 Gray 码。这非常适合用于异步 FIFO 的指针同步。需要注意Gray 码降低了多位同时跳变带来的不一致风险但跨时钟域后仍然需要两级触发器同步。14. 异步 FIFO 为什么还要保留二进制指针Gray 码适合同步但不适合直接计算 RAM 地址和执行加一运算。因此标准异步 FIFO 通常同时保存两套指针binary pointer地址计算、指针加一 Gray pointer跨时钟域同步写时钟域wbin → wgray → 同步到读时钟域读时钟域rbin → rgray → 同步到写时钟域15. 异步 FIFO 的空判断在读时钟域中本地保存当前读 Gray 指针rgray接收同步过来的写 Gray 指针wgray_sync。当两者相等时说明读指针已经追上写指针empty (rgray wgray_sync);如果empty是寄存器输出通常使用下一状态进行判断rbin_next rbin rd_fire; rgray_next (rbin_next 1) ^ rbin_next; empty_next (rgray_next wgray_sync);然后在读时钟沿更新empty empty_next;为什么比较rgray_next而不是当前rgray因为如果本拍成功读出了最后一个数据时钟沿后 FIFO 就应该进入空状态。16. 异步 FIFO 的满判断假设 FIFO 深度为DEPTH 2^ADDR_W二进制读写指针需要增加一位回绕位所以指针总宽度为PTR_W ADDR_W 1写满的含义是写指针比读指针领先一个完整 FIFO 深度对于 Gray 码指针标准判断方法是wgray_next { ~rgray_sync[PTR_W-1:PTR_W-2], rgray_sync[PTR_W-3:0] }也就是同步过来的读 Gray 指针最高两位取反其余低位保持不变再与下一写 Gray 指针比较。代码形式如下logic [PTR_W-1:0] rgray_full_cmp; assign rgray_full_cmp { ~rgray_sync[PTR_W-1:PTR_W-2], rgray_sync[PTR_W-3:0] }; assign full_next (wgray_next rgray_full_cmp);这里经常有人只取反最高一位// 错误示例 assign full_next (wgray_next {~rgray_sync[PTR_W-1], rgray_sync[PTR_W-2:0]});这是把二进制扩展指针的判断方式直接套到了 Gray 码上结果会在特定指针位置产生错误。对于经典异步 FIFO Gray 指针满判断需要取反最高两位。17. 可直接参考的异步 FIFO 代码下面代码适用于DEPTH 2^ADDR_W ADDR_W 2 FIFO 深度至少为 4代码采用二进制读写指针Gray 码跨时钟域两级同步器下一状态满空判断独立读写时钟。module async_fifo #( parameter int DATA_W 8, parameter int ADDR_W 4 )( input logic wclk, input logic wrst_n, input logic wr_en, input logic [DATA_W-1:0] wr_data, output logic full, input logic rclk, input logic rrst_n, input logic rd_en, output logic [DATA_W-1:0] rd_data, output logic empty ); localparam int DEPTH 1 ADDR_W; localparam int PTR_W ADDR_W 1; logic [DATA_W-1:0] mem [0:DEPTH-1]; /* * 写时钟域指针 */ logic [PTR_W-1:0] wbin; logic [PTR_W-1:0] wbin_next; logic [PTR_W-1:0] wgray; logic [PTR_W-1:0] wgray_next; /* * 读时钟域指针 */ logic [PTR_W-1:0] rbin; logic [PTR_W-1:0] rbin_next; logic [PTR_W-1:0] rgray; logic [PTR_W-1:0] rgray_next; /* * Gray 指针跨时钟域同步寄存器 */ (* ASYNC_REG TRUE *) logic [PTR_W-1:0] rgray_wq1; (* ASYNC_REG TRUE *) logic [PTR_W-1:0] rgray_wq2; (* ASYNC_REG TRUE *) logic [PTR_W-1:0] wgray_rq1; (* ASYNC_REG TRUE *) logic [PTR_W-1:0] wgray_rq2; logic wr_fire; logic rd_fire; logic full_next; logic empty_next; logic [PTR_W-1:0] rgray_full_cmp; assign wr_fire wr_en !full; assign rd_fire rd_en !empty; /* * 写指针下一状态 */ assign wbin_next wbin wr_fire; assign wgray_next (wbin_next 1) ^ wbin_next; /* * 读指针下一状态 */ assign rbin_next rbin rd_fire; assign rgray_next (rbin_next 1) ^ rbin_next; /* * 空判断 * 下一读指针追上同步后的写指针。 */ assign empty_next (rgray_next wgray_rq2); /* * 满判断 * 同步后的读 Gray 指针最高两位取反。 */ assign rgray_full_cmp { ~rgray_wq2[PTR_W-1:PTR_W-2], rgray_wq2[PTR_W-3:0] }; assign full_next (wgray_next rgray_full_cmp); /* * 读 Gray 指针同步到写时钟域 */ always_ff (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin rgray_wq1 0; rgray_wq2 0; end else begin rgray_wq1 rgray; rgray_wq2 rgray_wq1; end end /* * 写 Gray 指针同步到读时钟域 */ always_ff (posedge rclk or negedge rrst_n) begin if (!rrst_n) begin wgray_rq1 0; wgray_rq2 0; end else begin wgray_rq1 wgray; wgray_rq2 wgray_rq1; end end /* * 写时钟域逻辑 */ always_ff (posedge wclk or negedge wrst_n) begin if (!wrst_n) begin wbin 0; wgray 0; full 1b0; end else begin if (wr_fire) mem[wbin[ADDR_W-1:0]] wr_data; wbin wbin_next; wgray wgray_next; full full_next; end end /* * 读时钟域逻辑 */ always_ff (posedge rclk or negedge rrst_n) begin if (!rrst_n) begin rbin 0; rgray 0; rd_data 0; empty 1b1; end else begin if (rd_fire) rd_data mem[rbin[ADDR_W-1:0]]; rbin rbin_next; rgray rgray_next; empty empty_next; end end endmodule需要说明的是不同 FPGA 厂商对双时钟 RAM 的推断模板存在差异。如果综合器没有将mem推断为双口 RAM可以按照厂商推荐模板调整 RAM 描述单独例化双口 RAM直接使用厂商 FIFO IP。但无论 RAM 采用哪种实现方式Gray 指针和满空判断的基本原理不变。18. 异步 FIFO 的标志为什么会有延迟异步 FIFO 中写指针需要经过两级触发器才能进入读时钟域。因此写入第一个数据后读时钟域不会立刻看到写指针变化。示意如下写时钟域 wclk ↑ ↑ ↑ 写入数据 是 wgray G0 → G1 读时钟域 rclk ↑ ↑ ↑ 第一级同步 G0 G1 第二级同步 G0 G0 G1 empty 1 1 0从写入数据到读时钟域解除empty可能需要两个或更多读时钟周期。同理从读出数据到写时钟域解除full也会有同步延迟。这种延迟通常是安全的、保守的empty多保持一会只会暂时少读full多保持一会只会暂时少写。它不会造成溢出或下溢。因此异步 FIFO 的满空标志并不保证在另一个时钟域发生变化后立即更新。如果波形中empty晚两个读时钟周期解除不要第一时间认为 FIFO 写错了。19. 异步 FIFO 复位也可能导致满空异常异步 FIFO 有两个时钟域也通常有两个复位写时钟域复位 wrst_n 读时钟域复位 rrst_n复位后的初始状态应该是写指针 0 读指针 0 full 0 empty 1需要特别注意19.1 复位释放必须分别同步异步复位可以异步拉低但通常应在各自时钟域内同步释放。也就是异步置位时钟同步撤销否则复位信号可能在时钟沿附近释放引起寄存器恢复时间或移除时间问题。19.2 单独复位一个时钟域相当危险如果写指针已经运行到某个位置只复位读时钟域读指针重新变成 0 写指针保持原值此时两边对 FIFO 状态的理解已经不一致。除非专门设计了跨时钟域复位握手机制否则异步 FIFO 复位应被视为清空整个 FIFO读写两侧都要重新初始化。三、工程中最常见的六类错误20. 错误一只比较低地址位错误代码assign empty (wr_addr rd_addr); assign full (wr_addr rd_addr);结果无法区分满和空通常在第一次指针回绕后暴露问题。解决方式同步 FIFO 使用计数器或给指针增加回绕位异步 FIFO 使用扩展指针加 Gray 码。21. 错误二使用wr_en、rd_en更新状态错误代码if (wr_en) wr_ptr wr_ptr 1b1;当 FIFO 已满时即使数据没有真正写入写指针仍然会移动。正确写法wr_fire wr_en !full; if (wr_fire) wr_ptr wr_ptr 1b1;读操作同理rd_fire rd_en !empty;外部使能表示“请求”fire表示“请求被接受”。22. 错误三同一寄存器在一个时序块中被多次赋值错误代码if (wr_fire) count count 1b1; if (rd_fire) count count - 1b1;同时读写时后一次赋值覆盖前一次赋值。正确方式是使用case统一处理。23. 错误四当前指针和下一指针混淆如果标志在时钟沿被寄存通常需要根据下一状态计算。异步 FIFO 中应使用wgray_next rgray_next而不是只比较当前指针。否则可能出现full晚一拍empty晚一拍多接受一次写入多接受一次读取。24. 错误五把二进制总线直接跨时钟域错误做法always_ff (posedge rclk) begin wptr_sync1 wptr_binary; wptr_sync2 wptr_sync1; end两级同步器不能保证多位二进制总线的一致性。正确方式binary pointer ↓ 转换为 Gray pointer ↓ 两级触发器同步 ↓ 在目标时钟域比较25. 错误六认为异步 FIFO 标志必须立即变化写时钟域发生变化后读时钟域需要等待同步器传递指针。因此写入数据后empty可能晚几个读时钟周期解除读出数据后full可能晚几个写时钟周期解除。这种保守延迟通常不是错误。真正危险的是empty提前解除full提前解除在未知指针状态下允许非法读写。四、仿真时应该重点观察什么26. 同步 FIFO 建议加入的波形信号至少观察下面这些信号clk rst_n wr_en wr_fire wr_data wr_ptr rd_en rd_fire rd_data rd_ptr count full empty不要只看wr_en和rd_en。真正决定 FIFO 状态的是wr_fire rd_fire27. 异步 FIFO 建议加入的波形信号写时钟域wclk wr_en wr_fire wbin wgray rgray_wq1 rgray_wq2 full读时钟域rclk rd_en rd_fire rbin rgray wgray_rq1 wgray_rq2 empty通过这些信号可以清楚看到本地二进制指针何时更新Gray 指针何时变化指针何时通过第一级同步器指针何时通过第二级同步器满空标志何时更新。28. 同步 FIFO 建议加入的断言在仿真环境中可以增加一些基本断言。/* * count 不允许超过 FIFO 深度。 */ assert property ( (posedge clk) disable iff (!rst_n) count DEPTH ); /* * empty 必须与 count0 一致。 */ assert property ( (posedge clk) disable iff (!rst_n) empty (count 0) ); /* * full 必须与 countDEPTH 一致。 */ assert property ( (posedge clk) disable iff (!rst_n) full (count DEPTH) );还可以监控非法请求always_ff (posedge clk) begin if (rst_n) begin if (wr_en full) $warning(FIFO overflow request); if (rd_en empty) $warning(FIFO underflow request); end end这里监控的是非法请求不一定意味着 FIFO 内部已经损坏。只要内部使用wr_fire wr_en !full; rd_fire rd_en !empty;就能阻止非法操作真正改变状态。五、推荐的测试场景29. 不要只测试“先写满再读空”至少应覆盖以下场景。场景一复位后立即读取预期empty 1 rd_fire 0 读指针不移动场景二连续写到满预期成功写入 DEPTH 个数据 full 拉高 第 DEPTH1 次写请求被阻止场景三连续读到空预期成功读取 DEPTH 个数据 empty 拉高 后续读请求被阻止场景四同时读写预期读写都成功时数据数量保持不变场景五多次指针回绕不能只运行十几个时钟周期。建议让读写指针至少回绕几十次检查长时间运行后是否出现数据顺序错误满空标志错误指针错位。场景六异步时钟频率不同例如wclk 100 MHz rclk 73 MHz不要只使用整数倍时钟。还应改变两个时钟的初始相位增加跨时钟域边界条件覆盖。场景七随机读写随机产生wr_en rd_en wr_data同时在测试平台中维护一个软件队列。FIFO 每成功写入一个数据就向软件队列压入数据每成功读取一个数据就与软件队列队首比较。这是验证 FIFO 数据顺序最有效的方法之一。六、排错速查表波形现象优先检查写满后还能多写一次是否使用下一写指针计算full读空后还能多读一次是否使用下一读指针计算empty同时读写时计数器变化是否对count进行了两次非阻塞赋值第一次回绕后满空错误是否缺少回绕位16 深度只能写 15 个是否使用了预留空槽位方案仿真正常上板偶发错误是否直接跨时钟域传递二进制指针异步 FIFO 写入后empty未立即解除是否属于两级同步器的正常延迟复位后偶发错误复位是否在各时钟域同步释放full一直不解除读 Gray 指针是否正确同步到写时钟域empty一直不解除写 Gray 指针是否正确同步到读时钟域综合后没有推断出 RAMRAM 描述是否符合目标器件模板数据顺序错误但满空正常RAM 读延迟、读数据有效时序是否理解错误七、最后总结FIFO 满空判断最容易出错的地方不是代码有多复杂而是几个概念容易混在一起。同步 FIFO 中要注意地址相同不一定代表 FIFO 为空也可能代表 FIFO 为满推荐使用有效数据计数器或者增加一位回绕标志指针和计数器必须根据wr_fire、rd_fire更新同时读写时数据数量应该保持不变非 2 的整数次幂深度需要显式处理指针回绕。异步 FIFO 中要注意二进制指针不能直接跨时钟域Gray 码跨时钟域后仍然需要两级同步器空判断在读时钟域完成满判断在写时钟域完成满判断需要对同步读 Gray 指针的最高两位取反异步 FIFO 的满空标志存在保守同步延迟复位应在各自时钟域同步释放单侧复位要谨慎。真正稳定的 FIFO 设计必须保证下面这条关系始终成立FIFO 内有效数据数量 成功写入次数 - 成功读取次数这里说的是成功操作次数而不是外部请求次数。只要围绕这条关系检查指针、计数器、满空标志和跨时钟域同步大多数 FIFO 问题都能很快定位。#FPGA #Verilog #System #Verilog #FIFO 异步FIFO #同步FIFO #Gray码 #CDC #跨时钟域 #数字电路

相关新闻