异步FIFO实验小结

目录

写在前面 

一、概述 

1. 异步/同步FIFO

2. 异步fifo的特点

3. 异步fifo设计结构图

4. FIFO的常见参数

 5. 异步fifo空/满判断

 6.指针计数器的选择

 7. 格雷码/二进制码相互转换

8. RTL代码 

9. DUT debug 

二、验证平台构建

1.验证代码详解

2.验证功能点

3. 验证结构 

4. Analysis 

5.部分功能点验证

1 时钟功能点验证

​2 复位功能点验证

6.覆盖率分析

1.代码覆盖率 

2.功能覆盖率 

三、整体结构图 

四、Markfile


写在前面 

本小节内容主要针对异步FIFO进行设计验证,设计结构较为简单。作为SV学习阶段的大练习,其主要目的更多的是对SV基础语法的巩固,以及对验证功能点提取,覆盖率收集的学习。一些提供的代码和分析可能有误,也请阅读该文的小伙伴积极提出问题,一起进步。

RTL代码获取:

TB代码获取:

一、概述 

1. 异步/同步FIFO

FIFO在硬件上是一种地址依次自增的Simple Dual Port RAM,按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO,其中同步FIFO是指读时钟和写时钟为同步时钟,常用于数据缓存和数据位宽转换;异步FIFO通常情况下是指读时钟和写时钟频率有差异,即由两个异步时钟驱动的FIFO,由于读写操作是独立的,故常用于多比特数据跨时钟域处理。

2. 异步fifo的特点

异步fifo用于在不同的时钟域(clock domain)之间安全地传输数据。而同步FIFO主要是解决数据传输速率匹配问题;因此,异步fifo需要进行同步(synchronize)处理;一般来讲,我们可以采用同步器(由2FF组成)对单bit的信号进行同步操作。注意,这里的打拍是针对单bit信号而已的。

异步FIFO实验小结_第1张图片

 但是,二进制数的自增一位变化位数不仅仅是一位,所以在同步的过程中我们使用的格雷码或者独热码,由于独热码占位较多,所以格雷码被广泛应用。在使用格雷码的同时,还需要注意fifo的深度必须是2^n,具体原因后边将展开解释。

3. 异步fifo设计结构图

异步FIFO实验小结_第2张图片

由结构图可知,异步FIFO主要划分两个时钟域

写时钟域:

        always@(posedge wclk);  if(wclken && !wfull) mem[waddr]<=wdata;,表示只要MEM非满且写使能有效,即可往MEM写数据;而wfull_val =(wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); 表示读写指针之间的位移等于FIFO的最大深度,至于gray码高2bit相反原因下面详解。

读时钟域:

        assign rdata<=mem[raddr];,表示只要MEM 有数据,即可往MEM读数据(此处加上rempty的判断会更好);而rempty_val = (rgraynext==rq2_wptr); 表示读指针追上写指针,即MEM内没有数据。

4. FIFO的常见参数

FIFO的宽度(datasize):即FIFO一次读写操作的数据位;
FIFO的深度(depth):指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
满标志(wfull):FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
空标志(rempty):FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
读时钟(rclk):读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟(wclk):写操作所遵循的时钟,在每个时钟沿来临时写数据。

读使能(rcin):读操作有效,允许从fifo mem中读取数据。
写使能(wcin):写操作有效,允许向fifo mem中写入数据。

读指针(rptr):每读出一个数据。读指针加1,始终指向当前要读出的数据的位置。
写指针(wptr):每写入一个数据。写指针加1,始终指向下一次将要写入的数据的位置。

异步FIFO实验小结_第3张图片

 5. 异步fifo空/满判断

异步FIFO实验小结_第4张图片

读空状态可以理解为读地址指针追上写地址指针

实现代码:rempty_val = (rgraynext==rq2_wptr);

写满状态可以理解为写地址指针再次追上读地址指针 

实现代码:wfull_val =(wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});

 满标志判断,gray码高2bit相反分析:

异步FIFO实验小结_第5张图片

细心的小伙伴应该可以发现,上面在提到写满时,说的是写指针再次追上读指针,也就是说,写满时,写指针比读指针多走一圈,为了便于区分,将地址位宽从4 bit拓宽到5 bit,因此此时的写指针地址可以认为是11010

11010的格雷码是10111, 01010的格雷码是01111,对比两个格雷码是不是可以发现,此时高两位相反,低两位相同,这便是格雷码下写满的判断条件;

空/满标志处理细节:

