FPGA FIFO一篇完整解释 1.FIFO 简介FIFO 先入先出, First In First Out 存储器在 FPGA 和数字 IC 设计中非常常用。 根据接入的时钟信号可以分为同步 FIFO 和异步 FIFO 。FIFO 底层基于双口 RAM同步 FIFO 的读写时钟一致异步 FIFO 读时钟和写时钟不同。同步时钟主要应用于速率匹配数据缓冲类似于乒乓存储提高性能的思想可以让后级不必等待前级过多时间 异步 FIFO 主要用于多 bit 信号的跨时钟域处理。本文讨论同步 FIFO 的结构及控制逻辑设计并给出代码。2.同步 FIFO 接口对于同步 FIFO 包含必要的接口如下图所示1 clk 时钟信号读写共用2 rst_n 复位信号视具体设计和芯片采用同步复位还是异步复位此处默认使用异步低电平复位3 wdata 写数据信号信号后带“ \ ”表示是多 bit 信号4 rdata 读数据信号信号后带“ \ ”表示是多 bit 信号5 wfull 满信号指示 FIFO 写满了不能再写了如果再写会覆盖掉还没读出的写入数据造成数据丢失6 rempty 空信号指示 FIFO 读空了不能在读了如果再读相当于有的数据重复读了第二遍造成数据错误7 winc 写使能信号写使能有效时表示希望能写入数据8 rinc 读使能信号读使能有效时表示希望能读出数据3.双口 RAM 接口在实现 FIFO 时无论是同步 FIFO 还是异步 FIFO 通常会通过双口 RAM Dual Port RAM 并添加一些必要的逻辑来实现。双口 RAM 的接口如下图所示。左侧全部是写时钟域的包括写时钟、写数据、写地址和写使能信号右侧全部是读时钟域的包括读时钟、读数据、读地址和读使能信号4.基于双口 RAM 的同步 FIFO 结构根据同步 FIFO 的接口和双口 RAM 的接口在借助双口 RAM 实现同步 FIFO 时如下图所示结构只需要加入读、写控制逻辑即可。在写逻辑中用于产生写地址和写满信号 在读逻辑中用于产生读地址和读空信号。 读写控制逻辑还需要受到读写使能信号的控制。5.读写地址产生逻辑读写地址什么时候能够递增显然对于写地址必须满足1 写使能有效要写入2 没写满能写入即always (posedge clk or negedge rst_n)beginif(~rst_n) beginwaddr b0;endelse beginif( winc ~wfull ) beginwaddr waddr 1b1;endelse beginwaddr waddr;endendend对于读地址必须满足1 读使能有效要读出2 没读空能读出即always (posedge clk or negedge rst_n)beginif(~rst_n) beginraddr b0;endelse beginif( rinc ~rempty ) beginraddr raddr 1b1;endelse beginraddr raddr;endendend6.空满信号产生逻辑搞定了读写地址的控制逻辑还差最后一步也是最关键的信号空满信号如何产生。空 读空读地址追上写地址满 写满写地址追上读地址。问题来了 怎么判地址断追上了呢 如果地址相等那应该是追上了即 raadr waddr 或者 wddr raddr 。 如果按照这种判断显然这两个地址追上对方的判断是等效的无法区分出来到底是写追上读还是读追上写。可以考虑 使用 1 个标志位 flag 来额外指示写追上读还是读追上写。参考前人的文献判断空满的方式有多种非常常用的一种是 Clifford E. Cummings 文章中提到的扩展 1 bit 的读写地址方法也就是说将前面提到的 flag 指示信号和原本 N 位的读写地址结合使用 N1 位的读写地址其中最高位用于判断空满信号其余低位还是正常用于读写地址索引。以一个 4 深度的 FIFO 实例来说明 4 深度原本需要 2 bit 的读写地址现在扩展成 3 bit 。使用低 2 位来进行双口 RAM 的地址索引高位用于判断空满。 对于空信号可以知道当 FIFO 里没有待读出的数据时产生。也就是说此时读追上了写把之前写的数据刚刚全部都出读地址和写地址此时指向相同的位置读地址 - 写地址 0即raddr waddr对于写满信号当写入后还没被读出的数据恰好是 FIFO 深度的时候产生满信号即写地址 - 读地址 FIFO 深度 4。 对照下图可以发现此时对于双口 RAM 的 2 bit 的地址来说读写地址一致 对于最高位来所写是 1 而读是 0 。再考虑下图所示的一种情况写入待读出的数据仍然是 4 个此时也是 4 深度的 FIFO 已经满了。 读写地址的低位相同高位是写 0 读 1 。对于写满的 2 种情况总结下来都是低位相同最高位相反。即raddr[N] ~waddr[N]raddr[N-1:0] waddr[N-1:0]也就是raddr {~waddr[N], waddr[N-1:0]}所以空满逻辑产生的代码为always (posedge clk or negedge rst_n)beginif(~rst_n) beginwfull b0;rempty b0;endelse beginwfull (raddr {~waddr[ADDR_WIDTH], waddr[ADDR_WIDTH-1:0]});rempty (raddr waddr);endend7.全部代码timescale 1ns/1ns/****************************/// 作者FPGA探索者/****************************/module sfifo#(parameter WIDTH 8,parameter DEPTH 16)(input clk ,input rst_n ,input winc ,input rinc ,input [WIDTH-1:0] wdata ,output reg wfull ,output reg rempty ,output wire [WIDTH-1:0] rdata);// 用localparam定义一个参数可以在文件内使用localparam ADDR_WIDTH $clog2(DEPTH);reg [ADDR_WIDTH:0] waddr;reg [ADDR_WIDTH:0] raddr;always (posedge clk or negedge rst_n) beginif(~rst_n) beginwaddr b0;endelse beginif( winc ~wfull ) beginwaddr waddr 1b1;endelse beginwaddr waddr;endendendalways (posedge clk or negedge rst_n) beginif(~rst_n) beginraddr b0;endelse beginif( rinc ~rempty ) beginraddr raddr 1b1;endelse beginraddr raddr;endendendalways (posedge clk or negedge rst_n) beginif(~rst_n) beginwfull b0;rempty b0;endelse beginwfull (raddr {~waddr[ADDR_WIDTH], waddr[ADDR_WIDTH-1:0]});rempty (raddr waddr);endend// 带有 parameter 参数的例化格式dual_port_RAM#(.DEPTH(DEPTH),.WIDTH(WIDTH))dual_port_RAM_U0(.wclk(clk),.wenc(winc),.waddr(waddr[ADDR_WIDTH-1:0]),.wdata(wdata),.rclk(clk),.renc(rinc),.raddr(raddr[ADDR_WIDTH-1:0]),.rdata(rdata));endmodule/**************RAM 子模块*************/module dual_port_RAM #(parameter DEPTH 16,parameter WIDTH 8)(input wclk,input wenc,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数得到地址的位宽。,input [WIDTH-1:0] wdata //数据写入,input rclk,input renc,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数得到地址的位宽。,output reg [WIDTH-1:0] rdata //数据输出);reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];always (posedge wclk) beginif(wenc)RAM_MEM[waddr] wdata;endalways (posedge rclk) beginif(renc)rdata RAM_MEM[raddr];endendmodule