笔记根据正点原子官方教学视频第21讲
时间:2025/4/7
【第一期】手把手教你学领航者&启明星ZYNQ之FPGA开发篇【真人出镜】FPGA教学视频教程_哔哩哔哩_bilibili
有错误的地方希望可以告诉博主
FIFO是什么
FIFO(First In First Out):即先进先出,FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存(如数据读写带宽不同步),或者高速异步数据的交互也即所谓的跨时钟域信号传递。
FIFO的分类
同步FIFO:指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。
异步FIFO:指读写时钟不一致,读写时钟是互相独立的。
FIFO的端口示意图
下图中黑色为必须信号、蓝色为可选信号、灰色为可选的附加部分。
部分常用信号解释如下:
写时钟域信号 | 介绍 | 读时钟域信号 | 介绍 |
wr_clk | 写时钟,用于同步写入操作。 | rd_clk | 读时钟,用于同步读取操作。 |
wr_en | 写使能,当该信号为高时,允许数据写入FIFO。 | rd_en | 读使能,当该信号为高时,允许数据从FIFO读出。 |
din | 数据输入。 | dout | 数据输出。 |
full | 满标志,当FIFO已满时,该信号为高。 | empty | 空标志,当FIFO为空时,该信号为高。 |
almost_full | 几乎满标志,当FIFO接近满时,该信号为高。 | almost_empty | 几乎空标志,当FIFO接近空时,该信号为高。 |
Prog_full | 可编程满标志,用于用户自定义的满状态。 | prog_empty | 可编程空标志,用于用户自定义的空状态。 |
wr_ack | 写确认,表示写操作已被确认。 | valid | 有效信号,表示输出数据有效。 |
wr_data_count | 写数据计数,表示FIFO中已写入的数据量。 | rd_data_count | 读数据计数,表示FIFO中已读取的数据量。 |
overflow | 上溢信号,当写入已满的FIFO时产生。 | underflow | 下溢信号,当读取空的FIFO时产生。 |
FIFO时序
同步FIFO读写时序
下图没有给出 din 信号,din 信号数据用于在写使能时写入 FIFO。
解释如下:
假设 FIFO 可以装入四个数据。当 wr_en 为高电平时,写使能,开始写入数据,写入一个数据后 FIFO 不为空,空标志 empty 拉低。接着 rd_en 拉高,读使能,读出一个数据,此时 FIFO 中只有一个数据,所有 almost_full 标志位仍为高电平。当写入第三个数据后,此时 FIFO 中包含两个数据,almost_full 标志位拉低。
当 wr_en 再次拉高时,写使能,再次写入数据。写入一个数据后,此时 FIFO 中包含三个数据,almost_full 信号拉高。紧接着同时写入一个数据和读出一个数据,almost_full 信号不变,当再写入一个数据后,FIFO 被写满,full 信号被拉高。
异步FIFO写时序
D4 是无法写入的,因为写入数据 D3 后,FIFO 的 full 信号被拉高,表示 FIFO 已经写满了。因为 D4 及之后的数据无法写入,故也没有返回 wr_ack 信号。同时 overflow 信号拉高,表示上溢(即 FIFO 已经写满了,但还在写)了。当 FIFO 写满后,almost_full 信号通常会保持拉高状态,直到FIFO中的数据量减少到阈值以下。
异步FIFO读时序图(正常模式)
读出 D0 后,在读出 D1 时,almost_empty 被拉高,表示 FIFO 中还剩下一个数据可读。在读出 D2 时,empty 拉高表示 FIFO 中的内容被全部读出。后续继续再读取时,valid 信号拉低,表示数据无效,且 underflow 信号拉高表示发生了下溢(即没有数据后还在读取)。当 FIFO 读空后,almost_empty 信号通常也会保持拉高状态,直到有新的数据写入 FIFO,使得数据量超过阈值。
异步FIFO读时序图(预读模式)
相当于正常模式提前一拍输出数据。
实验任务
本节的实验任务是使用 Vivado 生成一个异步 FIFO,并实现以下功能:当FIFO为空时,向FIFO中写入数据,直至将FIFO写满后停止写操作;当FIFO为满时,从FIFO中读出数据,直到FIFO被读空后停止读操作。如:将FIFO设置的深度和宽度分别为256和8进行读写测试。
FIFO IP 核
详细配置说明请一定要参照官方配置文档。
具体 IP 核参数使用请参照手册(FIFO Generator v13.2 Product Guide (PG057) • 查看器 • AMD 技术信息门户网站) ,查看手册是必要的能力,这里不再对更多具体参数进行详细解释。
这里对 wr_rst_busy 和 rd_rst_busy 信号做一个解释。
wr_rst_busy 表示写端口的复位操作正在进行中。当写端口接收到复位信号时,wr_rst_busy 会被拉高,表示复位操作正在进行。复位完成后,wr_rst_busy 会自动拉低,表示复位完成。
rd_rst_busy 表示读端口的复位操作正在进行中。当读端口接收到复位信号时,rd_rst_busy 会被拉高,表示复位操作正在进行。复位完成后,rd_rst_busy 会自动拉低,表示复位完成。
这样做的好处是确保FIFO模块在复位完成后才能正常工作,避免因复位过程中的状态不稳定导致数据错误。不过通常在实际使用中,这两个信号经常不会使用,因为 FIFO 一般不会在项目启动时就开始工作,一般当 FIFO 被使用时,复位操作早就已经完成了。
模块框图
因为读模块和写模块的时钟独立,故需要使用 PLL 模块来得到两个时钟。
写模块的框图如下:
为什么不使用 full 信号作为满标志?因为当 full 信号拉高时,要在下一个时钟周期才可以采集到,这会导致 fifo_wr_en 使能信号慢一个周期被拉低。 当然使用 full 信号也是可以的,通过组合逻辑的方式,与上 full 信号的非,也可以做到及时拉低使能信号。
同理,读模块的框图如下:
最后,需要将这些信号接入 FIFO IP核例化的模块中,系统框图如下所示。
模块时序
读模块时序
对于读模块而言,full 信号属于异步信号,需要打两拍来进行同步。
写模块时序
对于写模块而言,empty 信号属于异步信号,需要打两拍来进行同步。
代码编写
fifo_wr 写模块代码如下:
module fifo_wr(
input clk_50m , // 写时钟
input rst_n ,
// FIFO
input empty ,
input almost_full ,
input wr_rst_busy ,
output reg fifo_wr_en ,
output reg [7:0] fifo_wr_data
);
reg empty_d0;
reg empty_d1;
// 对empty打两拍同步到写时钟域下
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
empty_d0 <= 1'b0;
empty_d1 <= 1'b0;
end
else begin
empty_d0 <= empty;
empty_d1 <= empty_d0;
end
end
// 对写使能信号进行赋值
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n)
fifo_wr_en <= 1'b0;
else if (!wr_rst_busy) begin
if (empty_d1)
fifo_wr_en <= 1'b1;
else if (almost_full)
fifo_wr_en <= 1'b0;
end
end
// 对fifo_wr_data赋值,0-254
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n)
fifo_wr_data <= 8'd0;
else if (fifo_wr_en && fifo_wr_data < 8'd254)
fifo_wr_data <= fifo_wr_data + 8'd1;
else
fifo_wr_data <= 8'd0;
end
endmodule
fifo_rd 读模块代码如下:
module fifo_rd(
input clk_100m , // 读时钟
input rst_n ,
// FIFO
input reg [7:0] fifo_rd_data,
input full ,
input almost_empty,
input rd_rst_busy ,
output reg fifo_rd_en
);
reg full_d0;
reg full_d1;
// 对full打两拍同步到读时钟域下
always @(posedge clk_100m or negedge rst_n) begin
if (!rst_n) begin
full_d0 <= 1'b0;
full_d1 <= 1'b0;
end
else begin
full_d0 <= full;
full_d1 <= full_d0;
end
end
// 对fifo_rd_en进行赋值,FIFO写满之后开始读,读空之后停止
always @(posedge clk_100m or negedge rst_n) begin
if (!rst_n)
fifo_rd_en <= 1'b0;
else if (!rd_rst_busy) begin
if (full_d1)
fifo_rd_en <= 1'b1;
else if (almost_empty)
fifo_rd_en <= 1'b0;
end
end
endmodule
顶层模块代码如下:
module ip_fifo(
input sys_clk,
input sys_rst_n
);
wire clk_50m;
wire clk_100m;
wire locked;
wire rst_n;
wire empty;
wire almost_full;
wire wr_rst_busy;
wire fifo_wr_en;
wire [7:0] fifo_wr_data;
wire full;
wire almost_empty;
wire rd_rst_busy;
wire fifo_rd_en;
wire [7:0] fifo_rd_data;
wire [7:0] rd_data_count;
wire [7:0] wr_data_count;
assign rst_n = sys_rst_n & locked;
clk_wiz_0 instance_name
(
.clk_out1 (clk_50m ),
.clk_out2 (clk_100m ),
.locked (locked ),
.clk_in1 (sys_clk )
);
fifo_wr u_fifo_wr(
.clk_50m (clk_50m ),
.rst_n (rst_n ),
.empty (empty ),
.almost_full (almost_full ),
.wr_rst_busy (wr_rst_busy ),
.fifo_wr_en (fifo_wr_en ),
.fifo_wr_data (fifo_wr_data )
);
fifo_rd u_fifo_rd(
.clk_100m (clk_100m ),
.rst_n (rst_n ),
.fifo_rd_data (fifo_rd_data ),
.full (full ),
.almost_empty (almost_empty ),
.rd_rst_busy (rd_rst_busy ),
.fifo_rd_en (fifo_rd_en )
);
fifo_generator_0 your_instance_name (
.rst (~rst ), // 取反是因为FIFO的复位是高有效
.wr_clk (clk_50m ),
.rd_clk (clk_100m ),
.din (fifo_wr_data ),
.wr_en (fifo_wr_en ),
.rd_en (fifo_rd_en ),
.dout (fifo_rd_data ),
.full (full ),
.almost_full (almost_full ),
.empty (empty ),
.almost_empty (almost_empty ),
.rd_data_count (rd_data_count),
.wr_data_count (wr_data_count),
.wr_rst_busy (wr_rst_busy ),
.rd_rst_busy (rd_rst_busy )
);
endmodule