快捷搜索:  汽车  科技

异步fifo和同步fifo(FIFO设计-异步FIFO篇)

异步fifo和同步fifo(FIFO设计-异步FIFO篇)介绍完内部结构,我们再和基本接口图做个联动两个时钟同步端用于将读写时钟进行同步处理写控制端用于判断是否可以写入数据读控制端用于判断是否可以读取数据FIFO Memory用于存储数据

0 写在前面

在上篇文章中,我们介绍了FIFO设计-同步FIFO,介绍了FIFO的重要参数,并给出了同步FIFO设计代码,本文将介绍异步FIFO

1 异步FIFO结构

在上篇文章中我们给出了FIFO的基本接口图

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(1)

并且指出,该图适用于所有的FIFO,这次我们先看看异步FIFO内部的大体框图

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(2)

异步FIFO主要由五部分组成:写控制端、读控制端、FIFO Memory和两个时钟同步端

写控制端用于判断是否可以写入数据

读控制端用于判断是否可以读取数据

FIFO Memory用于存储数据

两个时钟同步端用于将读写时钟进行同步处理

介绍完内部结构,我们再和基本接口图做个联动

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(3)

刚才说过,读/写控制端用于判断能否写入/读取数据,判断能否写入/读取数据关键在于:

  • 写操作时,写使能有效且FIFO未满
  • 读操作时,读使能有效且FIFO未空

因此两个使能信号和空满判断信号都连接到控制端上

最后我们再加上时钟信号和复位信号

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(4)

这便是完整的异步FIFO简化框图

2 空满判断

在同步FIFO篇中,我们给出了两个判断空满状态的图

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(5)

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(6)

并且也有指出,读空状态可以理解为读地址指针追上写地址指针,写满状态可以理解为写地址指针再次追上读地址指针

在同步FIFO中,因为读写都是在同一个时钟信号下进行的,因此两个地址指针可以直接进行比较

但在异步FIFO中,读写是在不同的时钟信号下进行的,因此在进行比较之前,应当先进行跨时钟与同步

在时钟同步之前,我们应当先将二进制地址转换为格雷码,因为格雷码相邻的两个状态之间,只有1 bit数据发生翻转

下面给出二进制数与格雷码的对照图

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(7)

上面也有说到,读指针追上写指针是读空,写指针再次追上读指针是写满,为了便于理解,我们做一个环形图

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(8)

假设内圈为读,外圈为写,读空时是读写指针应当指向同一个地址,就像这样

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(9)

此时,读地址应当和写地址完全相同,就以0010为例,0010的格雷码为0011,可以看出对于读空状态,无论是二进制还是格雷码均是所有位都相同

写满和读空略有不同,应当是下面这样

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(10)

细心的小伙伴应该可以发现,上面在提到写满时,说的是写指针再次追上读指针,也就是说,写满时,写指针比读指针多走一圈,为了便于区分,将地址位宽从3 bit拓宽到4 bit,因此此时的写指针地址可以认为是1010

1010的格雷码是1111, 0010的格雷码是0011,对比两个格雷码是不是可以发现,此时高两位相反,低两位相同,这便是格雷码下写满的判断条件

Verilog中表示为

//写满判断 always @ (posedge wr_clk or negedge wr_rstn) begin if(!wr_rstn) fifo_full <= 0; else if((wr_ptr_g[$clog2(DEPTH)] != rd_ptr_grr[$clog2(DEPTH)]) && (wr_ptr_g[$clog2(DEPTH) - 1] != rd_ptr_grr[$clog2(DEPTH) - 1]) && (wr_ptr_g[$clog2(DEPTH) - 2 : 0] == rd_ptr_grr[$clog2(DEPTH) - 2 : 0])) fifo_full <= 1; else fifo_full <= 0; end //读空判断 always @ (posedge rd_clk or negedge rd_rstn) begin if(!rd_rstn) fifo_empty <= 0; else if(wr_ptr_grr[$clog2(DEPTH) : 0] == rd_ptr_g[$clog2(DEPTH) : 0]) fifo_empty <= 1; else fifo_empty <= 0; end 3 时钟同步

在同步FIFO设计中,因为读写指针在同一个时钟下,因此可以直接进行比较

但在异步FIFO中,由于读写指针在不同的时钟下,因此需要将两个地址指针进行时钟同步操作

在异步FIFO中,常用的同步方法是两级同步打拍延迟,同步地址指针的大致过程如下:

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(11)

写操作时,先将写地址指针转换成格雷码,然后通过两级同步(两级同步在读时钟域下进行),将写地址指针同步到读时钟域下;读操作类似

