异步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的深度必须是2^n,具体原因后边将展开解释。

3. 异步fifo设计结构图

由结构图可知,异步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,始终指向下一次将要写入的数据的位置。

 5. 异步fifo空/满判断

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

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

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

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

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

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

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

空/满标志处理细节:

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

 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;

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

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<<ADDRSIZE;
reg [DATASIZE-1:0] mem [0:DEPTH-1];
assign rdata<=mem[raddr];

always@(posedge wclk);
	if(wclken && !wfull)
	mem[waddr]<=wdata;
`endif

endmodule


module rptr_empty #(parameter ADDRSIZR = 4)
					(input rclk,rrst_n,rinc,
					 input [ADDRSIZR-1:0] rq2_wptr,
					 output[ADDRSIZR-1:0] raddr,
					 output reg rempty,
					 output reg [ADDRSIZR:0] rptr
					 );
	
reg [ADDRSIZR:0] rbin;
wire[ADDRSIZR:0] rgraynext, rbinnext;
wire rempty_val;

//mem read-address pointer 
assign raddr = rbin[ADDRSIZR-1:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>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实际为空; 

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

代码定位: 

           

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

 dubug后代码:

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

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

代码定位:

 加入了对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后代码:

二、验证平台构建

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. 验证结构 

4. Analysis 

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:读写相同时钟频率同时读写

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

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

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

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

 2 复位功能点验证

case1:先读复位再写复位

case2:读写同时复位

6.覆盖率分析

1.代码覆盖率 

2.功能覆盖率

三、整体结构图 

四、Markfile

RTL代码详解:点此链接

TB代码详解:点此链接

  • 18
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
异步FIFO_gray code异步FIFO的多种约束方式包括以下几种: 1. 读写时钟约束:异步FIFO_gray code异步FIFO需要读写时钟进行同步操作。读写时钟之间应满足一定的时序关系,以保证数据的正确传输和读写的一致性。通常情况下,写时钟应该在读时钟的上升沿之前完成写入操作,以防止读取到不稳定或无效的数据。 2. 数据宽度约束:异步FIFO_gray code异步FIFO的数据输入和输出宽度应相等,即读写数据的位数应一致。如果数据宽度不匹配,可能会导致数据丢失或错误读取。 3. 读写顺序约束:异步FIFO_gray code异步FIFO的读写操作需要按照一定的顺序进行。通常情况下,写操作应在读操作之前完成,以确保数据的正确读取。同时,读写操作之间需要插入适当的等待时间以保证数据的稳定性。 4. 读写使能约束:异步FIFO_gray code异步FIFO的读写使能信号需要在时钟上升沿进行操作。写使能信号应在写入数据之前启用,读使能信号应在读取数据之前启用。同时,读使能和写使能信号之间需要插入适当的延时以保证数据的正确传输和读写的一致性。 5. 异步FIFO_gray code同步约束:异步FIFO_gray code异步FIFO的读写操作需要通过同步电路进行同步。异步FIFO_gray code异步FIFO通常采用双同步FIFO架构,即读写操作分别采用不同的时钟,但通过同步电路进行数据传输和同步操作。 总之,异步FIFO_gray code异步FIFO在设计和实现时需要考虑时钟约束、数据宽度约束、读写顺序约束、读写使能约束以及异步同步约束等多种约束方式,以确保数据的正确传输和读写的一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

创芯人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值