1、实验目的
了解Compact系列的FIFO IP的使用以及配置的方法。
2、实验原理
2.1、FIFO介绍
FIFO即先入先出,在FPGA中,FIFO的作用就是对存储进来的数据具有一个先入先出特性的一个缓存器,经常用作数据缓存或者进行数据跨时钟域传输。FIFO和RAM最大的区别就是FIFO不需要地址,采用的是顺序写入,顺序读出。
在紫光的IP工具中又分为Distribute FIFO和DRM FIFO,其实就是用不同的资源去构成,前者Distribute FIFO也就是分布式FIFO,使用的是片上的LUT资源去构成,而DRM FIFO使用的是片上的DRM资源去构成,DRM构成的FIFO其性能大于LUT资源构成的,不仅容量更大,且可配置更多功能。
本章着重介绍DRM Based FIFO。
注意:
FIFO 写满后禁止继续写入数据,否则将会写溢出。
FIFO 读空后禁止继续读数据,否则将会读溢出。
2.2、 IP配置
以下给出常用的FIFO的配置作为介绍,首先点击快捷工具栏的 IP 图标,进入IP例化设置:
然后在IP目录处选择DRM Based FIFO,在Instance name处为本次实例化的IP取一个名字,接着点击Customise进入IP配置页面。操作示意图如下:
FIFO Type有SYNC和ASYNC两种,第一种是同步FIFO,读写端口共用一个时钟和复位,另一种是异步FIFO,读写时钟和复位均独立。在平常设计中,比较常用的是异步FIFO,因为同步FIFO和异步FIFO的读写时序一模一样,只有读写端口的时钟复位有差异,当异步FIFO的读写端口使用相同的时钟和复位,此时异步FIFO和同步FIFO基本是一致的。
本次实验我们选择异步FIFO(ASYNC),不启用字节读写功能,写地址深度位宽设置为11,写数据位宽设置为8,读地址深度位宽设置为11,读数据位宽设置为8位。然后使能almost_full_water_level和almost_empty_water_level,用来观察FIFO中数据写入和读出的情况。 当我们将Enable Almost Full Water Level和Enable Almost Empty Water Level勾选上,才能看到rd_water_level和wr_water_level,其中rd_water_level和wr_water_level分别代表”可读的数据量”和”已写入的数据量”,其含义与Xilinx的FIFO的wr_data_count和rd_data_count是一致的。
而下面的Almost Full Numbers的设置是表示当写入124个数据时,Almost Full信号就会拉高,Almost Empty Numbers的设置表示当可读数据剩下4个时Almost Empty信号就会拉高。
注意:如果勾选Enable Output Register(输出寄存),输出数据会延迟一个时钟周期。
配置完成的FIFO端口如下:
其他各个端口的信号描述我们可以参考IP手册:
接着我们点击左上角的generate按钮,生成IP;
提示0错误0警告即IP生成完毕,同时PDS将自动打开实例化模板文件fifo_test_tmpl.v,我们打开这个模板文件即可方便的将IP实例化进我们的设计中。
2.3、FIFO的读写时序
因为同步FIFO和异步FIFO的读写时序一致,这里用异步FIFO的读写时序图来做介绍。
注意:复位时高电平有效。读出数据均未勾选Enable Output Register(输出寄存)。
FIFO未满时的写时序
可以看到在1时刻,复位信号时低电平,处于工作状态,此时在wr_clk的上升沿且wr_en为高电平时将数据D0写入FIFO,wr_water_level也从0变1,表示已经写入了一个数据,此时注意看读端口的empty信号,在3时刻empty信号从高变低,意味着读端口已经有数据可以读了,FIFO不再为空,而注意看,rd_clk和wr_clk是不一样的,从1写入到3时刻empty拉低时,经过了3个rd_clk。
所以这里我们可以得出结论:rd_water_level要滞后wr_water_level三个rd_clk。
FIFO将满时的写时序
将满时主要分析full和almost_full信号。假设Almost Full Numbers设置为N-2,在1时刻,此时已经写入了N-6个数据,意味着再写6个数据FIFO就满了,从1时刻到2时刻一共写入了4个数据,因此当wr_water_level变成N-2时,满足条件,可以看到Almost Full信号拉高,再写两个数据FIFO就满了,所以再经过两个时钟周期后,Full信号拉高。
FIFO在满状态下的读时序
在满状态下,FIFO已经有N个数据了,此时在1状态下,rd_clk的上升沿,且rd_en为高电平时,此时从FIFO里读出数据(数据的输出有延时,仿真中延时0.2ns)。此时rd_water_level变成N-1,rd_data输出D0。然后看2时刻,full信号拉低,此时可以看以下,在1时刻到2时刻期间一共经过了3个wr_clk写端口才能判断到此时数据量已经不为满。所以我们可以得出结论,wr_water_level要滞后rd_water_level三个wr_clk。
FIFO将空时的读时序
在1时刻,可读的数据量剩下4,假设Almost Empty Number设为2,在1时刻和2时刻分别读出了两个数据,所以在2时刻下,可读数据量剩下两个,达到Almost Empty Number触发条件,因此almost_empty信号拉高,再过两个时钟周期,即再读两个数据,FIFO将变成空状态,也就是状态3,此时empty信号拉高。
3、代码设计
顶层代码端口列表如下:
FIFO顶层模块:
module fifo_test_top
(
input wire sys_clk ,
input wire rst_n ,
input wire [7:0] wr_data ,
input wire wr_en ,
input wire rd_en ,
output wire [7:0] wr_water_level ,
output wire [7:0] rd_water_level ,
output wire [7:0] rd_data
);
wire wr_full;
wire almost_full;
wire rd_empty;
wire almost_empty;
fifo_test fifo_test_inst (
.wr_clk(sys_clk), // input
.wr_rst(~rst_n), // input
.wr_en(wr_en), // input
.wr_data(wr_data), // input [7:0]
.wr_full(wr_full), // output
.wr_water_level(wr_water_level), // output [11:0]
.almost_full(almost_full), // output
.rd_clk(sys_clk), // input
.rd_rst(~rst_n), // input
.rd_en(rd_en), // input
.rd_data(rd_data), // output [7:0]
.rd_empty(rd_empty), // output
.rd_water_level(rd_water_level), // output [11:0]
.almost_empty(almost_empty) // output
);
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
该模块就是简单的实例化了FIFOIP,将读写控制信号引出,不需要过多介绍。
fifo_test_tb.v测试文件代码如下:
`timescale 1ns/1ns
module fifo_test_tb();
reg sys_clk;
reg rst_n;
reg [7:0] wr_data;
reg wr_en;
reg rd_en;
reg rd_state; //读状态
reg wr_state;
wire [7:0] rd_data;
reg [7:0] rd_cnt;
wire [7:0] rd_water_level;
wire [7:0] wr_water_level;
initial
begin
rst_n <= 1'd0;
sys_clk <= 1'd0;
#20
rst_n <= 1'd1;
end
always#10 sys_clk = ~sys_clk; //50MHZ
always@(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
begin
wr_state <= 1'd0;
wr_en <= 1'd0;
wr_data <= 8'd0;
end
else
begin
case(wr_state)
1'd0: if(wr_water_level == 127) //128个数据
begin
wr_en <= #2 1'd0;
wr_data <= #2 8'd0;
wr_state <= #2 1'd1;
end
else
begin
wr_en <= #2 1'd1;
wr_data <= #2 wr_data+1'b1;
wr_state <= #2 1'd0;
end
1'd1: if(rd_cnt == 127)
wr_state <= #2 1'd0;
default: wr_state <=1'd0;
endcase
end
end
always@(posedge sys_clk or negedge rst_n) begin
if(!rst_n)
begin
rd_state<= 1'd0;
rd_en <= 1'd0;
rd_cnt <= 8'd0;
end
else
begin
case(rd_state)
1'd0: if(rd_water_level >= 8'd128) //等待128个数据
begin
rd_state <= #2 1'd1;
rd_en <= #2 1'd1;
end
else
begin
rd_cnt <= #2 8'd0;
rd_state <= #2 1'd0;
end
1'd1: begin
rd_cnt <= #2 rd_cnt + 1'b1;
if(rd_cnt == 127)
begin
rd_en <= #2 1'd0;
rd_state <= #2 1'd0;
end
end
default: rd_state <= 1'd0;
endcase
end
end
GTP_GRS GRS_INST(
.GRS_N(1'b1)
) ;
fifo_test_top u_fifo_test_top(
.sys_clk ( sys_clk ),
.rst_n ( rst_n ),
.wr_data ( wr_data ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.wr_water_level ( wr_water_level ),
.rd_water_level ( rd_water_level ),
.rd_data ( rd_data )
);
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
本设计分为读写两个状态的控制。分别完成了写入128个数据,和读出128个数据,由于FIFO不需要地址,所以只需要产生使能信号即可。
首先看写状态,在wr_state=0时,拉高写使能,并让wr_data不断累加,往FIFO里面写数据,当wr_water_level=127的时候,拉低写使能,写数据置0,写状态跳转到1,注意此时还会再写入一个数据,所以到此一个写入了128个数据。至于拉低写使能,写数据置0,写状态跳转到1这些操作将在下一个时钟周期才会被采样生效。之后,在wr_state=1时,不断等待rd_cnt,该条件就是判断当读出128个数据的时候,wr_state跳转到0状态。
接下来看读状态,在rd_state=0的时候,一旦可读的数据量超过128个(包括128),状态跳转到rd_state=1下,然后开始读出数据,同时在rd_state=1下用变量rd_cnt对我们的读出数据也进行计数,rd_cnt从0开始计数,当rd_cnt=127的时候会再往FIFO读出一个数据,因为时序逻辑的赋值总在下一个时钟周期才生效。所以在rd_cnt=127时执行的操作要在下一个时钟周期才会被采样生效。所以当前时钟rd_en还是为1,会再从FIFO读出一个数据,所以此时就一共读出了128个数据,下一个时钟周期rd_en和rd_state都将置0。
4、实验现象
右键仿真的文件,选择Run Behavior Simulation开始行为仿真。接下来我们启动Modelsim观察仿真波形
可以发现当wr_en为高电平时wr_data被写入FIFO。同时在下一个时钟wr_water_level变为1,表示FIFO中写入了一个数据。同时rd_water_level在三个时钟周期后加一,也符合之前对fifo的时序分析的时序分析。
当写入128个数据之后wr_state转变为1,并且拉低wr_en停止数据的写入,符合tb代码中的设计,同时在下一个时钟wr_water_level变为128, rd_water_level在三个时钟周期后也变为128。虽然在这里我们拉低了写使能进入读数据的状态,但是不代表FIFO数据的读写不能同时进行,事实上同时对FIFO的读写端口进行操作是十分正常的。
接下来进行读数据的状态,rd_state变为1,拉高读使能rd_en。这时数据在下一个时钟周期被读出,因为FIFO的先进先出的特性,第一个被写入的数据是1,所以读出的第一个数据也是1,同时rd_water_leve减一。
当读数据计数器计数计数到127时拉低读使能,下一个时钟周期后最后一个数据128被读出。