參考博文:https://blog.csdn.net/maxwell2ic/article/details/81051545, https://blog.csdn.net/dongdongnihao_/article/details/79873555 和 https://www.cnblogs.com/digital-wei/p/6014450.html
亞穩態
鎖存器出現亞穩態
(1)在其中一個輸入端輸入的脈沖太短。
(2)兩個端口輸入同時有效,或兩輸入有效相差足夠短。
(3)在使能輸入的邊緣處,輸入信號不穩定。
觸發器出現亞穩態
(1)建立/保持時間內輸入信號不穩定。
(2)時鍾脈沖太窄。
(3)異步信號對時鍾有效沿是隨機的,易產生亞穩態。異步信號包括:不被時鍾控制的信號;或被不同時鍾域的時鍾同步的信號。
觸發器進入亞穩態的時間可以用參數MTBF(Mean Time Between Failures)來描述,MTBF即觸發器采樣失敗的時間間隔,表示為:
其中fclock表示系統時鍾頻率,fdata代表異步輸入信號的頻率,tmet代表不會引起故障的最長亞穩態時間,C1和C2分別為與器件特性相關的常數。如果MTBF很大,就認為這個設計在實際工作中是能夠正常運行的,不會因為亞穩態導致整個系統的失效。
亞穩態的處理方法
(1)亞穩態不能避免。
(2)盡可能降低亞穩態的影響。
(3)高速數字電路依賴於同步器產生的從亞穩態事件中恢復的緩沖時間。
常見的同步電路解決方案
(1)兩級DFF(傳單信號)
(2)FIFO(傳有一定位寬的信號bus)
(3)握手信號(對於FIFO的深度要求比較大,需要引入握手信號)
總結一下 :
1、有關系的時鍾之間傳單bit數據,理論上只需要源數據保持足夠長的時間(clk2的兩個周期)即可;
2、無關系的時鍾之間傳單bit數據,必須要使用同步器;
3、不管有無關系的時鍾進行單bit傳輸,脈沖同步器都可以解決這個問題;
4、多bit傳輸只能使用握手機制或者異步fifo;
5、低頻采高頻,為防止數據不丟失,應當讓源數據變慢,多保持一些周期;高頻采低頻則不需要,但是高頻采低頻得到的結果可能帶有很多冗余
同步器設計
信號同步的要求
為了使同步工作能正常進行,從某個時鍾域傳來的信號應先通過原時鍾域上的一個觸發器,然后不經過兩個時鍾域間的任何組合邏輯直接進入同步器的第一個觸發器中。這一要求非常重要,因為同步器的第一級觸發器對組合邏輯所產生的毛刺非常敏感。如果一個足夠長的毛刺正好滿足建立-保持時間的要求則同步器的第一級觸發器會將其放行,給新時鍾域的后續邏輯送出一個虛假的信號。
跨時鍾域傳輸信號主要引起的問題就是亞穩態。處理跨時鍾域的數據有單bit和多bit之分,而打兩拍的方式常見於處理單bit數據的跨時鍾域問題。打兩拍本質就是定義兩級寄存器對數據進行延拍。流程如好圖所示: 兩級寄存器的原理:兩級寄存是一級寄存的平方,兩級並不能完全消除亞穩態危害,但是提高了可靠性減少其發生概率。總的來講,就是一級概率很大,三級改善不大。
電平同步器
電平同步器適用情況為:異步輸入信號的脈沖寬度大於時鍾周期時的同步電路(低速時鍾域→高速時鍾域)。
在電平同步器中,跨時鍾域的信號在新時鍾域中要保持高電平或低電平兩個時鍾周期以上。同步之后的信號是電平的形式,而該電平所維持的時鍾周期個數是其在跨時鍾域期間被上升沿檢測到的次數。這種同步器是所有同步器電路的核心。
module sync(
input async_in, bclk, rst,
output sync_out
);
reg bdat1, bdat2;
always @(posedge bclk or posedge rst) begin
if (rst) begin
bdat1 <= 1'b0;
bdat2 <= 1'b0;
end
else begin
bdat1 <= async_in;
bdat2 <= bdat1;
end
end
assign sync_out = bdat2;
endmodule
邊沿檢測同步器
邊沿檢測同步器適用情況為:異步輸入信號的脈沖寬度小於時鍾周期時的同步電路(高速時鍾域→低速時鍾域)。
原理圖:
不產生亞穩態時的時序圖
會產生亞穩態時的時序圖
module sync_h2lck(
input async_in, bclk, rst,
output sync_out
);
reg q1, q2, q3;
wire async_rst;
assign async_rst = !async_in && sync_out;
always @(posedge async_in or posedge async_rst) begin
if (async_rst) begin
q1 <= 1'b0;
end
else begin
q1 <= 1'b1;
end
end
always @(posedge bclk or posedge rst) begin
if (rst) begin
q2 <= 1'b0;
q3 <= 1'b0;
end
else begin
q2 <= q1;
q3 <= q2;
end
end
assign sync_out = q3;
endmodule
脈沖檢測同步器
簡單的脈沖同步器:1)源時鍾域脈沖轉換為源時鍾域電平信號;2)對單bit電平信號進行打拍的異步處理;3)在目的時鍾域中進行脈沖還原。
它可以實現簡單應用場景下的同步功能,同時也存在不少應用限制或缺陷:1) 對src_clk域dst_clk關系較為敏感,當src_clk與dst_clk時鍾頻率差別很大時可能不適應;2) 由於沒有完整的握手機制,當多個src_pulse之間間隔較短時,可能存在脈沖同步丟失情況。3) 當dst_clk時鍾域出現無時鍾或復位時,src_clk時鍾域將丟失。
從以上設計原理中,我們可以發現該同步器的控制傳遞是單向的,即僅從源時鍾域到目的時鍾域,目的時鍾域並沒有狀態反饋。假設存在如下應用:
(1) 源時鍾域中的第一個脈沖和第二個脈沖間隔過短,第一個脈沖未完成同步,第二脈沖又將狀態清空,導致最終脈沖同步丟失。
要解決以上同步問題,需要引入異步握手機制,保證每個脈沖都同步成功,同步成功后再進行下一個脈沖同步。握手原理如下:sync_req: 源時鍾域同步請求信號,高電平表示當前脈沖需要同步;sync_ack: 目的時鍾域應答信號,高電平表示當前已收到同步請求。
完整同步過程分為以下4個步驟:
(1) 同步請求產生;當同步器處於空閑(即上一次已同步完成)時,源同步脈沖到達時產生同步請求信號sync_req;
(2) 同步請求信號sync_req同步到目的時鍾域,目的時鍾域產生脈沖信號並將產生應答信號sync_ack;
(3) 同步應答信號sync_ack同步到源時鍾域,源時鍾域檢測到同步應答信號sync_ack后,清除同步請求信號;
(4) 目的時鍾域檢測到sync_req撤銷后,清除sync_ack應答;源時鍾域將到sync_ack清除后,認為一次同步完成,可以同步下一個脈沖。
異步FIFO
FIFO結構
異步FIFO處理多bit數據信號的CDC跨時鍾域問題。端口定義:
u w:寫時鍾域一方的信號;r:讀時鍾域一方的信號
u wclk:寫時鍾
u wrst_n:寫復位,低有效
u rclk:讀時鍾
u rrst_n:讀復位,低有效
u winc:外部輸入的寫使能信號
u rinc:外部輸入的讀使能信號
u wdata :要寫進數據,要寫進FIFO里面存儲的數據。
u rdata:讀數據,從FIFO里面讀取出來的數據。
u wdata:要讀出的數據,要讀出FIFO里面存儲的數據
u wfull:寫滿的狀態信號
u rempty:讀空的狀態信號
u wclken:RAM的允許寫信號,在這個信號有效的情況下,RAM才能寫得進數據。
u rclken:RAM的允許讀信號,在這個信號有效的情況下,RAM才能讀得出數據。
u waddr:RAM的寫地址。
u raddr:RAM的讀地址
u wptr:要同步到寫時鍾域的讀指針(讀地址)。
u rptr:要同步到讀時鍾域的寫指針(寫時鍾)
u wq2_rptr:讀地址rptr同步到寫時鍾域的讀地址(格雷碼,后面會說為什么用格雷碼)
u rq2_wptr:寫地址rptr同步到讀時鍾域的讀地址(格雷碼,后面會說為什么用格雷碼)
u syn_r2w:讀同步到寫觸發器鏈中間信號。
u syn_w2r:寫同步到讀觸發器鏈中間信號。
異步FIFO讀寫過程
l 寫過程
在復位的時候,FIFO(雙口RAM)里面的數據被清零(也就是不存在數據)。
復位之后,只能進行寫操作,因為什么都沒有,讀數據會讀出錯誤的值。
當外部給FIFO寫使能信號了,在時鍾的驅動下,數據就會被寫入FIFO里面的RAM存儲單元(存儲單元的地址由寫指針寄存器的內容確定,寫指針寄存器中的內容稱為寫地址,復位的時候為0),寫完數據之后(或者在允許寫數據之后),這個寫指針寄存器就會自動加一,指向下一個存儲單元。
當寫到一定程度的時候(寫指針寄存器到達一定的數值),舊數據還沒有被讀出的時候,再寫入新數據就會把舊數據給覆蓋,這個時候稱為寫滿,需要產生寫滿的狀態信號(full,簡稱滿)。在寫滿的時候,需要禁止繼續寫數據。
l 讀過程:
在復位的時候,FIFO里面沒有數據,因此這個時候是禁止讀數據的。
當里面有數據之后,外部讀信號到來后,在時鍾信號到來的時候,FIFO就會根據讀地址(由讀指針寄存器的內容確定,讀指針寄存器里面的內容稱為讀地址,復位的時候為0)讀出相應的數據,讀出數據之后(或者說允許RAM讀之后),讀指針寄存器自動加一。指向下一個存儲單元。
當讀到一定的程度的時候,也就是FIFO里面沒有數據了,這個時候稱為讀空,需要產生讀空的狀態信號(empty,簡稱空)。在讀空的時候,需要禁止繼續讀數據。
FIFO的空滿信號產生
- 空滿信號產生過程: 將指針同步到另一邊時鍾中,使用兩級同步器,會導致延遲兩個時鍾周期,然后出現在未滿時通知寫一邊fifo已滿,或在未空時通知讀一邊fifo已空,都是可以的,即使指針同步后的值(寫時同步讀指針或讀時同步寫指針)會持續一小段時間亞穩態,阻止寫、讀的影響使fifo掛起一段時間,但是不會導致任何錯誤。進行設計地址寄存器的時候,增多一位當做狀態。然后讀寫地址全相等的時候,表示是讀空;除了標志位外,剩余的地址為全部相等,那么就表示是寫滿。
- 空標志: 本地的讀指針( rp tr_nexttr_nexttr_next tr_nexttr_next )與異步傳輸過來的寫指針 異步傳輸過來的寫指針 異步傳輸過來的寫指針 異步傳輸過來的寫指針 異步傳輸過來的寫指針 rq _wptr_wptr _wptr2各位都相等。
- 滿標志 :本地的寫指針( wp tr_nexttr_nexttr_next tr_nexttr_next )與異步傳輸過來的讀指針 異步傳輸過來的讀指針 異步傳輸過來的讀指針 異步傳輸過來的讀指針 異步傳輸過來的讀指針異步傳輸過來的讀指針wq_rptr2wq_rptr2 wq_rptr2wq_rptr2 wq_rptr2wq_rptr2高兩位相反,其余相等。
- 格雷碼:在異步時鍾域中傳遞數據時使用異步fifo:產生空信號時需要同步寫指針到讀時鍾域,產生滿信號時需要同步讀指針到寫時鍾域;(指針)地址信號是多位,故采用gray碼編碼實現,同步gray碼計數器使取樣計數器值很少會出現亞穩態。
FIFO深度選擇
FIFO是有寬度和深度的。FIFO的寬度就是RAM的位寬,也是要存入/取出數據的位寬;然后深度就RAM的地址深度,也就是最多可以存多少個數據。
FIFO的深度選小了,在寫的時候就很有可能寫溢出;深度選大了,就會浪費存儲面積。選擇一個合適的深度,最主要的就是防止寫溢出;由於FIFO要讀也要寫,那么FIFO的(地址)深度該選多少合適呢?
根據讀寫速度來選擇FIFO深度,此外需要注意的是,在使用FIFO的時候,寫的平均吞吐量要和讀的平均吞吐量相等。
現在舉例來說明:設你的寫時鍾頻率為100M,讀時鍾為200M;寫速度為:100個時鍾寫如60個數據,讀速度為:100個時鍾讀出30個數據 。
①首先驗證你的數據吞吐量是否相等:
寫的平均吞吐量=100M*60/100=60M個數據/S
讀的平均吞吐量=200M*30/100=60M個數據/S
因此這兩個是相等的,不會發生寫溢出;如果寫大於讀,那么FIFO早晚會很快寫滿溢出;如果讀大於寫,那么FIFO遲早會讀空。因此需要讀寫吞吐量相同。
②求最低深度
我們知道讀寫速度之后,就可以判斷FIFO要多少深度才合適了,由於FIFO的深度考慮是出於我們要防止寫溢出(寫滿),因此我們考慮寫的情況:
寫的時候是100個時鍾寫60個數據,我們不知道它是怎么樣子寫的,我們從悲觀的角度出發,也就是從寫得最密集的角度出發:前100個寫時鍾的最后60個時鍾寫60個數據,然后后100個寫時鍾的最前60個時鍾寫入60個數據,也就是在120個寫時鍾內寫入了120數據。
這120個寫時鍾的時間是:
在這段時間內,根據讀寫的速率要求,肯定是要讀數據的,讀出的數據為:
因此我們FIFO需要的深讀就等於沒有讀出的數據的個數就是:120-72 =48
然而由於上面讀的方式是一個平均的方式,此外FIFO的深度一般是2的整數次冪,要符合格雷碼的編碼轉換規則,因此我們深度一般不選擇48,而是選擇比它大的2的整數次冪的數,比如64或者128。
異步FIFO代碼
// ********************************************************
// Copyright(c) 2018
// Author: gujiangtao
//-----------------------------------------------------------
// Asynchronization FIFO
// Asynchronism clock domain: data and control signals transaction
// Verilog-standard: Verilog 2001
//-----------------------------------------------------------
// Structure:
// 1r1w-async FIFO
//-----------------------------------------------------------
module asys_fifo #(
parameter DATA_WIDTH = 32,
parameter ADDR_WIDTH = 32)
(
input wclk,
input wreset_n,
input wden,
input [DATA_WIDTH-1:0] wdata,
input rclk,
input rreset_n,
input rden,
output [DATA_WIDTH-1:0] rdata,
output wfull,
output rempty
);
reg reg wfull, rempty;
reg [ADDR_WIDTH:0] wptr, wq_rptr1, wq_rptr2; //read point for being synchronized to write-point side by two D-flops
reg [ADDR_WIDTH-0:0] rptr, rq_wptr1, rq_wptr2; //write point for being synchronized to read-point side by two D-flops
reg [ADDR_WIDTH:0] rbin, wbin;
reg [DATA_WIDTH-1:0] mem [0: (1<< ADDR_WIDTH)-1];
wire[ADDR_WIDTH-1:0] raddr, waddr;
wire[ADDR_WIDTH:0] rgray_next, rbin_next;
wire[ADDR_WIDTH:0] wgray_next, wbin_next;
wire rempty_temp, wfull_temp;
/////////////////////////////////////////////////////////////////////////////
// 1r1w two-end RAM data transaction
assign rdata = mem[raddr]; //output directly without buffer
always @(posedge wclk) begin
if(wden && !wfull) mem[waddr] <= wdata;
end
//////////////////////////////////////////////////////////////////////////////
//read point for being synchronized to write-point side by two
always @(posedge wclk or negedge wreset_n) begin
if(!wreset_n)
{wq_rptr2, wq_rptr1} <= 0;
else
{wq_rptr2, wq_rptr1} <= {wq_rptr1, rptr};
end
//write point for being synchronized to read-point side by two D-flops
always @(posedge rclk or negedge rreset_n) begin
if(!rreset_n)
{rq_wptr2, rq_wptr1} <= 0;
else
{rq_wptr2, rq_wptr1} <= {rq_wptr1, wptr};
end
/////////////////////////////////////////////////////////////////////////////
// raddr produce for reading the mem RAM
always @(posedge rclk or negedge rreset_n) begin
if(!rreset_n)
{rbin, rptr} <= 0;
else
{rbin, rptr} <= {rbin_next, rgray_next};
end
assign rbin_next = rbin + (rden && ~rempty); //reading point increase
assign raddr = rbin[ADDR_WIDTH-1:0];
// bin to gray
assign rgray_next = (rbin_next>>1) ^ rbin_next;
// FIFO empty singal produce
assign rempty_temp = (rgray_next == rq_wptr2);
always @(posedge rclk or negedge rreset_n) begin
if(!rreset_n)
rempty <= 1'b1;
else
rempty <= rempty_temp;
end
//////////////////////////////////////////////////////////////////////////////
//waddr produce for writing the mem RAM
always @(posedge wclk or negedge wreset_n) begin
if(!wreset_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbin_next, wgray_next};
end
assign wbin_next = wbin + (wden && ~wfull); //writing point increase
assign waddr = wbin[ADDR_WIDTH-1:0];
// bin to gray
assign wgray_next= (wbin_next>>1) ^ wbin_next;
// FIFO Full signal produce
assign wfull_temp = (wgray_next == {~wq_rptr2[ADDR_WIDTH:ADDR_WIDTH-1], wq_rptr2[ADDR_WIDTH-2:0]});
always @(posedge wclk or negedge wreset_n) begin
if(!wreset_n)
wfull <= 1'b0;
else
wfull <= wfull_temp;
end
///////////////////////////////////////////////////////////////////////////////
endmodule// end of module asys_fifo
異步FIFO測試平台:
測試平台設計
// ********************************************************
// Copyright(c) 2018
// Author: gujiangtao
//-----------------------------------------------------------------------------
// Asynchronization FIFO
// Asynchronism clock domain: data and control signals transaction
// Verilog-standard: Verilog 2001
//--------------------------------------------------------------------------
// Structure:
// 1r1w-async FIFO --- testbench
//--------------------------------------------------------------------------
`define read_enable_time 5
`define write_enable_time 1
`define data_width 8
`define addr_width 6
module testbench;
localparam DATA_WIDTH = `data_width;
localparam ADDR_WIDTH = `addr_width;
reg wclk_i;
reg wreset_n_i;
reg wden_i;
reg [DATA_WIDTH-1:0] wdata_i;
reg rclk_i;
reg rreset_n_i;
reg rden_i;
wire [DATA_WIDTH-1:0] rdata_o;
wire rempty_o;
wire wfull_o;
reg [DATA_WIDTH-1:0] value_m;
asys_fifo #(
.DATA_WIDTH(DATA_WIDTH),
.ADDR_WIDTH(ADDR_WIDTH)
) asys_fifo
(. wclk(wclk_i)
,. wreset_n(wreset_n_i)
,. wden(wden_i)
,. wdata(wdata_i)
,. rclk(rclk_i)
,. rreset_n(rreset_n_i)
,. rden(rden_i)
,. rdata(rdata_o)
,. rempty(rempty_o)
,. wfull(wfull_o)
);
task read_word;
begin
@(negedge rclk_i);
rden_i = 1;
@(posedge rclk_i);
#`read_enable_time;
rden_i = 0;
end
endtask
task write_word
input [DATA_WIDTH-1:0] value;
begin
@(negedge wclk_i);
wdata_i = value;
wden_i = 1;
@(posedge wclk_i);
#`write_enable_time;
wdata_i = `data_width'bz;
wden_i = 0;
end
endtask
initial begin
wclk_i = 0;
forever begin
#2 wclk_i = 1;
#2 wclk_i = 0;
end
end
initial begin
rclk_i = 0;
forever begin
#10 rclk_i = 1;
#10 rclk_i = 0;
end
end
`ifdef DUMP_FSDB
initial begin
$fsdbDumpfile("testbench.fsdb");
$fsdbDumpvars;
end
`endif
initial begin
test1;
//test2;
#10000;
$finish();
end
task test1;
begin
wdata_i = `data_width'bz;
wden_i = 0;
rden_i = 0;
wreset_n_i = 0;
rreset_n_i = 0;
#50;
wreset_n_i = 1;
rreset_n_i = 1;
#50;
write_word(`data_width'h01);
write_word(`data_width'h02);
write_word(`data_width'h03);
read_word;
read_word;
write_word(`data_width'h04);
repeat(1)begin
read_word;
end
write_word(`data_width'h05);
write_word(`data_width'h06);
write_word(`data_width'h07);
write_word(`data_width'h08);
write_word(`data_width'h09);
write_word(`data_width'h10);
write_word(`data_width'h11);
write_word(`data_width'h12);
repeat(6)begin
read_word;
end
end
endtask
endmodule