宋桓公

【红色飓风Nano二代测评】FIFO易错点时序分析

0
阅读(7472)

   一直想写一篇关于FIFO的文章,这次为了给LCD(TFT)打造接口,必须添加FIFO。

从而对Xilinx的FIFO进行了时序测试,发现和Altera的FIFO用法和时序基本是一样的。

   在读FIFO时候,有一个错误是很容易忽略的。就是没有将FIFO“读空”。可能你

会觉得这怎么可能,我只要检查到FIFO空标志有效,不就证明FIFO被读空了吗?

没错,FIFO已然是空了,但是最后一个读出的数据你保存住了吗?

   我们先用Altera的做个实验:首先例化一个深度为16的FIFO。先一直写数据,

直到FIFO写满为止。然后就一直读FIFO直到读空为止。程序如下:


module FIFO_V1
(
	input CLK,
	input RSTn,
	
	output [7:0]FIFO_IN,
	output [7:0]FIFO_OUT
);

	reg Read_Sig = 1'b0;
	reg Write_Sig = 1'b0;
	reg [7:0]FIFO_Write_Data;
	wire Full_Sig;
	wire Empty_Sig;
	wire [7:0]FIFO_Read_Data;

	FIFO_Module		FIFO_Mem 
						(
							.clock(CLK),
							.data(FIFO_Write_Data),
							.rdreq(Read_Sig),
							.wrreq(Write_Sig),
							.empty(Empty_Sig),
							.full(Full_Sig),
							.q(FIFO_Read_Data)
						);
			
	reg [3:0]i;
	always @(posedge CLK or negedge RSTn)
		if(!RSTn)
		begin
			i <= 4'd0;
			Write_Sig <= 1'b0;
			FIFO_Write_Data <= 8'd0;
		end
		else 
			case(i)
				0:
				begin
					if(!Full_Sig)
					begin 
						Write_Sig <= 1'b1;
					FIFO_Write_Data <= FIFO_Write_Data + 1'b1;
						i <= i;
					end
					else 
					begin
						Write_Sig<= 1'b0;
						i <= i + 1'b1;
					end
				end
				1:
				begin
					i <= i;
				end
				
			endcase

		
	reg [7:0]Read_Data;
	reg [3:0]j = 4'd0;
	always @(posedge CLK or negedge RSTn)
		if(!RSTn)
		begin
			Read_Sig <= 1'b0;
			Read_Data <= 8'd0;
		end
		else 
			case(j)
			0:
			begin
				if(Full_Sig) j <= j + 1'b1;
				else j <= j;
			end
			1:
			begin
				if(!Empty_Sig)
				begin 
					Read_Sig <= 1'b1;
					Read_Data <= FIFO_Read_Data;
					j <= j;
				end
				else 
				begin
			Read_Data <= FIFO_Read_Data;// 加上这句才能,破除没有读空的假象
					Read_Sig <= 1'b0;
				end
			end
			endcase
		

		assign FIFO_IN = FIFO_Write_Data;
		assign FIFO_OUT = Read_Data;


endmodule


28~57行是将FIFO写满,60~89行是将FIFO读空,并且将读出的数据保存到

变量Read_Data。注意到85行:如果不加上这一句,你会发现最后一个数据没有

读出来,也就是FIFO没有“读空”。你可能会问,明明Empty已经至高了,为什么

在Read_Sig关闭之后,还要加这么一句,存一次数据呢?先看仿真时序图:

错误:没有加85行,没有读空的情况~~(残留假象)

image 

观察数序后半段,读FIFO时序,发现Read_Data只读到了15,

16这个数据并没有保存到。这是为什么呢?



正确:有加85行,读空了的情况~~

image

此时,16这个数据被保存到了。

您可能觉得,这个85行加的太诡异了,不好理解,不要紧,

我继续用Xilinx的FIFO举例子(前面说过Altera的FIFO用

法和时序基本是一的,所以用谁举例无关紧要。)

上面这种写法比较简洁,能方便的读写FIFO。但是不利于

理解以及波形的观察,现在继续做个实验,我先写3次FIFO

,接着再读3次FIFO,那么这次肯定能将FIFO读空了是吧。

程序入下:


module test;

	// Inputs
	reg CLK;
	reg [7:0] a;
	reg [7:0] b;
	reg ce;
	reg [31:0] din;
	reg wr_en;
	reg rd_en;

	// Outputs
	wire [7:0] s;
	wire full;
	wire empty;
	wire [31:0] dout;

	// Instantiate the Unit Under Test (UUT)
	FIFO_Test uut (
		.CLK(CLK), 
		.a(a), 
		.b(b), 
		.ce(ce), 
		.s(s), 
		.din(din), 
		.wr_en(wr_en), 
		.rd_en(rd_en), 
		.full(full), 
		.empty(empty), 
		.dout(dout)
	);

	initial begin
		// Initialize Inputs
		CLK = 0;
		a = 0;
		b = 0;
		ce = 0;
		din = 0;
		wr_en = 0;
		rd_en = 0;

		// Wait 100 ns for global reset to finish
		#100;
        
		// Add stimulus here
		forever #5 CLK = ~CLK;
	end
    
	reg [2:0]i = 3'd0;
	always @(posedge CLK)
		case(i)
				0://Write First
				begin
					if(!full)begin wr_en<= 1'b1; din <= 32'd1; i <= i + 1'b1; end
					else wr_en<= 1'b0;
				end
				1://Write again
				begin
					if(!full)begin wr_en<= 1'b1; din <= 32'd2; i <= i + 1'b1; end
					else wr_en<= 1'b0;
				end
				2://Write again
				begin
					if(!full)begin wr_en<= 1'b1; din <= 32'd3; i <= i + 1'b1; end
					else wr_en<= 1'b0;
				end
				3:
				begin
					wr_en<= 1'b0;
					i <= i + 1'b1;
				end
				
				4://Read
				begin
					if(!empty)begin rd_en <= 1'b1; i <= i + 1'b1;end
					else rd_en <= 1'b0; 
				end
				5://Read again
				begin
					if(!empty)begin rd_en <= 1'b1; i <= i + 1'b1;end
					else rd_en <= 1'b0; 
				end
				6://Read again
				begin
					if(!empty)begin rd_en <= 1'b1; i <= i + 1'b1;end
					else rd_en <= 1'b0; 
				end
				7:
				begin
					i <= i;
				end
		endcase
		
endmodule


波形如下:

image

我特意将空信号,和读使能信号,用不同的颜色标识,以便观察:

在写完,1,2,3这3个数据后,开始读FIFO,此时首先将读使

能置高,我们发现在读使能信号置高的一个时钟周期后,第一个

数据才“探出头”,同样的,空信号,在最后一个数据“探出头”

时就置高了。所以在需要保存FIFO读出的数据时是需要注意的,

其实,并不是FIFO没有读空,而是最后一个数据读出来的时候

你没有去保存它,而是认为FIFO已经空了。

总结:保存FIFO读出的数据的时候要注意“头和尾”

    头:在“读使能信号”一个时钟周期后的时钟上升沿,数据

发生变化,也就是FIFO的第一个数据输出。所以你想保存第一个

数据,必须是在“读使能信号”置高的两个周期之后。

    尾:空信号置高后,最后一个数据输出。所以你想保存最后

一个数据,必须在空信号置高后一个周期保存。

 

最后,介绍下在ISE下如何添加FIFO:

之前在添加时碰到一个错误:

ERROR:NgdBuild:604 - logical block
   'YourInstanceName/BU2/U0/grf.rf/mem/gbm.gbmg.gbmga.ngecc.bmg/blk_mem_generato
   r/valid.cstr/ramloop[0].ram.r/v5_noinit.ram/SDP.WIDE_PRIM18.TDP' with type
   'RAMB18SDP' could not be resolved. A pin name misspelling can cause this, a
   missing edif or ngc file, case mismatch between the block name and the edif
   or ngc file name, or the misspelling of a type name. Symbol 'RAMB18SDP' is
   not supported in target 'spartan6'.

网上查了很久,什么需要添加路径,什么软件的BUG,试了都不顶用。最后按照如下的操作步骤,就

没有报错了。

首先,在工程上单击右键,选择new source:

image

image

image

image

image

image

image

余下的下一步,最后Generate即可!

image

Generate,这个过程感觉等待的时间还是比较久(quartus一秒搞定的说)。

最后出现如下这个表示成功了:

image

image

再修改例化部分加到你的代码里即可~~

 

这篇博客其实也是为下一篇博客做准备,下一篇将FIFO运用到LCD驱动。

写累了,休息,休息~~


技术讨论欢迎加群~~电子技术协会   362584474