【原创】Xilinx 同步FIFO IP核时序仿真说明(附testbench代码)

引言

最近在研究硬件tcp/ip协议栈,需要用到同步FIFO缓存电脑发来的以太网数据。以前用的Altera家的芯片,还没仔细研究过Xilinx的同步FIFO IP核的时序,为了避免以后采坑,简单分析一下其操作时序。
软件环境:Vivado 2019.2
仿真环境:Vivado Simulator
官方手册:PG057


一、时序分析

大家知道一个数据从一个触发器输入端传输到另一个触发器输出端需要两个时钟周期,那么写进FIFO的第一个数据是wr_en上升沿时的数据还是wr_en上升沿后的下一个数据呢
查看一下PG057手册,发现有这样一句话:“When you assert wr_en, it causes a write operation to occur on the next rising edge of the wr_clk. ”意思是抬高wr_en时,将会在wr_clk的下一个上升沿进行写操作。
这难道是说写入FIFO的应该是wr_en上升沿后的下一个数据吗?但随即看下图中的写操作时序图,数据D1前是灰色的,说明D1应该是在wr_en的上沿被写进了FIFO,但是真正标志数据被写入FIFO的信号wr_ack却是在下一个时钟上升沿拉高的,难道产生了歧义?

为了验证手册中的时序图,在Vivado中进行了仿真,仿真结果如下: 

 可以看见,在wr_en拉高的同时,往FIFO中写了一个数据 “1” ,但这个 “1” 能否正确地写进FIFO中,则需要看读FIFO的时序图。

上图为读时序的仿真结果,可以看见,读出的第一个数据的确是 “1” (FIFO复位值为32'd85),说明手册中的写时序图是正确的,即在wr_en有效的第一个沿就将数据写入FIFO了,但为什么手册又说 “抬高 wr_en时,将会在wr_clk的下一个上升沿进行写操作。” 呢?
这里猜想一种Xilinx FIFO的结构:即信号wr_en后是锁存器结构(纯电平触发),wr_en负责先将输入数据以高电平锁存进FIFO,然后在下一个写时钟的上升沿驱动FIFO内部的触发器将锁存后的数据真正打入FIFO中。
示意图如下:

 所以由此可以猜测,PG057中所说的 “抬高 wr_en时,将会在wr_clk的下一个上升沿进行写操作。”  意思是wr_clk的下一个上升沿是将锁存器后的数据真正通过触发器打入FIFO中,但是对这个数据的采样在wr_en拉高的瞬间已经由第一级的锁存器完成了。

返回来再继续看上面的读时序仿真图,发现FIFO输出数据是在rd_en拉高的下一个时钟上沿输出到dout上的,说明在rd_en拉高后的下一个读时钟上升沿,输出数据才会有效 
这种读模式称为 Standard FIFO Read Operation ,其时序在手册中如下图所示,可见与仿真结果一致。

手册中还提到了一种称为 First-Word Fall-Through FIFO Read Operation(FWFT) 的读模式,其时序在手册中如下图所示,手册中描述为:“When data is available in theFIFO, the first word falls through the FIFO and appears automatically on the output bus(dout).”。  即当FIFO中有可用数据时,第一个数据将通过FIFO并自动出现在输出总线(dout)上。也就是无论rd_en是否拉高,第一个输出的数据总是有效的,这就使得读 rd_en 和 dout 之间不再相差一个时钟周期,而是拉高rd_en,立即在dout上输出有效数据。

Vivado仿真结果如下,可见在FWFT模式下,的确是rd_en拉高时的第一个输出数据就是有效的数据。


 


二、仿真源码

`timescale 1ns / 1ps



module sfifo_test_tb;

/**************************************************/
// input signals
reg				clk	 ;
reg				rst_n;
reg	[31:0]		din  ;
reg				wr_en;
reg				rd_en;

// output signals
wire	[31:0]	dout    ;
wire			wr_full ;
wire			rd_empty;
/**************************************************/

// reg define
reg	[15:0]		cnt 		;	// 写数据个数计数器
reg	[15:0]		tim_cnt 	;	// 复位延时定时器
reg				start_en 	;	// 读FIFO开始标志
reg				start_cnt_en;	// 写FIFO开始标志
reg	[31:0]		ext_reg     ;	// 连接FIFO dout的外部寄存器



///
//***				Main Code			  	  ***//
//***										  ***//
///


// 初始化
initial	begin
	// initial
	clk = 0;
	rst_n = 1;
	din = 32'd0;
	wr_en = 0;
	rd_en = 0;
	cnt = 16'd0;
	tim_cnt = 16'd0;
	start_cnt_en = 1'b0;
	start_en = 1'b0;
	ext_reg = 32'd0;

	// FIFO复位
	#10 rst_n = 0;
	#20 rst_n = 1;

	// 生成时钟
	forever #5 clk = ~clk;
end


// 复位延时
always @(posedge clk) begin
	tim_cnt <= tim_cnt + 1'b1;
	if( tim_cnt == 16'd20 ) begin
		tim_cnt <= 16'd0;
		start_cnt_en <= 1'b1;
	end
end


// 写FIFO
always @(posedge clk) begin
	if( start_cnt_en == 1'b1 ) begin
		cnt <= cnt + 1'b1;
		din <= din + 1'b1;
		wr_en <= 1'b1;
		if( cnt == 16'd255 ) begin
			cnt <= 16'd0;
			wr_en <= 1'b0;
			start_en <= 1'b1;
		end
	end
end


// 读FIFO
always @(posedge clk) begin
	if( start_en == 1'b1 ) begin
		rd_en <= 1'b1;
		ext_reg <= dout;
	end
end


// 同步FIFO例化
sync_fifo  U_sync_fifo(
  .clk	( clk 		)	,
  .rst	( ~rst_n 	)	,
  .din	( din 		)	,
  .wr_en( wr_en 	)	,
  .rd_en( rd_en 	)	,
  .dout	( dout 		)	,
  .full	( wr_full 	)	,
  .empty( rd_empty 	)
);


endmodule


 

猜你喜欢

转载自blog.csdn.net/qq_40807206/article/details/112574229