跨时钟域的典型范例——异步FIFO 
“时钟域(clock domain)”可以说是数字集成电路中一个非常重要的内容了,那么,何谓“跨时钟域”?
很好理解,在时序逻辑电路中,所有触发器、寄存器的运行都是由时钟激励而运行下去的。而一个大型的数字系统中不可能只有一种时钟:
例如Cortex-M3软核常常运行在50~100MHz、而UART串行口波特率要在921600以上的话,输入时钟频率最好高于200MHz、FPGA上的DDR3一般要求200MHz的时钟输入,不同PLL/MMCM输出的同频率时钟它们的相位也有可能不同……
这么多不同时钟激励的系统要组合在一起并能进行数据交互,那么势必会遇到“跨时钟域”的问题,解决这个问题的一大方法之一就是利用异步FIFO进行数据交互
什么是FIFO 
FIFO(F irst  I n F irst O ut),顾名思义,即先输入先输出的一种模块,FIFO一般分为同步FIFO和异步FIFO两种,同步FIFO即输入和输出在同一个时钟域中,异步FIFO即输入输出不处于同一个时钟域。
FIFO的存储模块可以看成一个二维寄存器(当然也可以通过生成ram代替),因此有些概念需要掌握,即:
FIFO宽度:指FIFO中一个空间容纳的数据比特数,即一个数据单位的大小,例如你要输入FIFO的是16位的数据,那么FIFO的宽度即为16bit 
FIFO深度:指FIFO中能容纳多少个数据单元,在异步FIFO中这个值往往是可以被计算出来的,例如: 
 
到底如何解决跨时钟域的问题 
有的同志可能会立刻回答:用FIFO!那好,FIFO为什么能解决跨时钟域的问题呢?异步FIFO自己又是如何解决输入输出时钟不同域的问题呢?
还是得从跨时钟域的深层次原因来看这个问题:
单bit跨时钟域 
当以时钟域CLK-A为激励源的寄存器输出信号给时钟域为CLK-B的寄存器时,会出现什么情况?
用verilog描述为:
1 2 3 4 5 6 7 8 reg  Q1,Q2;always @(posedge  clk_a) begin 	Q1 <= data; end always @(posedge  clk_b) begin 	Q2 <= Q1; end 
显然,Q1上的数据跨过了两个时钟域,那么如果当经过clk-a时输出的Q1不稳定时,clk-b端的DFF采样到的Q1数据将会是不可预测的,因为这两个时钟并非同源,所以可能无法满足第二个DFF的建立和保持时间,会让第二个DFF处于亚稳态。
那么如何解决这个问题呢?这个问题实际上无法解决,但我们可以让它(指不满足另一个DFF的setup和hold time,处于亚稳态)发生的概率几乎为零,有没有方法呢?有!“打两拍”处理即可
“打两拍”指让数据经过两个后级寄存器(两级DFF同步),具体为什么这样做就能有想降低出错概率,可以参考这篇文章:知乎-跨时钟域同步,为什么两级寄存器结构能够降低亚稳态? 具体的电路是这样的:
用verilog描述为:
1 2 3 4 5 6 7 8 9 reg  Q1,Q2,Q3;always @(posedge  clk_a) begin 	Q1 <= data; end always @(posedge  clk_b) begin 	Q2 <= Q1; 	Q3 <= Q2; end 
这样,第三个DFF处于亚稳态的概率大幅降低了,然而这是单个bit跨时钟域的情况,如果是多个bit呢?
多bit跨时钟域 
在异步FIFO中,不论是写入数据还是取出数据,都是对同一个Memory操作的,两者彼此独立,不会产生跨时钟域的问题,那么异步FIFO中跨时钟域的地方在哪里呢?在FIFO状态的判断上
应该了解到,FIFO是有几个状态判断标志的,例如:全空、全满、即将空和即将满等等状态,这些状态是根据写入和读取指针决定的,当写入指针等于读取指针的时候,FIFO为全空;当写入指针超过读取指针一圈(指写到FIFO底部后又重新从头开始写,再一次与读取指针相遇)的时候,FIFO为全满。那么显然,我们要获取FIFO的状态就得对读和写指针进行判断了,但是这两个指针分别是在不同时钟域内的,所以这时候便产生了多bit(正经fifo指针不可能只有1bit)跨时钟域的问题。
那么如何解决这个问题呢?打拍还能解决吗?我们来分析一下:
假如现在的写入指针wr-ptr = 4’b0111,读取指针rd-ptr = 4’b0011,即将在写入和读出的下一个上升沿判断指针,而写入时钟频率高于读取时钟频率,那么判断的时候可能会出现的情况为:
最好的情况:刚好写入和读取的时钟上升沿在同一时刻了,并且数据都满足了建立和保持时间后开始进行指针之间的比较,完全没有问题,比较的结果也是正确的。 
出了点状况:读取指针的时钟在这一时刻翻转了,有了一个上升沿,这个时候读取的数据还没有准备好 (DFF处于亚稳态),rd-ptr = 4’b0zzz(后三bit无法确定,因为不稳定),这下完了没法比较了。 
又出了点状况:写入指针也恰好没法满足时序条件,而且写入指针的四位BCD码都即将进行翻转(0111->1000),这下彻底完蛋了,写入和读取指针在这个状态下完全是不可测的  
 
显然,出现第一种情况的概率小到可以忽略(😅),那么这么严重的问题怎么解决呢?一个个看:
解决第2种情况的方法只需要我们进一步思考就行了,既然是读取指针没法满足写入指针的DFF输出数据的建立和保持条件,那么我再加一个DFF把读取指针同步到写入时钟域(或者把写入指针同步到读取时钟域)不就行了?没错,但是这样的话依旧无法解决第三个问题,怎么办?
这个问题再进一步思考,其实是BCD码自身的缺陷 问题,从0111自增一次变成1000的时候,四个位都变化了,这就导致有16种可能,那么有没有一种编码方式能让每次自增只变化一位码的呢?还真有,格雷码 。
格雷码每自增一次,只会翻转一个bit,具体可以自己查一下,这样的话,如果从格雷码0111(0101BCD)跳转到下一状态0101(0110BCD)的时候,只有一位改变了,不论你建立和保持时间是否满足,其余三位都是不会变的,也就是说即使处于亚稳态,这个时候另一个时钟域读取到的数据也只可能是0111或者0101,况且我们还有对单bit跨时钟的解决办法—打两拍,因此这对我之后的比较几乎没有影响 (关于利用格雷码解决异步FIFO的问题的详细解释,这个回答知乎-异步fifo格雷码同步问题 讲解的非常清楚)
那么这个问题就解决了,可以开始着手设计FIFO了。
异步FIFO的设计 
为了遵循模块化和参数化的设计标准,同时方便以后直接生成BRAM实现FIFO,我将异步FIFO拆分成控制端和数据端,控制端负责接收写入读取信号,产生写入读取指针,并转换成格雷码判断FIFO状态;数据端负责接受写入读取指针和写入读取信号以及写入的数据,输出读取的数据。把上面的过程分析清楚了,写出整个设计毫无困难:
异步FIFO控制模块 
直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 module  fifo_ctrl #(    parameter  Addr_Width = 5  ) (     input                    reset_n,     input                    wr_clk,     input                    wr_ena,     input                    rd_clk,     input                    rd_ena,     output                   empty,     output                   full,                     output  [Addr_Width:0 ]   wr_addr_ptr,     output  [Addr_Width:0 ]   rd_addr_ptr );     reg  wr_rstn;     reg  rd_rstn;     reg  [Addr_Width:0 ] wr_addr_count;     reg  [Addr_Width:0 ] rd_addr_count;     wire  [Addr_Width:0 ] wr_addr_gray;         reg  [Addr_Width:0 ] wr_addr_gray_reg1;     reg  [Addr_Width:0 ] wr_addr_gray_reg2;     wire  [Addr_Width:0 ] rd_addr_gray;     reg  [Addr_Width:0 ] rd_addr_gray_reg1;     reg  [Addr_Width:0 ] rd_addr_gray_reg2;               always  @(posedge  wr_clk ) begin               if  (!reset_n)              wr_rstn <= 1'b0 ;         else              wr_rstn <= 1'b1 ;     end           always  @(posedge  wr_clk or  negedge  wr_rstn) begin          if  (!wr_rstn) begin              wr_addr_count <= 0 ;             wr_addr_gray_reg1 <= 0 ;             wr_addr_gray_reg2 <= 0 ;         end          else  if  (wr_ena & ~full) begin              wr_addr_count = wr_addr_count + 1'b1 ;         end          else              wr_addr_count = wr_addr_count;     end      assign  wr_addr_ptr = wr_addr_count - rd_addr_count;          always  @(posedge  rd_clk ) begin               if  (!reset_n)              rd_rstn <= 1'b0 ;         else              rd_rstn <= 1'b1 ;     end           always  @(posedge  rd_clk or  negedge  rd_rstn) begin          if  (!rd_rstn) begin              rd_addr_count <= 0 ;             rd_addr_gray_reg1 <= 0 ;             rd_addr_gray_reg2 <= 0 ;         end          else  if  (rd_ena & ~empty) begin              rd_addr_count = rd_addr_count + 1'b1 ;         end          else  if  (rd_addr_count >= 'd31 ) begin              rd_addr_count = 0 ;         end          else              rd_addr_count = rd_addr_count;     end      assign  rd_addr_ptr = rd_addr_count;          assign  wr_addr_gray = (wr_addr_count >> 1 ) ^ wr_addr_count;          assign  rd_addr_gray = (rd_addr_count >> 1 ) ^ rd_addr_count;          always  @(posedge  rd_clk ) begin          wr_addr_gray_reg1 <= wr_addr_gray;         wr_addr_gray_reg2 <= wr_addr_gray_reg1;     end           always  @(posedge  wr_clk ) begin          rd_addr_gray_reg1 <= rd_addr_gray;         rd_addr_gray_reg2 <= rd_addr_gray_reg1;     end           assign  empty = (rd_addr_gray == wr_addr_gray_reg2);          assign  full  = (wr_addr_gray == {~rd_addr_gray_reg2[Addr_Width:Addr_Width-1 ],rd_addr_gray_reg2[Addr_Width-2 :0 ]}); endmodule  
FIFO数据模块 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 module  fifo_mem #(    parameter  Data_Width = 16 ,     parameter  Fifo_Depth = 32 ,     parameter  Addr_Width = 5  ) (     input                       reset_n,     input                       wr_clk,     input                       rd_clk,     input                       rd_ena,     input                       wr_ena,     input                       full,     input                       empty,     input  [Data_Width-1 :0 ]     wr_data,     input  [Addr_Width:0 ]       wr_addr_ptr,     input  [Addr_Width:0 ]       rd_addr_ptr,     output  [Data_Width-1 :0 ]    rd_data );          reg  [Data_Width-1 :0 ] fifo_ram [Fifo_Depth-1 :0 ];      reg  wr_rstn;     reg  rd_rstn;     reg  [Data_Width-1 :0 ] rd_data_reg;           always  @(posedge  wr_clk ) begin               if  (!reset_n) begin              wr_rstn <= 1'b0 ;         end          else              wr_rstn <= 1'b1 ;     end           genvar  i;     generate          for  (i = 0 ;i < Fifo_Depth; i = i + 1  ) begin              always  @(posedge  wr_clk or  negedge  wr_rstn) begin                  if  (!wr_rstn) begin                      fifo_ram[i] <= 0 ;                 end               end          end      endgenerate      always  @(posedge  wr_clk ) begin          if  (wr_ena && !full) begin              fifo_ram[wr_addr_ptr] <= wr_data;         end          else              fifo_ram[wr_addr_ptr] <= fifo_ram[wr_addr_ptr];      end           always  @(posedge  rd_clk ) begin               if  (!reset_n) begin              rd_data_reg <= 0 ;             rd_rstn <= 1'b0 ;         end          else              rd_rstn <= 1'b1 ;     end      always  @(posedge  rd_clk or  negedge  rd_rstn) begin          if  (!rd_rstn) begin              rd_data_reg <= 0 ;         end          else  if  (rd_ena && !empty) begin              rd_data_reg <= fifo_ram[rd_addr_ptr];         end          else              rd_data_reg <= 0 ;     end           assign  rd_data = rd_data_reg; endmodule 
TOP_MODULE 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 `timescale  1ns/100ps module  asyn_fifo_top #(    parameter  Data_Width = 16 ,     parameter  Fifo_Depth = 32 ,     parameter  Addr_Width = 5  ) (      input                       reset_n,      input                       wr_clk,      input                       wr_ena,      input  [Addr_Width-1 :0 ]     wr_data,      input                       rd_clk,      input                       rd_ena,      output  [Addr_Width-1 :0 ]    rd_data,      output                      valid,      output                      empty,      output                      full );          wire  [Addr_Width:0 ] wr_addr_ptr;     wire  [Addr_Width:0 ] rd_addr_ptr;   fifo_mem    #(     .Data_Width (Data_Width ),     .Fifo_Depth (Fifo_Depth ),     .Addr_Width  (Addr_Width )   )   fifo_mem_dut (     .reset_n  (reset_n ),     .wr_clk  (wr_clk ),     .rd_clk  (rd_clk ),     .rd_ena  (rd_ena ),     .wr_ena  (wr_ena ),     .full  (full ),     .empty  (empty ),     .wr_data  (wr_data ),     .wr_addr_ptr  (wr_addr_ptr ),     .rd_addr_ptr  (rd_addr_ptr ),     .rd_data   ( rd_data)   );   fifo_ctrl    #(     .Addr_Width  (Addr_Width )   )   fifo_ctrl_dut (     .reset_n  (reset_n ),     .wr_clk  (wr_clk ),     .wr_ena  (wr_ena ),     .rd_clk  (rd_clk ),     .rd_ena  (rd_ena ),     .empty  (empty ),     .full  (full ),     .wr_addr_ptr  (wr_addr_ptr ),     .rd_addr_ptr   ( rd_addr_ptr)   ); endmodule 
仿真测试结果: