IC设计经典题目(1)异步FIFO设计

读了Cummings的论文,对FIFO设计有了一定的掌握,在此进行总结梳理,以便日后复习。

1.FIFO的概念

FIFO 是英文 First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是:没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

2.什么情况下用 FIFO?

一般用于不同时钟域之间的数据传输,比如FIFO的一端是 AD数据采集,另一端时计算机的 PCI 总线,假设其 AD 采集的速率为16 位 100Kbps,那么每秒的数据量为 100K×16bit=1.6Mbps,而 PCI总线的速度为 33MHz,总线宽度 32bit,其最大传输速率为在两个不同的时钟域间就可以采用 FIFO 来作为数据缓冲。
另外对于不同宽度的数据接口也可以用 FIFO,例如单片机位 8 位数据输出,而 DSP 可能是 16 位数据输入,在单片机与 DSP 连接时就可以使用FIFO 来达到数据匹配的目的。

3.同步/异步FIFO

FIFO的分类根均FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO。
同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。
异步FIFO是指读写时钟不一致,读写时钟是互相独立的。

4.FIFO的常见参数

FIFO的宽度:即FIFO一次读写操作的数据位;
FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。

5.二进制码空满检测

异步FIFO的写指针总是指向下一个要被写入的地址,当发生一次写操作时,写指针所指的地址填入数据,然后写指针地址增加。
读指针总是指向当前FIFO中下一个要被读出的数据所在的地址,当发生一次读操作时,读指针所指的地址的数据被取出,然后读指针地址增加。
复位时,读写指针都指向FIFO缓冲区的0地址。

FIFO空的标志发生时是当读的指针和写的指针相等时。
当读写指针复位到0的时候如清空FIFO等操作的时候,或者是当读的指针赶上了写指针
在这里插入图片描述
FIFO满的标志也是当读指针和写指针相等的时候。
当写指针已经环绕一周(wrapped around),然后追上读指针的时候。
在这里插入图片描述

因此就存在一个问题:当读写指针相等的时候,如何判断是读空还是写满

为了区分到底是满状态还是空状态,可以采用以下方法:
在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1(0变1,1变0),其它位回零。对读指针也进行同样的操作。
此时,对于深度为2的n次方的FIFO,需要的读/写指针位宽为(n+1)位;如对于深度为8的FIFO,需要采用4bit的计数器;
0000(0)~0111(7)~1000(8) ~1001(9)~1111(15) ~0000(16)…
MSB作为折回标志位,而低3位作为二进制地址指针。

  • 如果两个指针的MSB不同,而其余位相同,说明写指针比读指针多折回了一圈,写满;如r_addr=0000,而w_addr = 1000。
  • 如果两个指针的MSB相同,且其余位相同,则说明两个指针折回的次数相等,读空

到这里逻辑就很清晰了,就可以去设计FIFO的架构了。如下
架构图1
在这里插入图片描述
架构图2
在这里插入图片描述
winc为写使能;rinc为读使能。
到这里,写指针wptr和读指针rptr是4bit;
wptr经读时钟域二级D触发器同步后成为rq2_wptr,送去读空标志位产生模块与读指针进行比较,进而判断是否为空,从而产生读空标志位rempty;
rptr经写时钟域二级D触发器同步后成为wq2_rptr,送去写满标志位产生模块与写指针进行比较,进而判断是否为满,从而产生写满标志位wfull;
送入RAM的写地址waddr和读地址raddr,是只取写指针wptr、读指针rptr的低三位即可。

但是!!!容易亚稳态!!!
在将读指针发送到写时钟域下进行同步时,如果采用二进制,那么就会面临地址指针同时有多位变化的情况。比如0011->0100,一次就变了3位。在数电的学习中我们知道,这种情况是要尽量避免的,容易引起亚稳态。
在这里插入图片描述
rd_ptr2sync 的3和4、4和5之间的中间态是到各寄存器的时钟rd_clk存在偏差而引起的。
二进制的递增操作,在大多数情况下都会有两位或者两以上的bit位在同一个递增操作内发生变化,但由于实际电路中会存在时钟偏差和不同的路径延时,二进制计数器在自增时会不可避免地产生错误的中间结果。
在这里插入图片描述
由于rd_clk上升沿到达三个寄存器的时间各不相同,这就导致了rd_ptr2sync的值从3’b011跳变3’b100的过程中经历了:3’b011=>3’b111=>3’b101=>3’b100,中间结果经历了3=>7=>5=>4的过程,持续的时间虽然相对短暂,但是这些不正确的中间结果完全有可能被其它时钟域的同步模块采集到而产生错误的动作。
由此可见,要避免中间结果的产生,其中一个可行的方案就是使被同步模块采集的数据递变时,每次只有一个bit位发生改变,即格雷码计数器

