“在数字IC设计中异步FIFO常用来解决多比特数据跨时钟域的数据传输与同步问题,就像一个蓄水池,用于调节上下游水量”
01
异步FIFO简介
在大规模ASIC设计中,多时钟系统通常是不可避免的,这会导致不同时钟域中的数据传输问题。其中一个好的解决方案是使用 异步FIFO来缓冲不同时钟域中的数据 ,并改善它们之间的传输效率。数据从一个时钟域写入FIFO缓冲区,并从另一个时钟域中读取,该缓冲区彼此异步。异步FIFO允许数据从一个时钟域安全地传输到另一个时钟域。
如果没有采取适当的预防措施,那么我们最终可能会遇到这样的情况:写入FIFO尚未完成,我们正在尝试读取它,反之亦然。这种情况通常会导致数据丢失和不稳定性问题。
为了避免这种情况,读取和写入是通过同步器完成的。同步器确保读写指针计算一致,并且 FIFO 中的数据不会被意外覆盖或读取两次。但是,对于跨时钟域,我们需要确保FIFO满和空条件都考虑到跨时钟周期。换句话说,需要添加悲观的满空条件。
异步FIFO的操作:
写入操作:
此操作涉及将数据写入或存储到FIFO中,直到它出现不再写入的任何标志条件。
要执行写入操作,输入端给出要写入的数据,并且要将写使能设置为高电平,然后在写入时钟的下一个上升沿写入数据。
读取操作:
当我们必须从FIFO存储器中获取数据时,执行读取操作,直到它通知没有更多的数据要从存储器中读取。
要执行读取操作,我们需要将读取使能设置为高电平,然后在读取时钟的下一个上升沿,输出读取的数据
控制操作的指针:
写指针:此指针控制 FIFO 的写入操作。它指向要写入下一个数据的内存位置。
读取指针:读取操作由读取指针控制。它指向要从中读取下一个数据的位置。
FIFO中的标志:
异步FIFO为我们提供了以下两个标志,用于确定状态并中断FIFO的操作。
- 空标志: 此标志可用于避免在 FIFO 已为空时出现读取操作请求无效的情况。
- 满标志: 此标志可用于避免在 FIFO 已满时出现无效的写入操作请求的情况。
将二进制计数值从一个时钟域同步到另一个时钟域将出现亚稳态问题,因为n位计数器的每个位都可以同时更改(例如,二进制数7-> 8的值为0111-> 1000,并且所有位都已更改)。 而使用格雷码相邻数据只有一位变化 ,因此在两个时钟域间同步多个位不会产生问题。所 以需要一个二进制到gray码的转换电路 , 将地址值转换为相应的gray码 , 然后将该gray码同步到另一个时钟域进行对比 ,作为空满状态的检测。二进制转为格雷码代码如下:
使用gray码解决了一个问题,但同时也带来另一个问题, 即使用格雷码如何判断空满状态 。判断读空状态时: 需要在读时钟域的格雷码gray_code和被同步到读时钟域的写指针每一位完全相同;判断写满时:需要写时钟域的格雷码和被同步到写时钟域的读指针高两位不相同,其余各位完全相同;
由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后再进行比较,常用的方法采用两级寄存器进行同步(简单点就是在另外一个时钟域打2拍)。
综上,编写异步FIFO主要有以下几点:
- 将二进制地址转换为格雷码;
- 将写时钟域转换后格雷码的写地址同步到读时钟域,进行读空判断;
- 将读时钟域转换后格雷码的读地址同步到写时钟域,进行写满判断;
02
Verilog代码
module fifo_async#(
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 empty,
output full
);
reg [addr_width:0] wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈
reg [addr_width:0] rd_addr_ptr;
wire [addr_width-1:0] wr_addr;//RAM 地址
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];
//数据写入 fifo
genvar i;
generate
for(i = 0; i < data_depth; i = i + 1 )
begin:fifo_init
always@(posedge wr_clk or posedge 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
//========================================================read_fifo
always@(posedge rd_clk or posedge rst)
begin
if(rst)
begin
dout <= 'h0;
valid <= 1'b0;
end
else if(rd_en && (~empty))
begin
dout <= fifo_ram[rd_addr];
valid <= 1'b1;
end
else
begin
dout <= dout;
valid <= 1'b0;
end
end
//FIFO实际读写地址
assign wr_addr = wr_addr_ptr[addr_width-1-:0];
assign rd_addr = rd_addr_ptr[addr_width-1-:0];
//读地址格雷码同步到写时钟域:打2拍
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 posedge 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
//写地址格雷码同步到读时钟域:打2拍
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 posedge 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-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
endmodule
评论
查看更多