FIFO_quartus

FIFO( First In First Out,即先入先出),是一种数据缓存器,常被用于多比特数据跨时钟域的转换、读写数据带宽不同步等场合。

一、FIFO IP 核简介

FIFO 分为同步 FIFO 和异步 FIFO。

同步 FIFO 是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作,常用于两边数据处理带宽不一致的临时缓冲。

异步FIFO 是指读写时钟不一致,读写时钟是互相独立的,一般用于数据信号跨时钟阈处理。

常见参数:

1、 FIFO 的宽度: FIFO 一次读写操作的数据位宽 N。

2、 FIFO 的深度: FIFO 可以存储多少个宽度为 N 位的数据。

3、 将空标志: almost_empty, FIFO 即将被读空。

4、 空标志: empty, FIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从FIFO 中读出数据而造成无效数据的读出。

5、 将满标志: almost_full, FIFO 即将被写满。

6、 满标志: full, FIFO 已满时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的写操作继续向FIFO 中写数据而造成溢出。

7、写时钟: 写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。

8、 读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。

9、可配置满阈值:影响可配置满信号于何时有效,其可配置范围一般为 3~写深度-3。

10、可配置满信号: prog_full,表示 FIFO 中存储的数据量达到可配置满阈值中配置的数值。

11、可配置空阈值:影响可配置空信号于何时有效,其可配置范围一般为 2~读深度-3。

12、可配置空信号: prog_empty,表示 FIFO 中剩余的数据量已经减少到可配置空阈值中配置的数值。

还有两点需要注意:

1、 “ almost_empty” 和“ almost_full” 这两个信号分别被看作“ empty” 和“ full” 的警告信号,他们相对于真正的空( empty)和满( full)都会提前一个时钟周期拉高。

2、 FIFO 中,先写入的数据被置于高位,后写入的数据被置于低位,由于其先入先出的特性,所以读出的数据也是高位在前,低位在后。这一点在读写数据位宽不对等时尤为重要,例如我们写数据位宽为 8,读数据位宽为 2,当写入的数据为 11000111 时,读出的数据依次为 11、 00、 01、 11,如下图所示:

写位宽大于读位宽时的数据排序

读位宽大于写位宽时,原理是相同的,所以我们就不再赘述了,其示意图如下:

读位宽大于写位宽时的数据排序

在 Quartus II 软件中用于实现 FIFO 设计的 FIFO IP 核( FIFO 发生器),其信号框图如下图所示:

单时钟 FIFO 与双时钟 FIFO 的符号图

FIFO 从输入时钟的角度来分,有两种类型:单时钟 FIFO( SCFIFO)和双时钟 FIFO( DCFIFO),其中双时钟 FIFO 又可从输出数据的位宽的角度分为普通双时钟( DCFIFO)和混合宽度双时钟 FIFO ( DCFIFO_MIXED_WIDTHS)。

单时钟 FIFO 和双时钟 FIFO 的符号图如上图所示。

单时钟 FIFO 具有一个独立的时钟端口 clock,因此所有的输入输出信号都同步于 clock 信号。

而在双时钟FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk,所有与读相关的信号都是同步于读时钟 rdclk。

在双时钟 FIFO 的符号图中,位于图中上侧部分的以“ data”和“ wr”开头的信号为与写相关的所有信号,位于中间部分的“ q”和以“ rd”开头的信号为与读相关的所有信号,位于底部的为异步清零信号。

对于 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 常用于跨时钟域的数据信号的传递,例如时钟域 A 下的数据 data1 传递给异步时钟域 B,当 data1 为连续变化信号时,如果直接传递给时钟域 B 则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题, 使用双时钟 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中。

二、任务

使用 Quartus II 生成一个异步 FIFO,并实现以下功能:当 FIFO 为空时,向 FIFO 中写入数据, 直至将 FIFO 写满后停止写操作;当 FIFO 为满时, 从 FIFO 中读出数据,直到 FIFO 被读空后停止读操作。

三、程序设计

3.1 FIFO IP 核讲解

3.1.1 FIFO IP 核配置

创建一个名为 ip_fifo 的工程;

下来我们创建 FIFO IP 核。

在 Quartus II 软件的菜单栏中找到【 IP Catalog】→在搜索框中输入“ fifo”。

在该页面中,在 On Chip Memory 下找到 FIFO,双击选中它,然后为 FIFO IP 核选择保存的路径及名称。

首先在工程所在路径 prj 文件夹下创建一个文件夹 ipcore,由于本次实验会用到多个 IP 核,为了方便管理 IP 核,在 ipcore 文件夹下创建一个 fifo 文件夹,用于存放 FIFO IP 核。

选择 FIFO IP 核页面

 

然后在“IP variation file name:”一栏中输入 IP 存放的路径及名称, 命名为 async_fifo 并且选择创建的 IP 核代码为 Verilog HDL。完成这些设置以后,点击“OK”,

FIFO IP 核模式配置页面

箭头 1 指向的位置用来设置 FIFO 的位宽,选择 8bits,

箭头 2 指向的位置用来设置 FIFO 的深度,也就是能存放多少个指定位宽的数据,选256words, 这样设置以后 FIFO 的容量大小为256 个 8bits。

箭头 3 和箭头 4 所指向的位置用于设置 FIFO 的驱动时钟类型,箭头 3 处用于选择单时钟FIFO,箭头 4 处用于选择双时钟 FIFO。

当选择双时钟 FIFO 时,箭头 5 所指向的位置可以选择不同的输出位宽。

箭头 6 处所指的即界面左下角是资源使用情况,可以对 FIFO 消耗的 FPGA 资源有个大概的了解。

这里我们选择双时钟 FIFO,采用默认方式输出数据与输入数据等宽,如下图所示:

选择双时钟 FIFO

下面开始进行 FIFO 参数的配置。

我们需要先选则框中的“ No” 将其设置为 DCFIFO 后才能够继续配置其他参数。 选择完之后,我们直接点击【 Next>】,进入如下所示页面: 

DCFIFO1 配置页面

 

从该页面的“ Which type of optimization do you want?”可以看出,该页面主要是用于DCFIFO 进行优化的,在箭头 1、 2、 3 所指处有三种针对读时钟同步、亚稳态保护、面积和速度的优化类型,下面我们简单的介绍一下这三种优化类型:

Lowest latency but requires synchronized clocks(最低延迟,但要求同步时钟) :此选项使用一个同步阶段,没有亚稳态保护,适用于同步时钟。它是最小尺寸,提供良好的 Fmax。

Minimal setting for unsynchronized clocks(异步时钟时的最小设置) :这个选项使用两个同步阶段,具有良好的亚稳态保护。它是中等尺寸,提供良好的 Fmax。

Best metastability protection, best fmax and unsynchronized clocks(异步时钟时最好的亚稳态保护,最好的 Fmax,不同步) :这个选项使用三个或更多的同步阶段,具有最好的亚稳态保护。它是最大尺寸,给出了最好的 Fmax。

在使用过程中,通常选择的是默认的中等优化,具体的优化主要还是看工程的要求,假如工程对速度和稳定性要求很高,同时资源又很多,那么就可以使用第三个选项,假如工程资源很紧张,那么可以选择使用第一个资源少的优化。

在此我们保持默认的设置,直接点击【 Next>】,进入如下所示页面:

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>】,进入如下所示页面:

Rdreq Option,Blk Type 的配置页面

该页面用于选择输出模式和存储器类型。

最上面的红框选择输出模式,输出模式有两种:正常模式和前显模式。

对于正常模式, FIFO 将端口 rdreq 看做正常的读请求并在该端口信号为高电平进行读操作。

对于前显模式, FIFO 将端口 rdreq 看做读确认信号,将 rdreq 信号置为高电平时将输出 FIFO 中的下一个数据字(如果存在)。

如果使用前显模式,将会使设计性能下降。这里我们使用默认值:正常模式。

接下来的红框用于指定实现存储器使用的存储块类型和存储器的存储深度,具体可选值与使用的FPGA 芯片有关,默认为 Auto,我们一般使用默认值就可以了, 所以这里直接点击【 Next>】,进入如下所示页面:

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>】,进入如下所示页面:

第一个输出时钟 c0 配置页面

点击【 Next>】,进入如下所示页面:

Summary 的配置页面

 

在该页面,可以看到,该 IP 核能生成的所有文件都在该页面中,在这么多的文件中,我们只要选择 FIFO_inst.v 文件就可以了,方便对 FIFO 的例化。

至此,关于 FIFO IP 核的配置就讲解完了。

然后我们点击【 Finish】完成整个 IP 核的创建。接下来 Quartus II 软件会在 ipcore 文件夹下创建 FIFO 的 IP 核文件,然后询问我们是否添加至工程,点击“ YES”按钮将生成的 IP 核添加至工程,如下图所示页面。

IP 核添加至工程确认界面

接下来返回到工程界面,在 File 界面里,我们可以看到生成的 fifo.qip 和 fifo.v 已经添加到工程中, 如下所示界面:

FIFO 添加至工程界面

至此, FIFO IP 核的创建已经全部完成。

3.2 顶层模块设计

本次实验的目的是为了将 FIFO IP 核配置成一个异步 FIFO 并对其进行读写操作,因此可以给模块命名为 ip_fifo;

因为我们做的是异步 FIFO,所以我们需要一个 PLL IP 核来输出 50MHz 的写时钟和 100MHz 的读时钟,当然输出其它频率的时钟也是可以的;

然后我们还需要一个写模块( fifo_wr)和一个读模块( fifo_rd),写模块通过 FIFO 的状态来判断是否给出写请求信号和写数据,读模块通过 FIFO 的状态来判断是否给出读请求信号,并接收从 FIFO 中读出的数据;

系统时钟和系统复位是一个完整的工程中必不可少的输入端口信号,这里就不再多讲了。

模块框图与端口

模块端口与功能描述如下表所示:

信号名位宽方向端口说明
sys_clk1输入系统时钟, 50MHz
sys_rst_n1输入系统复位按键,低电平有效

3.2.1 代码编写

因为是使用 FIFO Generator IP 核来生成一个异步 FIFO,所以我们需要使用到 PLL IP 核来输出两路不同频率的时钟,除此之外我们还需要一个读模块( fifo_rd)和一个写模块( fifo_wr)来进行异步的读写操作,所以我们需要创建一个顶层模块来例化两个 IP 核与读/写模块, 这里我们将顶层模块命名为ip_fifo, 代码如下:

module ip_fifo(
    input sys_clk , //时钟信号
    input sys_rst_n //复位信号
);

//wire define
wire clk_50m ; //50Mhz 时钟
wire clk_100m; //100Mhz 时钟
wire locked ; //时钟稳定信号

wire rst_n ; //复位信号

wire [7:0] rd_usedw; //读侧 FIFO 中的数据量
wire [7:0] wr_usedw; //写侧 FIFO 中的数据量

wire wr_full ; //写侧满信号
wire wr_empty; //写侧空信号
wire wr_req ; //写请求信号
wire [7:0] wr_data ; //写入 FIFO 的数据

wire rd_full ; //读侧满信号
wire rd_empty; //读侧空信号
wire rd_req ; //读请求信号
wire [7:0] rd_data ; //读出 FIFO 的数据

//*****************************************************
//** main code
//*****************************************************

//待时钟输出稳定后,再拉高 rst_n 信号
assign rst_n = sys_rst_n & locked;

//例化锁相环模块
pll_clk u_pll_clk (
    .areset (~sys_rst_n ),
    .inclk0 (sys_clk ),
    .c0 (clk_50m ),
    .c1 (clk_100m ),
    .locked (locked )
);

//例化 FIFO 写模块
fifo_wr u_fifo_wr(
    .clk (clk_50m),
    .rst_n (rst_n),

    .wr_full (wr_full ),
    .wr_empty (wr_empty),
    .wr_req (wr_req ),
    .wr_data (wr_data )
);

//例化异步 FIFO 模块
async_fifo u_async_fifo (
    .aclr (~rst_n ),
    .data (wr_data ),
    .rdclk (clk_100m ),
    .rdreq (rd_req ),
    .wrclk (clk_50m ),
    .wrreq (wr_req ),
    .q (rd_data ),
    .rdempty (rd_empty ),
    .rdfull (rd_full ),
    .rdusedw (rd_usedw ),
    .wrempty (wr_empty ),
    .wrfull (wr_full ),
    .wrusedw (wr_usedw )
);