6.格雷码空满检测

格雷码一个最大的特点就是在递增或递减的过程中,每次只变化一位,这是它最大的优点。同时它也有自己的局限性,那就是循环计数深度必须是2的n次幂,否则就失去了每次只变化1位的特性。
深度为16的二进制及格雷码递变表如下:在这里插入图片描述

6.1 二进制和格雷码的相互转换
6.1.1 二进制到格雷码

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

6.1.2 格雷码到二进制

在这里插入图片描述
在这里插入图片描述
格雷码转换代码如下:

assign  gray_code = (bin_code>>1)  ^  bin_code;
6.2 格雷码空满检测逻辑

在这里插入图片描述
如上图,是二进制地址码转换成格雷码的框图,格雷码为了具备空满检测的功能,也需要加一位MSB,但检测方法跟二进制有所不同。
在这里插入图片描述

  • 对于“空”的判断依据依然是:写指针和读指针完全相等(包括MSB);

  • 对于“满”的判断:最高位相反 && 次高位相反 && 其余低位相同。

如上图,gray码除了MSB外,上下两部分具有镜像对称的特点,当读指针指向7(0100),写指针指向8(1100)时,除了MSB,其余位皆相同,但并不能说它为满。
因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:最高位相反 && 次高位相反 && 其余低位相同。
如读指针指向7(0100),写指针指向15(1000),此时为满状态。

读空标志产生逻辑:
在这里插入图片描述
读空标志产生时序在这里插入图片描述

写满标志位产生逻辑:在这里插入图片描述
代码如下:

assign full = (wr_addr_gray =={~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]});//高两位不同
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

7. 跨时域比较的同步化操作

跨时钟域的问题:上面我们已经提到要通过比较读写指针来判断产生读空和写满信号,但是读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后再进行比较。

解决方法:两级寄存器同步 + 格雷码

  • 将写时钟域的格雷码写指针同步到读时钟域,将同步后的格雷码写指针与读时钟域的格雷码读指针进行比较产生读空信号
  • 将读时钟域的格雷码读指针同步到写时钟域,将同步后的格雷码读指针与写时钟域的格雷码写指针进行比较产生写满信号

补充:设计的时候读写指针用了至少两级寄存器同步,同步会消耗至少两个时钟周期,势必会使得判断空或满有所延迟,这会不会导致设计出错呢?

异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,将写指针同步到读时钟域再和读指针比较进行FIFO空状态判断,因为在同步写指针时需要时间,而在这个同步的时间内有可能还会写入新的数据,因此同步后的写指针一定是小于或者等于当前实际的写指针,所以此时判断FIFO为空不一定是真空,这样更加保守,一共不会出现空读的情况,虽然会影响FIFO的性能,但是并不会出错,同理将读指针同步到写时钟域再和写指针比较进行FIFO满状态判断,同步后的读指针一定是小于或者等于当前的读指针,所以此时判断FIFO为满不一定是真满,这样更保守,这样可以保证FIFO的特性:FIFO空之后不能继续读取,FIFO满之后不能继续写入。总结来说异步逻辑转到同步逻辑不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

8. 代码与仿真

8.1 单module式
module fifo_async#(
                 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_gray_d1;
reg    [addr_width:0]    wr_addr_gray_d2;
wire   [addr_width:0]    rd_addr_gray;
reg    [addr_width:0]    rd_addr_gray_d1;
reg    [addr_width:0]    rd_addr_gray_d2;


reg [data_width-1:0] fifo_ram [data_depth-1:0];

//=========================================================write fifo 
genvar i;
generate 
for(i = 0; i < data_depth; i = i + 1 )
begin:fifo_init
always@(posedge wr_clk or posedge rst)
    begin
       if(rst)
          fifo_ram[i] <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位。可无
       else if(wr_en && (~full))
          fifo_ram[wr_addr] <= din;
       else
          fifo_ram[wr_addr] <= fifo_ram[wr_addr];
    end   
end    
endgenerate    
//========================================================read_fifo
always@(posedge rd_clk or posedge rst)
   begin
      if(rst)
         begin
            dout <= 'h0;
            valid <= 1'b0;
         end
      else if(rd_en && (~empty))
         begin
            dout <= fifo_ram[rd_addr];
            valid <= 1'b1;
         end
      else
         begin
            dout <=   'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0;
            valid <= 1'b0;
         end
   end
assign wr_addr = wr_addr_ptr[addr_width-1-:addr_width];
assign rd_addr = rd_addr_ptr[addr_width-1-:addr_width];
//=============================================================格雷码同步化
always@(posedge wr_clk )
   begin
      rd_addr_gray_d1 <= rd_addr_gray;
      rd_addr_gray_d2 <= rd_addr_gray_d1;
   end
always@(posedge wr_clk or posedge rst)
   begin
      if(rst)
         wr_addr_ptr <= 'h0;
      else if(wr_en && (~full))
         wr_addr_ptr <= wr_addr_ptr + 1;
      else 
         wr_addr_ptr <= wr_addr_ptr;
   end
//=========================================================rd_clk
always@(posedge rd_clk )
      begin
         wr_addr_gray_d1 <= wr_addr_gray;
         wr_addr_gray_d2 <= wr_addr_gray_d1;
      end
always@(posedge rd_clk or posedge rst)
   begin
      if(rst)
         rd_addr_ptr <= 'h0;
      else if(rd_en && (~empty))
         rd_addr_ptr <= rd_addr_ptr + 1;
      else 
         rd_addr_ptr <= rd_addr_ptr;
   end

//========================================================== translation gary code
assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;

assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

endmodule

在这里插入图片描述

8.2 多module式
顶层模块
module	AsyncFIFO#(
	parameter	ADDR_SIZE = 4,
	parameter 	DATA_SIZE = 8
	)
(
	input		[DATA_SIZE-1:0] wdata,
	input						winc,
	input						wclk,
	input						wrst_n,
	input						rinc,
	input						rclk,
	input						rrst_n,
	output		[DATA_SIZE-1:0]	rdata,
	output						wfull,
	output						rempty
	);

	wire	[ADDR_SIZE-1:0]	waddr,raddr;
	wire	[ADDR_SIZE:0]	wptr,rptr,wq2_rptr,rq2_wptr;

	sync_r2w #(
	.ADDR_SIZE(ADDR_SIZE)
	)
	I1_sync_r2w(
		.wq2_rptr(wq2_rptr),
		.rptr(rptr),
		.wclk(wclk),
		.wrst_n(wrst_n)
		);

	sync_w2r #(
	.ADDR_SIZE(ADDR_SIZE)

	)I2_sync_w2r(
		.rq2_wptr(rq2_wptr),
		.wptr(wptr),
		.rclk(rclk),
		.rrst_n(rrst_n)
		);

	DualRAM #(
	.ADDR_SIZE(ADDR_SIZE),
	.DATA_SIZE(DATA_SIZE)
	)I3_DualRAM(
		.rdata(rdata),
		.wdata(wdata),
		.waddr(waddr),
		.raddr(raddr),
		.wclken(winc),
		.wclk(wclk)
		);

	rptr_empty #(
	.ADDR_SIZE(ADDR_SIZE)
	)I4_rptr_empty(
		.rempty(rempty),
		.raddr(raddr),
		.rptr(rptr),
		.rq2_wptr(rq2_wptr),
		.rinc(rinc),
		.rclk(rclk),
		.rrst_n(rrst_n));

	wptr_full #(
	.ADDR_SIZE(ADDR_SIZE)
	)I5_wptr_full(
		.wfull(wfull),
		.waddr(waddr),
		.wptr(wptr),
		.wq2_rptr(wq2_rptr),
		.winc(winc),
		.wclk(wclk),
		.wrst_n(wrst_n));
endmodule
双口RAM模块
module	DualRAM #(
	parameter		DATA_SIZE = 8,//数据位宽
	parameter		ADDR_SIZE = 4//FIFO地址宽度
	)(
	input		wclken,
	input		wclk,
	input	[ADDR_SIZE-1:0] raddr,
	input	[ADDR_SIZE-1:0] waddr,
	input	[DATA_SIZE-1:0]	wdata,
	output	[DATA_SIZE-1:0] rdata
	);


	localparam	RAM_DEPTH = 1<<ADDR_SIZE;//RAM深度,1左移4位为16

	reg	[DATA_SIZE-1:0] mem [0:RAM_DEPTH-1];//开辟内存

	always@(posedge wclk) begin
		if(wclken==1'b1) begin
			mem[waddr] <= wdata;
		end
		else begin
			mem[waddr] <= mem[waddr];//保持
		end
	end

	assign rdata = mem[raddr];//给地址直接出数据

endmodule
写指针同步到读时钟
module	sync_w2r#(
	parameter		ADDR_SIZE = 4
)
(
	input	[ADDR_SIZE:0] wptr,
	input				  rclk,
	input				  rrst_n,
	output	reg [ADDR_SIZE:0] rq2_wptr
	);

	reg		[ADDR_SIZE:0] rq1_wptr;
	
	//D触发器,两级同步
	always@(posedge rclk or negedge rrst_n) begin
		if(!rrst_n) begin
			{rq2_wptr,rq1_wptr} <=0;
		end
		else begin
			{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
		end
	end

endmodule
读指针同步到写时钟
module	sync_r2w#(
	parameter	ADDR_SIZE = 4)
	(
	input	[ADDR_SIZE:0] rptr,
	input				  wclk,
	input				  wrst_n,
	output	reg [ADDR_SIZE:0] wq2_rptr
	);


	reg		[ADDR_SIZE:0] wq1_rptr;
	//D触发器,两级同步
	always@(posedge wclk or negedge wrst_n) begin
		if(!wrst_n) begin
			{wq2_rptr,wq1_rptr} <= 0;
		end
		else begin
			{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
		end
	end
	
	
endmodule
判空模块

module	rptr_empty#(
	parameter	ADDR_SIZE = 4
	)
(
	output	reg	rempty,
	output		[ADDR_SIZE-1:0] raddr,//输出到RAM的读地址
	output	reg	[ADDR_SIZE:0]	rptr,//输出到写时钟域的格雷码
	input		[ADDR_SIZE:0]	rq2_wptr,
	input						rinc,
	input						rclk,
	input						rrst_n
	);

	reg		[ADDR_SIZE:0]	rbin;//二进制地址
	wire	[ADDR_SIZE:0]	rgraynext,rbinnext;//二进制和格雷码地址
	wire	rempty_val;

//----------------------------
//地址逻辑
//----------------------------

always@(posedge rclk or negedge rrst_n)begin
	if(!rrst_n)begin//
		rbin <=0;
		rptr <= 0;
	end
	else begin //
		rbin <= rbinnext;
		rptr <= rgraynext;
	end
end

//地址产生逻辑
assign rbinnext = !rempty ?(rbin+rinc):rbin;
assign rgraynext = (rbinnext>>1)^(rbinnext);
assign raddr = rbin[ADDR_SIZE-1:0];


//FIFO判空
assign rempty_val = (rgraynext==rq2_wptr) ;

always@(posedge rclk or negedge rrst_n)begin
	if(!rrst_n)
		rempty <= 1'b1;
	else begin
		rempty <= rempty_val;
	end
end

endmodule
判满模块

module wptr_full#(
	parameter	ADDR_SIZE = 4
	)
(
	output	reg					wfull,
	output		[ADDR_SIZE-1:0] waddr,
	output	reg	[ADDR_SIZE:0]	wptr,
	input	    [ADDR_SIZE:0]	wq2_rptr,
	input						winc,
	input						wclk,
	input						wrst_n
	);

	reg	[ADDR_SIZE:0]	wbin;
	wire	[ADDR_SIZE:0]	wbinnext;
	wire	[ADDR_SIZE:0]	wgraynext;

	wire	wfull_val;

	always@(posedge wclk or negedge wrst_n)	begin
		if(!wrst_n)begin
			wbin <= 0;
			wptr <= 0;
		end
		else begin
			wbin <= wbinnext;
			wptr <= wgraynext;
		end
	end			

	//地址逻辑
	assign wbinnext = !wfull?(wbin + winc):wbin;
	assign wgraynext = (wbinnext>>1)^wbinnext;
	assign waddr = wbin[ADDR_SIZE-1:0];
	
	//判满
	assign wfull_val = (wgraynext=={~wq2_rptr[ADDR_SIZE:ADDR_SIZE-1],wq2_rptr[ADDR_SIZE-2:0]});//最高两位取反,然后再判断
	always@(posedge wclk or negedge wrst_n)begin
		if(!wrst_n)
			wfull <=0;
		else begin
		 	wfull <= wfull_val;
		 end 
	end
endmodule
测试文件

module test();

parameter		DATA_SIZE = 16;
parameter		ADDR_SIZE = 16;


reg  [DATA_SIZE-1:0] wdata;
reg           winc, wclk, wrst_n; 
reg           rinc, rclk, rrst_n;
wire [DATA_SIZE-1:0] rdata;  
wire           wfull;  
wire          rempty;  
integer		i=0;

AsyncFIFO #(
.ADDR_SIZE(ADDR_SIZE),
.DATA_SIZE(DATA_SIZE)
)
u_fifo (
               .rdata(rdata),  
               .wfull(wfull),  
               .rempty(rempty),  
               .wdata (wdata),  
               .winc  (winc), 
               .wclk  (wclk), 
               .wrst_n(wrst_n), 
               .rinc(rinc), 
               .rclk(rclk), 
               .rrst_n(rrst_n)
 );
localparam CYCLE = 20;
localparam CYCLE1 = 40;



        //ʱÖÓÖÜÆÚ£¬µ¥Î»Îªns£¬¿ÉÔÚ´ËÐÞ¸ÄʱÖÓÖÜÆÚ¡£
     
            //Éú³É±¾µØʱÖÓ50M
            initial begin
                wclk = 0;
                forever
                #(CYCLE/2)
                wclk=~wclk;
            end
            initial begin
                rclk = 0;
                forever
                #(CYCLE1/2)
                rclk=~rclk;
            end

            //²úÉú¸´Î»ÐźÅ
            initial begin
                wrst_n = 1;
                #2;
                wrst_n = 0;
                #(CYCLE*3);
                wrst_n = 1;
            end
            
             initial begin
                rrst_n = 1;
                #2;
                rrst_n = 0;
                #(CYCLE*3);
                rrst_n = 1;
            end

            always  @(posedge wclk or negedge wrst_n)begin
                if(wrst_n==1'b0)begin
                    i <= 0;
                end
                else if(!wfull)begin
                    i = i+1;
                end
                else begin
                	i <= i;
                end
            end

            always  @(rempty or rrst_n)begin
                if(rrst_n==1'b0)begin                  
                    rinc = 1'b0;
                end
                else if(!rempty)begin                
                    rinc = 1'b1;
                end
                else
                	rinc = 1'b0;
            end
            
always@(wfull or wrst_n)begin
	if(wrst_n)
		winc = 1'b0;
	if(!wfull)
		winc = 1'b1;
	else
		winc = 1'b0;
end
always@(*)begin
  if(!wfull)
    wdata= i;
  else
    wdata = 0;
end  
endmodule

在这里插入图片描述

9. 参考声明

参考以下博客文章
http://bbs.eetop.cn/thread-308169-1-1.html

https://blog.csdn.net/MaoChuangAn/article/details/88783320?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-5.nonecase

https://blog.csdn.net/alangaixiaoxiao/article/details/81432144?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

https://blog.csdn.net/weixin_46022434/article/details/105348433?ops_request_misc=&request_id=&biz_id=102&utm_term=异步fifo设计&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-105348433

  • 0
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值