最近在准备FPGA校招,刷面经看到同步FIFO和异步FIFO是必考题。我理解同步FIFO空满标志用计数器就行,但异步FIFO用格雷码转换时,空满判断到底是用读写指针相等还是高两位不同?格雷码转换时打两拍会不会出现中间态?求大佬讲清楚判断逻辑和代码实现,最好有具体例子。
2026年FPGA校招,面试官让手撕Verilog实现一个同步FIFO,空满标志怎么判断才不会出错?格雷码跨时钟域怎么处理?
9 个回答

同步FIFO的坑其实不在计数器,在于你什么时候更新计数。用读写使能分别加减,读和写同时来的时候要判断是不是同一个时钟沿,这个case漏了,空满标志就会错一拍。建议计数器写成寄存器,在always块里同时判断wr_en和rd_en,写优先还是读优先想清楚,写满时不能写,读空时不能读,这两个条件要放在最前面。你先把这个逻辑理顺,异步FIFO再谈格雷码。格雷码那边,空判断确实是读指针等于写指针,满判断是高两位不同其余位相同,但要注意格雷码的满判断是基于二进制指针的,你得先转回二进制再比较,或者用格雷码本身的特性,这个容易混。

说实话,面试官让你手撕同步FIFO,重点不是看你背没背过计数器方案,而是看你有没有想过边界条件。计数器方案有个隐藏问题:如果读写同时有效,计数器应该保持不变,但很多新手会写成加一或减一,导致空满标志提前或延迟一拍。更稳的做法是直接用读写指针相等判断空,用写指针追上读指针(即写指针加一等于读指针)判断满,这样就不用额外计数器,也自然规避了同时读写的歧义。但注意,这要求FIFO深度是2的幂,且指针位宽要足够。异步FIFO那边,格雷码打两拍确实会有中间态,比如二进制从011到100,格雷码是从010到110,中间其实没有中间态,因为格雷码相邻码只有一位变化,所以打两拍采样的要么是旧值要么是新值,不会出现毛刺。你担心的其实是二进制直接打两拍才会出现中间态,格雷码正是为了消除这个。满判断的高两位不同,是基于格雷码的MSB和次MSB比较,但你需要把读写指针都转成格雷码后再比较,不能拿二进制指针直接比格雷码,这个顺序别搞反。

我建议你别只盯着代码,先把同步FIFO的三种实现方案在纸上推一遍:计数器法、扩展指针法、以及用状态机控制读写。计数器法最直观,但正如前面说的,同时读写时计数器不变,这个条件写错,空满标志就会在极端时序下出错。扩展指针法更常用在异步FIFO里,但同步FIFO也可以用:写指针和读指针位宽比地址多一位,最高位用来区分是绕了一圈还是没绕,满的条件是写指针的最高位与读指针相反,且其余位相同。这个方案的好处是空满判断不依赖计数器,逻辑延迟更小,面试时能讲出这个区别,说明你真的理解,而不仅是背模板。异步FIFO的格雷码处理,核心不是打两拍本身,而是你要明白为什么打两拍就安全:因为格雷码相邻码只有一位变化,所以跨时钟域采样时,最多只有一位可能在亚稳态,而这一位即使采错,也只是让指针暂时错误,但不会产生多位同时出错导致的逻辑冲突。打两拍是为了让亚稳态有足够时间稳定,但前提是格雷码本身,如果你用二进制指针打两拍,那两拍也没用,因为多位同时翻转时,采样结果可能是完全无效的组合。至于满判断的高两位不同,那是针对格雷码的编解码特性:格雷码的最高位和次高位组合,能区分出指针是处于前一半还是后一半循环。你写代码时,建议把二进制指针先存成寄存器,需要用格雷码比较时再现场转换,不要存两份,因为格雷码和二进制之间互相转换有组合逻辑延迟,存两份会导致两个版本不一致。最后给你个练习路径:先写同步FIFO,用随机读写验证100万拍没有空满标志错位;再改异步FIFO,用两个不同频率的时钟跑仿真,重点看读写指针跨时钟域后,空满标志会不会在边界处提前或滞后一拍。这个滞后是允许的,但不能错,因为滞后只会损失性能,错会导致数据覆盖或读空。你现在的阶段,能把同步FIFO的计数器法写对,异步FIFO能画出格雷码转换和打两拍的框图,面试基本就过关了。追问一句:你用的仿真工具是Vivado还是Quartus?因为格雷码转换的写法在综合时可能有差异,影响时序收敛。

面试手撕同步FIFO,我建议你直接放弃计数器,改用指针位宽多一位的做法。写指针和读指针都是地址位宽加1,最高位用来记录绕圈次数,空就是两个指针完全相等,满就是最高位相反且低地址位相等。这样天然规避了读写同时有效时计数器该加还是该减的争议,逻辑上更干净,面试官问起来也好解释。异步FIFO的格雷码,核心不是打两拍本身,而是你要明白为什么打两拍安全——格雷码相邻码只有一位变化,跨时钟域采样时最多只有一位处于亚稳态,而这一位即使采错,也只是让指针暂时错一位,不会导致空满判断同时出问题。实际写代码时,建议把二进制指针转格雷码后再打两拍,比较空满时再把同步过来的格雷码转回二进制,或者直接按格雷码特性比较,但转换回去更直观,不容易错。另外有个小坑:格雷码满判断的高两位不同,是基于二进制指针最高位和次高位的关系,不是随便两个高位,你得先画几张图把指针绕圈过程推一遍再动手写。

个人感觉异步FIFO这块,与其纠结打两拍会不会出现中间态,不如先跑一次仿真看看波形。格雷码打两拍确实有概率采到中间值,但那个中间值本身是合法的格雷码,不会让指针跳变多位,所以空满判断最多晚一拍生效,不会崩。真正容易翻车的是你写代码时把空满判断放到了同步之前,那才是大问题。建议顺序是:写指针进写时钟域,读指针进读时钟域,各自打两拍后再做空满判断。同步FIFO那边,如果你已经会计数器法,就再想想深度不是2的幂时怎么办,比如深度5的FIFO,计数器法照样能用,但指针加一取模要自己写,这时候计数器法反而更省事。面试官要是追问,你就说同步FIFO看场景选方案,异步FIFO才必须用格雷码,这样显得你灵活。

手撕同步FIFO时,我建议你直接把计数器方案和扩展指针方案都写一遍,面试官爱问区别。计数器法最直观,但你要注意读写同时有效时计数保持,且空满判断要放在读写使能生效之前,否则同一拍内先判断后更新会出问题。扩展指针法就是地址位宽加一,最高位记绕圈次数,写指针追上读指针且最高位相反就是满,这个方案在异步FIFO里是标配,同步FIFO用也不亏。格雷码那边,核心不是打两拍本身,而是你要理解为什么打两拍安全——相邻码只有一位变化,跨时钟域最多一位亚稳态,采错也只错一位,不会让空满同时翻转。但有个常见错误:很多人把二进制指针直接打两拍,那才是真会出现多位同时变化的中间态。正确做法是先把二进制转格雷码,打两拍同步后再转回二进制比较,或者直接用格雷码特性判断。满判断的高两位不同,是基于二进制指针的次高位和最高位关系推导的,你如果转回二进制再比较,逻辑更清晰,不容易错。面试时能讲出这个转换链条,比背代码强多了。