//例化 FIFO 读模块
fifo_rd u_fifo_rd(
    .clk (clk_100m),
    .rst_n (rst_n),

    .rd_full (rd_full ),
    .rd_empty (rd_empty),
    .rd_req (rd_req ),
    .rd_data (rd_data )
);

endmodule

可以看出 ip_fifo 顶层模块只是例化了 FIFO IP 核( async_fifo)、 PLL IP 核( pll_clk)、读模块( fifo_rd)和写模块( fifo_wr),

其中写模块负责产生 FIFO IP 核写操作所需的所有数据、写请求等信号;

读模块负责产生 FIFO IP 核读操作所需读请求信号,并将读出的数据也连接至读模块。

因为读写模块的时钟皆来自 PLL IP 核,而 PLL IP 核需要一定的时间才能输出稳定的时钟,所以在第31 行代码中通过系统复位和时钟锁定来产生一个信号复位信号,使读/写模块及 FIFO IP 核在时钟稳定后才进入工作状态。

3.3 FIFO 写模块设计

首先介绍下 FIFO 写模块的设计, 在 FIFO 写模块中, 我们的输入信号主要有系统时钟信号(写时钟域的时钟)、 系统复位信号;实验任务中我们提到了 FIFO 为空时进行写操作,因此还需要引入一个空相关的信号,这里我们引入的是 wr_empty(空)信号;

实验任务中我们还提到了写满了要停止写操作,所以这里我们引入了 wr_full(写满)信号,

因为写满信号表示 FIFO 进行了最后一次写操作,使用这个信号的话我们正好可以在写入最后一次数据后关闭写使能。

输出信号有控制写 FIFO 所需的 fifo_wr_en( 写端口使能) 和 fifo_wr_data(写数据)这两个信号。

由上述分析我们绘制出如下图所示的模块框图:

FIFO 写模块框图

模块端口与功能描述如下表所示:

信号名位宽方向端口说明
wr_clk1输入系统时钟, 50MHz
rst_n1输入系统复位按键,低电平有效
wr_full1输入写满信号, 高有效, 表示FIFO中已经写满了
wr_empty1输入写空信号,高有效,表示在FIFO中已经没有数据了。
wr_req1输出FIFO写端口使能信号,高有效
wr_data8输出FIFO写数据


绘制波形图 

在编写代码前,我们先大致梳理一下模块的端口时序。

因为 wr_empty(写空)是写时钟域下的信号,当检测到 empty 信号为高时,说明 FIFO 为空,此时我们可以开启写使能并向 FIFO 中写入数据,

直到检测到 wr_full(写满)信号拉高时,将写使能关闭。

综上,我们绘制出如下所示的波形图:

标题FIFO 写模块波形图

由上图可知,

当系统复位结束后,因为 wr_empty(空)信号对于写操作来说属于同步信号, 所以不需要同步,但这里我们对 wr_empty 信号进行流水,使时序更好。

代码编写

module fifo_wr(
    input clk, //时钟信号
    input rst_n, //复位信号

    //fifo 的写端口
    input wr_full, //写侧满信号
    input wr_empty, //写侧空信号
    output wr_req, //写请求信号
    output reg [7:0] wr_data //写入 FIFO 的数据
);

//reg define
reg wr_req_t;
reg wr_empty_d0;
reg wr_empty_d1;

//*****************************************************
//** main code
//*****************************************************

//防止 fifo 写满后继续写入数据
assign wr_req = wr_req_t & (~wr_full);


//为了时序更好, 所以对输入信号打两拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        wr_empty_d0 <= 1'b0;
        wr_empty_d1 <= 1'b0;
    end
    else begin
        wr_empty_d0 <= wr_empty;
        wr_empty_d1 <= wr_empty_d0;
    end
end


//wr_req_t 信号赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        wr_req_t <= 1'b0;
    else    if(wr_empty_d1)
                wr_req_t <= 1'b1;
            else    if(wr_full)
                        wr_req_t <= 1'b0;
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

 写 fifo 模块主要完成向 fifo 中写入数据的功能,当 fifo 为空时,向 fifo 中写入数据;当 fifo 写满之后,停止写入数据,然后重新判断 fifo 是否为空。

3.4 FIFO 读模块设计

首先介绍下 FIFO 读模块的设计,在 FIFO 读模块中,我们的输入信号主要有系统时钟信号(读时钟域时钟) 和系统复位信号;

因为 FIFO 的读操作需要在 FIFO 完成复位后进行;

实验任务中我们提到了 FIFO为满时进行读操作,因此还需要引入一个满相关的信号,这里我们引入的是 rd_full( 读满)信号;

实验任务中我们还提到了读空了要停止读操作,所以这里我们引入了 rd_empty( 读空)信号,因为读空信号表示FIFO 还能最后一次读操作,使用这个信号的话我们正好可以在读出最后一个数据后关闭读使能。

输出信号仅有控制写 FIFO 所需的 fifo_rd_en(读端口使能)信号。

由上述分析我们绘制出如下图所示的模块框图:

FIFO 读模块框图

信号名位宽方向端口说明
rd_clk1输入系统时钟, 50MHz
rst_n1输入系统复位按键,低电平有效
rd_data8输入FIFO读数据
rd_empty1输入读空信号,高有效,表示在FIFO数据已经被读空。
rd_full1输入满信号,高有效,表示在FIFO已被写满。
rd_req1输出FIFO读端口使能信号,高有效


绘制波形图 

在编写代码前,我们先大致梳理一下模块的端口时序。

因为 rd_full(满)是读时钟域下的同步信号,当检测到打拍后的 full 信号为高时,说明 FIFO 为满,此时我们可以开启读使能并读出 FIFO 中存储的数据,直到检测到 rd_empty(读空)信号拉高时,将写使能关闭。

综上,我们绘制出如下所示的波形图:

FIFO 读模块波形图

由上图可知,当系统复位结束后, FIFO 还处于复位状态,因为 rd_full(满)信号是 FIFO 读时钟域的信号, 所以不需要同步,但这里我们对 rd_full 信号进行流水,使时序更好。

需要注意的是,当 FIFO 中的数据量小于写深度-1 时,读满信号就会被拉低,但是因为是异步 FIFO,所以状态信号的同步需要一定的时间,因此读空信号是在读出第二个数据后拉低的。

编写代码 

module fifo_rd(
    input clk, //时钟信号
    input rst_n, //复位信号

    //fifo 的读端口
    input rd_full, //读侧满信号
    input rd_empty, //读侧空信号
    output rd_req, //读请求信号
    input [7:0] rd_data //读出 FIFO 的数据
);

//reg define
reg rd_req_t ;
reg rd_full_d0;
reg rd_full_d1;

//*****************************************************
//** main code
//*****************************************************

//防止 fifo 读空后继续读出数据
assign rd_req = rd_req_t & (~rd_empty);


//为了时序更好, 所以对输入信号进行打拍
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        rd_full_d0 <= 1'b0;
        rd_full_d1 <= 1'b0;
    end
    else begin
        rd_full_d0 <= rd_full;
        rd_full_d1 <= rd_full_d0;
    end
end


//rd_req_t 信号赋值
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rd_req_t <= 1'b0;
    else    if(rd_full_d1)
                rd_req_t <= 1'b1;
            else    if(rd_empty)
                        rd_req_t <= 1'b0;
end

endmodule

读 fifo 模块主要完成向 fifo 中读出数据的功能,当 fifo 数据为满的状态时,开始向 fifo 中读出数据;当 fifo 读空之后,停止读数据,然后重新判断 fifo 是否为满的状态。 

3.5 仿真验证

3.5.1 编写 TB 文件

`timescale 1ns/1ns //仿真的单位/仿真的精度

module ip_fifo_tb();

parameter T = 20;

reg sys_clk;
reg sys_rst_n;

initial begin
    sys_clk = 1'b0;
    sys_rst_n = 1'b0;
    #(T+1)
    sys_rst_n = 1'b1;
end

always #(T/2) sys_clk = ~sys_clk;

ip_fifo u_ip_fifo(
    .sys_clk (sys_clk ),
    .sys_rst_n (sys_rst_n)
);

endmodule

仿真验证


 

 


 

 


 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值