FPGA之fifo设计

                                                           FPGA之手撕fifo(含设计代码和仿真)

本文回答以下几个问题:

1:fifo的读空和写满信号如何给出

2:fifo的写控制模块设计

3:fifo的读控制模块设计

4:双口RAM使用

5:顶层文件

6:仿真文件编写

7:modelsim的RTL仿真

 

1:上一篇文章(FPGA之FIFO IP核详细教程)已经简单说了一下读写指针变换准则:概括一句话就是读地址和写地址不能相同,读地址不能追上写地址,写地址不能追上读地址(多一个周期),你可以把他们想成在一个圆圈上转动。那读写地址相同的情况如何区分呢?到底是读地址追上写地址(读空),还是写地址经过一个存储深度周期后追上读地址(写满)呢?

    这一段回答上一段的两个问题。对于读写地址来说地址的每一位对应其在地址数的权值,如4位地址MSB(高位)到LSB(低位)都有自己的数值分别是8,4,2,1,换句话说就是我有专门的用处了,别让我干其他事情了,心有余而力不足。所以无法用真实对应于RAM上地址的其中一位或者多位来区分读空和写满信号。这时只有在高位增加一位来区分。如8位地址在首位增加一位,看起来是16个数,其实真实地址(下面说的读地址写地址都是4位,真实地址是3位)还是只有8。先说读空这种情况最好理解,就是读地址追上了些地址,也就是读写地址的每一位都一样。图1右侧是写满时候的指针情况,可以看到读地址是0001,写地址是1001,首位不同而后三位相同。

      读写地址要对比,要涉及到跨时钟域了,但是地址仅仅是8421BCD编码在跨时钟域时候有缺陷:相邻地址间存在多bit变化情况如0111——>1000就发生4bit变化,多比特跨时钟域传输很容易产生亚稳态。这时你可能会想把信号在另一个时钟域打两拍就可以大大降低亚稳态发生概率了。没错,确实是可以将信号打两排,但是地址位数较多时候会消耗很多芯片内部的资源。

                                                                    图1 4位8421BCD码

 

       而格雷码相邻地址只有1bit变化,所以讲地址在跨时钟域前前转换成格雷码,然后再打两拍即可。格雷码的产生,8421编码的二进制右移一位再异或初始的二进制数如0010转换成格雷码的过程是0010右移一位变成0001,0001异或0010就是0011。格雷码编码中读空和上面一样只要读写地址一样就是读空。而写满稍有变化,还以地址2为例,这时候看到读地址指向0001,写地址指向1101,对比时候将读或者写的地址高两位取反如果相同则说明已经写满。

 

 

                                                                       图2 4位二进制格雷码编码

在说第二个问题前,先看一下整个fifo的“框架”,(示意的可能不规范)但心里至少有一个框架。

 

3

                                                                           图3 fifo框架

2:写控制模块

module	wr_ctrl(

	input		wire		wrclk,
	input		wire		rst_n,	
	input		wire	[8:0]	gray_rd_addr,
	input		wire		wr_req,//写请求
	output		reg		wr_full,
	output		wire	[8:0]	gray_wr_addr,
	output		wire	[8:0]	wr_addr
	
);
	wire				wr_en;
	reg		[8:0]		gray_rd_addr1;
	reg		[8:0]		gray_rd_addr2;
	reg		[8:0]		wraddr;
	
			
assign	wr_addr = wraddr;
assign	wr_en = (~wr_full) & wr_req;//写使能信号产生
assign	gray_wr_addr = (wr_addr >> 1) ^ wr_addr;

always @(posedge wrclk or negedge rst_n)
		if(rst_n == 1'b0)
		 wraddr <= 9'd0;
		 else if((~wr_full) & wr_en)
		 wraddr <= wraddr +1'b1;
		// else  wraddr <= 9'd0;
		
//把来自读时钟的格雷码地址打两排
always @(posedge wrclk or negedge rst_n)
		if(rst_n == 1'b0)
		 {gray_rd_addr2[8:0],gray_rd_addr1[8:0]} = 18'b0;
		else
		 {gray_rd_addr2[8:0],gray_rd_addr1[8:0]} = {gray_rd_addr1[8:0],gray_rd_addr} ;

//写满信号产生
always @(posedge wrclk or negedge rst_n)
		if(rst_n == 1'b0)
		 wr_full <= 1'b0;
		else if({~gray_rd_addr2[8:7],gray_rd_addr2[6:0]} == (gray_wr_addr[8:0]))
		 wr_full <= 1'b1;
		else
		 wr_full <= 1'b0;
		

endmodule

3:读控制模块

module rd_ctrl(
		input		wire		rdclk,
		input		wire		rst_n,
		input		wire	[8:0]	wr_gray_addr,
		input		wire	        rd_en,
		output		reg		rd_empty,
		output		wire	[8:0]	rd_gray_addr,
		output		wire	[8:0]	rd_addr

);
		reg		[8:0]		w_gaddr1;
		reg		[8:0]		w_gaddr2;
		reg		[8:0]		rdaddr;
assign	rd_gray_addr = (rd_addr >> 1) ^ rd_addr;//读地址格雷码
assign	rd_addr	= rdaddr;


always @(posedge rdclk or negedge rst_n)
		if(rst_n == 1'b0)
		  rdaddr <= 9'd0;
		  else if((~rd_empty) & rd_en)
		  rdaddr <= rdaddr + 1'b1;
		   //else rdaddr <= 9'd0;

//输入的写地址打两排
always @(posedge rdclk or negedge rst_n)
		if(rst_n == 1'b0)
		 {w_gaddr2,w_gaddr1} <= 18'b0;
		else
		 {w_gaddr2,w_gaddr1} <= {w_gaddr1,wr_gray_addr};
		 
//读空信号产生
always @(posedge rdclk or negedge rst_n)
		if(rst_n == 1'b0)
		 rd_empty <= 1'b0;
		else if(rd_gray_addr == w_gaddr2)
		 rd_empty <= 1'b1;
		else rd_empty <= 1'b0; 
		
endmodule

 

4:双口RAM(这里用到RAM IP核)

module	dp_ram(
		input		wire		rdclk,
		input		wire		wrclk,
		input		wire		rst_n,
		input		wire	[7:0]	w_data,
		output		wire		rden,
		output		wire		wren,
		input		wire	[8:0]	wr_addr,
		input		wire	[8:0]	rd_addr,
		output		wire		wr_full,
		output		wire		rd_empty,
		output		wire	[7:0]	rd_data		

);

		wire			ram_wren;
		wire			ram_rden;
assign	ram_wren = (~wr_full) & wren;
assign	ram_rden = (~rd_empty) & rden;
		

dpram_8x256	dpram_8x256_inst (
	.data ( w_data ),
	.rdaddress ( rd_addr[7:0] ),
	.rdclock ( rdclk ),
	.rden ( ram_rden ),
	.wraddress ( wr_addr[7:0] ),
	.wrclock ( wrclk ),
	.wren ( ram_wren ),
	.q ( rd_data )
	);

endmodule

5:顶层模块

module top_fifo(
		input		wire			wrclk,
		input		wire			rdclk,
		input		wire			rst_n,
		input		wire			wrfull,
		input		wire			rden,
		input		wire			wren,
		input		wire	[7:0]	        data_in,
		output		wire	[7:0]	        data_o
);
	//wire			wrfull;
	wire			rdempty;
	wire	[8:0]	rd_addr;
	wire	[8:0]	wr_addr;
	wire	[8:0]	wr_gaddr;
	wire	[8:0]	rd_gaddr;

dp_ram dp_ram_inst(                                 
			.rdclk		(rdclk),		      
			.wrclk		(wrclk),      
			.rst_n		(rst_n),      
			.w_data		(data_in),     
			.rden		(rden),       
			.wren		(wren),       
			.wr_addr	(wr_addr),    
			.rd_addr	(rd_addr),    
			.wr_full	(wrfull),    
			.rd_empty	(rdempty),   
			.rd_data	(data_o)
	
);

wr_ctrl wr_ctrl_inst(                              
                                      
			.wrclk			(wrclk),        
			.rst_n			(rst_n),	      
			.gray_rd_addr	(rd_gaddr), 
			.wr_req			(wren),       
			.wr_full		(wrfull),      
			.gray_wr_addr 	(wr_gaddr),
			.wr_addr		(wr_addr)
                                      
 );   
                                   
rd_ctrl rd_ctrl_inst(                                                   
			.rdclk			(rdclk),       
			.rst_n			(rst_n),       
			.wr_gray_addr	(wr_gaddr),	
			.rd_en			(rden),       
			.rd_empty		(rdempty),    
			.rd_gray_addr	(rd_gaddr),
			.rd_addr		(rd_addr)
                                                 
);                                                                      


endmodule
                                        
                                        

6:仿真文件

`timescale	1ns/1ns
module	tb_fifo;
reg				r_clk;
reg				w_clk;
reg				rst_n;
reg				wren;
reg		[7:0]	        w_data;
reg				rden;
wire			        w_full;
wire	        [7:0]	        r_data;
 


initial	begin
	r_clk <= 0;
	w_clk <= 0;
	rst_n <= 0;
	
	#100
	rst_n <= 1;	
end

initial begin
	w_data <= 0;
	wren <=0;
	
	#200
	wr_data_fun();
end

initial	begin
	rden <= 0;
	//@(posedge	w_full)
	#500
	rd_data_fun();
end

always #7 r_clk =~r_clk;
always #10 w_clk =~w_clk;

 top_fifo top_fifo_inst(
			.wrclk		(w_clk), 	
			.rdclk		(r_clk), 	
			.rst_n		(rst_n), 	
			.wrfull		(w_full),	
			.rden		(rden),  	
			.wren		(wren),  	
			.data_in	(w_data),
			.data_o 	(r_data)
);

task wr_data_fun();
	integer i;
	begin
		for (i=0;i<256;i=i+1)
		begin
			@(posedge w_clk);
			wren=1'b1;
			w_data=i;
		end
		@(posedge w_clk);
		wren = 1'b0;
		w_data =0;
	end
endtask

task rd_data_fun();
	integer i;
	begin
		for (i=0;i<256;i=i+1)
		begin
			@(posedge r_clk);
			rden=1'b1;
		end
		@(posedge r_clk);
		rden = 1'b0;
	end
endtask
endmodule

7:仿真

图4,同步fifo起始信号变化,读空信号在地址

                                                                                    图4

图5异步fifo信号变化

 

                                                                                   图5

图6边读边写,读时钟频率大于写时钟的

                                                                                               图6

写完一个周期

既然看到这里就动手试一试吧。如果发现瑕疵和错误之处还请指出,作者虚心学习,感激不尽。需要整个设计评论里留下邮箱。

猜你喜欢

转载自blog.csdn.net/qq_41754003/article/details/107066820