文章目錄
- 前言
- 時鐘域以及跨時鐘域的概念
- 亞穩態的概念
- 單脈沖信號的跨時鐘域處理
- 從慢時鐘域到快時鐘域的場景
- 從快時鐘域到慢時鐘域的場景
- 參考資料
前言
注:本文首發自易百納技術社區,原文地址:https://www.ebaina.com/articles/140000005331
另外,請近期路過的朋友投個csdn年度博客之星的票,博主需要你的鼓勵,https://bss.csdn.net/m/topic/blog_star2020/detail?username=reborn_lee
本文是上一篇文章FPGA邏輯設計回顧(3)多位元信號上升沿檢測的設計方式與陷阱?的姊妹篇,都是FPGA以及ASIC設計中再重要不過的設計且應用場景十分廣泛,我在以前也分享過類似的設計,但本文在大量參考外文文獻的基礎上,重新立意,重新組織,相信經過時間與設計經驗的積累,會有更清晰更規范的表述,既然是具有分享意義的技術教程,本文分享的RTL設計的原則應是以看得懂、能說明問題為宗旨,不追求復雜隱晦似的“高端大氣”,
時鐘域以及跨時鐘域的概念
通俗地講,時鐘域就是時鐘的管轄范圍,在我的管轄范圍之內的邏輯由我來提供時鐘,這樣一來,跨時鐘域就是資料在不同時鐘域之間的互動!從一個時鐘域到另一個時鐘域,環境(時鐘快慢、相位)可能不同,這樣就可能導致一系列的問題,輕則亞穩態(水土不服生病),重則影子都沒(對方檢測不到,當成不存在)了,
上面的描述主要讓你更通俗地了解時鐘域以及跨時鐘域的意思,但是上不了“臺面”,寫到答卷上要斟酌,一般來說, 邏輯設計中將所有同步元件(例如觸發器和RAM等)使用相同時鐘信號的部分稱為時鐘域 ,
各大FPGA廠家的FPGA編譯工具(這是習慣性叫法)在邏輯綜合以及實作之后都會出一個時序報告,里面就有跨時鐘域的報告,在里面你可以看到你有哪些信號進行了跨時鐘域,如下圖為Vivado工具的報告位置:

對于這些跨時鐘域的情況,一般我們要在邏輯設計的時候就解決,當然之后也要對其進行約束,一般可以設定為false path等,即讓綜合工具不要機關算盡般去布局布線讓時序滿足要求(這會拖慢編譯時間,當然時序也不會成功),設定為false path或者時鐘組之后,工具默認不對其進行時序分析(因為設計中已經解決了跨時鐘域的問題,這也就是為什么輸跨時鐘域問題是設計解決的,而不是約束解決的),
亞穩態的概念
上面說了如果信號從一個時鐘域到達另一個時鐘域會出現一系列問題,就像人因水土不服一樣,信號也會“生病”,最常見的癥狀就是亞穩態!什么是亞穩態呢?
對于這個問題,我覺得使用一個簡單的示例來解釋會更清晰,如下圖:

可能的場景可以為在時鐘域A中生成了一個使能信號En_Out去觸發時鐘域B內的演算法,總之時鐘域B內的邏輯需要時鐘域A中生成的使能控制信號,
注意:時鐘域A中的使能信號En_Out有效狀態至少持續一個時鐘周期的時間,否則稱不上脈沖,只能叫做毛刺!
假設時鐘1(clk1)和時鐘2(clk2)的波形關系以及使能信號En_Out的波形圖如下:

上圖還給出了各個信號之間的時序關系,Tclk-to-Q,DFF1的含義為信號從被clk1采樣到輸出(En_Out是輸出)之間的延遲(器件是有延遲的,觸發器也不例外!)Tsetup,DFF2的含義是觸發器DFF2的建立時間(setup time)要求,被采樣信號不要在時鐘的建立時間范圍內翻轉(變化),才能滿足觸發器的建立時間要求,否則就可能導致亞穩態,
可見,上述時序圖滿足關系

即信號En_Out的翻轉時刻不在時鐘clk2的建立時間范圍內,滿足建立時間要求,因此可以正常采樣,不存在亞穩態問題,
為了更清晰的描述,下圖用箭頭和標號給出了上述描述的示意,幫助理解,

但是問題是不一定都像上述設計那樣幸運,如果二者的時鐘關系如下呢?

上述的時序引數應該不需要我重復了,可以清楚地看到兩個時鐘之間的上升沿很近,這會導致時鐘clk1下的信號輸出變化時刻出現在時鐘clk2的建立時間范圍內,導致不滿足時鐘clk2的建立時間,而就是這個不滿足建立時間,就會導致亞穩態,
導致這種情況出現的原因可能還不只進行資料互動的時鐘采樣沿過近這一條,還可能下圖的情況:

也就是說,時鐘二者之間的布線延遲過大(上面的分析未考慮),導致信號En_Out的翻轉處傳輸到時鐘clk2時,進入了DFF2的建立時間范圍,
下面我們總結,如果不滿足建立時間要求導致的亞穩態會導致輸出的結果是怎么樣,如下圖:

-
作為第一種情況,假設DFF2的輸出值在t=t2的clk2上升沿時進入邏輯高電平, 在這種情況下,雖然我們有建立時間違規,但沒有錯誤,并且觸發器包含有效資料,資料的轉換符合預期,沒有錯誤,
-
第二種情況:假設在t= t2的clk2上升沿時,DFF2輸出轉為邏輯低電平,在這種情況下,使能信號在B時鐘域沒有成功采樣,然而,這不會是一個問題,因為En_Out來自A時鐘域,它將至少在clk1的一個周期內為高電平, 因此,下一個上升沿clk2在 t = t 3 將正確地采樣En_Out值,對于這個時鐘邊沿,DFF2的時序要求將得到滿足,因為En_Out的變化沒有超過clk2的一個周期,在這種情況下,我們對En_Out的采樣比它實際轉換的時間晚了一個時鐘周期,然而,這并不是一個問題,因為兩個時鐘域的時鐘被假設為獨立的,我們沒有對En_Out信號的到達時間做任何假設,如下圖:

- 接下來假設DFF2暫存器進入了亞穩態狀態,在這種情況下,暫存器的輸出變成了介于邏輯高電壓和邏輯低電壓之間的電壓,但這只是暫時的,觸發器最終將退出metastable狀態,進入邏輯高或邏輯低狀態,退出亞穩態所需的時間被稱為resolution Time(Tr),

如上圖的綠色方框部分就是resolution time,定義為處于不穩定狀態的時間或者退出亞穩態的時間,可以翻譯為決斷時間,其實從字面意思上也挺好理解的,決斷即決定輸出是高還是低的時間,一旦過了這個時間,輸出的電平狀態就確定了,
最后,我們對亞穩態進行總結:
在任何設計中使用的每個觸發器(FF)都有一個指定的設定和保持時間,或者說在采樣時鐘邊沿之前和之后,資料輸入在規則上不允許改變的時間,
由于DFF2的輸入資料在建立時間內發生了變化,暫存器的行為將是不可預測的, 由于建立時間的違反,暫存器的輸出電壓可能是代表邏輯高、邏輯低,甚至更糟糕的是介于邏輯高和邏輯低之間的電壓,
這三種情況都有可能發生,而輸入資料在相應的時鐘邊沿實際上是邏輯高電平,同樣,當違反暫存器保持時間時,即En_Out在暫存器保持時間定義的活動時鐘邊沿后的時間視窗內發生變化時,暫存器的輸出值將無法預測,
亞穩態是指觸發器無法在特定時間內達到已知狀態,當觸發器進入亞穩狀態時,您既無法預測元件的輸出電壓電平,也無法預測輸出何時將穩定至正確的電壓電平,在此穩定時間內,觸發器的輸出處于某個中間電壓電平,或者可能振蕩,并且可以級聯無效的輸出電平,以使觸發器在信號路徑的更下方,
既然跨時鐘域傳輸會有可能出現亞穩態,那么如何解決這個問題呢?答案我們接著看下面的小專題,單脈沖信號的跨時鐘域處理,就是為了解決這個問題且不只是解決這一個問題而生,
單脈沖信號的跨時鐘域處理
所謂的單脈沖信號,就是單位元信號,如上一個小標題中的使能控制信號等!對于這類信號的跨時鐘域處理,有兩種場景:
- 一種是上一個小標題 亞穩態的概念 中講到的從慢時鐘域到快時鐘域的脈沖信號處理,如圖:

- 另一種恰好相反,是從快時鐘域到慢時鐘域之間進行跨時鐘域傳輸的脈沖信號處理,

針對這兩種情況,我們分別討論這兩種情況的跨時鐘域的處理方式!
從慢時鐘域到快時鐘域的場景
為了表述方便,我們約定一個原則吧,將時鐘分為源時鐘和目的時鐘,如下圖:

在這個小標題下,源時鐘就是慢時鐘,目的時鐘就是快時鐘,將源時鐘域(慢)內的單位元信號同步到另一個目的時鐘域(快),我們默認單位元脈沖信號在源時鐘域內已經被本地時鐘控制的暫存器同步(例如DFF1),這是因為經過時鐘同步后信號不僅與時鐘保持同步,而且有利于時序優化(時序路徑為兩個時鐘元件之間的資料路徑,使用觸發器同步,無疑將長的資料路徑截短,有利于時序通過),這也是我們推薦的一種設計習慣;
信號從慢時鐘域同步到快時鐘域,在目的時鐘域是一定能采樣到的,只不過可能會出現亞穩態的結果,針對這種場景下出現的亞穩態,我們的處理方式是兩級暫存器同步,也就是通常我們說的使用目的暫存器對源時鐘域的脈沖信號”打兩拍“!

兩級暫存器同步能夠將最終輸出信號發生亞穩態發生的幾率降低到很低的量級,可以大致地認為“消除了”亞穩態,
可能還需要注意的是:
在一個完整的兩級暫存器同步電路中,信號跨時鐘域應從原時鐘域的原點觸發器傳遞到同步器的第一觸發器,而不需要經過原點觸發器和同步器的第一觸發器之間的任何組合邏輯,如下圖:

針對這張圖:

我們給出RTL語言描述(加入了復位信號):
module dff2(
input wire dat ,
input wire aclk ,
input wire bclk ,
input wire rst ,
output reg bdat2
);
reg adat ;
reg bdat1 ;
//源時鐘同步輸入脈沖信號,改善時序
always@(posedge aclk or posedge rst) begin
if(rst) begin
adat <= 1'b1 ;
end
else begin
adat <= dat ;
end
end
//兩級暫存器同步,“消除”亞穩態
always@(posedge bclk or posedge rst) begin
if(rst) begin
bdat1 <= 1'b1 ;
bdat2 <= 1'b1 ;
end
else begin
bdat1 <= adat ;
bdat2 <= bdat1 ;
end
end
endmodule
綜合后RTL原理圖:

從快時鐘域到慢時鐘域的場景
上個小標題講過了從慢時鐘域到快時鐘域的場景,在那種場景下,目的時鐘域的暫存器一定能采樣到源時鐘域脈沖信號,只是不能保證不會出現亞穩態,因此采樣兩級暫存器同步的方式降低亞穩態發生的概率,
現在這個小標題下,我們來總結從快時鐘域到慢時鐘域的跨時鐘域場景,如下:

同步一個單位元信號到頻率較慢的時鐘域,比較麻煩,因為這存在目的時鐘域檢測不到源時鐘域脈沖的情況,如下圖:

上圖可以清晰地看到目的時鐘域在時鐘上升沿采樣不到源時鐘域信號,如何才能讓目的時鐘域能夠檢測到源時鐘域信號呢?
我們首先應該會想到展寬源時鐘域信號,這里的展寬需要犧牲一點東西,例如展寬高電平信號就得犧牲低電平持續的時間,同理展寬低電平,就得犧牲一點高電平的時間,不過這都不是事,因為我們既然要展寬,肯定是展寬我們需要使用的電平信號,
如何做到呢?
這通常需要在每個時鐘域中設定一個暫存器,從目的時鐘域向源時鐘域反饋一種形式,表示檢測到了信號,
不好理解嗎?
下面我們結合時序圖來理解我們的處理方式:

針對這一處理程序,我們使用RTL硬體描述語言描述:
module fast2slow_CDC(
input wire clk1 ,
input wire clk2 ,
input wire rst ,
input wire pulse_clk1 ,
output wire pulse_syn_clk2
);
reg pulse_wide_clk2 ;
reg reg1_pulse_wide_clk2 ;
reg reg1_pulse_wide_clk1 ;
reg reg2_pulse_wide_clk1 ;
//生成脈沖展寬信號
reg pulse_wide_clk1 ;
always@(posedge clk1 or posedge rst) begin
if(rst) begin
pulse_wide_clk1 <= 1'b0 ;
end
else if(pulse_clk1) begin
pulse_wide_clk1 <= 1'b1 ;
end
else if(reg2_pulse_wide_clk1) begin
pulse_wide_clk1 <= 1'b0 ;
end
else begin
pulse_wide_clk1 <= pulse_wide_clk1 ;
end
end
//在目的時鐘域內采樣展寬后的信號
always@(posedge clk2 or posedge rst) begin
if(rst) begin
pulse_wide_clk2 <= 1'b0 ;
reg1_pulse_wide_clk2 <= 1'b0 ;
end
else begin
pulse_wide_clk2 <= pulse_wide_clk1 ;
reg1_pulse_wide_clk2 <= pulse_wide_clk2 ;
end
end
//在源時鐘域內同步目的時鐘域內的展寬信號,以生成反饋信號
always@(posedge clk1 or posedge rst) begin
if(rst) begin
reg1_pulse_wide_clk1 <= 1'b0 ;
reg2_pulse_wide_clk1 <= 1'b0 ;
end
else begin
reg1_pulse_wide_clk1 <= reg1_pulse_wide_clk2 ;
reg2_pulse_wide_clk1 <= reg1_pulse_wide_clk1 ;
end
end
assign pulse_syn_clk2 = reg1_pulse_wide_clk2;
endmodule
給出RTL原理圖:

其實這種方式的效果是不容懷疑的,但是如果你還是不放心,我還是簡單寫個仿真平臺仿真下吧:

可以看到,這種設計很不錯,效果很好,但是它也有自身的局限性:就是不能對過近的脈沖進行完美的同步,如下圖:

可見,兩個源時鐘域的脈沖距離太近,導致目的暫存器無法完全同步;
如果距離足夠遠,就可以完美同步:

盡管如此,這種方式也不失為一種很好的脈沖同步方式,因為大部分場景都是適用的,好了,這篇文章到此也快告一段落了,花了很久來重新構思,目前已經將近凌晨三點,手也凍僵了,好在自配高端臺式電腦性能不錯,跑Vivado也很快,可以邊實驗邊寫文章,
最后附出仿真平臺:
module cdc_tb(
);
reg clk1 ;
reg clk2 ;
reg rst ;
reg pulse_clk1 ;
wire pulse_syn_clk2 ;
initial begin
clk1 = 0;
forever
#3 clk1 = ~clk1;
end
initial begin
clk2 = 0;
forever
#5.5 clk2 = ~clk2;
end
initial begin
rst = 1;
pulse_clk1 = 0;
#15
rst = 0;
#15
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
#3 //距離太近
#59.5 //距離足夠遠
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b1;
end
@(posedge clk1) begin
pulse_clk1 = #0.5 1'b0;
end
end
fast2slow_CDC u_fast2slow_CDC(
.clk1 ( clk1 ),
.clk2 ( clk2 ),
.rst ( rst ),
.pulse_clk1 ( pulse_clk1 ),
.pulse_syn_clk2 ( pulse_syn_clk2 )
);
endmodule
本系列文章還未結束,下一篇來一起處理多位元信號的跨時鐘域問題!
參考資料
Synchronizer techniques for multi-clock domain SoCs & FPGAs
Verifying Clock Domain Crossing
Crossing Clock Domains in an FPGA
Crossing clock domains
Clock-Domain Crossing (CDC)
Get those clock domains in sync
Introduction to Clock Domain Crossing: Double Flopping
Understanding clock domain crossing issues
Clock Domain Crossing (CDC) : Asynchronous communications across boundaries
Clock Domain Crossing
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/250144.html
標籤:其他
下一篇:運動影像分割-前景目標提取
