读写指针表示
- 在异步FIFO的设计中,由于二进制的计数值跨时钟域时有可能出现亚稳态,所以可以使用格雷码来解决这个问题。
- 格雷码特点:
- 相邻格雷码之间只有一位变化,其他位相同,所以可以降低亚稳态出现的概率。
- 格雷码是循环码,0 和 2^n-1 之间只有一位不同。当 FIFO 深度是 2 次幂时,可以满足用格
雷码消除亚稳态。
写满、读空判断
在读时钟域进行空状态判断,在写时钟域进行满状态判断。
- 写满:将读时钟域的读指针同步到写时钟域,被同步的读指针与写时钟域的写指针高两位不一致,其余完全相同。
- 读空:将写时钟域的二进制写指针转化为格雷码经过两级触发器同步到读时钟域,被同步的写指针与读时钟域的读指针每一位完全相同。
异步FIFO读指针属于读时钟域,写指针属于写时钟域,读写时钟域不同,如下所示:
判断机制: 最高位与次高位相同,其余位相同则为读空。 最高位与次高位不同,其余位相同则为写满。
电路结构
- 在电路中,使用了一个双端口SRAM来作为FIFO的memory,用来存储上游节点的写数据wdata,然后在下游节点读出rdata。此外,SRAM的读写地址采用了每次只递增1的机制,保证了写入和读出的顺序进行,写和读到最高地址后,重新返回零地址。
- 满信号生成电路:在上游节点和SRAM之间有一个满信号生成电路。这个电路通过判断写时钟域下写指针和读指针的关系,实时生成满信号full以通知上游节点停止写操作。
- 空信号生成电路:在下游节点和SRAM之间有一个空信号生成电路。这个电路通过判断读时钟域下写指针和读指针的关系,实时生成空信号empty以通知下游节点停止读操作。
- 将读指针传递到写时钟域才能产生满信号,将写指针传递到读时钟域才能产生空信号。
- 正确地产生空满标志是任何FIFO设计的关键,空满状态产生的原则是:写满而不溢出,读空而不多读。
代码
module async_fifo
#(parameter DATA_WIDTH=32, parameter ADDR_WIDTH=3)
(
input wr_clk,
input wr_rst_n_i,
input wr_en_i,
input [DATA_WIDTH-1:0] wr_data_i,
input rd_clk,
input rd_rst_n_i,
input rd_en_i,
output reg [DATA_WIDTH-1:0] rd_data_o,
output full_o,
output empty_o
);
reg [ADDR_WIDTH:0] wr_ptr;
reg [ADDR_WIDTH:0] rd_ptr;
wire FIFO_DEPTH = 1 << ADDR_WIDTH;
reg [DATA_WIDTH-1:0] RAM [0:FIFO_DEPTH-1];
reg [ADDR_WIDTH:0] gray_wr_ptr;
reg [ADDR_WIDTH:0] gray_wr_ptr_next;
reg [ADDR_WIDTH:0] gray_wr2rd_ptr_1;
reg [ADDR_WIDTH:0] gray_wr2rd_ptr_2;
reg [ADDR_WIDTH:0] gray_rd_ptr;
reg [ADDR_WIDTH:0] gray_rd_ptr_next;
reg [ADDR_WIDTH:0] gray_rd2wr_ptr_1;
reg [ADDR_WIDTH:0] gray_rd2wr_ptr_2;
wire full_comb;
wire empty_comb;
interger i;
always@(posedge wr_clk or negedge wr_rst_n_i )
if(!wr_rst_n_i)
wr_ptr <= {ADDR_WIDTH+1}{1'b0};
else if(wr_en_i && !full_o)
wr_ptr <= wr_ptr +1'b1;
else
wr_ptr <= wr_ptr;
always@(posedge rd_clk or negedge rd_rst_n_i )
if(!rd_rst_n_i)
rd_ptr <= {ADDR_WIDTH+1}{1'b0};
else if(rd_en_i && !empty_o)
rd_ptr <= rd_ptr +1'b1;
else
rd_ptr <= rd_ptr;
assign gray_wr_ptr = (wr_ptr>>1)^wr_ptr ;
always@(*)
begin
gray_wr_ptr_next= gray_wr_ptr;
end
always@(posedge rd_clk or negedge rd_rst_n)
begin
if(!rd_rst_n)
begin
gray_wr2rd_ptr_1 <= {ADDR_WIDTH+1}{1'b0};
gray_wr2rd_ptr_2 <= {ADDR_WIDTH+1}{1'b0};
end
else
begin
gray_wr2rd_ptr_1 <= gray_wr_ptr_next;
gray_wr2rd_ptr_2 <= gray_wr2rd_ptr_1;
end
end
assign gray_rd_ptr = (rd_ptr>>1)^rd_ptr ;
always@(*)
begin
gray_rd_ptr_next= gray_rd_ptr;
end
always@(posedge wr_clk or negedge wr_rst_n)
begin
if(!wr_rst_n)
begin
gray_rd2wr_ptr_1 <= {ADDR_WIDTH+1}{1'b0};
gray_rd2wr_ptr_2 <= {ADDR_WIDTH+1}{1'b0};
end
else
begin
gray_rd2wr_ptr_1 <= gray_rd_ptr_next;
gray_rd2wr_ptr_2 <= gray_rd2wr_ptr_1;
end
end
assign full_comb =( {~(gray_rd2wr_ptr_2[ADDR_WIDTH:ADDR_WIDTH-2]),(gray_rd2wr_ptr_2[ADDR_WIDTH-2:0])}== gray_wr_ptr);
always@(posedge wr_clk or negedge wr_rst_n)
begin
if(!wr_rst_n)
full_o <= 1'b0;
else
full_o <= full_comb;
end
assign empty_comb= (gray_wr2rd_ptr_2==gray_rd_ptr);
always@(posedge rd_clk or negedge rd_rst_n)
begin
if(!rd_rst_n)
empty_o <=1'b0;
else
empty_o <= empty_comb;
end
always@(posedge wr_clk or negedge wr_rst_n_i )
if(!wr_rst_n_i)
begin
for(i=0;i<FIFO_DEPTH;i=i+1)begin
RAM[i] <= 0;
end
end
else if(wr_en_i && !full_comb)
RAM[wr_ptr] <= wr_data_i;
always@(posedge rd_clk or negedge rd_rst_n_i)
if(!rd_rst_n_i)
begin
rd_data_o <= 0;
end
else if(rd_en_i && !empty_comb)
rd_data_o <= RAM[rd_ptr];
endmodule
FIFO深度计算
在设计FIFO深度时需要分析轻载和重载时数据的传输任务,一般来说,应该考虑FIFO在重载时的性能,如果其能在重载时满足需求,轻载的时候肯定也没问题。 举例: FIFO的写时钟为100MHz,读时钟为80MHz。在FIFO输入侧,每100个写时钟,写入80个数据;读数据侧,假定每个时钟读走一个数据。问FIFO深度设置多少可以保证FIFO不会上溢出和下溢出?
其数据传输情况如下所示: 我们假设写入时为最坏情况(背靠背),即在160 X(1/100)微秒内写入160个数据。以下为写入160个burst数据的时间的计算方法: 在这段时间内只能读出160 X (1/100) X 80个数据。 FIFO的深度即为burst长度减去读出数据的长度。 即FIFO深度至少应为32。
具体细节可参考:硬件加速设计方法
|