异步FIFO的写时钟和读时钟为异步时钟,FIFO内部的写逻辑和读逻辑的交互需要异步处理,异步FIFO常用于跨时钟域交互。异步FIFO的设计重点有两个,第一个就是跨时钟域的转换及同步;第二个就是状态信号的产生采用相对保守的方式。 异步FIFO接口异步 FIFO的输入/输出 ,如下图所示:
FIFO还提供其他标识,如almost_full和almost_empty,用于提供关于FIFO再写人多少会满以及再读出多少会空的信息。 异步FIFO结构 异步FIFO基本上分为7个部分,分别是写时钟域的地址管理、读时钟域的地址管理、读时钟域读地址到写时钟域的格雷码方式同步、写时钟域写地址到读时钟域的格雷码方式同步、写时钟域的满和将满信号的产生、读时钟域的空和将空信号产生以及FIFO Memory。 异步FIFO设计思想 复位后,读和写地址指针均指在0地址处,同时almost empty 和empty信号均有效。 双口RAM接口 在 FIFO 中常用的RAM包括单口RAM、简单双口RAM、真双口RAM、单口ROM、双口ROM这5种类型的RAM,也可以使用寄存器来实现FIFO的存储器。寄存器实现的方式比较适合深度和位宽较小的情况,一般在FPGA里还是推荐使用Block RAM来实现异步FIFO的存储器。这里介绍简单双口RAM来做FIFO内部的RAM,双口RAM的接口如下图所示。
实现代码 module dual_port_RAM #( parameter DEPTH = 16, parameter WIDTH = 8 )( input wclk , //写数据时钟 input wenc , //写使能 input [$clog2(DEPTH)-1:0] waddr, //写地址 input [WIDTH-1:0] wdata, //输入数据 input rclk , //读数据时钟 input renc , //读使能 input [$clog2(DEPTH)-1:0] raddr, //读地址 output reg [WIDTH-1:0] rdata //输出数据 ); /********************参数定义********************/ /*********************IO 说明********************/ /********************** 内部信号声明 **********************/ reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1]; //RAM大小定义 /*************************功能定义*************************/ //RAM写操作 always @(posedge wclk) begin if(wenc) RAM_MEM[waddr] <= wdata; end //RAM读操作 always @(posedge rclk) begin if(renc) rdata <= RAM_MEM[raddr]; end endmodule 地址管理写时钟域的地址管理主要在写时钟域中产生FIFO Memory写地址和写有效信号,当写使能有效且FIFO没有写满时,FIFO写地址的递增应。实现代码如下: always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) w_bin_waddr <= {(ADDR_WIDTH+1){1'b0}}; else if (wren && !full) w_bin_waddr <= w_bin_waddr + 1'd1; else w_bin_waddr <= w_bin_waddr; end 读时钟域的地址管理读控制逻辑主要产生FIFO Memory读地址和读有效信号,当读使能有效且FIFO没有读空时,FIFO读地址的递增应。实现代码如下: always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_bin_raddr <= {(ADDR_WIDTH+1){1'b0}}; else if (rden && !empty) r_bin_raddr <= r_bin_raddr + 1'd1; else r_bin_raddr <= r_bin_raddr; end 格雷码方式同步格雷码二进制码转换为格雷编通用电路 二进制码转换成二进制格雷码,其法则是保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,而格雷码其余各位与次高位的求法相类似。N位二进制码到N位格雷码的生成公式为: {Gi=Bi⨁Bi+1 (i=0,1,...,n−2)Gn−1=Bn−1{Gi=Bi⨁Bi+1 (i=0,1,...,n−2)Gn−1=Bn−1 {Gi=Bi⨁Bi+1 (i=0,1,...,n−2)Gn−1=Bn−1实现代码如下: module binary_to_gray#(parameter WIDTH = 8) ( input [WIDTH-1:0] binary_value, //二进制编码数值 output [WIDTH-1:0] gray_value //格雷编码数值 ); /*************************功能定义*************************/ generate genvar i; for(i=0;i<(WIDTH-1);i=i+1) begin: gray assign gray_value[i] = binary_value[i]^binary_value[i+1]; end endgenerate assign gray_value[WIDTH-1] = binary_value[WIDTH-1]; endmodule 格雷编转换为二进制码通用电路 二进制格雷码转换成二进制码,其法则是保留格雷码的最高位作为自然二进制码的最高位,而次高位自然二进制码为高位自然二进制码与次高位格雷码相异或,而自然二进制码的其余各位与次高位自然二进制码的求法相类似。 {Bn−1=Gn−1Bi=Bi+1⨁Gi (i=n−2,...,1){Bn−1=Gn−1Bi=Bi+1⨁Gi (i=n−2,...,1) {Bn−1=Gn−1Bi=Bi+1⨁Gi (i=n−2,...,1)实现代码如下: module gray_to_binary #(parameter WIDTH = 8) ( input [WIDTH-1:0] gray_value, //格雷编码数值 output [WIDTH-1:0] binary_value //二进制编码数值 ); /*************************功能定义*************************/ genvar i; generate for(i=0;i<(WIDTH-1);i=i+1) begin: binary assign binary_value[i] = gray_value[i]^binary_value[i+1]; end endgenerate assign binary_value[WIDTH-1] = gray_value[WIDTH-1]; endmodule 格雷码方式同步原理 由于异步FIFO读写时钟非同一个,那么写地址计数值在从写时钟域往读时钟域和读地址计数器在从读时钟域往写时钟域传递的过程中会存在重汇聚(re-convergence)的问题。异步FIFO的地址进行跨时钟域的电路示意图如下: 写时钟域写地址到读时钟域的格雷码方式同步代码实现binary_to_gray #(.WIDTH(ADDR_WIDTH+1)) binary_to_gray_U1 ( .binary_value (w_bin_waddr), .gray_value (w_gray_waddr) ); //写时钟域寄存 always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) w_gray_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else w_gray_waddr_reg <= w_gray_waddr; end //读时钟域两级同步 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) begin r_gray_waddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ; r_gray_waddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ; end else begin r_gray_waddr_sync1 <= w_gray_waddr_reg ; r_gray_waddr_sync2 <= r_gray_waddr_sync1 ; end end 读时钟域读地址到写时钟域的格雷码方式同步代码实现//二进制码转格雷码 binary_to_gray #(.WIDTH(ADDR_WIDTH+1)) binary_to_gray_U2 ( .binary_value (r_bin_raddr), .gray_value (r_gray_raddr) ); //读时钟域寄存 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_gray_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else r_gray_raddr_reg <= r_gray_raddr; end //写时钟域两级同步 always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) begin w_gray_raddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ; w_gray_raddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ; end else begin w_gray_raddr_sync1 <= r_gray_raddr_reg ; w_gray_raddr_sync2 <= w_gray_raddr_sync1 ; end end 空满标志判断 当读写地址指针在复位操作期间被重置为零时,或者当读指针在从FIFO中读取了最后一个字之后赶上写指针时,此时读指针和写指针相等代表着FIFO为读空。而当写指针再次追赶上读指针时,此时读指针和写指针相等代表着FIFO为写满。也就是说当读写指针相等时,FIFO要么为空,要么为满。 若不产生almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志。则可以在将地址扩展一位后,用地址为格雷码格雷码编码时,当写地址和读地址的格雷码的最高位和次高位相反,其余低位相同时,表示FIFO写满,当写地址和地读地址的格雷码的相同时,表示FIFO读空。采用格雷码时电路结构如下所示: 空满标志判断代码:这里采用读写地址的格雷码进行比较,判断空满标志。实现代码如下所示: /*写满标志判断*/ assign full = ({~w_gray_waddr_reg[ADDR_WIDTH:ADDR_WIDTH-1],w_gray_waddr_reg[ADDR_WIDTH-2:0]} == w_gray_raddr_sync2)?1'b1:1'b0; /*读空标志判断*/ assign empty = (r_gray_raddr_reg == r_gray_waddr_sync2)? 1'b1:1'b0; 异步FIFO代码实现一不产生almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志采用读写地址的格雷码进行比较,判断空满标志; module async_fifo #( parameter WIDTH = 8, parameter DEPTH = 16 )( input wclk , //写时钟 input rclk , //读时钟 input w_rst_ , //写时钟域异步复位,低电平有效 input r_rst_ , //写时钟域异步复位,低电平有效 input wren , //写使能 input rden , //写使能 input [WIDTH-1:0] wdata , //写数据 output wire full , //写满信号 output wire empty , //读空信号 output wire [WIDTH-1:0] rdata //读数据 ); /********************参数定义********************/ /*********************IO 说明********************/ /********************** 内部信号声明 **********************/ localparam ADDR_WIDTH = $clog2(DEPTH); //地址位宽 wire wenc ; //双端口RAM写使能 wire renc ; //双端口RAM读使能 reg [ADDR_WIDTH:0] w_bin_waddr ; //写地址(二进制) reg [ADDR_WIDTH:0] r_bin_raddr ; //读地址(二进制) wire [ADDR_WIDTH:0] w_gray_waddr ; //写地址(格雷码) wire [ADDR_WIDTH:0] r_gray_raddr ; //读地址(格雷码) reg [ADDR_WIDTH:0] w_gray_waddr_reg ; //写地址(格雷码)写时钟域暂存寄存器 reg [ADDR_WIDTH:0] r_gray_raddr_reg ; //读地址(格雷码)读时钟域暂存寄存器 reg [ADDR_WIDTH:0] r_gray_waddr_sync1; //写地址(格雷码)写时钟域到读时钟域第一级同步 reg [ADDR_WIDTH:0] r_gray_waddr_sync2; //写地址(格雷码)写时钟域到读时钟域第二级同步 reg [ADDR_WIDTH:0] w_gray_raddr_sync1; //读地址(格雷码)读时钟域到写时钟域第一级同步 reg [ADDR_WIDTH:0] w_gray_raddr_sync2; //读地址(格雷码)读时钟域到写时钟域第二级同步 /*************************功能定义*************************/ assign wenc = wren && !full; assign renc = rden && !empty; /*双端口RAM*/ dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH)) dual_port_RAM_U1 ( .wclk (wclk ), //写数据时钟 .wenc (wenc ), //写使能 .waddr (w_bin_waddr), //写地址 .wdata (wdata ), //输入数据 .rclk (rclk ), //读数据时钟 .renc (renc ), //读使能 .raddr (r_bin_raddr), //读地址 .rdata (rdata ) //输出数据 ); /*写控制逻辑*/ always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) w_bin_waddr <= {(ADDR_WIDTH+1){1'b0}}; else if (wren && !full) w_bin_waddr <= w_bin_waddr + 1'd1; else w_bin_waddr <= w_bin_waddr; end /*读控制逻辑*/ always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_bin_raddr <= {(ADDR_WIDTH+1){1'b0}}; else if (rden && !empty) r_bin_raddr <= r_bin_raddr + 1'd1; else r_bin_raddr <= r_bin_raddr; end /*写地址从写时钟域到读时钟域的格雷码方式同步*/ //二进制码转格雷码 binary_to_gray #(.WIDTH(ADDR_WIDTH+1)) binary_to_gray_U1 ( .binary_value (w_bin_waddr), .gray_value (w_gray_waddr) ); //写时钟域寄存 always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) w_gray_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else w_gray_waddr_reg <= w_gray_waddr; end //读时钟域两级同步 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) begin r_gray_waddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ; r_gray_waddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ; end else begin r_gray_waddr_sync1 <= w_gray_waddr_reg ; r_gray_waddr_sync2 <= r_gray_waddr_sync1 ; end end /*读时钟域读地址到写时钟域的格雷码方式同步*/ //二进制码转格雷码 binary_to_gray #(.WIDTH(ADDR_WIDTH+1)) binary_to_gray_U2 ( .binary_value (r_bin_raddr), .gray_value (r_gray_raddr) ); //读时钟域寄存 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_gray_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else r_gray_raddr_reg <= r_gray_raddr; end //写时钟域两级同步 always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) begin w_gray_raddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ; w_gray_raddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ; end else begin w_gray_raddr_sync1 <= r_gray_raddr_reg ; w_gray_raddr_sync2 <= w_gray_raddr_sync1 ; end end /*写满标志判断*/ assign full = ({~w_gray_waddr_reg[ADDR_WIDTH:ADDR_WIDTH-1],w_gray_waddr_reg[ADDR_WIDTH-2:0]} == w_gray_raddr_sync2)?1'b1:1'b0; /*读空标志判断*/ assign empty = (r_gray_raddr_reg == r_gray_waddr_sync2)? 1'b1:1'b0; endmodule 实现二产生almost_full(FIFO再写人多少会满)和almost_empty(FIFO再读出多少会空)标志采用读写地址的二进制码进行比较,判断空满标志; module async_fifo #( parameter WIDTH = 8, parameter DEPTH = 16, parameter ALMOST_FULL_GAP = 3, //离满还有ALMOST_FULL_GAP时,almost_full有效 parameter ALMOST_EMPTY_GAP = 3 //离满还有ALMOST_EMPTY_GAP时,almost_empty有效 )( input wclk , //写时钟 input rclk , //读时钟 input w_rst_ , //写时钟域异步复位,低电平有效 input r_rst_ , //写时钟域异步复位,低电平有效 input wren , //写使能 input rden , //写使能 input [WIDTH-1:0] wdata , //写数据 output wire almost_full , //将满信号 output wire almost_empty, //将空信号 output wire full , //写满信号 output wire empty , //读空信号 output wire [WIDTH-1:0] rdata //读数据 ); /********************参数定义********************/ /*********************IO 说明********************/ /********************** 内部信号声明 **********************/ localparam ADDR_WIDTH = $clog2(DEPTH); //地址位宽 wire wenc ; //双端口RAM写使能 wire renc ; //双端口RAM读使能 reg [ADDR_WIDTH:0] w_bin_waddr ; //写地址(二进制) reg [ADDR_WIDTH:0] r_bin_raddr ; //读地址(二进制) wire [ADDR_WIDTH:0] w_gray_waddr ; //写地址(格雷码) wire [ADDR_WIDTH:0] r_gray_raddr ; //读地址(格雷码) reg [ADDR_WIDTH:0] w_gray_waddr_reg ; //写地址(格雷码)写时钟域暂存寄存器 reg [ADDR_WIDTH:0] r_gray_raddr_reg ; //读地址(格雷码)读时钟域暂存寄存器 reg [ADDR_WIDTH:0] r_gray_waddr_sync1; //写地址(格雷码)写时钟域到读时钟域第一级同步 reg [ADDR_WIDTH:0] r_gray_waddr_sync2; //写地址(格雷码)写时钟域到读时钟域第二级同步 reg [ADDR_WIDTH:0] w_gray_raddr_sync1; //读地址(格雷码)读时钟域到写时钟域第一级同步 reg [ADDR_WIDTH:0] w_gray_raddr_sync2; //读地址(格雷码)读时钟域到写时钟域第二级同步 wire [ADDR_WIDTH:0] r_bin_waddr ; //读时钟域二进制写地址 wire [ADDR_WIDTH:0] w_bin_raddr ; //写时钟域二进制读地址 reg [ADDR_WIDTH:0] w_bin_raddr_reg ; //写时钟域读地址(二进制)暂存寄存器 reg [ADDR_WIDTH:0] r_bin_waddr_reg ; //读时钟域写地址(二进制)暂存寄存器 reg [ADDR_WIDTH:0] room_avail ; //FIFO内剩余空间 reg [ADDR_WIDTH:0] data_avail ; //FIFO内已存入数据个数 /*************************功能定义*************************/ /*RAM写使能*/ assign wenc = wren && !full; /*RAM读使能*/ assign renc = rden && !empty; /*双端口RAM*/ dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH)) dual_port_RAM_U1 ( .wclk (wclk ), //写数据时钟 .wenc (wenc ), //写使能 .waddr (w_bin_waddr), //写地址 .wdata (wdata ), //输入数据 .rclk (rclk ), //读数据时钟 .renc (renc ), //读使能 .raddr (r_bin_raddr), //读地址 .rdata (rdata ) //输出数据 ); /*写控制逻辑*/ always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) w_bin_waddr <= {(ADDR_WIDTH+1){1'b0}}; else if (wren && !full) w_bin_waddr <= w_bin_waddr + 1'd1; else w_bin_waddr <= w_bin_waddr; end /*读控制逻辑*/ always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_bin_raddr <= {(ADDR_WIDTH+1){1'b0}}; else if (rden && !empty) r_bin_raddr <= r_bin_raddr + 1'd1; else r_bin_raddr <= r_bin_raddr; end /*写地址从写时钟域到读时钟域的格雷码方式同步*/ //二进制码转格雷码 binary_to_gray #(.WIDTH(ADDR_WIDTH+1)) binary_to_gray_U1 ( .binary_value (w_bin_waddr), .gray_value (w_gray_waddr) ); //写时钟域写地址(格雷码)寄存 always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) w_gray_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else w_gray_waddr_reg <= w_gray_waddr; end //读时钟域两级同步 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) begin r_gray_waddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ; r_gray_waddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ; end else begin r_gray_waddr_sync1 <= w_gray_waddr_reg ; r_gray_waddr_sync2 <= r_gray_waddr_sync1 ; end end //格雷码转二进制 gray_to_binary #(.WIDTH(ADDR_WIDTH+1)) gray_to_binary_U1 ( .gray_value (r_gray_waddr_sync2), .binary_value (r_bin_waddr) ); //读时钟域写地址(二进制)寄存 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_bin_waddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else r_bin_waddr_reg <= r_bin_waddr; end /*读时钟域读地址到写时钟域的格雷码方式同步*/ //二进制码转格雷码 binary_to_gray #(.WIDTH(ADDR_WIDTH+1)) binary_to_gray_U2 ( .binary_value (r_bin_raddr), .gray_value (r_gray_raddr) ); //读时钟域寄存 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) r_gray_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else r_gray_raddr_reg <= r_gray_raddr; end //写时钟域两级同步 always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) begin w_gray_raddr_sync1 <= {(ADDR_WIDTH+1){1'b0}} ; w_gray_raddr_sync2 <= {(ADDR_WIDTH+1){1'b0}} ; end else begin w_gray_raddr_sync1 <= r_gray_raddr_reg ; w_gray_raddr_sync2 <= w_gray_raddr_sync1 ; end end //格雷码转二进制 gray_to_binary #(.WIDTH(ADDR_WIDTH+1)) gray_to_binary_U2 ( .gray_value (w_gray_raddr_sync2), .binary_value (w_bin_raddr) ); //读时钟域写地址(二进制)寄存 always@(posedge rclk or negedge r_rst_) begin if(!r_rst_) w_bin_raddr_reg <= {(ADDR_WIDTH+1){1'b0}} ; else w_bin_raddr_reg <= w_bin_raddr; end /*FIFO内剩余空间计算*/ always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) room_avail <= {(ADDR_WIDTH+1){1'b0}}; else if(~w_bin_waddr[ADDR_WIDTH]==w_bin_raddr_reg[ADDR_WIDTH])//回卷时 room_avail <= w_bin_raddr_reg[ADDR_WIDTH-1:0]- w_bin_waddr[ADDR_WIDTH-1:0]; else //未回卷时 room_avail <= DEPTH + w_bin_raddr_reg - w_bin_waddr; end /*FIFO内已存入数据个数计算*/ always@(posedge wclk or negedge w_rst_) begin if(!w_rst_) data_avail <= {(ADDR_WIDTH+1){1'b0}}; else if(~r_bin_raddr[ADDR_WIDTH]==r_bin_waddr_reg[ADDR_WIDTH])//回卷时 data_avail <= DEPTH + r_bin_waddr_reg[ADDR_WIDTH-1:0]- r_bin_raddr[ADDR_WIDTH-1:0]; else //未回卷时 data_avail <= r_bin_waddr_reg - r_bin_raddr; end /*将满信号判断*/ assign almost_full = room_avail<ALMOST_FULL_GAP ? 1'b1:1'b0; /*将空信号判断*/ assign almost_empty = data_avail<ALMOST_EMPTY_GAP ? 1'b1:1'b0; /*写满标志判断*/ assign full = ({~w_bin_waddr[ADDR_WIDTH],w_bin_waddr[ADDR_WIDTH-1:0]} == w_bin_raddr_reg)?1'b1:1'b0; /*读空标志判断*/ assign empty = (r_bin_raddr == r_bin_waddr_reg)? 1'b1:1'b0; endmodule |
|