目录:
1.FIFO IP 核简介
FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。
FIFO 的典型结构如下,主要分为读和写两部分,另外就是状态信号,空和满信号,同时还有数据的数量状态信号,与 RAM 最大的不同是 FIFO 没有地址线,不能任意读取某个地址的数据。而 FIFO 则不同,不能进行随机读取,这样的好处是不用频繁地控地址线。
虽然用户看不到地址线,但是在 FIFO 内部还是有地址的操作的,用来控制 RAM 的读写接口。其地址在读写操作时如下图所示,其中深度值也就是一个 FIFO 里最大可以存放多少个数据。初始状态下,读写地址都为 0,在向 FIFO 中写入一个数据后,写地址加 1,从 FIFO 中读出一个数据后,读地址加 1。此时 FIFO 的状态即为空,因为写了一个数据,又读出了一个数据。
可以把 FIFO 想象成一个水池,写通道即为加水,读通道即为放水,假如不间断的加水和放水,如果加水速度比放水速度快,那么 FIFO 就会有满的时候,如果满了还继续加水就会溢出overflow,如果放水速度比加水速度快,那么 FIFO 就会有空的时候,所以把握好加水与放水的时机和速度,保证水池一直有水是一项很艰巨的任务。也就是判断空与满的状态,择机写数据或读数据。
根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO。 同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。
同步 FIFO 和异步 FIFO 各自的作用不同。同步 FIFO 常用于同步时钟的数据缓存,异步 FIFO 常用于跨时钟域的数据信号的传递,例如时钟域 A 下的数据 data1 传递给异步时钟域 B,当 data1 为连续变化信号时,如果直接传递给时钟域 B 则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中。
2.FIFO的时序
FIFO 的数据写入和读出都是按时钟的上升沿操作的,当 wr_en 信号为高时写入 FIFO 数据,当 almost_full 信号有效时,表示 FIFO 只能再写入一个数据,一旦写入一个数据了,full 信号就会拉高,如果在 full 的情况下 wr_en 仍然有效,也就是继续向 FIFO 写数据,则 FIFO 的overflow 就会有效,表示溢出。
当rd_en信号为高时读FIFO数据,数据在下个周期有效。valid为数据有效信号,almost_empty表示还有一个数据读,当再读一个数据,empty 信号有效,如果继续读,则 underflow 有效,表示下溢,此时读出的数据无效。
而从 FWFT 模式读数据时序图可以看出,rd_en 信号有效时,有效数据 D0 已经在数据线上准备好有效了,不会再延后一个周期。这就是与标准 FIFO 的不同之处。
3.实验: 异步 FIFO
1)实验任务
当 FIFO 为空时就开始向 FIFO 中写入数据,直到 FIFO 被写满为止;
当 FIFO 为满时则开始从 FIFO 中读出数据,直到 FIFO 被读空为止。
2)创建工程并添加 fifo ip
|
|
图2中 “Fifo Implementation”选项用于选择我们想要实现的是同步 FIFO 还是异步 FIFO 以及使用哪种资源实现 FIFO,这里我们选择“Independent Clocks Block RAM”,即使用块 RAM 来实现的异步 FIFO。
|
|
图3中 Read Mode 有两种方式,一个 Standard FIFO,也就是平时常见的 FIFO,数据滞后于读信号一个周期,还有一种方式为 First Word Fall Through,数据预取模式,简称 FWFT 模式。也就是 FIFO 会预先取出一个数据,当读信号有效时,相应的数据也有效。
图4是“Status Flags”选项卡,用于设置其他的状态信号,保持默认即可。
|
|
图5 “Data Counts”选项卡用于设置FIFO 内数据计数的输出信号 ,此信号表示当前在 FIFO 内存在多少有效数据。为了更加方便地观察读/写过程,这里我们把读/写端口的数据计数都打开。
3)编写 FIFO 写模块
fifo_wr 模块的核心部分是一个不断进行状态循环的小状态机,如果检测到 FIFO 为空,则先延时 10 拍,这里注意,由于FIFO的边带信号的更新比实际的数据读/写操作有所延时,所以延时10拍的目的是等待FIFO的空/满状态信号、数据计数信号等边带信号的更新完毕之后再进行 FIFO 写操作,如果写满,则回到状态 0,即等待 FIFO 被读空,以进行下一轮的写操作。
module fifo_wr(
input clk,
input rst_n,
input fifo_empty,
input fifo_full,
output reg fifo_wr_en,
output reg [7:0] fifo_wr_data
);
reg [1:0] state; // 状态转移
reg [3:0] dly_cnt; // 延时计数器
reg fifo_empty_d0; // empty信号延迟1个clk
reg fifo_empty_syn; // empty信号延迟2个clk
//因为 fifo_empty 信号是属于 FIFO 读时钟域的
//所以要将其同步到写时钟域中
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
fifo_empty_d0 <= 1'b0;
fifo_empty_syn <= 1'b0;
end
else begin
fifo_empty_d0 <= fifo_empty;
fifo_empty_syn <= fifo_empty_d0;
end
end
// 数据写入FIFO
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <=1'b0;
dly_cnt <=1'b0;
fifo_wr_en <=1'b0;
fifo_wr_data <=1'b0;
end
else begin
case(state)
2'd0: begin
if(fifo_empty_syn) //判断是否检测到空信号
state <= 2'd1; //有则进入延时状态
else
state <= state;
end
2'd1: begin
if(dly_cnt == 4'd9) begin //延时 10 拍
//原因是 FIFO IP 核内部状态信号的更新存在延时
//延迟 10 拍以等待状态信号更新完毕
dly_cnt <= 4'd0;
state <= 2'd2; //开始写操作
fifo_wr_en <= 1'b1; //开始写使能
end
else
dly_cnt <= dly_cnt + 1'b1;
end
2'd2: begin
if(fifo_full) begin
fifo_wr_en <= 1'b0; //关闭写操作
fifo_wr_data <= 1'b0;
state <= 2'd0;
end
else
fifo_wr_data <= fifo_wr_data + 1'b1;
end
default:
state <= 2'd0;
endcase
end
end
endmodule
4)编写 FIFO 读模块
读模块的代码结构与写模块几乎一样,也是使用一个不断进行状态循环的小的状态机来控制操作过程.
module fifo_rd(
input clk,
input rst_n,
input fifo_empty,
input fifo_full,
input [7:0] fifo_rd_data,
output reg fifo_rd_en
);
reg [1:0] state; // 状态转移
reg [3:0] dly_cnt; // 延时计数器
reg fifo_full_d0; // full信号延迟1个clk
reg fifo_full_syn; // full信号延迟2个clk
//因为 fifo_full 信号是属于 FIFO 写时钟域的
//所以要将其同步到读时钟域中
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
fifo_full_d0 <= 1'b0;
fifo_full_syn <= 1'b0;
end
else begin
fifo_full_d0 <= fifo_full;
fifo_full_syn <= fifo_full_d0;
end
end
// 读出FIFO数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
state <=1'b0;
dly_cnt <=1'b0;
fifo_rd_en <=1'b0;
end
else begin
case(state)
2'd0: begin
if(fifo_full_syn) //判断是否检测到满信号
state <= 2'd1; //有则进入延时状态
else
state <= state;
end
2'd1: begin
if(dly_cnt == 4'd9) begin //延时 10 拍
//原因是 FIFO IP 核内部状态信号的更新存在延时
//延迟 10 拍以等待状态信号更新完毕
dly_cnt <= 4'd0;
state <= 2'd2; //开始读操作
fifo_rd_en <= 1'b1; //开始读使能
end
else
dly_cnt <= dly_cnt + 1'b1;
end
2'd2: begin
if(fifo_empty) begin
fifo_rd_en <= 1'b0; //关闭读使能
state <= 2'd0;
end
end
default:
state <= 2'd0;
endcase
end
end
endmodule
5)编写顶层文件
顶层模块主要是对 FIFO IP 核、写 FIFO 模块、读 FIFO 模块进行例化。
module top_ip_fifo(
input clk,
input rst_n
);
wire fifo_empty;
wire fifo_full;
wire fifo_wr_en;
wire [7:0] fifo_wr_data;
wire [7:0] fifo_rd_data;
wire fifo_rd_en;
wire [7:0] rd_data_count;
wire [7:0] wr_data_count;
fifo_wr u_fifo_wr(
.clk (clk),
.rst_n (rst_n),
.fifo_empty (fifo_empty ),
.fifo_full (fifo_full ),
.fifo_wr_en (fifo_wr_en ),
.fifo_wr_data (fifo_wr_data)
);
// 例化读模块
fifo_rd u_fifo_rd(
.clk (clk),
.rst_n (rst_n),
.fifo_empty (fifo_empty),
.fifo_full (fifo_full),
.fifo_rd_data (fifo_rd_data),
.fifo_rd_en (fifo_rd_en)
);
/*
wire fifo_empty;
wire fifo_full;
wire fifo_wr_en;
wire [7:0] fifo_wr_data;
wire [7:0] fifo_rd_data;
wire fifo_rd_en;
*/
// 例化 fifo ip核
ip_fifo your_instance_name (
.wr_clk(clk), // input wire wr_clk
.rd_clk(clk), // input wire rd_clk
.din(fifo_wr_data), // input wire [7 : 0] din
.wr_en(fifo_wr_en), // input wire wr_en
.rd_en(fifo_rd_en), // input wire rd_en
.dout(fifo_rd_data), // output wire [7 : 0] dout
.full(fifo_full), // output wire full
.empty(fifo_empty), // output wire empty
.rd_data_count(rd_data_count), // output wire [7 : 0] rd_data_count
.wr_data_count(wr_data_count) // output wire [7 : 0] wr_data_count
);
endmodule
6)编写激励文件
`timescale 1ns / 1ps
module tb_ip_fifo();
reg clk;
reg rst_n;
initial begin
clk = 1'b0;
rst_n = 1'b0;
#200
rst_n = 1'b1;
end
always #10 clk = ~clk;
top_ip_fifo u_top_ip_fifo(
.clk (clk),
.rst_n (rst_n)
);
endmodule
7)仿真测试
由波形图可知,当写满 255 个数据后,fifo_full 满信号就会拉高。经过延时之后,fifo_rd_en 写使能信号拉高,经过一拍之后就开始将 fifo 中的数据送到 fifo_dout 端口上。
由波形图可知,当读完 255 个数据后,fifo_empty 空信号就会拉高。经过延时之后,fifo_wr_en 写使能信号拉高,经过一拍之后就开始向 fifo 中继续写入数据。
8)ILA测试
结果如下