【原创】基于FPGA的异步FIFO设计
1赞今天要介绍的异步FIFO,可以有不同的读写时钟,即不同的时钟域。由于异步FIFO没有外部地址端口,因此内部采用读写指针并顺序读写,即先写进FIFO的数据先读取(简称先进先出)。这里的读写指针是异步的,处理不同的时钟域,而异步FIFO的空满标志位是根据读写指针的情况得到的。为了得到正确的空满标志位,需要对读写指针进行同步。一般情况下,如果一个时钟域的信号直接给另一个时钟域采集,可能会产生亚稳态,亚稳态的产生对设计而言是致命的。为了减少不同时钟域间的亚稳态问题,我们先对它进行两拍寄存同步,如图1所示。当然,对异步信号的寄存越多,产生亚稳态的概率就越小,但延时越多。不过一般情况下,寄存两拍就够了。为了继续减少亚稳态产生的概率,在对异步信号同步之前,将其转换为格雷码,使其每个状态只有一个位在变化。例如,假设N位二进制变量产生的亚稳态概率为a,那么二进制转换成格雷码后其产生的亚稳态概率则为a/N。
图1 对异步信号用两级寄存器同步
根据上述原理,设计了异步FIFO的架构,如图2所示。
图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的功能验证放在下一篇博文中吧!!!