PS:轉載請標明出處 http://www.cnblogs.com/IClearner/;本文如有錯誤,歡迎留言更正。
因為學習了其他方面的知識,耽擱了更新。今天我們就聊聊跨時鍾域中的數據信號傳輸的問題。主要內容預覽:
·使用握手信號進行跨時鍾域的數據傳輸
·FIFO的介紹
·在進行FIFO的RTL設計前的問題
·FIFO的RTL設計(與仿真測試)
·跨時鍾域中的數據信號傳輸總結
一、使用握手信號進行跨時鍾域的數據傳輸
下面敘述的意義相同:前級時鍾=發送時鍾; 后級時鍾=采樣時鍾=接收時鍾
使用握手信號傳輸數據不是我們的重點,重點是FIFO的設計。在使用握手信號進行數據傳輸之前,我們說說為什么雙D觸發器鏈不應該用於數據的傳輸。
一般情況下,我們要傳輸的數據都是多位的,也就是以數據總線的形式傳播的。如果我們使用簡單的多組D觸發器鏈進行同步數據的話,由於每一種D觸發器鏈第一級觸發器都有可能出現亞穩態,穩定下來之后的電平可能出錯;由於有多組D觸發器鏈,就有可能發送多個電平出錯,因而導致數據出錯,如下圖所示:
可以看到,原來前面的時鍾域發送的0111數據變成1000的時候,捕獲時鍾的時鍾采樣本來要才到0111的,由於保持時間不足,導致了b[2]、b[1]出現亞穩態,而且b[2]穩定后的電平是錯誤的電平,由此就傳輸了錯誤的數據。因此直接使用觸發器鏈進行同步數據是不建議的。
於是乎,我們就看看使用握手信號是怎么進行傳輸數據的。
·數據變化速率比采樣時鍾域低
當數據的速率比采樣時鍾域慢時,也就是說,數據速率相對於采樣時鍾域(接收數據的時鍾域)來說是慢時鍾,可以使用控制信號進行同步,在采樣到慢時鍾域的控制信號后,接收采樣數據,時序圖如下所示:
這里只給出了時序圖,電路可以按照時序圖進行設計。需要注意的是,這個額外的控制信號(wr_en_s)是由前面的邏輯產生的。這與下面的電路不一樣:
在下面的這個電路中,控制信號是由上升沿檢測電路產生的,而且是接收時鍾驅動的上升沿檢測電路(也就是說這個控制信號是由后級的邏輯產生的),電路如下所示:
下面我們來分析一下這個電路吧,時序圖如下所示:
從時序圖中可以看到,可以用上升沿檢測電路,檢測發送時鍾的上升沿,然后這個沿相當於使能信號。上面中,檢測到了第二個發送時鍾的上升沿,之后就有了使能信號,采樣的數據也是第二時鍾發送的數據DB,因此對應起來是沒有問題。這里由於沒有檢測到EN1第一個上升沿,所以沒有采樣到DA也是正常的,這是因為前面的波形沒有畫出的緣故。
·當數據的速率(或者說發送時鍾的頻率)略高於接收時鍾端
由於發送時鍾比接收時鍾快,於是對於接收時鍾,發送時鍾就相當於窄脈沖信號,這樣我們就有思路了。我們還是上面一樣,采用上升沿檢測信號當做使能信號;但是問題來了,發送時鍾是快的,可能會錯過上升沿。於是乎,我們就把窄脈沖捕獲電路和上升沿檢測電路結合起來。先是窄脈沖捕捉電路,把時鍾的沿捕捉到,然后進行邊沿檢測,檢測得到的結果作為使能信號,電路圖如下所示:
具體就不分析了,需要強調的是,這個是發送時鍾也不能太快。
·數據變化速率比采樣時鍾快很多
當數據的速率比采樣的時鍾的速率快很多時,對應到時鍾的關系就是——發送時鍾和比接收時鍾快很多時,這個時候采樣時鍾就采樣不到數據,或者說會采漏部分數據,因此這時候就不能用握手信號了。也許有人說我可以增加使能信號,把數據拉長啊,等后面的采樣時鍾采樣到使能信號、接收到數據之后,我再改變時鍾。這種方法的實質就是硬生生地把數據變化率蓋滿,也就是把發送時鍾域的時鍾改慢,跟前面的數據變化速率比采樣時鍾域低的實質是一樣的。因此當數據變化率比采樣時鍾快很多時,就要采樣下面介紹的FIFO了。
二、FIFO的介紹
終於寫到FIFO了,FIFO 是first in first out的縮寫,也就是“先進先出”;從字面理解,就是說,數據先進來的,就先出去。前面說了當快時鍾域傳輸數據到慢時鍾域時,就推薦用FIFO了。FIFO無論是快到慢,還是慢到快,都可以使用它進行數據的緩沖,可謂是“快慢皆宜”啊。
FIFO的工作流程如下:
FIFO在寫時鍾和狀態信號的控制下,根據寫使能信號往FIFO里面寫數據,當寫到一定程度后,FIFO存不下新數據的了(或者要以犧牲丟棄舊數據為貸款),這時候就不能往FIFO里面寫數據了;在讀時鍾和狀態信號的控制下,根據讀使能信號從FIFO里面讀出數據,當讀到一定程度后,FIFO里面沒有數據了,就不能繼續讀了,不然就會讀出錯誤的數據。根據讀寫時鍾是否一致(同步),FIFO的種類又可以分成同步FIFO和異步FIFO。FIFO能夠讀寫數據,肯定需要數據的存儲單元,這里存儲數據的單元往往是雙口RAM。
FIFO的寫過程:在復位的時候,FIFO(雙口RAM)里面的數據被清零(也就是不存在數據)。復位之后,只能進行寫操作,因為什么都沒有,讀數據會讀出錯誤的值。這個時候,當外部給FIFO寫使能信號了,在時鍾的驅動下,數據就會被寫入FIFO里面的RAM存儲單元(存儲單元的地址由寫指針寄存器的內容確定,寫指針寄存器中的內容稱為寫地址,復位的時候為0),寫完數據之后(或者在允許寫數據之后),這個寫指針寄存器就會自動加一,指向下一個存儲單元。當寫到一定程度的時候(寫指針寄存器到達一定的數值),舊數據還沒有被讀出的時候,再寫入新數據就會把舊數據給覆蓋,這個時候稱為寫滿,需要產生寫滿的狀態信號(full,簡稱滿)。在寫滿的時候,需要禁止繼續寫數據。
FIFO的讀過程:在復位的時候,FIFO里面沒有數據,因此這個時候是禁止讀數據的。當里面有數據之后,外部讀信號到來后,在時鍾信號到來的時候,FIFO就會根據讀地址(由讀指針寄存器的內容確定,讀指針寄存器里面的內容稱為讀地址,復位的時候為0)讀出相應的數據,讀出數據之后(或者說允許RAM讀之后),讀指針寄存器自動加一。指向下一個存儲單元。當讀到一定的程度的時候,也就是FIFO里面沒有數據了,這個時候稱為讀空,需要產生讀空的狀態信號(empty,簡稱空)。在讀空的時候,需要禁止繼續讀數據。
根據前面的描述,我們就可以知道,在復位的時候,FIFO空有效、滿無效,禁止讀數據,只能往里面寫數據。當把FIFO里面的內容都寫滿的時候,FIFO滿有效,空無效,這時候只能讀數據,而不能繼續往里面寫數據。
三、在進行FIFO的RTL設計前的問題
根據FIFO的介紹內容,我們試着來推導一下FIFO大致由哪些部分構成。
首先,FIFO需要存儲數據,因此就需要存儲器;由於需要讀,也需要寫,於是乎就需要一個DPRAM(double port RAM,雙端口RAM)。
然后,RAM需要讀/寫地址,它才知道在哪里讀/寫數據,因此需要讀/寫地址產生模塊,也就是需要讀/寫地址寄存器。什么時候進行寫,什么時候進行讀,因此需要讀/寫控制邏輯和空滿狀態的信號產生邏輯。
最后,空滿信號的產生需要通過對讀地址和寫地址的比較,由於讀寫地址在不同的時鍾域,因此需要同步電路進行同步。
通過上面的簡單介紹,我們就得到了FIFO的大致框圖如下(主要是告訴大家為什么會有這么一個框圖):
現在來看看這些信號是什么意思吧:
w:寫時鍾域一方的信號;r:讀時鍾域一方的信號
wclk:寫時鍾
wrst_n:寫復位,低有效
rclk:讀時鍾
rrst_n:讀復位,低有效
winc:外部輸入的寫使能信號
rinc:外部輸入的讀使能信號
wdata :要寫進數據,要寫進FIFO里面存儲的數據。
rdata:讀數據,從FIFO里面讀取出來的數據。
wdata:要讀出的數據,要讀出FIFO里面存儲的數據
wfull:寫滿的狀態信號
rempty:讀空的狀態信號
wclken:RAM的允許寫信號,在這個信號有效的情況下,RAM才能寫得進數據。
rclken:RAM的允許讀信號,在這個信號有效的情況下,RAM才能讀得出數據。
waddr:RAM的寫地址。
raddr:RAM的讀地址
wptr:要同步到寫時鍾域的讀指針(讀地址)。
rptr:要同步到讀時鍾域的寫指針(寫時鍾)
wq2_rptr:讀地址rptr同步到寫時鍾域的讀地址(格雷碼,后面會說為什么用格雷碼)
rq2_wptr:寫地址rptr同步到讀時鍾域的讀地址(格雷碼,后面會說為什么用格雷碼)
syn_r2w:讀同步到寫觸發器鏈中間信號。
syn_w2r:寫同步到讀觸發器鏈中間信號。
介紹完這些信號之后,我開始聊聊FIFO設計前的一些問題。
·FIFO的空滿信號產生
空狀態信號:
一開始復位的時候,空信號是有效的,當寫了數據之后,空信號就無效了。然后當數據被讀取完之后,空信號就有效了。那么什么時候數據被讀取完了呢,也就是數據被讀取完的時候有什么特征呢?特征就是讀地址和寫地址相等,如下所示:
由於讀地址要追趕寫地址,在趕上的時候,地址全等就證明了讀空了。
也許有人會問:寫地址由於要同步到讀時鍾域去,會存在同步延時的,比如 說t=0s的時候同步過去,此時寫地址為A;在t=2s的時候A同步過來了,但是這個 時候寫地址已經變為A+2,而你同步過來的這個寫地址為A。如果在t=2s這個時候讀地址=A,即讀地址=寫地址,讀趕上了寫,按照上面的設計想法就會產生讀空信號,但是實際上是不相等的,也就是實際上讀並沒有趕上寫,即沒有讀空的,這不就是產生錯誤的讀空信號了嗎?
首先,是存在這樣的情況,但是這種情況不是設計錯誤。一方面由於我們要產生讀空信號,目的是也就是防止繼續讀從而讀出錯誤的數據;實際上沒有讀空,即使產生了讀空信號,也是沒有影響,相當於提前判斷產生讀空信號而已。另一方面由於是讀時鍾域采樣的讀的地址,這個讀地址是實時的;寫地址是延時的,當這個兩者相等時,我們這個實時的地址在比實際的寫地址小的時候就產生讀空信號,防止了讀空。因此即使產生讀空信號,也不會因為讀空而產生錯誤的數據。因此是沒有設計錯誤的。
寫滿狀態:
一開始復位之后,進行寫數據;由於地址(假設地址是4位,也就是深度是4位)是可以回卷的,也就是說,寫指針從3寫到15后,繼續寫又會返回到3那里;假如復位后讀操作只讀到地址3那里就不讀了,那么這個時候就寫滿了。也就是說,寫滿的時候,寫地址和讀地址是相等的,如下所示:
於是乎,我們該怎么區分在讀地址和寫地址相同的時候是讀空還是寫滿呢?下面來介紹一種常用的方法:
將地址深度拓寬1位當做標志位,回卷一次標志位取反。比如上面的例子中,4bit地址拓寬為5bit,那么讀地址就是3(由於讀地址沒有回卷,所以是(0)0011)那里,當寫地址回卷之后與讀地址相同(由於寫地址回卷了,最高位取反,所以是(1)0011),因此這就是寫滿了。當讀地址回卷之后,變成10011,這個時候,就讀空了。也就說,雖然DPRAM的深度還是4bit,但是我們在進行設計地址寄存器的時候,增多一位當做狀態。然后讀寫地址全相等的時候,表示是讀空;除了標志位外,剩余的地址為全部相等,那么就表示是寫滿。
這里還是會產生與前面的空信號一樣的問題,也就是同步過來的讀信號是延時的值,與前面一樣,是不會影響寫滿信號的,不屬於設計錯誤。
除了上面這種方法之外,在同步FIFO中,還可以使用計數器的方法。設置一個狀態計數器,復位的時候為0。寫的時候,計數器加1;讀的時候,計數器減1。那么很容易得出,計數器為0的時候,就是讀空就有效了;當計數器等於FIFO的深度(2^n - 1)時,就說明寫滿了。這種方法如果FIFO深度很大的話,就需要很大的計數器了,所以有局限性。
從上面的分析中,由此也可以知道,空信號的產生需要把寫地址同步到讀時鍾域,然后進行比較(比較之后產生);滿的信號需要把讀地址同步到寫時鍾域,然后進行比較(比較之后產生)。
·為什么要選擇格雷碼作為同步地址的編碼
首先,我們知道,讀地址需要跟寫地址比較來產生空和滿信號,然后對於異步FIFO,讀寫為不同時鍾,如果直接采樣,就會有:類似前面數據產生多位亞穩態的問題,(時序圖就不畫了)比如寫地址從00111改變從01000的時候,讀時鍾恰好采樣,那么除了最高位外,其它的4位都有可能產生亞穩態,有可能同步得錯誤的地址。這是引入格雷碼的一個原因。另外一個原因就是:無論是讀地址還是寫地址,在(允許)進行讀和寫之后,地址都是加1,而不是加2或者加3等其他的值。為什么會這樣呢?我們來看看格雷碼的編碼:
從上圖中我們可以知道,從地址0變成地址1,格雷碼和二進制碼都是0000變成0001;地址從1變成地址2,格雷碼是0001變成0011,而二進制是0010......我們很容易得到,在相鄰地址變化中,格雷碼只有一位發生變化,如地址從7變為8時,格雷碼是0100變成1100,也就是只有最高位發送變化;我們再來看看二進制編碼,二進制編碼則有可能全部都改變,地址從7變為8時,二進制碼是0111變成1000,4位都發生了變化。假如采樣的時候地址恰好從7變為8時,那么二進制編碼就有多位發生亞穩態,穩定后的值什么都有可能;而格雷碼由於只有最高位跳變,第三位由於沒有跳變,不會產生亞穩態可以穩定正確采樣,穩定后的值只有0100和1100,地址只差數值1,是不會影響判斷的結果的(因為是同步過來的,是個延時的值,不打緊)。
知道了格雷碼的優點之后,我們就要使用各格雷碼了。由於RAM的讀寫地址都是(傳統)二進制編碼,這里使用格雷碼有兩種使用方法,第一種使用方式是,將二進制編碼轉換成格雷碼,然后把格雷碼同步過去,再把同步過來的格雷碼反轉換成二進制碼,進行二進制地址和二進制地址的比較;另外一種使用方式是,將二進制編碼轉換成格雷碼,然后把格雷碼同步過去,然后使用格雷碼進行比較。這里使用第一種方式,雖然這種方式比較需要多兩塊格雷碼轉二進制的電路,但是我們可以實時比較,能將尋址的二進制馬上與同步過來的“延時”二進制進行比較;使用格雷碼比較的話,實際值會慢一拍(因為實時方的格雷碼需要寄存輸出,會慢一拍,如果不寄存輸出,就有可能產生毛刺)。
然后格雷碼的與二進制的互相轉換如上圖,下面是轉換講解(左邊為格雷轉二進制,右邊為二進制轉格雷):
在布爾代數里面有A^B=C →A=B^C
·FIFO的深度選擇
首先,FIFO是有寬度和深度的。FIFO的寬度就是RAM的位寬,也是要存入/取出數據的位寬;然后深度就RAM的地址深度,也就是最多可以存多少個數據。例如FIFO的寬度是8bit,那么FIFO每個時鍾存入的數據的寬度也是8bit;FIFO的深度是10bit,那么FIFO就最多可以存2^10=1024個8bit的數據。
我們要存儲數據,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深度的選擇過程就如上面所述,(這里參考《FPGA深度解析》)。
四、FIFO的設計(與仿真測試)
接下來我們就要設計一個異步FIFO了,這里我們設計的FIFO跟上面的有點不同,整體結構如下所示:
這里主要是多出了兩個狀態信號:
wfull_almost:將滿信號。為了預防萬一,FIFO要滿的時候,使這個信號有效,當面的模塊時鍾(前級電路)檢測到這個信號有效后,就把winc變為無效,用來提供給前面電路的指示信號。這個信號要比full信號提前,因為考慮到在判斷出滿之后,還需要一些動作(延時),才能不寫;於是乎我們就用將滿信號來補充這些延時,而不是等到滿信號才做出反應。
rempty_almost:將空信號。這個也是為了考慮在空信號判斷出來之后,到禁止繼續讀可能有延時,從而設立這個標識,在將空的時候就禁止繼續讀數據。
將滿信號和將空信號的關鍵因素就是讀寫地址之間的舉例(間隔),那我們來看看寫和讀的間隔怎么產生:
對於寫時鍾域,我們是要產生幾乎滿信號,這對應的間隔就是看看寫地址還有多少就趕上了讀地址,求出這兩個地址之間的間隔,然后再與預設的間距比較,如果這個間隔小於預設的間距,那么就產生幾乎滿的信號。那么我們這個間隔怎么求:
·當讀寫狀態位相同的時候,如下圖所示:
由於最高位相同,所以寫需要回卷才能最上讀,那么間隔也就是A+B;假設FIFO的深度是D,寫地址為waddr,讀地址為raddr,那么間隔就是D-C=D-(waddr-raddr)=D+raddr-waddr.(注意,這里的讀/寫地址不包括狀態位)
·當讀寫狀態不同位時,如下圖所示:
這時候waddr再有C就追上raddr了,因此間隔就是raddr-waddr。
上面是對於寫區域間隔的生成,下面就來說說讀區域的間隔怎么產生吧:
·狀態位一樣的時候,也就是沒有回卷的時候,如下圖所示:
很顯然,無論是加不加狀態位,都是間隔都是waddr-raddr,也就是說,還有waddr-raddr的舉例,raddr就追上了waddr。
·當狀態位不一樣時:
需要回卷才能追上寫,因此間隔就是:
FIFO的深度-(raddr-waddr)=FIFO+waddr-raddr(這里的讀寫地址不包括狀態位)。
當加上狀態位之后,我們發現,間隔是可以用raddr-waddr來表示的,比如raddr=1011,waddr是0011,間隔是8;加上狀態位后,Raddr=01011,Waddr=10011,間隔也可以表示為01011-10011=01000=8(借位是會被省略掉的),因此用加上狀態位后,間隔可以表示為waddr-raddr。
因此在讀時鍾域,間隔的表示就是帶狀態位的waddr-raddr。
說完了幾乎滿和幾乎空信號,我們再聊聊上面的框圖,整個FIFO可以分成寫邏輯模塊、讀邏輯模塊、寫/讀同步讀/寫模塊。其中
·寫邏輯的模塊的功能是:根據狀態信號和外部的寫信號產生對RAM的寫控制信號、產生RAM的地址信號、產生空和將空的狀態的狀態信號。因此寫邏輯模塊可以分成3個部分:①(RAM)寫控制邏輯部分;②RAM寫地址產生部分;③狀態產生部分。
①RAM寫控制邏輯部分的功能就是,產生RAM的寫使能信號:wclken有效的條件是:外部信號寫使能信號winc來了,而且此時滿信號沒有效,這個時候就允許往RAM里面寫數據了。
②RAM寫地址產生部分的功能就是產生RAM的地址和產生格雷碼地址(產生的格雷碼地址傳輸給同步模塊):復位的時候,RAM的地址waddr為0;此后,在寫時鍾上升沿檢測到RAM的寫使能wclken有效之后,waddr自動加一,指向下一個單元,wclken無效waddr則不變。我們使用比RAM地址寬1位的地址寄存器進行遞增,地址寄存器的最高位充當空滿信號時候的狀態比較位。
③狀態產生部分的功能就是:將同步模塊過來的格雷碼轉換成二進制,然后跟RAM寫地址產生部分傳來的地址進行比較,產生將滿信號和滿信號。(將滿信號的產生就是兩個地址小於某個間隔時有效,滿信號產生則是間隔等於0或者:間隔只有一個地址只差,但是這個時候RAM的寫信號還有效)。
·讀邏輯也是一樣,這里不再詳述,具體細節我們在代碼后面進行討論。
·寫/讀同步讀/寫模塊其實就是雙D觸發器(鏈)。
代碼如下所示:

1 //Async_FIFO ,4bit字寬,4bit深度
2 module Async_FIFO #( 3 parameter DATA_WIDTH = 4 , 4 parameter DEEP_WIDTH = 4
5 )( 6 //寫時鍾域信號
7 output wfull , 8 output wfull_almost , 9 input [DATA_WIDTH-1:0] wdata , 10 input winc , 11 input wclk , 12 input wrst_n , 13 //讀時鍾域信號
14 output rempty , 15 output rempty_almost , 16 output [DATA_WIDTH-1:0] rdata , 17 input rinc , 18 input rclk , 19 input rrst_n 20 ); 21 //中間的連線信號
22 wire [DEEP_WIDTH-1:0] raddr ; 23 wire [DEEP_WIDTH:0] rptr ; 24 wire rclken ; 25 wire [DEEP_WIDTH:0] rq2_wptr; 26
27 wire [DEEP_WIDTH-1:0] waddr ; 28 wire [DEEP_WIDTH:0] wptr ; 29 wire wclken ; 30 wire [DEEP_WIDTH:0] wq2_rptr; 31
32 Read_Data inst_Read_Data( 33 .rempty ( rempty ), 34 .rempty_almost ( rempty_almost ), 35 .raddr ( raddr ), 36 .rptr ( rptr ), 37 .rclken ( rclken ), 38 .rinc ( rinc ), 39 .rq2_wptr ( rq2_wptr ), //input,同步過來寫格雷碼指針
40 .rclk ( rclk ), 41 .rrst_n ( rrst_n ) 42 ); 43
44 DFF_Sync inst_r2w( 45 .dff_out ( wq2_rptr ), 46 .dff_in ( rptr ), 47 .dff_clk ( wclk ), 48 .dff_rst_n ( wrst_n ) 49 ); 50
51
52 Write_Data inst_Write_Data( 53 .wfull ( wfull ), //幾乎滿信號
54 .wfull_almost ( wfull_almost ), //幾乎空信號
55 .waddr ( waddr ), //輸出給RAM的地址
56 .wptr ( wptr ), //格雷碼地址指針
57 .wclken ( wclken ), //寫RAM信號
58 .wq2_rptr ( wq2_rptr ), //同步過來的讀格雷碼指針
59 .winc ( winc ), //外部輸入的使能信號
60 .wclk ( wclk ), //寫時鍾
61 .wrst_n ( wrst_n ) //寫復位
62
63 ); 64
65 DFF_Sync inst_w2r( 66 .dff_out ( rq2_wptr ), 67 .dff_in ( wptr ), 68 .dff_clk ( rclk ), 69 .dff_rst_n ( rrst_n ) 70 ); 71
72 ram_16x16 ram_16x16_inst ( 73 .data ( wdata ), 74 .rdaddress ( raddr ), 75 .rdclock ( rclk ), 76 .rden ( rclken ), 77 .wraddress ( waddr ), 78 .wrclock ( wclk ), 79 .wren ( wclken ), 80 .q ( rdata ) 81 ); 82
83
84 endmodule

