「原創博文」基於FPGA的異步FIFO設計

「原创博文」基于FPGA的异步FIFO设计

今天要介紹的異步FIFO,可以有不同的讀寫時鐘,即不同的時鐘域。由於異步FIFO沒有外部地址端口,因此內部採用讀寫指針並順序讀寫,即先寫進FIFO的數據先讀取(簡稱先進先出)。這裡的讀寫指針是異步的,處理不同的時鐘域,而異步FIFO的空滿標誌位是根據讀寫指針的情況得到的。為了得到正確的空滿標誌位,需要對讀寫指針進行同步。一般情況下,如果一個時鐘域的信號直接給另一個時鐘域採集,可能會產生亞穩態,亞穩態的產生對設計而言是致命的。為了減少不同時鐘域間的亞穩態問題,我們先對它進行兩拍寄存同步,如圖1所示。當然,對異步信號的寄存越多,產生亞穩態的概率就越小,但延時越多。不過一般情況下,寄存兩拍就夠了。為了繼續減少亞穩態產生的概率,在對異步信號同步之前,將其轉換為格雷碼,使其每個狀態只有一個位在變化。例如,假設N位二進制變量產生的亞穩態概率為a,那麼二進制轉換成格雷碼後其產生的亞穩態概率則為a/N。

「原创博文」基于FPGA的异步FIFO设计

圖1 對異步信號用兩級寄存器同步

根據上述原理,設計了異步FIFO的架構,如圖2所示。

「原创博文」基于FPGA的异步FIFO设计

圖2 異步FIFO設計架構

根據異步FIFO的設計架構,歸納以下設計步驟:

寫時鐘域:

(1)根據寫使能wr_en和寫滿標誌位wr_full產生二進制寫指針

(2)根據二進制寫指針產生雙端口RAM的寫地址

(3)由二進制寫指針轉換成格雷碼寫指針

(4)對格雷碼讀指針在寫時鐘域中進行兩級同步得同步後格雷碼讀指針

(5)同步後格雷碼讀指針轉化成同步後二進制讀指針

(6)步驟(3)與步驟(4)比較得寫滿標誌位wr_full

(7)步驟(1)與步驟(5)相減得指示寫FIFO的數據量

讀時鐘域:

(8)根據讀使能rd_en和讀空標誌位rd_empty產生二進制讀指針

(9)根據二進制讀指針產生雙端口RAM的讀地址

(10)由二進制讀指針轉換成格雷碼讀指針

(11)對格雷碼寫指針在讀時鐘域中進行兩級同步得同步後格雷碼寫指針

(12)同步後格雷碼寫指針轉化成同步後二進制寫指針

(13)步驟(10)與步驟(11)比較得讀空標誌位rd_empty

(14)步驟(8)與步驟(12)相減得指示讀FIFO的數據量

Verilog HDL設計電路,如下所示:

