跨時鍾域


跨時鍾域處理是FPGA設計中經常遇到的問題,而如何處理好跨時鍾域間的數據,可以說是每個FPGA初學者的必修課。如果是還是在校的學生,跨時鍾域處理也是面試中經常常被問到的一個問題。

在本篇文章中,主要介紹3種跨時鍾域處理的方法,這3種方法可以說是FPGA界最常用也最實用的方法,這三種方法包含了單bit和多bit數據的跨時鍾域處理,學會這3招之后,對於FPGA相關的跨時鍾域數據處理便可以手到擒來。

本文介紹的3種方法跨時鍾域處理方法如下:
1.打兩拍
2.異步雙口RAM
3.格雷碼轉換

第一種方法:打兩拍

大家很清楚,處理跨時鍾域的數據有單bit和多bit之分,而打兩拍的方式常見於處理單bit數據的跨時鍾域問題。

打兩拍的方式,其實說白了,就是定義兩級寄存器,對輸入的數據進行延拍。如下圖所示。

 

 



先簡單說下兩級寄存器的原理:兩級寄存是一級寄存的平方,兩級並不能完全消除亞穩態危害,但是提高了可靠性減少其發生概率。總的來講,就是一級概率很大,三級改善不大。

這樣說可能還是有很多人不夠完全理解,那么請看下面的時序示意圖:

 

 



data是時鍾域1的數據,需要傳到時鍾域2(clk)進行處理,寄存器1和寄存器2使用的時鍾都為clk。假設在clk的上升沿正好采到data的跳變沿(從0變1的上升沿,實際上的數據跳變不可能是瞬時的,所以有短暫的跳變時間),那這時作為寄存器1的輸入到底應該是0還是1呢?這是一個不確定的問題。所以Q1的值也不能確定,但至少可以保證,在clk的下一個上升沿,Q1基本可以滿足第二級寄存器的保持時間和建立時間要求,出現亞穩態的概率得到了很大的改善。

如果再加上第三級寄存器,由於第二級寄存器對於亞穩態的處理已經起到了很大的改善作用,第三級寄存器在很大程度上可以說只是對於第二級寄存器的延拍,所以意義是不大的。

第二種方法:異步雙口RAM

處理多bit數據的跨時鍾域,一般采用異步雙口RAM。假設我們現在有一個信號采集平台,ADC芯片提供源同步時鍾60MHz,ADC芯片輸出的數據在60MHz的時鍾上升沿變化,而FPGA內部需要使用100MHz的時鍾來處理ADC采集到的數據(多bit)。

在這種類似的場景中,我們便可以使用異步雙口RAM來做跨時鍾域處理。先利用ADC芯片提供的60MHz時鍾將ADC輸出的數據寫入異步雙口RAM,然后使用100MHz的時鍾從RAM中讀出。

對於使用異步雙口RAM來處理多bit數據的跨時鍾域,相信大家還是可以理解的。當然,在能使用異步雙口RAM來處理跨時鍾域的場景中,也可以使用異步FIFO來達到同樣的目的。

第三種方法:格雷碼轉換

對於第三種方法,Kevin在大學里邊從沒接觸過,也是在工作中才接觸到。
我們依然繼續使用介紹第二種方法中用到的ADC例子,將ADC采樣的數據寫入RAM時,需要產生RAM的寫地址,但我們讀出RAM中的數據時,肯定不是一上電就直接讀取,而是要等RAM中有ADC的數據之后才去讀RAM。這就需要100MHz的時鍾對RAM的寫地址進行判斷,當寫地址大於某個值之后再去讀取RAM。

在這個場景中,其實很多人都是使用直接用100MHz的時鍾於RAM的寫地址進行打兩拍的方式,但RAM的寫地址屬於多bit,如果單純只是打兩拍,那不一定能確保寫地址數據的每一個bit在100MHz的時鍾域變化都是同步的,肯定有一個先后順序。如果在低速的環境中不一定會出錯,在高速的環境下就不一定能保證了。所以更為妥當的一種處理方法就是使用格雷碼轉換。

對於格雷碼,相鄰的兩個數間只有一個bit是不一樣的(格雷碼,在本文中不作詳細介紹),如果先將RAM的寫地址轉為格雷碼,然后再將寫地址的格雷碼進行打兩拍,之后再在RAM的讀時鍾域將格雷碼恢復成10進制。這種處理就相當於對單bit數據的跨時鍾域處理了。對於格雷碼與十進制互換的代碼,僅提供給大家作參考:

 

 


代碼使用的是函數的形式,方便調用,op表示編碼或者譯碼,WADDRWIDTH和RADDRWIDTH表示位寬。 

 

 

 

 

文章主要是基於學習后的總結。

1. 時鍾域

假如設計中所有的觸發器都使用一個全局網絡,比如FPGA的主時鍾輸入,那么我們說這個設計只有一個時鍾域。假如設計有兩個輸入時鍾,如圖1所示,一個時鍾給接口1使用,另一給接口2使用,那么我們說這個設計中有兩個時鍾域。

2. 亞穩態

觸發器的建立時間和保持時間在時鍾上升沿左右定義了一個時間窗口,如果觸發器的數據輸入端口上數據在這個時間窗口內發生變化(或者數據更新),那么就會產生時序違規。存在這個時序違規是因為建立時間要求和保持時間要求被違反了,此時觸發器內部的一個節點(或者要輸出到外部的節點)可能會在一個電壓范圍內浮動,無法穩定在邏輯0或者邏輯1狀態。換句話說,如果數據在上述窗口中被采集,觸發器中的晶體管不能可靠地設置為邏輯0或者邏輯1對應的電平上。所以此時的晶體管並未處於飽和區對應的高或者低電平,而是在穩定到一個確定電平之前,徘徊在一個中間電平狀態(這個中間電平或許是一個正確值,也許不是)。如圖2所示,這就是所謂的亞穩態。

一般解決信號亞穩態有三種方法:

  1. 相位控制
    相位控制技術可以在一個時鍾頻率是另外一個時鍾的數倍,並且其中一個時鍾可以由FPGA 內部PLL 或者DLL 控制時使用。
  2. 多級寄存器
    一般針對單bit控制信號跨越兩個異步時鍾域傳輸,可以采用多級寄存器,俗稱多打拍。同步電路中的第一拍后也許會產生亞穩態,但是信號有機會在其被第二級寄存以及被其它邏輯看到之前穩定下來。常用的就是對單bit信號打兩拍,這也是最簡單、最常見的處理方式。
  3. 異步FIFO緩存
    一般用於跨時鍾域傳輸數據,寫端和讀端分別對應兩個時鍾域,由空/滿信號控制着讀寫過程,實現數據的跨域傳輸。

每種方法應對的情況不同,下面着重介紹最常用的單bit信號消除亞穩態的方法:多級觸發器法。


3. 多級寄存器處理

在全同步設計中,如果信號來自同一時鍾域,各模塊的輸入不需要使用寄存器來寄存。只要滿足建立時間和保持時間的約束,可以保證在時鍾上升沿到來時,輸入信號已經穩定,可以采樣得到正確的值。但是如果要采用輸入信號的邊沿來觸發某一過程,則需要寄存來檢測上升沿,這是另外一個范疇的問題。

一般而言單bit信號就是我們所用到的脈沖信號或者電平信號。假設A和B是兩個時鍾域,各自的頻率是clk_a和clk_b,clk_a的頻率高於clk_b(同頻相位差穩定的,不在討論范圍內),那么單bit信號傳輸分為兩種情況。

3.1 信號從B到A(慢到快)

在時鍾域B下的脈沖信號pulse_b在時鍾域A看來,是一個很寬的“電平”信號會,保持多個clk_a的時鍾周期,所以一定能被clk_a采到。經驗設計采集過程必須寄存兩拍。第一拍將輸入信號同步化,同步化后的輸出可能帶來建立/保持時間的沖突,產生亞穩態。需要再寄存一拍,減少亞穩態帶來的影響。一般來說兩級是最基本要求,如果是高頻率設計,則需要增加寄存級數來大幅降低系統的不穩定性。也就是說采用多級觸發器來采樣來自異步時鍾域的信號,級數越多,同步過來的信號越穩定。

