本章目录:

  • 1. 了解FIFO
    • 1.1 定义
    • 1.2 FIFO有什么用处?
    • 1.3 FIFO的参数有哪些?
  • 2. 同步FIFO
    • 2.1 原理
    • 2.2 代码
  • 3. 异步FIFO
    • 3.1 原理
    • 3.2 最小深度计算
      • 3.2.1 需要用到FIFO最小深度的情况
      • 3.2.2 fa>fb并且没有空闲(IDLE)周期
      • 3.2.3 fa>fb并且有空闲(IDLE)周期
      • 3.2.4 fa

1. 了解FIFO

1.1 定义

FIFO(First In First Out),即先进先出队列。FIFO存储器是一个先入先出的双口缓冲器,即第一个进入其内的数据第一个被移出,其中一个是存储器的输入口,另一个口是存储器的输出口。对于单片FIFO来说,主要有两种结构:触发导向结构和零导向传输结构。触发导向传输结构的FIFO是由寄存器阵列构成的,零导向传输结构的FIFO是由具有读和写地址指针的双口RAM构成。

FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。

根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO。同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。 Xilinx 的 FIFO IP 核可以被配置为同步 FIFO 或异步 FIFO,其信号框图如下图所示。从图中可以了解到,当被配置为同步 FIFO 时,只使用 wr_clk,所有的输入输出信号都同步于 wr_clk 信号。而当被配置为异步 FIFO 时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关的信号都是同步于读时钟 rd_clk。

1.2 FIFO有什么用处?

  1. 跨时钟域
  2. 在将数据发送到芯片外之前将其缓冲(例如,发送到DRAM或SRAM)
  3. 缓冲数据以供软件在以后查看
  4. 存储数据以备后用

1.3 FIFO的参数有哪些?

宽度:一次读写操作的数据位
深度:可以存储的 N 位数据的数目(宽度为 N)
满标志: full。FIFO 已满时,由 FIFO 的状态电路送出的信号,阻止 FIFO 写操作
空标志: empty。FIFO 已空时,由 FIFO 的状态电路送出的信号,阻止 FIFO 读操作
读时钟:读操作所遵循的时钟
写时钟:写操作所遵循的时钟

2. 同步FIFO

2.1 原理

典型同步FIFO由三部分组成:FIFO写控制逻辑、FIFO读控制逻辑、FIFO存储实体。

FIFO写控制逻辑主要功能:产生FIFO写地址、写有效信号,同时产生FIFO写满、写错等状态信号;
FIFO读控制逻辑主要功能:产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号。

FIFO读写过程的地址控制如下图所示:

当FIFO初始化(复位)时,fifo_write_addr与fifo_read_addr同指到0x0,此时FIFO处于空状态;
当FIFO进行写操作时,fifo_write_addr递增(增加到FIFO DEPTH时回绕),与fifo_read_addr错开,此时FIFO处于非空状态;
当FIFO进行读操作时,fifo_read_addr递增。

FIFO空满状态产生:

为产生FIFO空满标志,引入Count 计数器,用于指示FIFO内部存储数据个数;
当只有写操作时,Count加1;只有读操作时,Count减1;其它情况下,保持不变;
Count为0时,说明FIFO为空,fifo_empty置位;
Count等于FIFO_DEPTH时,说明FIFO已满,fifo_full置位。

2.2 代码

参照博客

3. 异步FIFO

3.1 原理

异步FIFO的实现通常是利用双口RAM和读写地址产生模块来实现的。FIFO的接口包括异步的写时钟(wr_clk)和读时钟(rd_clk)、与写时钟同步的写有效(wr_en)和写数据(wr_data)、与读时钟同步的读有效(rd_en)和读数据(rd_data)。为了实现正确的读写和避免FIFO的上溢或下溢,通常还应该给出与读时钟和写时钟同步的FIFO的空标志(empty)和满标志(full)以禁止读写操作。

写地址产生模块还根据读地址和写地址关系产生FIFO的满标志。当wren有效时,若写地址+2=读地址时,full为1;当wren无效时,若写地址+ 1=读地址时,full为1。读地址产生模块还根据读地址和写地址的差产生FIFO的空标志。当rden有效时,若写地址-1=读地址时,empty为 1;当rden无效时,若写地址=读地址时,empty为1。按照以上方式产生标志信号是为了提前一个时钟周期产生对应的标志信号。

