您现在的位置是:首页 >技术教程 >Vivado 下 IP核之FIFO 实验网站首页技术教程

Vivado 下 IP核之FIFO 实验

OliverH-yishuihan 2024-10-12 00:01:03
简介Vivado 下 IP核之FIFO 实验

Vivado 下 IP核之FIFO 实验

       FIFO(First In First Out,即先进先出),是一种数据缓存器,用来实现数据先进先出的读写方式。在FPGA 或者 ASIC 中使用到的 FIFO 一般指的是对数据的存储具有先进先出特性的缓存器,常被用于数据的缓存、多比特数据跨时钟域的转换、读写数据带宽不同步等场合,或者高速异步数据的交互也即所谓的跨时钟域信号传递。 它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式, 使用起来简单方便,由此带来的缺点就是不能像 RAM ROM 那样可以由地址线决定读取或写入某个指定 的地址。本节将对 Vivado 软件生成的 FIFO IP 核进行读写测试,来向大家介绍 Xilinx FIFO IP 核的使用 方法。

1、FIFO IP 核简介

        FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器,其与普通存储器 RAM 区别在于 FIFO 没有外部读写地址线,使用起来非常简单,但 FIFO 只能顺序写入数据,并按顺序读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址,不过也正是因为这个特性,使得 FIFO 在使用时并不存在像 RAM 那样的读写冲突问题。
       根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作,常用于两边数据处理带宽不一致的临时缓冲异步 FIFO是指读写时钟不一致,读写时钟是互相独立的,一般用于数据信号跨时钟阈处理。

       从上图中可以看到,单时钟 FIFO 具有一个独立的时钟端口 clock,因此所有的输入输出信号都同步于 clock 信号。而在双时钟FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk,所有与读相关的信号都是同步于读时钟 rdclk。在双时钟 FIFO 的符号图中,位于图中上侧部分的以“data”和 “wr”开头的信号为与写相关的所有信号,位于中间部分的“q”和以“rd”开头的信号为与读相关的所有信号,位于底部的为异步清零信号。

对于 FIFO 我们还需要了解一些常见参数:

  • 1、FIFO 的宽度:FIFO 一次读写操作的数据位宽 N。
  • 2、FIFO 的深度:FIFO 可以存储多少个宽度为 N 位的数据。
  • 3、将空标志:almost_emptyFIFO 即将被读空。
  • 4、空标志:emptyFIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从FIFO 中读出数据而造成无效数据的读出。
  • 5、将满标志:almost_fullFIFO 即将被写满。
  • 6、满标志:fullFIFO 已满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出。
  • 7、写时钟:写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
  • 8、读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
  • 9、可配置满阈值:影响可配置满信号于何时有效,其可配置范围一般为 3~写深度-3
  • 10、可配置满信号:prog_full,表示 FIFO 中存储的数据量达到可配置满阈值中配置的数值。
  • 11、可配置空阈值:影响可配置空信号于何时有效,其可配置范围一般为 2~读深度-3
  • 12、可配置空信号:prog_empty,表示 FIFO 中剩余的数据量已经减少到可配置空阈值中配置的数值。

这里还有两点需要大家注意:

1、“almost_empty” 和 “almost_full” 这两个信号分别被看作 “empty” 和 “full” 的警告信号,他们相对于真正的空(empty)和满(full)都会提前一个时钟周期拉高。

2、FIFO 中,先写入的数据被置于高位,后写入的数据被置于低位,由于其先入先出的特性,所以读出的数据也是高位在前,低位在后。这一点在读写数据位宽不对等时尤为重要,例如我们写数据为 8,读数据位宽为 2,当写入的数据为 11000111 时,读出的数据依次为 11000111,如下图所示:

读位宽大于写位宽时,原理是相同的,其示意图如下:

 在逻辑设计的时候,尤其是在 FPGA 设计中,FIFO 都有哪些实现方法:

  • 1、根据需求自己设计并编写 FIFO 的控制逻辑,一般对 FIFO 的功能有特殊需求时,可以使用此种方法。
  • 2、使用第三方提供的开源 IP 核,一般是以源码的形式提供的,能够快速应用到用户系统中,当对 FIFO 有特殊需求时,可以在此源码的基础上自行修改,以适应自己的设计需求。
  • 3、调用官方开发软件中免费提供的 FIFO IP ,我们可以通过软件中的图形化界面对 FIFO 的各项参数和结构进行配置,生成的 FIFO IP 核针对旗下的不同系列器件,还可以实现结构上的优化,且其可配置的功能足以实现大部分的设计需求,因此在实际应用中推荐大家使用 IP 核来实现 FIFO 设计。

        Xilinx 的 FIFO IP 核可以被配置为同步 FIFO 或异步 FIFO,其信号框图如下图所示。从图中可以了解到,当被配置为同步 FIFO 时,只使用 wr_clk,所有的输入输出信号都同步于 wr_clk 信号。而当被配置为异步 FIFO时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关的信号都是同步于读时钟 rd_clk

       首先说明下,上图中黑色箭头表示此信号为必要信号;蓝色箭头表示此信号为可选信号;灰色箭头表示此信号为可选的边带信号。

       从图中我们可以了解到,当被配置为同步 FIFO 时,只使用 wr_clk,所有的输入输出信号都同步于 wr_clk 信号。而当被配置为异步 FIFO 时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关的信号都是同步于读时钟 rd_clk

       这里我们对框图中的常用信号端口做一下讲解,其他很少用到的信号如果大家感兴趣的话也可以在课 后打开 IP 核的数据手册进行学习,各常用端口的功能描述如下:

   需要注意的是在使用 Vivado FIFO Generato IP 核时,输入的读写时钟频率不能超过 500MHz。

2、实验任务

       本节的实验任务是使用 Vivado 生成一个异步 FIFO,并实现以下功能:当 FIFO 为空时,向 FIFO 中写入数据,直至将 FIFO 写满后停止写操作;当 FIFO 为满时,从 FIFO 中读出数据,直到 FIFO 被读空后停止读操作,以此向大家详细介绍一下 FIFO IP 核的使用方法。

3、硬件设计

本节实验只用到了输入的时钟信号和按键复位信号,没有用到其它硬件外设。

本实验中,各端口信号的管脚分配如下表所示:

3.1、约束文件 ip_fifo.xdc 

对应的 XDC 约束语句如下所示:

set_property -dict {PACKAGE_PIN R4 IOSTANDARD LVCMOS15} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U7 IOSTANDARD LVCMOS15} [get_ports sys_rst_n]

4、程序设计

       根据实验任务要求和模块化设计的思想,我们需要如下 4 个模块:fifo IP 核、写 fifo 模块、读 fifo 模块以及顶层例化模块实现前三个模块的信号交互。由于 FIFO 多用于跨时钟域信号的处理,所以本实验我们使用异步 FIFO 来向大家详细介绍双时钟 FIFO IP 核的创建和使用。为了方便大家理解,这里我们将读/写时钟都用系统时钟来驱动。系统的功能框图如下图所示:

4.1、FIFO IP 核配置

首先我们创建一个名为 “ip_fifo” 的空白工程,然后点击 Vivado 软件左侧 “Flow Navigator” 栏中的  “IP Catalog”,在弹出的 “IP Catalog” 窗口的搜索栏中输入“fifo”关键字后,我们找到 “FIFO Generator” 如下图所示:

 双击 “FIFO Generator” 后弹出 IP 核的配置界面,接着我们就可以对 BMG IP 核进行配置了, “Basic” 选项卡配置界面如下图所示。

       最上面的“Component Name”一栏可以设置该 IP 元件的名称,这里我们保持默认命名,当然也可以命名为其它方便自己一眼看出其功能的名称。

4.1.1、“Basic” 选项卡下各参数配置

       接着目光回到 “Basic” 选项卡上,该选项卡下各参数(我们重点关注(1)和(2)中的内容)含义如下:

(1)、lnterface Type(接口模式)”:有三种接口模式可选,分别为 Native(常规)接口、AXI Memory Mapped(内存映射)接口和 AXI Stream(流)接口。其中 AXI Memory Mapped 接口一般用于与PS 端进行数据交互;AXI Stream 接口一般应用于高速信号处理场景中,例如光口通信;通常情况下我们一般采用 Native 模式,所以本次实验我们选择 Native 模式。