根据这个过程图,也可以看出空满判断的方式:

  1. 写满在写时钟下判断,将写地址指针的格雷码与同步过来的读地址指针格雷码进行比较,符合写满条件,即FIFO虚满
  2. 读空在读时钟下判断,将读地址指针的格雷码与同步过来的写地址指针格雷码进行比较,符合读空条件,即FIFO虚空

留意下,这里我说的是虚空/满,并不是输入错误哟,具体解释我放在文章最后,爱思考的朋友现在可以思考一下原因

下面给出时钟同步的Verilog代码

assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1); //B2G assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1); //写指针同步到读时钟域 always @ (posedge rd_clk or negedge rd_rstn) begin if(!rd_rstn) begin wr_ptr_gr <= 0; wr_ptr_grr <= 0; end else begin wr_ptr_gr <= wr_ptr_g; wr_ptr_grr <= wr_ptr_gr; end end //读指针同步到写时钟域 always @ (posedge wr_clk or negedge wr_rstn) begin if(!wr_rstn) begin rd_ptr_gr <= 0; rd_ptr_grr <= 0; end else begin rd_ptr_gr <= rd_ptr_g; rd_ptr_grr <= rd_ptr_gr; end end 4 异步FIFO设计

下面给出整体Verilog代码

module asy_fifo#( parameter WIDTH = 8 parameter DEPTH = 8 )( input [WIDTH - 1 : 0] wr_data input wr_clk input wr_rstn input wr_en input rd_clk input rd_rstn input rd_en output fifo_full output fifo_empty output [WIDTH - 1 : 0] rd_data ); //定义读写指针 reg [$clog2(DEPTH) : 0] wr_ptr rd_ptr; //定义一个宽度为WIDTH,深度为DEPTH的fifo reg [WIDTH - 1 : 0] fifo [DEPTH - 1 : 0]; //定义读数据 reg [WIDTH - 1 : 0] rd_data; //写操作 always @ (posedge wr_clk or negedge wr_rstn) begin if(!wr_rstn) wr_ptr <= 0; else if(wr_en && !fifo_full) begin fifo[wr_ptr] <= wr_data; wr_ptr <= wr_ptr 1; end else wr_ptr <= wr_ptr; end //读操作 always @ (posedge rd_clk or negedge rd_rstn) begin if(!rd_rstn) begin rd_ptr <= 0; rd_data <= 0; end else if(rd_en && !fifo_empty) begin rd_data <= fifo[rd_ptr]; rd_ptr <= rd_ptr 1; end else rd_ptr <= rd_ptr; end //定义读写指针格雷码 wire [$clog2(DEPTH) : 0] wr_ptr_g; wire [$clog2(DEPTH) : 0] rd_ptr_g; //读写指针转换成格雷码 assign wr_ptr_g = wr_ptr ^ (wr_ptr >>> 1); assign rd_ptr_g = rd_ptr ^ (rd_ptr >>> 1); //定义打拍延迟格雷码 reg [$clog2(DEPTH) : 0] wr_ptr_gr wr_ptr_grr; reg [$clog2(DEPTH) : 0] rd_ptr_gr rd_ptr_grr; //写指针同步到读时钟域 always @ (posedge rd_clk or negedge rd_rstn) begin if(!rd_rstn) begin wr_ptr_gr <= 0; wr_ptr_grr <= 0; end else begin wr_ptr_gr <= wr_ptr_g; wr_ptr_grr <= wr_ptr_gr; end end //读指针同步到写时钟域 always @ (posedge wr_clk or negedge wr_rstn) begin if(!wr_rstn) begin rd_ptr_gr <= 0; rd_ptr_grr <= 0; end else begin rd_ptr_gr <= rd_ptr_g; rd_ptr_grr <= rd_ptr_gr; end end //声明空满信号数据类型 reg fifo_full; reg fifo_empty; //写满判断 always @ (posedge wr_clk or negedge wr_rstn) begin if(!wr_rstn) fifo_full <= 0; else if((wr_ptr_g[$clog2(DEPTH)] != rd_ptr_grr[$clog2(DEPTH)]) && (wr_ptr_g[$clog2(DEPTH) - 1] != rd_ptr_grr[$clog2(DEPTH) - 1]) && (wr_ptr_g[$clog2(DEPTH) - 2 : 0] == rd_ptr_grr[$clog2(DEPTH) - 2 : 0])) fifo_full <= 1; else fifo_full <= 0; end //读空判断 always @ (posedge rd_clk or negedge rd_rstn) begin if(!rd_rstn) fifo_empty <= 0; else if(wr_ptr_grr[$clog2(DEPTH) : 0] == rd_ptr_g[$clog2(DEPTH) : 0]) fifo_empty <= 1; else fifo_empty <= 0; end endmodule

下面是tb

module asy_fifo_tb; parameter width = 8; parameter depth = 8; reg wr_clk wr_en wr_rstn; reg rd_clk rd_en rd_rstn; reg [width - 1 : 0] wr_data; wire fifo_full fifo_empty; wire [width - 1 : 0] rd_data; //实例化 asy_fifo myfifo ( .wr_clk(wr_clk) .rd_clk(rd_clk) .wr_rstn(wr_rstn) .rd_rstn(rd_rstn) .wr_en(wr_en) .rd_en(rd_en) .wr_data(wr_data) .rd_data(rd_data) .fifo_empty(fifo_empty) .fifo_full(fifo_full) ); //时钟 initial begin rd_clk = 0; forever #25 rd_clk = ~rd_clk; end initial begin wr_clk = 0; forever #30 wr_clk = ~wr_clk; end //波形显示 initial begin $fsdbDumpfile("wave.fsdb"); $fsdbDumpvars(0 myfifo); $fsdbDumpon(); end //赋值 initial begin wr_en = 0; rd_en = 0; wr_rstn = 1; rd_rstn = 1; #10; wr_rstn = 0; rd_rstn = 0; #20; wr_rstn = 1; rd_rstn = 1; @(negedge wr_clk) wr_data = {$random}0; wr_en = 1; repeat(7) begin @(negedge wr_clk) wr_data = {$random}0; end @(negedge wr_clk) wr_en = 0; @(negedge rd_clk) rd_en = 1; repeat(7) begin @(negedge rd_clk); end @(negedge rd_clk) rd_en = 0; #150; @(negedge wr_clk) wr_en = 1; wr_data = {$random}0; repeat(15) begin @(negedge wr_clk) wr_data = {$random}0; end @(negedge wr_clk) wr_en = 0; #50; $finish; end endmodule

下面贴上运行结果

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(12)

这里有一点需要说明

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(13)

蓝色框的位置,已经开始写入数据,但fifo_empty信号并没有被拉低,而是在第三个rd_clk上升沿被拉低,这是因为在判断FIFO是否读空时,是在读时钟下判断,并且,进行判断时,写地址格雷码需要在读时钟域进行两次同步,最后进行比较

为了更清晰的解释,可以将所有的地址指针也加入到波形图中

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(14)

注意刚才提到的位置

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(15)

在位置1,有数据写入,此时wr_ptr和wr_ptr_g都发生了变化,wr_ptr_gr和wr_ptr_grr保持不变

在位置2,wr_ptr_gr变化,wr_ptr_grr保持不变

在位置3,wr_ptr_grr才开始发生变化

在位置4,wr_ptr_grr和rd_ptr_g进行比较,判定此时FIFO非空

还有一个有意思的地方

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(16)

注意wr_ptr_gr和wr_ptr_grr,这两个保持了两个rd_clk时钟周期,返回查看完整波形图的话,这种情况只出现在写指针的格雷码

这tb文件中,我们设定的是,rd_clk比wr_clk快,打两拍同步的方式,慢时钟域同步到快时钟域和快时钟域同步到慢时钟域处理方式是不同的,后面有时间再做介绍。

有兴趣的小伙伴,可以试下如果rd_clk比wr_clk慢,这种情况会出现在rd_ptr_gr和rd_ptr_grr

5 一个我在面试中被问到的问题

我在面试的时候被问到,如果跨时钟域同步时,同步过来的是一个亚稳态的数据,那么FIFO还能否正常工作?

在空满判断部分说过,读空或写满可以理解为:

  • 读空时:可以理解为是读指针在追写指针
  • 写满时:可以理解为是写指针在追读指针

为了方便理解,我们将其抽象成一个追逐运动,以读空为例

异步fifo和同步fifo(FIFO设计-异步FIFO篇)(17)

先确定读空的判断,读空判断是在rd_clk下,读指针的格雷码与写指针同步过来的格雷码进行比较,实际上,读指针追的是wr_ptr_grr

而wr_ptr_grr又是写指针留在原地的残影(写指针经过两个读时钟周期后得来的),在两个读时钟周期的这段时间,写指针可能会原地不动,也可能继续前行,这便是虚空的概念

虚满的概念同理

参考资料

FPGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇_Reborn Lee-CSDN博客

4.4 Verilog FIFO 设计 | 菜鸟教程 (runoob.com)

二进制和格雷码之间的转换_whik1194的博客-CSDN博客_格雷码和二进制码的转换

猜您喜欢: