所谓异步是指写时钟是完全独立并且不一致的,或者不同频率,或者同频但不同相。读地址和空标志是由读时钟产生的,而写地址和满标志则由写时钟产生,当要产生FIFO的空、满标志时,必须进行读写地址的比较时,问题就来临了。如果直接采样地址比较的话,地址线一般有多位,由于每个地址寄存器的物理空间位置的不一致性,造成写地址的每一位在写时钟作用下,跳变得不一致,即产生毛刺,要过一小段时间才能稳定。在未稳定期内,刚好读时钟进行采样写地址,如果正好读写地址一样,这时就出现误判断,逻辑错误。同时采样读写地址相差N个来产生空满信号,时间上会多一些,因为涉及加和减操作。
为避免地址跳变的不一致造成读写地址误判断,采样格雷码。该码的优点是相邻两值只有一位跳变,其他不变。这样地址变化的时间较短,极大提高比较精度。
格雷码是一种做加一运算时,只变化一位的编码码,下表即为一个三位格雷码编码格式。
格雷码映射表
可见,格雷码每次只在相邻的位发生变化,这种编码带来的好处是它可避免前面介绍的因线延迟不一致而引起的毛刺现象。
图1 格雷码FIFO的基本原理图
运用格雷码编写异步FIFO的基本思想如上图所示:
图2 格雷码的时序
格雷码是不能进行加减产生空满标志的,所以采样延时一拍的方法。用读地址Rd_addr产生读地址的格雷码Rd_next_gray_addr,将Rd_next_gray_addr延一拍得到Rd_gray_addr再将Rd_gray_addr延一拍得到Rd_last_gray_addr你会发现在绝对时间上
Rd_next_gray_addr、Rd_gray_addr、Rd_last_gray_addr这个地址有先后关系从大到小排列,并且相差如图所示写地址的格雷码的产生也类似,即Wt_next_gray_addr 、Wt_gray_addr 、Wt_last_gray_addr利用这6个格雷码进行比较 ,同时加上读写使能 ,就能表示空和满标志。
空满标志的产生
先说空标志Empty,当读写格雷码地址相等(Rd_gray_addr== Wt_gray_addr )或者FIFO内还剩下一个深度的字(Rd_next_gray_addr== Wt_gray_addr ),并且正在不空的情况下执行读操作Read_enable==1,这时Empty标志应该置为有效 (高电平有效) ,如下图所示
图3 空标志信号产生示意图
图4 满标志信号产生示意图
满标志full
当写FIFO的格雷码地址等于上次读的格雷码地址时
(Wt_gray_addr==Rd_last_gray_addr),或者下次要写的格雷码地址等于上次读的格雷码地址(Wt_next_gray_addr==Rd_last_gray_addr ),并且正在执行写操作 ,此时需要置Full标志有效。为了避免复杂的逻辑,提高FIFO的整体速度 ,可使用FIFO深度=实际深度-1,如上图3所示。
如果要产生几乎空、几乎满标志时,可以多做几个格雷码的延时地址,利用这些读、写格雷码地址距离远近关系就可以灵活的产生特定读写地址间距的几乎空或几乎满标志。如果需要在大间距内时,产生几乎空满信号(比如读写地址相差10),那必须采用另外一种方法:以几乎满为例。
一 、首先得到FIFO已经有多少个未读数据,只要使用写地址减去读地址就可以了。原因是结果和两个读写地址的位宽一样,借位和进位不理会,读者可以自己验算一下。
二 、异步电路要产生精确的几乎空和几乎满比较难,都有一定的误差,但是具体情况(已知读、写时钟频率)可以做到误差很小。
下面给出了几乎满的控制逻辑,首先将读地址read_addr变成格雷码Rd_truegray(相对于读时钟) ,再用写时钟同步该读地址的格雷码Rd_truegray得到Rag_wt_syn(相对于写时钟)。
然后将同步后的格雷码Rag_wt_syn变成二进制的地址Ra_write_syn,最后用写地址的延时一拍Wt_addr_p1减去同步后的地址Ra_write_syn(为什么写地址延时,是为保证同一时刻的读写地址比较), 就得到比较精确的FIFO有效数据的个数Fifo_status。利用该个数与FIFO的可用深度(自己决定 ,高溢出水线) 进行比较得到几乎满。这就是范围较大的比较。
代码:
代码语言:javascript复制always @(posedge read_clock or posedge fifo_gsr)begin
if(fifo_gsr)begin
Rd_truegray <= 8'h0;
end
else begin
Rd_truegray <=#1 {
(read_addr[7]),(read_addr[7]^read_addr[6]),
(read_addr[6]^read_addr[5]),(read_addr[5]^read_addr[4]),
(read_addr[4]^read_addr[3]),(read_addr[3]^read_addr[2]),
(read_addr[2]^read_addr[1]),(read_addr[1]^read_addr[0]) };
end
end
always @(posedge write_clock or posedge fifo_gsr)begin
if(fifo_gsr)begin
Rag_wt_syn <= 8'h0;
end
else begin
Rag_wt_syn <=#1 Rd_truegray;
end
end
wire Ra_7_5 = Rag_wt_syn[7] ^ Rag_wt_syn[6] ^ Rag_wt_syn[5] ;
wire Ra_7_4 = Rag_wt_syn[7] ^ Rag_wt_syn[6] ^ Rag_wt_syn[5] ^ Rag_wt_syn[4];
wire Ra_3_1 = Rag_wt_syn[3] ^ Rag_wt_syn[2] ^ Rag_wt_syn[1] ;
assign Ra_write_syn[7] = Rag_wt_syn[7]; //7
assign Ra_write_syn[6] = Rag_wt_syn[7] ^ Rag_wt_syn[6]; //6
assign Ra_write_syn[5] = Ra_7_5; //5
assign Ra_write_syn[4] = Ra_7_4 ; //4
assign Ra_write_syn[3] = Ra_7_4 ^ Rag_wt_syn[3] ; //3
assign Ra_write_syn[2] = Ra_7_4 ^ Rag_wt_syn[3] ^ Rag_wt_syn[2]; //2
assign Ra_write_syn[1] = Ra_7_4 ^ Ra_3_1 ; //1
assign Ra_write_syn[0] = Ra_7_5 ^ Ra_3_1 ^ Rag_wt_syn[4] ^ Rag_wt_syn[0]; //0
always @(posedge write_clock or posedge fifo_gsr)begin
if(fifo_gsr)begin
Wt_addr_p1 <=#1 0;
end
else begin
Wt_addr_p1 <=#1 write_addr ;
end
end
always @(posedge write_clock or posedge fifo_gsr)begin
if(fifo_gsr)begin
Fifo_status <= 8'h0;
end
else begin //if(!Full)
Fifo_status <= Wt_addr_p1 - Ra_write_syn;
end
end
always @(posedge write_clock or posedge fifo_gsr)begin
if(fifo_gsr)begin
Almostfull <=#1 1'b0;
end
else if (Fifo_status[7:4] == 4'hF)begin
Almostfull <=#1 1'b1;
end
else begin
Almostfull <=#1 1'b0;
end
end
三、几乎空、与几乎满类似,, 由读时钟产生。
四、几乎满相对于写时钟而言,同理几乎空相对读时钟,这样产生的精确度比较高如果几乎满和几乎空用同一个时钟,误差与两个时钟的具体大小相关,比如几乎满和几乎空都是用写时钟得到的,那么几乎满比较精确,如果写时钟比读时钟慢,则几乎空误差较大,如果写时钟比读时钟快,几乎空的误差也比较精确。
读写地址的产生
如果外部写使能(Write_enable )来了,同时FIFO的不满的话,写地址自增1 。如果读使能(Read_enable )有效 ,同时FIFO不空的话,读地址自减1。在进行地址的递增时判断FIFO的空满标志是为了自我保护,避免读写地址交错,产生错误逻辑。
图5 FIFO的自我保护机制
END