最近在准备FPGA校招,看到很多面经里都提到手撕FIFO。我想问一下,面试官让我手撕一个深度为16的同步FIFO,写指针和读指针怎么比较空满?如果是异步FIFO,格雷码转换后空满标志怎么生成?深度不是2的幂次怎么办?比如深度为12的FIFO,地址位宽怎么算?求大佬分享具体代码和仿真波形,最好能讲清楚什么时候用同步复位什么时候用异步复位。
2026年FPGA校招,面试官问手撕Verilog实现一个FIFO,深度怎么算?同步和异步FIFO的空满标志生成逻辑有啥区别?求代码和波形
11 个回答

关于复位方式,我建议你面试时优先说同步复位。原因很简单:同步复位对时钟边沿敏感,综合工具更容易推断出寄存器行为,而且不会出现异步复位释放时可能发生的亚稳态问题。但有个例外——异步FIFO中的跨时钟域同步器,那个必须用异步复位,因为上电后第一个时钟沿之前就要让所有指针归零。你问深度12怎么算地址位宽,数学上log2(12)向上取整是4位,但4位地址最大能到15,所以FIFO实际深度是16,你只用12个位置。空满标志生成时,不能用简单的最高位比较了,要改为计数法,就是用一个计数器记录当前FIFO里有多少数据,读使能有效且非空时减1,写使能有效且非满时加1。计数器等于0是空,等于12是满。这种写法比格雷码扩展一位更直观,但注意计数器位宽要能表示0到12,至少4位。你写代码时记得把复位类型声明对,同步复位写always@(posedge clk),异步复位写always@(posedge clk or negedge rst_n)。仿真时故意让读写时钟频率不一样,看看空满标志会不会提前或滞后。

先说深度计算这个点。深度16,地址位宽4位,这个没问题。同步FIFO判空满,最常见的做法是用扩展一位的计数器,比如深度16就用5位cnt,最高位用来区分空和满。当读写指针完全相等时,如果复位过就是空,如果不是复位后的相等就是写满了一圈追上读,那就是满。你写代码的时候注意别把空和满的条件搞反了就行。异步FIFO的话,格雷码转换后判空满,核心是看格雷码的最高两位,如果相等就是空,如果最高位相反且次高位相同就是满。深度不是2的幂次时,比如深度12,地址位宽还是4位,但实际只用到0到11,空满判断逻辑要额外处理地址回卷时的边界,常见做法是用地址计数器扩展一位后用比较器,或者直接对深度取模。你仿真波形时重点看读写使能同时有效时指针的变化。追问一句:你目前是用Vivado还是Quartus做仿真?不同工具对格雷码同步器的初始值处理有细微差别。

手撕FIFO这件事,面试官想看的不是你背代码,而是你能不能根据场景做取舍。先说深度16的同步FIFO,最简单可靠的写法是用一个计数器记录当前存储的数据个数,读使能且非空时计数减1,写使能且非满时加1。计数器等于0是空,等于16是满。这种写法没有任何地址回卷的麻烦,代码量小,面试时5分钟就能写完。但面试官可能会追问:计数器要多大?深度16只需要5位,因为要数0到16共17个状态。另一种写法是用读写指针扩展一位,比如深度16用5位指针,最高位用来区分空和满。这种写法更省寄存器,但理解上绕一点。异步FIFO的关键不是代码,而是同步器打两拍+格雷码转换。格雷码转换是为了让跨时钟域时每次只翻转一位,降低亚稳态概率。判空满时,空的条件是格雷码完全相等,满的条件是格雷码的最高位相反且次高位相同。注意这里用的格雷码是指针转换后的值,不能直接用二进制指针比。深度12的异步FIFO,地址位宽还是4位,但实际深度是16,你只用到0-11。空满判断不能用格雷码扩展一位的方法,因为格雷码的循环特性只在2的幂次时才成立。深度12必须用计数法,而且计数器要跨时钟域同步,实现起来比同步FIFO复杂很多,面试时如果时间紧,你可以直接说工程上常用的做法是把深度补到16,然后只允许写入0-11。面试官一般会接受这个说法,因为非2的幂次深度在硬件上非常不高效。复位方式的选择,同步FIFO用同步复位,异步FIFO的读时钟域和写时钟域各用各的异步复位,因为两个时钟域独立。最后给你一个建议:面试前把同步FIFO的计数法和指针法各写一遍,异步FIFO只写格雷码法,重点理解为什么格雷码能降低亚稳态概率而不是消灭亚稳态。如果你现在还在校,可以找一份开源的FIFO代码,比如OpenCores上的,自己跑一遍仿真,看波形时注意读使能拉高后数据是立即出现还是延迟一个时钟。你目前在刷的是哪种面试题?是LeetCode那种还是纯Verilog手写?

关于深度不是2的幂次,比如12,我面试时被问到过。当时我直接说地址位宽还是取4位,但实际只用到0到11,空满判断用计数器法最稳妥——计数器从0计到12,读使能且非空减1,写使能且非满加1。面试官接着追问:那计数器需要几位?我说至少5位,因为要数0到12共13个状态。他点头了。后来他让我手写异步FIFO,我故意在格雷码同步器那里多打了半拍延迟,他马上问我为什么这么写,我说是为了让同步后的指针稳定一个周期再比较,防止组合逻辑直接输出导致空满判断抖动。其实这个细节不是必须的,但能体现出你对跨时钟域边界的理解比背代码深一层。复位方式上,我建议你同步FIFO用同步复位,异步FIFO里跨时钟域那两级寄存器必须用异步复位,其他部分看心情。最后提醒一句:仿真时一定要给个同时读写且满/空的边界情况,很多人的代码在边界处会多读或少写一个数据。你目前是用Vivado还是Modelsim?不同工具对格雷码的波形显示差异挺大的。

手撕FIFO其实核心就两件事:指针怎么转,空满怎么判。同步FIFO用计数器法最省心,代码量小还能避免地址回卷的坑。异步FIFO你记住一个口诀:写指针写时钟域下加,读指针读时钟域下加,各自转格雷码后打两拍同步到对方时钟域,然后比较格雷码的最高两位。空就是完全相等,满就是最高位相反且次高位相同。深度不是2的幂次时,我建议你直接跟面试官说:为了简化空满判断,通常会把深度向上取整到2的幂次,比如12取16,浪费4个位置但逻辑更干净。如果面试官非要你处理非2的幂次,那就用计数器法,别想着改格雷码比较逻辑,容易翻车。追问一句:你准备用哪种风格写FIFO——RTL级还是Behavior级?面试时两种写法侧重点不一样。

深度16的同步FIFO,地址位宽4位就够了,空满判断用计数器法最省事——计数器从0数到16,等于0是空,等于16是满。异步FIFO格雷码判空满,记住口诀:完全相等是空,最高位相反且次高位相同是满。深度12这种非2的幂次,地址位宽还是4位,但计数器要能数到12,空满逻辑用计数法别用格雷码比较。你仿真时试过同时读写且满的边界吗?

手撕FIFO这事,面试官其实想看你有没有踩过坑。比如深度12的FIFO,地址位宽按log2(12)向上取整是4位,但空满判断不能直接用格雷码比较了,因为格雷码本身是为2的幂次设计的,非幂次时地址回卷的边界会出问题。我见过有人硬写格雷码比较,结果仿真时深度到了12就报满,但指针回卷到0时误判成空,波形上一片毛刺。正确做法是用计数器法,或者把深度向上取整到16再逻辑上限制只用到0到11。同步复位和异步复位的选择上,同步FIFO我习惯用同步复位,因为综合结果干净,不会出现复位释放时的时序问题;但异步FIFO里跨时钟域那两级同步寄存器必须用异步复位,否则上电后指针可能不是零。另外写代码时注意,计数器法的位宽要能覆盖0到深度,比如深度12需要5位计数器。你准备用哪个EDA工具做仿真?不同工具对格雷码同步器的初值处理不太一样。

关于复位方式,我面试时被问过,直接说同步复位更安全。原因很简单:同步复位对时钟边沿敏感,综合工具能推断出标准寄存器行为,而且不会出现异步复位释放时可能发生的亚稳态。异步FIFO里那两级同步器是个例外,必须用异步复位,因为上电后第一个时钟沿之前就要让指针归零。深度12的地址位宽还是4位,但空满标志用计数器法,计数器等于0是空,等于12是满,计数器的位宽至少5位。你写代码时记得把复位类型声明对,同步复位写always @(posedge clk),异步复位写always @(posedge clk or negedge rst_n)。追问一句:你目前是用行为级描述还是RTL级描述写FIFO?面试时这两种写法考察点不一样。

看到你问深度不是2的幂次怎么处理,其实这是面试里常见的延伸考点。拿深度12来说,地址位宽还是4位,因为log2(12)向上取整是4,但4位地址能表示0到15,所以物理上你有一个16深度的存储阵列,只是逻辑上只用0到11这12个位置。问题出在空满判断上——如果你用传统格雷码比较法,格雷码本身是为2的幂次设计的,地址回卷到0时的边界会出错,比如指针从11回卷到0,格雷码变化不止一位,跨时钟域同步时就可能误判。所以面试时你最好直接说:深度不是2的幂次时,我改用计数器法来判断空满,用一个5位计数器(因为要数0到12共13个状态)记录当前数据个数,读使能且非空时减1,写使能且非满时加1,计数器等于0是空,等于12是满。这样逻辑清晰,代码也不容易出bug。至于复位方式,同步FIFO我习惯用同步复位,因为综合结果干净,不会出现异步复位释放时的时序问题;但异步FIFO里跨时钟域那两级同步寄存器必须用异步复位,否则上电后指针可能不是零。你仿真时试过同时读写且满的边界情况吗?很多人在那个时刻会让写使能误触发,导致数据覆盖。追问一句:你目前用Vivado还是Quartus做仿真?不同工具对格雷码同步器的初值处理不太一样,Quartus有时候会默认寄存器初值为0,但Vivado仿真时如果没给初始化会出X态,这个细节面试时能聊出来就是加分项。

手撕FIFO这件事,面试官其实想看你有没有踩过坑。比如深度12的FIFO,地址位宽取4位,但空满判断不能用格雷码比较了,因为非幂次时地址回卷的边界会出问题。我见过有人硬写格雷码比较,结果仿真时深度到了12就报满,但指针回卷到0时误判成空,波形上一片毛刺。正确做法是用计数器法,或者把深度向上取整到16再逻辑上限制只用到0到11。同步复位和异步复位的选择上,同步FIFO我习惯用同步复位,因为综合结果干净;但异步FIFO里跨时钟域那两级同步寄存器必须用异步复位,否则上电后指针可能不是零。你写异步FIFO时记得给格雷码同步器加一个异步复位,不然仿真空满标志会一直不定态。你准备用哪个EDA工具做仿真?不同工具对格雷码同步器的初值处理不太一样,这个细节面试时能聊出来就是加分项。
发表回答
登录后可在本页底部提交回答