原创声明:
本原创教程由芯驿电子科技(上海)有限公司(ALINX)创作,版权归本公司所有,如需转载,需授权并注明出处。
适用于板卡型号:
AXU2CGA/AXU2CGB/AXU3EG/AXU4EV-E/AXU4EV-P/AXU5EV-E/AXU5EV-P /AXU9EG/AXU15EG
实验Vivado工程为“fifo_test”。
FIFO是FPGA应用当中非常重要的模块,广泛用于数据的缓存,跨时钟域数据处理等。学好FIFO是FPGA的关键,灵活运用好FIFO是一个FPGA工程师必备的技能。本章主要介绍利用XILINX提供的FIFO IP进行读写测试。
1.实验原理
FIFO: First in, First out代表先进的数据先出,后进的数据后出。Xilinx在VIVADO里为我们已经提供了FIFO的IP核, 我们只需通过IP核例化一个FIFO,根据FIFO的读写时序来写入和读取FIFO中存储的数据。
其实FIFO是也是在RAM的基础上增加了许多功能,FIFO的典型结构如下,主要分为读和写两部分,另外就是状态信号,空和满信号,同时还有数据的数量状态信号,与RAM最大的不同是FIFO没有地址线,不能进行随机地址读取数据,什么是随机读取数据呢,也就是可以任意读取某个地址的数据。而FIFO则不同,不能进行随机读取,这样的好处是不用频繁地控制地址线。
虽然用户看不到地址线,但是在FIFO内部还是有地址的操作的,用来控制RAM的读写接口。其地址在读写操作时如下图所示,其中深度值也就是一个FIFO里最大可以存放多少个数据。初始状态下,读写地址都为0,在向FIFO中写入一个数据后,写地址加1,从FIFO中读出一个数据后,读地址加1。此时FIFO的状态即为空,因为写了一个数据,又读出了一个数据。
可以把FIFO想象成一个水池,写通道即为加水,读通道即为放水,假如不间断的加水和放水,如果加水速度比放水速度快,那么FIFO就会有满的时候,如果满了还继续加水就会溢出overflow,如果放水速度比加水速度快,那么FIFO就会有空的时候,所以把握好加水与放水的时机和速度,保证水池一直有水是一项很艰巨的任务。也就是判断空与满的状态,择机写数据或读数据。
根据读写时钟,可以分为同步FIFO(读写时钟相同)和异步FIFO(读写时钟不同)。同步FIFO控制比较简单,不再介绍,本节实验主要介绍异步FIFO的控制,其中读时钟为75MHz,写时钟为100MHz。实验中会通过VIVADO集成的在想逻辑分析仪ila,我们可以观察FIFO的读写时序和从FIFO中读取的数据。
2. 创建Vivado工程
2.1 添加FIFO IP核
在添加FIFO IP之前先新建一个fifo_test的工程, 然后在工程中添加FIFO IP,方法如下:
2.1.1点击下图中IP Catalog,在右侧弹出的界面中搜索fifo,找到FIFO Generator,双击打开。
2.1.2 弹出的配置页面中,这里可以选择读写时钟分开还是用同一个,一般来讲我们使用FIFO为了缓存数据,通常两边的时钟速度是不一样的。所以独立时钟是最常用的,我们这里选择“Independent Clocks Block RAM”,然后点击“Next”到下一个配置页面。
2.1.3 切换到Native Ports栏目下,选择数据位宽16;FIFO深选择512,实际使用大家根据需要自行设置就可以。Read Mode有两种方式,一个Standard FIFO,也就是平时常见的FIFO,数据滞后于读信号一个周期,还有一种方式为First Word Fall Through,数据预取模式,简称FWFT模式。也就是FIFO会预先取出一个数据,当读信号有效时,相应的数据也有效。我们首先做标准FIFO的实验。
2.1.4 切换到Data Counts栏目下,使能Write Data Count(已经FIFO写入多少数据)和Read Data Count(FIFO中有多少数据可以读),这样我们可以通过这两个值来看FIFO内部的数据多少。点击OK,Generate生成FIFO IP。
2.2 FIFO的端口定义与时序
信号名称 | 方向 | 说明 |
rst | in | 复位信号,高有效 |
wr_clk | in | 写时钟输入 |
rd_clk | in | 读时钟输入 |
din | in | 写数据 |
wr_en | in | 写使能,高有效 |
rd_en | in | 读使能,高有效 |
dout | out | 读数据 |
full | out | 满信号 |
empty | out | 空信号 |
rd_data_count | out | 可读数据数量 |
wr_data_count | out | 已写入的数据数量 |
FIFO的数据写入和读出都是按时钟的上升沿操作的,当wr_en信号为高时写入FIFO数据,当almost_full信号有效时,表示FIFO只能再写入一个数据,一旦写入一个数据了,full信号就会拉高,如果在full的情况下wr_en仍然有效,也就是继续向FIFO写数据,则FIFO的overflow就会有效,表示溢出。
标准FIFO写时序
当rd_en信号为高时读FIFO数据,数据在下个周期有效。valid为数据有效信号,almost_empty表示还有一个数据读,当再读一个数据,empty信号有效,如果继续读,则underflow有效,表示下溢,此时读出的数据无效。
标准FIFO读时序
而从FWFT模式读数据时序图可以看出,rd_en信号有效时,有效数据D0已经在数据线上准备好有效了,不会再延后一个周期。这就是与标准FIFO的不同之处。
FWFT FIFO读时序
关于FIFO的详细内容可参考pg057文档,可在xilinx官网下载。
3. FIFO测试程序编写
我们按照异步FIFO进行设计,用PLL产生出两路时钟,分别是100MHz和75MHz,用于写时钟和读时钟,也就是写时钟频率高于读时钟频率。
`timescale1ns/1ps
//////////////////////////////////////////////////////////////////////////////////
module fifo_test
(
input clk, //25MHz时钟
input rst_n //复位信号,低电平有效
);
reg [15:0] w_data ; //FIFO写数据
wire wr_en ; //FIFO写使能
wire rd_en ; //FIFO读使能
wire[15:0] r_data ; //FIFO读数据
wire full ; //FIFO满信号
wire empty ; //FIFO空信号
wire[8:0] rd_data_count ; //可读数据数量
wire[8:0] wr_data_count ; //已写入数据数量
wire clk_100M ; //PLL产生100MHz时钟
wire clk_75M ; //PLL产生100MHz时钟
wire locked ; //PLL lock信号,可作为系统复位信号,高电平表示lock住
wire fifo_rst_n ; //fifo复位信号, 低电平有效
wire wr_clk ; //写FIFO时钟
wire rd_clk ; //读FIFO时钟
reg [7:0] wcnt ; //写FIFO复位后等待计数器
reg [7:0] rcnt ; //读FIFO复位后等待计数器
wire clkbuf ;
BUFG BUFG_inst (
.O(clkbuf),// 1-bit output: Clock output.
.I(clk)// 1-bit input: Clock input.
);
//例化PLL,产生100MHz和75MHz时钟
clk_wiz_0 fifo_pll
(
// Clock out ports
.clk_out1(clk_100M), // output clk_out1
.clk_out2(clk_75M), // output clk_out2
// Status and control signals
.reset(~rst_n), // input reset
.locked(locked), // output locked
// Clock in ports
.clk_in1(clkbuf) // input clk_in1
);
assign fifo_rst_n = locked ; //将PLL的LOCK信号赋值给fifo的复位信号
assign wr_clk = clk_100M ; //将100MHz时钟赋值给写时钟
assign rd_clk = clk_75M ; //将75MHz时钟赋值给读时钟
/* 写FIFO状态机 */
localparam W_IDLE =1 ;
localparam W_FIFO =2 ;
reg[2:0] write_state;
reg[2:0] next_write_state;
always