一、前置知识
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) begin
if(!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;
else
wr_req_temp <= wr_req_temp;
end
// 生成待写入的数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_data <= 8'd0;
else if(wr_req)
wr_data <= wr_data + 1'b1;
else
wr_data <= 8'd0;
end
endmodule
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) begin
if(!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;
else
rd_req_temp <= rd_req_temp;
end
endmodule
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 ns
module ip_fifo_vt();
// constants
// general purpose registers
//reg eachvec;
// test vector input registers
reg sys_clk;
reg sys_rst_n;
// wires
parameter 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)
);
initial
begin
// code that executes only once
// insert code here --> begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#(5*T) sys_rst_n = 1'b1;
#(500*T) $stop;
// --> end
//$display("Running testbench");
end
always
// 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;
// --> end
end
endmodule
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
目录。