基于verilog的异步FIFO设计

微信公众号“FPGA科技室”
在这里插入图片描述

本文所研究的FIFO,从硬件的观点来看,就是一块数据内存。

它有两个端口,一个用来写数据,就是将数据存入FIFO;另一个用来读数据,也就是将数据从FIFO当中取出。与FIFO操作相关的有两个指针,写指针指向要写的内存部分,读指针指向要读的内存部分。FIFO控制器通过外部的读写信号控制这两个指针移动,并由此产生FIFO空信号或满信号。

对于异步FIFO(当读写FIFO的时钟不同那么称其为异步FIFO)而言,数据是由某一个时钟域的控制信号写入FIFO,而由另一个时钟域的控制信号将数据读出FIFO。也就是说,读写指针的变化动作是由不同的时钟产生的。因此,对FIFO空或满的判断是跨时钟域的。如何根据异步的指针信号对FIFO的满状态或空状态进行正确的判断是本文研究的重点。

FIFO就是一个环形存储器,读操作会把读指针指向下一个读数据,写操作会把写指针指向下一个写数据地址,当读指针追上写指针时称作读空,当写地址追上读地址时称作写满。

下面来说明写满读空信号的产生:

读空信号:复位的时候,读指针和写指针相等,读空信号有效(这里所说的指针其实就是读地址、写地址)

当读指针赶上写指针的时候,写指针等于读指针意味着最后一个数据被读完,此时读空信号有效

写满信号:当写指针比读指针多一圈时,写指针等于读指针意味着写满了,此时写满信号有效

我们会发现 读空的条件是写指针等于读指针,写满的条件也是写指针等于读指针,到底如何区分呢?

解决方法:将指针的位宽多定义一位

eg:

8个深度的存储器地址二进制编码,此时定义读写指针只需要3位(2^3=8)就够用了,但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满

 4'b0_000
 4'b0_001 r
 4'b0_010
 4'b0_011
 4'b0_100
 4'b0_101
 4'b0_110
 4'b0_111
 4'b1_000
 4'b1_001 w
 4'b1_010
 4'b1_011
 4'b1_100
 4'b1_101
 4'b1_110
 4'b1_111    

下面是二进制条件下的写满读空信号的产生

assign w_full=(r_addr[2:0]== w_addr[2:0]&&r_addr[3] == (~w_addr[3]));

assign r_empty=(r_addr[3:0]== w_addr[3:0]);

由于设计的FIFO为异步FIFO,读写时钟相互独立,当二进制读地址从0111向1000变化时,地址所有位都要变化,如果写时钟恰好在读地址的变化时刻采样,写所得到的读地址值有可能是从0000到1111中的任何一个(即亚稳态的发生),可以采用gray码形式。由于格雷码每次只变化一位,采用格雷码可以降低亚稳态的发生概率。

比如: 二进制数10110,要求它对应的格雷码,先将10110>>1(右移1) , 得到 01011,然后(10110)^(01011)就得到其对应的格雷码。

因此用格雷码判断是否为读空或写满时应看最高位和次高位是否相等,具体如下:

当最高位和次高位相同,其余位相同认为是读空
在这里插入图片描述
当最高位和次高位不同,其余位相同认为是写满
在这里插入图片描述
在这里插入图片描述
由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?

跨时钟域的问题:上面我们已经提到要通过比较读写指针来判断产生读空和写满信号,但是读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较

解决方法:两级寄存器同步 + 格雷码

同步的过程有两个:

(1)将写时钟域的写指针同步到读时钟域,将同步后的写指针与读时钟域的读指针进行比较产生读空信号

(2)将读时钟域的读指针同步到写时钟域,将同步后的读指针与写时钟域的写指针进行比较产生写满信号

同步的思想就是用两级寄存器同步,简单说就是打两拍。

下图为异步FIFO设计图
在这里插入图片描述
上图中的左侧模块是来产生FIFO的写指针与满标志,右边模块产生FIFO读指针和空标志,当左侧的模块(写时钟域)产生了写满标志,使得FIFO再写满后可以检测并产生出FULL信号,故需要将读指针同步到写时钟域下进行比较。同样的,右侧读时钟域的模块是来产生FIFO空标志,写空后能产生empty标志信号,且需要将写指针同步到读时钟域下进行比较。

写控制器的时钟同步(将读控制器输出的格雷码地址打两拍):
在这里插入图片描述
读控制器的时钟同步(将写控制器输出的格雷码地址打两拍):
在这里插入图片描述
在这里插入图片描述
最后 给出读控制器具体代码和写控制器具体代码:

写:

module w_ctrl(
 input wire  w_clk,//写时钟
 input wire  rst_n,//复位
 input wire  w_en,//写使能
 input wire [8:0] r_gaddr,//读时钟域过来的格雷码读地址指针
 output reg  w_full,//写满标志
 output wire [8:0] w_addr,//256深度的FIFO写二进制地址
 output wire [8:0] w_gaddr//写FIFO地址格雷码编码
);
reg [8:0] addr;
reg [8:0] gaddr;
wire [8:0] addr_wire;
wire [8:0] gaddr_wire;
reg [8:0] r_gaddr_d1,r_gaddr_d2;
wire  w_full_wire;
//打两拍进行时钟同步
always @(posedge w_clk or negedge rst_n)
 if(rst_n == 1'b0)
  {r_gaddr_d2,r_gaddr_d1} <= 18'd0;
 else 
  {r_gaddr_d2,r_gaddr_d1} <= {r_gaddr_d1,r_gaddr};
//产生些ram的地址指针二进制
assign w_addr = addr;
always @(posedge w_clk or negedge rst_n)
 if(rst_n == 1'b0)
  addr <= 9'd0;
 else 
  addr <= addr_wire;

assign addr_wire = addr + ((~w_full)&w_en);
//转换格雷码编码地址
assign gaddr_wire=(addr_wire>>1)^addr_wire;
always @(posedge w_clk or negedge rst_n)
 if(rst_n == 1'b0)
  gaddr<=9'd0;
 else gaddr <= gaddr_wire;
assign w_gaddr = gaddr;

//写满标志产生完成
always @(posedge w_clk or negedge rst_n)
 if(rst_n == 1'b0)
  w_full <= 1'b0;
 
 else if({~gaddr_wire[8:7],gaddr_wire[6:0]}==r_gaddr_d2)
  w_full <=1'b1; //w_full_wire;
 else w_full <=1'b0;


endmodule

读:

module r_ctrl (
 input wire  r_clk,//读时钟
 input wire  rst_n,
 input wire  r_en,//读使能
 input wire [8:0] w_gaddr,//写时钟域中的写地址指针
 output reg  r_empty,//读空标志
 output wire [8:0] r_addr,//读地址二进制
 output wire [8:0] r_gaddr//读格雷码地址
);
reg [8:0] addr;
reg [8:0] gaddr;
wire [8:0] addr_wire;
wire [8:0] gaddr_wire;
reg [8:0] w_gaddr_d1,w_gaddr_d2;
wire  r_empty_wire;
//打两拍进行时钟同步
always @(posedge r_clk or negedge rst_n)
 if(rst_n == 1'b0)
  {w_gaddr_d2,w_gaddr_d1} <= 18'd0;
 else 
  {w_gaddr_d2,w_gaddr_d1} <= {w_gaddr_d1,w_gaddr};
//二进制的读地址
assign r_addr = addr;
always @(posedge r_clk  or negedge rst_n)
 if(rst_n == 1'b0)
  addr <=9'd0;
 else 
  addr <= addr_wire;

assign addr_wire = addr + ((~r_empty)&r_en);
//格雷码的读地址
assign r_gaddr = gaddr;
assign gaddr_wire = (addr_wire >>1 )^ addr_wire;

always @(posedge r_clk or negedge rst_n)
 if(rst_n == 1'b0)
  gaddr <= 9'd0;
 else 
  gaddr <= gaddr_wire;
 
//读空标志的产生
assign r_empty_wire =(gaddr_wire == w_gaddr_d2);
always @(posedge r_clk or negedge rst_n)
 if(rst_n == 1'b0)
  r_empty<=1'b1;
 else //if(gaddr_wire == w_gaddr_d2)
  r_empty <= r_empty_wire;
 //else 
  //r_empty <= 1'b0;

endmodule

更多内容请关注微信公众号“FPGA科技室”
在这里插入图片描述

原创文章 10 获赞 12 访问量 4758

猜你喜欢

转载自blog.csdn.net/weixin_42747385/article/details/104298227