由于空标志和满标志控制了FIFO的操作,因此标志错误会引起操作的错误。如上所述,标志的产生是通过对读写地址的比较产生的,当读写时钟完全异步时,对读写地址进行比较时,可能得出错误的结果。例如,在读地址变化过程中,由于读地址的各位变化并不同步,计算读写地址的差值,可能产生错误的差值,导致产生错误的满标志信号。若将未满标志置为满标志时,可能降低了应用的性能,降低写数据速率;而将满置标志置为未满时,执行一次写操作,则可能产生溢出错误,这对于实际应用来说是绝对应该避免的。空标志信号的产生也可能产生类似的错误。

3.2 最小深度计算


对于异步FIFO而言,无非就是写入频率大于或者小于读出频率呗!然而,对于写入频率 小于 读出频率,意味着写的慢,读的快,会怎样?那肯定是对你的FIFO深度没有要求了呀!你没了我就不读了呗。重要的是看另一种情况:写入大于读出,那就需要FIFO进行储存,既然储存,那肯定得有深度要求!

3.2.1 需要用到FIFO最小深度的情况

大概有以下四种情况:(以上图为例)

  1. fa>fb并且没有空闲(IDLE)周期;
  2. fa>fb并且有空闲(IDLE)周期;
  3. fa<fb并且有空闲(IDLE)周期;
  4. fa<fb并且随机读写;

3.2.2 fa>fb并且没有空闲(IDLE)周期


上边这种情况,通过一个例子可以看出来,此时需要FIFO深度。

3.2.3 fa>fb并且有空闲(IDLE)周期

3.2.4 fa<fb并且有空闲(IDLE)周期

3.2.5 fa<fb并且随机读写


通过理解这个例子,我们就可以知道为什么会有下边总结的这些公式了===>

3.2.6 总结最后一种情况的公式

写时钟频率w_clk
读时钟频率 r_clk,
写时钟周期里,每B个时钟周期会有A个数据写入FIFO
读时钟周期里,每Y个时钟周期会有X个数据读出FIFO


举例说明:
假设 FIFO 的写时钟为 100MHZ,读时钟为 80MHZ。在 FIFO 输入侧,每 100 个时钟,写入80 个数据;FIFO 读入测,每个时钟读取一个数据。设计合理的 FIFO 深度,使 FIFO 不会溢出:考虑背靠背(20个clk不发数据+80clk发数据+80clk发数据+20个clk不发数据的200个clk)代入公式可计算FIFO的深度:160-1601(80/100)=32.

3.3 代码实现

参考博客

4. 小试牛刀

基础知识学完之后,那就小试牛刀!
VL22 同步FIFO
代码如下:

`timescale 1ns/1ns
/**********************************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;
end always @(posedge rclk) beginif(renc)rdata <= RAM_MEM[raddr];
end endmodule  /**********************************SFIFO************************************/
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 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 <= 1'b0;endelse beginif(winc && !wfull) beginwaddr <= waddr + 1'b1; endelse beginwaddr <= waddr; endendend//读地址 如何操作always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginraddr <= 1'b0; endelse beginif(rinc && !rempty) beginraddr <= raddr + 1'b1; endelse beginraddr <= raddr; endendend//空满判断always @ (posedge clk or negedge rst_n) beginif(!rst_n) beginwfull <= 1'b0;rempty <= 1'b0;endelse beginwfull <= (waddr == {~raddr[ADDR_WIDTH], raddr[ADDR_WIDTH-1:0]});rempty <= (raddr == waddr);endend// 实例化dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH)) dual_port_RAM_0 (.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

VL21 异步FIFO
代码如下:

`timescale 1ns/1ns/***************************************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;
end always @(posedge rclk) beginif(renc)rdata <= RAM_MEM[raddr];
end endmodule  /***************************************AFIFO*****************************************/
module asyn_fifo#(parameter WIDTH = 8,parameter    DEPTH = 16
)(input                     wclk    , input                     rclk    ,   input                   wrstn   ,input                  rrstn   ,input                  winc    ,input                  rinc    ,input      [WIDTH-1:0] wdata   ,output wire                wfull   ,output wire                rempty  ,output wire [WIDTH-1:0]    rdata
);// 本地参数localparam ADDR_WIDTH = $clog2(DEPTH);reg [ADDR_WIDTH:0] waddr;reg [ADDR_WIDTH:0] raddr;// 写地址 操作always @ (posedge wclk or negedge wrstn) beginif(!wrstn) beginwaddr <= 'b0; endelse beginif(winc && !wfull) beginwaddr <= waddr + 1'b1; endelse beginwaddr <= waddr; endendend// 读地址 操作always @ (posedge rclk or negedge rrstn) beginif(!rrstn) beginraddr <= 'b0; endelse begin`timescale 1ns/1ns/***************************************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;
end always @(posedge rclk) beginif(renc)rdata <= RAM_MEM[raddr];
end endmodule  /***************************************AFIFO*****************************************/
module asyn_fifo#(parameter WIDTH = 8,parameter    DEPTH = 16
)(input                     wclk    , input                     rclk    ,   input                   wrstn   ,input                  rrstn   ,input                  winc    ,input                  rinc    ,input      [WIDTH-1:0] wdata   ,output wire                wfull   ,output wire                rempty  ,output wire [WIDTH-1:0]    rdata
);// 本地参数localparam ADDR_WIDTH = $clog2(DEPTH);reg [ADDR_WIDTH:0] waddr;reg [ADDR_WIDTH:0] raddr;// 写地址 操作always @ (posedge wclk or negedge wrstn) beginif(~wrstn) beginwaddr <= 'b0; endelse beginif(winc && ~wfull) beginwaddr <= waddr + 1'b1; endelse beginwaddr <= waddr; endendend// 读地址 操作always @ (posedge rclk or negedge rrstn) beginif(~rrstn) beginraddr <= 'b0; endelse beginif(rinc && ~rempty) beginraddr <= raddr + 1'b1; endelse beginraddr <= raddr; endendend// 二进制 转 格雷码wire [ADDR_WIDTH:0] waddr_gray;wire [ADDR_WIDTH:0] raddr_gray;assign waddr_gray = waddr ^ (waddr>>1);assign raddr_gray = raddr ^ (raddr>>1);// 因为上边转码的时候是 组合逻辑赋值,为了避免出现亚稳态,这里打一拍reg [ADDR_WIDTH:0] waddr_gray_reg;always @ (posedge wclk or negedge wrstn) beginif(~wrstn) beginwaddr_gray_reg <= 'd0;endelse beginwaddr_gray_reg <= waddr_gray; endend//因为上边转码的时候是 组合逻辑赋值,为了避免出现亚稳态,这里打一拍reg [ADDR_WIDTH:0] raddr_gray_reg;always @ (posedge rclk or negedge rrstn) beginif(~rrstn) beginraddr_gray_reg <= 'd0; endelse beginraddr_gray_reg <= raddr_gray;endend// 读时钟域 同步到 写时钟域 ===> 跨时钟域,打两拍reg [ADDR_WIDTH:0] addr_r2w_temp;reg [ADDR_WIDTH:0] addr_r2w;always @ (posedge wclk or negedge wrstn) beginif(~wrstn) beginaddr_r2w_temp <= 'd0;addr_r2w <= 'd0;endelse beginaddr_r2w_temp <= raddr_gray_reg;addr_r2w <= addr_r2w_temp;endend// 写时钟域 同步到 读时钟域 ===> 跨时钟域,打两拍reg [ADDR_WIDTH:0] addr_w2r_temp;reg [ADDR_WIDTH:0] addr_w2r;always @ (posedge rclk or negedge rrstn) beginif(~rrstn) beginaddr_w2r_temp <= 'd0;addr_w2r <= 'd0;endelse beginaddr_w2r_temp <= waddr_gray_reg;addr_w2r <= addr_w2r_temp;endend// 空满判断assign wfull = (waddr_gray_reg == {~addr_r2w[ADDR_WIDTH:ADDR_WIDTH-1], addr_r2w[ADDR_WIDTH-2:0]});assign rempty = (raddr_gray_reg == addr_w2r);dual_port_RAM #(.DEPTH(DEPTH),.WIDTH(WIDTH)) dual_port_RAM_0 (.wclk(wclk),.wenc(winc && ~wfull),.waddr(waddr[ADDR_WIDTH-1:0]),.wdata(wdata),.rclk(rclk),.renc(rinc && ~rempty),.raddr(raddr[ADDR_WIDTH-1:0]),.rdata(rdata));
endmodule

这里遇到一个问题,反复查看代码,怎么也找不出来,哭了!!!
最后一查,实例化RAM的时候,有个线连错了,最后通过,奥里给!!!

==========================================================================================

参考文献

参考FIFO深度

声明

本人所有系列的文章,仅供学习,不可商用,如有侵权,请告知,立删!!!

本人主要是记录学习过程,以供自己回头复习,再就是提供给后人参考,不喜勿喷!!!

如果觉得对你有用的话,记得收藏+评论!!!

