当前位置:   article > 正文

硬件架构的艺术:处理多个时钟(跨时钟域处理)_硬件开机时钟域问题

硬件开机时钟域问题

1. 概述

  只涉及单个时钟的设计是容易实现的,但极少有设计只在一个时钟下工作,设计通常会工作在多个时钟域。本文会介绍多时钟设计(跨时钟域)中所遇到的问题和处理方法,进而得到可以工作在多时钟下的健壮设计。

2. 多时钟域

  满足以下情况之一可认为时钟不同,两种情况如图所示:

  • 时钟的频率不同
  • 时钟的频率相同,但相位不同
    在这里插入图片描述

3. 多时钟域设计的难题

  多时钟设计面临以下问题:

  • 建立时间和保持时间的违背
  • 亚稳态

3.1 建立时间和保持时间的违背

  在同步设计中,只要设计在静态时序分析(STA)中没有时序违例的情况,则可以认为系统不会出现建立时间和保持时间的违背。但在异步系统中,x_clk和y_clk的相位关系是不固定的,在时钟的上升沿前后要求的建立时间和保持时间是难以满足的,很容易就出现一个时钟域的输出在另一个时钟域中的时钟上升沿到来时发生改变的现象,如下图所示。

  • xclk_output1:在yclk时钟上升沿发生变化,导致输出为亚稳态
  • xclk_output2:在yclk时钟上升沿未发生变化,得到正确的结果

在这里插入图片描述

3.2 亚稳态

  跨时钟域信号传输违背建立时间和保持时间就会输出亚稳态,导致系统输出错误的结果,甚至影响系统正常工作。
  亚稳态详细分析见:https://blog.csdn.net/shiwq1127/article/details/122815601

4. 多时钟设计的处理技术

  在进行一个含多时钟的设计时,仿真和综合过程中遵循一定的准则会带来很大的好处。一些通用的准则如下:

  • 时钟命名规则
  • 分模块设计

4.1 时钟命名规则

  信号的命名要有以下两个特点:物理意义明确、简洁。在跨时钟域信号的命名上,在上述基础上要有以下规则:

  • 时钟命名前缀,如sys_clk、tx_clk、rx_clk,物理意义清晰;
  • 信号前缀:如sys_rom_addr、sys_rom_data,明确指出信号所属时钟域
  • 接口信号方向:接口信号还可以使用模块名缩写确定方向,如rx2tx_data、rx2tx_valid、tx2rx_ready,rx2tx_data和rx2tx_valid为rx模块输出,tx2rx_ready为rx模块输入信号。

  使用这种命名方法,团队中的工程师可以分辨出每个特定信号所属时钟域,并决定是应该直接使用该信号,还是需要先将其通过同步器后再使用。

4.2 分块化设计

  分模块化设计有以下特点:

  • 每个模块只应当在单个时钟下工作;
  • 跨时钟域传输时,使用同步器模块,以使所有信号在进入某个时钟域时,与该模块的时钟保持同步;
  • 同步器模块的规模应尽可能的小
      模块化设计的优点在于使得静态时序分析变得简单,因为在单个模块内的信号都是同一个时钟驱动的,单个模块可以看作同步设计。另外同步器模块不需要做静态时序分析,但是需要设计合理。如下图所示,整个逻辑分成了三个时钟域,分别是clock1、clock2、clock3。根据上述命名规则对各时钟域信号进行命名,所有跨时钟域传输的信号都需要经过同步器,该模块的作用是将信号从原来所在时钟域转化到将使用该信号的时钟域(src_clk→dst_clk)
    在这里插入图片描述

4.3 跨时钟域

  跨时钟域传输的信号可以归为两类:

  • 跨时钟域传输,允许数据丢失
  • 跨时钟域传输,不允许数据丢失

4.3.1 控制信号的传输

  多级同步器无法保证数据不丢失,可以用于同步允许数据丢失的场景。

4.3.1.1 两级同步器

  在设计中,如果将信号直接驱动其他时钟域的信号,如“硬件架构的艺术:亚稳态世界”文章中所描述,由于异步时钟相位关系不固定,目标时钟采样时源信号若发生变化,此时很容易违背建立时间和保持时间,此时目标时钟域寄存器输出亚稳态。为了避免这种情况下的亚稳态,我们常用同步触发器的输出来取代异步信号,如下图所示:
在这里插入图片描述
  如果同步器的第一级产生亚稳态输出,那么这个亚稳态极大概率会在第二个同步器采样之前进入稳定,虽然不能保证第二级输出一定不会出现亚稳态,但的确降低出现亚稳态的概率。

4.3.1.2 三级同步器

  有些情况下,第一级同步触发器的输出信号从亚稳态进入稳定状态所需时间可能不止一个时钟周期,这就意味着第二级同步触发器的输出仍然是亚稳态,此时安全起见,应当使用三级同步器。如下图所示,第一级同步器从亚稳态返回到稳定状态的时间超过一个时钟周期,第二级同步器输出亚稳态,但第二级同步器极大概率在一个时钟周期内返回稳定状态,此时第三级同步器采样得到的是稳定状态。
在这里插入图片描述
  单级同步器的MTBF(mean time between failure,平均无故障时间)计算如下:
在这里插入图片描述
  多级级联同步器的MTBF计算公式如下:

         MTBF_cascade = MTBF_1 * MTBF_2 * … *MTBF_N

  故三级同步器可以将亚稳态发生的概率降至很低,在频率较高的设计中常使用三级同步器。

4.3.2 数据信号的传输

  在多时钟域设计中,数据经常会从一个时钟域传输到另一个时钟域,保证数据不丢失的两种方法如下,这两种技术在后续章节中分析。

  • 使用握手信号
  • 使用异步FIFO

4.3.3 跨时钟域传输发送端信号

  在跨时钟域传输时,发送信号应该为寄存器输出,避免组合逻辑输出驱动同步器,组合逻辑输出可能存在毛刺,即多个边沿,增加同步器输出亚稳态的概率,如下图所示:
在这里插入图片描述
  在组合逻辑输出跨时钟域传输之前使用寄存器锁存后输出,可以降低该信号在一个时钟周期内的边沿,进而降低多级同步器输出亚稳态的概率,如下图所示。跨时钟域传输的信号应该是“干净的”,即寄存器输出。
在这里插入图片描述

5. 控制信号跨时钟域

  假设控制信号为单bit信号,下文将分析控制信号在跨时钟域传输时面临的问题及解决方法。

5.1 控制信号跨时钟域传输问题

  跨时钟域传输中,在目的时钟域采样可能存在两个问题:

  • 目的时钟采样前数据变化多次,导致采样丢失数据
  • 目的时钟采样时钟上升沿与控制信号变化相隔较近,容易出现亚稳态

5.2 跨时钟域信号传输要求

  在跨时钟域传输中,慢时钟域信号传输至快时钟域(dst_f >= src_f * 1.5)通常风险较小,使用多级同步器即可完成。但快时钟域到慢时钟域的传输对信号有一些要求:

“三个时钟沿要求”:使用多级同步器跨时钟域传输信号,需要保证源数据稳定时间大于1.5*T_dst,即在目的时钟域三个时钟沿(上升、下降)内需要保持稳定。

  该要求可以保证发送端的数据可以正确被接收端时钟采样。

5.3 快时钟域到慢时钟域

5.3.1 多级同步器问题

  快时钟域到慢时钟域信号通过同步器传输一个典型的问题就是“快时钟域输出信号为一个周期的脉冲信号”,此时该信号不满足“三个时钟沿”要求,但该场景很常见,如中断、开始、结束信号通常为脉冲信号。此时可能出现两种情况:

  • 脉冲信号未被采样
    在这里插入图片描述

  • 脉冲信号采样输出亚稳态
    在这里插入图片描述

5.3.2 开环多级同步器

  发送端脉冲信号宽度小于1.5*T_dst,可以将该脉冲信号展宽然后再通过多级同步器跨时钟域传输,保证接收端可以正确采样到该信号,但接收端可能会采样多次。如下图所示:
在这里插入图片描述

5.3.2.1 脉冲展宽方法

  脉冲展宽的常用方法如下,展宽后的信号最好经过一级寄存器锁存后输出至目的时钟域。

module pulse_extend(
//input
clk,rst_n,pulse_in,
// output
pulse_ext
	);
//-----------------------------------------------
// parameter
parameter EXTEND_STAGE = 2;

//-----------------------------------------------
// input
input		clk;
input		rst_n;
input		pulse_in;

//-----------------------------------------------
// output
output		pulse_ext;

//-----------------------------------------------
// signal
reg		pulse_in_dff[0:EXTEND_STAGE-1];

