仔细观察

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

这篇文章是《Simulation and Synthesis Techniques for Asynchronous FIFO Designs》的一些总结。异步FIFO可以用于数据的跨时钟域传输,FIFO即First In First Out,先入先出。我的理解下FIFO就是一个暂存数据的memory
image

Full & Empty

数据从IN端进入从OUT端读出并且遵循先入先出的原则。很容易想到这个FIFO要工作起来要解决两个问题

  1. 什么时候可以写数据
  2. 什么时候可以读数据

如果直接回答这个问题,那就是当buffer没有满的时候就可以写,当buffer没有空的时候就可以读,于是问题就转换为了如何判断“满”和“空”。不管是用memory器件还是完全用寄存器来实现buffer,数据的访问都离不开地址,也就是要通过地址来决定FIFO的状态。下面就来讨论如何解决这个问题。先做两个假设

  • 写地址对应下次要写入数据的地址,即写地址指向的内存在时钟沿到来前还没有被写入数据
  • 读地址对应现在要读的数据的地址,即读地址指向的内存已经是有效的了

这两点其实是可以改变的,这里只是约定一下。在这个规则下一个FIFO的状态可以用下面的图来示意
image
假设buffer一共有16个地址,红色的代表允许写的地址,绿色的代表允许读的地址。当两个地址处于下面的关系时代表buffer被写满了,此时不能再写否则就会丢数据
image
这个时候如果再写的话3位置的还没有被读取的数据就会被覆盖掉造成数据丢失。当两个地址处于下面的关系时代表buffer被读空了,此时不能再读否则会读出重复或者未知的数据
image
对比这两个图可以发现一个有意思的问题,buffer被写满和读空时都是两个地址相等,那应该如何区分呢?
注意到虽然最终结果都是两个地址相等,但是它们达到相等的过程是不一样的
image
可以发现写满是写地址循环了一圈追上了读地址,而读空是读地址在同一圈内追上了写地址。明确这一点之后对于如何判断FIFO的状态就变得容易了。这里提出的一种方式是给地址增加一位来标记读地址和写地址是否在同一圈内,例如写满的情况发生时,写地址会发生“溢出”导致其MSB改变,这样通过比较最高位是否相等就可以判断两者是否在同一圈内,然后通过比较剩余位是否相等来判断FIFO的状态。
Clifford这篇论文中提到的FIFO结构在判断Full和Empty这里的一个思想是“谁使用谁维护”。比如empty这个信号是读端使用的那这个信号就在读时钟域中被驱动,同理full信号要在写时钟域中被驱动。这里就出现了一个问题,也是异步电路设计要考虑的问题:亚稳态Metastability。关于亚稳态我打算在后面在写文章总结,这里就简单的认为数据跨时钟域传输很容易出现问题。例如我们要产生empty信号,那我们就需要比较读地址和写地址,按照之前的规则,读地址就是在读时钟域内产生的,这个没什么问题,但是写地址是在写时钟域内产生的,直接用读时钟来采样就会有问题,并且地址一般是多位的,这种多位信号跨时钟域直接采样是不行的,因为会涉及多个比特的高低电平同时转换,比如一个4比特的地址0x0011变为0x0100就设计到3位同时转换。那有没有一种地址编码方式是每次只变化1比特的呢?

格雷码

格雷码的特点就是相邻的两个数之间只有1比特不同,并且从二进制编码到格雷码是非常容易实现的
image

FIFO

格雷码这么好,能不能直接用来当读/写地址?可以也不可以。地址无非就是为了索引数据,只要不重复就能当地址,所以可以用格雷码。但是也有一些问题:
首先,格雷码不好直接确定大小关系,比如5和7的四位格雷码分别是0111和0100,并且格雷码不方便进行加减法运算,这样地址的加减就不好操作。
第二点也是需要注意的一点就是,按照前面的描述地址需要额外的一位来标记读地址和写地址是否在一圈内,但是如果用格雷码作为地址的话就会出现问题,如下图
image
假设有效地址是3比特,当地址增加到0100之后继续变化会变为1100,按理来说这个时候其实真正的地址是第一个位置,即0000这个位置,但是这时候去掉MSB之后的剩余位并不相等(100和000),这就会导致判断FIFO的状态时出问题。那怎么办呢?其实也很简单,在判断是否读空时就是直接判断读格雷码地址和写格雷码地址是否相等;在判断是否写满时,必须要求读格雷码地址和写格雷码地址的最高两位是不同的,然后剩余位是相同的。到这里怎么构建Full和Empty电路其实就有一些眉目了,下面是论文中提到的结构
image
首先地址的累加部分仍然是用二进制码来做的,这一点没有其他办法;然后通过二进制码格雷码转换来产生格雷码地址。作为memory的地址用二进制码和格雷码其实都是可以的,这里选择用二进制码,这样如果要产生一些其他信号会容易一些,然后用来进行读空和写满判断的需要跨时钟域的用格雷码来做。下main贴一下我的代码,以full产生电路为例

module wfull(full,waddr,wptr,wclk,wen,wrstn,rptr);
	parameter A_SIZE = 4;
	output full;
	output [A_SIZE-1:0] waddr;
	output [A_SIZE:0] wptr;
	input wclk,wsrtn;
	input [A_SIZE:0] rptr;
	
	reg [A_SIZE:0] wrptr2,wrptr1;
	reg [A_SIZE:0] bin_reg,gray_reg;
	wire [A_SIZE:0] bin_next,gray_next;
	reg full_reg;
	assign bin_next=bin_reg+(wen&~full);
	assign gray_next=(bin_next>>1)^bin_next;
	always@(posedge wclk, negedge wrstn)
		if(!wrstn)
			{bin_reg,gray_reg} <= 0;
		else
			{bin_reg,gray_reg} <= {bin_next,gray_next};
	assign waddr = bin_reg[A_SIZE-1:0];
	assgin wptr=gray_reg;
	always@(posedge wclk, negedge wrstn)
		if(!wrstn)
			{wrptr2,wrptr1}<=0;
		else
			{wrptr2,wrptr1}<={wrptr1,rptr};
	always@(posedge wclk, negedge wrstn)
		if(!wrstn)
			full_reg<=0;
		else
			full_reg<=(wptr[A_SIZE]!=wrptr[A_SIZE])&&(wptr[A_SIZE-1]!=wrptr[A_SIZE-1])&&(wptr[A_SIZE-2:0]!=wrptr[A_SIZE-2:0]);
	
	assign full=full_reg;
endmodule

empty产生电路的原理与之类似。这里rptr是读电路时钟域产生的信号,采用寄存器打两拍的方式来进行同步。整个FIFO的电路结构如下图
image

什么语法错误就不说了,说几个我写rtl和testbench时碰到的错误

  1. full和empty的初始值问题。full和empty都是寄存器打一拍再输出的,然后也都用了异步复位,不过复位值就很有意思了
  2. testbench的send端到底何时可以发送数据,何时应该停止

贴一下自己的代码(甚至都没有分目录,因为github怎么用token提交还没看)
FIFO

posted on 2021-12-31 01:00  注意看  阅读(98)  评论(0编辑  收藏  举报