▷ 把读、写指针都额外增加1bit,假如FIFO的深度为8,理论上指针位只需要[2:0]。为了能够正确甄别空、满,需要将指针都扩展到[3:0]。
▷ 其中额外引入的最高位[3],用于辅助甄别是否已经发生了回环(wrap around)的情形。当指针计满FIFO的深度,折回头重新开始时,最高位MSB加1,其它位清0。
▷ 如果读写指针的最高位不同,就意味着写指针速度快,并已经多完成一次回环。
▷ 如果两个指针的最高位相同,就意味着双方完成了相同次数的回环。

 6.指针计数器的选择

异步FIFO实验小结_第6张图片

两种计数方法对比:

        普通二进制计数

     数据同步问题:> 1 bit,从一个clock domain到另一个clock domain,由于亚稳态现象的出现,会导致数据出错; 极端情形:所有的数据位都变化;

        格雷码计数

     每次当从一个值变化到相邻的一个值时,有且仅有一位发生变化;

  1. 由于格雷码的这种特性,我们就可以通过简单的synchronizer对指针(多位宽)进行同步操作了,而不用担心由于发生亚稳态而出现数据错误的情形(前边有解释);
  2. 对于2的整数次幂的FIFO,采用格雷码计数器; 接近2的整数次幂的FIFO, 采用接近2的幂次方格雷码修改实现;如果这两种都满足不了,就设计一种查找表的形式实现。所以,一般采用2的幂次方格雷码实现FIFO,会浪费一些地址空间,但可以简化控制电路;
    需要注意:格雷码计数器适用于地址范围空间为2的整数次幂的FIFO,例如8, 16, 32, 64…

 7. 格雷码/二进制码相互转换

二进制码转格雷码只需将二进制码字整体右移一位,最高位补0,再与原先的码字按位做异或操作(如下图)

实现代码:rgraynext = (rbinnext>>1)^rbinnext;

异步FIFO实验小结_第7张图片

格雷码转二进制码,将格雷码的最高位作为二进制的最高位,将格雷码次高位与二进制码最高位异或运算,得到二进制码次高位,以此类推(如图)。

异步FIFO实验小结_第8张图片

8. RTL代码 

module fifo1 #(parameter DSIZE = 8, parameter ASIZE = 4)
				(input [DSIZE-1:0] wdata,
				 input winc,wclk,wrst_n,
				 input rinc,rclk,rrst_n,
				 output[DSIZE-1:0] rdata,
				 output wfull,
				 output rempty
				 );

wire [ASIZE-1:0] waddr,raddr;
wire [ASIZE:0] wptr,rptr,wq2_rptr,rq2_wptr;

sync_r2w sync_r2w   (.rptr(rptr),
					 .wclk(wclk),
					 .wrst_n(wrst_n),
					 .wq2_rptr(wq2_rptr)
					 );
					 
sync_w2r sync_w2r   (.wptr(wptr),
					 .rclk(rclk),
					 .rrst_n(rrst_n),
					 .rq2_wptr(rq2_wptr)
					 );
rptr_empty #(ASIZE) rptr_empty(.rclk(rclk),
					  .rrst_n(rrst_n),
					  .rinc(rinc),
					  .rq2_wptr(rq2_wptr),
					  .raddr(raddr),
					  .rempty(rempty),
					  .rptr(rptr)
					 );
wptr_full #(ASIZE)  wptr_full(.wq2_rptr(wq2_rptr),
					 .wclk(wclk),
					 .wrst_n(wrst_n),
					 .winc(winc),
					 .wfull(wfull),
					 .waddr(waddr),
					 .wptr(wptr)
					 );
fifomem #(DSIZE,ASIZE) fifomem (.wdata(wdata),
					 .waddr(waddr),
					 .raddr(raddr),
					 .wclken(winc), 
					 .wfull(wfull), 
					 .wclk(wclk),
					 .rdata(rdata)
					 );
endmodule


module fifomem #(parameter DATASIZE = 8,parameter ADDRSIZE = 4) //mem data word width,number od mem address bits
			(input [DATASIZE-1:0] wdata,
			 input [ADDRSIZE-1:0] waddr,raddr,
			 input wclken, wfull, wclk,
			 output[DATASIZE-1:0] rdata);
			 
`ifdef 	VENDORRAM
//instantiation of a vendor's dual-port RAM
vendor_ram mem(.dout(rdata),
			   .din(wdata),
			   .waddr(waddr),
			   .raddr(raddr),
			   .wclken(wclken),
			   .wclken_n(wfull),
			   .clk(wclk)
			   );
`else
//RTL verilog mem model
localparam DEPTH = 1<>1)^rbinnext;
//--------------------------------------------------------
//fifo empty when the next rptr == synchronized wptr or on reset
//--------------------------------------------------------
assign rempty_val = (rgraynext==rq2_wptr);

