超级版主
- 积分
- 4676
- 金钱
- 4676
- 注册时间
- 2019-5-8
- 在线时间
- 1224 小时
|
本帖最后由 正点原子运营 于 2021-10-30 10:09 编辑
1)实验平台:正点原子新起点V2FPGA开发板
2) 章节摘自【正点原子】《新起点之FPGA开发指南 V2.1》
3)购买链接:https://detail.tmall.com/item.htm?id=609758951113
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_xinqidian(V2).html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流QQ群:712557122
第二十章 IP核之FIFO实验
FIFO的英文全称是First In First Out,即先进先出。FPGA使用的FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递。它与FPGA内部的RAM和ROM的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像RAM和ROM那样可以由地址线决定读取或写入某个指定的地址。本章我们将对Quartus II软件生成的FIFO IP核进行读写测试,来向大家介绍Altera FIFO IP核的使用方法。
本章包括以下几个部分:
2020.1简介
20.2实验任务
20.3硬件设计
20.4程序设计
20.5下载验证
20.1简介
FIFO从输入时钟的角度来分,有两种类型:单时钟FIFO(SCFIFO)和双时钟FIFO(DCFIFO),其中双时钟FIFO又可从输出数据的位宽的角度分为普通双时钟(DCFIFO)和混合宽度双时钟FIFO(DCFIFO_MIXED_WIDTHS)。单时钟FIFO和双时钟FIFO的符号图如图 20.1.1所示。从图中可以看到,单时钟FIFO具有一个独立的时钟端口clock,因此所有的输入输出信号都同步于clock信号。而在双时钟FIFO结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟wrclk,所有与读相关的信号都是同步于读时钟rdclk。在双时钟FIFO的符号图中,位于图中上侧部分的以“data”和“wr”开头的信号为与写相关的所有信号,位于中间部分的“q”和以“rd”开头的信号为与读相关的所有信号,位于底部的为异步清零信号。
图 20.1.1 单时钟 FIFO 与双时钟 FIFO 的符号图
对于FIFO需要了解一些常见参数:
FIFO的宽度:FIFO一次读写操作的数据位N;
FIFO的深度:FIFO可以存储多少个宽度为N位的数据。
空标志:对于双时钟FIFO又分为读空标志rdempty和写空标志wrempty。FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出。
满标志:对于双时钟FIFO又分为读满标志rdfull和写满标志wrfull。FIFO已满或将要写满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出。
读时钟:读FIFO时所遵循的时钟,在每个时钟的上升沿触发。
写时钟:写FIFO时所遵循的时钟,在每个时钟的上升沿触发。
对于FIFO的基本知识先了解这些就足够了,可能有人会好奇为什么会有单时钟FIFO和双时钟FIFO,它们各自的用途是什么。之所以有单时钟FIFO和双时钟FIFO是因为各自的作用不同。单时钟FIFO常用于同步时钟的数据缓存,双时钟FIFO常用于跨时钟域的数据信号的传递,例如时钟域A下的数据data1传递给异步时钟域B,当data1为连续变化信号时,如果直接传递给时钟域B则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用双时钟FIFO能够将不同时钟域中的数据同步到所需的时钟域中。
20.2实验任务
本节实验任务是使用Quartus II生成FIFO IP核,并实现当FIFO为空时就开始向FIFO中写入数据,直到FIFO写满为止;当FIFO为满时则开始从FIFO中读出数据,直到FIFO读空为止的功能,来向大家详细介绍一下FIFO IP核的使用方法。
20.3硬件设计
本章实验只用到了输入的时钟信号和按键复位信号,没有用到其它硬件外设。
本实验中,各端口信号的管脚分配如下表所示。
表 20.3.1 IP实验管脚分配
信号名 方向 管脚 端口说明
sys_clk input M2 系统时钟,50Mhz
sys_rst_n input M1 系统复位,低有效
因为引脚数极少这里不再给出TCL文件。
20.4程序设计
根据实验任务要求和模块化设计的思想,我们需要如下5个模块:fifo模块、写fifo模块、读fifo模块、PLL IP核模块以及顶层模块,顶层模块例化了其余模块实现前四个模块的数据交互。由于FIFO多用于跨时钟域信号的处理,所以本实验我们使用双时钟FIFO来向大家详细介绍双时钟FIFO IP核的创建和使用,我们使用两个不同的时钟分别作为RAM的写时钟和读时钟,这两个时钟由PLL IP核生成,输出的时钟分别是50Mhz和25Mhz。系统的功能框图如下所示:
图 20.4.1 系统框图
接下来我们创建一个名为ip_fifo的工程,在这里我们就不再给出Quartus II软件创建工程的详细过程,如果大家对Quartus II软件的创建过程还不熟悉的话,可以参考“第四章 Quartus II软件的安装和使用”章节中的Quartus II软件的使用部分。新建后的工程如下所示:
图 20.4.2 工程新建完成页面
创建好了工程以后,接下来我们创建FIFO IP核。我们在Quartus II软件的菜单栏中找到【Tools】→【MegaWizard Plug-In Manager】按钮并点击打开,Tool工具栏打开面及打开后弹出的页面如图 20.4.3和图 20.4.4所示。
图 20.4.3 工具栏打开IP核页面
图 20.4.4 创建IP核向导页面
在该页面中,可以看到有三个选项,第一个是创建一个新的IP核,第二个是编辑一个已经创建好的IP核,第三个是复制一个已经创建好的IP核。因为我们这里是首次创建IP核,因此直接选择默认的第一个选项,然后点击【Next>】,进入如图 20.4.5所示页面。
图 20.4.5 选择FIFO IP核页面
在该页面中,我们可以在Memory Compiler下找到FIFO IP核,也可以直接在搜索框中输入FIFO找到它。我们找到FIFO IP核以后,单击选中它,然后我们需要选择FIFO IP核保存的路径及名称。首先大家在工程所在路径par文件夹下创建一个文件夹ipcore,由于本次实验会用到多个IP核,为了方便管理IP核,我们在ipcore文件夹下创建一个async_fifo文件夹,用于存放FIFO IP核(如果之前没有创建ipcore和async_fifo文件夹的话)。
接下来在“What name do you want for the output file”一栏中输入IP存放的路径及名称,这里我们命名为async_fifo并且选择创建的IP核代码为Verilog HDL(默认为Verilog HDL)。完成这些设置以后,我们点击【Next>】,进入如图 20.4.6所示页面。
图 20.4.6 FIFO IP核模式配置页面
How wide should the FIFO be:设置FIFO的位宽,这里我们选择8bits;
How deep should the FIFO be:设置FIFO的深度,也就是能存放多少个指定位宽的数据,这里我们选择256words,这样设置以后FIFO的容量大小为256个8bits;
Do you want a common clock for reading and writing the FIFO:设置FIFO的驱动时钟类型,可以选择单时钟FIFO或者双时钟FIFO。FIFO一般用来实现数据的缓存和跨时钟的处理,因此我们选择双时钟FIFO。
选择完之后,我们直接点击【Next>】,进入如图 20.4.7所示页面。
图 20.4.7 DCFIFO1配置页面
从该页面的“Which type of optimization do you want?”我们可以看出,该页面主要是用于对我们的DCFIFO进行优化的,在箭头1、2、3所指处有三种针对读时钟同步、亚稳态保护、面积和速度的优化类型,下面我们简单的介绍一下这三种优化类型:
Lowest latency but requires synchronized clocks(最低延迟,但要求同步时钟):此选项使用一个同步阶段,没有亚稳态保护,适用于同步时钟。它是最小尺寸,提供良好的Fmax。
TYPE_Cmal setting for unsynchronized clocks(异步时钟时的最小设置):这个选项使用两个同步阶段,具有良好的亚稳态保护。它是中等尺寸,提供良好的Fmax。
Best metastability protection, best fmax and unsynchronized clocks(异步时钟时最好的亚稳态保护,最好的Fmax,不同步):这个选项使用三个或更多的同步阶段,具有最好的亚稳态保护。它是最大尺寸,给出了最好的Fmax。
在使用过程中,通常我们选择的是默认的中等优化,具体的优化主要还是看你的工程的要求,假如你的工程对速度和稳定性要求很高,同时资源又很多,那么你就可以使用第三个选项,假如你的工程资源很紧张,那么你可以选择使用第一个资源少的优化。在此我们保持默认的设置,直接点击【Next>】,进入如图 20.4.8所示页面。
图 20.4.8 DCFIFO2配置页面
该页面用于选择可选的输出控制信号,从读方(Read side)和写方(Write side)分别进行选择。下面我们简单的介绍一下:
rdfull和wrfull:FIFO满的标记信号,为高电平时表示FIFO已满,此时不能再进行写操作。
rdempty和wrempty:FIFO空的标记信号,为高电平时表示FIFO已空,不能在进行读操作。
rdusedw[]和wrusedw[]:显示存储在FIFO中数据个数的信号。
Add an extra MSB to usedw ports:将rdusedw和wrusedw数据位宽增加1位,用于保护FIFO在写满时不会翻转到0。
Asynchronous clear:异步复位信号,用于清空FIFO。
这里我们选择读空、读满、读侧数据量和写空、写满信号、写侧数据量,并勾选使能异步复位,然后继续点击【Next>】,进入如图 20.4.9所示页面。
图 20.4.9 Rdreq Option,Blk Type的配置页面
该页面用于选择输出模式和存储器类型。最上面的红框选择输出模式,输出模式有两种:正常模式和前显模式。对于正常模式,FIFO将端口rdreq看做正常的读请求并在该端口信号为高电平进行读操作。对于前显模式,FIFO将端口rdreq看做读确认信号,将rdreq信号置为高电平时将输出FIFO中的下一个数据字(如果存在)。如果使用前显模式,将会使设计性能下降。这里我们使用默认值:正常模式。
接下来的红框用于指定实现存储器使用的存储块类型和存储器的存储深度,具体可选值与使用的FPGA芯片有关,默认为Auto,我们一般使用默认值就可以了,所以这里直接点击【Next>】,进入如图 20.4.10所示页面。
图 20.4.10 Optimization,Circuitry Protection 的配置页面
该页面主要用于选择是否禁止上溢检测和下溢检测的保护电路。如果你不需要上溢检测和下溢检测保护电路,那么你可以通过Disable来禁止它们,以此来提高我们的FIFO性能。
上溢检测保护电路主要是用于在FIFO满时禁止wrreq端口,下溢检测保护电路主要是用于在FIFO空时,禁止rdreq端口,它们默认的状态是打开的。这里我们使用默认设置。
“Implement FIFO storage with logic cells only, even if the device contains memory blocks?”选项使用逻辑单元实现FIFO存储器,即使器件拥有存储块。这里使用默认设置,用存储块实现FIFO。
然后直接点击【Next>】,进入如图 20.4.11所示页面。
图 20.4.11 第一个输出时钟c0配置页面
从该页面中,我们可以看出,如果我们想要仿真FIFO IP核,那么我们就需要用到“altera_mf”这个仿真库。如果我们想要将此FIFO IP核用在其他的EDA工具上,我们可以通过选择“Generate netlist”这个选项来生成IP_syn.v文件,用于其他的EDA工具中。这里需要注意的是,并不是所有的第三方EDA工具都支持。点击【Next>】,进入如图 20.4.12所示页面。
图 20.4.12 Summary 的配置页面
在该页面,我们可以看到,该IP核能生成的所有文件都在该页面中,在这么多的文件中,我们只要选择FIFO_inst.v文件就可以了,方便对FIFO的例化。至此,关于FIFO IP核的配置就讲解完了。然后我们点击【Finish】完成整个IP核的创建。接下来Quartus II软件会在ipcore文件夹下创建FIFO的IP核文件,然后询问我们是否添加至工程,点击“YES”按钮将生成的IP核添加至工程,如下图所示页面。
图 20.4.13 IP核添加至工程确认界面
接下来返回到工程界面,在File界面里,我们可以看到生成的fifo.qip已经添加到工程中,如图 20.4.14所示界面。
图 20.4.14 FIFO添加至工程界面
打开async_fifo.v文件,我们可以看到使用Modelsim对fifo文件进行仿真时需要添加一个名为altera_mf的仿真库,如下图所示:
图 20.4.15 FIFO_clk文件注释说明
至此,FIFO IP核的创建已经全部完成,如果需要修改IP核的话,点击在Quartus II软件的菜单栏中找到【Tools】→【MegaWizard Plug-In Manager】按钮并点击打开,图 20.4.16为打开后的页面。
图 20.4.16 修改IP核页面
和我们第一次创建IP核不同的是,这一次我们选择第二个选项,修改已经存在的IP核,然后点击【Next>】,进入选择IP核路径页面,双击ipcore文件夹,进入如图 20.4.17所示页面。然后双击fifo.v文件,点击【Next>】开始重新配置FIFO IP核。
图 20.4.17 选择需要修改的IP核路径页面
除此之外,我们再创建一个PLL IP核(命名为pll_clk),共输出两路时钟,时钟频率分别是50Mhz和25Mhz,存放路径为par/ipcore/pll_clk,创建过程此处不再赘述。
接下来我们设计一个verilog文件来对FIFO写入数据,文件名为fifo_wr.v,编写的verilog代码如下:
- 1 module fifo_wr(
- 2 input clk, //时钟信号
- 3 input rst_n, //复位信号
- 4
- 5 //fifo的写端口
- 6 input wr_full, //写侧满信号
- 7 input wr_empty, //写侧空信号
- 8 output wr_req, //写请求信号
- 9 output reg [7:0] wr_data //写入FIFO的数据
- 10 );
- 11
- 12 //reg define
- 13 reg wr_req_t;
- 14
- 15 //*****************************************************
- 16 //** main code
- 17 //*****************************************************
- 18
- 19 //防止fifo写满后继续写入数据
- 20 assign wr_req = wr_req_t & (~wr_full);
- 21
- 22 //wr_req_t信号赋值
- 23 always @(posedge clk or negedge rst_n) begin
- 24 if(!rst_n)
- 25 wr_req_t <= 1'b0;
- 26 else if(wr_empty)
- 27 wr_req_t <= 1'b1;
- 28 else if(wr_full)
- 29 wr_req_t <= 1'b0;
- 30 end
- 31
- 32 //写数据信号赋值
- 33 always @(posedge clk or negedge rst_n) begin
- 34 if(!rst_n)
- 35 wr_data <= 8'd0;
- 36 else if(wr_req)
- 37 wr_data <= wr_data + 1'b1;
- 38 else
- 39 wr_data <= 8'd0;
- 40 end
- 41
- 42 endmodule
- 写fifo模块主要完成向fifo中写入数据的功能,当fifo为空时,向fifo中写入数据;当fifo写满之后,停止写入数据,然后重新判断fifo是否为空。
- 然后设计一个verilog文件来对FIFO进行读取数据,文件名为fifo_rd.v,编写的verilog代码如下:
- 1 module fifo_rd(
- 2 input clk, //时钟信号
- 3 input rst_n, //复位信号
- 4
- 5 //fifo的读端口
- 6 input rd_full, //读侧满信号
- 7 input rd_empty, //读侧空信号
- 8 output rd_req, //读请求信号
- 9 input [7:0] rd_data //读出FIFO的数据
- 10 );
- 11
- 12 //reg define
- 13 reg rd_req_t;
- 14
- 15 //*****************************************************
- 16 //** main code
- 17 //*****************************************************
- 18
- 19 //防止fifo读空后继续读出数据
- 20 assign rd_req = rd_req_t & (~rd_empty);
- 21
- 22 //rd_req_t信号赋值
- 23 always @(posedge clk or negedge rst_n) begin
- 24 if(!rst_n)
- 25 rd_req_t <= 1'b0;
- 26 else if(rd_full)
- 27 rd_req_t <= 1'b1;
- 28 else if(rd_empty)
- 29 rd_req_t <= 1'b0;
- 30 end
- 31
- 32 endmodule
复制代码
读fifo模块主要完成向fifo中读出数据的功能,当fifo数据为满的状态时,开始向fifo中读出数据;当fifo读空之后,停止读数据,然后重新判断fifo是否为满的状态。
最后我们创建一个顶层文件,文件名为ip_fifo.v,实例化刚才创建的FIFO IP核和读写FIFO文件,编写的verilog代码如下。
- 1 module ip_fifo(
- 2 input sys_clk , //时钟信号
- 3 input sys_rst_n //复位信号
- 4 );
- 5
- 6 //wire define
- 7 wire clk_50m ; //50Mhz时钟
- 8 wire clk_25m ; //25Mhz时钟
- 9 wire locked ; //时钟稳定信号
- 10
- 11 wire rst_n ; //复位信号
- 12
- 13 wire [7:0] rd_usedw; //读侧FIFO中的数据量
- 14 wire [7:0] wr_usedw; //写侧FIFO中的数据量
- 15
- 16 wire wr_full ; //写侧满信号
- 17 wire wr_empty; //写侧空信号
- 18 wire wr_req ; //写请求信号
- 19 wire [7:0] wr_data ; //写入FIFO的数据
- 20
- 21 wire rd_full ; //读侧满信号
- 22 wire rd_empty; //读侧空信号
- 23 wire rd_req ; //读请求信号
- 24 wire [7:0] rd_data ; //读出FIFO的数据
- 25
- 26 //*****************************************************
- 27 //** main code
- 28 //*****************************************************
- 29
- 30 //待时钟输出稳定后,再拉高rst_n信号
- 31 assign rst_n = sys_rst_n & locked;
- 32
- 33 //例化锁相环模块
- 34 pll_clk u_pll_clk (
- 35 .areset (~sys_rst_n ),
- 36 .inclk0 (sys_clk ),
- 37 .c0 (clk_50m ),
- 38 .c1 (clk_25m ),
- 39 .locked (locked )
- 40 );
- 41
- 42 //例化FIFO写模块
- 43 fifo_wr u_fifo_wr(
- 44 .clk (clk_50m),
- 45 .rst_n (rst_n),
- 46
- 47 .wr_full (wr_full ),
- 48 .wr_empty (wr_empty),
- 49 .wr_req (wr_req ),
- 50 .wr_data (wr_data )
- 51 );
- 52
- 53 //例化异步FIFO模块
- 54 async_fifo u_async_fifo (
- 55 .aclr (~rst_n ),
- 56 .data (wr_data ),
- 57 .rdclk (clk_25m ),
- 58 .rdreq (rd_req ),
- 59 .wrclk (clk_50m ),
- 60 .wrreq (wr_req ),
- 61 .q (rd_data ),
- 62 .rdempty (rd_empty ),
- 63 .rdfull (rd_full ),
- 64 .rdusedw (rd_usedw ),
- 65 .wrempty (wr_empty ),
- 66 .wrfull (wr_full ),
- 67 .wrusedw (wr_usedw )
- 68 );
- 69
- 70 //例化FIFO读模块
- 71 fifo_rd u_fifo_rd(
- 72 .clk (clk_25m),
- 73 .rst_n (rst_n),
- 74
- 75 .rd_full (rd_full ),
- 76 .rd_empty (rd_empty),
- 77 .rd_req (rd_req ),
- 78 .rd_data (rd_data )
- 79 );
- 80
- 81 endmodule
复制代码
代码中例化了PLL IP核、FIFO IP核、写FIFO模块wr_fifo和读FIFO模块rd_fifo,各模块的信号连接图如下图所示:
图 20.4.18 各模块信号连接图
将FIFO模块输出的写空信号wrempty和写满信号wrfull连接至写FIFO模块(fifo_wr),当写FIFO模块检测到写空信号wrempty拉高时,向FIFO模块发送写请求信号wrreq并写入数据,检测到写满信号wrfull拉高时,停止向FIFO模块写入数据。将FIFO模块输出的读空信号rdempty和读满信号rdfull连接至读FIFO模块(fifo_rd),当读FIFO模块检测到读满信号rdfull拉高时,向FIFO模块发送读请求信号rdreq并从FIFO模块读取数据,检测到读空信号rdempty拉高时,停止从FIFO模块读取数据。
ip_fifo模块添加至工程后,如果工程名字和ip_fifo模块名字不一致的话,必须先将ip_fifo模块设置为顶层模块,设置方法是右键选择ip_fifo.v文件,点击Set as Top-Level Entity。
图 20.4.19 ip_fifo设置为顶层文件
接下来点击分析和综合图标编译工程。
图 20.4.20 开始编译工程界面
工程编译成功后,打开Pin Planner配置FPGA的管脚,管脚按照本章硬件设计中的列表来分配,分配完成后重新编译工程。
接下来我们对FIFO IP核进行仿真,来验证对FIFO的读写操作是否正确。首先在Modelsim软件中创建一个名为ip_fifo_tb的工程,在这里我们就不再给出软件创建工程的详细过程,如果大家对Modelsim软件的创建过程还不熟悉的话,可以参考“第五章Modelsim软件的安装与使用”章节中的Modelsim软件使用部分。
ip_fifo_tb仿真文件源代码如下:
- 1 `timescale 1ns/1ns //仿真的单位/仿真的精度
- 2
- 3 module ip_fifo_tb();
- 4
- 5 parameter T = 20;
- 6
- 7 reg sys_clk;
- 8 reg sys_rst_n;
- 9
- 10 initial begin
- 11 sys_clk = 1'b0;
- 12 sys_rst_n = 1'b0;
- 13 #(T+1)
- 14 sys_rst_n = 1'b1;
- 15 end
- 16
- 17 always #(T/2) sys_clk = ~sys_clk;
- 18
- 19 ip_fifo u_ip_fifo(
- 20 .sys_clk (sys_clk ),
- 21 .sys_rst_n (sys_rst_n)
- 22 );
- 23
- 24 endmodule
复制代码
我们还需要将Testbench添加至我们的Quartus II软件的仿真设置中,添加好以后,我们就可以点击Quartus II软件菜单栏中的找到【Tools】→【Run Simulation Tool】→【RTL Simulation】点击打开,弹出如下图所示页面。
图 20.4.21 FIFO空时
可以看到在读空FIFO时,读空信号rd_empty先拉高,过两个时钟周期后写空信号wr_empty才拉高,这是由FIFO内部结构决定的,并且在写请求信号wr_req拉高后的第4个时钟周期读空信号rd_empty才拉低。
图 20.4.22 FIFO满时
由上图可以看到在写满信号wr_full拉高2个时钟周期后,读满信号rd_full才有效,并且在读请求信号rd_req拉高后的第3个时钟周期写满信号wr_full才拉低。
20.5下载验证
首先将下载器一端连接电脑,另一端与开发板上对应端口连接,最后连接电源线并打开电源开关。接下来我们使用SignalTap II软件对FIFO IP核进行调试,首先我们在Quartus II软件中创建一个SignalTap II调试文件,我们将wr_empty、wr_full、wr_req、wr_data和wrusedw信号添加至SignalTap II调试文件中,采样时钟使用50Mhz,下图为SignalTap II软件采集到的波形图。
图 20.5.1 FIFO写数据
可以看到在FIFO为空后,开始向FIFO中写入数据,写入的数据从0~255(255在图中未展示)。
记下来我们将rd_empty、rd_full、rd_req、rd_data和rdusedw信号添加至SignalTap II调试文件中,采样时钟使用25Mhz,下图为SignalTap II软件采集到的波形图。
图 20.5.2 FIFO读数据
可以看到在写满后,开始从FIFO中读出数据,读出的数据为0~255(255在图中未展示),写入的数据和读出的数据一致,说明FIFO读写验证成功。 |
|