FPGA FIFO——IP核


前言

环境:
1、Quartus18.0
2、vscode
3、板子型号:原子哥开拓者2(EP4CE10F17C8)
要求:
实现当 FIFO 为空时就开始向 FIFO 中写入数据,直到 FIFO 写满为止;当 FIFO 为满时则开始从 FIFO 中读出数据,直到 FIFO 读空为止的功能。


一、FIFO

FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存或者高速异步数据的交互,也即所谓的跨时钟域信号传递

1、区别

与RAM、ROM的相比,FIFO没有外部读写地址线,采用的是顺序写入、顺序读出数据的方式,缺点就是不能像RAM、ROM那样由地址线决定读取或写入的地址。

2、分类

  • 单时钟 FIFO:

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

  • 作用:
    单时钟 FIFO 常用于同步时钟的数据缓存
  • 双时钟 FIFO:

从输出数据的位宽的角度分为普通双时钟和混合宽度双时钟。
在双时钟FIFO 结构中,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wrclk,所有与读相关的信号都是同步于读时钟 rdclk。

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

  • 查找FIFO IP:
    在这里插入图片描述
  • 双击过后:
    在这里插入图片描述

选择路径、语言。

  • 选择位宽、深度、时钟:
    在这里插入图片描述
  • 选择中等优化:
    在这里插入图片描述

1、最低延迟,但要求同步时钟: 此选项使用一个同步阶段,没有亚稳态保护,适用于同步时钟。它是最小尺寸,提供良好的 Fmax。
2、异步时钟时的最小设置: 这个选项使用两个同步阶段,具有良好的亚稳态保护。它是中等尺寸,提供良好的 Fmax。
3、异步时钟时最好的亚稳态保护,最好的Fmax,不同步: 这个选项使用三个或更多的同步阶段,具有最好的亚稳态保护。它是最大尺寸,给出了最好的 Fmax。

我们一般会根据工程需求以及资源来选择合适的优化。

扫描二维码关注公众号,回复: 16399464 查看本文章
  • 选择输出控制信号:
    在这里插入图片描述

这里我们选择读空、读满、读侧数据量和写空、写满信号、写侧数据量

  • 选择输出模式和存储器类型:
    在这里插入图片描述

输出模式:
1、正常模式:FIFO 将端口 rdreq 看做正常的读请求并在该端口信号为高电平进行读操作。
2、前显模式:FIFO 将端口 rdreq 看做读确认信号,将 rdreq 信号置为高电平时将输出 FIFO 中的下一个数据字(如果存在)。

  • 上溢检测与下溢检测:
    在这里插入图片描述

这里的上下溢检测默认是打开的,如果不需要的话可以进行勾选来禁止,以此来节省资源,提高FIFO性能。

  • 添加仿真库(默认存在):
    在这里插入图片描述
  • 勾选inst文件:
    在这里插入图片描述
  • 点击finish后点击YES添加到工程:
    在这里插入图片描述
  • 添加成功效果:
    在这里插入图片描述

四、源码

1、fifo_wr(写模块)

module fifo_wr(
    input               clk     ,
    input               rst_n   ,
    input               wrempty ,//写空信号
    input               wrfull  ,//写满信号

    output reg [7:0]    data    ,//写数据
    output reg          wrreq    //写请求
);
reg [1:0] flow_cnt;//状态计数器
/***************
写 fifo 模块主要完成向 fifo 中写入数据的功能,当 fifo 为空时,向 fifo 中写入数据;
当 fifo 写满之后,停止写入数据,然后重新判断 fifo 是否为空。
***************/
//写数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        wrreq <= 1'b0;
        data  <= 8'd0;
        flow_cnt <= 2'd0;
    end
    else begin
        case(flow_cnt)
        2'd0: begin
            if(wrempty) begin//写空间为空,写请求使能,进入下一个状态
                wrreq <= 1'b1;
                flow_cnt <= flow_cnt + 1'b1;
            end
            else
                flow_cnt <= flow_cnt;//其实最多就计到1,其余状态不变,在写满后置0
        end
        2'd1: begin
            if(wrfull) begin
                wrreq <= 1'b0;
                data  <= 8'd0;
                flow_cnt <= 2'd0;
            end
            else begin
                wrreq <= 1'b1;//保持写请求为有效状态
                data  <= data + 1'd1;//写数据加1
            end
        end
        default: flow_cnt <= 2'd0;
        endcase
    end
end
endmodule

2、fifo_rd(读模块)

module fifo_rd(
    input           clk         ,
    input           rst_n       ,
    input [7:0]     data        ,
    input           rdfull      ,
    input           rdempty     ,

    output reg rdreq
);

reg [7:0] data_fifo;//读取的数据
reg [1:0] flow_cnt;//状态计数器
/***************
读 fifo 模块主要完成向 fifo 中读出数据的功能,当 fifo 数据为满的状态时,
开始向 fifo 中读出数据;当 fifo 读空之后,停止读数据,然后重新判断 fifo 是否为满的状态。
***************/
//读取数据
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) begin
        rdreq <= 1'b0;
        data_fifo <= 8'd0;
    end
    else begin
        case(flow_cnt)
            2'd0: begin
                if(rdfull) begin//与写不同,读是满的时候才使能
                    rdreq <= 1'b1;
                    flow_cnt <= flow_cnt + 1'b1;
                end
                else
                    flow_cnt <= flow_cnt;
            end
            2'd1: begin
                if(rdempty) begin
                    rdreq <= 1'b0;
                    data_fifo <= 8'd0;
                    flow_cnt  <= 2'd0;
                end
                else begin
                    rdreq <= 1'b1;
                    data_fifo <= data;
                end
            end
            default: flow_cnt <= 2'd0;
        endcase
    end
end
endmodule

3、ip_fifo(顶层文件)

module ip_fifo(
    input sys_clk ,
    input sys_rst_n
);
//写数据
wire wrreq ;
wire [7:0] data ;
wire wrempty ;
wire wrfull ;
wire wrusedw;//写侧FIFO中的数据量
//读数据
wire rdreq ;
wire [7:0] q ;//FIFO读出的数据
wire rdempty ;
wire rdfull ;
wire rdusedw;//读侧FIFO中的数据量
//fifo IP核
fifo fifo_inst(
    .wrclk ( sys_clk ), // 写时钟
    .wrreq ( wrreq ), // 写请求
    .data ( data ), // 写入 FIFO 的数据
    .wrempty ( wrempty ), // 写空信号
    .wrfull ( wrfull ), // 写满信号
    .wrusedw ( wrusedw ), // 写侧数据量

    .rdclk ( sys_clk ), // 读时钟
    .rdreq ( rdreq ), // 读请求
    .q ( q ), // 从 FIFO 输出的数据
    .rdempty ( rdempty ), // 读空信号
    .rdfull ( rdfull ), // 读满信号
    .rdusedw ( rdusedw ) // 读侧数据量
);
//写模块
fifo_wr fifo_wr_inst(
    .clk       (sys_clk),
    .rst_n     (sys_rst_n),
    .wrempty   (wrempty),//写空信号
    .wrfull    (wrfull),//写满信号

    .data      (data),//写数据
    .wrreq     (wrreq) //写请求
);
//读模块
fifo_rd fifo_rd_inst(
    .clk         (sys_clk),
    .rst_n       (sys_rst_n),
    .data        (q),
    .rdfull      (rdfull),
    .rdempty     (rdempty),

    .rdreq       (rdreq)
);
endmodule

五、仿真

1、仿真文件

`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;
    #(3000*4)
    $stop;
end

always #(T) sys_clk = ~sys_clk;

ip_fifo ip_fifo_inst(
    .sys_clk     (sys_clk),
    .sys_rst_n   (sys_rst_n)
 );
endmodule

2、波形分析

  • 说明:

这里为了我们理解,使用的是同步FIFO,即读写使用的都是系统时钟。意味着读和写操作不能同时进行。只有在写操作完成后,才能进行读操作。

当有数据写入FIFO时,写操作将会阻塞,直到有读操作将数据读取走。同样地,当FIFO已满时,写操作也会阻塞,直到有读操作将数据读取走腾出空间。这确保了数据的顺序性:先进先出。

在这里插入图片描述
在这里插入图片描述

  • 波形分析:

在波形写满过后,且经过三个时钟周期后,读满信号 rdfull 才有效,并且在读请求信号rereq 拉高后的第 3 个时钟周期写满信号 wrfull 才拉低。写完才进行的读,且是从0开始读的,符合先进先出。


六、SignalTap II在线验证

  • 读状态:
    在这里插入图片描述
  • 分析:

在写满过后,写使能置0,无法进行写入,隔三个时钟周期读满信号有效,读使能有效,从0开始读出数据,我们开始写入也是从0开始的,符合先进先出。

  • 写状态:
    在这里插入图片描述
  • 分析:

在读空三个周期过后,写使能有效,开始从0进行写入操作。因为这里使用的是同一时钟,所以在写的时候会堵塞,写满后才能读取。

七、总结

写到这一篇IP核的系列文章就结束了,还有许多的IP核等待我们的学习,只是这几个是比较常用的先进行一个学习。与RAM不同,FIFO是先进先出,不能随意对某一个地址进行读写,只能按照顺序读写。

八、参考资料

以上资料均来自正点原子的教学视频或开拓者2开发教程:原子官方

猜你喜欢

转载自blog.csdn.net/qq_52215423/article/details/131805974