秋招基本结束,来总结一下从实习面试到秋招面试以来面试会问到的FIFO相关的问题。
一、同步/异步FIFO面试提问汇总
-
FIFO怎么实现跨时钟域?/为什么要用格雷码?
—> 格雷码相邻只有1bit的跳变,不会出现多个bit同时出现亚稳态从而产生中间状态,所以可以直接打拍;而且就算出现了亚稳态,那也有空满去辅助,最多出现假空假满,不会出现数据错误。 -
多bit打两拍之后还有亚稳态吗?多bit直接打拍稳定下来之后和转格雷码对应的二进制一样吗?那为什么还要转格雷码呢?
- 亚稳态不能消除,打拍只是让概率降低。
- 多bit最后打拍完稳定下来应该结果和单bit合并起来是一样的。但主要问题在于多bit产生亚稳态和他们稳定下来的时间不同,中间可能会出现中间状态,从而导致其他判断逻辑出错。
-
空满判断如何实现?(包括用格雷码和二进制判断的情况)
这里要清楚指针的实现和空满的判断。- 指针是基于地址做循环,一般为了格雷码方便判断会多加一位,相当于FIFO深度扩倍。
- 空满判断:二进制(读写指针第一位相反其余相同则满,读写指针全相同是空。也可以说读写指针中间差一个FIFO深度就是满)和格雷码(读写指针前两位相反,其余相同则满,读写指针全相同是空。)
-
使用过程中FIFO的深度如何确定?(笔试经常出,面试也问到过,主要是考虑背对背传输的情况,要注意两边频率不同时的换算)
-
假如写很快,但读却很慢,那这样写了很多个之后才读,相当于读地址变一个的时候,写已经很多个了,那这样格雷码之间就不只有一位不一样了,逻辑会出问题吗?
---->不会。有没有亚稳态看的是这一时刻的几个多bit有几个在变,不应该一段时间不同的值去比较。 -
异步FIFO跨时钟域要设置什么约束?
这个主要是考虑布局布线之后各个bit之间的延时会有差异,他们到同一个采样点的时间不同,那这样就不能保证其中只有一个bit在变化了。
具体是要设max_delay,读到写,写到读都要设置。要设置的值,是launch域的一个周期,也就是从发的FF到采样的FF,几个bit的传输延时都不能超过T(设T/2更保险,T是最大不能超过的值了)。 -
FIFO深度必须是2的幂次吗?可以是2n,怎么实现?
不必须,2n也可以实现。参见 FIFO设计中与深度depth相关的问题
思路就是,比如需要的深度5,那么因为判断空满地址要加倍,就需要10,占4bit,而本身4bit最大到15,所以就从0-15里取中间的10个数。也就是前三个和后三个数都不要了,要3-12对应的格雷码。
注意这样满判断的时候也会有区别。 -
读写的频差越大,会不会虚空虚满的越厉害
会。首先明确虚满是在写时钟域。比如写的已经满了,读很慢,很久才知道已经不空了,然后写入一个数据再传过去,这样才不满了,中间间隔的时间都是虚满的时间。但是假如读的快一些,他就很快知道已经写入数据,从而可以读出。 -
能不能满在读域判断,写在写域判断?(芯动面试官问的,说可以,这样还不会出现虚空虚满的问题。我问那为什么平时我们不这样用,答要给代码留有余量)
我其实没太明白,我自己理解满一定要在写时钟域产生,读域可以自己单独再生成一个满,当读的时候读域就里面知道已经不满了。但是如果满全部在读域,那就既要要先同步写地址,满即便直接组合逻辑不同步过去,也是晚两拍。
还有一种可能,他说的是同步FIFO?那就没有同步时钟域的问题了,不懂了… -
格雷码和二进制,二进制和格雷码的转化方式
grey=bin^(bin>>1)
格雷码转二进制(代码中不会用其实)
module gray_to_bin (bin, gray);
parameter N= 4;
input [N– 1:0] bin;
output [N– 1:0] gray;
reg [N– 1:10] bin;
//法1
integer i;
always @ (gray)
for ( i = 0; i <= N; i = i + 1)
bin[i] = ^(gray >> i); //右移一位并按位异或
//法2
assign bin[N-1] = gray[N-1];
genvar i;
generate //这里for循环从0开始其实也是一样的,因为for循环会把他们全部展开
for(i = N-2; i >= 0; i = i - 1) begin: gray_2_bin
assign bin[i] = bin[i + 1] ^ gray[i];
end
endgenerate
endmodule
- 手撕同步/异步FIFO+深度为1的FIFO
二、FIFO手撕
1、同步FIFO
下面是同步FIFO代码实现,基于cnt计数。
| 注意其中会有三个关键参数,RAM的位宽深度和指针的位宽。
读写dat的位宽就是RAM宽度;指针的值最大到RAM深度-1,所以他的位宽就是clog2(RAM_DEPTH-1);计数要记到RAM深度,所以位宽比ptr宽一位。 |
---|
module synfifo #( //注意这里要有#,里面都是逗号
parameter WIDTH = 'd4,
parameter DEPTH = 'd8
)(
input clk,
input rst_n,
input wr_en,
input rd_en,
input [WIDTH-1:0] wr_dat, //这个位宽之前就写错了,注意不是$clog2(DEPTH)-1,这里是数据位宽,只有指针才要clog2
output reg [WIDTH-1:0] rd_dat, //都写成对齐的形式,避免位宽和是否有reg出错
output full,
output empty
);
//关于位宽经常容易出错。可以假设width是3,深度是16=2^4.
//写进去的数据位宽肯定是一样的
//指针的值是0-15,那么他的位宽只需要4,也就是$clog2(15)=$clog2(DEPTH -1)
localparam ADDR_WIDTH = $clog2(DEPTH-1); //注意这里要-1!多次复用的计算写成一个参数的形式
reg [WIDTH