本节文章主打Intel FPGA 开发工具Quartus中FIFO IP核的调用方法
系列文章目录
【Altera】IP核(1)—PLL锁相环
【Altera】IP核(2)—RAM随机存取存储器
【Altera】IP核(3)—FIFO先进先出存储器
前言
之前已经学习了Quartus 的PLL IP核、RAM IP核如何调用的过程。本节文章来学习FIFO IP核的调用方法。
一、FIFO是什么
FIFO(First In First Out),先进先出。常用于数据的缓存或者高速异步数据的交互,即跨时钟域信号传递。FIFO没有外部地址线,采用顺序写入数据又顺序读出数据的方式。
三种存储器的区别:
RAM 主要用来存放程序以及程序执行过程中产生的中间数据和运算结果。
ROM:只读储存器,只能读取数据,不能改变 ROM 中数值。
FIFO:先进先出存储器,数据缓冲和跨时钟域数据同步处理。
FIFO不能像RAAM和ROM那样由地址线决定读取或者写入某个指定地址。
FIFO 从输入时钟的角度来分,有两种类型:单时钟 FIFO(SCFIFO)和双时钟 FIFO(DCFIFO);
其中,双时钟 FIFO 又可从输出数据的位宽的角度,分为普通双时钟(DCFIFO)和混合宽度双时钟 FIFO(DCFIFO_MIXED_WIDTHS)。
单时钟 FIFO 具有一个独立的时钟端口 clock,因此,所有的输入输出信号都同步于 clock 信号。
双时钟 FIFO结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟wrclk,所有与读相关的信号都是同步于读时钟 rdclk。
单时钟 FIFO 常用于同步时钟的数据缓存;
双时钟 FIFO 常用于跨时钟域的数据信号的传递,例如时钟域A下的数据data1传递给异步时钟域B,当data1为连续变化信号时,如果直接传递给时钟域B则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态(数据采样失真)问题在内的一系列问题,使用双时钟FIFO能够将不同时钟域中的数据同步到所需的时钟域中。
二、IP调用
本节文章以Quatrus为例,用文字加图片的方式展示调用FIFO的方法。
1.调用IP
(1)创建文件
创建FIFO IP测试文件夹,并且在prj中创建一个quartus的工程。(芯片类型自己选,我用的EP4CE6F17C8,芯片无所谓,本工程没用到开发板。)
doc:存放结果、图片等等
ip :存放IP核
prj :存放工程文件
rtl :存放主要代码
tb :存放testbench代码
(2)调用IP
在IP Catalog 搜索栏中搜索“FIFO”,然后选择“FIFO”。
将IP存入文件夹中ip内,点击OK。
(3)配置IP
接下来就是配置IP的步骤:
1、FIFO的位宽
2、FIFO的深度
3、是否设置成双时钟FIFO。
点击NEXT。
此页不改动
下图
设置读写的空满。
下图
1、选择FIFO 的工作模式,这两种工作模式的区别在于输出以及一些信号的相对延时。数据在读请求发出之前和之后有效。
首先是正常模式(normal mode),在此模式下,读使能有效,FIFO 会在下一个时钟输出有效的数据 q。
而前显模式(Show-Ahead)下空指示信号 empty 会滞后写使能两个时钟周期。
这里选前显模式。
2、选择默认就行。
把例化文件勾选上,可以看左边的简化图,点击finish完成配置。
具体可以去这里看,细节讲的好,我就是演示如何调用IP
2.代码
IP核配置完成后就直接在文件夹中的rtl中新建“.v”文件编写代码:
将inst文件例化进代码中,然后把文件加入工程并编译:
/*
* @Proj: fifo_test
* @Module: test
* @Author: Yang.
* @Date: 2023-12-12 17:35:46
* @LastEditors: Yang.
* @LastEditTime: 2023-12-13 11:41:58
*/
//---------<模块及端口声名>------------------------------------------
module fifo_test(
input wire wr_clk ,//写时钟
input wire rd_clk ,//读时钟
input wire rst_n ,
input wire [7:0] wr_din ,//写入fifo数据
input wire wr_en ,//写使能
input wire rd_en ,//读使能
output reg [7:0] rd_dout ,//读出的数据
output reg rd_out_vld //读有效信号
);
//------------<参数说明>--------------------------------------------
wire [7:0] wr_data ;
wire [7:0] q ;
wire wr_req ;//写请求
wire rd_req ;//读请求
wire wr_empty ;//写空
wire wr_full ;//写满
wire rd_empty ;//读空
wire rd_full ;//读满
wire [7:0] wr_usedw ;//在写时钟域下,FIFO 中剩余的数据量;
wire [7:0] rd_usedw ;//在读时钟域下,FIFO 中剩余的数据量。
fifo fifo_inst (
.data ( wr_data ),
.rdclk ( rd_clk ),
.rdreq ( rd_req ),
.wrclk ( wr_clk ),
.wrreq ( wr_req ),
.q ( q ),
.rdempty ( rd_empty ),
.rdfull ( rd_full ),
.rdusedw ( rd_usedw ),
.wrempty ( wr_empty ),
.wrfull ( wr_full ),
.wrusedw ( wr_usedw )
);
assign wr_data = wr_din;
assign wr_req = (wr_full == 1'b0)?wr_en:1'b0;
assign rd_req = (rd_empty == 1'b0)?rd_en:1'b0;
always @(posedge rd_clk or negedge rst_n)begin
if(!rst_n)begin
rd_dout <= 0;
end
else begin
rd_dout <= q;
end
end
always @(posedge rd_clk or negedge rst_n)begin
if(!rst_n)begin
rd_out_vld <= 1'b0;
end
else begin
rd_out_vld <= rd_req;
end
end
endmodule
3.仿真
`timescale 1 ns/1 ns
module tb_fifo();
//时钟和复位
reg wr_clk;
reg rd_clk;
reg rst_n ;
//输入信号
reg [7:0] wr_din;
reg wr_en ;
reg rd_en ;
//输出信号
wire rd_out_vld;
wire [7:0] rd_dout;
parameter WR_CYCLE = 20;//写时钟周期,单位为 ns,
parameter RD_CYCLE = 30;//读时钟周期,单位为 ns,
parameter RST_TIME = 3 ;//复位时间,此时表示复位 3 个时钟周期的时间。
//待测试的模块例化
fifo_test fifo_test_inst(
/*input wire */.wr_clk ( wr_clk ) ,//写时钟
/*input wire */.rd_clk ( rd_clk ) ,//读时钟
/*input wire */.rst_n ( rst_n ) ,
/*input wire [7:0] */.wr_din ( wr_din ) ,//写入fifo数据
/*input wire */.wr_en ( wr_en ) ,//写使能
/*input wire */.rd_en ( rd_en ) ,//读使能
/*output reg [7:0] */.rd_dout ( rd_dout ) ,//读出的数据
/*output reg */.rd_out_vld ( rd_out_vld ) //读有效信号
);
integer i = 0;
//生成本地时钟 50M
initial wr_clk = 0;
always #(WR_CYCLE/2) wr_clk=~wr_clk;
initial rd_clk = 0;
always #(RD_CYCLE/2) rd_clk=~rd_clk;
//产生复位信号
initial begin
rst_n = 1;
#2;
rst_n = 0;
#(WR_CYCLE*RST_TIME);
rst_n = 1;
end
//输入信号赋值
initial begin
#1;
wr_din = 0;//赋初值
wr_en = 0;
#(10*WR_CYCLE);
for(i=0;i<500;i=i+1)begin//开始赋值
wr_din = {$random};
wr_en = {$random};
#(1*WR_CYCLE);
end
#(100*WR_CYCLE);
end
initial begin
#1;
rd_en = 0;//赋初值
#(12*RD_CYCLE);
for(i=0;i<500;i=i+1)begin//开始赋值
rd_en = {$random};
#(1*RD_CYCLE);
end
#(100*RD_CYCLE);
$stop;
end
endmodule
总结
在写使能有效时将数据写入,读使能有效时将数据读出。空满信号没用到,因为数据还没填满FIFO就读出来了。
已经学完了Quartus的基础IP核的调用,接下来就是Vivado的这些IP的调用以及基础协议的学习。继续关注。
有问题网站发私信,就是网站回复可能不及时,或者加qq:35二4291零六5
qq回复及时。
具体工程去百度网盘:
链接:https://pan.baidu.com/s/1a4yQaC2YQ6U9hMK5QWfgZA?pwd=aysz
提取码:aysz
压缩包和具体工程都有,资源里就是一些万能代码。
加油学习吧家人们。