异步FIFO设计与实现及相关问题

与普通存储器的区别是不需要外部读写地址线,缺点是只能顺序写入,顺序读出数据,数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指针的地址。

异步fifo读写分别采用不同的时钟,可以用来跨时钟域。也可以进行数据输入输出的位宽转换。

FIFO的深度计算

数据突发长度(burst length):
模块A向FIFO中写入数据,模块B从FIFO中不间断的读出数据,模块A写数据的时钟频率大于模块B读数据的时钟频率,在一段时间内总是有一些数据没来得及被读走,那么数据会积累的越来越多,需要的FIFO的深度就无穷大,因此在突发数据传输过程中讨论FIFO深度才是有意义。
一次传递一包数据完成后再传递下一包数据,这一段时间内传递的数据个数称为burst length。要确定FIFO的深度,关键在于计算出突发读写这段时间内有多少个数据没被读走,FIFO的最小深度就是等于没有被读走的数据个数。
模块A想FIFO写数据的时钟频率是Fa,模块B从FIFO读数据的时钟频率为Fb。

情况1:

Fa>Fb with no idle cycles in both write and read
假设:
写数据的时钟频率是Fa=80Mhz
读数据的时钟频率是Fb=50Mhz
突发长度 = 120
在突发传输过程中,数据都是连续读写的
结果:
写一个数据所需要的时间t=1/80Mhz=12.5ns
突发传输中,写完所有数据的时间=120 * 12.5ns=1500ns
读一个数据所需要的时间=1/50Mhz=20ns
所以在写完所有的突发传输数据需要花费1500ns
在1500ns内能够I读走的数据个数=1500ns/20ns=75
所以在1500ns内还没有被读走的数据个数=120-75=45
所以FIFO的最小深度为45

情况2

Fa>Fb with two clock cycle delay between two successive read and write
读比写慢两拍,在异步FIFO设计中,我们需要去判断FIFO的空满来保证逻辑的正确性,判断空满标志需要去比较读写指针,而读指针与写指针处在不同的时钟域中,我们需要次啊应格雷码和两级同步寄存器去降低亚稳态的概率,而两级同步必然会导致空满标志位的判断至少延迟2个cycle。对于空标志位来说,将写指针同步到读时钟域至少需要花费两个时钟,而在同步这段时间内可能还会写入新的数据,因此同步后的写指针一定小于或等于当前的写指针,所以此时的判断不一定是真空;同理,对于满标志来说,将读指针同步到写时钟域至少需要花费两个时钟,在同步的这段时间内有可能会读出新的数据,因此同步后的读指针一定小于或等于当前读指针,所以此时判断并不一定是真满。
所以此时的FIFO最小深度应该比情况1的最小深度45略大。

情况3

Fa>Fb with idle cycles in both write and read
假设:
写数据时钟频率Fa=80Mhz
读数据时钟频率Fb=50Mhz
突发长度=120
每隔1个cycle写一次
每隔3个cycle读一次
结果:
每隔1个cycle写一次,意味着2个cycle才写一个数据
每隔3个cycle读一次,意味着4个cycle才读一个数据
写一个数据所需要的时间=2 * 1/80Mhz=25ns
突发传输中,写完所有数据所需要的时间=120 * 25ns=3000ns
读一个数据所需要的时间=4*1/50Mhz=80ns
所以写完所有的突发传输数据需要花费3000ns
在3000ns内能够读走的数据个数=3000ns/80ns=37.5
所以在3000ns内还没用被读走的数据个数=120-37.5=82.5
因此FIFO的最小深度为83

情况4

Fa>Fb with duty cycles given for wr_enb and rd_enb
假设:
写数据时钟频率Fa=80Mhz
读数据时钟频率Fb=50Mhz
突发长度=120
写使能信号占整个burst时间比重为1/2
读使能信号占整个burst时间比重为1/4
结果:
写入一个数据原本需要=1/80Mhz=12.5ns
写完突发数据需要的时间为120*12.5ns=1500ns
实际上写完所有数据的时间为1500/(1/2)=3000ns
读出一个数据实际上需要的时间为1/50Mhz/(1/4)=80ns
所以在整个突发时间内能够读出数据位3000/80=37.5
所以在3000ns内还没用被读走的数据个数=120-37.5=82.5
因此FIFO的最小深度为83

情况5

Fa<Fb with no idle cycles in both write and read
假设:
写数据时钟频率Fa=40Mhz
读数据时钟频率Fb=50Mhz
突发长度=120
在突发传输过程中,数据都是连续读写的
结果:
由于读数据比写数据要快,因此FIFO只起到过时钟域的作用,FIFO的最小深度为1就行。

情况六

Fa<Fb with idle cycles in both write and read
假设:
写数据时钟频率Fa=40Mhz
读数据时钟频率Fb=50Mhz
突发长度=120
每隔1个cycle写一次
每隔3个cycle读一次
结果:
每隔1个cycle写一次,意味着2个cycle才写一个数据
每隔3个cycle读一次,意味着4个cycle才读一个数据
写一个数据所需要的时间=2 * 1/40Mhz=50ns
在突发传输中,写完所有数据需要的时间=120 * 50ns=6000ns
读一个数据所需要的时间=4*1/50Mhz=80ns
写完所有的突发传输数据需要花费6000ns
在6000ns内能够读走的数据个数=6000ns/80ns=75
所以在6000ns内还没读走的数据个数=120-75=45
因此FIFO的最小深度为45

情况七

Fa=Fb with no idle cycles in both write and read
假设:
写数据时钟频率Fa=50Mhz
读数据时钟频率Fb=50Mhz
突发长度=120
结果:
如果读写时钟同源并且无相位差,那么可以不需FIFO,否则FIFO的最小深度为1

情况八

Fa=Fb with idle cycles in both write and read
假设:
写数据时钟频率Fa=50Mhz
读数据时钟频率Fb=50Mhz
突发长度=120
每隔1个cycle写一次
每隔3个cycle读一次
结果:
每隔1个cycle写一次,意味着2个cycle才写一个数据
每隔3个cycle读一次,意味着4个cycle才读一个数据
写一个数据所需要的时间=2* 1/50Mhz=40ns
突发传输中,写完所有数据所需要的时间=120* 40ns=4800ns
读一个数据所需要的时间=4*1/50Mhz=80ns
在写完所有的突发传输数据需要花费4800ns
在4800ns内能够读走的数据个数=4800/80ns=60
在4800ns内还没被读走的数据个数=120-60=60
因此FIFO的最小深度为60

情况九

data rates are given, read and write random
只给出数据在一段时间内的读写速率,怎么读写完全随机,需要考虑最坏的一种情况避免数据丢失,最坏的情况中,读写的速率相差应该最大,也就是说要找出最大的写速率和最小的读速率。
假设:
写数据的时钟频率Fa=80Mhz
读数据的时钟频率Fb=50Mhz
在写时钟周期内,每100个周期就有40个数据写入FIFO
在读时钟周期内,每10个周期可以有8个数据读出FIFO
结果:
首先没有给出数据的突发长度,从假设中可以得出每100周期就有40个数据写入FIFO,这突发长度不是40,因为数据是随机FIFO的,需要考虑最坏的情形,即写入速率最大的情形,只有背靠背的情形才是写速率最高的情形,burst length 为80,背靠背
在这里插入图片描述
注意:验证是否有误解,即写入burst数据时间必须大于读出burst数据时间,不然会使得FIFO深度无限大。首先写入80和数据需要时间1/80Mzh*(80*100/40)=2500ns,读出80个数据需要时间=1/50Mhz(80x10/8)=2000ns,因为写入burst数据时间大于读出burst数据时间,因此有解。
结果:
连续写入80个数据最快所需要的时间=1/80Mhz * 80=1000ns
从FIFO中读出一个数据至少需要的时间(1/50Mhz) * (10/8)=25ns
在1000ns内能读出的数据=1000ns/25ns=40
在1000ns内没读出的数据=80-40=40
因此FIFO的最小深度为40

总结

要确定FIFO的深度关键在于计算出在突发读写这段时间内有多少个数据没有被读走
由于FIFO空满标志位的判断延迟,在实际中需要预留一些余量
推导公式:
假设:
写时钟频率Fwr
读时钟频率Frd
在写时钟周期内,每m个周期内就有n个数据写入FIFO
在读时钟周期内,每x个周期内就有y个数据读出FIFO
结果:
首先必须满足(1/Fwr)*(m/n)>=(1/Frd) x (x/y)
背靠背的情形下是FIFO读写最坏情形,burst长度B=2n
写完burst长度数据最快需要时间T=(1/Fwr)*B
从FIFO中读出一个数据需要时间t=(1/Frd)x(x/y)
在T时间内能够从中读走的数据个数=T/t=B(Frd/Fwr)(y/x)
在T时间内没读走的数据个数=B - B(Frd/Fwr)(y/x)
因此FIFO的最小深度为=B - B(Frd/Fwr)(y/x)
保留一些余量

在这里插入图片描述

关于FIFO的一些问题

