1、FIFO介绍
1.1、基础知识
FIFO(先进先出)是一种数据缓冲机制,广泛应用于 FPGA 中,用于管理数据流的临时存储。它允许数据按顺序进出,确保最早进入 FIFO 的数据最早被读取。说到这里就有一些同学觉得这个是不是和单片机的堆栈差不多,接下来我就带大家分析分析。
FPGA:
1、数据结构特性
- 用途:适用于数据流的管理,如数据缓冲、流量控制等。
- 访问方式:通常提供读写操作,支持双向访问。
2、存储机制
实现方式:可以使用环形缓冲区或链表实现,支持动态增长。
内存分配:通常在 FPGA 设计中,FIFO 的深度可以动态配置以适应不同应用需求。
3、性能与效率
延迟:通常较低,因为数据可以并行读写。
吞吐量:在数据流处理时,吞吐量高。
4、实现复杂度
- 实现复杂性:通常需要考虑溢出和下溢等情况,设计时较复杂。
堆栈(Stack):
1、数据结构特性
常用于函数调用管理、临时数据存储、回溯操作等。
访问方式:仅提供顶端的读写操作,数据只能从顶端进入或离开。
2、存储机制
实现方式:可以使用数组或链表实现,通常有固定的深度。
内存分配:深度在编译时确定,使用固定大小的内存区域。
3、性能与效率
延迟:在多次函数调用时,可能会造成较高的延迟。
吞吐量:适用于较小数据量的存储和管理。
4、实现复杂度
- 实现复杂性:相对简单,通常只需管理一个指针来指示栈顶。
总结:FIFO 更适合处理高速数据流,而在单片机中,堆栈更常用于管理函数调用和局部数据。
对于 FIFO 我们还需要了解一些常见参数:
1、深度(Depth):FIFO 中可以存储的最大数据项数量,影响其缓冲能力。
2、宽度(Width):每个数据项的位数,通常与数据类型相关,如 8 位、16 位或 32 位。
3、时钟频率(Clock Frequency):FIFO 操作的时钟频率,影响其读写速度。
4、读写指针(Read/Write Pointers):用于指示当前读/写位置,确保数据的正确读写,在每个时钟来临沿触发。
5、满标志(Full Flag):指示 FIFO 是否已满,防止溢出。
6、空标志(Empty Flag):指示 FIFO 是否为空,防止读取无效数据。
7、带宽(Bandwidth):FIFO 每单位时间内处理的数据量,通常以比特每秒(bps)表示。
8、延迟(Latency):数据在 FIFO 中存储的平均时间,影响实时性。
9、复位信号(Reset Signal):用于初始化 FIFO 的状态,清空数据和指针。
10、异步接口(Asynchronous Interface):支持与不同时钟域之间的交互,适用于异步数据流。
1.2、FIFO 结构
FIFO 从读写时钟上来分有两类结构:单时钟 FIFO(同步 FIFO)和双时钟FIFO(异步 FIFO)。
同步 FIFO
1、定义:
- 读写操作在同一个时钟域下进行,即读时钟和写时钟相同。
2、特点:
a、简单的控制逻辑,设计和实现相对容易。
b、不需要处理时钟域交叉问题,避免了异步设计带来的复杂性。
c、在高频率应用中,可能会遇到数据碰撞或延迟问题。
3、适用场景
- 适合数据流不复杂的应用,如单片机的串口缓冲。
异步 FIFO
1、定义:
- 读写操作可以在不同时钟域下进行,由两个独立的时钟域控制,它能够解决读写操作在不同时钟域下的时序问题,保证数据的正确传输。
2、特点:
a、需要额外的控制逻辑来处理时钟域交叉问题,如使用双端口 RAM、握手信号等。
b、能够有效处理时钟频率不匹配的情况,适合高速数据流。
c、设计复杂,调试时可能需要考虑数据完整性和时序问题。
3、适用场景
- 适合复杂系统中,如 FPGA 中的图像处理、音频信号处理等需要异步数据流的应用。
接着我们就来看高云 FPGA 其 FIFO 结构图是怎么样的,如下图所示:
异步 FIFO 结构示意图
同步 FIFO 结构示意图
如上图所示,进行对比分析可以发同步 FIFO 的读端口和写端口信号均由一个时钟域控制,而异步 FIFO 的读写端口信号由两个独立的时钟域控制。这就是这两个时钟的由来。
(官方datasheet:https://cdn.gowinsemi.com.cn/IPUG105-1.07_Gowin先进先出队列(FIFO)用户指南.pdf 第28页)
2、实战任务
用 Gowin 生成一个双时钟 FIFO(异步 FIFO),
3、系统框图
4、FIFO IP介绍
1、Options 介绍
Output Register Select(输出寄存器选择):有效时,数据延迟一个周期输出。BSRAM 输出数据需要满足时钟到输出延时。
Controlled by RdEn(输出受 RdEn 控制):输出寄存器功能,是否受读使能(RdEn)控制。
Write Depth:写数据深度
Write Data Width:写数据位宽
Read Depth:读数据深度
Read Data Width:读数据位宽
注:异步 FIFO IP 满足写入数据宽度不同于读出数据宽度,Read Data Width 自动计算等于 Write Depth xWrite Data Width /Read Depth。最重要的一点是 FPGA 内部的片上 SRAM 资源是有限的不要将位宽和深度设置成远远超过实际需求的值,造成 SRAM 资源的浪费。
2、FIFO Implementation
用于实现 FIFO 的资源有三种,分别为 Block SRAM(块 RAM)、Shadow SRAM(分布式 RAM)和 REG(LUT移位寄存器)。
注: BSRAM 和 SSRAM 是创建 FIFO 时最常的存储资源类型,一般小于 1 KB 选择 SSRAM。超过 1KB 的话就可以考虑 BSRAM 了。这只是一个建议。
3、Read Mode(读取模式)
Standard FIFO:标准 FIFO 时序
First-Word Fall Through:FWFT FIFO 不管是否有读使能信号,都会将写入的第一个数马上放在输出数据总线上,读使能拉高后会按顺序输出写入的其他数据。
4、Data Number
Read Data Num:读数据数目
Write Data Num:写数据数目
En_Reset:使能复位。在使能时,读写各自复位,增加输入WrReset、RdReset。
Reset_Synchronization:同一个复位。En_Reset 有效后,Reset_Synchronizati on 可选;若选择,表示使用一个复位,增加输入 Reset。
5、Flag Control
- Almost Full Flag :半满标志使能
会增加输出 Almost_Full,有四种信号类型设置。如下表所示:
信号名称 | 描述 |
---|---|
Full-Single Threshold Constant Parameter | 静态半满单常量阈值,有效时,Set 有效 |
Full-Dual Threshold Constant Parameters | 静态半满双常量阈值,有效时,Set、Clear有效 |
Full-Single Threshold Input Parameter | 动态半满单输入阈值,有效时,增加输入AlmostFullTh |
Full-Dual Threshold Input Parameters | 动态半满双输入阈值,有效,增加输入AlmostFullSetTh、AlmostFullClrTh |
Set:半满置 1 阈值大小。
Clear:半满清 0 阈值大小。
6、Almost Empty Flag
- Almost Empty Flag:半空标志使能。
会增加输出 Almost_Empty,有四种信号类型设置。如下表所示:
信号名称 | 描述 |
---|---|
Empty-Single Threshold Constant Parameter | 静态半空单常量阈值,有效时,Set 有效 |
Empty-Dual Threshold Constant Parameters | 静态半满双常量阈值,有效时,Set、Clear有效 |
Empty-Single Threshold Input Parameter | 动态半满单输入阈值,有效时,增加输入AlmostEmptyTh |
Empty-Dual Threshold Input Parameters | 动态半满双输入阈值,有效,增加输入AlmostEmptySetTh、AlmostEmptyClrTh |
7、ECC Selected
- ECC Selected:ECC 功能,Data width 1-64bit 时有效,勾选后增加输出ERROR。
8、Generation Config
Disable I/O Insertion:选择后不是生成 I/O Buffer,具体效果应该类似于临时存储数据的内存区域。
Read Write check on RAM 功能是防止 RAM 的读写冲突。
注:这里是只介绍 FIFO 配置介绍。如果想了解单时钟 FIFO SC (也可以叫做同步时钟)的话也可以在链接中找到(https://cdn.gowinsemi.com.cn/IPUG105-1.07_Gowin先进先出队列(FIFO)用户指南.pdf 第28页)
5、时序图
同样以官方 IPUG105 datasheet 为例。配置下的一次读写操作示例:
上图所示,深度和位宽皆为8,Almost Full Numbers 的 Set 配置为 1,Almost Empty Numbers 的 Set 配置为 1
上图所示,在 FIFO 复位后,Empty 信号为 1 ,表示 FIFO 为空,此时 Almost_Empty 信号也为 1 ,当 WrEn 写使能被拉高,将数据写入 FIFO ;写入8 个数后 FIFO 写满,写使能将被屏蔽,此时无法再写入数据。在 RdEn 读使能被拉高,FIFO 将数据依次读到 Q 寄存器里面。直到 Empty 信号拉高,此时无法再读出数据。
6、FIFO 核创建
Gowin 软件自带的 FIFO 的 IP 核分为 FIFO(异步 FIFO )、 FIFO SC(单时钟 FIFO )和FIFO HS/FIFO SC HS与前面两个的区别在于支持更高的时钟,如下图所示:
选择 FIFO IP 核 -> 双击 FIFO ,后弹出 IP 核的配置界面,IP 核配置界面如下图所示:
上面绿色框中是需要更改,因为默认是 fifo_top ,在我们实际应用中一般 top 表示几个模块的顶层例化文件,为此我们这里直接将名字改成 fifo_ip ,我们这里写入\读取深度设置为 128 ,读\写数据宽度为 7 ,full满标志输入 120 ,Empty空标志输入 10 ,在 Write\Read Data Num 、En_Reset 和 Reset_Synchronization 都打钩选上。
注:FIFO 的 IO 端口说明,如下表所示:
信号名称 | 方向 | 描述 |
---|---|---|
Data | 输入 | 写入数据 |
Wr\RdClk | 输入 | 写\读时钟 |
Wr\RdEn | 输入 | 写\读使能 |
Reset | 输入 | 复位,高电平有效 |
Almost_Empty | 输出 | 半空标志 |
Empty | 输出 | 空标志 |
Almost_Full | 输出 | 半满标志 |
Full | 输出 | 满标志 |
Wnum | 输出 | 写入数据数目 |
Rnum | 输出 | 可读数据数目 |
Q | 输出 | 读出数据 |
7、程序编写
7.1、FIFO 读模块
接下来我们编写 FIFO 读模块,用于处理 FIFO 的读操作,(fifo_rd.v)代码编写如下:
module fifo_rd(
input rd_clk , //读时钟信号
input rd_rst_n , //复位信号
input [7:0] fifo_Rnum_count , //FIFO读计数
input fifo_full , //FIFO满标准
output reg fifo_rd_en //FIFO读使能
);
parameter FIFO_EMPTY = 8'd1; //FIFO空的标准
always @(posedge rd_clk or negedge rd_rst_n) begin
if(!rd_rst_n)
fifo_rd_en <= 1'd0;
else if(fifo_full) // 如果FIFO满,允许读取
fifo_rd_en <= 1'd1;
else if(fifo_Rnum_count == FIFO_EMPTY) // 如果读计数达到FIFO空的标准,禁止读取
fifo_rd_en <= 1'd0;
else
fifo_rd_en <= fifo_rd_en; // 保持当前读使能状态
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注:
- 第 12 - 21 行:如果 fifo_full 信号为高,表示 FIFO 已满,此时 fifo_rd_en 被设置为 1,允许读取数据,当 FIFO 里面的数据被读取到最后一个时,将读使能关闭,此时不应再进行读取操作,以避免读取错误数据。
7.2、FIFO 写模块
接下来编写 FIFO 写模块,用于处理 FIFO 的写操作,(fifo_wr.v)代码编写如下:
module fifo_wr(
input wr_clk , // 写时钟信号
input wr_rst_n , // 复位信号
input [7:0] fifo_Wnum_count , //FIFO写计数
input fifo_empty , // FIFO空标志
output reg fifo_wr_en , // FIFO写使能
output reg [6:0] fifo_wr_data // FIFO写入数据
);
parameter WR_DATA =8'd128; //写入数据的最大值
always @(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n)
fifo_wr_en <= 1'd0;
else if(fifo_empty) // 如果FIFO空,允许写入
fifo_wr_en <= 1'd1;
else if(fifo_Wnum_count == WR_DATA) // 如果写计数达到最大值,禁止写入
fifo_wr_en <= 1'd0;
else // 保持当前写使能状态
fifo_wr_en <= fifo_wr_en;
end
always @(posedge wr_clk or negedge wr_rst_n) begin
if(!wr_rst_n)
fifo_wr_data <= 8'd0;
else if(fifo_wr_en && fifo_wr_data < WR_DATA) // 如果写使能打开且当前数据小于最大值,写入数据加
fifo_wr_data <= fifo_wr_data + 7'd1;
else
fifo_wr_data <= 8'd0; // 否则重置写入数据
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
注:
第 11 行:宏定义最大写入深度。
第 13 - 22 行:用于控制 FIFO 的写使能信号 fifo_wr_en 。在复位信号 wr_rst_n 为低时,禁止写入。当 FIFO 为空时,允许写入;如果写入计数达到最大值 128 ,则禁止写入设置为 0 。
第 24 - 31 行:用于管理 FIFO 写入数据的计数 fifo_wr_data。写使能信号 fifo_wr_en 为高且当前数据小于最大值 WR_DATA,则将 fifo_wr_data 增加 1;否则,将其重置为 0。这一逻辑确保了写入数据的正确计数和在特定条件下的重置。
7.2、FIFO 顶层模块
接下来编写 FIFO 顶层模块,用于处理 FIFO 的读\写模块的数据交互,(fifo_top.v)代码编写如下:
module fifo_top(
input sys_clk, //系统时钟
input sys_rst_n, // 系统复位信号,低电平有效
output [6:0] fifo_rd_data // FIFO读出数据
);
//wire define
wire fifo_wr_en ; // FIFO写使能
wire fifo_rd_en ; // FIFO读使能
wire [6:0] fifo_wr_data ; // FIFO写入数据
wire almost_full ; // FIFO半满标志
wire almost_empty ; // FIFO半空标志
wire fifo_full ; // FIFO满标准
wire fifo_empty ; // FIFO空标志
wire [7:0] fifo_Wnum_count; // FIFO写计数
wire [7:0] fifo_Rnum_count; // FIFO读计数
//FIFO IP 核
fifo_ip u_fifo_ip(
.Data (fifo_wr_data ), //input [6:0] Data
.Reset (~sys_rst_n ), //input Reset
.WrClk (sys_clk ), //input WrClk
.RdClk (sys_clk ), //input RdClk
.WrEn (fifo_wr_en ), //input WrEn
.RdEn (fifo_rd_en ), //input RdEn
.Wnum (fifo_Wnum_count ), //output [7:0] Wnum
.Rnum (fifo_Rnum_count ), //output [7:0] Rnum
.Almost_Empty (almost_empty ), //output Almost_Empty
.Almost_Full (almost_full ), //output Almost_Full
.Q (fifo_rd_data ), //output [6:0] Q
.Empty (fifo_empty ), //output Empty
.Full (fifo_full ) //output Full
);
//FIFO 写 模块
fifo_wr u_fifo_wr(
.wr_clk (sys_clk ), // 写FIFO时钟
.wr_rst_n (sys_rst_n ), // 复位信号
.fifo_Wnum_count (fifo_Wnum_count ), // FIFO写计数
.fifo_wr_en (fifo_wr_en ), // FIFO写使能
.fifo_wr_data (fifo_wr_data ), // FIFO写入数据
.fifo_empty (fifo_empty ) // FIFO空标志
);
//FIFO 读 模块
fifo_rd u_fifo_rd(
.rd_clk (sys_clk ), // 读时钟
.rd_rst_n (sys_rst_n ), // 复位信号
.fifo_Rnum_count (fifo_Rnum_count ), // FIFO读计数
.fifo_rd_en (fifo_rd_en ), // FIFO读使能
.fifo_full (fifo_full ) // FIFO满标志
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
8、仿真编写
接下来我们编写仿真代码,我们只有系统时钟和系统复位这两个输入信号,而输出信号是用于 Gowin 在线逻辑仿真,fifo_mod.v 代码编写如下:
`timescale 10ns/10ns
module fifo_mod();
//在对 FIFO 或者 SP IP 核等(或其它在底层转换 GSR 时调用了全局复位的 IP/原语)进行仿真时,在仿真代码中需要添加如下这一段代码,否则联合仿真时就会报错。
GSR GSR(.GSRI(1'b1));
//reg define
reg sys_clk; //系统时钟
reg sys_rst_n; //系统复位信号,低电平有效
//wire define
wire [6:0] fifo_rd_data; // FIFO读出数据
always #10 sys_clk = ~sys_clk;
initial begin
sys_clk = 1'd0;
sys_rst_n = 1'd0;
#200;
sys_rst_n = 1'd1;
#1000;
end
//FIFO 实例化被测模块
fifo_top u_fifo_top(
.sys_clk (sys_clk), //系统时钟
.sys_rst_n (sys_rst_n), // 系统复位信号,低电平有效
.fifo_rd_data (fifo_rd_data) // FIFO读出数据
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
注释:
第 9 - 14 行:用于定义 FIFO 的信号。
第 33 - 38 行:和 fifo_top 的时钟、复位和 FIFO 读出数据信号进行绑定。
接下来打开 Modelsim 软件对代码进行仿真,需要添加文件如下图所示:
将 fifo_mod 的端口信号添加至波形窗口进行观察,然后观察全局波形,首先我们来查看往 FIFO 里面写入数据和 alimost_full 信号,如下图所示:
从上面波形中我们可以看出,在往 FIFO 里面写入数据到了 120 的时候,可以看见 almost_full ,半满标志被拉高了,证明我们的 FIFO 快装满了。
从上面波形中我们可以看出,在 FIFO 读出到第 117 个数据的时候,就开始将半空标准拉高,证明我们的 FIFO 已经快读完了。当读完最后一个数时,fifo_rd_en 被拉低,禁止读使能。
9、I/O 引脚绑定
在仿真验证完成后,接下来使用 Gowin 对时钟约束和引脚进行分配并上板验证。本实验中,系统时钟、和复位按键(其余端口仅仅只是为了仿真使用,可以不用绑定)管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | F10 | 复位 | LVCMOS33 |
Gowin 软件中 I/O Constraints 界面如下图所示:
7、在线逻辑分析仪
我们需要添加读写数据,空满信号等 如下图所示:
GAO 中观察到的波形如下图所示:
写使能拉高后,开始向 FIFO 中写入数据,当写到第 120 时数据后,半满信号(Almost_Full)拉高(白色线),写到第 127 个信号后,也就是最后一个信号(黄色),此时写使能关闭,等待读使能打开。如下图所示:
读使能拉高后,开始从 FIFO 中读出数据,当读到第 116 时数据后,半空信号(Almost_Empty)拉高(白色线),当读出最后一个数据后,空信号(fifo_empty)拉高,此时读使能关闭,等待写使能打开。如下图所示:
注:若出现乱跳情况可以忽略,这是因为在线逻辑分析仪抓的信号可能不准确(仿真没有问题即可),等待后续更新。如下图所示:
至此、我们的 FIFO 实验就到处结束。