FIFO的基本理解和测试
1 FIFO的基本原理
1.0 IP核简介
FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。
它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。
1.1 结构示意图
上图的中间部分就是fifo IP核。
写数据时:写模块给fifo一个写使能,如果fifo的数据没有写满,写模块就往fifo里面写数据。所以wr_en和din对于fifo而言,是输入。
读数据时:读模块给fifo一个读使能,如果fifo的数据没有读空,读模块就继续从fifo读数据。所以rd_en对于fifo而言,是输入。dout是输出。
常见参数:
FIFO 的宽度:FIFO 一次读写操作的数据位 N。
FIFO 的深度:FIFO 可以存储多少个宽度为 N 位的数据。
将空标志:almost_empty。FIFO 即将被读空。
空标志:empty。FIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO
中读出数据而造成无效数据的读出。
将满标志:almost_full。FIFO 即将被写满。
满标志:full。FIFO 已满或将要写满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继
续向 FIFO 中写数据而造成溢出。
读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
写时钟:写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
1.2 跨时钟域和位宽不同步
跨时钟域:存储器A的时钟(100MHz)和存储器B的时钟(75MHz)不同,此时先将存储器A的数据写到FIFO中,然后存储器B从FIFO中读取数据。
带宽不同步:存储器A的数据宽度为[7:0],一共8位。存储器B的数据宽度为[11:0],一共12位。此时存储器A的数据不能直接写到存储器B。此时先将存储器A的数据写到FIFO中,然后存储器B从FIFO中读取数据。如图2.2所示。
对于这两种情况,我们就用到FIFO了。
1.3 先入先出
写数据时,第一个数据D0占据地址1,第二个数据D1占据地址2,… ,第N个数据D11占据地址N(N=12)。写数据需要的输入信号有:①写时钟:wr_clk;②写使能:wr_req;③写数据:wr_data,D0_D11。
读数据时,首先读地址1的数据D0,此时D1进入地址1,其余的数据的地址也减1。第二次读取数据时,首先读地址1的数据D1,其余的数据的地址减1。读数据需要的信号有:①读时钟:rd_clk;②读使能:rd_req。输出为读出的数据: data_out。器示意图如下图所示。
1.4 同步FIFO和异步FIFO
如果读时钟和写时钟的频率相同,则为同步FIFO。如果读时钟和写时钟的频率不同,则为异步FIFO。
1.5 具体实现
子模块功能:
写模块 :产生原始数据
读模块:把数据从fifo读出来
FIFO :数据缓存区
写FIFO模块:它会对FIFO IP核进行写入数据,FIFO IP核反馈一个将空信号和将满信号给写FIFO模块。
读FIFO模块:发送读请求给FIFO IP核,FIFO IP核会将数据读出来。
2 新建工程
3 添加fifo IP核和ila IP核
3.1 添加fifo IP核,并设置参数。
参数设置如下:
(1)“Basic”选项
“Interface Type”选项用于选择 FIFO 接口的类型,这里我们选择默认的“Native”,即传统意义上的 FIFO接口。
“fifo Implementation”选项用于选择我们想要实现的是同步 FIFO 还是异步 FIFO 以及使用哪种资源实现 FIFO.
(2)Native Ports选项
“Read Mode”选项用于设置读 FIFO时的读模式,这里我们选择默认的Standard FIFO。
“Data Port Parameters”一栏用于设置读写端口的数据总线的宽度以及 FIFO 的深度,写宽度“Write Width”我们设置为16位,写深度“Write Depth”我们设置为 1024.
(3)“Status Flags”选项卡,用于设置用户自定义接口或者用于设定专用的输入口。这里我们使用“即将写满”和“即将读空”这两个信号,所以我们把它们勾选上,其他保持默认即可,如下图所示。
4 程序
4.1 top 文件
`timescale 1ns / 1ps
// 函数功能:当 FIFO 空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;
// 当 FIFO 满时,从 FIFO 中读出数据,直到 FIFO 被读空为止。
// FIFO数据位宽16bit,深度:1024
module my_FIFO(
// ------写数据的相关接口
input adc_clk , //fifo写时钟 65M
input adc_rst_n , //fifo写复位
input [15:0] adc_data , //fifo写数据
//-------读数据的相关接口
input M_AXIS_CLK ,//fifo读时钟 100M
input M_AXIS_RSTN ,//fifo读复位
//AXI接口
input M_AXIS_tready ,//外部准备好接收FIFO读出的数据
output reg [15:0] M_AXIS_tdata ,//fifo读出的数据
output [1:0] M_AXIS_tkeep ,
output reg M_AXIS_tlast ,//fifo读数据的最后一个数据
output reg M_AXIS_tvalid //fifo输出给外部的读数据有效信号
);
//---------------1、例化写模块
//模块功能:控制写使能信号fifo_wr_en和FIFO的写数据fifo_wr_data
wire full ; // FIFO满信号
wire empty ; // FIFO空信号
//写模块
wire fifo_wr_en ;// 写使能
wire [15:0] fifo_wr_data ;// 写数据
fifo_wr fifo_wr_inst(
// input
.adc_clk (adc_clk ),//写时钟
.adc_rst_n (adc_rst_n ),//写使能
.empty (empty ),//fifo将空信号
.full (full ),//fifo将满信号
.adc_data (adc_data ),//外部待写入FIFO的数据
//output
.fifo_wr_en (fifo_wr_en ),// 写使能
.fifo_wr_data (fifo_wr_data)// FIFO的写数据
);
// ----------------2、例化读模块
// 模块功能:控制读使能信号fifo_rd_en
wire fifo_rd_en ;// 读使能
fifo_rd fifo_rd_inst(
//input
.M_AXIS_CLK (M_AXIS_CLK ), // 读时钟
.M_AXIS_RSTN (M_AXIS_RSTN ), // 读复位
.empty (empty ),
.full (full ),
.M_AXIS_tready (M_AXIS_tready ), //外部准备好接收FIFO的读数据
//output
.fifo_rd_en (fifo_rd_en ) // 读使能
);
//--------------3、例化FIFO IP核
wire [15 : 0] dout;
fifo_generator_0 Inst_fifo_generator_0(
.rst (!adc_rst_n ), // input wire rst
.wr_clk (adc_clk ), // input wire wr_clk
.rd_clk (M_AXIS_CLK ), // input wire rd_clk
.din (fifo_wr_data ), // input wire [15 : 0] din
.wr_en (fifo_wr_en ), // input wire wr_en
.rd_en (fifo_rd_en ), // input wire rd_en
.dout (dout ), // output wire [15 : 0] dout
.full (full ), // output wire full
.empty (empty ) // output wire empty
);
//------------4、FIFO的读出数据有效信号ReadData_vaild控制
//为什么要这样写,是根据仿真波形得到的
reg ReadData_vaild ;
always@(posedge M_AXIS_CLK)
begin
ReadData_vaild<=fifo_rd_en;//相当于ReadData_vaild等于fifo_rd_en延后一个clk的信号
end
//------------5、输出数据的有效信号和输出数据控制
always@(posedge M_AXIS_CLK)
begin
if(!M_AXIS_RSTN)//复位
begin
M_AXIS_tvalid<=1'b0;
end
else if(ReadData_vaild && M_AXIS_tready)//FIFO读出的数据有效,且外部准备好接收数据
begin
M_AXIS_tvalid<=1'b1;//FIFO给外部的数据是有效的
M_AXIS_tdata<=dout;// FIFO给外部的有效数据
end
else
begin
M_AXIS_tvalid<= 1'b0;
M_AXIS_tdata<=16'dx;
end
end
assign M_AXIS_tkeep = 2'b11 ;
//-------- 6、读有效数据计数器,记到1024,就将M_AXIS_tlast拉高
reg [15:0] dma_cnt ; //dma counter
always@(posedge M_AXIS_CLK)
begin
if(M_AXIS_RSTN == 1'b0)
begin
dma_cnt <= 16'd0;
M_AXIS_tlast<= 1'b0;
end
else if (M_AXIS_tvalid & ~M_AXIS_tlast)//主端数据有效,且last信号为低
begin
dma_cnt <= dma_cnt + 1'b1 ;
end
else
begin
dma_cnt <= dma_cnt ;
end
end
// ----7、M_AXIS_tlast信号控制
// 读有效数据计数器记到1024,就将M_AXIS_tlast拉高,
always@(posedge M_AXIS_CLK)
begin
if(M_AXIS_tvalid && (dma_cnt == 1024-1'd1))//sample_len=1024
begin
M_AXIS_tlast <= 1'b1 ;
end
else
begin
M_AXIS_tlast <= 1'b0 ;
end
end
//-----------8、找1024个点的最大值
wire [15:0] data_max ;
wire DataMax_Vaild ;
Max_Get Inst_Max_Get(
//input
.clk (M_AXIS_CLK ),
.reset (!adc_rst_n ),
.En (ReadData_vaild ),
.data_in (M_AXIS_tdata ),
//output
.data_max (data_max ) ,
.DataMax_Vaild (DataMax_Vaild)
);
endmodule
4.2 FIFO写模块(fifo_wr)
`timescale 1ns / 1ps
module fifo_wr(
input adc_clk , // 时钟信号
input adc_rst_n , // 复位信号
input empty , //读空信号
input full , //写满信号
input [15:0] adc_data ,// 外部采集的数据
output reg fifo_wr_en ,// 写使能
output reg [15:0] fifo_wr_data // 写数据
);
//**--------------1、写使能控制----------------**//
reg [3:0] ST_WirteData; // 状态机
reg [7:0] delay_cnt;
always@(posedge adc_clk or negedge adc_rst_n)
begin
if(adc_rst_n==1'b0) // 复位
begin
ST_WirteData <= 4'd0;//
fifo_wr_en <= 1'b0;
delay_cnt <= 8'd0;
end
else
begin
case(ST_WirteData)
0://适当延时---第一种延时方法
begin
if(delay_cnt==8'd5)
begin
ST_WirteData <= 4'd1;
end
else
begin
delay_cnt <= delay_cnt+1'b1;//等待fifo空
ST_WirteData <= 4'd0;
end
end
1:
begin
if(empty) // fifo为空,开始写数据
begin
ST_WirteData <= 4'd2;
end
else
begin
ST_WirteData <= 4'd1;//等待fifo空
end
end
2: //适当延时---第2种延时方法 可以不要
ST_WirteData<= 4'd3;//跳到状态3
3:
begin
if(full) //FIFO写满
begin
fifo_wr_en <= 1'b0;//写使能无效
fifo_wr_data <= 16'dx;
ST_WirteData <= 4'd0;//跳到状态0
end
else//FIFO没有写满
begin
fifo_wr_en <= 1'b1; //写使能有效
fifo_wr_data <= adc_data; //将外部数据送给FIFO
ST_WirteData<= 4'd3;//跳到状态2
end
end
default:
ST_WirteData<= 4'd0;//跳到状态0
endcase
end
end
endmodule
4.3 FIFO读模块(fifo_rd)
`timescale 1ns / 1ps
//-----------模块功能:FIFO读使能控制
module fifo_rd(
input M_AXIS_CLK , // 时钟信号
input M_AXIS_RSTN , // 复位信号
input empty , //快要读空信号
input full , //快要写满信号
input M_AXIS_tready , //外部准备好接收FIFO的读数据
output reg fifo_rd_en // 读使能 fifo读模块给fifo一个读数据的请求,对于读模块而言,是输出
);
reg [3:0] ST_ReadData; //状态机
always@(posedge M_AXIS_CLK)
begin
if(!M_AXIS_RSTN) // 复位
begin
fifo_rd_en <= 1'b0;
ST_ReadData<= 4'd1;//进入状态1
end
else
begin
case(ST_ReadData)
0:
begin
if(M_AXIS_tready)//外部准备好接收FIFO的读数据
begin
ST_ReadData <= 4'd1;//跳到下一个状态
end
else
begin
ST_ReadData <= 4'd0;//等待外部准备好接收FIFO的读数据
end
end
1:
begin
if(full)//FIFO数据已经写满
begin
ST_ReadData <= 4'd2;
end
else
begin
ST_ReadData <= 4'd1;//等待FIFO写满
end
end
2:
begin
if(empty)//FIFO数据读空
begin
fifo_rd_en <= 1'b0;//读使能无效
ST_ReadData<= 4'd0;
end
else
begin
fifo_rd_en <= 1'b1; //使能有效,继续读数据
ST_ReadData<= 4'd2;
end
end
default:
ST_ReadData<= 4'd0;
endcase
end
end
endmodule
4.3 最大值寻找模块(Max_Get)
`timescale 1ns / 1ps
// 找1024个点的最大值
module Max_Get(
input clk ,
input reset ,
input En ,//模块开始工作的使能信号
input [15:0] data_in ,// 输入数据
output reg [15:0] data_max ,//搜索得到的最大值
output reg DataMax_Vaild
);
reg [15:0] data_max_temp;
reg [3:0] ST_max_search;
reg [15:0] count;
always@(posedge clk)
begin
if(reset)
begin
data_max_temp<=16'd0;
ST_max_search<=4'd0;
count<=16'd0;
DataMax_Vaild<=1'b0;
end
else
begin
case(ST_max_search)
0:
begin
if(En==1'b1)
begin
ST_max_search<=4'd1;
end
else
begin
ST_max_search<=4'd0;
end
end
1:
begin
if(count<16'd1024)
begin
count<=count+1'd1;
ST_max_search<=4'd1;
if(data_in>data_max_temp)
data_max_temp<=data_in;
else
data_max_temp<=data_max_temp;
end
else//if(count==16'd1024)
begin
count<=16'd0;
ST_max_search<=4'd0;
data_max<= data_max_temp;
data_max_temp<=16'd0;//数据归零,避免影响下一次寻求最大值
DataMax_Vaild<=1'b1;
end
end
default:
ST_max_search<= 4'd0;//跳到状态0 end
endcase
end
end
endmodule
4.4 testbench文件
`timescale 1ns / 1ps
module sim_FIFO;
// ------写数据的相关接口
wire adc_clk ;
reg adc_rst_n ;
reg [15:0] adc_data ;
// AXIS 接口
wire M_AXIS_tready ;
wire M_AXIS_CLK ;//时钟
wire M_AXIS_RSTN ;//复位
assign M_AXIS_tready=1'b1;
assign M_AXIS_RSTN=adc_rst_n;
//output
wire [15:0] M_AXIS_tdata ;
wire [1:0] M_AXIS_tkeep ;
wire M_AXIS_tlast ;
wire M_AXIS_tvalid ;
my_FIFO Inst_my_FIFO(//新增代码
// ------写数据的相关接口
//input
.adc_clk (adc_clk ) ,//fifo写时钟
.adc_rst_n (adc_rst_n ) ,//fifo写复位
.adc_data (adc_data ) ,//fifo写数据
//-------读数据的相关接口
//input
.M_AXIS_CLK (M_AXIS_CLK ) , //fifo读时钟
.M_AXIS_RSTN (M_AXIS_RSTN ) , //fifo读复位
.M_AXIS_tready (M_AXIS_tready) ,
//output
.M_AXIS_tdata (M_AXIS_tdata ) , //fifo读数据
.M_AXIS_tkeep (M_AXIS_tkeep ) ,
.M_AXIS_tlast (M_AXIS_tlast ) , //fifo读数据的最后一个数据
.M_AXIS_tvalid (M_AXIS_tvalid) //fifo输出给外部的读数据有效信号
);
reg clk;
initial
begin
clk <= 1'b0;
adc_rst_n <=1'b0;
#20;
adc_rst_n <=1'b1;
end
always #10 clk = ~ clk; //20ns一个周期,产生50MHz时钟源
//产生输入数据
always @(posedge adc_clk)
begin
if(!adc_rst_n)
begin
adc_data<=16'd0;
end
else
begin
adc_data<=adc_data+1'd1;
end
end
//产生读、写模块的时钟
clk_wiz_0 Inst_clk_wiz_0
(
// Clock out ports
.clk_out1(adc_clk), // output clk_out1 65M
.clk_out2(M_AXIS_CLK), // output clk_out2 100M
// Status and control signals
.reset(!adc_rst_n), // input reset
.locked(), // output locked
// Clock in ports
.clk_in1(clk)// 50M
); // input clk_in1
endmodule
5 仿真结果
FIFO写初值
FIFO写终值和FIFO读初值
FIFO读终值
这几个信号控制正确
output reg [DATA_WIDTH:0] M_AXIS_tdata ,
output [1:0] M_AXIS_tkeep ,
output reg M_AXIS_tlast ,
output reg M_AXIS_tvalid