前言FIFO 是 FPGA 面试里非常高频的问题。本文从新人视角讲清楚 FIFO 的基本作用、同步 FIFO 和异步 FIFO 的区别、空满判断方法、异步 FIFO 为什么要用 Gray 码以及 almost full 到底是干什么的。最后给出一套面试时可以直接套用的回答模板避免只会背概念。关键词FPGA、FIFO、同步 FIFO、异步 FIFO、almost full、empty、full、Gray 码、CDC、Verilog、面试1. 先说结论FIFO 面试不是考你背缩写FIFO全称 First In First Out先进先出。在 FPGA 里FIFO 的作用可以简单理解为前级先写进去的数据后级先读出来。它本质上是一个带读写控制的数据缓冲区。面试官问 FIFO一般不是想听你说“先进先出”四个字。真正想考的是下面这些问题你知不知道 FIFO 用在什么场景你能不能区分同步 FIFO 和异步 FIFO你是否理解 full、empty 怎么产生你知不知道异步 FIFO 为什么要用 Gray 码你能不能解释 almost full 的工程意义你有没有意识到 overflow 和 underflow 是严重问题。所以这篇文章不堆概念直接按工程和面试两条线讲。2. FIFO 在 FPGA 里到底解决什么问题FIFO 常见作用有三个。2.1 缓冲数据比如前级模块一下子来了一串数据后级模块暂时处理不过来。这时可以用 FIFO 先把数据存起来。前级模块 - FIFO - 后级模块前级只要 FIFO 没满就可以继续写。后级只要 FIFO 不空就可以继续读。2.2 解耦速率前级和后级处理速度不完全一样时FIFO 可以吸收短时间速率差。比如ADC 数据输入 - FIFO - FPGA 内部处理模块ADC 可能持续吐数据而内部模块可能偶尔忙不过来。FIFO 就像一个水池短时间内先进来的数据先存着。2.3 跨时钟域如果写端和读端不是同一个时钟就要用异步 FIFO。比如摄像头像素时钟域 - 异步 FIFO - 系统处理时钟域这种场景不是普通同步 FIFO 能解决的因为它涉及 CDC也就是跨时钟域问题。3. 同步 FIFO 和异步 FIFO 的核心区别一句话同步 FIFO读写用同一个时钟。异步 FIFO写端和读端用不同的时钟。3.1 同步 FIFO同步 FIFO 只有一个时钟clk wr_en rd_en din dout full empty写操作和读操作都在clk的上升沿发生。典型应用同一个模块内部做数据缓存同一个时钟域内前后级速率不完全一致流水线模块之间做缓冲AXI-Stream 内部同频缓冲。同步 FIFO 的特点是结构相对简单不涉及跨时钟域full/empty 判断比较直接可以用计数器count判断当前 FIFO 里有多少数据。3.2 异步 FIFO异步 FIFO 有两个时钟wr_clk rd_clk wr_en rd_en din dout full empty写端在wr_clk下工作。读端在rd_clk下工作。典型应用ADC 采样时钟到系统时钟摄像头 pixel clock 到 FPGA 处理时钟Ethernet RX 时钟到内部处理时钟SPI/UART 接收时钟域到系统时钟域两个不同时钟频率模块之间传输数据。异步 FIFO 的特点是用来解决多 bit 数据跨时钟域内部需要处理 CDC读写指针需要跨时钟域同步通常会用 Gray 码降低跨域采样风险工程里优先用厂商 IP不建议新人手写。4. 面试怎么回答同步 FIFO面试官问你了解同步 FIFO 吗不要只答先进先出读写同一个时钟。这样太浅了。可以这样答同步 FIFO 是读写在同一个时钟域下工作的 FIFO。内部一般有存储 RAM、写指针、读指针和计数器。写使能有效且 FIFO 未满时数据写入写指针位置写指针加一读使能有效且 FIFO 非空时从读指针位置读出数据读指针加一。full 可以由 count 等于深度判断empty 可以由 count 等于 0 判断。如果同一拍同时读写count 通常保持不变。这个回答就比“先进先出”强很多。5. 同步 FIFO 的基本结构同步 FIFO 可以理解成四部分存储区 RAM 写指针 wr_ptr 读指针 rd_ptr 数据计数 count逻辑关系如下写入成功wr_en !full 读出成功rd_en !empty写入成功时mem[wr_ptr] din wr_ptr wr_ptr 1 count count 1读出成功时dout mem[rd_ptr] rd_ptr rd_ptr 1 count count - 1同时读写成功时wr_ptr 1 rd_ptr 1 count 不变这里要注意工程里不同 FIFO IP 对边界情况的处理可能不一样比如 FIFO 满时同一拍又读又写是否允许写入取决于具体实现和 IP 配置。但面试时你至少要讲清楚基本原则写不能写爆读不能读空。6. 一个简化版同步 FIFO 代码下面这段代码主要用于理解结构不建议直接当生产代码使用。module sync_fifo #( parameter DATA_WIDTH 8, parameter DEPTH 16, parameter ADDR_WIDTH 4 )( input wire clk, input wire rst_n, input wire wr_en, input wire [DATA_WIDTH-1:0] din, output wire full, input wire rd_en, output reg [DATA_WIDTH-1:0] dout, output wire empty, output reg [ADDR_WIDTH:0] count ); reg [DATA_WIDTH-1:0] mem [0:DEPTH-1]; reg [ADDR_WIDTH-1:0] wr_ptr; reg [ADDR_WIDTH-1:0] rd_ptr; wire wr_fire; wire rd_fire; assign full (count DEPTH); assign empty (count 0); assign wr_fire wr_en !full; assign rd_fire rd_en !empty; always (posedge clk or negedge rst_n) begin if (!rst_n) begin wr_ptr 0; rd_ptr 0; dout 0; count 0; end else begin if (wr_fire) begin mem[wr_ptr] din; wr_ptr wr_ptr 1b1; end if (rd_fire) begin dout mem[rd_ptr]; rd_ptr rd_ptr 1b1; end case ({wr_fire, rd_fire}) 2b10: count count 1b1; 2b01: count count - 1b1; 2b11: count count; default: count count; endcase end end endmodule这段代码体现了同步 FIFO 的核心思路wr_ptr管写地址rd_ptr管读地址count记录 FIFO 当前数据量count DEPTH表示满count 0表示空。实际工程里还要考虑DEPTH 是否为 2 的幂RAM 推断方式读数据是同步读还是异步读full 状态下同时读写是否允许写reset 后输出数据是否有效时序和资源是否符合目标器件。所以面试可以讲原理工程建议用成熟 IP 或经过充分验证的模块。7. empty 和 full 到底怎么判断这是面试高频问题。7.1 用 count 判断最直观的方法是用count。empty: count 0 full : count DEPTH优点好理解适合同步 FIFOalmost full / almost empty 很容易做。缺点count位宽要正确大深度时 count 比较器可能影响时序异步 FIFO 不能简单用一个 count 同时服务两个时钟域。7.2 用读写指针判断另一种方法是用读写指针。empty: wr_ptr rd_ptr但 full 比较麻烦。因为当写指针追上读指针时有两种可能情况 1FIFO 空 情况 2FIFO 满如果只看低位地址空和满都可能表现为wr_ptr rd_ptr。常见解决方法有两种浪费一个存储单元保留一个空位给指针多加一位扩展位用来区分是否绕了一圈。面试里可以这样答如果只用地址指针读写指针相等既可能表示空也可能表示满所以需要额外机制区分。常见做法是预留一个空位或者给指针增加一位 wrap bit用它判断读写指针是否处在同一轮循环。8. 异步 FIFO 为什么比同步 FIFO 难异步 FIFO 的难点不是 RAM而是指针跨时钟域。写端需要知道读指针在哪里 我还能不能继续写 FIFO 是否满读端需要知道写指针在哪里 我还能不能继续读 FIFO 是否空问题是写指针属于写时钟域读指针属于读时钟域两个时钟没有固定相位关系。所以异步 FIFO 内部必须做 CDC。通常做法是写时钟域 维护写指针 wr_ptr_bin 转成 Gray 码 wr_ptr_gray 同步读指针 gray 到写时钟域 判断 full 读时钟域 维护读指针 rd_ptr_bin 转成 Gray 码 rd_ptr_gray 同步写指针 gray 到读时钟域 判断 empty重点来了异步 FIFO 不是把 data 每一位打两拍。它通常是用 RAM 存数据只跨域同步读写指针。这和上一篇 CDC 文章的结论是一致的多 bit 数据流跨时钟域优先用异步 FIFO。9. 异步 FIFO 为什么要用 Gray 码普通二进制计数器有个问题相邻两个数之间可能多个 bit 同时变化。比如0111 - 10004 个 bit 都变了。如果这个多 bit 指针跨时钟域时被目的域采样目的域可能看到一个中间错误值。Gray 码的特点是相邻两个数只有 1 个 bit 变化。比如二进制000 - 001 - 010 - 011 - 100 Gray 000 - 001 - 011 - 010 - 110每次只变一个 bit跨时钟域同步时风险会小很多。所以异步 FIFO 里常见做法是二进制指针用于本地地址加一 Gray 指针用于跨时钟域同步 同步回来后再参与 full/empty 判断。面试可以这样答异步 FIFO 需要把读写指针跨时钟域同步。如果直接同步二进制指针指针跳变时可能多个 bit 同时变化目的时钟域可能采到错误组合。Gray 码相邻状态只变化一位可以降低跨域采样时的多 bit 不一致风险所以异步 FIFO 通常用 Gray 码同步指针。这个回答基本够用了。10. 异步 FIFO 的 empty 怎么产生empty 是读时钟域使用的信号。它应该在rd_clk域产生。读端关心的是我还能不能读所以读端需要知道写端写到了哪里。写指针wr_ptr_gray会被同步到读时钟域。当读指针追上同步过来的写指针时说明没有新数据可读empty 读指针 同步到读域的写指针工程里通常会用“下一拍读指针”和“同步后的写指针”做比较提前生成 empty保证时序和标志行为正确。但新人面试时不需要把公式背得很复杂。你只要讲清楚empty 在读时钟域产生它通过同步过来的写指针判断 FIFO 是否还有数据。11. 异步 FIFO 的 full 怎么产生full 是写时钟域使用的信号。它应该在wr_clk域产生。写端关心的是我还能不能写所以写端需要知道读端读到了哪里。读指针rd_ptr_gray会被同步到写时钟域。当写指针即将追上读指针并且已经绕了一圈就表示 FIFO 满。可以简单理解为full 写指针下一位置追上读指针但因为 FIFO 是环形缓冲区只看低位地址不够还要看高位 wrap 信息。面试时可以这样答full 在写时钟域产生它通过同步过来的读指针判断 FIFO 是否还有空间。异步 FIFO 常用扩展位区分空和满写指针下一值如果等于读指针对应的满条件就拉高 full。不要把 full 放到读时钟域里直接用也不要把 empty 放到写时钟域里直接用。这是新人常犯错误。12. almost full 是什么almost full接近满。它不是 full。它的意思是FIFO 还没满但已经快满了需要提前通知上游减速或停止写入。比如 FIFO 深度是 1024。你可以设置count 900 时almost_full 拉高 count 1024 时full 拉高这样上游看到almost_full后就可以提前停下来避免真正写到 full。13. 为什么需要 almost full很多新人会问既然有 full为什么还要 almost full因为真实工程里上游不是看到 full 后马上就能停。中间可能有延迟。比如上游模块 - pipeline 1 - pipeline 2 - FIFO当 FIFO 拉高 full 时前面 pipeline 里可能已经有几个数据正在路上。这几个数据停不住如果 FIFO 已经满了它们继续写进来就会 overflow。所以要提前告警almost_full 拉高 上游开始停止发数据 路上的数据继续进入 FIFO 最终刚好不写爆almost full 的本质是给系统留余量。一句话full 是最后底线almost full 是提前刹车。这句话面试里很好用。14. almost full 阈值怎么设置almost full 阈值不是随便拍脑袋。它至少要考虑上游收到 almost full 后多久能停止中间 pipeline 里还有多少拍数据总线协议是否允许立即停止下游读数据速度是否稳定写端是否存在 burstFIFO 深度是否足够吸收突发。举个例子FIFO 深度1024 上游停止延迟8 拍 中间 pipeline4 拍 协议额外延迟4 拍 安全余量8 拍那么至少要提前8 4 4 8 24 个位置可以设置almost_full count 1000当然实际工程还要结合数据流量仿真和压力测试。面试时可以这样答almost full 的阈值一般根据系统反压延迟和突发长度来设置。它要保证上游收到 almost full 后即使还有若干拍数据已经在路上FIFO 也不会真正 overflow。这个回答比“快满了就拉高”更像工程师。15. almost empty 是什么和 almost full 类似almost empty 表示 FIFO 快空了。它常用于提醒下游数据快没了别再连续读了或者用于某些 burst 读取场景FIFO 里数据量达到一定数量后再开始读比如做视频、音频、DMA 时有时不希望 FIFO 里刚有一个数据就开始读而是希望先积累一段数据避免读着读着断流。不过 FPGA 面试里almost full 比 almost empty 更常被问因为 almost full 直接关系到 overflow 风险。16. overflow 和 underflow 一定要讲清楚FIFO 面试里还有两个词overflow FIFO 满了还继续写 underflow FIFO 空了还继续读这两个都是错误使用。16.1 overflow当full 1时如果还写wr_en 1就可能覆盖还没读走的数据。结果是数据丢失、顺序错乱、系统异常。16.2 underflow当empty 1时如果还读rd_en 1读出来的数据可能无效或者重复读到旧数据。所以工程里一般写成assign wr_fire wr_en !full; assign rd_fire rd_en !empty;而不是只看wr_en、rd_en。面试时可以说FIFO 的写入条件不是 wr_en而是 wr_en !full读取条件不是 rd_en而是 rd_en !empty。这样可以避免 overflow 和 underflow。17. standard FIFO 和 FWFT FIFO有些面试官还会问FIFO 的 standard mode 和 FWFT mode 有什么区别FWFT 是 First Word Fall Through首字直通。17.1 Standard FIFO标准 FIFO 通常是rd_en 有效后下一个周期 dout 才有效也就是说你要先发读使能数据才出来。17.2 FWFT FIFOFWFT 模式下只要 FIFO 非空第一笔数据就已经出现在dout上。empty 拉低时dout 已经是有效数据这对流式接口很方便因为下游看到非空就可以直接取数据。面试时不用讲太深但可以补一句具体读延迟和 dout 有效时机要看 FIFO IP 的配置不能自己想当然。这句话很工程化。18. FIFO 深度怎么估算FIFO 深度不是越大越好。深度太小容易 overflow吸收不了突发系统容易卡顿。深度太大浪费 BRAM增加延迟调试时数据滞留时间变长。简单估算思路FIFO 深度 最大突发写入量 - 同期可读出量 安全余量举个简单例子写端一次 burst 写 256 个数据 读端在这段时间只能读走 100 个 那 FIFO 至少要能吸收 156 个数据 再加安全余量可以开 256 或 512如果涉及跨时钟域还要考虑写时钟频率读时钟频率burst 间隔下游暂停时间上游是否能被反压系统允许的最大延迟。面试时可以这样答FIFO 深度主要根据写入突发、读出速率、反压延迟和安全余量来定不是随便选。工程里一般先估算最坏情况再用仿真验证 overflow 和 underflow 是否会出现。19. FIFO 能不能解决所有跨时钟域问题不能。FIFO 适合多 bit 数据流跨时钟域。但如果只是单 bit 控制信号用 FIFO 就太重了。比如start done enable locked irq这些信号更适合用双触发器同步pulse togglerequest/ack 握手。如果是连续数据比如data[31:0] valid packet stream adc_sample pixel_data就更适合异步 FIFO。所以回答时要体现选择能力单 bit 电平用双触发器单 bit 事件用 toggle 或握手多 bit 低速配置用握手多 bit 连续数据流用异步 FIFO。20. 面试高频问题与参考回答问题 1FIFO 是什么参考回答FIFO 是先进先出的数据缓冲结构先写入的数据会先被读出。在 FPGA 中常用于数据缓存、速率匹配、突发吸收以及跨时钟域数据传输。它通常包含存储 RAM、写指针、读指针以及 empty/full 等状态标志。问题 2同步 FIFO 和异步 FIFO 有什么区别参考回答同步 FIFO 的读写在同一个时钟域下进行控制逻辑比较简单可以用 count 或读写指针判断空满。异步 FIFO 的写端和读端属于不同时钟域主要用于 CDC 场景。异步 FIFO 内部通常会把读写指针转成 Gray 码后跨时钟域同步再在本地时钟域产生 full 和 empty。问题 3同步 FIFO 的 full 和 empty 怎么判断参考回答如果用 count 实现count 等于 0 时 emptycount 等于 FIFO 深度时 full。如果用读写指针实现读写指针相等可能表示空也可能表示满所以需要额外机制区分比如保留一个空位或者给指针增加一位 wrap bit。问题 4异步 FIFO 为什么要用 Gray 码参考回答因为异步 FIFO 需要把读写指针跨时钟域同步。如果直接同步二进制指针指针变化时可能多个 bit 同时翻转目的时钟域可能采到错误组合。Gray 码相邻状态只变化 1 bit可以降低跨域采样时多 bit 不一致的风险所以常用于异步 FIFO 指针同步。问题 5almost full 是什么为什么不用 full 就行参考回答almost full 表示 FIFO 还没满但已经接近满。它的作用是提前通知上游停止或减速。因为真实系统里上游收到反压信号后不会立刻停下来中间可能还有 pipeline 延迟和已经发出的数据。如果等 full 再停可能已经来不及导致 overflow。所以 almost full 是提前刹车full 是最后底线。问题 6almost full 阈值怎么设置参考回答阈值要根据上游停止延迟、pipeline 深度、burst 长度、协议反压延迟和安全余量来设置。原则是 almost full 拉高后即使还有若干拍数据继续进入 FIFO也不能写爆 FIFO。问题 7FIFO 满了还能不能写空了还能不能读参考回答正常情况下满了不能写空了不能读。工程里写入条件应该是 wr_en !full读取条件应该是 rd_en !empty。满了继续写会 overflow可能覆盖未读数据空了继续读会 underflow读出的数据无效或重复。21. 新人最容易犯的 7 个错误错误 1把异步 FIFO 当普通 RAM 用异步 FIFO 的重点不是 RAM而是跨时钟域的读写指针同步和空满判断。错误 2在写时钟域直接使用 emptyempty通常是读时钟域信号。写端要关心的是full读端要关心的是empty。错误 3在读时钟域直接使用 fullfull通常是写时钟域信号。不要跨域乱用状态标志。错误 4看到 full 才停写很多系统看到 full 再停已经晚了。有 pipeline 或 burst 时应该用 almost full 提前反压。错误 5忽略 simultaneous read/write同步 FIFO 同一拍可能同时读写。这时指针都要更新但 count 可能不变。边界条件要特别小心。错误 6自己手写异步 FIFO 但不验证异步 FIFO 涉及 CDC、Gray 码、空满判断、复位同步。新人不建议直接手写上板优先用厂商 IP。错误 7不看 IP 文档里的读延迟不同 FIFO 模式下dout的有效时机不一样。standard mode 和 FWFT mode 行为不同不能凭感觉接后级逻辑。22. 工程里更推荐怎么做如果是正式项目我建议这样选同一时钟域、简单缓存 同步 FIFO可以自己写也可以用 IP 跨时钟域、多 bit 数据流 异步 FIFO优先用厂商 IP AXI-Stream 跨时钟 用 AXIS Clock Converter 或 AXIS FIFO 大容量缓存 用 BRAM/URAM FIFO 小容量缓存 可以用 LUTRAM / SRL FIFO 面试学习 手写同步 FIFO理解指针和 count 理解异步 FIFO 原理但工程优先用 IP在 AMD/Xilinx 平台可以关注FIFO Generator XPM_FIFO_SYNC XPM_FIFO_ASYNC XPM_FIFO_AXIS AXIS Clock Converter在 Intel 平台可以关注scfifo dcfifo dcfifo_mixed_widths不要为了证明自己会写就在正式项目里手撸异步 FIFO。能稳定交付比炫技更重要。23. 本文总结FIFO 是 FPGA 面试的高频题但它不是一个简单的“先进先出”概念题。同步 FIFO 的重点是同一个时钟域 读写指针 count full/empty 同时读写异步 FIFO 的重点是两个时钟域 CDC Gray 码指针 指针同步 本地生成 full/emptyalmost full 的重点是提前反压 避免 overflow 给 pipeline 和 burst 留余量最后用几句话总结同步 FIFO同一时钟域缓存数据。 异步 FIFO不同时钟域传输多 bit 数据流。 empty读端判断还能不能读。 full写端判断还能不能写。 almost full提前通知上游刹车。 Gray 码降低异步指针同步时的多 bit 采样风险。面试时不要只背 FIFO 是先进先出。你要能讲出它为什么用、怎么判断空满、异步 FIFO 为什么需要 Gray 码、almost full 为什么比 full 更适合做反压。能讲到这一步面试官基本会觉得你不是只看过概念而是真的接触过 FPGA 工程。