在《时钟与复位》一文中已经解释了亚稳态的含义以及亚稳态存在的危害。在单时钟系统中,亚稳态出现的概率非常低,采用同步设计基本可以规避风险。但在实际应用中,一个系统往往包含多个时钟,且许多时钟之间没有固定的相位关系,即所谓的异步时钟域,这就给设计带来很大的挑战。
01跨时钟域类型
跨时钟域传输可分为以下几种情况:
同频同相时钟可视为等效的时钟,在不考虑时钟抖动(Jitter)影响的情况下,两个时钟域之间的传输可视作同步的。而同频恒定相位差的时钟由于时钟有效沿之间有一定的相位差,因此对建立/保持时间裕量有一定的影响。设计时需要确保组合逻辑的延时能满足采样处的建立/保持时间要求,一般可通过在发射沿或捕获沿加入适当的偏移来完成时序的调整。
而对于非同频的时钟,二者可以是同步关系,也可以是异步关系,这里再次强调一下同步与异步的界定标准:有固定相位关系的称为同步时钟,没有固定相位关系的称为异步时钟。
那么什么叫固定的相位关系?即两个时钟的有效沿之间的偏移量始终保持恒定,也即所谓的相位差固定。
非同频时钟之间的关系可以分为整数倍频和非整数倍频。整数倍频的时钟往往是同源时钟,相位关系也往往是固定的。但是存在源时钟域和目标时钟域的快慢关系。假设源时钟是20MHz,而目标时钟域只有5MHz,那么就是快时钟域到慢时钟域的传输。
“慢采快”会导致数据丢失的问题,为了避免这个问题,要求源时钟域的数据至少保持一个目标时钟域的周期的稳定状态。对于单比特控制信号的“快→慢”传输,可以采用如图所示的脉冲同步器来检测快时钟域的脉冲信号。
一旦data1有脉冲拉高,经过mux的反馈,第一级DFF的输出就会反转并锁住,直到下一个data1脉冲的到来才会再次反转。中间两级DFF就是用来打两拍消除亚稳态的,最后一级DFF输出的异或用来检测同步过来的信号是否发生反转,由于data1每次脉冲都会导致clk1时钟域的输出发生反转,于是该脉冲信号就在clk2时钟域被记录下来并保持一个周期的稳定状态。
那么假如源时钟比目标时钟要慢呢?在“快采慢”的情况下,需要注意快时钟域触发器不要对慢时钟域的输出信号进行重复采样,在慢时钟域维持一个周期的信号在慢时钟域可能维持好几个周期,这显然会导致逻辑功能的错误。因此需要确保同步过来的信号在快时钟域也只维持一个周期,因此采样如图所示的边沿(上升沿)同步电路,就能将慢时钟域的脉冲同步到快时钟域。
(此法只用于慢时钟域的单周期脉冲信号的同步,若源信号存在维持几个周期的高电平,在快时钟域也只会存在一个周期的脉冲信号,此时用基本的电平同步电路就可以了)
非整数倍频的时钟的之间相位关系总是在变化,这让时序分析变得很复杂,除了亚稳态的问题,还要考虑相位差的变化是否会导致丢数据,即便能满足时序,数据传输不连贯的问题也始终存在。
此时,已经标准化的技术,如FIFO和握手,解决了绝大多数情况下的跨时钟域数据传输。
02FIFO
关于握手机制,笔者在《解剖AXI(一)—— 从握手开始》一文中已有说明,本文不再过多赘述。在实际设计中,使用握手机制来传输单比特信号产生的延迟要远大于使用FIFO进行传输,因此本文重点介绍FIFO传输的机制,以及同步FIFO和异步FIFO的架构。
FIFO,即First In First Out,先进先出的数据缓存器。可类比为排队,先进队伍的先服务,先写入FIFO的数据先读出。所以FIFO没有外部地址线,只能靠回滚递增的内部读写指针来指示下一个要读/写的地址。根据读写控制信号的时钟关系,又可分为同步FIFO和异步FIFO。
2.1 同步FIFO
同步FIFO的写时钟和读时钟为同步时钟,一般情况下为同一个时钟。同步FIFO内的所有逻辑都是同步逻辑。一个最简单的同步FIFO框图如下所示。
产生FIFO空满信号的方法有两种:
一是在FIFO内部设置一个计数器来指示当前FIFO内部缓存了多少个数据,但这会引入新的寄存器造成额外的资源开销;
二是通过拓宽读写指针的位数,然后根据读写指针的关系来判断空满。
当读指针等于写指针时,标明FIFO处于空状态,如图所示,假设FIFO深度为8,那么读写地址的位宽取3,读写指针在此基础上拓宽一位:
当FIFO内有数据写入时,写指针增加,此时读指针的低N-1位不等于写指针的低N-1位(N为指针拓宽一位之后的位宽),如图所示:
如果此时再读出两个数据,FIFO又将回到空状态,此时读指针与写指针又相等:
假如此时,一口气写入8个数据,写指针+8,变成4'b1010,FIFO处于满状态,此时读写指针的最高位不同,但低N-1位相同:
于是可以总结得到FIFO空满信号的生成条件:
assign fifo_empty_o = (wr_ptr == rd_ptr);
assign fifo_full_o = ((wr_ptr[$clog2(DEPTH)] != rd_ptr[$clog2(DEPTH)]) && (wr_addr == rd_addr));
2.2 异步FIFO
同步FIFO可用于在同步时钟域传输多比特信号,但若要实现两个异步时钟域之间的数据传输,就要使用异步FIFO。
异步FIFO相较于同步FIFO最大的不同在于读/写时钟域是异步的,也就是说读写控制信号是由不同的时钟驱动的,这就导致读指针需要同步到写时钟域,才能判断FIFO是否满;写指针也需要同步到读时钟域,才能判断FIFO是否空。
异步FIFO的框图如下:
但是对于多比特信号的跨时钟域同步,存在一个严重的问题。如果像同步FIFO那样使用二进制的读写指针,指针每次加一都可能使好几位发生翻转,以三位指针为例,假设当前指针为3'b111下一次操作后指针加1变为3'b000.
我们已经知道,单比特信号打两拍同步之后的结果可能恢复成0或1的其中一个稳态,假如该单比特信号是从0→1,最终哪怕同步结果是0,也不影响整个电路的功能,只当是没有发生这次操作,在后续的运行过程中再把正确信号同步过来也是ok的。
但是对于多比特信号而言,多位发生翻转,其中的每一位都可能同步为不同的结果,也就是说3'b111→3'b000可能变成3'b111→3'b010或者3'b111→3'b110、3'b111→3'b001、3'b111→3'b101之类的任意值,这就明显违背了指针的正常逻辑,破坏了电路的功能。
我们希求的效果是3'b111→3'b000,哪怕同步结果不是3'b000,也得是3’b111保持不变。因此要求指针递增每次只变化一位。
刚好,格雷码的编码方式就是相邻两个数值只有一位不同。因此在异步FIFO设计中,跨时钟域同步读写指针之前,要将二进制指针先转换成格雷码编码的指针,再进行同步。
二进制和格雷码的互相转化逻辑,这里不再赘述,只展示Verilog代码以供参考:
bin2gray:
module bin2gray
#(
parameter WIDTH = 8
)
(
input [WIDTH-1:0] bin,
output [WIDTH-1:0] gray
);
assign gray = bin ^ {1'b0,bin[WIDTH-1:1]};
endmodule
gray2bin:
module gray2bin
#(
parameter WIDTH = 8
)
(
input [WIDTH-1:0] gray,
output [WIDTH-1:0] bin
);
genvar i;
generate
for(i=0;i< WIDTH;i=i+1)begin
assign bin[i] = ^gray[WIDTH-1:i];
end
endgenerate
endmodule
相应的,用格雷码编码的指针来判断异步FIFO的空满状态:
assign empty = (rd_ptr_gray == wr_ptr_gray_sync_r2);
assign full = ({~wr_ptr_gray[ADDR_WIDTH:ADDR_WIDTH-1],wr_ptr_gray[ADDR_WIDTH-2:0]} == rd_ptr_gray_sync_r2);
全部0条评论
快来发表一下你的评论吧 !