1 module Write_Data #( 2 parameter DEEP_WIDTH = 4 , 3 parameter FIFO_DEEP = 5'd16 ,
4 parameter GAP_WIDTH = 3
5 )( 6 output wfull , //幾乎滿信號
7 output reg wfull_almost , //幾乎空信號
8 output [ DEEP_WIDTH-1:0] waddr , //輸出給RAM的地址
9 output reg [ DEEP_WIDTH:0] wptr , //格雷碼地址指針
10 output wclken , //寫RAM信號
11 input [ DEEP_WIDTH:0] wq2_rptr , //同步過來的讀格雷碼指針
12 input winc , //外部輸入的使能信號
13 input wclk , //寫時鍾
14 input wrst_n //寫復位
15
16 ); 17 reg [ DEEP_WIDTH:0] waddr_reg ;//地址寄存器,5位
18 reg [ DEEP_WIDTH:0] wq2_rptr_bin ;//讀指針同步到寫時鍾域后,從格雷碼轉換成二進制
19 reg [ DEEP_WIDTH:0] wgap_reg ;//寄存間隔的距離 20
21 //第一部分,寫RAM使能信號的生成
22 assign wclken = winc &&(~ wfull ); 23
24 //--------------------------------------//
25
26 //第二部分,產生RAM的地址和格雷碼
27 always @(posedge wclk or negedge wrst_n) 28 if(wrst_n == 1'b0) begin
29 waddr_reg <= 5'd0;
30 end
31 else if( wclken )begin //地址自增一
32 waddr_reg <= waddr_reg + 5'd1 ;
33 end
34
35 //生成RAM地址
36 assign waddr = waddr_reg[ DEEP_WIDTH-1:0]; 37
38 //生成格雷碼
39 always @(posedge wclk or negedge wrst_n) 40 if(wrst_n == 1'b0) begin
41 wptr <= 5'd0;
42 end
43 else begin
44 wptr <= waddr_reg ^( waddr_reg >> 1'b1); //其實也就是位錯之后的異或
45 //移位操作不代表移位寄存器
46 end
47
48 //-----------------------------------//
49
50 //第三部分 51
52 //將格雷碼轉換成二進制編碼
53 always @( * )begin
54 wq2_rptr_bin[4] = wq2_rptr[4] ; 55 wq2_rptr_bin[3] = wq2_rptr[4] ^ wq2_rptr[3] ; 56 wq2_rptr_bin[2] = wq2_rptr[4] ^ wq2_rptr[3] ^ wq2_rptr[2] ; 57 wq2_rptr_bin[1] = wq2_rptr[4] ^ wq2_rptr[3] ^ wq2_rptr[2] ^ wq2_rptr[1] ; 58 wq2_rptr_bin[0] = wq2_rptr[4] ^ wq2_rptr[3] ^ wq2_rptr[2] ^ wq2_rptr[1] ^ wq2_rptr[0] ; 59 end
60
61 //產生滿的間隔
62 always @(*)begin
63 if( waddr_reg[4] ^ wq2_rptr_bin[4] ) //最高位不相等的時候,也就是有一個是回卷了
64 wgap_reg = wq2_rptr_bin[3:0] - waddr_reg[3:0] ; 65 else //最高位相等的時候,也就是沒有回卷,那么間隔就是FIFO_DEEP-( waddr_reg - wq2_rptr_bin)
66 wgap_reg = FIFO_DEEP + wq2_rptr_bin - waddr_reg ; 67 end
68
69 //根據間隔產生幾乎滿信號
70 always @(posedge wclk or negedge wrst_n) 71 if(wrst_n == 1'b0) begin
72 wfull_almost <= 1'b0 ;
73 end
74 else if( wgap_reg < GAP_WIDTH)begin
75 wfull_almost <= 1'b1 ;
76 end
77 else
78 wfull_almost <= 1'b0 ;
79
80 //產生滿信號
81 assign wfull = (~(|wgap_reg ))||(( wgap_reg == 1)&&( winc ));//間隔為0的時候為滿,間隔是1的時候還要寫,也為滿
82
83 endmodule

1 module Read_Data #( parameter DATA_WIDTH = 4 , 2 parameter DEEP_WIDTH = 4 , 3 parameter GAP_WIDTH = 3
4 )( 5 output rempty , 6 output reg rempty_almost , 7 output [DEEP_WIDTH-1:0] raddr , //給 DPRAM的地址
8 output reg [DATA_WIDTH:0] rptr , //給寫時鍾域的讀格雷碼
9 output rclken , //DPRAM的讀使能信號
10 input rinc , //外部輸入的讀使能信號
11 input [DEEP_WIDTH:0] rq2_wptr , 12 input rclk , 13 input rrst_n 14 ); 15
16 reg [ DEEP_WIDTH:0] raddr_reg ;//地址寄存器,5位
17 reg [ DEEP_WIDTH:0] rq2_wptr_bin ;//寫指針同步到讀時鍾域后,從格雷碼轉換成二進制
18 reg [ DEEP_WIDTH:0] rgap_reg ;//寄存間隔的距離 19
20 //第一部分,讀RAM使能信號的生成
21 assign rclken = rinc &&(~ rempty ); 22
23 //--------------------------------------//
24
25 //第二部分,產生RAM的地址和格雷碼
26 always @(posedge rclk or negedge rrst_n) 27 if(rrst_n == 1'b0) begin
28 raddr_reg <= 5'd0;
29 end
30 else if( rclken )begin //地址自增一
31 raddr_reg <= raddr_reg + 5'd1 ;
32 end
33
34 //生成RAM地址
35 assign raddr = raddr_reg[ DEEP_WIDTH-1:0]; 36
37 //生成格雷碼
38 always @(posedge rclk or negedge rrst_n) 39 if(rrst_n == 1'b0) begin
40 rptr <= 5'd0;
41 end
42 else begin
43 rptr <= raddr_reg ^( raddr_reg >> 1'b1); //其實也就是位錯之后的異或,移位操作不代表移位寄存器
44 end
45
46 //-----------------------------------//
47
48 //第三部分 49
50 //將格雷碼轉換成二進制編碼
51 always @( * )begin
52 rq2_wptr_bin[4] = rq2_wptr[4] ; 53 rq2_wptr_bin[3] = rq2_wptr[4] ^ rq2_wptr[3] ; 54 rq2_wptr_bin[2] = rq2_wptr[4] ^ rq2_wptr[3] ^ rq2_wptr[2] ; 55 rq2_wptr_bin[1] = rq2_wptr[4] ^ rq2_wptr[3] ^ rq2_wptr[2] ^ rq2_wptr[1] ; 56 rq2_wptr_bin[0] = rq2_wptr[4] ^ rq2_wptr[3] ^ rq2_wptr[2] ^ rq2_wptr[1] ^ rq2_wptr[0] ; 57 end
58
59 //產生讀空的間隔
60 always @(*)begin
61 //無論狀態位是否相同,都可以用帶狀態位的寫地址減讀地址
62 rgap_reg = rq2_wptr_bin - raddr_reg ; 63 end
64
65 //根據間隔產生幾乎滿信號
66 always @(posedge rclk or negedge rrst_n) 67 if(rrst_n == 1'b0) begin
68 rempty_almost <= 1'b0 ;
69 end
70 else if( rgap_reg < GAP_WIDTH)begin
71 rempty_almost <= 1'b1 ;
72 end
73 else
74 rempty_almost <= 1'b0 ;
75
76 //產生滿信號
77 assign rempty = (~(|rgap_reg ))||(( rgap_reg == 1)&&( rinc ));//間隔為0的時候為空,間隔是1的時候還要讀,也為空
78
79
80
81 endmodule

1 module DFF_Sync #(parameter DEEP_WIDTH = 4
2 )( 3 output reg [DEEP_WIDTH:0] dff_out , 4 input [DEEP_WIDTH:0] dff_in , 5 input dff_clk , 6 input dff_rst_n 7 ); 8
9 reg [DEEP_WIDTH:0] sync_reg ; //兩級同步D觸發器
10 always @(posedge dff_clk or negedge dff_rst_n)begin
11 if( dff_rst_n == 1'b0)begin
12 sync_reg <= 5'd0 ;
13 dff_out <= 5'd0 ;
14 end
15 else begin
16 sync_reg <= dff_in ; 17 dff_out <= sync_reg ; 18 end
19 end
20
21 endmodule

1 //timescale
2 `timescale 1ns/1ns 3 module tb_module #( 4 parameter DATA_WIDTH = 4 )(); 5 //the Internal motivation variable(register) and output wire
6 wire wfull ; 7 wire wfull_almost ; 8 reg [DATA_WIDTH-1:0] wdata ; 9 reg winc ; 10 reg wclk ; 11 reg wrst_n ; 12 //讀時鍾域信號
13 wire rempty ; 14 wire rempty_almost ; 15 wire [DATA_WIDTH-1:0] rdata ; 16 reg rinc ; 17 reg rclk ; 18 reg rrst_n ; 19
20
21 //the External motivation storage variable 22
23
24 //Sub module signal,example: wire [1:0] xxx == xxx_inst.xxx_inst.xxx; 25
26 // Global variable initialization ,such as 'clk'、'rst_n'
27 initial begin
28 #0 rrst_n = 0; 29 wrst_n = 0 ; 30 wclk = 0; 31 rclk = 0; 32 // rinc = 0 ; 33 // winc = 0 ; 34 //wdata = 0 ;
35 #20 rrst_n = 1 ; 36 #25 wrst_n = 1 ; 37 end
38
39 //Internal motivation variable initialization 40 //initial begin 41 //end 42 // winc generate
43 always @(posedge wclk or wrst_n)begin
44 if( wrst_n == 1'b0 )begin
45 winc = 1'b0;
46 end
47 else if( wfull_almost ) 48 winc = 1'b0;
49 else
50 winc = 1'b1 ;
51 end
52
53 // rinc generate
54 always @(posedge rclk or rrst_n)begin
55 if( rrst_n == 1'b0 )begin
56 rinc = 1'b0 ;
57 end
58 else if( rempty ) 59 rinc = 1'b0;
60 else
61 rinc = 1'b1 ;
62 end
63
64 // wdata
65 always @(posedge wclk or negedge wrst_n)begin
66 if( wrst_n == 1'b0 )begin
67 wdata = 4'd0 ;
68 end
69 else if( winc )begin
70 wdata = wdata + 1'b1;
71 end
72 end
73
74 //cloclk signal generation
75 always #15 rclk = ~rclk ; 76 always #10 wclk = ~wclk ; 77
78 //Cases of sub module xxxx xxxx_inst(.(),.(), ... ,.());
79 Async_FIFO Async_FIFO_inst( 80 //寫時鍾域信號
81 .wfull ( wfull ), 82 .wfull_almost ( wfull_almost ), 83 .wdata ( wdata ), 84 .winc ( winc ), 85 .wclk ( wclk ), 86 .wrst_n ( wrst_n ), 87 //讀時鍾域信號
88 .rempty ( rempty ), 89 .rempty_almost ( rempty_almost ), 90 .rdata ( rdata ), 91 .rinc ( rinc ), 92 .rclk ( rclk ), 93 .rrst_n ( rrst_n) 94 ); 95
96 // Internal motivation variable assignment using task or random
97 /* example 98 task data_assign(xx); | task rand_bit(); 99 integer xx,xx,...; | integer i; 100 begin | begin 101 for( ; ; )begin | for(i=0; i<255; i=i+1)begin 102 @(posedge clock) | @(posedge sclk); 103 Internal motivation variable <= xxxxx; | Internal motivation variable <={$random} %2; 104 end | end 105 end | end 106 endtask | endtask 107 */
108
109
110 endmodule
這里的簡單仿真代碼仿真了寫時鍾間隔寫,讀時鍾一直讀的情況,使用modelsim的仿真波形如下所示:
最后我們來小節一下:
異步FIFO,主要用於跨時鍾域的數據信號傳輸,它的基本架構如下所示(純手工畫):
根據從上面電路架構圖,就可以寫代碼實現。
需要注意的是問題有:一個就是地址同步的問題,另外一個是空滿信號產生的問題。(深度選擇問題這里就不說了)
地址同步主要是用格雷碼同步實現;空滿信號則通過拓寬地址寄存器來實現。
格雷碼與二進制的轉換關系上面說了。格雷碼的優點就是相鄰之間只有一位信號變化,因此常常用來異步設計的編碼;缺點就是需要增加相應的組合邏輯。
五、跨時鍾域中的數據信號傳輸總結
跨時鍾域的數據信號傳輸到這里就結束了,在這里進行總結一下:
·數據變化速率比采樣速率低、或者比采樣速率略快時,可以使用握手信號進行。
·無論是快到慢,還是慢到快,FIFO通吃。
·FIFO的設計需要注意FIFO空滿信號產生問題、格雷碼的應用問題、深度選擇問題等。
·說到格雷碼的應用問題,也許會想到能不能先把數據變成格雷碼,然后再通過雙D觸發器同步過去呢?這明顯是不能的啊,你的數據不像FIFO的地址產生那樣,是具有相鄰性的,也就是只差一個1;因此不能把數據變成格雷碼,再傳輸。
PS:本文部分圖片來自網絡