一、前置知识
FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递。FIFO 采用顺序读取、顺序写入的方式,没有外部读写地址。
FIFO 分类
- 同步FIFO:SCFIFO 读时钟和写时钟是同一个时钟
- 异步FIFO:DCFIFO 读写时钟不一致,读写时钟互相独立
- 异步混合位宽FIFO:DCFIFO_MIXED_WIDTHS:读写时钟不一致并且读写数据位宽不一致。


| 引脚 | 必要 | 说明 |
|---|---|---|
| data | 是 | 向FIFO写入的数据 |
| q | 是 | 从FIFO读出的数据 |
| wrreq/edreq | 是 | 写请求和读请求,拉高时可进行读写操作。 |
| clock | 是 | 读写同步时钟 |
| sclr/aclr | 否 | 同步复位和异步复位 |
| full/almost_full | 否 | FIFO满信号和FIFO超过设定值信号,高电平有效 |
| empty/almost_empty | 否 | FIFO空信号和FIFO低于设定值信号,高电平有效 |
| usedw | 否 | FIFO中保持的数据量 |
| eccstatus | 否 | 纠错状态端口 |

| 引脚 | 必要 | 说明 |
|---|---|---|
| data | 是 | 向FIFO写入的数据 |
| q | 是 | 从FIFO读出的数据 |
| wrclk/rdclk | 是 | 写时钟和读时钟 |
| wrreq/edreq | 是 | 写请求和读请求,拉高时可进行读写操作。 |
| clock | 是 | 读写同步时钟 |
| aclr | 否 | 异步复位 |
| wrfull/rdfull | 否 | 写时钟侧的满信号和读时钟侧的满信号 |
| wrempty/rdempty | 否 | 写时钟侧的空信号和读时钟侧的空信号 |
| wrusedw/rdusedw | 否 | 写时钟侧检查FIFO中存储的数据量 和 读时钟侧检查FIFO中存储的数据量 |
| eccstatus | 否 | 纠错状态端口 |
二、原理分析


顶层模块例化四个模块:PLL时钟模块、FIFO模块、读FIFO模块、写FIFO模块
使用异步FIFO,分别接收PLL模块产生的读写时钟。
写FIFO模块向FIFO模块写入数据,接收FIFO模块的空满信号,空时写入,满时停止写入。
读FIFO模块从FIFO模块读取数据,接收FIFO模块的空满信号,满时读取,空时停止读取。
程序编写完成后生成的RTL视图如下:
10_ip_fifo.pdf
三、代码编写
3.1 创建 FIFO IP 核
3.1.1 创建工程
3.1.2 创建 IP 核

IP核名称:async_fifo.v
IP核路径:X:/FPGA/Projects/10_ip_fifo/par/ipcore/async_fifo/async_fifo.v
3.1.3 配置 IP 核
- 通用配置
- 数据输入输出位宽:8bit
- FIFO数据深度:256words
- 使用异步读写时钟,为每一个时钟配置满空信号

- 性能优化
选择不同的优化模式会消耗不同的资源,从而也会得到不同的性能。
- 端口配置
- 读侧配置满、空、数据量端口
- 写侧配置满、空、数据量端口
- 额外的MSB位用来防止数据满后usedw溢出为0,导致不好判断空或满,增加一位可以防止溢出。这里不需要,因为可以直接用满空信号来做判断。
- 添加异步复位信号

- 模式选择
- 选择正常模式,输出数据会有一个时钟的延迟
- 前显模式不会有一个时钟的数据延迟
- 块类型自动选择即可

- 保护电路
FIFO IP核自带数据保护电路,即数据满或空之后不再继续写或读数据。可以在此页面禁用保护电路,这里不需要禁用。
- EDA界面
提示仿真需要的仿真库:altera_mf.v
- 产生的文件、FIFO样式、资源占用
3.2 创建 PLL IP 核
PLL IP核与上一节RAM IP核实验中多用到的一样,即PLL产生两个时钟:写时钟为50MHz,读时钟为25MHz。因此直接将上一节实验中所用到的IP核代码拷贝添加到本次实验中即可。

