异步FIFO设计 - 牛客网VL45 异步FIFO

刷题做题过程中遇到很多问题,异步FIFO是个比较不错的开端来记录。该题涉及到格雷码、同步FIFO、跨时钟域等问题,值得记录一下。该篇文章写得不错,解答了我很多疑惑,拿来MARK一下,同时分享给跟我有同样疑惑的人。

一、FIFO简介
FIFO是一种现先进先出的数据缓冲器,特点是没有外部的读写地址。由于没有外部的地址信号,所以只能顺序的读写,而不能跳读。FIFO的读写是根据满和空信号设计写使能和读使能来写/读FIFO,当FIFO满的时候不可以往里面写、当FIFO空的时候不能读数据。读FIFO时,内部的读指针自动的加一,当写FIFO时写指针自动的加一。
什么是异步FIFO,什么又是同步FIFO?
异步FIFO简单的来说就是读写时钟不相同,同步FIFO就是读写的时钟相同。

二、异步FIFO的用途
1、使用异步FIFO可以在两个不同的时钟域之间快速而方便的传输数据,起到跨时钟域处理的作用。经常用于处理跨时钟域的问题。
2、对于不同宽度的数据接口也可以采用FIFO进行缓冲,如8位输入,16位输出。(注:本文只简介输入输出位宽相同的情况)

三、FIFO的常见参数

wfull: 满标志, 表示FIFO已经满,不能再写入数据。
rempty:空标志,表示FIFO已经空,不能再读取数据。
wclk: 写时钟
rclk: 读时钟
winc: 写使能
rinc: 读使能
wdata:写数据
rdata: 读数据
wrst_n: 写复位
10.rrst_n:读复位


四、读写指针的工作原理

读指针:总是指向下一个将要被写入的单元,复位时指向第一个单元。
写指针:总是指向当前要被读出的数据,复位时指向第一个单元。
也就是说,复位时读写指针都指向第一个单元。并且向FIFO写入一个数据,写指针加1。从FIFO中读出一个数据,都指针加1。

五、FIFO满空标志的产生
FIFO设计的关键是如何产生可靠的读写指针和满空信号。FIFO读写指针的工作原理如上第四点所述。

那么剩下的就是要讨论如何产生FIFO的满空信号了。FIFO什么时候为空呢?我们来思考一下,假设我从第一个单元写入数据,那么写指针从地址0—>1,读指针不变,此时FIFO中有一个数据。接着我把这个数据读出来,读指针从0—>1。此时写指针为1,读指针也为1,FIFO中没有数据了,因此FIFO为空。从中可以发现,判断FIFO为空很简单,只要读写指针相等就是空。但是事情好像也没那么简单,再想一下,假设一开始就复位,读写指针都在0地址,然后一直王FIFO中写入数据,当写满FIFO的时候,写指针刚好转了一圈回到了0地址,此时读写指针也相等,但是这时候FIFO是满的。因此得到下面的判空和判满条件。

判空:读指针追上写指针的时候,两者相等,为空。
判满:写指针追上读指针的时候,两者相等,为满。

突然发现两者相等的话不是空就是满,区别就是谁追上谁而已了。那么如何来区别是谁追上谁呢?

六、如何判断读写指针相等时,为空还是为满呢?

答案就是在表示读写指针的数据位宽上再加1位来区分是满还是空。比如FIFO的深度位8,那么需要3位二进制数来表地址,则需要再最高之前再加一位,变成4位。一开始读写都是0000,FIFO为空。当写指针增加并越过最后一个存储单元的时候,就将这个最高位取反,变成1000。这时是写地址追上了读地址,FIFO为满。同理,当读地址越过最后一个存储单元的时候把读地址的最高位也取反。可以看到,当最高位相同,并且剩下的位也相同的时候FIFO为空;当最高位不同,并且剩下的位相同时,为满。

七、异步时钟域下如何判断时空还是满?

在上述六中已经解释了如何判断FIFO的满空。但是如果在不同时钟域下,显然需要将读写指针进行同步化才可以进行判断。具体就是在判断空的时候,需要将写地址同步到读时钟域下进行判断。同理,在进行判断满的时候需要将读时钟域中的读指针同步到写时钟域进行判断。

八、使用格雷码来表示地址

其实在读时钟域中读指针的增加仍然是自然二进制,同理在写时钟域中写地址的增加也是按照自然二进制变化的。但是在将读指针发送到写时钟域下进行同步时,如果仍然采用自然二进制,那么就会面临地址同时有多位变化的情况。比如0111->1000,一次就变了四位。在数电的学习中我们知道,这种情况是要尽量避免的,因为这样容易引起亚稳态或者是毛刺(具体是亚稳态还是毛刺我还不太确定)。

但是采用格雷码每次就只有一个位变化。格雷码和自然二进制码如下。图片来自百度百科。

在这里插入图片描述

那么问题又来了,采用格雷码又要如何判断满和空呢?首先还是要记住:在读指针和写指针相等的时候进行判断。

举个例子:
        假如T1时刻,读指针的自然二进制为0111,写指针的自然二进制位1000。
简化如下:
               T1:读:0111(bin) 0100 (grey)
        写:1000(bin) 1100(grey)
可以看到,此时此时格雷码 的最高位不同,剩下的都相同。那么可以判为满吗?显然是不行,可以冥想的知道,此时刚刚向FIFO中写入一个数,怎么就满了呢。

因此必须考虑用别的办法来比较。方法就是:

判满:格雷码的最高位和次高为不同,剩下的都同,就是满。
判空:格雷码完全相同,就是空。

二进制如何转换成格雷码:

二进制:10110 对应的格雷码:11101
转换方法:

在这里插入图片描述 

简单说下就是,最高位不变,剩下的分别与自己左边的进行异或就可以得到当前位。用公式表示就是:

11101 = (10110>>1)^10110;
左移一位,再与自己异或。

九、模块说明

图片来源于网络,侵删。
上图上FIFO设计的总体框图,可以将其分为五个部分,分别如图中的虚线框所示。分别是双端口ROM模块(用来存储)、数据写入控制器(FIFO判满模块)、数据读出控制器(FIFO判空模块)、读指针同步器、写指针同步器五个模块。

1. 第1部分是双口RAM,用于数据的存储。

2. 第2部分是数据写入控制器

3. 第3部分是数据读取控制器

4. 读指针同步器

   使用写时钟的两级触发器采集读指针,输出到数据写入控制器。

5. 写指针同步器

   使用读时钟的两级触发器采集写指针,输出到数据读取控制器

下边的代码是提交到牛客网的代码,附上题目链接,感兴趣的可以自己到平台上答题。

异步FIFO_牛客题霸_牛客网

`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

/***************************************AFIFO*****************************************/
module asyn_fifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk	, 
	input 					rclk	,   
	input 					wrstn	,
	input					rrstn	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output wire				wfull	,
	output wire				rempty	,
	output wire [WIDTH-1:0]	rdata
);
localparam ADDR_WIDTH = $clog2(DEPTH);

reg [ADDR_WIDTH:0] waddr_bin;
reg [ADDR_WIDTH:0] raddr_bin;
always@(posedge wclk or negedge wrstn)
begin
	if(!wrstn)
		waddr_bin <= 'd0;
	else if(winc&&!wfull)
		waddr_bin <= waddr_bin + 1;
end
always@(posedge rclk or negedge rrstn)
begin
	if(!rrstn)
		raddr_bin <= 'd0;
	else if(rinc&&!rempty)
		raddr_bin <= raddr_bin + 1;
end

wire [ADDR_WIDTH:0] waddr_gray;
wire [ADDR_WIDTH:0] raddr_gray;
reg [ADDR_WIDTH:0] wptr;
reg [ADDR_WIDTH:0] rptr;
assign waddr_gray = waddr_bin^(waddr_bin>>1);
assign raddr_gray = raddr_bin^(raddr_bin>>1);
always@(posedge wclk or negedge wrstn)
begin
	if(!wrstn)
		wptr <= 'd0;
	else
		wptr <= waddr_gray;
end
always@(posedge rclk or negedge rrstn)
begin
	if(!rrstn)
		rptr <= 'd0;
	else
		rptr <= raddr_gray;
end
reg [ADDR_WIDTH:0] wptr_buff;
reg [ADDR_WIDTH:0] rptr_buff;
reg [ADDR_WIDTH:0] wptr_syn;
reg [ADDR_WIDTH:0] rptr_syn;
always@(posedge rclk or negedge rrstn)
begin
	if(!rrstn)begin
		wptr_buff <= 'd0;
		wptr_syn <= 'd0;
	end else begin
		wptr_buff <= wptr;
		wptr_syn <= wptr_buff;
	end
end

always@(posedge wclk or negedge wrstn)
begin
	if(!wrstn)begin
		rptr_buff <= 'd0;
		rptr_syn <= 'd0;
	end else begin
		rptr_buff <= rptr;
		rptr_syn <= rptr_buff;
	end
end

assign wfull = (wptr=={~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]});
assign rempty = (rptr == wptr_syn);

wire [ADDR_WIDTH-1:0] waddr;
wire [ADDR_WIDTH-1:0] raddr;
assign waddr = waddr_bin[ADDR_WIDTH-1:0];
assign raddr = raddr_bin[ADDR_WIDTH-1:0];
dual_port_RAM 
#( .DEPTH(DEPTH),
   .WIDTH(WIDTH))
dual_port_RAM_U1(
	.wclk(wclk),
	.wenc(winc&&!wfull),
	.waddr(waddr[ADDR_WIDTH-1:0]),
	.wdata(wdata),
	.rclk(rclk),
	.renc(rinc&&!rempty),
	.raddr(raddr[ADDR_WIDTH-1:0]),
	.rdata(rdata)
);
endmodule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值