跨时钟域问题(三)异步FIFO的Verilog实现(格雷码)
🏡 博客首頁:安靜到無聲
?? 歡迎關注 ?? 點贊 🎒 收藏 ?? 留言
目錄
- 異步FIFO概述
- 格雷碼(gray code)的使用
- 2.1 格雷碼編碼的verilog的實現
- 格雷碼下的 fifo空滿判斷
- 異步FIFO代碼及仿真實現
- 參考
異步FIFO概述
異步FIFO是用來在兩個異步時鐘域間傳輸數據。
其中fifo_full和fifo_empty分別是滿標志和空標志,用于說明數據狀態,當fifo_full時,不再進行數據的寫入,當fifo_empty時不再進行數據的讀取。
格雷碼(gray code)的使用
在產生FIFO滿信號時,要將寫指針和讀指針進行比較,由于兩個指針分別在各自的時鐘域,彼此之間是異步的,在使用二進制進行計數器實現指針時,就會導致用于比較的指針取樣錯誤。
使用自然二進制碼計數時,相鄰數據之間可能會產生多bit的變化。這會產生較大的尖峰電流以及其他問題。
比如,二進制計數器的值會從FFF變為000。這時所有位會同時改變。雖然能通過同步計數器避免亞穩態,但是仍然能得到極不相關的取樣值,所以同步計數器不是最終的解決方案。
從FFF 到000可能的轉換:
- FFF→000
- FFF→001
- FFF→010
- FFF→011
- FFF→100
- FFF→101
- FFF→110
- FFF→111
如果同步時鐘邊沿在FFF向000轉換的中間位置到來,就可能將三位二進制數的任何值取樣并同步到新的時鐘域中。鑒于以上情況,強烈建議避免使用二進制計數器實現讀、寫指針。
格雷碼是一種相鄰數據只有1bit變化的碼制。因此可以使用格雷碼去取代二進制計數器,并且用打拍的方式去同步(跨時鐘域問題(二)(單bit信號跨時鐘域 1. 電平同步器 2. 邊沿同步器 3. 脈沖檢測器))(只有深度為2的n次方才能用格雷碼的方式去同步,這樣才能保證最大值和最小值只有一位的變化)。我們可以將指針轉為格雷碼同步到另一個時鐘域再進行比較。如果同步時鐘在計數值轉換期間到來,這種編碼能夠消除絕大部分的錯誤。
2.1 格雷碼編碼的verilog的實現
自然二進制編碼轉換為格雷碼如下:
gray_code=binary_code⊕(binary_code>>1)gray\_code = binary\_code \oplus (binary\_code > > 1)gray_code=binary_code⊕(binary_code>>1)
格雷碼轉化為自然二進制
我們以??途WVL47 格雷碼計數器為例!
代碼
格雷碼下的 fifo空滿判斷
在判斷寫滿時,要將讀地址指針同步到寫地址指針的時鐘域;在判讀讀空是,要將寫地址指針同步到讀地址指針的時鐘域。
對于“空”的判斷依然依據二者完全相等(包括MSB);
而對于“滿”的判斷,如下圖,由于gray碼除了MSB外,具有鏡像對稱的特點,當讀指針指向7,寫指針指向8時,除了MSB,其余位皆相同,不能說它為滿。因此不能單純的只檢測最高位了,在gray碼上判斷為滿必須同時滿足以下3條:
異步FIFO代碼及仿真實現
module asyn_fifo#(parameter data_width = 16 ,//數據的寬度parameter data_depth = 8 ,//數據的深度parameter ram_depth = 256 //定義ram)(//復位時鐘input rst_n ,//復位時鐘//寫時鐘域input wr_clk ,//寫時鐘域時鐘input wr_en ,//寫使能input [data_width-1:0] data_in ,//寫入數據output full ,//滿標志//讀時鐘域input rd_clk ,//讀時鐘域時鐘input rd_en ,//讀使能output reg [data_width-1:0] data_out ,//讀出數據output empty //滿標志 );//定義雙端口ram的讀寫地址 wire [data_depth-1:0] wr_adr ;//雙端口ram的寫地址 wire [data_depth-1:0] rd_adr ;//雙端口ram的讀地址//定義雙端口ram的讀寫指針!為什么比讀寫地址多一位,在博客中講解! reg [data_depth:0] wr_adr_ptr ;//寫指針 reg [data_depth:0] rd_adr_ptr ;//讀指針//轉換為格雷碼進行打拍操作 wire [data_depth:0] wr_adr_gray ;//寫地址指針二進制轉化為格雷碼 reg [data_depth:0] wr_adr_gray1 ;//打一拍緩存 reg [data_depth:0] wr_adr_gray2 ;//打兩拍緩存wire [data_depth:0] rd_adr_gray ;//讀地址指針二進制轉化為格雷碼 reg [data_depth:0] rd_adr_gray1 ;//打一拍緩存 reg [data_depth:0] rd_adr_gray2 ;//打兩拍緩存//讀寫地址比控制指針少一位 assign wr_adr = wr_adr_ptr[data_depth-1:0]; assign rd_adr = rd_adr_ptr[data_depth-1:0];//開辟一段內存空間 reg [data_width-1:0] ram_fifo [ram_depth-1:0] ;//data_width*ram_depth//寫數據 integer i; always @(posedge wr_clk or negedge rst_n) beginif (!rst_n) beginfor (i = 0; i < ram_depth; i = i + 1) beginram_fifo[i] <= 'd0;endend else beginif(wr_en && (~full)) begin //如果寫使能開啟,且未滿ram_fifo[wr_adr] <= data_in;end else beginram_fifo[wr_adr] <= ram_fifo[wr_adr];endend end//讀數據 always @(posedge rd_clk or negedge rst_n) beginif (!rst_n) begindata_out <= 'd0;end else beginif(rd_en && (~empty)) begin //如果讀使能開啟,且未空data_out <= ram_fifo [rd_adr];end else begindata_out <= 'd0; //否則讀出為0endend end//寫地址指針控制 always @(posedge wr_clk or negedge rst_n) beginif (!rst_n) beginwr_adr_ptr <= 'd0;end else beginif(wr_en && (~full)) beginwr_adr_ptr <= wr_adr_ptr + 1'b1;end else beginwr_adr_ptr <= wr_adr_ptr;endend end//讀地址指針控制 always @(posedge rd_clk or negedge rst_n) beginif (!rst_n) beginrd_adr_ptr <= 'd0;end else beginif(rd_en && (~empty)) beginrd_adr_ptr <= rd_adr_ptr + 1'b1;end else beginrd_adr_ptr <= rd_adr_ptr;endend end//二進制指針轉化為格雷碼 assign wr_adr_gray = (wr_adr_ptr >> 1) ^ wr_adr_ptr; assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;//格雷碼的同步 讀時鐘域同步到寫時鐘域 always @(posedge wr_clk or negedge rst_n) beginif (!rst_n) beginrd_adr_gray1 <= 'd0;rd_adr_gray2 <= 'd0;end else beginrd_adr_gray1 <= rd_adr_gray;rd_adr_gray2 <= rd_adr_gray1;end end//格雷碼的同步 寫時鐘域同步到讀時鐘域 always @(posedge rd_clk or negedge rst_n) beginif (!rst_n) beginwr_adr_gray1 <= 'd0;wr_adr_gray2 <= 'd0;end else beginwr_adr_gray1 <= wr_adr_gray;wr_adr_gray2 <= wr_adr_gray1;end end//空標志---->表示讀空---->同步到讀時鐘域的寫地址指針和讀地址指針相同 assign empty = (rd_adr_gray == wr_adr_gray2) ? 1'b1 : 1'b0; assign full = (wr_adr_gray[data_depth] != rd_adr_gray2[data_depth]) && (wr_adr_gray[data_depth-1] != rd_adr_gray2[data_depth-1]) && (wr_adr_gray[data_depth-2:0] == rd_adr_gray2[data_depth-2:0]);endmodule仿真
`timescale 1ns / 1psmodule asyn_fifo_tb;reg rst_n ;reg wr_clk ; reg wr_en ; reg [ 15:0] data_in ; wire full ;reg rd_clk ; reg rd_en ; wire [ 15:0] data_out ; wire empty ;asyn_fifo asyn_fifo_inst(.rst_n (rst_n ),.wr_clk (wr_clk ),.wr_en (wr_en ),.data_in (data_in ),.full (full ),.rd_clk (rd_clk ),.rd_en (rd_en ),.data_out (data_out ),.empty (empty ) );initial wr_clk = 0;always#10 wr_clk = ~wr_clk;initial rd_clk = 0;always#30 rd_clk = ~rd_clk;always@(posedge wr_clk or negedge rst_n)beginif(!rst_n)data_in <= 'd0;else if(wr_en)data_in <= data_in + 1'b1;elsedata_in <= data_in;endinitial beginrst_n = 0;wr_en = 0;rd_en = 0;#200;rst_n = 1;wr_en = 1;#6000;wr_en = 0;rd_en = 1;#18000;rd_en = 0;$stop;endendmodule仿真結果
vivado的工程已將經傳至地址
參考
總結
以上是生活随笔為你收集整理的跨时钟域问题(三)异步FIFO的Verilog实现(格雷码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python发展历程
- 下一篇: jupyter浅析