方法1 雙觸發器(打2拍)
該方法只用于慢到快時鐘域的1bit信號傳遞。在Xilinx器件中,可以使用(* ASYNC_REG = "TRUE" *)標記,將兩個寄存器盡量靠近綜合,降低 亞穩態因導線延遲太大而傳播到第二個寄存器的可能性。
moduleff2( input clk0,//10Minput din, input clk1,//100Moutputdout ); regdin_r=1'd0; (* ASYNC_REG ="TRUE"*) regr0=1'd0; (* ASYNC_REG ="TRUE"*) regr1=1'd0; assigndout = r1; always@(posedgeclk0) din_r <= din;//由于不確定前級是否有觸發器,這里默認加一級寄存,防止出現毛刺always@(posedge?clk1)begin? ? ?r0 ? ?<= din_r; ? ? ? ? r1 ? ?<= r0; ? ?endendmodule
方法2 雙向握手 傳遞信號
雙向握手方法使用了5個觸發器,右邊3個與方法1一致。左邊兩個用于將確認信號同步到寫時鐘域。用戶必須將信號保持到確認信號置高。
該方法適用于慢到快、快到慢、1bit信號傳遞,一般而言,信號有效值為高電平。
可以用一個非門、一個異或門、一個與門,和一個觸發器(下圖中的reg-2)實現信號保持,輸入低速單脈沖,但是不保證輸出單脈沖,可以在輸出端加一個寄存器,用于上升沿判斷.
在verilog可以用一些簡單的判斷來保持信號。下面是一個單向信號異步橋,支持單脈沖輸入,單脈沖輸出,時鐘速率無限制
使用時,確保在active置低時,將s_vld給異步橋。
/* * Name : 單向信號異步橋 * Origin: 230406 * EE : hel */modulesignalbridge_async( inputwire s_clk , inputwire s_rstn , inputwire s_vld ,// pulse inputinputwire d_clk , inputwire d_rstn , outputreg d_vld ,// pulse outputoutputwire active );reg[1 :0] s_syncer ;reg[1 :0] d_syncer ;wire ack_d2 = s_syncer[1];wire req_d2 = d_syncer[1]; reg req_d3;reg async_req;wire async_ack = req_d2;assign active = async_req | ack_d2 ;// Source clock domainalways@(posedges_clkornegedges_rstn)beginif(!s_rstn)begin s_syncer <=?2'd0; ? ?endelsebegin? ? ? ? ?s_syncer ? <= ?{s_syncer[0],async_ack}; ? ?endendalways?@(posedge?s_clk?ornegedge?s_rstn)?beginif?(!s_rstn)?begin? ? ? ? ?async_req ? <=?1'd0; ? ?endelseif(ack_d2)begin? ? ? ? ?async_req ? <=?1'd0; ? ?endelseif(s_vld)begin? ? ? ? ?async_req ? <=?1'd1; ? ?endend// Destination clock domainalways?@(posedge?d_clk?ornegedge?d_rstn)?beginif?(!d_rstn)?begin? ? ? ? ?d_syncer ? <=?2'd0; ? ?endelsebegin? ? ? ? ?d_syncer ? <= ?{d_syncer[0],async_req}; ? ?endendalways?@(posedge?d_clk?ornegedge?d_rstn)?beginif?(!d_rstn)?begin? ? ? ? ?req_d3 ? ? ?<=?1'd0; ? ? ? ? d_vld ? ? ? <=?1'd0; ? ?endelsebegin? ? ? ? ?req_d3 ? ? ?<= req_d2; ? ? ? ? d_vld ? ? ? <= req_d2 & (~req_d3); ? ?endendendmodule
方法3 雙向握手 傳遞數據
可以使用請求信號采樣跨時鐘域過來的多比特數據,就可以做到數據的跨時鐘域了(數據跨時鐘域,頻率低到高,高到低)如下圖:
圖里沒有畫反饋信號,數據需要在目的時鐘采樣時保持穩定。這種單個數據跨時鐘域傳輸的編寫流程如下:
原時鐘域置高req,置好data
目的時鐘域將req打兩拍變成req_d2
目的時鐘域檢測到req_d2置高,將data打到寄存器內,并將ack置高
原時鐘域檢測到ack_d2被置高,拉低req
目的時鐘域檢測到req_d2被拉低,將ack拉低
通過上述流程編寫的數據異步橋如下:
支持單個數據輸入輸出,時鐘速率無限制。使用時,確保在active置低時,將s_din和s_vld給異步橋,s_vld必須為單脈沖。
/* * Name : 單向數據異步橋 * Origin: 230404 * EE : hel */moduledatabridge_async #( parameterDW =8)( inputwire s_clk , inputwire s_rstn , inputwire [DW-1:0] s_din , inputwire s_vld ,// pulse inputinputwire d_clk , inputwire d_rstn , outputreg [DW-1:0] d_dout , outputreg d_vld ,// pulse outputoutputwire active );reg[1 :0] s_syncer ;reg[1 :0] d_syncer ;wire ack_d2 = s_syncer[1];wire req_d2 = d_syncer[1]; reg req_d3;reg[DW-1:0] async_dat;reg async_req;wire async_ack = req_d2;assign active = async_req | ack_d2 ;// Source clock domainalways@(posedges_clkornegedges_rstn)beginif(!s_rstn)begin s_syncer <=?2'd0; ? ?endelsebegin? ? ? ? ?s_syncer ? <= ?{s_syncer[0],async_ack}; ? ?endendalways?@(posedge?s_clk?ornegedge?s_rstn)?beginif?(!s_rstn)?begin? ? ? ? ?async_dat ? <= {DW{1'd0}}; ? ?endelseif(s_vld && (!active))begin? ? ? ? ?async_dat ? <= ?s_din; ? ?endendalways?@(posedge?s_clk?ornegedge?s_rstn)?beginif?(!s_rstn)?begin? ? ? ? ?async_req ? <=?1'd0; ? ?endelseif(ack_d2)begin? ? ? ? ?async_req ? <=?1'd0; ? ?endelseif(s_vld)begin? ? ? ? ?async_req ? <=?1'd1; ? ?endend// Destination clock domainalways?@(posedge?d_clk?ornegedge?d_rstn)?beginif?(!d_rstn)?begin? ? ? ? ?d_syncer ? <=?2'd0; ? ?endelsebegin? ? ? ? ?d_syncer ? <= ?{d_syncer[0],async_req}; ? ?endendalways?@(posedge?d_clk?ornegedge?d_rstn)?beginif?(!d_rstn)?begin? ? ? ? ?d_dout ? ? ?<= {DW{1'd0}}; ? ?endelseif(req_d2)begin? ? ? ? ?d_dout ? ? ?<= async_dat; ? ?endendalways?@(posedge?d_clk?ornegedge?d_rstn)?beginif?(!d_rstn)?begin? ? ? ? ?req_d3 ? ? ?<=?1'd0; ? ? ? ? d_vld ? ? ? <=?1'd0; ? ?endelsebegin? ? ? ? ?req_d3 ? ? ?<= req_d2; ? ? ? ? d_vld ? ? ? <= req_d2 & (~req_d3); ? ?endendendmodule
testbench:
`timescale1ns/1psmoduledatabridge_async_tb ();parameterDW =8;regs_clk =0;regs_rstn =0;reg[DW-1:0] s_din =0;regs_vld =0;regd_clk =0;regd_rstn =0;wire[DW-1:0] d_dout;wired_vld;wireactive;reg[DW-1:0]buffer ;realts;realtd;reala;realb;realclkstp;always#(ts) s_clk <= ~s_clk;always#(td)? d_clk <= ~d_clk; databridge_async?#(DW)?databridge_async ( ? ?.s_clk? ?( s_clk ?), ? ?.s_rstn? ( s_rstn ), ? ?.s_din? ?( s_din ?), ? ?.s_vld? ?( s_vld ?), ? ?.d_clk? ?( d_clk ?), ? ?.d_rstn? ( d_rstn ), ? ?.d_dout? ( d_dout ), ? ?.d_vld? ?( d_vld ?), ? ?.active? ( active ) );taskautomatic?gen_trans;input?[DW-1:0]data;begin? ? ?#0? ? ?s_vld ?= ?1; ? ? s_din ?= data; ? ? @(posedge?s_clk); ? ? #0? ? ?s_vld ?= ?0; ? ? s_din ?= ?0;endendtask//automatictaskautomatic?wait_busy;begin? ? ?@(posedge?s_clk);#0; ? ?while?(active)?begin? ? ? ? ?@(posedge?s_clk);#0; ? ?endendendtask//automatictaskautomatic?loop_test;inputinteger?n;integer?i;reg?[63:0]random_dat;beginfor?(i =?0;i
方法4 轉為獨熱碼或格雷碼
如果多bit信號是簡單連續變化(連續加一或減一),可以將其轉為格雷碼,再打2拍到目標時鐘域,最終再解碼為二進制編碼。
如果多bit信號沒有變化規律,可以將其轉為獨熱碼,再打2拍到目標時鐘域,最終再解碼為二進制編碼。該方法適用于慢到快、多bit信號傳遞。
為什么data不能直接通過同步器過域:
多比特數據由于存在傳輸路徑延遲,到達同步器有先后順序,導致同步器采樣到不同電平,輸出錯誤數據。
根本原因是源端數據有多個bit發生跳變。如果每次只有一個bit跳變(連續變化格雷碼、獨熱碼),就一定能采樣到正確的數據。
假如目的時鐘非常慢,比原時鐘慢很多,在慢時鐘上升沿時還是能采到正確數據,可能采到跳變之前的也可能是之后的,但總之都是source的數據。如果采到跳變之前的數據,就會產生異步FIFO的虛空虛滿。從這個角度看,異步FIFO是絕對安全的(忽略同步器失效),異步FIFO兩邊的時鐘頻率沒有任何要求。方法5 深度為2的ASYNC-FIFO
在傳輸信號,不需要較高帶寬時,可以將普通格雷碼異步FIFO特化為2-deep ASYNC-FIFO,該方法適用于慢到快、快到慢、多bit信號傳遞。一般很少使用2deep-FIFO,而是使用雙向握手來傳遞單個多比特數據。
方法6 ASYNC-FIFO
該方法適用于慢到快、快到慢、多bit數據傳遞。
moduleasync_fifo//(First_Word_Fall_Through FIFO,數據提前輸出,可能不利于時序收斂)#( parameterintegerDATA_WIDTH =16, parameterintegerADDR_WIDTH =8//深度只能是2**ADDR_WIDTH) ( inputwire wr_rst , inputwire wr_clk , inputwire wr_ena , inputwire[DATA_WIDTH-1:0] wr_dat , outputwire wr_full , inputwire rd_rst , inputwire rd_clk , inputwire rd_ena , outputwire[DATA_WIDTH-1:0] rd_dat , outputwire rd_empty );reg [DATA_WIDTH-1:0]mem[0:2**ADDR_WIDTH-1];reg [ADDR_WIDTH :0]wrptr;reg [ADDR_WIDTH :0]rdptr;wire[ADDR_WIDTH-1:0]wraddr = wrptr[ADDR_WIDTH-1:0];wire[ADDR_WIDTH-1:0]rdaddr = rdptr[ADDR_WIDTH-1:0];wire[ADDR_WIDTH :0]wrptr_gray = (wrptr>>1)^wrptr;wire[ADDR_WIDTH :0]rdptr_gray = (rdptr>>1)^rdptr;reg [ADDR_WIDTH :0]wrptr_gray_r0;reg [ADDR_WIDTH :0]wrptr_gray_r1;reg [ADDR_WIDTH :0]rdptr_gray_r0;reg [ADDR_WIDTH :0]rdptr_gray_r1;assignwr_full = (wrptr_gray == {~rdptr_gray_r1[ADDR_WIDTH:ADDR_WIDTH-1],rdptr_gray_r1[ADDR_WIDTH-2:0]});assignrd_empty = (rdptr_gray == wrptr_gray_r1);//在有些設計中,full和empty提早一個時鐘出現,并加了一級寄存,這有助于收斂always@(posedgewr_clkorposedgewr_rst)beginif(wr_rst)begin rdptr_gray_r0 <=?'d0; ? ? ? ? rdptr_gray_r1 <=?'d0; ? ?endelsebegin? ? ? ? ?rdptr_gray_r0 <= rdptr_gray; ? ? ? ? rdptr_gray_r1 <= rdptr_gray_r0; ? ?endendalways?@(posedge?rd_clk?orposedge?rd_rst)?beginif?(rd_rst)?begin? ? ? ? ?wrptr_gray_r0 <=?'d0; ? ? ? ? wrptr_gray_r1 <=?'d0; ? ?endelsebegin? ? ? ? ?wrptr_gray_r0 <= wrptr_gray; ? ? ? ? wrptr_gray_r1 <= wrptr_gray_r0; ? ?endendalways?@(posedge?wr_clk?orposedge?wr_rst)?beginif?(wr_rst)?begin? ? ? ? ?mem[0] ? ? ?<=?'d0; ? ? ? ? wrptr ? ? ? <=?'d0; ? ?endelseif?(wr_ena&&(!wr_full))?begin? ? ? ? ?mem[wraddr] <= wr_dat; ? ? ? ? wrptr ? ? ? <= wrptr +?1'd1; ? ?endelsebegin? ? ? ? ?mem[wraddr] <= mem[wraddr]; ? ? ? ? wrptr ? ? ? <= wrptr; ? ?endendalways?@(posedge?rd_clk?orposedge?rd_rst)?beginif?(rd_rst)?begin? ? ? ? ?rdptr ? ? ? <=?'d0; ? ?endelseif?(rd_ena&&(!rd_empty))?begin? ? ? ? ?rdptr ? ? ? <= rdptr +?1'd1; ? ?endelsebegin? ? ? ? ?rdptr ? ? ? <= rdptr; ? ?endendassign?rd_dat = mem[rdaddr];endmodule
-
寄存器
+關注
關注
31文章
5421瀏覽量
123308 -
Xilinx
+關注
關注
73文章
2182瀏覽量
124332 -
觸發器
+關注
關注
14文章
2032瀏覽量
61871 -
時鐘域
+關注
關注
0文章
53瀏覽量
9725
原文標題:跨異步時鐘域的6種方法
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設計論壇】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
評論