一、定义

模块之间有数据交互但两个模块不是同一个时钟驱动。根据clk1与clk2是否为同步时钟,分为跨同步时钟域和跨异步时钟域。。根据信号是控制信号还是数据信号可以分为控制信号传输和数据信号的传输。
解释
同步时钟与异步时钟
同步时钟:(1)同频同相位(2)同频不同相位,但相位固定(3)不同频,但存在整数倍的关系
异步时钟:两时钟信号完全没有关系。
二、单比特数据
1、跨同步时钟域:
(1) 同频同相:

该情况只要满足普通的同步电路设计的要求(建立和保持时间,信号的传输延时要在一定范围内)即可。一般不需要同步器。
(2) 同频不同相:

相位为固定值,允许的传输时间小于一个时钟周期。但是只要满足控制信号的输出是在clk1的控制下进行翻转的,因此只要满足同步设计的一般要求(满足建立时间和保持时间,控制信号的传输延时要在一定范围内),就可以满足时序,不会出生亚稳态,也不会出现数据丢失的情况,因此一般不需要同步器。
(3) 不同频:
慢->快,打拍即可。快->慢,展宽后,打拍。(参考下文)
2、跨异步时钟域
当从慢时钟域到快时钟域时,只需要打两拍防止亚稳态即可。
注:单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态,第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为70%~80%左右,第二级寄存器可以稳定输出的概率为99%左右,再后面改善就不明显了,所以数据进来后一般选择打两拍即可。
当从快时钟域到慢时钟域时,如图所示,慢时钟域可能根本就采集不到信号。

1、因此需要想办法将信号进行展宽,可以将脉冲信号转化为沿信号。当检验到信号电平为高时,就对沿信号进行取反,使得的第一个脉冲变为沿信号的上升沿,第二个脉变为沿信号的下降沿,后面如果信号还有脉冲依然是变为沿信号的上升沿和下降沿。

2、对沿信号进行打拍两次,获得稳定的信号。

3、最后,再将沿信号转化为脉冲信号。使用的方法为边沿检测。为了还原出脉冲,还需要再打一拍。
上升沿检测:对数据进行寄存,若前值为低,后值为高则为上升沿。 方法一:与逻辑实现 //----------------------------------------------- always@(posedge sys_clk ornegedge sys_rst_n) if(sys_rst_n ==1'b0) podge <=1'b0; else if((data)&&(~data_reg)) //核心逻辑 podge <=1'b1; else podge <=1'b0; //----------------------------------------------- 方法二:或逻辑实现 //----------------------------------------------- always@(posedge sys_clk ornegedge sys_rst_n) if(sys_rst_n ==1'b0) podge <=1'b0; else if((~data)||(data_reg)) //核心逻辑 podge <=1'b0; else podge <=1'b1; //----------------------------------------------- 下降沿检测 //----------------------------------------------- always@(posedge sys_clk ornegedge sys_rst_n) if(sys_rst_n ==1'b0) nedge <=1'b0; else if((~data)&&(data_reg)) //核心逻辑 nedge <=1'b1; else nedge <=1'b0; //-----------------------------------------------

注:对于一个信号的展宽,也可以在快时钟域打拍,然后三信号相与。

三、多比特数据
1、使用握手信号进行跨时钟域数据传输

使用握手信号xack和yreq,系统X发给系统Y,下面是使用握手信号传输数据的例子:
1)发送器系统X将数据放到数据总线上并发出xreq请求信号,表示有效数据已经发送到接收器系统Y的数据总线上
2)把xreq信号同步到接收器的时钟域yclk上。
3)接收器在识别xreq同步信号yreq2后,锁存数据总线上的信号
4)接收器发出确认信号yack,表示其已经接受了数据
5)接收器发出的yack信号同步到发送时钟xclk上
6)发送器在识别同步的ack信号后,将下一个数据放到数据总线上
握手信号的时序图如下所示:

注:数据应该在发送时钟域内稳定至少两个时钟上升沿,请求信号xreq的宽度应该超过两个上升沿时钟,否则从高速时钟的低速时钟域传递可能无法捕捉到该信号。缺点是是延迟太大(相比于FIFO)。

