理论参考:
异步FIFO—Verilog实现
异步FIFO——结构、Verilog代码实现与仿真
FPGA逻辑设计回顾(6)多比特信号的CDC处理方式之异步FIFO有空满判断详细说明
引用红色闪电007的评论:
异步FIFO使用格雷码的唯一目的就是"即使在亚稳态进行读写指针抽样也能进行正确的空满状态判断"。 在异步的FIFO中,采用格雷码进行计数,相邻的数据仅仅只有1bit变化,这样在两个时钟域同步的时候仅仅可能只有1bit产生亚稳态,通过同步以后,亚稳态可以消除,最坏的情况是这1bit采错,但是即使是采错地址也只是相差1个,这对判断空满标志不会产生影响。 如果是采用10十进制进行编码,则相邻的数据可能有很多位同时进行变化,那么如果多位同时产生亚稳态而且同时采错数据,会对寄存器的空满标志做出严重错误的判断,会丢失数据或者读出无用的数据,使系统出错。 结论:个人认为格雷码的作用不能表述为消除亚稳态。
引用wulsong的评论:
【个人的一点理解】所谓的“虚满”、“虚空”概念并不那么重要。以“虚满”为例:写速率大于读速率时,会产生“满”标志,它是由读指针read_ptr转换为格雷码后同步到写时钟域进行判断生成的,相当于慢时钟同步到快时钟(empty也是,这是异步fifo能够正常工作的潜在原因)。慢时钟的读指针经过两级延时(wr_clk)同步到写时钟域,之间延时是写时钟的两个周期。又因为这种情况下读指针大于写指针,因此full可能会提前1~2个周期(写时钟),这对所有的读写频率差都是如此。所以,所谓的“虚满”只是在快时钟域的2级延时,属于正常现象,对FIFO本身没有影响。
module asys_fifo#(
parameter DATA_WIDTH = 16,
parameter DATA_DEPTH = 256,
parameter ADDR_WIDTH = 8
)
(
input rst,
input wr_clk,
input wr_en,
input [DATA_WIDTH-1:0] din,
input rd_clk,
input rd_en,
output reg valid,
output reg [DATA_WIDTH-1:0] dout,
output full,
output empty
);
reg [ADDR_WIDTH:0] wr_addr_ptr;//地址指针,比地址多一位
reg [ADDR_WIDTH:0] rd_addr_ptr;
wire [ADDR_WIDTH-1:0] wr_addr;
wire [ADDR_WIDTH-1:0] rd_addr;
wire [ADDR_WIDTH:0] wr_addr_gray;
reg [ADDR_WIDTH:0] wr_addr_gray_d1;
reg [ADDR_WIDTH:0] wr_addr_gray_d2;
wire [ADDR_WIDTH:0] rd_addr_gray;
reg [ADDR_WIDTH:0] rd_addr_gray_d1;
reg [ADDR_WIDTH:0] rd_addr_gray_d2;
reg [DATA_WIDTH-1:0] fifo_ram[DATA_DEPTH-1:0];
genvar i;
generate
for(i = 0; i < DATA_DEPTH; i = i + 1) begin:fifo_init
always@(posedge wr_clk or negedge rst) begin
if(rst)
fifo_ram[i] <= 'h0;
else if(wr_en && ~full)
fifo_ram[wr_addr] <= din;
else
fifo_ram[wr_addr] <= fifo_ram[wr_addr];
end
end
endgenerate
always@(posedge rd_clk or negedge rst) begin
if(rst) begin
dout <= 'h0;
valid <= 0;
end
else if(rd_en && !empty) begin
dout <= fifo_ram[rd_addr];
valid <= 1'b1;
end
else begin
dout <= 'h0;
valid <= 0;
end
end
assign wr_addr = wr_addr_ptr[ADDR_WIDTH-1:0];
assign rd_addr = rd_addr_ptr[ADDR_WIDTH-1:0];
always@(posedge wr_clk) begin
rd_addr_gray_d1 <= rd_addr_gray;
rd_addr_gray_d2 <= rd_addr_gray_d1;
end
always@(posedge wr_clk or negedge rst) begin
if(rst)
wr_addr_ptr <= 'h0;
else if (wr_en && !full)
wr_addr_ptr <= wr_addr_ptr + 1;
else
wr_addr_ptr <= wr_addr_ptr;
end
always@(posedge rd_clk) begin
wr_addr_gray_d1 <= wr_addr_gray;
wr_addr_gray_d2 <= wr_addr_gray_d1;
end
always@(posedge rd_clk or negedge rst) begin
if(rst)
rd_addr_ptr <= 'h0;
else if(rd_en && !empty)
rd_addr_ptr <= rd_addr_ptr + 1;
else
rd_addr_ptr <= rd_addr_ptr;
end
assign wr_addr_gray = (wr_addr_ptr>>1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr>>1) ^ rd_addr_ptr;
assign full = (wr_addr_gray == {~rd_addr_gray_d2[ADDR_WIDTH:ADDR_WIDTH-1],rd_addr_gray_d2[ADDR_WIDTH-2:0]});
assign empty = (rd_addr_gray == wr_addr_gray_d2);
endmodule
module tb_asys_fifo();
parameter DATA_WIDTH = 16;
parameter DATA_DEPTH = 16;
parameter ADDR_WIDTH = 4;
reg wr_clk, rd_clk ;
reg [DATA_WIDTH-1:0] din ;
reg rd_en ;
reg rst ;
reg wr_en ;
wire [DATA_WIDTH-1:0] dout;
wire empty ;
wire full ;
wire valid;
initial begin
rd_clk=0;
wr_clk=0;
rst=1;
rd_en=0;
wr_en=0;
din=0;
#40
rst = 0 ;
#35
wr_en=1;
#400
rd_en=1;
#400
wr_en=0;
#400
wr_en = 1;
#400
rd_en = 0;
#400
$stop;
end
//写得比读的快,容易写满,产生full信号
always #20 din<=din+1;
always #15 rd_clk=~rd_clk;
always #10 wr_clk=~wr_clk;
//读得比写的快,容易读空,产生empty信号
//always #30 din<=din+1;
//always #10 rd_clk=~rd_clk;
//always #15 wr_clk=~wr_clk;
asys_fifo #(
DATA_WIDTH,
DATA_DEPTH,
ADDR_WIDTH)
u_asyc_fifo (
.rst(rst),
.wr_clk(wr_clk),
.wr_en(wr_en),
.din(din),
.rd_clk(rd_clk),
.rd_en(rd_en),
.valid(valid),
.dout(dout),
.full(full),
.empty(empty)
);
endmodule
结果:
1. 当读时钟频率 > 写时钟频率,容易读空,产生empty信号;
当读空后,同时读写,产生空标志,读出的数据不连续,写入数据不影响。
读出数据与fifo_ram存储数据,看出读出数据不连续:
产生空标志,读出的数据不连续,写入数据不影响
2. 当写时钟频率 > 读时钟频率,容易写满,产生full信号;
当写满后,同时读写,产生满标志,写入的数据不连续,读出数据不影响。
面试问题
芯动科技:
1.一般设计FIFO深度为2的幂次方,现在设计一个深度为6的FIFO要怎么设计?
答:还是采用格雷码,只是找到连续6个相邻变化只有1位的格雷码组
可以采用1-6的格雷码
2.两边时钟差异很大时,比如写时钟500M,读时钟5M,读时钟采写时钟域地址时,相比上一次采样,数据都变了,要怎么设计(大致这个意思)
答:不变 ,读时钟采数据的时候稳定了
全志:
双口ROM同时读写时读写冲突怎么办?