(一)异步FIFO的设计

本文详细介绍了异步FIFO的设计原理,包括亚稳态的处理、单bit信号的跨时钟域同步以及多bit信号为何不能简单同步。重点讨论了异步FIFO在跨时钟域数据存储读写中的应用,阐述了使用格雷码来减少亚稳态的影响,以及FIFO空满标志的判断方法。并列举了关键的RTL代码模块。
摘要由CSDN通过智能技术生成

一、异步FIFO

  • 数据写入FIFO的时钟和数据读出FIFO的时钟是异步的(asynchronous), 异步是指相位不同,频率可能不同,不同时钟上升沿之间关系不确定,相位关系不确定,常用于跨时钟域数据的存储读写。

1、亚稳态

        以单bit信号为例:

说明:

  • 亚稳态:上图Din是DFF1的输入由上一个DFF0在时钟域clka下输出的单bit信号,当DFF0输出到clkb时钟域时,由于两个时钟域是异步的,所以无法保证采样到的DFF1的输出Ds是稳定的,因为信号跨时钟域,它要在clkb时钟域稳定下来需要一定的时间(这段时间包括触发器内部电路的时间、导线,导线中间组合逻辑电路的时间),这段稳定时间中的输出Ds是不稳定的,叫这个不稳定的状态为亚稳态但经过一段时间最终输出Ds会达到稳定状态0或1

2、单bit信号Din慢时钟a同步到快时钟b

若单bit信号Din慢时钟a到快时钟b采用打两拍的方法,打的拍数越多亚稳态消除的越好(信号抖动时间不是确定的,随时钟变化)

  1. 第一拍:b时钟下同步Din,减小采到亚稳态的概率,虽采到随机值,但最终会达到一个正确的稳定值(随机值最终不会到一个错误的值), 所以输出信号的Ds基本稳定了,但不能用这个信号,因为在时钟b下,仍然有可能会采样到亚稳态,采样到的亚稳态可能为1或0,是不确定的。
  2. 第二拍:第二次b时钟下同步Din,此时采到Ds信号基本稳定已消除了亚稳态,再输出Dout信号

3、单bit信号Din快时钟b同步到慢时钟a

若单bit信号Din快时钟b同步到慢时钟a,一定可以采到稳定的信号,但可能会出现重复采样问题即出现了多个快时钟周期内同一个数据的信号。

4、多bit信号为什么不能采用打两拍的方法进行跨时钟域同步

  1. 假设从时钟域A垮到时钟域B,是域A从00变到11,在时域B还能采样到从00变到11吗?在时域B中,时钟的上升沿来的时间是肯定一致的,但是各个bit从时钟域A到达时钟域B的时间是不一样的,亚稳态恢复到稳态的时间也是不一样的。即00中的两个0并不是同时变成1的,因此时钟域B中可能采到中间态01或10。
  2. 单比特发送亚稳态的概率变成多bit后概率会大很多,不直接打两拍是可以降低亚稳态发生的概率的。

二、异步FIFO设计:用于跨时钟域数据的存储读写

说明:

  • 写指针(wptr)指向的位置表示还未写,将要写,系统复位后(FIFO为空),写指针指向0,每写入一笔数据,写指针加1;
  • 读指针(rptr)指向的位置表示还未读,将要读,系统复位后(FIFO为空),读指针指向0,每读入一笔数据,读指针加1;
  • 空标志(empty):当读指针和写指针相等时,空标志=1,复位时,两指针都为0,或者当读指针追赶上了写指针
  • 满标志(full):当写指针和读指针相等时,满标志=1,写指针写了一圈后,又追上了读指针

1、为什么多bit要使用格雷码?

以深度为8的FIFO为例

  •  在异步FIFO中,读写指针多出一个高bit信号来表示读写指针是否已经跑完一圈,自然二进制跑完一圈,例如rptr是1,wptr是9,从高位可以看出FIFO为满,若wptr也是1,说明FIFO为空,但使用自然二进制不好,因为按自然二进制读写指针的变化从3跳到4,变化了3个bit,多bit很容易采到亚稳态,所以使指针转换到格雷码来判断FIFO空满更方便,相邻格雷码只有一个bit不同,可以采取打两拍的方式同步。
  • 例如rptr是1,wptr是9,对应格雷码是0001,1101,即wptr格雷码的高两位取反后与rptr格雷码相同,表示FIFO满,若对应格雷码直接相同,则表示FIFO为空。
  • full信号产生:与写时钟域有关,需要把转换为格雷码的读指针同步到写时钟域。
  • empty信号产生:与读时钟域有关,需要把转换为格雷码的写指针同步到读时钟域。

2、自然二进制转格雷码

//法一
module bin2gry(Gry,Bin);
	parameter length = 8;
	output 	[length-1:0] Gry;
	input 	[length-1:0] Bin;
	reg 	[length-1:0] Gry;
	
integer i;
always @(Bin)
begin
	for(i=0;i<length-1;i=i+1)
		Gry[i]=Bin[i]^Bin[i+1];
	Gry[i]=Bin[i];
end
//法二
assign Gray = (Bin >>1)^Bin;

endmodule

3、格雷码转自然二进制

module gry2bin(Gry,Bin);
	parameter length = 8;
	input 	[length-1:0] Gry;
	output  [length-1:0] Bin;
	reg 	[length-1:0] Bin;
integer i;
always @(Gry)
begin
	Bin[length-1]=Gry[length-1];
	for(i=length-2;i>=O;i=i-1)
		Bin[i]=Bin[i+1]^Gry[i];
end
endmodule

 说明:

  • 格雷码转二进制可以用来判断几乎空几乎满,此验证项目没有验证这一点。

验证DUT使用的RTL代码

1、fifo1.v

用来将各个模块端口连接在一起

module fifo1 #(
	parameter DSIZE = 8, //DSIZE:表示FIFO的数据位宽
	parameter ASIZE = 4)(//ASIZE:表示读写指针的位宽
	input  			   wclk  , 
	input  			   wrst_n,
	input  			   winc  ,//写数据有效信号
	input  [DSIZE-1:0] wdata ,
	input  			   rclk  , 
	input  			   rrst_n,
	input  			   rinc  , //读数据有效信号
	output [DSIZE-1:0] rdata ,
	output 			   wfull ,
	output 			   rempty
);

wire [ASIZE-1:0] waddr,raddr;//表示mem的读写地址
wire [ASIZE:0]   wptr,rptr,wq2_rptr,rq2_wptr;//多设置一bit,表示空满

sync_r2w sync_r2w (
	.wq2_rptr(wq2_rptr),
	.rptr	 (rptr    ),
	.wclk	 (wclk    ),
	.wrst_n	 (wrst_n  )
);

sync_w2r sync_w2r (
	.rq2_wptr(rq2_wptr),
	.wptr	 (wptr    ),
	.rclk	 (rclk    ),
	.rrst_n	 (rrst_n  )
);

fifomem #(
	.DATASIZE(DSIZE),
	.ADDRSIZE(ASIZE))
	fifomem( 
	.rdata (rdata),
	.wdata (wdata),
	.waddr (waddr),
	.raddr (raddr),
	.wclken(winc ),
	.wfull (wfull),
	.rempty(rempty),
	.wclk  (wclk ),
	.wrst_n(wrst_n)
);

rptr_empty #(
	.ADDRSIZE(ASIZE))
	rptr_empty( 
	.rempty  (rempty  ),
	.raddr   (raddr   ),
	.rptr    (rptr    ),
	.rq2_wptr(rq2_wptr),
	.rinc    (rinc    ),
	.rclk    (rclk    ),
	.rrst_n  (rrst_n  )
);

wptr_full #(
	.ADDRSIZE(ASIZE))
	wptr_full( 
	.wfull	 (wfull   ),
	.waddr	 (waddr   ),
	.wptr 	 (wptr    ),
	.wq2_rptr(wq2_rptr),
	.winc    (winc    ),
	.wclk    (wclk    ),
	.wrst_n  (wrst_n  )
);

endmodule

2、fifomem.v

module fifomem #( 

	parameter DATASIZE = 8,//Memory data word width
	parameter ADDRSIZE = 4)(//Number of mem address bits
	input  [DATASIZE-1:0] wdata ,
	input  [ADDRSIZE-1:0] waddr ,
	input  [ADDRSIZE-1:0] raddr ,
	input 				  wclken, 
	input 				  wfull , 
	input 			      rempty,
	input 				  wclk  ,
	input 				  wrst_n,
	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  ),
	.rst_n   (wrst_n)
);

`else

//用mem模拟双端口RAM
localparam  DEPTH = 1 << ADDRSIZE;//10000=2^ADDRSIZE,表示FIFO深度

reg [DATASIZE-1:0] mem [0:DEPTH-1];//即FIFO的数据宽度与深度

assign rdata = !rempty ? mem[raddr] : 8'b0;

integer i;
always @(posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		for(i=0;i<=DEPTH-1;i=i+1)//循环不可综合,会被分成16个清零代码,费空间
			mem[i] <= {(DATASIZE){1'b0}};	
	else if(wclken && !wfull)//FIFO写有效且未写满
		mem[waddr] <= wdata;
end
`endif

endmodule

3、rptr_empty.v


module rptr_empty #(
	parameter ADDRSIZE = 4)(
	input      [ADDRSIZE:0]   rq2_wptr,//rq2_wptr是在读时钟域同步后的写指针
	input 				      rinc	  , 
	input 				      rclk	  , 
	input 				      rrst_n  ,
	output reg 			      rempty  ,
	output     [ADDRSIZE-1:0] raddr   ,
	output reg [ADDRSIZE :0]  rptr
);
reg  [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext;//读指针的格雷码
wire [ADDRSIZE:0] rbinnext;//自然二进制读指针

assign raddr     = rbin[ADDRSIZE-1:0];		//把读指针的低ADDRSIZE位地址给mem
assign rbinnext  = rbin + (rinc & ~rempty); //读指针在读有效且非空条件下加1
assign rgraynext = (rbinnext>>1)^rbinnext;  //读指针转换为格雷码地址

assign rempty_val = (rgraynext ==rq2_wptr);

//rbin,rgraynext直接输出的格雷码会有毛刺,当前时钟域打一拍,dff输出确保没有毛刺
always @(posedge rclk or negedge rrst_n)
begin
	if (!rrst_n)
		{rbin,rptr} <= 0;
	else
		{rbin,rptr} <= {rbinnext,rgraynext};//注意拼接符的使用
end

always @(posedge rclk or negedge rrst_n)
begin
	if (!rrst_n)
		rempty <= 1'b1;
	else
		rempty <= rempty_val;
end		

endmodule

4、 sync_w2r

module sync_w2r #(
	parameter ADDRSIZE = 4)(
	input      [ADDRSIZE:0] wptr    ,
	input 				    rclk    ,
	input					rrst_n  ,
	output reg [ADDRSIZE:0] rq2_wptr //rq2_wptr是在读时钟域同步后的写指针
);
reg [ADDRSIZE:0] rq1_wptr;

//打两拍,同步写指针到读时钟域,使用rclk来同步
always @(posedge rclk or negedge rrst_n)
begin
	if (!rrst_n)
		{rq2_wptr,rq1_wptr} <= 0;
	else
		{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
end
endmodule

5、wptr_full.v


module wptr_full #(
	parameter ADDRSIZE = 4)(
	input [ADDRSIZE:0]   	  wq2_rptr,//wq2_rptr是在写时钟域同步后的读指针
	input 				  	  winc    , 
	input 				  	  wclk    , 
	input 				  	  wrst_n  ,
	output reg 			  	  wfull   ,
	output     [ADDRSIZE-1:0] waddr   ,
	output reg [ADDRSIZE:0]   wptr
);
reg  [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext; 
wire [ADDRSIZE:0] wbinnext;

assign waddr     = wbin[ADDRSIZE-1:0];      //把写指针的低ADDRSIZE位地址给mem
assign wbinnext  = wbin +(winc & ~wfull);   //写指针在写有效且非满条件下加1
assign wgraynext = (wbinnext>>1)^wbinnext;  //写指针转换为格雷码地址

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

//wbin,wgraynext直接输出的格雷码会有毛刺,当前时钟域打一拍,dff输出确保没有毛刺
always @(posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		{wbin,wptr} <= 0;
	else
		{wbin,wptr} <= {wbinnext,wgraynext};
end		

always @(posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		wfull <= 1'b0;
	else
		wfull <= wfull_val;
end

endmodule

6、 sync_r2w

module sync_r2w #(
	parameter ADDRSIZE = 4)( 
	input [ADDRSIZE:0] 		rptr    ,
	input 			   		wclk    , 
	input				    wrst_n  ,
	output reg [ADDRSIZE:0] wq2_rptr //wq2_rptr是在写时钟域同步后的读指针
);
reg [ADDRSIZE:0] wq1_rptr;

//打两拍,同步读指针到写时钟域,在wclk下同步
always @(posedge wclk or negedge wrst_n)
begin
	if (!wrst_n)
		{wq2_rptr,wq1_rptr} <= 0;
	else
		{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
end
endmodule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值