3.3 编写写模块代码
写模块用来向FIFO模块写入数据。通过判断满空信号,当满时写请求拉低;当空时写请求拉高,可以控制写请求状态。当写请求拉高时,改变待写入的数据,从而向FIFO写入数据。
需要注意的是,当写入数据时,利用时序逻辑判断写请求wr_req,会导致在第一次写满信号拉高时,写请求wr_req无法检测到写满信号的拉高状态(有一个时钟的延迟),进而导致写满数据后又写入了一次数据(写不进去),下一次时钟才能停止写入数据。解决办法可以利用组合逻辑,将当前写请求信号与满信号取反相与的结果作为最终的写请求信号。
module fifo_wr(input clk,input rst_n,input wr_full,input wr_empty,output wr_req,output reg [7:0] wr_data);reg wr_req_temp;// 写满后写请求信号立即被拉低assign wr_req = wr_req_temp & (~wr_full);// 写请求控制always @(posedge clk or negedge rst_n) beginif(!rst_n)wr_req_temp <= 1'b0;else if(wr_empty)wr_req_temp <= 1'b1;else id(wr_full)wr_req_temp <= 1'b0;elsewr_req_temp <= wr_req_temp;end// 生成待写入的数据always @(posedge clk or negedge rst_n) beginif(!rst_n)wr_data <= 8'd0;else if(wr_req)wr_data <= wr_data + 1'b1;elsewr_data <= 8'd0;endendmodule
3.4 编写读模块代码
读模块用来从FIFO模块读取数据。通过判断满空信号,当满时读请求拉高;当空时读请求拉低,可以控制读请求的状态。当读请求拉高时,从FIFO读取数据。
需要注意的是,与写入数据时一样,当读取数据时,利用时序逻辑判断读请求rd_req,也会导致在第一次空信号拉高时,读请求rd_req无法检测到空信号的拉高状态(有一个时钟的延迟),进而导致数据为空后又读取了一次数据(读不出来),下一次时钟才能停止读取数据。解决办法可以利用组合逻辑,将当前读请求信号与空信号取反相与的结果作为最终的读请求信号。
module fifo_rd(input clk,input rst_n,input rd_full,input rd_empty,input [7:0] rd_data,output rd_req);reg rd_req_temp;// FIFO为空后读请求信号立即被拉低assign rd_req = rd_req_temp & (~rd_empty);// 读请求控制always @(posedge clk or negedge rst_n) beginif(!rst_n)rd_req_temp <= 1'b0;else if(rd_full)rd_req_temp <= 1'b1;else id(rd_empty)rd_req_temp <= 1'b0;elserd_req_temp <= rd_req_temp;endendmodule
3.5 编写顶层模块代码
顶层模块代码只需要将这些信号线束连接起来即可。
module ip_fifo(input sys_clk,input sys_rst_n);wire wr_clk_50m ;wire rd_clk_25m ;wire locked_sig ;wire wr_full ;wire wr_empty ;wire wr_req ;wire [7:0] wr_data ;wire rd_full ;wire rd_empty ;wire [7:0] rd_data ;wire rd_req ;wire [7:0] rdusedw ;wire [7:0] wrusedw ;wire rst_n ;assign rst_n = sys_rst_n & locked_sig;pll_clk u_pll_clk (.areset ( ~sys_rst_n ),.inclk0 ( sys_clk ),.c0 ( wr_clk_50m ),.c1 ( rd_clk_25m ),.locked ( locked_sig ));fifo_wr u_fifo_wr (.clk ( wr_clk_50m ),.rst_n ( rst_n ),.wr_full ( wr_full ),.wr_empty ( wr_empty ),.wr_req ( wr_req ),.wr_data ( wr_data ));async_fifo u_async_fifo (.aclr ( ~rst_n ),.data ( wr_data ),.rdclk ( rd_clk_25m ),.rdreq ( rd_req ),.wrclk ( wr_clk_50m ),.wrreq ( wr_req ),.q ( rd_data ),.rdempty ( rd_empty ),.rdfull ( rd_full ),.rdusedw ( rdusedw ),.wrempty ( wr_empty ),.wrfull ( wr_full ),.wrusedw ( wrusedw ));fifo_rd u_fifo_rd(.clk ( rd_clk_25m ),.rst_n ( rst_n ),.rd_full ( rd_full ),.rd_empty ( rd_empty ),.rd_data ( rd_data ),.rd_req ( rd_req ));endmodule
四、代码仿真
4.1 编写激励文件
生成一个激励文件模板,并在此基础上进行修改。
// Copyright (C) 2019 Intel Corporation. All rights reserved.// Your use of Intel Corporation's design tools, logic functions// and other software and tools, and any partner logic// functions, and any output files from any of the foregoing// (including device programming or simulation files), and any// associated documentation or information are expressly subject// to the terms and conditions of the Intel Program License// Subscription Agreement, the Intel Quartus Prime License Agreement,// the Intel FPGA IP License Agreement, or other applicable license// agreement, including, without limitation, that your use is for// the sole purpose of programming logic devices manufactured by// Intel and sold by Intel or its authorized distributors. Please// refer to the applicable agreement for further details, at// https://fpgasoftware.intel.com/eula.// *****************************************************************************// This file contains a Verilog test bench template that is freely editable to// suit user's needs .Comments are provided in each section to help the user// fill out necessary details.// *****************************************************************************// Generated on "04/29/2022 12:52:01"// Verilog Test Bench template for design : ip_fifo//// Simulation tool : ModelSim-Altera (Verilog)//`timescale 1 ns/ 1 nsmodule ip_fifo_vt();// constants// general purpose registers//reg eachvec;// test vector input registersreg sys_clk;reg sys_rst_n;// wiresparameter T = 20;// assign statements (if any)ip_fifo i1 (// port map - connection between master ports and signals/registers.sys_clk(sys_clk),.sys_rst_n(sys_rst_n));initialbegin// code that executes only once// insert code here --> beginsys_clk = 1'b0;sys_rst_n = 1'b0;#(5*T) sys_rst_n = 1'b1;#(500*T) $stop;// --> end//$display("Running testbench");endalways// optional sensitivity list// @(event1 or event2 or .... eventn)begin// code executes for every event on sensitivity list// insert code here --> begin#(T/2) sys_clk = ~sys_clk;//@eachvec;// --> endendendmodule
4.2 配置 ModelSim 工程
4.2.1 创建项目
项目名称:ip_fifo
项目路径:D:/Projects/FPGA/Projects/10_ip_fifo/sim/tb
4.2.2 添加仿真文件
- ip_fifo.vt:X:/Projects/10_ip_fifo/par/simulation/modelsim/ip_fifo.vt
- ip_fifo.v:X:/Projects/10_ip_fifo/rtl/ip_fifo.v
- fifo_rd.v:X:/Projects/10_ip_fifo/rtl/fifo_rd.v
- fifo_wr.v:X:/Projects/10_ip_fifo/rtl/fifo_wr.v
- async_fifo.v:X:/Projects/10_ip_fifo/par/ipcore/async_fifo/async_fifo.v
- pll_clk.v:X:/Projects/10_ip_fifo/par/ipcore/pll_clk/pll_clk.v
- altera_mf.v:D:/Quartus/install/quartus/eda/sim_lib/altera_mf.v
4.3 开始仿真
编译完成后,选择ip_fifo_vt开始仿真。
大致看一下,读写过程中的信号波形没有问题。具体看一下读写的数据:
- 一开始时写空信号wr_empty为高,待数据稳定后(即locked_sig拉高后),写请求信号拉高,开始写数据,wr_data开始累加。
- 一开始读空信号是高电平,所以读请求信号为低,不读数据。

- 再写入256个数据(255)之后,FIFO满,写满信号wr_full被拉高,写请求拉低,不再继续写入数据。
- 在读到满数据后,读满信号rd_full拉高,从而读请求rd_req拉高,开始读数据。

- 再读到第256个数据(255)后,FIFO中没有数据了,读空信号被拉高,与此同时读请求信号rd_req被拉低,不再读取数据。
- FIFO数据为空后,写空信号wr_empty拉高,从而写请求信号wr_req拉高,开始写入数据。
- 这样,在写满后开始读数据,数据被读空后再写入数据,以此循环。
波形文件:10_ip_fifo.do和10_ip_fifo.wlf详见工程代码10_ip_fifo\sim\tb目录。

