跨时钟域同步问题(五)
跨时钟域同步问题(五)
异步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)

直接用格雷码判断写满或读空状态:
读空(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框图

注意这里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技巧
留坑待填

浙公网安备 33010602011771号