当前位置:网站首页>从底层结构开始学习FPGA----FIFO IP的定制与测试

从底层结构开始学习FPGA----FIFO IP的定制与测试

2022-07-06 17:39:00 孤独的单刀

文章目录

系列目录与传送门

1、FIFO IP的定制

①、第一页

②、第二页 

③、第三页

④、第四页

⑤、第五页

2、FIFO IP的例化与测试

2.1、例化一个FIFO IP核

2.2、RTL

2.3、仿真测试与测试结果 

2.4、下板实测

3、总结与参考


系列目录与传送门

        《从底层结构开始学习FPGA》目录与传送门

        在定制一个FIFO IP核之前,强烈建议您先阅读:从底层结构开始学习FPGA----FIFO IP核及其关键参数介绍

        在这篇文章中,已经对FIFO IP核的各个关键因素做了详细的讲解。


1、FIFO IP的定制

  1. 在新建一个工程后, 点击IP Catalog
  2. 点击后会出现 IP Catalog 页面,在 IP 核的搜索框中搜索 fifo
  3. 根据筛选,双击选择fifo核“FIFO Generator


①、第一页


②、第二页 


③、第三页


④、第四页


⑤、第五页


2、FIFO IP的例化与测试

2.1、例化一个FIFO IP核

        按上述步骤生成IP后,复制 IP核自带的例化模板。


2.2、RTL

        我们编写一个RTL代码来验证一下这个FIFO IP,并学习一下它的时序逻辑。

        由于是异步FIFO,所以还例化了一个PLL来生成写时钟50M,读时钟75M。RTL代码使用了一个简单的状态机,以实现如下逻辑:

  1. 等待PLL稳定
  2. PLL稳定后对FIFO进行复位,时长10个周期以上
  3. FIFO复位完成后,写入数据直到写满,写入数据分别为0,1,2···14,共15个数据
  4. 写完数据后,从FIFO中读取数据,观察读取数据是否与写入数据一致
module fifot_test(
(* MARK_DEBUG="true" *)	input		sys_clk,
(* MARK_DEBUG="true" *)	input		sys_rst_n
);

(* MARK_DEBUG="true" *)reg				fifo_rst		;	//自己生成一个FIFO的复位信号
(* MARK_DEBUG="true" *)reg	[3:0] 		din				;
(* MARK_DEBUG="true" *)reg	[3:0] 		rst_cnt			;	//复位计数器
(* MARK_DEBUG="true" *)reg				wr_en			;
(* MARK_DEBUG="true" *)reg				rd_en			;
(* MARK_DEBUG="true" *)reg	[2:0]		state			;
reg	[2:0]		state_rd1		;
reg	[2:0]		state_rd2		;

(* MARK_DEBUG="true" *)wire			locked			;
(* MARK_DEBUG="true" *)wire			rst_n			;
(* MARK_DEBUG="true" *)wire			rd_clk			;
(* MARK_DEBUG="true" *)wire			wr_clk			;
(* MARK_DEBUG="true" *)wire	[3 : 0] dout		    ;
(* MARK_DEBUG="true" *)wire			full		    ;
(* MARK_DEBUG="true" *)wire			almost_full		;
(* MARK_DEBUG="true" *)wire			wr_ack			;
(* MARK_DEBUG="true" *)wire			overflow		;
(* MARK_DEBUG="true" *)wire			empty			;
(* MARK_DEBUG="true" *)wire			almost_empty	;
(* MARK_DEBUG="true" *)wire			valid			;
(* MARK_DEBUG="true" *)wire			underflow		;
(* MARK_DEBUG="true" *)wire	[3 : 0] rd_data_count	;
(* MARK_DEBUG="true" *)wire	[3 : 0] wr_data_count	;
(* MARK_DEBUG="true" *)wire			prog_full		;
(* MARK_DEBUG="true" *)wire			prog_empty		;
(* MARK_DEBUG="true" *)wire			wr_rst_busy		;
(* MARK_DEBUG="true" *)wire			rd_rst_busy     ;

assign	rst_n = sys_rst_n && locked;	//在locked拉高之前一直复位

//PLL输出波形后,开始计数直到1111
always @(posedge sys_clk or negedge rst_n)begin
	if(~rst_n)
		rst_cnt <= 1'b0;
	else if(&rst_cnt)			//rst_cnt == 1111
		rst_cnt <= rst_cnt;	
	else
		rst_cnt <= rst_cnt + 1;
end

always @(posedge sys_clk or negedge rst_n)begin
	if(~rst_n)
		fifo_rst <= 1'b1;
	else if(&rst_cnt)
		fifo_rst <= 1'b0;
	else
		fifo_rst <= 1'b1;
end

always @(posedge sys_clk or negedge rst_n)begin
	if(~rst_n)
		state <= 3'd0;
	else begin
		case(state)
			3'd0:begin	
				if(~fifo_rst)
					state <= 3'd1;	
				else
					state <= state;		
			end			
			3'd1:begin	
				if(~wr_rst_busy && ~full)
					state <= 3'd2;	
				else
					state <= state;		
			end							
			3'd2:begin	
				if(almost_full)
					state <= 3'd3;	
				else
					state <= state;		
			end			
			3'd3:begin	
				if(~rd_rst_busy && ~empty)
					state <= 3'd4;	
				else
					state <= state;		
			end	
			3'd4:begin	
				if(almost_empty)
					state <= 3'd5;	
				else
					state <= state;		
			end
			3'd5:begin	
					state <= state;		
			end	
			
			default:state <= 3'd0;
		endcase
	end