特別需要強調的是,此時pulse_b必須是clk_b下的寄存器信號,如果pulse_b是clk_b下的組合邏輯信號,一定要先在clk_b先用D觸發器(DFF)抓一拍,再使用兩級DFF向clk_a傳遞。這是因為clk_b下的組合邏輯信號會有毛刺,在clk_b下使用時會由setup/hold時間保證毛刺不會被clk_b采到,但由於異步相位不確定,組合邏輯的毛刺卻極有可能被clk_a采到。一般代碼設計如下:

always @ (posedge clk_a or negedge rst_n)
	begin
		if (rst_n == 1'b0) 
			begin
			   pules_a_r1 <= 1'b0;
			   pules_a_r2 <= 1'b0;
			   pules_a_r3 <= 1'b0;
			end
		else 
			begin									//打3拍
			   pules_a_r1 <= pulse_b;
			   pules_a_r2 <= pules_a_r1;
			   pules_a_r3 <= pules_a_r2;
			end
	end

assign pulse_a_pos	= pules_a_r2 & (~pules_a_r3);	//上升沿檢測
assign pulse_a_neg	= pules_a_r3 & (~pules_a_r2);	//下降沿檢測
assign pulse_a		= pules_a_r2;

實際上,具體打幾拍背后是有時序收斂的理論作支撐的,對於一般的設計而言,打兩三拍就已經足夠了。

3.2 信號從A到B(快到慢)

如果單bit信號從時鍾域A到時鍾域B,那么存在兩種不同的情況,傳輸脈沖信號pulse_a或傳輸電平信號level_a。實際上,在一般情況下只有電平信號level_a的寬度能被clk_b采集到才可以保證系統正常工作。那么對於脈沖信號pulse_a采取怎樣的處理方法呢?可以用一個展寬信號來替代pulse_a實現垮時鍾域的握手。

主要原理就是先把脈沖信號在clk_a下展寬,變成電平信號signal_a,再向clk_b傳遞,當確認clk_b已經“看見”信號同步過去之后,再清掉signal_a。代碼通用框架如下:

module Sync_Pulse (
                  clk_a, 		
                  clk_b,   
				  rst_n, 			
                  pulse_a_in, 	
                 
                  pulse_b_out, 	
                  b_out	
                  );
/****************************************************/

	input				clk_a;
	input				clk_b;
	input				rst_n;
	input				pulse_a;
	
	output				pulse_b_out;
	output				b_out;		
	
/****************************************************/	

	reg					signal_a;
	reg					signal_b;
	reg					signal_b_r1;
	reg					signal_b_r2;
	reg					signal_b_a1;
	reg					signal_b_a2;
	
/****************************************************/
	//在時鍾域clk_a下,生成展寬信號signal_a
	always @ (posedge clk_a or negedge rst_n)
		begin
			if (rst_n == 1'b0)
				signal_a <= 1'b0;
			else if (pulse_a_in)			//檢測到到輸入信號pulse_a_in被拉高,則拉高signal_a
				signal_a <= 1'b1;
			else if (signal_b_a2)			//檢測到signal_b1_a2被拉高,則拉低signal_a
				signal_a <= 1'b0;
			else;
		end
	
	//在時鍾域clk_b下,采集signal_a,生成signal_b
	always @ (posedge clk_b or negedge rst_n)
		begin
			if (rst_n == 1'b0)
				signal_b <= 1'b0;
			else
				signal_b <= signal_a;
		end
	//多級觸發器處理
	always @ (posedge clk_b or negedge rst_n)
		begin
			if (rst_n == 1'b0) 
				begin
					signal_b_r1 <= 1'b0;
					signal_b_r2 <= 1'b0;
				end
			else 
				begin
					signal_b_r1 <= signal_b;		//對signal_b打兩拍
					signal_b_r2 <= signal_b_r1;
				end
		end
	//在時鍾域clk_a下,采集signal_b_r1,用於反饋來拉低展寬信號signal_a
	always @ (posedge clk_a or negedge rst_n)
		begin
			if (rst_n == 1'b0) 
				begin
					signal_b_a1 <= 1'b0;
					signal_b_a2 <= 1'b0;
				end
			else 
				begin
					signal_b_a1 <= signal_b_r1;		//對signal_b_r1打兩拍,因為同樣涉及到跨時鍾域	
					signal_b_a2 <= signal_b_a1;
				end
		end

	assign	pulse_b_out =	signal_b_r1 & (~signal_b_r2);
	assign	b_out		=	signal_b_r1;

endmodule

這樣一來,實際上clk_a下的脈沖信號“作用”到了clk_b時鍾域下,它對於clk_a與clk_b的時鍾頻率關系沒有任何限制,快到慢,慢到快就都沒問題了。

總而言之,在設計中可以簡單的牢記以下五條原則:
1. 再全局時鍾的跳變沿最可靠。
2. 來自異步時鍾域的輸入需要寄存一次以同步化,再寄存一次以減少亞穩態帶來的影響。
3. 不需要用到跳變沿的來自同一時鍾域的輸入,沒有必要對信號進行寄存。
4. 需要用到跳變沿的來自同一時鍾域的輸入,寄存一次即可。
5. 需要用到跳變沿的來自不同時鍾域的輸入,需要用到3個觸發器,前兩個用以同步,第3個觸發器的輸出和第2個的輸出經過邏輯門來判斷跳變沿。

3.3 設計分區同步器模塊

在頂層為設計分區是一個好的設計實踐行為,這樣任何功能模塊外面都包含一個獨立的同步器模塊。這樣有利於在划分模塊的基礎上實現所謂的理想時鍾域情況(即整個設計模塊只有一個時鍾),如下圖所示:

對設計進行分區有很多理由。首先,對每個獨立的功能模塊進行時序分析變得簡易,因為模塊都是完全的同步設計。其次,整個同步模塊中的時序例外也很容易得到定義。再次,底層模塊的同步器加時序例外在代入到設計頂層時,大大降低了由於人為失誤造成的疏漏。所以,同步寄存器應該在功能模塊外單獨分區。


 

 

前言:本系列將對sunburst design網站的2008最佳文章《Clock Domain Crossing (CDC) Design & Verification Techniques Using SystemVerilog》進行翻譯和基於自我理解的分析闡述,本文將介紹打兩拍同步器

同步器

“同步器是一種對異步信號進行采樣並輸出與跳變同步到本地或采樣時鍾的信號版本的設備。”

數據同步時分兩種情況:

  1. 可以不采集所有的數據,但必須要正確
  2. 必須每一個采集到的信號都是正確的

情況1對應的比如異步FIFO,格雷碼轉換時不需要每個數據都采集正確,只需要在最后空滿判斷時采集到正確數據即可。但是有可能在中間由於采集到錯誤數據導致誤判空滿,但是概率較低。

情況2時就需要對每個數據進行傳輸之前,都需要進行正確識別,或者通過握手的方式進行傳輸,保證數據的正確性。

握手應該是最穩定的數據傳輸手段,尤其是全握手,其數據安全性要比單比特數據打拍傳輸更安全,這也許是異步FIFO更多采用格雷碼方式跨時鍾域的原因,而不是全握手,為了平衡性能與安全性。

打兩拍同步器

我們預期的是在第一級寄存器中采樣到亞穩態信號,但是在第二級寄存器可以采集到正確數據,如下圖所示。

但是,也有可能第一級采集到錯誤數據,第二級仍然采集到亞穩態數據,並將錯誤結果傳播到后級,這個時候就引入了概率計算公式MTBF(mean time before failure)。

MTBF

MTBF是概率公式,該公式代表數據在跨時鍾域時,經過第一級寄存器產生亞穩態數據,由於數據不穩定,同步到第二級寄存器時也可能產生亞穩態數據,所以該公式就是計算這種連續兩級寄存器同步后結果仍然是錯誤的概率。MTBF數值越大,代表兩次失敗間隔越長,相反,越短代表錯誤發生頻率越高。

Dally和Poulton給出了一個很好的方程式,並對計算進行了非常徹底的分析,可以計算出同步電路的MTBF。 在不重復方程式和分析的情況下,應該指出,直接影響同步器電路MTBF的兩個最重要因素是采樣時鍾頻率(將信號采樣到接收時鍾域的速度)和數據更改頻率(跨越CDC邊界的數據更改速度有多快)。公式如下圖所示:

由上圖可知,數據原時鍾頻率和數據更改頻率(跨越CDC邊界的數據更改速度有多快),MTBF越小,錯誤概率越高。

打三拍

對於某些非常高速的設計,如果打兩拍時間太短,可以考慮打三拍來降低MTBF發生概率。

------------------------------------END----------------------------------------


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM