< 老李>
使用double flop來同步,有個最基本的“3個沿”要求,就是source data必須保證穩定不變至少碰見destination clock 3個連續的沿,這個沿可以是上升沿也可以是下降沿,持續3個沿之後才能變,否則就有可能在destination clock domain根本看不到這個data的變化。 例如下圖所示:adata第一次變高,只碰到了bclk的2個沿,就可能導致bdata根本沒有看到這個pulse,而第二次adata變高,持續了3個沿,這樣bdata就能夠確保也可以變高了。
always_ff @(posedge clk) begin
if (rst) {rptr_t2,rptr_t1} <= 0;
else {rptr_t2,rptr_t1} <= {rptr_t1,rptr};
end
pulse信號在設計當中很常見,通常在某個時鐘沿變高,在下一個時鐘沿變低。 我們上期舉了下面的例子,可見當aclk頻率比bclk頻率高的時候,adata變高一個週期,那麼有可能bclk的時鐘沿根本看不到這個變化,或者有時候即使能被bclk采到一次,也可能無法滿足3個沿的要求,導致無法用常見的2flop synchronizer來去sync。
那我們應該要怎麼解呢?
module sync_pulse (
input aclk, arst,
input bclk, brst,
input Ain,
output logic Bout
);
logic Aintog, syn_Aintog_tmp, syn_Aintop, reg_syn_Aintop;
assign Bout = syn_Aintop ^ reg_syn_Aintop; // q 1T pulse
//toggle flop
always_ff @(posedge aclk or posedge arst) begin
if (arst) Aintog <= 1'd0;
else Aintog <= Aintog ^ Ain;
end
//sync 2ff
always_ff @(posedge bclk or posedge brst) begin
if (brst) syn_Aintog_tmp <= 1'd0;
else syn_Aintog_tmp <= Aintog;
end
always_ff @(posedge bclk or posedge brst) begin
if (brst) syn_Aintop <= 1'd0;
else syn_Aintop <= syn_Aintog_tmp;
end
//reg syn_Aintog
always_ff @(posedge bclk or posedge brst) begin
if (brst) reg_syn_Aintop <= 1'd0;
else reg_syn_Aintop <= syn_Aintop;
end
endmodule
既然這個pulse synchronizer中間利用了2flop,那麼2flop的3edge要求就必須要滿足,換句話說,我們轉化成為的level的信號Tq要足夠長。 如果Tq不滿足bclk的3edge要求,那麼這個level信號我們就無法同步過去,也就無法產生bclk的pulse了。 而Tq每次變化是由於aclk來了一個新的pulse,這也就是要求aclk的連續兩個pulse之間的間隔要足夠大,要滿足bclk的3edge要求。
aclk時鐘域最接近的兩個pulse能靠多近呢,顯然就是兩個pulse中間只有一個aclk週期,這其實就是將aclk進行了2分頻。 那麼相應的,Tq就是對aclk進行了4分頻,每個Tq的level持續時間是2個aclk cycle,這2個cycle需要滿足bclk的3edge要求,如果滿足,那麼就可以保證bclk域也可以產生每個cycle的pulse的要求。
如果無法滿足,那麼我們就要另想辦法,如果還是要使用pulse synchronizer這個電路,我們繼續祭出反饋大法。
可是這個反饋的辦法要求每個pulse產生都要同步回來,這樣做的效率不高,有沒有更快的辦法呢? 我們想像有一個細長的管子,管子的一頭我們往裡面塞玻璃球,每塞進去個玻璃球對應一個aclk時鐘域的pulse,管子的另一頭我們往外彈玻璃球,每彈出一個玻璃球對應一個bclk時鐘域的pulse。 (哎,是不是嗅到了一點FIFO的味道了? )那麼除非假定管子無限長,那麼我們往管子里塞進球的平均速率不能永遠大於從管子里彈出球的速率,我們可以允許某一段時間塞進球的速率高,管子作為一個緩衝區,來容納之前塞入但是還沒有彈出的球,但是當管子塞滿的時候,我們必須得停下塞進球的動作,等待彈出球的那邊彈出球之後才能騰出空間來繼續塞。 也就是說,當管子不是無限長時,儘管兩邊的時鐘速率不同,能夠保持一一對應的條件是兩邊塞球彈球的平均速率是一樣的。 這就是利用FIFO來解決的思路。 如果FIFO告訴aclk說FIFO滿了,那麼aclk域就得停止產生pulse,如果不滿,就可以繼續產生pulse,最後我們把FIFO裡面的元素完全彈完,就可以做到一一對應了。
How to slove ???
第一,要有專門的邏輯保證aload為高的時候data_aclk不變。
第二,在aload為1'b1的時候,data_bclk會持續load data_aclk, aload從0—>1是ok的,但是1—>0會發生錯誤,因為data_aclk是不穩定的!
第三,aclk時鐘域怎麼知道data_aclk已經被成功傳到bclk時鐘域,從而可以更新下一組data了呢?
How to solve???
我們首先看如何解決第二個問題。 我們其實需要的是:當load_aclk變高的時候,把data_aclk當前的值同步過去之後就行了,並不需要持續load。 這個時候我們上一篇講的pulse synchronizer就派上用場了,我們讓load_aclk是一個pulse,然後把這個pulse同步過去,這樣data_bclk只會load一次。
可是這個還是沒有辦法解決第三個問題,要解決它,我們只能繼續引入反饋大法:把信號從bclk時鐘域反饋回來,告訴aclk時鐘域load成功,可以更新下一個數據了。 如下圖所示,aclk時鐘域的load_aclk是由一個valid/ready的握手邏輯產生。 我們可以把load_bclk再利用pulse synchronizer同步回去,從而讓ready_aclk為1,這樣我們就知道data_aclk肯定已經被同步到了bclk時鐘域,可以更新下一個data了。
always_ff @(posedge clk2) begin
if(rst2) begin
wtocnt1 <= 32'b0;
wtocnt2 <= 32'b0;
wtocnt3 <= 32'b0;
end
else begin
wtocnt1 <= wtocnt;
wtocnt2 <= wtocnt1;
wtocnt3 <= wtocnt2;
end
end
需要data變化的頻率不高的狀況
異步 FIFO = push 和 pop 在不同的時域
對pop來說FIFO是不是空的是重點,對push來說就是滿不滿才是重點
Gray code
連結 : https://www.youtube.com/watch?v=4FMmDabL4wY
老李所說的軸對稱是什麼意思呢? 請看Gray code前兩個數4'b0000, 4'b0001,它們倆之間可以畫一條對稱軸,第1-3位都是相同的。 再看前4個數,在4'b0001和4'b0011之間畫一條對稱軸,第2、3位是相同的,第0位則是軸對稱的。
bnary to Gray
g(3) = b(3) ^ 0
g(2) = b(3) ^ b(2)
g(1) = b(2) ^ b(1)
g(0) = b(1) ^ b(0)
g(n) = b(n+1) ^ b(n)
Gray to Binary
b(3) = g(3)
b(2) = g(3) ^ g(2)
b(1) = g(3) ^ g(2) ^ g(1)
b(0) = g(3) ^ g(2) ^ g(1) ^ g(0)
關鍵就在於它的第一個特點:相鄰兩個編碼之間有且只有1位不同。 我們說multi-bit如果在一個時鐘沿有多個bit同時翻轉,在另外一個時鐘域采到的時候由於2flop 穩定需要1個或2個週期,所以可能會出現錯誤的值。 Gray code這種編碼,從根本上就沒有這個問題,因為以Gray code編碼作為計數器,每個時鐘沿來的時候只會有1個bit發生了翻轉,其餘所有bit都是穩定的! 這樣即使這一個bit在用2flop synchronizer同步到另外一個時鐘域時,可能需要1個周期發生變化,或者2個週期,在發生變化前,另一個域的值就是之前的穩定值,變化后就是新的值,而不會出現其他不該出現的值。
我們需要做的就是在同步pointer的時候把binary pointer 轉化為gray code pointer,然後用2flop synchronizer同步到對面時鐘域之後,再來判斷空滿。
假設FIFO一開始一直寫,不讀,寫滿8個entry後write pointer 的binary變成4'b1000
, gray code是4'b1100
, 而read pointer的gray code是4'b0000
,可以看到高兩位是相反的,之後的低位是相同的。 再舉個例子,假設write pointer 的gray code到了4'b1011
, 而這個時候read pointer如果是4'b0111
,那麼也是8個entry滿了。
HINT : 差八格 = Gray code中兩個bits不相同 or 前二不同,後二相同。
assign full = (write_ptr_gray[N:N-1] == ~read_ptr_gray[N:N-1]) &&(write_ptr_gray[N-2:0] == read_ptr_gray[N-2:0]);
如果兩個Pointer低位全等,最高位不等,那就是「滿」;
如果兩個Pointer低位全等,最高位也相等,那就是“空”。
那真空假空真滿假滿的問題呢?
https://zhuanlan.zhihu.com/p/24439677
assign Empty = (WR_Pointer_syn[4:0]== RD_Pointer[4:0]);
assign Full = (WR_Ponter[4] != RD_Pointer_syn[4]) && (WR_Ponter[3:0] == RD_Pointer_syn[3:0]);
真滿空
之前我們是在寫時鐘域判斷「滿」信號,在讀時鐘域判斷「空」信號,得到了「假」滿空。
如果我們在寫時鐘域判斷「空」信號,在讀時鐘域判斷「滿」信號,得到的就是「真」滿空!
assign Empty_Real = (WR_Pointer[4:0] == RD_Pointer_syn[4:0]);
assign Full_Real = (RD_Pointer[4] != WR_Pointer_syn[4]) && (RD_Pointer[3:0] == WR_Pointer_syn[3:0]);
eg. depth = 7?
比如從4'd15到4'd0,也只有1位不同。 但是如果不是2的冪次,比如DEPTH=7,那我們怎麼樣來利用Gray code呢? 直接從4'b0000到4'b0101肯定是不行的,因為4'b0101變到4'b0000有兩個bit發生了變化,這樣我們就沒法利用2flop synchronizer來同步了。 解決這個辦法的訣竅其實就是老李上一篇提到的gray code的第二個性質:gray code每一位是有個對稱軸的。 我們可以這樣編碼,addr==0的時候gray code不從4'b0000開始,而是從4'b0001開始,直到4'b1001來wrap around,這樣從4'b1001->4'b0001依然只有一個bit翻轉。 同理,如果是depth=6,那麼我們繼續往裡收縮1位,只利用gray code關於對稱軸兩側的部分編碼,從4'b0011到4'b1011,我們可以看到,這樣的編碼依然可以保證相鄰兩個碼之間只會有1位變化。
一般情況下深度為N=2^n的FIFO其位址的位寬為n,其讀寫位址的位寬也為n。 共有N個儲存單元,若數據位寬為W則該FIFO的容量即為N*W bit。
現在在指標中添加額外的位(extra bit,即位址的MSB)變為n+1bit,該extra bit用於指示寫指標是否遞增並越過最後一個FIFO位址,若越過則將該MSB加1,其它位清零。 對讀指標也進行同樣的操作。 如對於深度為8的FIFO,需要採用3+1bit是計數器:0000-1000、1001-1111,MSB作為折回標誌,而低3位作為位址指標。
那麼判斷機制為:
***如果兩個指標的MSB不同,就說明寫指標比讀指標多折回一次:如r_addr=0000,且w_addr=1000,為滿;
如果兩個指標的MSB相同,就說明兩個指標折回次數相等。 再者其餘位相等(則說明FIFO為空。*
同步reset需要時鐘,而異步reset不需要時鐘。 如果說你的模組需要在沒有時鐘的時候複位,那只有異步reset能夠做到,這也是絕大多數晶元的上電複位信號(PowerOn Reset)以及一些PHY比如USB的內部需要異步reset的原因。 而在一些IP中,如果你可以等到時鐘開始翻轉之後再複位,時鐘開始翻轉之前內部即使沒有複位也沒有關係的話,那麼就可以用同步reset。
syn reset
always_ff @(posedge clk) begin
if (!reset_n) begin
q2 <= 1'b0;
end
else begin
// q2 <= ...
end
end
Asyn reset
always_ff @(posedge clk or negedge reset_n) begin
if(!reset_n) begin
q2 <= 1'b0;
end
else begin
//q2 <= ...
end
end
從綜合出來的邏輯可以看出,異步reset由於對寄存器之間的datapath沒有貢獻,所以在timing上面能夠略微比同步reset好一些,特別是reset信號作為一個負載很大的信號,如果reset tree做得不好可能使得reset path的combo delay變得很大,反而限制了performance的提高。 所以在對logic depth摳得很細的設計中,可以使用異步reset來避免引入更多的combo delay。
但是同步reset還有一個優勢,由於reset信號會最終起作用在寄存器的D輸入端,那麼通過reset的組合邏輯都會被STA所約束,也就是說reset信號和其他datapath的信號一起要滿足寄存器的setup time, hold time, min pulse等一系列check,在timing close的情況下我們可以拍著胸脯保證:寄存器不會因為reset信號的變化產生metastable。
如果使用異步reset,reset assertion是異步的,但是reset release一定要和時鐘同步!
因為對於reset assertion,reset active之後flop的值是穩定在reset value的,只要reset繼續active,來多少個clock,其他datapath上的信號怎麼變,flop的值都不會變化,所以reset 在什麼時候assertion都沒關係。 但是reset release就不一樣了,一旦reset從active變為in-active,那麼flop之後的值就得取決於其他信號輸入和時鐘沿的關係了,所以對於reset de-assertion,在STA里有兩個專門的參數來check,叫做recovery time和removal time。
recovery time指的是reset release之後要求距離下一個時鐘沿的最小間隔,可以類比於其他信號datapth上的setup time。
removal time指的是reset release之後要求距離上一個時鐘沿的最小間隔,可以類比於其他信號datapth上的hold time。
換句話說,reset release必須在recovery time和removal time加起來這個視窗之外,這樣才能保證寄存器不會產生metastable。