/*******************************版權申明******************************** ** 電子技術應用網站, CrazyBird ** http://www.chinaaet.com, http://blog.chinaaet.com/crazybird ** **------------------------------文件信息-------------------------------- ** 文件名: asyn_fifo.v ** 創建者: CrazyBird ** 創建日期: 2016-1-16 ** 版本號: v1.0 ** 功能描述: 異步FIFO,用於處理不同的時鐘域 ** ***********************************************************************/ // synopsys translate_off `timescale 1 ns / 1 ps // synopsys translate_on module asyn_fifo( wr_rst_n, wr_clk, wr_en, wr_data, wr_full, wr_cnt, rd_rst_n, rd_clk, rd_en, rd_data, rd_empty, rd_cnt ); //****************************************************************** // 參數定義 //****************************************************************** parameter C_DATA_WIDTH = 8; parameter C_FIFO_DEPTH_WIDTH = 4; //****************************************************************** // 端口定義 //****************************************************************** input wr_rst_n; input wr_clk; input wr_en; input [C_DATA_WIDTH-1:0] wr_data; output reg wr_full; output reg [C_FIFO_DEPTH_WIDTH:0] wr_cnt; input rd_rst_n; input rd_clk; input rd_en; output [C_DATA_WIDTH-1:0] rd_data; output reg rd_empty; output reg [C_FIFO_DEPTH_WIDTH:0] rd_cnt; //****************************************************************** // 內部變量定義 //****************************************************************** reg [C_DATA_WIDTH-1:0] mem [0:(1 << C_FIFO_DEPTH_WIDTH)-1]; wire [C_FIFO_DEPTH_WIDTH-1:0] wr_addr; wire [C_FIFO_DEPTH_WIDTH-1:0] rd_addr; wire [C_FIFO_DEPTH_WIDTH:0] next_wr_bin_ptr; wire [C_FIFO_DEPTH_WIDTH:0] next_rd_bin_ptr; reg [C_FIFO_DEPTH_WIDTH:0] wr_bin_ptr; reg [C_FIFO_DEPTH_WIDTH:0] rd_bin_ptr; wire [C_FIFO_DEPTH_WIDTH:0] next_wr_gray_ptr; wire [C_FIFO_DEPTH_WIDTH:0] next_rd_gray_ptr; wire [C_FIFO_DEPTH_WIDTH:0] syn_wr_bin_ptr_rd_clk; wire [C_FIFO_DEPTH_WIDTH:0] syn_rd_bin_ptr_wr_clk; wire [C_FIFO_DEPTH_WIDTH:0] syn_wr_gray_ptr_rd_clk; wire [C_FIFO_DEPTH_WIDTH:0] syn_rd_gray_ptr_wr_clk; wire [C_FIFO_DEPTH_WIDTH:0] wr_cnt_w; wire [C_FIFO_DEPTH_WIDTH:0] rd_cnt_w; wire wr_full_w; wire rd_empty_w; //****************************************************************** // 雙端口RAM的讀寫 //****************************************************************** // 寫RAM always @(posedge wr_clk) begin if((wr_en & ~wr_full) == 1'b1) mem[wr_addr] <= wr_data; end // 讀RAM assign rd_data = mem[rd_addr]; //****************************************************************** // 二進制寫指針的產生 //****************************************************************** assign next_wr_bin_ptr = wr_bin_ptr + (wr_en & ~wr_full); always @(posedge wr_clk or negedge wr_rst_n) begin if(wr_rst_n == 1'b0) wr_bin_ptr <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}}; else wr_bin_ptr <= next_wr_bin_ptr; end //****************************************************************** // RAM寫地址的產生 //****************************************************************** assign wr_addr = wr_bin_ptr[C_FIFO_DEPTH_WIDTH-1:0]; //****************************************************************** // 二進制寫指針轉換成格雷碼寫指針 //****************************************************************** bin2gray #( .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1) ) u_bin2gray_wr ( .bin ( next_wr_bin_ptr ), .gray ( next_wr_gray_ptr ) ); //****************************************************************** // 對格雷碼讀指針在寫時鐘域中進行兩級同步 //****************************************************************** double_syn_ff #( .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1) ) u_double_syn_ff_wr ( .rst_n ( wr_rst_n ), .clk ( wr_clk ), .din ( next_rd_gray_ptr ), .dout ( syn_rd_gray_ptr_wr_clk ) ); //****************************************************************** // 同步後的格雷碼讀指針轉換成同步後的二進制讀指針 //****************************************************************** gray2bin #( .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1) ) u_gray2bin_wr ( .gray ( syn_rd_gray_ptr_wr_clk ), .bin ( syn_rd_bin_ptr_wr_clk ) ); //****************************************************************** // FIFO寫滿標誌位的產生和寫FIFO數據量的計數 //****************************************************************** assign wr_full_w = (next_wr_gray_ptr == ({~syn_rd_gray_ptr_wr_clk[C_FIFO_DEPTH_WIDTH:C_FIFO_DEPTH_WIDTH-1], syn_rd_gray_ptr_wr_clk[C_FIFO_DEPTH_WIDTH-2:0]})); assign wr_cnt_w = next_wr_bin_ptr - syn_rd_bin_ptr_wr_clk; always @(posedge wr_clk or negedge wr_rst_n) begin if(wr_rst_n == 1'b0) begin wr_full <= 1'b0; wr_cnt <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}}; end else begin wr_full <= wr_full_w; wr_cnt <= wr_cnt_w; end end //****************************************************************** // 二進制讀指針的產生 //****************************************************************** assign next_rd_bin_ptr = rd_bin_ptr + (rd_en & ~rd_empty); always @(posedge rd_clk or negedge rd_rst_n) begin if(rd_rst_n == 1'b0) rd_bin_ptr <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}}; else rd_bin_ptr <= next_rd_bin_ptr; end //****************************************************************** // RAM讀地址的產生 //****************************************************************** assign rd_addr = rd_bin_ptr[C_FIFO_DEPTH_WIDTH-1:0]; //****************************************************************** // 二進制讀指針轉換成格雷碼讀指針 //****************************************************************** bin2gray #( .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1) ) u_bin2gray_rd ( .bin ( next_rd_bin_ptr ), .gray ( next_rd_gray_ptr ) ); //****************************************************************** // 對格雷碼寫指針在讀時鐘域中進行兩級同步 //****************************************************************** double_syn_ff #( .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1) ) u_double_syn_ff_rd ( .rst_n ( rd_rst_n ), .clk ( rd_clk ), .din ( next_wr_gray_ptr ), .dout ( syn_wr_gray_ptr_rd_clk ) ); //****************************************************************** // 同步後的格雷碼寫指針轉換成同步後的二進制寫指針 //****************************************************************** gray2bin #( .C_DATA_WIDTH(C_FIFO_DEPTH_WIDTH+1) ) u_gray2bin_rd ( .gray ( syn_wr_gray_ptr_rd_clk ), .bin ( syn_wr_bin_ptr_rd_clk ) ); //****************************************************************** // FIFO讀空標誌位的產生和讀FIFO數據量的計數 //****************************************************************** assign rd_empty_w = (next_rd_gray_ptr == syn_wr_gray_ptr_rd_clk); assign rd_cnt_w = syn_wr_bin_ptr_rd_clk - next_rd_bin_ptr; always @(posedge rd_clk or negedge rd_rst_n) begin if(rd_rst_n == 1'b0) begin rd_empty <= 1'b0; rd_cnt <= {(C_FIFO_DEPTH_WIDTH+1){1'b0}}; end else begin rd_empty <= rd_empty_w; rd_cnt <= rd_cnt_w; end end endmodule

