本次设计源码下载地址:http://download.csdn.net/detail/noticeable/9915523
课程目标:学习调用quartus II 软件的FIFO(先进先出)IP核,并通过仿真,了解其时序。
实验现象:通过quartus II 调用FIFO IP核,并进行不同形式的配置,通过仿真验证其接口时序。
知识点:FIFO IP核的使用。
FIFO是什么?有什么用?
FIFO 即先进先出存储器,是一个在FPGA中使用到的具有先进先出特性的一个存储器,其常被用来作为数据的缓存或者高速异步数据的交互。
FIFO可分为两种结构:单时钟FIFO(SCFIFO)和双时钟FIFO(DCFIFO),其中双时钟FIFO 又可分为普通时钟(DCFIFO)和混合宽度时钟FIFO(DCFIFO_MIXED_WIDTHS).。
两种结构的时钟的符号图如下所示:
关于各接口的引脚说明可以在上传到压缩包中查看官方文档,这里不再缀述 。
从结构图可以看出
单时钟FIFO具有一个独立的时钟端口clock,因此所有输入信号的读取都是在clock的上升沿进行的,所有输出信号的变化也是在clock信号的上升沿的控制下进行的1,即单时钟FIFO的所有输入输出信号都是同步于clock信号的。
在双时钟FIFO结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟wrclk的,所有与读相关的信号都是同步于读时钟rdclk的。在双时钟FIFO的符号图中,位于上部分的为与写相关的所有信号,位于中间部分的为与读相关的所有信号,位于下部的为异步清零信号。
其中根据两种FIFO 各种的用处不同,使用的地点也不同。‘
单时钟FIFO:
单时钟FIFO常用于片内数据交互,例如,在FPGA的控制下从外部传感器读取到的一连串传感器数据,首先被写入FIFO中,然后再以UART串口的数据发送速率将数据依次发送出去。由于传感器的单次读取数据可能很快,但并不是时刻都需要采集数据,例如某传感器使用SPI接口的协议,FPGA以2M的SPI数据速率从该传感器中读取20个数据,然后以9600的波特率通过串口发送出去。此过程每秒钟执行一次。因为2M的数据速率远高于串口9600的波特率,因此需要将从传感器中采集到的数据首先用FIFO缓存起来,然后再以串口的数据速率缓慢发送出去。这里,由于传感器数据的读取和串口数据的发送都是可以同步于同一个时钟的,因此可以使用单时钟结构的FIFO来实现此功能。
双时钟FIFO:
双时钟FIFO的一个典型应用就是异步数据的收发。
所谓异步数据是指数据的发送端和接收端分别同步与不同的时钟域,使用双时钟FIFO的独立的读写时钟结构,能够将不同时钟域中的数据同步到所需的时钟域系统中。例如,在一个高速数据采集系统中,实现将高速ADC采集的数据通过千兆以太网发送到PC机。ADC的采样时钟(CLK1)由外部专用锁相环芯片产生,则高速ADC采样得到的数据就是同步于该时钟信号的,在FPGA内部,如果FPGA工作时钟(CLK2)是由独立的时钟芯片加片上锁相环产生的,则CLK1和CLK2就是两个不同域的时钟,他们的频率和相位没有必然的联系,假如CLK1为65M,CLK2为125M,那么就不能使用125M的数据来直接采集65M速率的数据,因为两者数据速率不匹配,在采集过程中会出现包括亚稳态问题在内的一系列问题,所以这里就可以使用一个具备双时钟结构的FIFO来进行异步数据的收发。
下图为使用FIFO进行异步数据收发的简易系统框图:
基于千兆以太网传输的高速数据采集(8bit)系统
在此系统中,由于ADC的数据位宽为8位,基于UDP协议的以太网发送模块所需的数据也是8位,因此使用的是非混合宽度的双时钟FIFO结构。假如CLK1的频率为20M,ADC的数据位宽为16位,则可以使用混合宽度的双时钟FIFO,在实现异步时钟域数据收发的同时,实现数据位宽的转换。通过设置双时钟FIFO的写入位宽为16位,读取位宽为8位,则可以实现将16位的ADC数据转换为以太网支持的8位发送数据,然后通过以太网发送到PC机。
总而言之:FIFO在系统中是作为缓冲器存在的一种状态,其根据缓冲的需求不同选择不同机构的fifo,从而使两个或多个不同采样率的数据可以进行数据的交互。
FIFO的设计方法:
在Altera FPGA中使用FIFO实现用户功能设计主要有三种实现方式,第一种为用户根据需求自己编写FIFO逻辑,当用户对于FIFO的功能有特殊需求时,可以使用此种方式实现,但此种方式要求用户有较高的RTL设计能力。第二种方式为使用第三方提供的开源IP核,此种IP核以源码的形式提供,能够快速的应用到用户系统中,当用户对FIFO功能有特殊需求时,可以在此源码的基础上进行修改,以适应自己的系统需求。第三种方式为使用Quartus II软件提供的免费FIFO IP核,此种方式下,Quartus II软件为用户提供了友好的图形化界面方便用户对FIFO的各种参数和结构进行配置,生成的FIFO IP核针对Altera不同系列的器件,还可以实现结构上的优化。该FIFO IP核也是通过Verilog语言进行描述的,在Quartus II13.0软件中,该IP核源码存放于Quartus II软件安装目录quartusedasim_lib下的altera_mf.v文件中的第48189行(scfifo)(dcfifo结构较多,因此代码内容很多,与之相关的代码有几千行,大家可以在文件中搜索dcfifo即可找到)。由于该FIFO IP核已经提供了几乎我们设计所需的所有功能,因此在系统设计中,推荐使用该FIFO IP核进行系统设计。
设计步骤:
新建porject ,打开megawizard plug-in manager,选择FIFO IP核
之后直接next到finish即可。
下面对于IP核的时序接口进行仿真验证:
编写testbench文件
`timescale 1ns/1ps `define clock_peride 20 module fifo_tb; reg clk;//时钟信号接口 reg [15:0] data;//输入数据接口 reg rdreq;//读请求 reg sclr;//同步清零 reg wrreq;//写请求 wire almost_empty;//将空信号 wire almost_full;//将满信号 wire empty;//空信号 wire full;//满信号 wire [15:0] q;//输出接口 wire [7:0] usedw;//可用数据 fifo fifo1 ( .clock(clk), .data(data), .rdreq(rdreq), .sclr(sclr), .wrreq(wrreq), .almost_empty(almost_empty), .almost_full(almost_full), .empty(empty), .full(full), .q(q), .usedw(usedw) ); initial clk=1; always#(`clock_peride/2) clk=~clk; integer i; initial begin wrreq=0; rdreq=0; data=0; #(`clock_peride*20+1); for(i=0;i<=255;i=i+1) //写操作 begin wrreq=1; data=i; #(`clock_peride); end wrreq=0; #(`clock_peride*20); for(i=0;i<=255;i=i+1) //读操作 begin rdreq=1; #(`clock_peride); end $stop; end endmodule
设置仿真文件路径,点击仿真进行前仿,仿真结果如下,可以通过仿真了解各接口的作用及FIFO的读写操作。
下面继续创建一个双时钟FIFO,并进行仿真
之后next 到finish
编写dc_fifo_tb文件对IP核文件进行仿真
`timescale 1ns/1ps `define wrclock_peride 20 `define rdclock_peride 10 module dc_fifo_tb; reg [15:0] data; //输入数据 reg rdclk; //读时钟 reg rdreq; //读请求 reg wrclk; //写时钟 reg wrreq; //写请求 wire [7:0] q; //输出开口 wire rdempty; //读空 wire wrfull; //写满 dc_fifo dc_fifo1 ( .data(data), .rdclk(rdclk), .rdreq(rdreq), .wrclk(wrclk), .wrreq(wrreq), .q(q), .rdempty(rdempty), .wrfull(wrfull) ); initial wrclk = 1; always #(`wrclock_peride/2) wrclk = ~wrclk; initial rdclk = 1; always #(`rdclock_peride/2) rdclk = ~rdclk; integer i; initial begin data=0; rdreq=0; wrreq=0; #(`wrclock_peride*20+1) for (i=0;i <= 255 ;i = i + 1)begin wrreq = 1; data = i + 1024; #`wrclock_peride; end wrreq = 0; #(`rdclock_peride*20); for (i=0;i <= 511 ;i = i + 1)begin rdreq = 1; #(`rdclock_peride); end rdreq = 0; #(`rdclock_peride*20); $stop; end endmodule
设置仿真路径,并进行仿真,仿真结果如下图
还可以自己观察仿真波形中的接口的变化规律,这里就不再缀述了。