同步FIFO的计数器法,最大的坑不在计数逻辑,而在你把它写进always块时的优先级。个人感觉最稳的写法是:先在组合逻辑里算出下一拍计数,再在时序逻辑里更新,这样空满标志天然比读写动作晚一拍,不会出现同一拍内既判断又变化的矛盾。异步FIFO的格雷码,你其实不用太担心打两拍的中间态,因为格雷码相邻只有一位变化,采到旧值或新值都合法,空满判断最多晚一拍生效。真正要留意的是满判断的高两位不同,这是基于二进制指针绕圈次数推导的,如果你直接拿格雷码比较,容易把次高位和最高位搞混。建议写代码时,同步过来的格雷码先转回二进制,再和本地指针比较,虽然多一步转换,但正确率提升明显。面试官要是追问,你就说转换回去是为了降低推导复杂度,显得你有工程意识。

同步FIFO的计数器法,其实核心就一句话:把空满判断放在读写动作之前。很多人写代码喜欢先处理wr_en和rd_en,再去更新count,结果同一拍里count还没变,空满标志却已经拿去用了,逻辑上就差了半拍。我自己的习惯是,在always块里先算出next_count,再判断空满,最后才更新count,这样空满标志天然反映的是下一拍的状态,不会出现同一拍内又判断又变化的矛盾。至于读写同时有效,count保持不变这个case,很多人会漏,但只要你把next_count的表达式写对,比如next_count = count + wr_en – rd_en,同时有效时自然抵消,不用额外if判断。异步FIFO的格雷码,我建议你直接放弃用格雷码做空满判断,因为格雷码本身不适合直接比较大小,满判断的高两位不同是基于二进制指针绕圈次数推导的,你硬拿格雷码比较很容易搞混。正确做法是:二进制指针先转格雷码,打两拍同步到对侧时钟域,再转回二进制,然后和本地指针做比较。这样虽然多一次转换,但逻辑清晰,面试时你能讲出这个转换是为了降低比较复杂度,反而加分。打两拍会不会出中间态?格雷码相邻码只有一位变化,所以采到旧值或新值都合法,不会出现多位同时跳变的毛刺,空满判断最多晚一拍生效,不影响功能。还有一个细节:异步FIFO的深度必须是2的幂,否则格雷码回绕会出问题,面试官如果问到你没准备,就说深度非2的幂时用计数器法更合适,显得你灵活。

我建议你换个思路想这个问题:同步FIFO和异步FIFO本质上是在解决不同的事。同步FIFO里读写时钟同源,你只需要保证同一拍内判断和更新的先后关系不出错;异步FIFO里两个时钟域独立,你真正怕的不是空满判断慢了半拍,而是指针多位同时变化导致采样到一个根本不存在的位置。所以格雷码的意义,不是让空满判断更快,而是让跨时钟域采样的错误被限制在一位以内,这样即使采到旧值或新值,逻辑上依然是合法的,最多晚一拍生效。基于这个理解,你在写代码时就不会纠结打两拍会不会出中间态——格雷码相邻只有一位变化,打两拍采到的要么是变化前的值,要么是变化后的值,不存在二进制指针那种多位翻转的中间态。空满判断的具体写法,建议你记住一个原则:异步FIFO里,空判断用同步过来的读指针等于本地写指针,满判断用同步过来的写指针与本地读指针高两位不同且其余位相同。这里的关键是,格雷码的满判断是基于二进制指针的绕圈次数推导出来的,最高位和次高位组合起来表示圈数,所以你不能拿格雷码直接比大小,要先把同步过来的格雷码转回二进制,再做比较。实际写代码时,我一般把转换函数单独写一个function,这样逻辑清晰,面试官问起来也好解释。另外,同步FIFO用计数器法的时候,建议你把next_count放在组合逻辑里算,时序逻辑里只做更新,空满标志用next_count去判断,这样天然比读写动作晚半拍,不会出现同一拍内又判断又变化的矛盾。你如果能把同步FIFO的计数器法和异步FIFO的格雷码转换都自己推一遍,而不是背代码,面试的时候被追问到细节就不会慌。你现在是准备用Xilinx的板子做实验验证,还是只想先把代码逻辑跑通?
发表回答
登录后可在本页底部提交回答