Q1、RDclk和WRclk差距较大时,是否会造成multi-bit的问题?是否会造成跳过了full而full+1的状态漏过满?
A1、1.慢时钟采快时钟会出现漏采,但不会出现功能错误,但是将空满信号变得更为保守,效率会因此降低。
2.由于出现漏采,快时钟的读/写指针sync到慢时钟时,gray code就不是按照1次变化1个bit的方式进行了,而是可能一次变化好几个bit,这样gray code的好处就没法体现。
异步FIFO需要将读写pointer作比较产生满和空信号,但是假如FIFO的两个时钟域的clk相差特别大,pointer在做跨时钟域转换的时候就会出现问题。慢时钟采快时钟会漏采数据

举个例子:异步FIFO,写时钟500Mhz,读时钟100Mhz,相差5倍。写时钟域的write_pointer就会以500Mhz的频率增加,但是读时钟的read_pointer增加的频率是100Mhz,现在要判断满和空,满是在写时钟域判断的,空是在读时钟域判断的。

判断满的方法使拿write_pointer和做完CDC转换过来的read_pointer作比较。CDC转换的方法是采用格雷码过两级filp-flop的同步器。由于read_pointer属于100Mhz,写时钟域是500Mhz,属于快采慢,CDC不会出现问题,因此满的判断不会有问题。

判断空的方法是拿read_pointer和做完CDC转换过来的write_pointer作比较。CDC转换的方法也是采用格雷码过两级filp-flop的同步器。由于write-pointer的变化频率是500Mhz,而读时钟域是100Mhz,属于慢采快,CDC转换就会出现问题。
会出现什么问题?
因为read时钟的频率过低,所以write_pointer做完CDC之后得到将是零星的离散的采样值,而非连续的值,比如可能采样到的格雷码的5->10->15而不是连续的5->6->7
这个问题没法解决也不需要解决。
为什么没法解决?
因为快时钟往慢时钟传数据,要想一个不漏,唯一的办法就是hold住,把当前数据Kepp住,等到数据被取走了再传下一个。但是write_pointer属于自增型,只要FIFO没有满,就可以持续增阿吉,并不会hold不变,所以必然会漏采
为什么不需要解决?
因为只要FIFO足够大,即便督导的write_pointer是离散的,也不会影响到FIFO判空。只要FIFO判空不出错,异步FIFO的行为就没有问题,只不过效率会低一点。

如果在读时钟域看懂write_pointer等于5了,真实的write_pointer必然不会小于5,那么基于write_pointer等于5来判断空,将会得到一个很保守的空(假空),也就是异步FIFO还有数,读时钟域就判断出空,暂时停止读取数据。

Q2、亚稳态真的消除了?如果出现了亚稳态,你的FIFO还能用?
A2、通过使用格雷码,我们可以降低亚稳态出现的可能性,但是不能说是真的消除了。如果出现亚稳态,FIFO还是能够继续使用。因为:写快读慢,我们看看full信号的产生,只有当读地址变化的时候(触发器的输入变阿虎的时候才会出现亚稳态,由于是慢到快,就算出现亚稳态,因为相邻的格雷码每次只有一位发生变化,这个出错结果实际上也就是读指针没有跳变保持不变)亚稳态才会产生,这就以为着我们又读了一个数据,所以即使传递过去之后产生了full信号,也没有溢出,因为传递的时候至少又读了一个数据的。 (由快到慢就不会出现读空了,写地址同步过去时候更会写好几次)

在涉及到触发器的电路中,亚稳态无法彻底消除,只能想办法将其发生的概率降到最低。其中一个方法就是使用格雷码。格雷码在相邻的两个码元之间只由一位变化,这就避免计数器与时钟同步的时候发生亚稳态现象,但是格雷码的缺点就是只能定义2^n的深度,而不能像二进制那样随意定义FIFO的深度,因为格雷码必须循环一个 2 ^n,否则不能保证两个相邻码元黄子健相差一位的条件,也就不是真正的格雷码了。第二就是使用冗余的触发器,假设一个触发器发生亚稳态的概率是P,那么两个级联的触发器发生亚稳态的概率就是p ^2.但这会导致延时的增加。亚稳态发生会导致FIFO出现错误,读写时钟采样的地址指针与真实值之间不同,这就导致写入或读出的地址错误。由于考虑延时作用,空/满标志的产生并不一定出现在FIFO真的空/满时才出现。可能FIFO还未空/未满就出现了空满标志。

Q3、格雷码的轴对称问题
A3、1.镜像对称的原因是为了每一个相邻的变化都只有1bit,为了满足0000(0)和1000(15)也只有1bit的变化
2.格雷码地址直接去比较判断写满读空
当最高位和次高位相同,其余位相同,认为是读空
当最高位和次高位不同,其余位相同,认为是写满
对“空”的判断是二者完全相等(包括MSB)
对于”满”的判断,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下三条:
1.wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
2.wptr和让rptr的次高位不相等,MSB不同说明多折回一次
3.剩下的其余位完全相等
在这里插入图片描述

在实际设计中如果不想用格雷码比较,可以将格雷码读写地址同步到一个时钟域后再次转换成二进制数据,用以下比较原理:
当最高位相同,其余位相同认为读空
当最高位不同,其余位相同认为写满
深度为8的异步FIFO,定义的指针只需要3位就够用,但是设计时将指针设计成4位,最高位的作用是区分读空还是写满。

设计

异步FIFO的设计框图如下
在这里插入图片描述
二进制转格雷码:
将二进制数右移一位,再与原本的二进制数异或
assign gray_code = (bin_code>>1)^bin_code
空信号的判断:

assign  empty_val = (rd_cnt == wr_cnt_r);
 always @(posedge rclk or negedge rst_n)begin
     if(!rst_n) 
         fifo_empty <= 0;
     else 
         fifo_empty <= empty_val;    
 endassign empty

满信号判断:

assign full_val = (wr_cnt == {~rd_cnt_r[ADDR_SIZE: ADDR_SIZE-1], rd_cnt_r[ADDR_SIZE-2:0]});
 
 always @(posedge wclk or negedge rst_n)begin
     if(!rst_n)
         fifo_full <= 0;
     else
         fifo_full <= full_val;    
 end

设计代码—未经仿真

module fifo_asyn #(
	parameter data_width = 16,
	parameter data_depth = 256,
	parameter addr_width = 8;
)
(
	input rst,
	input wr_clk,
	input wr_en,
	input [data_width-1:0] din;
	
	input rd_clk,
	input rd_en,
	output reg valid,
	output reg [data_width-1:0] dout,
	output empty,
	output full
);
reg [addr_width:0] wr_addr_ptr;     //地址指针,比地址多一位,MSB用来检测在同一圈
reg [addr_width:0] rd_addr_ptr;

wire [addr_width-1:0] wr_addr;   //RAM地址
wire [addr_width-1:0] rd_addr;

wire [addr_width:0] wr_addr_gray;     //地址指针对应的格雷码
reg [addr_width:0] wr_addr_gray1;     //同步使用
reg [addr_width:0] wr_addr_gray2;

wire [addr_width:0] rd_addr_gray;
reg [addr_width:0] rd_addr_gray1;
reg [addr_width:0] rd_addr_gray2;

reg [data_width-1:0] fifo_ram [data_depth-1:0];
//----------------write fifo----------------------

always @(posedge wr_clk or negedge rst) begin
 if(wr_en && (~full)) begin
		fifo_ram[wr_addr] <= din;
	end
	else begin
		fifo_ram[wr_addr] <= fifo_ram[wr_addr]
	end
end

//---------------read fifo----------------------
always @ (posedge rd_clk or negedge rst) begin
	if(!rst) begin
		dout <= 'h0;
		valid <= 1'b0;
	end
	else if(rd_en && (~empty)) begin
		dout <= fifo_ram[rd_addr];
		valis <= 1'b1;
	end
	else begin
		dout <= 'h0;
		valid <= 1b0;
	end
end



assign wr_addr = wr_addr_ptr[addr_width-1:0];
assign rd_addr = rd_addr_ptr[addr_width-1:0];

//---------------------------读指针同步到写时钟域
always @ (posedge wr_clk) begin
	rd_addr_gray1 <= rd_addr_gray;
	rd_addr_gray2 <= rd_addr_gray1;
end
//-----------------写指针递增
always @ (posedge wr_clk or negedge rst) begin
	if(!rst) begin
		wr_addr_ptr <= 'h0;
	end
	else if(wr_en &&(~full)) begin
		wr_addr_ptr <= wr_addr_ptr + 1;
	end
	else begin
		wr_addr_ptr <= wr_addr_ptr;
	end
end


//----------------写指针同步到读时钟域
always @ (posedge rd_clk) begin
	wr_addr_gray1 <= wr_addr_gray;
	wr_addr_gray2 <= wr_addr_gray1;
end
//--------------读指针递增
always @ (posedge rd_clk or negedge rst) begin
	if(!rst) begin
		rd_addr_ptr <= 'h0;
	end
	else if(rd_en && (~empty)) begin
		rd_addr_ptr <= rd_addr_ptr + 1'b1;
	end
	else begin
		rd_addr_ptr <= rd_addr_ptr;
	end
end

//--------------------bin to gray---
assign wr_addr_gray = (wr_addr_ptr>>1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr>>1) ^ rd_addr_ptr;

//-------------generate full and empty  signals----
assign full = (wr_addr_gray=={~(rd_addr_gray2[addr_width:addr_width-1]),rd_addr_gray2[addr_width-2:0]});
assign empty = (rd_addr_gary == wr_addr_gary2);
endmodule

在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值