`timescale 1ns/1ns module data_driver( input clk_a, input rst_n, input data_ack, output reg [3:0]data, output reg data_req ); reg data_ack_reg_1; reg data_ack_reg_2; reg [9:0] cnt; always @ (posedge clk_a or negedge rst_n) if (!rst_n) begin data_ack_reg_1 <= 0; data_ack_reg_2 <= 0; end else begin data_ack_reg_1 <= data_ack; data_ack_reg_2 <= data_ack_reg_1; end always @ (posedge clk_a or negedge rst_n) if (!rst_n) begin data <= 0; end else if(data_ack_reg_1 && !data_ack_reg_2) begin data <= data+1; end else begin data <= data; end //同时在data_ack有效之后,开始计数五个时钟,之后发送新的数据,也就是再一次拉高data_req. always @ (posedge clk_a or negedge rst_n) if (!rst_n) cnt <= 0; else if (data_ack_reg_1 && !data_ack_reg_2) cnt <= 0; else if (data_req) cnt <= cnt; else cnt <= cnt+1; always @ (posedge clk_a or negedge rst_n) if (!rst_n) data_req <= 0; else if (cnt == 3'd4) data_req <= 1'b1; else if (data_ack_reg_1 && !data_ack_reg_2) data_req <= 1'b0; else data_req <= data_req; endmodule module data_receiver( input clk_b, input rst_n, output reg data_ack, input [3:0]data, input data_req ); reg [3:0]data_in_reg; reg data_req_reg_1; reg data_req_reg_2; always @ (posedge clk_b or negedge rst_n) if (!rst_n) begin data_req_reg_1 <= 0; data_req_reg_2 <= 0; end else begin data_req_reg_1 <= data_req; data_req_reg_2 <= data_req_reg_1; end always @ (posedge clk_b or negedge rst_n) if (!rst_n) data_ack <= 0; else if (data_req_reg_1) data_ack <= 1; else data_ack <=0 ; always @ (posedge clk_b or negedge rst_n) if (!rst_n) data_in_reg <= 0; else if (data_req_reg_1 && !data_req_reg_2) data_in_reg <= data; else data_in_reg <= data_in_reg ; endmodule
2、FIFO
同步FIFO常用于同步时钟的数据缓存,异步FIFO常用于跨时钟域的数据信号的传递。
a、双端口RAM

module dual_port_RAM #(parameter DEPTH = 16, parameter WIDTH = 8)( input wclk ,input wenc ,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。 ,input [WIDTH-1:0] wdata //数据写入 ,input rclk ,input renc ,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。 ,output reg [WIDTH-1:0] rdata //数据输出 ); reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1]; always @(posedge wclk) begin if(wenc) RAM_MEM[waddr] <= wdata; end always @(posedge rclk) begin if(renc) rdata <= RAM_MEM[raddr]; end endmodule
b、FIFO地址设计
我们知道FIFO中是没有地址线的,地址靠自身计数器自加1来控制,那么我们很容易想到把外部输入信号wr_addr和rd_addr换成内部信号并用计数器来控制其自加,计数器加满之后直接清零,从0重新开始写/读,循环往复。由于写端和读端的时钟速率不同,就会有快慢的问题,那么就出现了一个问题,以地址3为例,写入的数据还没有被读出,又被新的数据覆盖了,造成数据丢失;或者写入的数据已经被读出,新的数据还没有写进来,地址3的老数据又被读了一遍,造成数据重复。

上图是本异步FIFO的结构示意图。蓝色区域是读时钟域,黄色部分是写时钟域。异步FIFO主要包含四部分:读写地址发生器、格雷码的产生与打拍、空满信号发生器以及RAM。
输入:winc, rcin, wdata
输出:rdata, wfull, rempty
读写使能:rcin\wcin,读写数据:rdata\wdata,空满信号:wfull\rempty。
当读使能rinc==1且FIFO非空rempty==0时,读地址在读时钟rclk下自增;当写使能winc==1且FIFO非满wfull==0时,写地址在写时钟wclk下自增。这两种地址可以直接传入RAM模块。
wire wenc, renc; wire [$clog2(DEPTH)-1:0] waddr, raddr; assign wenc = winc&!wfull; assign renc = rinc&!rempty; always@(posedge wclk or negedge wrstn) begin if(~wrstn) waddr_bin <= 0; else waddr_bin <= wenc? waddr_bin+1: waddr_bin; end always@(posedge rclk or negedge rrstn) begin if(~rrstn) raddr_bin <= 0; else raddr_bin <= renc? raddr_bin+1: raddr_bin; end
c、格雷码打拍
为了产生空满信号,需要比较读写地址的大小。但两个地址是由不同的时钟控制的,需要先做跨时钟域处理才能比较。所以使用了打两拍。又因为FIFO的读写地址是连续变化的,采用格雷码可以有效减少相邻地址的bit变化,进一步降低打拍过程产生亚稳态的可能性。
reg [$clog2(DEPTH):0] waddr_bin, raddr_bin; wire [$clog2(DEPTH):0] waddr_gray, raddr_gray; reg [$clog2(DEPTH):0] waddr_gray1, raddr_gray1; reg [$clog2(DEPTH):0] waddr_gray2, raddr_gray2; reg [$clog2(DEPTH):0] waddr_gray3, raddr_gray3; assign waddr_gray = waddr_bin^(waddr_bin>>1); assign raddr_gray = raddr_bin^(raddr_bin>>1); assign waddr = waddr_bin[$clog2(DEPTH)-1:0]; assign raddr = raddr_bin[$clog2(DEPTH)-1:0]; always@(posedge rclk or negedge rrstn) begin if(~rrstn) raddr_gray1 <= 0; else raddr_gray1 <= raddr_gray; end always@(posedge rclk or negedge rrstn) begin if(~rrstn) begin waddr_gray2 <= 0; waddr_gray3 <= 0; end else begin waddr_gray2 <= waddr_gray1; waddr_gray3 <= waddr_gray2; end end always@(posedge wclk or negedge wrstn) begin if(~wrstn) begin raddr_gray2 <= 0; raddr_gray3 <= 0; end else begin raddr_gray2 <= raddr_gray1; raddr_gray3 <= raddr_gray2; end end
d、空满信号发生器
当读写地址的格雷码仅有最高的2bit不同时,FIFO满;当读写地址完全相同时,FIFO空。
assign wfull = (waddr_gray1=={~raddr_gray3[$clog2(DEPTH):$clog2(DEPTH)-1], raddr_gray3[$clog2(DEPTH)-2:0]}); assign rempty = (raddr_gray1==waddr_gray3);
格雷码地址比`waddr`和`raddr`多出1bit用于判断是否满。当读地址和写地址在不同圈时,格雷码的最高位也会不同。再根据格雷码的对称性,此时如果次高位不同而其他位相同,说明读写地址的自然二进制码是相通的。这体现出了格雷码的另一个优势,就是方便判断是否套圈和满信号。

参考:题解 | #异步FIFO#_牛客博客 (nowcoder.net)