其中,模塊gray2bin是格雷碼轉二進制碼,模塊bin2gray是二進制碼轉格雷碼,詳情見上一篇博客,地址:http://blog.chinaaet.com/crazybird/p/5100000866。模塊double_syn_ff是兩級寄存器,用於同步信號,對應的Verilog HDL實現如下所示:

/*******************************版權申明******************************** ** 電子技術應用網站, CrazyBird ** http://www.chinaaet.com, http://blog.chinaaet.com/crazybird ** **------------------------------文件信息-------------------------------- ** 文件名: double_syn_ff.v ** 創建者: CrazyBird ** 創建日期: 2016-1-16 ** 版本號: v1.0 ** 功能描述: 對輸入信號進行兩級同步後輸出 ** ***********************************************************************/ // synopsys translate_off `timescale 1 ns / 1 ps // synopsys translate_on module double_syn_ff( rst_n, clk, din, dout ); //****************************************************************** // 參數定義 //****************************************************************** parameter C_DATA_WIDTH = 8; //****************************************************************** // 端口定義 //****************************************************************** input rst_n; input clk; input [C_DATA_WIDTH-1:0] din; output reg [C_DATA_WIDTH-1:0] dout; //****************************************************************** // 內部變量定義 //****************************************************************** reg [C_DATA_WIDTH-1:0] data_r; //****************************************************************** // 對輸入信號進行兩級同步後輸出 //****************************************************************** always @(posedge clk or negedge rst_n) begin if(rst_n == 1'b0) {dout,data_r} <= {(2*C_DATA_WIDTH){1'b0}}; else {dout,data_r} <= {data_r,din}; end endmodule

由於字數的限制,異步FIFO的功能驗證放在下一篇博文中吧!!!

招聘信息

「原创博文」基于FPGA的异步FIFO设计「原创博文」基于FPGA的异步FIFO设计
「原创博文」基于FPGA的异步FIFO设计


分享到:


相關文章: