概述
同步FIFO:读写操作共用一个时钟。
异步FIFO:读操作时钟和写操作时钟相互独立。
因此异步FIFO的端口相对于同步FIFO时钟和复位信号有所变化:异步FIFO的输入端口有:时钟与复位信号(wr_clk、wr_rstn、rd_clk、rd_rst_n)、读写使能信号(wr_en、rd_en)、写数据信号(wr_data)。
异步FIFO的输出端口有:读数据信号(rd_data)、判空信号(fifo_empty)、判满信号(fifo_full)
异步FIFO内部一共有五个模块:
1.存储模块
使用双端口RAM进行建模,数据写入和读出都是对这个RAM进行操作。
2、3.读指针同步模块and写指针同步模块
异步FIFO由于读写不是同一个时钟,如果还像同步FIFO那样直接将读指针和写指针比较的话,会产生亚稳态问题,因为两个指针所遵循的时钟是异步的。
1.为什么会产生亚稳态?
已知:需要将读指针和写指针比较来产生空满信号,其中空信号用来控制读,满信号用来控制写。所以需要在写控制端得到读控制端的读指针,来产生判空信号。需要在读控制端得到写控制端的写指针,来产生判满信号。
由于读控制端和写控制端不是同一个时钟,倘若,读指针传给写控制端的时机不对,没有满足建立时间或保持时间,就会产生亚稳态,导致判满信号输出错误;同理,假若写指针传递到读控制端的时机不对,没有满足时序,就会产生亚稳态;
2.如何解决跨时钟传输的亚稳态问题?
采用两级同步的D触发器,也就是图中的读指针同步模块和写指针同步模块。
读指针同步模块:在写时钟域下的两级触发器采集读指针,输出数据到写控制端。
写指针同步模块:在读时钟与下的两级触发器采样写指针,输出数据到读控制端。
4、5.写控制端and读控制端
写控制端主要完成三个功能:1.如果非满且写有效,就将wr_data写入二进制写指针对应的RAM中。2.将二进制写指针转换成格雷码写指针。3.将读指针同步模块传过来的格雷码读指针与格雷码写指针相比较,用于判满。
读控制端同理,也要完成三个功能:1。如果非空且读有效,就将RAM中二进制读指针指向的内容输出给rd_data。2.将二进制读指针转换为格雷码读指针。3.将格雷码读指针与写指针同步模块传过来的格雷码写指针相比较,用于判空。
第一个功能比较好理解,就是普通的RAM写入读出操作。
第二个功能中,为什么要用格雷码指针呢?
将一个二进制数据从一个时钟域同步到另一个时钟域很容易出问题,因为二进制数的所有位都可能发生变化,比如上一个读指针是4’b1111,下一个读指针及变成4‘b0000了。位数变化太多,在同步的时候产生亚稳态的概率会大大增加。而格雷码具有相邻两位只有一位数据变化的特性,因此可以将二进制指针转换为格雷码指针,进而降低亚稳态发生的概率。
二进制数据转换为格雷码数据:将二进制数据右移一位在与该二进制数相异或就得到了格雷码数据
assign wr_ptr_g=wr_ptr^(wr_ptr>>1);//wr_ptr_g是格雷码写指针,wr_ptr是二进制写指针。
第三个功能中,如何使用格雷码指针来进行判空判满操作呢?
已知在同步FIFO中,我们用的是二进制指针,通过扩展位来判断空满的。现在在异步FIFO中我们为了降低亚稳态,不仅采用了二级同步,还采用了格雷码指针。其实判空就是读指针追上了写指针,而判满就是写指针追上了读指针。同样是分为四种情况,只不过将二进制指针转换成了格雷码指针,四个图目前没空画,直接给出结论:
判断读空时:读时钟域的格雷码指针和被同步到读时钟域的写指针每一位完全相同;
判断写满时:写时钟域的格雷码指针和被同步到写时钟域的读指针高两位不相同,其余各位完全相同;
异步FIFO设计代码
`timescale 1ns/1ns
module ASY_FIFO #(parameter DEPTH=8,
WIDTH=8
)
(input wr_clk,
input wr_rstn,
input rd_clk,
input rd_rstn,
input wr_en,
input rd_en,
input [WIDTH-1:0] wr_data,
output reg [WIDTH-1:0] rd_data,
output reg fifo_full,
output reg fifo_empty
);
/******************************1.写控制端&读控制端**************************/
//1.完成读写操作
reg [$clog2(DEPTH):0]wr_ptr,rd_ptr; //二进制指针,用于读写RAM。$clog2()函数表示求以2为底的对数
reg [WIDTH-1:0] fifo [DEPTH-1:0]; //RAM建模
//1.1写操作
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[$clog2(DEPTH)-1:0]]<=wr_data;
wr_ptr<=wr_ptr+1;
end
end
//1.2读操作
always@(posedge rd_clk or negedge rd_rstn)begin
if(!rd_rstn)
rd_ptr<=0;
else if(rd_en && !fifo_empty) begin
fifo[rd_ptr[$clog2(DEPTH)-1:0]]<=rd_data;
rd_ptr<=rd_ptr+1;
end
end
//2.完成格雷码转换
wire [$clog2(DEPTH):0] wr_ptr_g, rd_ptr_g;
assign wr_ptr_g=wr_ptr^(wr_ptr>>1);
assign rd_ptr_g=rd_ptr^(rd_ptr>>1);
//3.判断空满,此处由于需要用到时钟同步模块的输出信号,因此,下面写一下同步模块的代码,判空判满放在最后
/***************************2.读指针时钟同步and写指针同步*******************/
reg [$clog2(DEPTH):0] wr_ptr_gr,wr_ptr_grr;//分别为一级触发后的读指针,和二级触发后的读指针
reg [$clog2(DEPTH):0] rd_ptr_gr,rd_ptr_grr;
//2.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
//2.1读指针同步
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
/**************************3.判空判满**********************/
//3.1判满
always@(posedge wr_clk or negedge wr_rstn)begin
if(!wr_rstn)begin
fifo_full<=0;
end
else
if((wr_ptr_g[$clog2(DEPTH):$clog2(DEPTH)-1]!=rd_ptr_grr[$clog2(DEPTH):$clog2(DEPTH)-1])&&(wr_ptr_g[$clog2(DEPTH)-2:0]==rd_ptr_grr[$clog2(DEPTH)-2:0]))
fifo_full<=1;
end
//3.2判空
always@(posedge rd_clk or negedge rd_rstn)begin
if(!rd_rstn)begin
fifo_empty<=0;
end
else
if(wr_ptr_grr[$clog2(DEPTH):0]==rd_ptr_g[$clog2(DEPTH):0])
fifo_empty<=1;
end
endmodule
虚空与虚满
参考:FIFO设计-异步FIFO篇 - 知乎
IC基础(一):异步FIFO原理与代码实现 - 你好24h - 博客园
IC基础课:异步FIFO的"假"满空和"真"满空 - 知乎
最后
以上就是默默烧鹅为你收集整理的【IC设计】异步FIFO的全部内容,希望文章能够帮你解决【IC设计】异步FIFO所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复