跨時鍾域信號傳輸(二)——數據信號篇


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:本文部分圖片來自網絡

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM