赞
踩
在做集创赛项目时使用到了异步fifo,查找了很久才发现了漏数据的问题,因此再次补习了一下异步FIFO知识。
FIFO作为数字电路中的常客经常用于数据的缓冲存储(当然也是面试笔试中的常考问题)。对于同步FIFIO,主要是实现速率匹配,起到数据缓冲的作用。对于异步FIFO,主要是实现不同时钟域之间的数据交互,涉及到指针跨时钟域的问题,处理起来更复杂一些,因此本篇主要讨论异步fifo的问题。
如前面所说,在实际的数字电路项目中,往往存在很多个时钟源,不同时钟源的数据需要进行传输互通。如果是单bit数据,可以通过打两拍寄存,展宽等方式进行同步,异步fifo主要解决的是多bit数据的跨时钟传输问题。
我们都知道fifo中的关键变量是读写指针,读写指针决定了将往fifo中写入或者读出的位置。异步fifo在处理跨时钟信号时,采用的是将读指针和写指针转化为格雷码的形式,这时候一定会有初学者产生疑问:为什么采用格雷码的形式可以有效改善亚稳态的问题?
要理解采用格雷码的原因,首先要明白fifo的“写满”和“读空”逻辑是怎样生成的。
注意:指针的定义位宽比fifo实际深度会多出一位,例如深度为32的fifo读指针和写指针的位宽都为6位,这样当读写指针的低五位都相同时,可以根据最高位判断写指针究竟是超了读指针一圈还是两者完全相同。
读指针rd_ptr指向fifo读出的地址,写指针wr_ptr则指向下一个讲写入的地址,那就不难理解用指针来判断写满和读空的逻辑:
当读指针等于(追上)写指针时,证明此时fifo已经读空,需要停止继续向外读数,避免一直读0。
当写指针超过读指针一圈赶上读指针时,此时fifo已经写满,需要停止继续写入数据,造出数据丢失。
显然,满和空的关键在于指针比较的判断,在同步fifo中这很容易处理,因为是相同的时钟源只需要直接比较即可。但在异步fifo中,我们需要将读写指针同步到一个时钟源中进行比较。
具体来说,对“读空”的判断要将写指针同步到读时钟域中进行比较,对“写满”的判断要将读指针同步到写时钟域进行比较。这其实也很好理解,同步到另一个时钟域需要打拍消耗时间,以读空举例,在写指针同步的这段时间,实际的写指针可能已经继续向前,所以当同步完成后,同步的写指针一定小于等于实际写时钟域的写指针。这会造成什么情况?由于读指针始终处于读时钟域不需要同步,所以读指针一定采样的是正确的数据;而进行比较的写指针有可能小于真正的写指针,因此存在可能:实际上读指针并没有追上写指针,但在读时钟域,读指针已经追上了同步后的写指针,fifo做出已经读空的判断,称为假空。
但是,这种“假空”判断是我们完全可以接受的,在fifo没有空时我们说它已空停止读出数据,只会牺牲它的一小部分性能和深度。但是反过来如果在写时钟域做读空的判断,就有可能出现实际上已经空了却判断还未空的现象。相对来说,当然是假空可以被接受,毕竟不会影响fifo正确的功能,甚至可以说留有了一部分裕量。
明白了读写指针的逻辑之后,就更容易理解为什么采用格雷码了,具体原因大概有两个:
1.我们知道格雷码临近的两位只有1bit数据发生变化,在本就容易出现亚稳态的跨时钟域,我们当然希望每一拍数据的变化尽可能小,因此使用格雷码可以有效降低亚稳态出现的可能。
2.读写指针采用格雷码,即使出现传输错误,也不会影响fifo的功能。
这也许是更关键的一个问题,在fifo中我们不希望看见的是fifo已空或者已满却没有被指示出来的情况,而在使用bcd码时,例如从0111翻转到1000时,有可能翻转出没有出现过的错误数据1111,这肯定会对空满的逻辑判断带来错误影响。
但是,如果使用的是格雷码,即使0101没有成功翻转成0111,代价也只是指针多停留了一拍。与指针同步相同的逻辑,此时真正的指针信号大于我们未成功翻转的信号,带来的影响是更易判断出假空或者假满,但绝不会有已空已满却没被判断出来这种情况的出现。
- `timescale 1ns/1ns
- module fifo_in #(
- parameter DSIZE = 256,//fifo宽度
- parameter ASIZE = 32 //fifo深度
- )
- (
- input wire rstn,//低电平复位
-
- input wire wclk,//写时钟
- input wire [DSIZE-1:0] wdata,//写数据
- input wire w_en,//写使能,1有效
- output wire w_full,//写满信号,1有效,组合逻辑输出
- output reg [ASIZE-1:0] wuse,//写域fifo已使用空间
-
- input wire rclk,//读时钟
- output reg [DSIZE-1:0] rdata,//读数据
- output wire r_empty,//读空信号,1有效,组合逻辑输出
- input wire r_en,//读使能,1有效
- output reg r_valid,//读数据有效,1有效
- output reg [ASIZE-1:0] ruse//读域fifo已使用空间
- );
-
- reg [5:0] wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈
- reg [5:0] rd_addr_ptr;
- wire [4:0] wr_addr;//RAM 地址
- wire [4:0] rd_addr;
-
- wire [5:0] wr_addr_gray;//地址指针对应的格雷码
- reg [5:0] wr_addr_gray_d1;
- reg [5:0] wr_addr_gray_d2;
- wire [5:0] rd_addr_gray;
- reg [5:0] rd_addr_gray_d1;
- reg [5:0] rd_addr_gray_d2;
-
- reg [DSIZE-1:0] fifo_ram [ASIZE-1:0];
- always@(posedge wclk or negedge rstn)
- begin
- if(!rstn)
- begin
- wr_addr_ptr <= 0;
- fifo_ram[0] <= 0;
- end
- else if(w_en && (~w_full))
- begin
- @(posedge wclk);
- fifo_ram[wr_addr] <= wdata;
- wr_addr_ptr <= wr_addr_ptr+1;
- end
- else
- begin
- fifo_ram[wr_addr] <= fifo_ram[wr_addr];
- wr_addr_ptr <= wr_addr_ptr;
- end
- end
-
- //========================================================read_fifo
- always@(posedge rclk or negedge rstn)
- begin
- if(!rstn)
- begin
- rdata <= 'h0;
- r_valid <= 1'b0;
- rd_addr_ptr <= 0;
- end
- else if(r_en && (~r_empty))
- begin
- rdata <= fifo_ram[rd_addr];
- r_valid <= 1'b1;
- rd_addr_ptr <= rd_addr_ptr +1;
- end
- else
- begin
- rdata <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0;
- r_valid <= 1'b0;
- rd_addr_ptr<=rd_addr_ptr;
- end
- end
- assign wr_addr = wr_addr_ptr[4:0];
- assign rd_addr = rd_addr_ptr[4:0];
- //=============================================================graycode syn
- always@(posedge wclk )
- begin
- rd_addr_gray_d1 <= rd_addr_gray;
- rd_addr_gray_d2 <= rd_addr_gray_d1;
- end
- //=========================================================rd_clk
- always@(posedge rclk )
- begin
- wr_addr_gray_d1 <= wr_addr_gray;
- wr_addr_gray_d2 <= wr_addr_gray_d1;
- end
- //========================================================== translation gary code
- assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
- assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;
- assign w_full = (wr_addr_gray == {~(rd_addr_gray_d2[5:4]),rd_addr_gray_d2[3:0]}) ? 1:0;
- assign r_empty = ( rd_addr_gray == wr_addr_gray_d2 ) ? 1:0;
- endmodule
特别需要注意的是在wdata写入fiforam时需要@一个上升沿或者等一定延时,否则就可能出现像我一样的采样到上一拍数据的情况。
明白了异步fifo的逻辑之后,还可以考虑一些其他有意思(面试中常问的问题)
因为格雷码始终相邻只改变一个单位的前提是总体数据是2的幂次;并且fifo实际上使用的ram资源也是2的幂次,不用也是浪费。
对fifo深度影响最大的条件无外乎两个:写频率和读频率。根据总结,可以直接给出以下公式:
其中,wclk和rclk代表写时域时钟和读时域时钟,写数据时每B个时钟周期内会有A个数据写入FIFO、读时每Y个时钟周期会有X个数据读出FIFO。burst_length表示这段时间写入的数据量,burst_length/wclk则表示这个burst的持续时间。
一眼看起来好像比较复杂不便于理解,实际上也并不用记住这个公式,只要能确定当fifo内部数据存储最多的时刻就能计算出它的最小深度。
看一道乐鑫的笔试题:假设FIFO的写时钟为100MHz,读时钟为80MHz。在FIFO输入侧,每100个写时钟,写入80个数据;读数据侧,假定每个时钟读走一个数据,问FIFO深度设置多少可以保证FIFO不会上溢出和下溢出?
这个fifo的写入速度大于读出速度,在读数据侧,每个时钟稳定读走一个数据,而写数据侧,并不是每个时钟都会写入数据。因此问题就转变成了在哪一次写数据暂时停止后fifo内部存储最多的数据。
不难想到,当某100个写时钟最后连写的80个数据连上了后100个写时钟连写的80个数据时,fifo的深度达到最大。
因此可以计算:一共写入160个数据,占用160个wclk,也就是1600ns。1600ns等于128个rclk,在这期间读出了128个数,随后写入暂时停止,开始只读。最小需要的fifo深度即为:160-128=32.
以上考虑的均为读写时钟频率差异不大的情况,一般在实际使用时,读写时钟也不会有太大差异。但是考虑极端情况,如果写时钟频率几倍或数十倍于读时钟频率,或者反过来呢?采用这样的异步fifo还能实现正确功能吗?今天华为面试题:异步FIFO读时钟是写时钟的100倍,或者写是读的100倍会出现什么问题? - 第3页 - 数字IC设计讨论(IC前端|FPGA|ASIC) - EETOP 创芯网论坛 (原名:电子顶级开发网) -
可以。记住使用格雷码的核心仍然是确保满空的判断不会出错,即留出了一定余量。在此情形下,fifo仍然会输出假空假满信号,需要注意的也就是fifo的深度设置,比如当写时钟太高,fifo就会极易被写满...
比如:写时域为100mhz,读时域为1mhz,开始写入数据之前,FIFO置空指示信号,写入数据后,由于写地址同步到读时钟域有延迟,因此空指示仍然有效,如果FIFO深度不够,当数据写满之后,写地址还是没有成功同步到读时钟域,这个时候就会同时置空满指示信号。例如FIFO深度为256时,写满FIFO只需要256个写时钟周期,即2560ns,而在这个时间内,变化的写地址都还没有能够同步到读时钟域。因此,当读写时钟速率差异太大时,需要注意FIFO的深度是否合适。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。