文章主要是基於學習后的總結。
1. 時鍾域
假如設計中所有的觸發器都使用一個全局網絡,比如FPGA的主時鍾輸入,那么我們說這個設計只有一個時鍾域。假如設計有兩個輸入時鍾,如圖1所示,一個時鍾給接口1使用,另一給接口2使用,那么我們說這個設計中有兩個時鍾域。

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

一般解決信號亞穩態有三種方法:
- 相位控制
相位控制技術可以在一個時鍾頻率是另外一個時鍾的數倍,並且其中一個時鍾可以由FPGA 內部PLL 或者DLL 控制時使用。 - 多級寄存器
一般針對單bit控制信號跨越兩個異步時鍾域傳輸,可以采用多級寄存器,俗稱多打拍。同步電路中的第一拍后也許會產生亞穩態,但是信號有機會在其被第二級寄存以及被其它邏輯看到之前穩定下來。常用的就是對單bit信號打兩拍,這也是最簡單、最常見的處理方式。 - 異步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 設計分區同步器模塊
在頂層為設計分區是一個好的設計實踐行為,這樣任何功能模塊外面都包含一個獨立的同步器模塊。這樣有利於在划分模塊的基礎上實現所謂的理想時鍾域情況(即整個設計模塊只有一個時鍾),如下圖所示:

對設計進行分區有很多理由。首先,對每個獨立的功能模塊進行時序分析變得簡易,因為模塊都是完全的同步設計。其次,整個同步模塊中的時序例外也很容易得到定義。再次,底層模塊的同步器加時序例外在代入到設計頂層時,大大降低了由於人為失誤造成的疏漏。所以,同步寄存器應該在功能模塊外單獨分區。
參考:
Verilog基本電路設計之一(單bit跨時鍾域同步)
異步時鍾的同步化,俗稱“慢打一拍",寄存一拍
高級FPGA設計:第六章 時鍾域(譯文)