end

//写使能
always @(posedge wr_clk or negedge rst_n)begin
	if(~rst_n)
		wr_en <= 1'b0;
	else if(state == 3'd2)
		wr_en <= 1'b1;
	else
		wr_en <= 1'b0;			
end

//写数据
always @(posedge wr_clk or negedge rst_n)begin
	if(~rst_n)
		din <= 4'd0;
	else if(wr_en)
		din <= din + 1;
end

//把状态state同步到读时钟域
always @(posedge rd_clk or negedge rst_n)begin
	if(~rst_n)begin
		state_rd1 <= 3'd0;
		state_rd2 <= 3'd0;
	end
	else begin
		state_rd1 <= state;	
		state_rd2 <= state_rd1;
	end
end

//读使能
always @(posedge rd_clk or negedge rst_n)begin
	if(~rst_n)
		rd_en <= 1'b0;
	else if(state_rd2 == 3'd4)
		rd_en <= 1'b1;
	else
		rd_en <= 1'b0;			
end

clk_wiz_0	clk_wiz_0_inst
(
	.clk_in1	(sys_clk	),
	.clk_out1	(wr_clk		),  			//50M  
	.clk_out2	(rd_clk		),    			//75M
	.resetn		(sys_rst_n	),		
	.locked		(locked		)       
);     	
		
//例化FIFO
fifo_w4_d16	fifo_w4_d16_inst(
  .rst				(fifo_rst),  
  
  .wr_clk			(wr_clk),                               
  .din				(din),                      
  .wr_en			(wr_en), 
  .wr_rst_busy		(wr_rst_busy),  
  .wr_data_count	(wr_data_count),                 
  .prog_full		(prog_full),                     
  .full				(full),                    
  .almost_full		(almost_full),      
  .wr_ack			(wr_ack),                
  .overflow			(overflow),
  
  .rd_clk			(rd_clk),
  .rd_en			(rd_en),  
  .empty			(empty),                  
  .almost_empty		(almost_empty),    
  .valid			(valid),                            
  .rd_data_count	(rd_data_count),  
  .dout				(dout), 
  .underflow		(underflow),          
  .prog_empty		(prog_empty),              
  .rd_rst_busy		(rd_rst_busy)       
);	
		
endmodule

2.3、仿真测试与测试结果 

        由于测试逻辑都在RTL中实现了,所以testbench就比较简单了,只需要例化被测模块和实现时钟和复位即可。

`timescale 1 ns / 1 ns
module tb_fifo_test();

reg	sys_clk;
reg	sys_rst_n;

initial begin
	sys_clk = 0;
	sys_rst_n = 0;
	
	#60
	sys_rst_n = 1;
	#6000
	$finish;	//停止仿真
end

always #10 sys_clk = ~ sys_clk;

fifot_test	fifot_test_inst(
	.sys_clk	(sys_clk	),
	.sys_rst_n	(sys_rst_n	)
);

endmodule

        使用vivado自带的仿真工具simulator运行仿真,仿真结果如下: 

(1)整体

(2)PLL从不稳定到稳定

(3)FIFO复位完成后需要一定的时间才可以操作

(4)写FIFO操作

(5)读FIFO操作

        

        写入数据0-14共15个数据,读出数据也是0-14共15个数据。写、读一致,功能验证无误。


2.4、下板实测

        把代码下载到开发板,用ILA观察一下,观察结果和仿真结果是一致的,不赘述,只放几张图:

(1)PLL稳定及FIFO复位

(2)写FIFO操作,写入数据0-14

(3)读FIFO操作,读取数据0-14 

 


3、总结与参考

  • FIFO的使用要注意实际深度
  • FIFO使用需要先复位,复位时间要足够长,且复位后需要一定的时间才能使用FIFO
  • 同步FIFO的使用会比较简单,因为其没有异步FIFO所需要的同步时钟域导致的延迟问题
  • 不同资源实现的FIFO在各个功能特性上会有细微的区别,使用之前一定要认真测试,总结时序
  • 异步FIFO的计数器不是精确的,只能作为大致的参考,以实现例如半空半满、1/4空满等判断
  • 可编程空满的实现是依赖计数器的,其值同样不够精确

参考资料1:pg057-fifo-generator


  • 博客主页:wuzhikai.blog.csdn.net
  • 本文由 孤独的单刀 原创,首发于CSDN平台
  • 文章还在持续更新,您有任何问题,都可以在评论区和我交流!
  • 创作不易,您的支持是我持续更新的最大动力!如果本文对您有帮助,还请多多点赞、评论和收藏!

原网站

版权声明
本文为[孤独的单刀]所创,转载请带上原文链接,感谢
https://wuzhikai.blog.csdn.net/article/details/125633983