//-----------------------
//GRAYSTYLE2 pointer
//-----------------------

always@(posedge rclk or negedge rrst_n)
	if(!rrst_n)
		{rbin,rptr} <= 0;
	else
		{rbin,rptr} <= {rbinnext,rgraynext};
	
always@(posedge rclk or negedge rrst_n)
	if(!rrst_n)
		rempty<=1'b1;
	else
		rempty<=rempty_val;
endmodule


module wptr_full #(parameter ADDRSIZE = 4)
					(input [ADDRSIZE:0] wq2_rptr,
					 input wclk,wrst_n,winc,
					 output reg wfull,
					 output [ADDRSIZE-1:0] waddr,
					 output reg [ADDRSIZE:0] wptr
					 );
					
reg [ADDRSIZE:0] wbin;
reg [ADDRSIZE:0] wbinnext,wgraynext;
wire wfull_val;

//mem write-address pointer
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
//--------------------------------------------
assign wfull_val =(wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});

//GRAYSTYLE2 pointer
always@(posedge rclk or negedge wrst_n)
	if(!wrst_n)
	{wbin,wptr} <= 0;
	else
	{wbin,wptr} <= {wbinnext,wgraynext};
	
always@(posedge rclk or negedge wrst_n)
	if(!wrst_n)
	wfull<= 1'b0;
	else
	wfull<= wfull_val;
endmodule


module sync_r2w #(parameter ADDRSIZE = 4)
					(input [ADDRSIZE:0] rptr,
					 input wclk,wrst_n,
					 output reg [ADDRSIZE:0] wq2_rptr
					 );

reg [ADDRSIZE:0] wq1_rptr;

always@(posedge wclk or negedge wrst_n)
	if(!wrst_n)
		{wq2_rptr,wq1_rptr} <= 0;
	else
		{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
endmodule


module sycn_w2r #(parameter ADDRSIZE = 4)
					(input [ADDRSIZE:0] wptr,
					 input rclk, rrst_n,
					 output[ADDRSIZE:0] rq2_wptr
					 );

reg [ADDRSIZE:0] rq1_wptr;

always@(posedge rclk or negedge rrst_n)
	if(!rrst_n)
	{rq2_wptr,rq1_wptr} <= 0;
	else
	{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};

endmodule

9. DUT debug 

bug1:wdata没有数据写入时,只要写指针循环一圈后,wfull就会拉高,此时实际上没有数据写入,fifo实际为空; 

异步FIFO实验小结_第9张图片

原因分析:主要是在waddr 和wptr 设置为自增形式,没有考虑wdata的因素,所以将wdata加入作为判断即可

代码定位: 

           异步FIFO实验小结_第10张图片

此处添加了对wdata的判断,只有wdata有数据是,写指针以及写地址才会加1.

 dubug后代码:

异步FIFO实验小结_第11张图片

bug2: 在winc拉高的前提下,当wrst_n和rrst_n在拉低过程中没有交集时,在后边即使二者都拉高,且wdata有值,fifo依然不会读入数据,显示为不定态;

异步FIFO实验小结_第12张图片

原因分析:从代码的分析中不难发现,当wrst_n拉高后,wfull取决于wgraynext的状态,换句话说,如果wgraynext为不定态,那wfull就是不定态;而wgraynext又取决于wfull的状态,这样就形成了一个闭环,所以需要有一个值提前初始化,打破这种闭环。

代码定位:

异步FIFO实验小结_第13张图片

 加入了对wdata的判断,如果有wdata写入,就认为fifo非空,接下来判断是否为空。

前些日子突然有小伙伴问我这个问题,后来仔细研究一番发现有一种更简单的修改方法,将wfull_val赋值变量语句中的“=="改为”===“,问题就解决了。

wfull_val =(wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]})

wfull_val =(wgraynext==={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]})

期待大家向我提问哦!

debug后代码:

异步FIFO实验小结_第14张图片

二、验证平台构建

1.验证代码详解

由于篇幅受限,验证代码详解请点此链接

2.验证功能点

功能类:

  • 一、时钟测试点提取:
  1. read/write相同时钟和不同时钟同时读写;
  2. Read时钟快,write时钟慢,同时读写;
  3. Read时钟慢,write时钟快,同时读写;
  4. 大比例时钟时正常工作:1)write时钟频率是read的4倍,同时读写;2).write时钟频率是read的1/4倍,同时读写.
  • 二、复位测试点提取:
  1. read端口reset测试;
  2. Write端口reset测试;
  3. 先reset read端口,在reset write端口;
  4. 先reset write端口,在reset read端口;
  5. 同时将read和write 进行reset。
  • 三、其他功能测试点提取:
  1. 基本的写功能验证;
  2. 基本的读功能验证;
  3. 读写功能同时进行的验证;

接口类:

  • 一、Clk验证点提取:

验证不同频率下功能点是否正确;

  • 二、Reset验证点提取:

验证不同时刻reset,功能是否正确;

  • 三、寄存器接口验证:

验证寄存器访问是否正确;

  • 四、串行接口:

验证发送接收是否符合预期;

  • 五、中断接口:

验证产生中断触发条件,中断输出以及中断清除是否符合预期;

场景类:

  • 一、验证收发功能:

验证是否可以正常接收发送数据,是否符合通信协议;

异常类:

  1. 空状态下读;
  2. 满状态下写。

3. 验证结构 

异步FIFO实验小结_第15张图片

4. Analysis 

异步FIFO实验小结_第16张图片

S:     if(!wrst_n) {wbin,wptr} <= 0; if(!wrst_n)  wfull<= 1'b0;

M0: if(!rrst_n) {rbin,rptr} <= 0;   if(!rrst_n)  rempty<=1'b1;

M1: wrst_n拉高,{wbin,wptr} <= {wbinnext,wgraynext}; wfull<= wfull_val;注意waddr=wbin,

wbinnext= wbin + (winc & ~wfull);此时,winc为高,wfull非空,所以接下来随着wclk的增加,waddr也会自加,直到wfull拉高为止,即M1-M2阶段;此时虽然为满,但内部没有数据;

M2:随着waddr的增加,当满足wfull<= wfull_val(full_val =(wgraynext=={~wq2_rptr[ADDRSIZE:

ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]})时,wfull拉高;

M3:rrst_n拉高,由TB文件激励发送协议,repeat(5)@(posedge itf.wclk); nv.fifowrite(26);等待5个wclk时钟周期,开始发送激励,即M4;

M4:开始发送激励;

M5: rrst_n拉高后两个rclk时钟周期,{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr}; rq2_wptr= wptr,wptr为格雷码11000;此时rempty<=rempty_val (rempty_val = (rgraynext==rq2_wptr))结果为0;rempty拉低;

M6:写入数据后,随着rclk的增加,raddr开始自加,所以在M6 site raddr指针指向1,但是此时mem内部并没有数据,所以没有读回data;

M7: rrst_n拉高两个rclk时钟周期后,理论上写入一个数据会被读走一个数据,但是由于wclk的频率是rclk的三倍,所以每次只能是前边的一个数据被写入mem中,但是由于此时写入addr是1,而此时raddr指针指向2,所以读不出数据,将一直保持读指针和写指针差一个地址单位,所以只有读指针循环一圈才可以读出数据,这也是M10读回data的原因;

M8:wfull拉低的时间为一个wclk周期;

M9:当所有的data写完,winc拉低,此时实际上mem中只有部分数据写入,其余数据丢失。

M10:等待raddr循环一个fifo深度,开始读回数据。 

5.部分功能点验证

1 时钟功能点验证

case1:读写相同时钟频率同时读写

异步FIFO实验小结_第17张图片

异步FIFO实验小结_第18张图片

 case2:读时钟快,写时钟慢,同时读写

异步FIFO实验小结_第19张图片

异步FIFO实验小结_第20张图片

case3:读时钟慢,写时钟快,同时读写

异步FIFO实验小结_第21张图片

case4: 大比例时钟验证 write时钟频率是read的4倍,同时读写

异步FIFO实验小结_第22张图片

 case5: 大比例时钟验证 write时钟频率是read的1/4倍,同时读写

异步FIFO实验小结_第23张图片

异步FIFO实验小结_第24张图片 2 复位功能点验证

case1:先读复位再写复位

异步FIFO实验小结_第25张图片

case2:读写同时复位

异步FIFO实验小结_第26张图片

异步FIFO实验小结_第27张图片

6.覆盖率分析

1.代码覆盖率 

异步FIFO实验小结_第28张图片

2.功能覆盖率

异步FIFO实验小结_第29张图片

三、整体结构图 

异步FIFO实验小结_第30张图片

四、Markfile

异步FIFO实验小结_第31张图片

RTL代码详解:点此链接

TB代码详解:点此链接

你可能感兴趣的:(实验代码,fpga开发,fifo,System,Verilog,1024程序员节)