赞
踩
本文参考Cummings论文、《硬件加速设计》MOOC课程、很多博客汇总而成,图片基本来自Cummings论文,一些细节为个人理解,仅供参考。
数据流和指示信号不同之处:数据流大多具有连续性,即背靠背传输;数据流要求信号具有较快的传输速度。通常解决方法有两种——两个时钟域通过sram来缓冲;FIFO。
FIFO含义:first in first out,先进先出的存储结构。
FIFO与普通存储器的区别:没有外部读写地址线,使用起来简单,但缺点是只能顺序写入数据,顺序读出数据,数据地址由内部读写指针自动加1完成,不像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
FIFO用途:跨时钟域快速传输实时数据;匹配不同数据宽度的接口。
FIFO分类:同步FIFO,异步FIFO。
FIFO参数:宽度、深度、满标志、空标志、读时钟、写时钟。
注意FIFO深度过小,则总是接受满信号,使得系统的数据吞吐量降低。如果过大,则浪费空间太多。FIFO深度是一个很关键的参数。
同步FIFO和异步FIFO的区别:同步FIFO的读写时钟是相同的,异步FIFO的读写时钟是不同的。也有人理解为同步FIFO的读写时钟是同步的,异步FIFO的读写时钟是异步的。由于异步FIFO读写时钟不同,因此生成空满标志的方式与同步FIFO不同,同步FIFO读写指针在相同时钟域下产生,可直接比较来产生空满信号,异步FIFO的读写指针在不同时钟域下分别产生,不能直接比较产生空满信号,所以要判断空满就要先异步处理,将读指针同步到写时钟域和写指针比较来产生满信号,将写指针同步到读时钟域和读指针比较来产生空信号。因此才涉及到格雷码和两级同步器的使用。
异步FIFO适用于对性能要求较高的设计中,尤其是时钟延迟比系统资源更重要的环境中。
FIFO工作原理:
什么时候写入?什么时候读出?读写指针如何工作?空满状态的作用?空满标志怎么判断?
写信号有效时,数据将被写入FIFO,由FIFO内部写指针控制,并在FIFO内部,写指针递增一个单元,FIFO满信号控制是否发送数据。读信号有效时,数据被读出,由FIFO内部读指针控制,并在FIFO内部读指针递增一个单元,FIFO空信号控制是否读出数据。读、写指针都指向下一个要读、写的地址。因此在时钟到来时可以读/写原来地址的值,并同时递增读/写指针。读写指针读写到最高地址后会重新从零地址开始读写,这对空满标志的判断有作用。
正确产生空满标志对FIFO设计很重要。空满状态产生的原则:写满而不溢出,读空而不多读。空标志阻止读操作防止读出无效数据,满标志阻止写操作防止造成溢出。空满标志通过比较读写指针得到。读写指针相等时可能是空,比如进行复位操作或读指针独处FIFO最后一个字追赶上写指针。读写指针相等也可能是满,比如写指针转了一圈折回来追上读指针。读写指针相等时可能是空也可能是满,如何区分?在地址中添加一个额外的位,读/写指针增加到最高地址后,将该读、写指针的MSB加1,其他位回零。若读写指针的MSB相同,其他位也相同,则FIFO为空;若读写指针的MSB不同,其他位相同,则写指针比读指针多折回一次,FIFO为满。
空满标志通过比较读写指针来生成,异步FIFO读写指针在不同的时钟域下产生,所以要进行同步处理,要把读指针传递到写时钟域,把写指针传递到读时钟域,这是因为读时钟域实时生成空标志来控制FIFO不空读,写时钟域实时生成满标志来控制FIFO不满写,否则,以满标志为例,写指针同步到读时钟域已经晚了两个读时钟周期,而这段时间写指针仍在递增的话,即使同步后的写指针和读指针比较能生成满信号,FIFO也已经因为写指针满写而丢失数据,对空标志的生成同理。能否用二进制指针传输?不能,因为二进制指针通常位宽超过1bit,多bit信号不能直接用两级同步器,多bit信号可能同时跳变,而如果此时采样时钟沿到来,采样和同步到的值可能是预期外的值,导致触发错误的满或空标志,甚至不会触发真正的满或空标志,导致数据因FIFO溢出而丢失,或在FIFO真正为空时试图读取数据而导致从FIFO读取伪数据。举个例子,写指针7→8为0111→1000,所有位都更改,那可能跳变时读时钟采样到的不止新值和旧值,可能是任意4bit值,这是由于地址总线长度不同造成地址总线的偏移,各bit不同时跳变,而是先后跳变,另一个原因是写指针跳变时采样时钟沿正好到来,导致亚稳态的问题,无法确定某bit同步后的值是0还是1。
解决方法:用格雷码传输。为什么用格雷码传输?格雷码的特点?
格雷码的特点是:
①格雷码相邻的2个数值间只有一位发生变化,其余各位相同;
②格雷码是一种循环码,0和最大数(2n-1)间也只有一位不同。
引入格雷码后,相邻数值只有1bit发生翻转,引起亚稳态的概率远小于几位同时翻转所引起的概率。两级同步器进一步降低亚稳态发生的概率。引入格雷码后就相当于数据每次只改变1位,能用单bit跨时钟同步方法两级同步器传递过去,误码概率和单bit信号的跨时钟域同步的概率一致。若用格雷码传输,且同步时钟信号出现在格雷码计数器转换的中间,同步后的值将是旧值或新值。同步了新值那地址同步就正确,同步的旧值那就同步出错,但只有1bit出错。对于地址同步出错的情况,FIFO仍能正常工作,不会发生写满溢出、读空多读的情况。也就是说保持FIFO能写满不溢出、读空不多读更重要,在实际非空、非满情况下产生空满信号不会造成FIFO功能错误,但在实际为空、为满时不产生空满信号会造成FIFO功能错误。举个例子,写地址从000→001,同步到读时钟域时同步出错,写地址000→000,即地址未跳变,但用这个错误的写地址去做空判断也不会出错,最多让空标志在FIFO不是真正空的时候产生,但不会出现空读的情况。所以格雷码保证了即使同步后的读写地址出错也能使FIFO正确工作,当然同步后的读写地址出错总是存在的。
格雷码可以解决异步FIFO读/写地址采样不稳定的问题,提高系统的可靠性;缺点是有延迟(可以提前判断来补偿),电路门数、复杂度增加。
格雷码如何判断空满?
判断空的依据是读写指针完全相等,判断满与二进制指针的依据不同,不能仅靠最高位不同、其余位相同就判定为满。之前靠最高位不同、其余位相同判断为满是因为其余位对应FIFO读写地址,读写地址相同说明要么是空要么是满,此时二进制码的最高位不同说明写指针比读指针多折回一次,可判断为满,相同则为空。但是格雷码是由二进制码转换而来,格雷码的最高位依然可以用来判断是否写指针是否比读指针多折回一次,但其余位对应的不是真实的读写地址。举个例子,比如深度为8的FIFO,读指针为7(0100),写指针为8(1100),虽然两个指针的最高位不同,其余位均为100,但实际上不是满,格雷码0100对应100地址,格雷码1100对应000地址,因为写到最高地址要重新从零地址开始写。那用格雷码指针要怎么来判断满信号呢?由下图可以看出,格雷码是反射码,镜像对称。将格雷码指针8-15中的次高位翻转,然后后三位作为地址编码,则地址0-7依次编码为000、001、011、010、110、111、101、100。所以格雷码判断满信号对写指针和同步过来的读指针有三个条件:①最高位不同,也就是写指针必须比读指针多折回一次;②次高位不同;③其余位相同。
- //complex writing
- //assign wfull_val = ((wgnext[ADDRSIZE] !=wq2_rptr[ADDRSIZE] ) &&
- // (wgnext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1]) &&
- // (wgnext[ADDRSIZE-2:0]==wq2_rptr[ADDRSIZE-2:0]));
-
- assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]});
-
- always @(posedge wclk or negedge wrst_n)
- if (!wrst_n) wfull <= 1'b0;
- else wfull <= wfull_val;
格雷码空满判断方法的特点?
空满判断的方法比较保守,保证了FIFO在空满极限情况下,依然留有余量,存在一定的冗余空间。这种方法使得FIFO不会发生写满溢出、读空多读的情况。对于满信号,当写指针和同步后的读指针满足满生成条件时,实时生成满标志,阻止写操作。但即使生成满标志,此时实际可能也不是满的,因为同步两个写时钟周期的时间内读指针仍可能递增,同步后的读指针小于等于当前实际读指针,导致同步到写时钟域的时候实际已经被读走了部分数据,但这种情况下仍然阻止了写操作,不会发生溢出的情况。撤销满标志可能晚几个时钟周期,这同样是因为要将指针同步造成晚两个接受时钟周期变化,导致晚几个时钟周期才写入新数据。所以说保守生成满标志。对于空信号,同理。
请注意,如果两个指针同时递增,则设置满标志或空标志可能不太准确。例如,如果写入指针赶上同步读取指针,将设置满标志,但如果读取指针与写入指针同时递增,则满标志将提前设置,因为读取操作与“写满”操作同时发生,但读取指针尚未同步到写入时钟域导致FIFO并非真正满。满标志的设置有点太早,也有点悲观。
格雷码和二进制转换方法:
1、gray-to-binary转换器:
方法1:为了将格雷码值转换为等效的二进制码值,以n位格雷码值为例,二进制位0等于格雷码位0与1到n的所有其他格雷码位异或。二进制位1等于格雷码位1与2到n的所有其他格雷码位异或,最重要的二进制位正好等于最重要的格雷码位(最高有效位)。利用for-loop编码,对可变索引范围的格雷码向量进行异或递减操作,每次索引范围的最低有效位增大。
- module gray2bin_bad(bin,gray);
- parameter SIZE=4;
- output [SIZE-1:0] bin;
- input [SIZE-1:0] gray;
- reg [SIZE-1:0] bin;
- integer i;
-
- //Syntax error-variable index range
- always@(gray)
- for(i=0;i<SIZE;i=i+1)
- bin[i]=^(gray[SIZE-1:i]);
- endmodule
不幸的是,Verilog不允许使用变量索引范围进行部分选择(part select),因此示例1中的代码虽然概念正确,但不会编译。
另一种考虑格雷码到二进制转换的方法是使用填充的0进行格雷码位的异或。
这个例子在语法上是正确的,可以编译并运行。
- module gray2bin(bin,gray);
- parameter SIZE=4;
- output [SIZE-1:0] bin;
- input [SIZE-1:0] gray;
- reg [SIZE-1:0] bin;
- integer i;
-
- always@(gray)
- for(i=0;i<SIZE;i=i+1)
- bin[i]=^(gray>>i);
- endmodule
binary-to-gray转换器:
- module bin2gray(gray,bin);
- parameter SIZE=4;
- output [SIZE-1:0] gray;
- input [SIZE-1:0] bin;
-
- assign gray=(bin>>1)^bin;
- endmodule
对二进制到格雷码转换器进行编码的最简单方法是对一个简单的连续赋值进行编码,该赋值在二进制向量和同一二进制向量的右移版本之间执行逐位异或运算,如例3所示。这个例子在语法上是正确的,可以编译并运行。
格雷码计数器的参数化Verilog模型
- module graycntr(gray,clk,inc,rst_n);
- parameter SIZE=4;
- output [SIZE-1:0] gray;
- input clk,inc,rst_n;
- reg [SIZE-1:0] gnext,gray,bnext,bin;
- integer i;
-
- always@(posedge clk or negedge rst_n)
- if(!rst_n) gray <= 0;
- else gray <= gnext;
-
- always@(gray or inc)begin
- for(i=0;i<SIZE;i=i+1)
- bin[i] = ^(gray>>i);
- bnext = bin + inc;
- gnext = (bnext>>1) ^ bnext;
- end
-
- endmodule
如果用格雷码和两级同步器替代异步FIFO传递多bit数据信号,行不行?也就是说把要传递的数据先转换成格雷码,再用两级同步器同步,这样能不能传递多bit数据信号?
不行。首先数据是随机的,不管是值还是数据个数,无法转换成相邻两个值之间只有1bit不同的格雷码,这种传递方式是不能实现的;第二,快到慢时钟域传输是会丢失数据的,无法完整地把数据跨时钟域传输;第三,即使用格雷码和两级同步器,依然可能出现同步后的值不是新值,那数据就丢失了。
由于异步FIFO是从两个不同的时钟域计时的,显然时钟运行的速度不同。将更快的时钟同步到较慢的时钟域时,会跳过一些计数值,因为更快的时钟会在较慢的时钟边缘之间半周期性地增加两次。这引发了对以下两个问题的讨论:
第一个问题。注意,增加两次但仅采样一次的被同步的格雷码将显示被同步值中的多位变化,这会导致多位同步问题吗?
答案是否定的。只有当多个位在同步时钟上升沿附近发生变化时,同步多个位的变化才是一个问题。在较慢的同步时钟边沿之间,格雷码计数器可能会增加两次(或更多),这意味着第一次格雷码更改将在较慢时钟的上升边沿之前发生,并且只有第二次格雷码转换可以在上升时钟边沿附近更改。格雷码计数器不存在多位同步问题。
个人理解:虽然快时钟域的格雷码计数器可能在慢时钟边沿间计数多次,导致看上去好像被同步到慢时钟域的格雷码值之间有多位变化,但实际同步器输入指针的格雷码值每次都只变化一位,而且是先后变化的,所以即使在格雷码值变化时被慢时钟采样也只会有一位变化,不会发生multi-bit同步问题。只有慢时钟采样时格雷码值多位同时变化才会造成multi-bit同步问题。
第二个问题。再次注意,更快的格雷码计数器可能会在较慢时钟信号的上升沿之间增加不止一次,在检测到full(满)之前,更快时钟域的格雷码计数器是否可能会增加到full状态和full+1状态,从而导致FIFO溢出,而没有意识到FIFO曾经是满的?(这个问题同样适用于FIFO空)
答案是不。首先考虑FIFO满的生成。当写指针赶上同步读指针,并且在写时钟域中检测到FIFO满状态时,FIFO满。如果wclk域比rclk域快,写指针最终将赶上同步读指针,FIFO将满,wfull位将被设置,FIFO将停止写入,直到同步读取指针再次增大。写指针不能超过wclk域中的同步读指针。
对空标志的类似检查表明,当读指针赶上同步写指针时,FIFO变空,并且在读时钟域中检测到FIFO空状态。如果rclk域比wclk域快,读指针最终将赶上同步写指针,FIFO将为空,rempty位将被设置,FIFO将停止读取,直到同步写指针再次增大。读指针无法超过rclk域中的同步写指针。
使用这种实现,当FIFO变满或变空时,会准确地将“满”或“空”置为有效。撤销“满”和“空”状态是保守的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。