跨时钟域问题(三)异步FIFO的Verilog实现(格雷码)
🏡 博客首頁:安靜到無聲
?? 歡迎關(guān)注 ?? 點(diǎn)贊 🎒 收藏 ?? 留言
目錄
- 異步FIFO概述
- 格雷碼(gray code)的使用
- 2.1 格雷碼編碼的verilog的實(shí)現(xiàn)
- 格雷碼下的 fifo空滿判斷
- 異步FIFO代碼及仿真實(shí)現(xiàn)
- 參考
異步FIFO概述
異步FIFO是用來在兩個(gè)異步時(shí)鐘域間傳輸數(shù)據(jù)。
其中fifo_full和fifo_empty分別是滿標(biāo)志和空標(biāo)志,用于說明數(shù)據(jù)狀態(tài),當(dāng)fifo_full時(shí),不再進(jìn)行數(shù)據(jù)的寫入,當(dāng)fifo_empty時(shí)不再進(jìn)行數(shù)據(jù)的讀取。
格雷碼(gray code)的使用
在產(chǎn)生FIFO滿信號(hào)時(shí),要將寫指針和讀指針進(jìn)行比較,由于兩個(gè)指針分別在各自的時(shí)鐘域,彼此之間是異步的,在使用二進(jìn)制進(jìn)行計(jì)數(shù)器實(shí)現(xiàn)指針時(shí),就會(huì)導(dǎo)致用于比較的指針取樣錯(cuò)誤。
使用自然二進(jìn)制碼計(jì)數(shù)時(shí),相鄰數(shù)據(jù)之間可能會(huì)產(chǎn)生多bit的變化。這會(huì)產(chǎn)生較大的尖峰電流以及其他問題。
比如,二進(jìn)制計(jì)數(shù)器的值會(huì)從FFF變?yōu)?00。這時(shí)所有位會(huì)同時(shí)改變。雖然能通過同步計(jì)數(shù)器避免亞穩(wěn)態(tài),但是仍然能得到極不相關(guān)的取樣值,所以同步計(jì)數(shù)器不是最終的解決方案。
從FFF 到000可能的轉(zhuǎn)換:
- FFF→000
- FFF→001
- FFF→010
- FFF→011
- FFF→100
- FFF→101
- FFF→110
- FFF→111
如果同步時(shí)鐘邊沿在FFF向000轉(zhuǎn)換的中間位置到來,就可能將三位二進(jìn)制數(shù)的任何值取樣并同步到新的時(shí)鐘域中。鑒于以上情況,強(qiáng)烈建議避免使用二進(jìn)制計(jì)數(shù)器實(shí)現(xiàn)讀、寫指針。
格雷碼是一種相鄰數(shù)據(jù)只有1bit變化的碼制。因此可以使用格雷碼去取代二進(jìn)制計(jì)數(shù)器,并且用打拍的方式去同步(跨時(shí)鐘域問題(二)(單bit信號(hào)跨時(shí)鐘域 1. 電平同步器 2. 邊沿同步器 3. 脈沖檢測(cè)器))(只有深度為2的n次方才能用格雷碼的方式去同步,這樣才能保證最大值和最小值只有一位的變化)。我們可以將指針轉(zhuǎn)為格雷碼同步到另一個(gè)時(shí)鐘域再進(jìn)行比較。如果同步時(shí)鐘在計(jì)數(shù)值轉(zhuǎn)換期間到來,這種編碼能夠消除絕大部分的錯(cuò)誤。
2.1 格雷碼編碼的verilog的實(shí)現(xiàn)
自然二進(jìn)制編碼轉(zhuǎn)換為格雷碼如下:
gray_code=binary_code⊕(binary_code>>1)gray\_code = binary\_code \oplus (binary\_code > > 1)gray_code=binary_code⊕(binary_code>>1)
格雷碼轉(zhuǎn)化為自然二進(jìn)制
我們以牛客網(wǎng)VL47 格雷碼計(jì)數(shù)器為例!
代碼
格雷碼下的 fifo空滿判斷
在判斷寫滿時(shí),要將讀地址指針同步到寫地址指針的時(shí)鐘域;在判讀讀空是,要將寫地址指針同步到讀地址指針的時(shí)鐘域。
對(duì)于“空”的判斷依然依據(jù)二者完全相等(包括MSB);
而對(duì)于“滿”的判斷,如下圖,由于gray碼除了MSB外,具有鏡像對(duì)稱的特點(diǎn),當(dāng)讀指針指向7,寫指針指向8時(shí),除了MSB,其余位皆相同,不能說它為滿。因此不能單純的只檢測(cè)最高位了,在gray碼上判斷為滿必須同時(shí)滿足以下3條:
異步FIFO代碼及仿真實(shí)現(xiàn)
module asyn_fifo#(parameter data_width = 16 ,//數(shù)據(jù)的寬度parameter data_depth = 8 ,//數(shù)據(jù)的深度parameter ram_depth = 256 //定義ram)(//復(fù)位時(shí)鐘input rst_n ,//復(fù)位時(shí)鐘//寫時(shí)鐘域input wr_clk ,//寫時(shí)鐘域時(shí)鐘input wr_en ,//寫使能input [data_width-1:0] data_in ,//寫入數(shù)據(jù)output full ,//滿標(biāo)志//讀時(shí)鐘域input rd_clk ,//讀時(shí)鐘域時(shí)鐘input rd_en ,//讀使能output reg [data_width-1:0] data_out ,//讀出數(shù)據(jù)output empty //滿標(biāo)志 );//定義雙端口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 ;//讀指針//轉(zhuǎn)換為格雷碼進(jìn)行打拍操作 wire [data_depth:0] wr_adr_gray ;//寫地址指針二進(jìn)制轉(zhuǎn)化為格雷碼 reg [data_depth:0] wr_adr_gray1 ;//打一拍緩存 reg [data_depth:0] wr_adr_gray2 ;//打兩拍緩存wire [data_depth:0] rd_adr_gray ;//讀地址指針二進(jìn)制轉(zhuǎn)化為格雷碼 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];//開辟一段內(nèi)存空間 reg [data_width-1:0] ram_fifo [ram_depth-1:0] ;//data_width*ram_depth//寫數(shù)據(jù) 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//讀數(shù)據(jù) 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//二進(jìn)制指針轉(zhuǎn)化為格雷碼 assign wr_adr_gray = (wr_adr_ptr >> 1) ^ wr_adr_ptr; assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;//格雷碼的同步 讀時(shí)鐘域同步到寫時(shí)鐘域 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//格雷碼的同步 寫時(shí)鐘域同步到讀時(shí)鐘域 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//空標(biāo)志---->表示讀空---->同步到讀時(shí)鐘域的寫地址指針和讀地址指針相同 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仿真結(jié)果
vivado的工程已將經(jīng)傳至地址
參考
總結(jié)
以上是生活随笔為你收集整理的跨时钟域问题(三)异步FIFO的Verilog实现(格雷码)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python发展历程
- 下一篇: Vue相关知识总结