//-----------------------------------------------
// main body
genvar i;
generate
	for (i=0;i<EXTEND_STAGE-1;i++) begin
		if(i==0) begin
			always@(posedge clk or negedge rst_n) begin
				if(rst_n==1'b0) begin
					pulse_in_dff[0] <= 1'b0;
				end
				else begin
					pulse_in_dff[0] <= pulse_in;
				end
			end
		end
		else begin
			always@(posedge clk or negedge rst_n) begin
				if(rst_n==1'b0) begin
					pulse_in_dff[i] <= 1'b0;
				end
				else begin
					pulse_in_dff[i] <= pulse_ext_dff[i-1];
				end
			end
		end
	end
endgenerate

// dff_1 ... dff_n "or" operation
assign pulse_ext = |pulse_in_dff;

endmodule
  • 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
5.3.2.2 开环多级同步器优缺点

  开环多级同步器的优点

  • 结构简单,信号延迟小
  • 不需要接收端的确认信号

  多级同步器的缺点

  • 仅适用于确定的时钟关系场景,需要根据时钟关系调整展宽倍数;
  • 代码可移植性差,项目迭代需求和工程师变化时,风险大。

5.3.3 握手设计(闭环多级同步器设计)

  握手信号是最古老的在不同域之间传输数据的方式。

  两个不同时钟域分割成的两个单独的系统如图所示,使用握手信号xreq和yack,“system X”将数据发送给“system Y”。
在这里插入图片描述
  跨时钟域传输的步骤如下,时序波形如下图所示,其中xreq的宽度至少为2*T_b。

  • 锁存发送信号(送至数据总线),产生xreq;
  • 同步“xreq”至yclk,yreq2;
  • 接收端识别“yreq2”后,锁存数据总线上的信号;
  • 接收器发出“yack”,表示其已经接收到数据;
  • 接收器发送的“yack”信号同步至xclk域,记为xack2;
  • 发送器在识别同步的“xack2”后,将下一个数据放到数据总线上.
    在这里插入图片描述
5.3.3.1 脉冲信号握手传输

  脉冲信号的跨时钟域传输不需要考虑数据,RTL实现如下:

module pulse_async(
// input
xclk,xrst_n,xclk_pulse_in,yclk,yrst_n,
// output
yclk_pulse_out,xclk_async_busy
);

//----------------------------------------------------
// input
input		xclk;
input		xrst_n;
input		xclk_pulse_in;
input		yclk;
input		yrst_n;
//----------------------------------------------------
// output
output		yclk_pulse_out;
output		xclk_async_busy;

//----------------------------------------------------
// signal define
// ---- xclk domain
reg			xclk_pls_ext;		//extend
reg			xclk_yack_dff1;
reg			xclk_yack_dff2;
wire		xclk_yack_sync2;

// ---- yclk domain
reg			yclk_pls_ext_dff1;
reg			yclk_pls_ext_dff2;
wire		yclk_yack;
wire		yclk_pls_ext_sync2;
reg			yclk_pls_ext_sync2_dff1;

//----------------------------------------------------
// main body

// -------- send ctrl(xclk)
// ---- pulse extend ctrl
always @(posedge xclk or negedge xrst_n) begin
	if(xrst_n == 1'b0) begin
		xclk_pls_ext <= 1'b0;
	end
	else if(xclk_pulse_in == 1'b1) begin
		xclk_pls_ext <= 1'b1;
	end
	else if(xclk_yack_sync2 == 1'b1) begin
		xclk_pls_ext <= 1'b0;
	end
end

// ---- yack synchronizer
always @(posedge xclk or negedge xrst_n) begin
	if(xrst_n == 1'b0) begin
        xclk_yack_dff1 <= 1'b0;
        xclk_yack_dff2 <= 1'b0;
    end
    else begin
        xclk_yack_dff1 <= yclk_yack;
        xclk_yack_dff2 <= xclk_yack_dff1;
    end
end
assign xclk_yack_sync2 = xclk_yack_dff2;

// ---- busy
assign xclk_async_busy = xclk_pulse_in || xclk_pls_ext || xclk_yack_sync2;

// -------- receive ctrl(yclk)
// ---- sample xclk_pls_ext
always @(posedge yclk or negedge yrst_n) begin
    if (yrst_n == 1'b0) begin
        yclk_pls_ext_dff1 <= 1'b0;
        yclk_pls_ext_dff2 <= 1'b0;
    end
    else begin
        yclk_pls_ext_dff1 <= xclk_pls_ext;
        yclk_pls_ext_dff2 <= yclk_pls_ext_dff1;
    end
end
assign yclk_pls_ext_sync2 = yclk_pls_ext_dff2;
assign yclk_yack          = yclk_pls_ext_dff2;

// ---- generate out pulse
always @(posedge yclk or negedge yrst_n) begin
    if (yrst_n == 1'b0) begin
        yclk_pls_ext_sync2_dff1 <= 1'b0;
    end
    else begin
        yclk_pls_ext_sync_dff1 <= yclk_pls_ext_sync2;
    end
end

assign yclk_pulse_out = yclk_pls_ext_sync2 && (!yclk_pls_ext_sync2_dff1);   // posedge detect

endmodule

  • 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
5.3.3.2 数据信号握手传输

  数据信号也可以使用握手方式传输,此时虽然数据不会丢失,但传输效率很低。RTL实现代码如下:

module pulse_async(
// input
xclk,xrst_n,xclk_data,xclk_data_vld,yclk,yrst_n,
// output
yclk_data,yclk_data_vld,yclk_busy
);

//----------------------------------------------------
// parameter
parameter DATA_WIDTH = 8;

//----------------------------------------------------
// input
input		                xclk;
input		                xrst_n;
input	[DATA_WIDTH-1:0]	xclk_data;
input                       xclk_data_vld;
input		                yclk;
input		                yrst_n;
//----------------------------------------------------
// output
output	[DATA_WIDTH-1:0]	yclk_data;
output                      yclk_data_vld;
output		                xclk_async_busy;

//----------------------------------------------------
// signal define
// ---- xclk domain
reg		    	            xclk_data_req;		//extend
reg     [DATA_WIDTH-1:0]    xclk_data_hold;
reg			                xclk_yack_dff1;
reg			                xclk_yack_dff2;
wire		                xclk_yack_sync2;

// ---- yclk domain
reg			                yclk_pls_ext_dff1;
reg			                yclk_pls_ext_dff2;
wire		                yclk_yack;
reg     [DATA_WIDTH-1:0]    yclk_data_lock;
reg                         yclk_yack_dff1;

//----------------------------------------------------
// main body

// -------- send ctrl(xclk)
// ---- generate request
always @(posedge xclk or negedge xrst_n) begin
	if(xrst_n == 1'b0) begin
		xclk_data_req <= 1'b0;
	end
	else if(xclk_data_vld == 1'b1) begin
		xclk_data_req <= 1'b1;
	end
	else if(xclk_yack_sync2 == 1'b1) begin
		xclk_data_req <= 1'b0;
	end
end
// ---- data hold
always @(posedge xclk or negedge xrst_n) begin
	if(xrst_n == 1'b0) begin
		xclk_data_hold <= {DATA_WIDTH{1'b0}};
	end
	else if(xclk_data_vld == 1'b1) begin
		xclk_data_hold <= xclk_data;
	end
	else if(xclk_yack_sync2 == 1'b1) begin
		xclk_data_hold <= {DATA_WIDTH{1'b0}};
	end
end

// ---- yack synchronizer
always @(posedge xclk or negedge xrst_n) begin
	if(xrst_n == 1'b0) begin
        xclk_yack_dff1 <= 1'b0;
        xclk_yack_dff2 <= 1'b0;
    end
    else begin
        xclk_yack_dff1 <= yclk_yack;
        xclk_yack_dff2 <= xclk_yack_dff1;
    end
end
assign xclk_yack_sync2 = xclk_yack_dff2;

// ---- busy
assign xclk_async_busy = xclk_pulse_in || xclk_pls_ext || xclk_yack_sync2;

// -------- receive ctrl(yclk)
// ---- sample xclk_pls_ext
always @(posedge yclk or negedge yrst_n) begin
    if (yrst_n == 1'b0) begin
        yclk_pls_ext_dff1 <= 1'b0;
        yclk_pls_ext_dff2 <= 1'b0;
    end
    else begin
        yclk_pls_ext_dff1 <= xclk_pls_ext;
        yclk_pls_ext_dff2 <= yclk_pls_ext_dff1;
    end
end
assign yclk_yack          = yclk_pls_ext_dff2;

// ---- lock data & valid
always @(posedge yclk or negedge yrst_n) begin
    if (yrst_n == 1'b0) begin
        yclk_data_lock <= 1'b0;
    end
    else if(yclk_yack == 1'b1)begin
        yclk_data_lock <= xclk_data_hold;
    end
end

always @(posedge yclk or negedge yrst_n) begin
    if (yrst_n == 1'b0) begin
        yclk_yack_dff1 <= 1'b0;
    end
    else begin
        yclk_yack_dff1 <= yclk_yack;
    end
end

assign yclk_data     = yclk_data_lock;
assign yclk_data_vld = yclk_yack_dff1;

endmodule


  • 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
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

6. 数据信号跨时钟域

  数据信号(多bit信号)跨时钟域,在要求不丢失数据的情况下,异步FIFO是最常用的设计,而且异步FIFO不需要握手,数据可以连续传输。异步FIFO的设计见“硬件架构的艺术:异步FIFO设计”。

参考资料

  1. 硬件架构的艺术
  2. Clock Domain Crossing (CDC) Design & VerificationTechniques Using SystemVerilog
  3. https://bbs.eetop.cn/thread-605419-1-1.html
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/839161
推荐阅读
相关标签
  

闽ICP备14008679号