IP 核之 FIFO 实验

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

图1 PICTURE ONE
图2 PICTURE TWO

图2中 “Fifo Implementation”选项用于选择我们想要实现的是同步 FIFO 还是异步 FIFO 以及使用哪种资源实现 FIFO,这里我们选择“Independent Clocks Block RAM”,即使用块 RAM 来实现的异步 FIFO。

图3 PICTURE ONE
图4 PICTURE TWO

图3中 Read Mode 有两种方式,一个 Standard FIFO,也就是平时常见的 FIFO,数据滞后于读信号一个周期,还有一种方式为 First Word Fall Through,数据预取模式,简称 FWFT 模式。也就是 FIFO 会预先取出一个数据,当读信号有效时,相应的数据也有效。

图4是“Status Flags”选项卡,用于设置其他的状态信号,保持默认即可。

图5 PICTURE ONE
图6 PICTURE TWO

图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测试

结果如下
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jay丶ke

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值