本页是有关 FIFOs的五页系列中的第四页。本页提出了一种应对可能发生溢出的方法。
介绍
在许多使用 FIFO的应用程序中,无法控制到达的数据流。例如,在数据采集(data acquisition)应用中,逻辑将捕获的数据直接写入 FIFO。如果 FIFO 已满,该数据就会丢失。在此类应用中, FIFO 的读取器负责足够快地消耗 FIFO 中的数据,以便 FIFO 永远不会变满。
但是如果 FIFO 变满了,就会因为数据丢失而破坏数据的连续性: FIFO 忽略写入尝试。结果 FIFO 的内容不正确。这种情况称为溢出。
溢出是某种故障的结果,应该避免。然而,如果发生溢出,检测此事件并减轻损害仍然很重要。下面建议了一个策略。
改装的 FIFO
主要努力应该始终是防止溢出的发生。但如果失败,剩下的就是确保 FIFO不会消耗错误的数据。换句话说,从 FIFO 读取的所有数据都是连续且正确的。
建议的解决方案是修改后的 FIFO,它有两点不同:
- 当溢出发生时(即 FIFO 已满),在 FIFO 复位之前,不能再向 FIFO 写入数据。
- 当 FIFO 在溢出之后变为空时, FIFO 报告它将保持为空,直到发生复位(reset)。我们将这种情况称为 EOF。
第一个功能确保从修改后的 FIFO 读取的数据始终是连续且正确的: 连续性破坏后 FIFO 拒绝写入数据。第二个功能 (EOF) 向使用 FIFO的逻辑发送出现问题的消息。这使逻辑有机会重新启动数据流。
EOF 这个名字来自 End of File: 此修改后的 FIFO 对于基于 Xillybus IP core的数据采集应用程序非常有用。在此类应用中, Xillybus IP core 将数据传输到计算机。一个简单的计算机程序使用标准 file I/O 应用程序接口来读取 FIFO中的数据。换句话说,计算机程序以通常的方式打开文件并从中读取数据。 FIFO的 EOF 使该文件的行为与到达常规文件末尾时类似。这是对没有更多数据可供读取这一事实的自然反应。
Verilog中的实现
这是一个 Verilog 模块的示例,它实现了上面建议的修改后的 FIFO :
module eof_fifo
(
input rst,
input wr_clk,
input rd_clk,
input [31:0] din,
input wr_en,
input rd_en,
output [31:0] dout,
output full,
output empty,
output eof
);
reg rst_sync;
reg rst_cross;
reg fifo_has_been_full;
reg fifo_has_been_nonfull;
reg has_been_full_cross;
reg has_been_full;
assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;
assign eof = empty && has_been_full;
always @(posedge wr_clk)
begin
if (!full)
fifo_has_been_nonfull <= 1;
else if (rst_sync)
fifo_has_been_nonfull <= 0;
if (full && fifo_has_been_nonfull)
fifo_has_been_full <= 1;
else if (rst_sync)
fifo_has_been_full <= 0;
end
// Clock domain crossing logic: asynchronous -> wr_clk
always @(posedge wr_clk)
begin
rst_cross <= rst;
rst_sync <= rst_cross;
end
// Clock domain crossing logic: wr_clk -> rd_clk
always @(posedge rd_clk)
begin
has_been_full_cross <= fifo_has_been_full;
has_been_full <= has_been_full_cross;
end
fifo fifo_ins
(
.rst(rst),
.wr_clk(wr_clk),
.rd_clk(rd_clk),
.din(din),
.wr_en(wr_en && ok_to_write),
.rd_en(rd_en),
.dout(dout),
.full(full),
.empty(empty)
);
endmodule
很明显,这个模块由标准 FIFO(standard FIFO)的例化(instantiation)加上一些额外的逻辑组成。请注意,这个模块的端口与标准 FIFO的端口几乎完全相同。只有一个区别: 修改后的 FIFO 有一个名为 @eof的端口。
另请注意, @din 和 @dout 的宽度为 32 比特。如果需要不同宽度的 FIFO ,唯一需要的更改是在模块顶部声明这些端口。
改装后的 FIFO 有个 @full 端口,不一定有用。写入 FIFO 的逻辑可能会忽略这个端口: 如果 FIFO 满了,无论如何也无能为力。不管怎样, FIFO 将忽略以后向其中写入数据的尝试。在大多数应用中,知道 FIFO 已满并没有多大帮助: 最好等到 @eof 变高后再重新启动整个机制。
防止溢出之后的写操作
前面已经提到过,这个模块是基于标准 FIFO的。除了一个端口之外,所有 FIFO的端口都直接连接到 eof_fifo的端口: @wr_en。该端口改为连接到“wr_en && ok_to_write”。所以很明显, @ok_to_write 是用来在 FIFO 写满后停止写操作的。这根线(wire)的定义是:
assign ok_to_write = !rst_sync && !full && !fifo_has_been_full;
从这个表达式我们得知,有三种情况会阻止写操作:
- 当 FIFO 复位时
- 当 FIFO 满时
- 当 FIFO 已经满过去的时候
第两个条件是微不足道的。我们重点关注第三个条件,以 @fifo_has_been_full为代表:
always @(posedge wr_clk)
begin
if (!full)
fifo_has_been_nonfull <= 1;
else if (rst_sync)
fifo_has_been_nonfull <= 0;
if (full && fifo_has_been_nonfull)
fifo_has_been_full <= 1;
else if (rst_sync)
fifo_has_been_full <= 0;
end
当 FIFO 已满时,@fifo_has_been_full 为高电平。当 @full 和 @fifo_has_been_nonfull 都为高电平时,寄存器(register)变为高电平。
第一部分并不奇怪: @full 连接到 FIFO的 "full" 端口。但为什么需要 @fifo_has_been_nonfull ?原因是,只要 FIFO 保持在复位状态, FIFO 就经常将其“full”保持在高电平。该功能的目的是告诉应用逻辑 FIFO 尚未准备好接收数据。 @fifo_has_been_nonfull 的目的是防止 @fifo_has_been_full 在这种情况下错误地变高。
当 FIFO 复位时,@fifo_has_been_full 变为低电平。换句话说,在发生溢出后,重置 FIFO 是修改后的 FIFO 恢复正常操作的唯一方法。请注意, @rst 是异步复位(asynchronous reset)。因此,有必要添加逻辑才能创建属于正确时钟域(clock domain)的复位信号(reset signal)。这个信号是 @rst_sync,它是 @rst的副本。
生成 EOF
当同时满足以下两个条件时, @eof 端口为高电平:
- FIFO 中的所有数据已被消耗: FIFO 是空的。
- 不再有数据写入 FIFO: @fifo_has_been_full 很高,因为 FIFO 过去已经满了。因此, @ok_to_write 保持低电平,直到 FIFO 复位。
这些条件的 Verilog 代码中的表达式为:
assign eof = empty && has_been_full;
请注意,此表达式基于 @has_been_full 而不是 @fifo_has_been_nonfull: @eof 和 @empty 都属于 @rd_clk的时钟域。但 @fifo_has_been_nonfull 属于 @wr_clk的时钟域。因此, @fifo_has_been_nonfull 凭借跨时钟域(clock domain crossing)被复制到 @rd_clk的时钟域中。这个副本是 @has_been_full。
所以 @eof 的意思是“ FIFO 不仅是空的,而且在重置 FIFO之前它不会被填满”。
结论
在许多应用中,不可能保证溢出不会发生。如果此事件无法容忍,则可以在发生时减少损害: 策略是让已经写入的数据通过,直到溢出,然后不再允许写入更多数据。最终 FIFO 将变空。发生这种情况时, FIFO 会报告不再有数据通过 @eof 端口到达。
该方法保证了从 FIFO读取的数据的连续性: 中间没有因为 FIFO满而导致数据丢失。这使得该方法对于数据采集应用程序非常有用。
本页展示了如何实现基于此策略的修改版 FIFO 。此修改版 FIFO 可用作标准 FIFO的 drop-in replacement 。唯一重要的区别是使用 FIFO 的逻辑必须注意 @eof 端口: 当端口变高时,逻辑必须重置 FIFO 并重新启动数据流。
关于 FIFOs的系列文章的第四页到此结束。下一页将展示如何将“标准 FIFO”变成 FWFT FIFO ,反之亦然,以及如何改进时序(timing)。