【基础知识】~ FIFO相关推荐

  1. 同步FIFO的设计,介绍一下FIFO的基础知识

    同步FIFO的设计,介绍一下FIFO的基础知识 \\\插播一条: 自己在今年整理一套单片机单片机相关论文800余篇 论文制作思维导图 原理图+源代码+开题报告+正文+外文资料 想要的同学私信找我. 本 ...

  2. FPGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇

    博文目录 写在前面 正文 同步FIFO回顾 $clog2()系统函数使用 综合属性控制资源使用 异步FIFO设计 FIFO用途回顾 异步FIFO原理回顾 异步FIFO设计 异步FIFO仿真 参考资料 ...

  3. 服务器架设笔记——Apache模块开发基础知识

    通过上节的例子,我们发现Apache插件开发的一个门槛便是学习它自成体系的一套API.虽然Apache的官网上有对这些API的详细介绍,但是空拿着一些零散的说明书,是很难快速建立起一套可以运行的系统. ...

  4. 计算机二级公共基础知识证书,计算机二级公共基础知识

    计算机二级公共基础知识 下面是小编收集整理的计算机二级公共基础知识,希望对您有所帮助!如果你觉得不错的话,欢迎分享! 第一章 数据结构与算法 1.1算法 算法:是指解题方案的准x而完整的描述. 算法不 ...

  5. 计算机二级c语基础知识,计算机二级C语基础知识整理.doc

    计算机二级C语基础知识整理 1.1 算法 算法:是一组有穷指令集,是解题方案的准确而完整的描述.通俗地说,算法就是计算机解题的过程.算法不等于程序,也不等于计算方法,程序的编制不可能优于算法的设计. ...

  6. 汇编学习(1)——基础知识

    汇编学习(1)--基础知识 ---谨以此系列文章记录我的汇编学习.  关于汇编 说起汇编语言,那自然不得不想到机器语言,在汇编语言尚未诞生之际,程序猿们只能非常苦逼的敲着0和1,还要记住一大堆复杂难记 ...

  7. 全国计算机等级考试——二级公共基础知识辅导讲义 卿勇军主讲

    全国计算机等级考试--二级公共基础知识辅导讲义 卿勇军主讲 第一章数据结构与算法 1.1算法 1.算法是指解题方案的准确而完整的描述.换句话说,算法是对特定问题求解步骤的一种描述. *:算法不等于程序 ...

  8. pwn学习总结(四)—— 堆基础知识(持续更新)

    pwn学习总结(四)-- 堆基础知识(持续更新) 前言 chunk 使用中(分配后) 空闲中(释放后) 堆块大小 空间复用 bins fastbin unsorted bin small bin 前言 ...

  9. 二级c语言基础知识pdf下载,全国计算机等级考试二级C语言公共基础知识.pdf

    您所在位置:网站首页 > 海量文档 &nbsp>&nbsp资格/认证考试&nbsp>&nbsp计算机等级考试 全国计算机等级考试二级C语言公共基础知识 ...

最新文章

  1. rabbitmq可靠发送的自动重试机制 --转
  2. 基于分类任务的信号(EEG)处理
  3. PATB1014福尔摩斯的约会
  4. me)不支持html,属于me的vue练习(参考菜鸟教程).html
  5. ASP.NET性能优化小结(ASP.NETC#)(转)
  6. linux查看mariadb安装卸载,MySQL——在Linux下安装和卸载MariaDB
  7. 跨浏览器javascript
  8. 中国铁塔行业市场企业投资及运营策略分析报告2022-2028年版
  9. 第九周博客作业西北师范大学|李晓婷
  10. win10此计算机未连接到网络,win10提示无法连接到此网络怎么解决
  11. filter-grok,dissect匹配数据
  12. 如何解决ssh断开/关闭之后python程序自动中止
  13. 软件需求分析复习要点
  14. win10笔记本识别不到蓝牙鼠标的解决办法
  15. linux的用户和组的管理
  16. 电信主机托管费用_电信托管服务器需要如何计费?
  17. React技巧之设置行内样式
  18. 数字电路:硬件描述语言AHDL纵览
  19. Java OOP 第三章 多态
  20. hive优化参数配置

热门文章

  1. C语言题解:谁是凶手!
  2. kafka windows环境搭建 SASL_PLAINTEXT/SCRAM
  3. kafka身份认证 maxwell_Kafka 使用SASL / SCRAM进行身份验证
  4. Kafka SASL SCRAM动态授权实现方案Java版
  5. 宝塔linux面板和centOS的区别,AMH面板和宝塔linux面板哪个好
  6. statsmodels遇到的坑!!!
  7. 一篇文章让你搞懂什么是Iaas、Paas、Saas
  8. 网页设计初了解-基础知识篇
  9. 未来的春晚,可能就不需要活的主持人了
  10. 【张亚飞】 Adobe Flash Player和Flash Player 检测工具包