跨时钟域同步问题(五)

跨时钟域同步问题(五)

异步FIFO

1.格雷码同步FIFO读写指针

FIFO 作为一个存储管道,有写入口和读出口,如果是同步FIFO,那么写入和读出就是在同一个时钟域下,写入(wp)读出(rp)指针每次写入或读出一个数据后都会加一,所以如果FIFO空,则wp==rp,如果FIFO慢,则wp-rp=full

所以异步FIFO同样,其核心也是空满判断。对于读出端,关心FIFO是否是空,而对于写入端则关心FIFO是否为慢,因此,我们在push一端更新wp,在pop一端更新rp。同时,我们需要通过指针比较来判断空满,因此在读出端比较是否空时,需要把另一侧的wp指针同步过来,放在一个时钟域下进行比较,反之亦然。

我们当然可以用load模块做反馈来同步pointer,但是反馈需要跨两次时钟对效率有影响,对整体性能影响大。

因此我们选择用格雷码来做wp与rp的同步。

格雷码最大的特征就是,相邻两个数之间跳转时,有且仅有一位(1bit)发生变换。由于指针变化在各自时钟域上也是每一时钟周期+1,即不会出现多bit的跳转,只有1bit发生变换,因此即使在同步时发生了亚稳态,最多也只会在目标时钟域上延迟两个周期,不会出现数据错误的现象。

对于格雷码计算有两种方法:异或公式发与图表推导法.

bnary to Gray
g(3) = b(3) ^ 0
g(2) = b(3) ^ b(2)
g(1) = b(2) ^ b(1)
g(0) = b(1) ^ b(0)
g(n) = b(n+1) ^ b(n)
Gray to Binary
b(3) = g(3)
b(2) = g(3) ^ g(2)
b(1) = g(3) ^ g(2) ^ g(1)
b(0) = g(3) ^ g(2) ^ g(1) ^ g(0)

image

直接用格雷码判断写满或读空状态:

读空(wp与rp相同):
assign empty =(write_ptr_gray[N:0]==read_ptr_gray[N:0])

写满(wp与rp最高两位相反,其余位相同):
assign full = (write_ptr_gray[N:N-1] == ~read_ptr_gray[N:N-1]) &&(write_ptr_gray[N-2:0] == read_ptr_gray[N-2:0]);

2.异步FIFO设计
异步FIFO框图
image

注意这里wptr与rptr都是格雷码(gray code),可以直接利用2flop synchonizer进行同步,但是对于memory中进行实际读写时必须用二进制地址(binary address),在FIFO write control和FIFO read control里面我们进行binary to gray code 的转换。

// fifo memory 部分的简单实现
module fifomem #(parameter DATASIZE = 8,//memory data word width
			     parameter ADDRSIZE = 4)//number of mem address bits
	   (output [DATASIZE-1:0] rdata,
	    input  [DATASIZE-1:0] wdata,
		input  [DATASIZE-1:0] waddr,raddr,
		input  [DATASIZE-1:0] wen,wfull,wclk);
		
		`ifdef VENDORRAM  //conditional compilation
		//instantiation of a vendor's dual-port RAM
		vendor_ram mem(
					.dout(rdata),
					.din(wdata),
					.waddr(waddr),
					.raddr(raddr),
					.wen(wen),
					.wclken_n(wfull),
					.clk(wclk))
		`else
		// RTL Verilog memory model
		localparam DEPTH = 1 << ADDRSIZE
		reg [DATASIZE-1:0] mem [0:DEPTH-1];
		assign rdata = mem[raddr];
		always @(posedge wclk)
			if(wen && !wfull) mem[wadder] <= wdata;
		`endif
endmodule

这段code简化了rdata的逻辑,如果使用SRAM可能需要加一级flop来存储SRAM读出的值。

这里有一个留藏的问题,即FIFO的深度,这是一个重要的问题。另一个问题是关于使用SRAM和flop array的cost。一般情况下根据经验当存储位大于2kbit使用vedor提供的compile memory在面积上比较划算,低于2kbit,用flop array划算。

//rp->wclk同步
module sync_r2w #(parameter ADDRSIZE = 4)
		(
		output reg[ADDRSIZE:0] rptr_wclk,
		input 	  [ADDRSIZE:0] rptr,
		input				   wclk,wrst_n
		);
		
		//reg[ADDRSI:0] rptr_wclk1;
		//
		//always(posedge wclk or negedge wrst_n)
		//	if(!wrst_n)
		//		{rptr_wclk,rptr_wclk1} <= 0;
		//	else
		//		{rptr_wclk,rptr_wclk1} <={rptr_wclk1,rptr};
				
		sync_lib #(.STAGE(2),.WIDTH(4))
				  (
				  .d (rptr),
				  .q (rptr_wclk),
				  .clk (wclk),
				  .rstn (wrst_n)
				  );
endmodule

以上代码是rp向wclk时钟域同步的代码,通常情况下可以直接调用cdc库进行例化。

//FIFO write control
module writecontrol #(parameter ADDRSIZE =4)
		(
		input wen,
		input wclk,
		input wrst_n,
		input [ADDRSIZE:0] rptr_wclk,
		output reg [ADDRSIZE:0] wptr,
		output reg wfull,
		output[ADDRSIZE-1:0] wadder
		)
	reg [ADDRSIZE:0] wbin;
	wire [ADDRSIZE:0] wgraynext,wbinnext;
	wire wfull_val;
	//glaywp and waddress
	always@(posedge wclk or negedge wrst_n)
	if(!wrst_n) {wbin,wptr} <= 0;
	else        {wbin,wptr} <= {wbinnext,wgraynext};
	
	//output of wadder
	assign wadder = wbin[ADDRSIZE-1:0];
	//logic of wadder
	assign wbinnext = wbin + (wen & ~wfull);
	//logic of binary to gray
	assign wgraynext = (wbinnext>>1) ^ wbinnext; 
	
	// tans of wfull
	always@(posedge wclk or negedge wrst_n)
	if(!wrst_n) wfull <= 0;
	else        wfull <= wfull_val;
	
	//logic of wfull
	assign wfull_val = 	(wgraynext=={~rptr_wclk[ADDRSIZE:ADDRSIZE-1]:
						rptr_wclk[ADDRSIZE-2:0]});
endmodule

以上是write control部分的RTL代码实现,这里关于写满的的逻辑判断为读指针和写指针的最高两位相反,其余位相同,则此时可以判断FIFO已写满。

//FIFO read control
module readcontrol#(parameter ADDRSIZE = 4)
		(input rclk,rpop,rrst,
		 input [ADDRSIZE] wptr_rclk,
		 output[ADDRSIZE] rptr,
		 output[ADDRSIZE-1] raddr,
		 output reg rempty
		);
	    reg [ADDRSIZE] rbin;
		wire [ADDRSIZE] rbinnext;
		wire [ADDRSIZE] rgraynext;
		wire rempty_val;
		
		//rbin and gray tans
		always@(posedge rclk or negedge rrst)
		if(!rrst) {rbin,rptr} <= 0;
		else      {rbin,rptr} <= {rbinnext,rgraynext};
		
		assign rbinnext = rbin + (rpop&&!rempty);
		assign rgraynext = (rbinnext ^ rbinnext>>1);
		assign raddr = rbin[ADDRSIZE-1:0];
		
		//empty判断
		always@(posedge rclk or negedge rrst)
		if(!rrst) rempty <= 1;
		else      rempty <= rempty_val;
		
		assign rempty_val = (wptr_rclk == rgraynext);

endmodule

几个问题:
1.假设wclk速度比rclk快,那么当raddr+1,再同步到wclk后,如果这期间有了push操作,那会不会使得wptr超过了rptr,造成FIFO overflow呢?如何判断FIFO是真空/真满呢?

答:这类问题的本质就是读写指针的跨时钟周期同步存在延迟,因此无论是当前状态下是在读还是写,都不会对对面时钟域的状态变量(full/empty)产生影响,因此就会出现假空或假满,但这并不会影响准确性,只会在性能上造成一些损失,特别是当FIFO深度较大时并不是什么问题。相应的,如果确实要判断真满或真空,则需要在push端去判断空,在pop端判断满。

2.设计一个depth=1的异步FIFO
答:如果只是depth=1的异步FIFO,那么不再需要用格雷码指针,直接用地址信号0和1即可。带反馈的asynchronous load即为depth=1的异步FIFO.

3.如何设计depth不是2的幂次的异步FIFO
答:利用格雷码的对称轴特点,比如对于四位的格雷码,7.8之间就为对称轴,可以根据需要进行选取,注意到我们在用格雷码做指针时其位数要比FIFO地址高一位,其目的主要是方便记圈从而判断空满。
对于depth等于非2次幂的情形,在判断其满状态时,需要先用二进制数比较其大小,较小的数在二进制数的前提下-1,然后再转换为格雷码进行最高两位相反其余位相同的判断。

3.CDC工具使用
1.CDC检查工具
Synopsys Cadence Mentor三大EDA公司都有各自的cdc检查工具。
其中synopsys的sypglass CDC最为常用
(具体使用方法和细节留坑待填)

2.工程经验

  • 设计前要三思,是否真的需要跨时钟域?
    能否不夸时钟域实现?能否将逻辑简化将多bit信号转化为单bit信号剩下synchronizer。
  • 一个module中包含一个clock domain
  • 同步时不能根据source的频率和destination clock的频率关系来同步,要假定它们之间的关系是任意的,即不同时钟域的相位是无法获知的
  • 通过直接例化已经验证好的底层模块避免重复造轮子
  • 所有IP的output必须来自一个register,禁止将组合逻辑的输出当作IP的输出。
  • 一个信号同步到另一个时钟域只能同步一次,禁止同步多次,否则就会有coherency的问题
  • 跨时钟域信号要标记clk
  • 在功能仿真验证中,testbench要对2flop synchoronize来deposit随机的值,来模拟真实值可能出现在一个周期后或两个周期后
  • 随机clk的相位差
  • sypglass CDC技巧
    留坑待填
posted @ 2021-03-25 21:32  disagreements  阅读(509)  评论(0)    收藏  举报