(2)、Fifo ImplementationFIFO 实现)”:用于设置用什么资源来实现什么样的 FIFO可配置用于实现 FIFO 的资源有四种,分别为 Block RAM(块 RAM)Distributed RAM(分布式 RAM)Shift Register(移位寄存器)Builtin FIFO(内置 FIFO)其中移位寄存器仅可用于实现同步 FIFO可配置的 FIFO 类型有两类,分别为 Common Clocks(公共时钟,即同步 FIFO) Independent Clocks(独立时钟,即异步 FIFO)资源与种类两两组合之下便有了七种不同的选择。需要说明的是 BRAM 和分布式RAM 是创建 FIFO 时最常选用的存储资源类型,一般来说,FIFO 容量超过 1024 个字节就需要考虑使用 BRAM 了,没超过 1024 字节选择分布式 RAM当然,如果芯片 BRAM 资源很富余的话,全部采用BRAM 也是可以的,后两种基本用不到。本次实验我们选择“Independent Clocks Block RAM”,即使用BRAM 资源来实现一个异步 FIFO

(3)、“synchronization Stages(同步阶段)”:定义跨交叉时钟域逻辑的同步器级数,即设置读写状态信号的输出延迟。保持默认设置 2 即可。

(4)、“FIFO Implementation OptionsFIFO 实现方案)”:此处的表格将实现 FIFO 的七种方案的特征都一一列出了,当我们不清楚自己的 FIFO 设计该使用哪种方案实现时,可以看下此处的表格。

4.1.2、“Native Ports”选项卡下各参数配置

接下来我们对 “Native Ports” 选项卡进行配置,如下图所示:

 “Native Ports”选项卡下各参数(我们重点关注(1)、(2)和(4)中的内容)含义如下: 、(1)、“Read Mode(读取模式)”有 “Standard FIFO(标准 FIFO)” 和 “First Word Fall Through (首字直通,简称 FWFT 模式,即预读模式)” 两种可选需要注意的是标准模式的数据输出会比读使能延迟一拍,预读模式的数据与读使能同时输出,这里我们选择默认的标准模式。

(2)、“Data Port Parameters(数据端口参数)”用于设置 FIFO 的读写数据位宽和读写深度,其中写数据位宽可在 1~1024 内任意设置;写深度的可支持参数配置我们可以通过下拉来查看,这里我们设置为 256需要注意的是,虽然我们设置的深度为 256,但实际深度只有 255;读数据位宽支持 18~81 之间的偶数比,这里我们保持默认的 11 比例,即读数据位宽为 8;读深度是我们设置完读写数据位宽和写深度后自动确定的,无需我们进行设置。

        这里有一点需要我们注意,在实际应用中,FIFO 的读写数据位宽和深度在满足设计需求的情况下要尽量设置的小一点,因为 FIFO 使用的是片上 BRAM 资源,而 FPGA 内部的片上 BRAM 资源是有限的,所以大家不要将位宽和深度设置成远远超过实际需求的值,造成 BRAM 资源的过度浪费。

(3)、“ECCOutput Register and Power Gating OptionsECC、输出寄存器和电源选通选项)”, 其下各配置如下:

  • 第一行有四个信号,当我们勾选 ECC(纠错码)后,可以选择 Hard ECC(硬 ECC)或 Soft ECC (软 ECC),并可以勾选 Single Bit Error Injection(注入单 bit 错误)和 Double Bit Error Injection(注入双 bit 错误),这里我们保持默认的不启用 ECC 即可。
  • 第二行有两个信号,“ECC Pipeline RegECC 管道寄存器)”和“Dynamic Power Gating(动态功率选通)”都是仅限 UltraScale 系列芯片使用 Builtin FIFO 资源实现 FIFO 时才可进行配置。
  • 第三行用于配置输出寄存器,勾选“Output Registers(输出寄存器)”后,可以选择添加Embedded Registers(嵌入式寄存器)”和“Fabric Registers(结构寄存器)”。其作用是可以改善 FIFO 的时序,为此付出的代价是每添加一个输出寄存器,输出就会延后一拍。这里我们保持默认,不做勾选。

(4)、“Initialization(初始化)”,也就是设置复位相关的参数,详情如下:

  • Reset Pin(复位脚):选择是否引入复位信号,高电平有效。实际设计中,在 FPGA 配置完成后,读写操作开始前,FIFO 必须要进行复位操作,需要注意的是,在进行复位操作时,读写时钟必须是有效的。这里我们保持默认的勾选状态,即启用复位信号。
  • Enable Reset Synchronization(启用复位同步):用于设置异步 FIFO 时是否启用同步复位,需要注意的是官方文档中建议复位信号至少要保持三个时钟周期(以慢时钟为准)的有效,且在复位后至少要经过三十个时钟周期(以慢时钟为准)后,才能对 FIFO 进行写数据操作。这里我们保持默认的勾选状态,即启用同步复位。
  • Enable Safety Circuit(启用安全电路):用于设置 FIFO 是否向外输出 wr_rst_busy(写复位忙信号)rd_rst_busy(读复位忙信号),这两个信号皆是高电平表示处于复位状态,低电平表示空闲,我们可以通过这两个信号来判断 FIFO 是否复位完成,防止我们在复位完成前对 FIFO 进行读写操作而导致读写错误,所以我们保持默认的勾选状态,即启用安全电路。需要注意的是官方文档中建议当启用安全电路时,复位信号至少要保持八个时钟周期(以慢时钟为准)的有效,且在复位后至少要经过六十个时钟周期(以慢时钟为准)后,才能对 FIFO 进行写数据操作。
  • Reset Type(复位类型):当选择使用非 Builtin FIFO 资源来实现同步 FIFO 时,可以选择复位类型为Asynchronous Reset(异步复位)或 Synchronous Reset(同步复位),使用异步 FIFO 模式时不需要考虑该配置。
  • Full Flags Reset Value(满信号的重置值):用于设置复位时三个满信号(满信号,将满信号,设置满信号)的状态是高电平还是低电平。这里我们保持默认设置 1 即可。
  • Dout Reset Value(输出的数据重置值):设置复位期间 FIFO 输出总线上的数据值,若未启用,则复位期间输出总线上的值时未知的。切记设置时此值的位宽不可超过读数据的位宽,这里我们保持默认的 0 即可。

5)、“Read Latency(读延迟)”,可以在此处看出经过以上设置后,输出被延迟了几拍。因为我们选择的读取模式是标准模式,且没有启用任何输出寄存器,所以输出延迟了一拍。

接下来我们对“Status Flags”选项卡进行配置,如下图所示:

(5)、“Read Latency(读延迟)”,可以在此处看出经过以上设置后,输出被延迟了几拍。因为我们选择的读取模式是标准模式,且没有启用任何输出寄存器,所以输出延迟了一拍。

4.1.3、“Status Flags” 选项卡下各参数配置

接下来我们对 “Status Flags” 选项卡进行配置,如下图所示:

 “Status Flags”选项卡下各参数(我们重点关注(1)中的内容)含义如下:

(1)、“Optional Flags(可选标准)”,可勾选是否输出 Almost Full Flag(将满信号)和 Almost Empty Flag(将空信号),两个信号皆为高有效。其中将满信号是在 FIFO 内部写数据个数>=FIFO 深度-1 之后的第一个写时钟上升沿置高,直到 FIFO 内部写数据个数<FIFO 深度-1 后的第一个写时钟上升沿拉低;将空信号是在 FIFO 内部读数据个数<=1 之后的第一个读时钟上升沿置高,直到 FIFO 内部读数据个数>1 后的第一个读时钟上升沿拉低。这里我们选择输出这两个标志信号。

(2)、“Handshaking Options(握手选项)”,可用于配置读写端口的握手机制,这里我们简单的介绍下各个配置含义:

  • Write Port Handshaking(写端口握手):可使能 Write Ackongledge(写应答)信号和 Overflow(满溢出)信号。其中写应答信号是成功写入数据的标志,一次成功写入对应一个写时钟周期的写应答,有效电平状态可配;满溢出信号是写入数据无效(溢出)的标志,一次溢出对应一个写时钟周期,有效电平状态可配。这里我们不做任何勾选。
  • Read Port Handshaking(读端口握手):可使能 Valid Flag(读有效标志)信号和 Underflow Flag(空溢出)信号。这两个信号的有效电平状态同样可配,信号含义和写端口握手中的两个信号类似,这里就不多赘述了,这里我们不做任何勾选。

(3)、“Programmable Flags(可编程标志)”有六个可配置参数,这里的参数配置将影响到可编程空满信号(prog_empty 和 prog_full 信号)在何时使能,各参数含义如下:

  • Programmable Full Type(可编程满类型):有五种类型可选,如下表所示:

  •  Full Therhold Assert Value(满阈值有效值):当类型配置为单可编程满阈值常量,其可配置范围为3~写深度-3;当类型配置为双可编程满阈值常量,其可配置范围为 4~写深度-3
  • Full Therhold Negate Value(满阈值无效值):当类型配置为双可编程满阈值常量才可进行配置,可配置范围为 3~满阈值有效值-1
  • Programmable Empty Type(可编程空类型):有五种类型可选,含义与可编程满类型相同,只是满阈值变成了空阈值,这里就不再赘述了。
  • Empty Threshold Assert Value(空阈值有效值):当类型配置为单可编程空阈值常量,其可配置范围为 2~读实际深度-3 ;当类型配置为双可编程满阈值常量,其可配置范围为 2~读实际深度-4 
  • Empty Threshold Negate Value(空阈值无效值):当类型配置为双可编程满阈值常量才可进行配置,可配置范围为空阈值有效值 +1~读实际深度-3 
  • 以可编程满阈值为例,假如在单可编程满阈值类型下,写深度为 32,而我们将满阈值有效值设置为28,那么 prog_full 信号将会在 FIFO 中的 数据>=28 后的第一个写时钟上升沿置高,直到 FIFO 内部写数据 个数<28 后的第一个写时钟上升沿拉低。

 4.1.4、“Data Counts(数据计数)”选项卡下各参数配置

接下来我们对 “Data Counts” 选项卡进行配置,如下图所示:

“Data Counts(数据计数)”选项卡下各参数(我们重点关注(1)中的内容)含义如下:

(1)、“More Accurate Data Counts(更精确的数据计数)”,该功能只有选择使用块 RAM 或者分布式 RAM 来实现 FIFO 时,将读取模式设置为预读模式才可进行配置和使用。

(2)、“Data Counts(数据计数)”,当使用非 Builtin FIFO 资源来实现同步 FIFO 时可进行设置,可用于跟踪 FIFO 中的字数(数据个数),我们可以指定其计数总线的宽度,最小为 1,最大宽度为 log2(FIFO 深度),如果指定的宽度小于最大宽度,则低位会被截断。例如数据深度为 16,则 Data Counts 的最大位宽为 4,如果我们设置为 3,那么 FIFO 中的数据量=2 时,Data Counts 才会加 1

(3)、“Write Data Count(写数据计数)”,与写时钟同步。当使用非 Builtin FIFO 资源来实现异步FIFO 时可进行设置,可用于跟踪写 FIFO 中的字数(数据个数),我们可以指定其计数总线的宽度,最小 为 1,最大宽度为 log2(写 FIFO 深度),如果指定的宽度小于最大宽度,则低位会被截断。

(4)、“Read Data Count(读数据计数)”,与读时钟同步。当使用非 Builtin FIFO 资源来实现异步FIFO 时可进行设置,可用于跟踪读 FIFO 中的字数(数据个数),我们可以指定其计数总线的宽度,最小为 1,最大宽度为 log2(读 FIFO 深度),如果指定的宽度小于最大宽度,则低位会被截断。

 4.1.5、“Summary” 选项卡下各参数配置

       最后一个是 “Summary” 选项卡,该界面显示了我们配置的存储器的类型,消耗的 BRAM 资源等信息, 我们直接点击“OK”按钮完成 FIFO Generator IP 核的配置,如下图所示:

       可以看到“Summary”选项卡中有一条警告时建议我们启用安全电路,我们在之前的配置中已经启用了,所以不用理会。之后的几步操作是生成 IP 核时的必要操作,在之前的几篇 IP 核相关的文档中已经讲解过数次了,想必大家已经很熟悉了,这里就不再赘述了。

4.2、时序图讲解

       无论是同步 FIFO 还是异步 FIFO,我们都需要注意写满和读空的情况,若写满时继续写入数据,则会导致有效数据的丢失;若读空时继续读数据,则会导致读出的是无效数据。下面我们通过一个典型的FIFO 读写时序图来理解一下写满信号和读空信号。

       可以看出,初始状态时 empty(读空)信号是拉高的,即此时 FIFO 为空,如果我们在此时发起读操作,那么我们读出的就会是未知的无效数据。当拉高 wr_en(写使能)信号后,开始向 FIFO 发出写操作,当 FIFO 中有了数据,empty 信号便会拉低,说明此时 FIFO 非空。接着同时发起了读操作和写操作,此时因为是同步 FIFO,读写速率相同,所以状态标志是没有发生变化的。

       当只写不读时,因为 FIFO 中存在两个或两个以上的数据,所以 almost_empty(将空)信号也会被拉低,表示 FIFO 不处于将空状态。经过一段时间只写操作后,FIFO 就会逐渐趋于写满状态,当 FIFO 只能再接受一次写入(不带读取)时,almost_full(将满)信号就会拉高。最后,在没有进行读操作的情况下,单独进行了一次写操作,full(写满)信号就会被拉高,说明此时的 FIFO 已经写满了,在发出读请求之前将无法再写入任何数据,如果此时我们还在发起写操作,就会导致我们的有效数据因为无法写入FIFO 而丢失。所以大家在使用 FIFO 时一定要注意 FIFO 的空信号和满信号,防止发生读无效和写丢失的现象。

除此之外,大家还需要注意下 FIFO 的复位信号,其时序图如下所示:

 由上图可总结出以下几点注意事项:

1、建议在安全电路下的异步 FIFO 的复位信号(RST)至少要保持八个时钟周期(以慢时钟为准)的有效。

2、在 RST 上升沿时期,经过 7 个 WR_CLK(写时钟)周期后 WR_RST_BUSY(写复位忙)信号拉高, FULL(满)信号拉高,此时的 WR_EN(写使能)信号应该拉低。

3、在 RST 上升沿时期,经过 7 个 RD_CLK(读时钟)周期后 RD_RST_BUSY(读复位忙)信号拉高,EMPTY(空)信号拉高,此时的 RD_EN(读使能)信号应该拉低,而且此时的数据输出端口均为无效。

4、图中建议我们至少在复位结束后,经过 60 个时钟周期(以慢时钟为准)后再对 FIFO 进行写操作, 在实际应用中我们以读写复位的忙信号来判断是否对 FIFO 进行写操作也是可以的。

       所以大家在对 FIFO 进行复位时,一定要注意避免在复位结束时就立马对 FIFO 进行写操作,因为此时的 FIFO 仍然处于复位状态。

       接着我们再来看下写操作的时序图,一个典型的写操作时序如下图所示:

       当 wr_en(写使能)信号使能时,会在 wr_clk(写时钟)的下一个上升沿上发生写操作,由于 FIFO 未满,因此 wr_ack(写应答)信号处于有效状态,表示写入操作成功。当只能再执行一次写操作时,almost_full(将满)信号会被拉高,此时若再进行一次写操作,full(满)信号就会被拉高,表示 FIFO 已被写满,在有数据被读出前,无法再写入数据了。如果在 full 信号拉高后执意要进行写操作,wr_ack 就会被拉低,表示此次数据写入失败,同时 overflow(满溢出)信号就会被拉高,表示 FIFO 存在溢出现象。

       最后我们再来看下读操作的时序图,一个典型的读操作时序如下图所示:

       只要 FIFO 中存有数据,empty(空)信号就会一直为低电平,表明 FIFO 中有数据可以进行读取。当 rd_en(读使能)信号使能时,会在 rd_clk(读时钟)的下一个上升沿上发生读操作,FIFO 会在 dout(数据输出线)上输出数据,并拉高 valid(读有效)信号,表示读操作成功。当 FIFO 中还剩最后一个数据时,almost_empty(将空)信号会被拉高,此时若再进行一次读操作,empty(空)信号就会被拉高,表示 FIFO 已被读空,在 FIFO 中有存储数据前,读请求将被忽视。如果在 empty 信号拉高后执意要进行读操作,valid 就会被拉低,表示此次数据读出失败,同时 underflow(空溢出)信号就会被拉高,表示 FIFO 中已经没有可被读取的数据了。

5、顶层模块设计

        本次实验的目的是为了将 Xilinx FIFO Generato IP 核配置成一个异步 FIFO 并对其进行读写操作,因此可以给模块命名为 ip_fifo;因为我们做的是异步 FIFO,所以我们需要一个 PLL IP 核来输出 50MHz 的写时钟和 100MHz 的读时钟当然输出其它频率的时钟也是可以的;然后我们还需要一个写模块(fifo_wr和一个读模块(fifo_rd,写模块负责给出写请求信号和写数据,读模块负责给出读请求信号,并接收从FIFO 中读出的数据;系统时钟和系统复位是一个完整的工程中必不可少的输入端口信号,这里就不再多讲了。经过上述分析我们可以画出一个大致的模块框图,如下图所示:

模块端口与功能描述如下表所示:

5.1、顶层模块 ip_fifo.v 代码

        因为本次实验是使用 FIFO Generator IP 核来生成一个异步 FIFO,所以我们需要使用到 PLL IP 核来输出两路不同频率的时钟,除此之外我们还需要一个读模块(fifo_rd和一个写模块(fifo_wr来进行异步的读写操作,所以我们需要创建一个顶层模块来例化两个 IP 核与读/写模块,这里我们将顶层模块命名为 ip_fifo,代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/06/12 15:52:44
// Design Name: 
// Module Name: ip_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//
//实验任务:
//本节的实验任务是使用 Vivado 生成一个异步 FIFO,并实现以下功能:当 FIFO 为空时,
//向 FIFO 中写入数据,直至将 FIFO 写满后停止写操作;
//当 FIFO 为满时,从 FIFO 中读出数据,直到 FIFO 被读空后停止读操作,
//以此向大家详细介绍一下 FIFO IP 核的使用方法。


module ip_fifo(
    input       sys_clk,    //系统时钟信号
    input       sys_rst_n   //系统复位信号
    );

//wire define 
wire           clk_50m;         //50M时钟
wire           clk_100m;        //100M时钟
wire           locked;          //时钟锁定信号
wire           rst_n;           //复位,低电平有效
wire           wr_rst_busy;     //写复位忙信号
wire           rd_rst_busy;     //读复位忙信号
wire           fifo_wr_en;      //FIFO写使能信号
wire           fifo_rd_en;      //FIFO读使能信号
wire  [7:0]    fifo_din;        //写入到FIFO的数据
wire  [7:0]    fifo_dout;       //从FIFO读出的数据
wire           almost_full;     //FIFO将满信号
wire           almost_empty;    //FIFO将空信号
wire           fifo_full;       //FIFO满信号
wire           fifo_empty;      //FIFO空信号
wire  [7:0]    fifo_wr_data_count;      // FIFO 写时钟域的数据计数
wire  [7:0]    fifo_rd_data_count;      // FIFO 读时钟域的数据计数

//*****************************************************
//** main code
//*****************************************************

//通过系统复位信号和时钟锁定信号来产生一个新的复位信号
assign rst = sys_rst_n & locked;

//例化 PLL IP 核
clk_wiz_0 clk_wiz_0(
    //clock out ports
    .clk_out1         (clk_50m    ),   //output clk_out1
    .clk_out2         (clk_100m   ),   //output clk_out2
    // Status and control signal
    .locked           (locked     ),    //output locked
    //clock in port
    .clk_in1          (sys_clk    )     //input clk_in1
);

//例化 FIFO IP 核
fifo_generator_0  fifo_generator_0(
    .rst                      (~sys_rst_n           ),  //input wire rst
    .wr_clk                   (clk_50m              ),  //input wire wr_clk
    .rd_clk                   (clk_100m             ),  //input wire rd_clk
    .wr_en                    (fifo_wr_en           ),  //input wire wr_en
    .rd_en                    (fifo_rd_en           ),  //input wire rd_en
    .din                      (fifo_din             ),  //input wire [7:0] din
    .dout                     (fifo_dout            ),  //output wire [7:0] dout
    .almost_full              (almost_full          ),  //output wire almost_full
    .almost_empty             (almost_empty         ),  //output wire almost_empty
    .full                     (fifo_full            ),  //output wire full
    .empty                    (fifo_empty           ),  //output wire empty
    .wr_data_count            (fifo_wr_data_cout    ),  //output wire [7:0] wr_data_cout
    .rd_data_count            (fifo_rd_data_cout    ),  //output wire [7:0] rd_data_cout
    .wr_rst_busy              (wr_rst_busy          ),  //output wire wr_rst_busy
    .rd_rst_busy              (rd_rst_busy          )   //output wire rd_rst_busy 
);

//例化写 FIFO 模块
fifo_wr  u_fifo_wr(
    .clk           (clk_50m       ),  //写时钟
    .rst_n         (rst_n         ),  //复位信号
    .wr_rst_busy   (wr_rst_busy   ),  //写复位忙信号
    .fifo_wr_en    (fifo_wr_en    ),  //fifo 写请求
    .fifo_wr_data  (fifo_wr_data  ),  //写入FIFO的数据
    .almost_empty  (almost_empty  ),  //fifo 空信号
    .almost_full   (almost_full   )   //fifo 满信号
);

endmodule

         可以看出 ip_fifo 顶层模块只是例化了 FIFO IP 核(fifo_generator_0)、PLL IP 核(clk_wiz_0)、读模 块(fifo_rd)和写模块(fifo_wr),其中写模块负责产生 FIFO IP 核写操作所需的所有数据、写请求等信号;读模块负责产生 FIFO IP 读操作所需读请求信号,并将读出的数据也连接至读模块。

        因为读写模块的时钟皆来自 PLL IP 核,而 PLL IP 核需要一定的时间才能输出稳定的时钟,所以在代码中(第56 行)我们通过系统复位和时钟锁定来产生一个信号复位信号,使读/写模块在时钟稳定后才进入工作状态。

5.2、FIFO 写模块设计

       首先介绍下 FIFO 写模块的设计,在 FIFO 写模块中,我们的输入信号主要有系统时钟信号、系统复位信号;因为 FIFO 的写操作需要在 FIFO 完成复位后进行,所以我们还需要输入 wr_rst_busy(写复位忙)来作为判断 FIFO 是否结束了复位状态;实验任务中我们提到了 FIFO 为空时进行写操作,因此还需要引入一个空相关的信号,这里我们引入的是 almost_empty(将空)信号,当然引入 empty(空)信号也是可以的;实验任务中我们还提到了写满了要停止写操作,所以这里我们引入了 almost_full(将满)信号,因为将满信号表示 FIFO 还能再进行最后一次写操作,使用这个信号的话我们正好可以在写入最后一次数据后关闭写使能,当然引入 full(满)信号也是可以,区别只是在于这么做会在写使能关断前执行一次无效的写操作。

        输出信号有控制写 FIFO 所需的 fifo_wr_en(写端口使能)和 fifo_wr_data(写数据)这两个信号。由上述分析绘制出如下图所示的模块框图:

模块端口与功能描述如下表所示:

5.2.1、绘制波形图

       在编写代码前,我们先大致梳理一下模块的端口时序,并绘制出如下波形图:

 

       由上图可知,当系统复位结束后,FIFO 还处于复位状态,我们需要等待 FIFO 写复位结束(即 wr_rst_busy 信号拉低)后,再对 FIFO 进行写操作。这里说明一下,因为 almost_empty(将空)信号是FIFO 读时钟域的输出信号,对于写操作来说属于异步信号,所以这里我们引入了 almost_empty_syn 信号,通过打拍的方式将 almost_empty 信号同步到写时钟域下。需要注意的是,当 FIFO 中有两个或两个以上的数据时,将空信号就会被拉低,但是因为是异步 FIFO,所以状态信号的同步需要一定的时间,因此将空信号并不是在写入第二个数据后拉低的。

        写模块的运行是通过一个小的状态机(state)实现的,当 FIFO 复位结束后,在 state=0(空闲状态)时,若检测到 almost_empty_syn 信号为高,则跳转到延迟状态(state=1);延迟状态用于延迟 10 拍(dly_cnt 计数实现),其目的是等待 FIFO 的内部状态信号更新完成,在第 10 拍时,打开fifo_wr_en(写使能)信号,清空 dly_cnt 计数器并跳转到写操作状态(state=2);写操作状态时,向 FIFO 中写入从 0 开始的累加数据,直至检测到 almost_full(将满)信号时,关闭写使能,将写数据清零并跳转回空闲状态(state=0),等待下一次的写操作。

5.2.2、fifo_wr 模块代码

fifo_wr 模块用于产生 FIFO 写操作所需的信号,其代码如下所示:

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。