[米联客-XILINX-H3_CZ08_7100] FPGA程序设计基础实验连载-26浅谈XILINX FIFO的基本使用

软件版本:VIVADO2021.1

操作系统:WIN10 64bit

硬件平台:适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA

实验平台:米联客-MLK-H3-CZ08-7100开发板

板卡获取平台:https://milianke.tmall.com/

登录"米联客"FPGA社区 http://www.uisrc.com 视频课程、答疑解惑!

目录

1概述

[2配置FIFO IP](#2配置FIFO IP)

3半空/半满法控制读写FIFO

3.1测试代码程序

[3.2 RTL仿真](#3.2 RTL仿真)

3.2.1仿真激励文件

3.2.2仿真结果

4关键信号法

[4.1 almost_full 和almost_empty](#4.1 almost_full 和almost_empty)

4.1.1测试代码

[4.1.2 RTL仿真结果](#4.1.2 RTL仿真结果)

[4.2 almost_full和valid](#4.2 almost_full和valid)

4.2.1测试代码

[4.2.2 RTL仿真结果](#4.2.2 RTL仿真结果)


1概述

首先来大概了解下什么是FIFO ,FIFO( First Input First Output)简单说就是指先进先出。FIFO也是缓存机制的一种,下面是我总结的FIFO的三大用途:

1)、提高传输效率,增加DDR带宽的利用率。比如我们有4路视频数据缓存到DDR中去,比较笨的方法是,每个通道视频数据对应一颗DDR。现在对于DDR来说非常浪费,因为现在的DDR3可以跑1600Mbps DDR4可以跑到2400Mbps,如果你还是把一路视频数据对应一颗DDR显然严重浪费了带宽。加入FIFO后,只要把4路数据先缓存进入DDR,在缓存的过程中,快速得把数据从FIFO取出并且写入到DDR中,只要FIFO没有满就不会出现数据丢失。现在我们带宽够用,FIFO给的足够大就可以确保数据不丢失。

2)、数据位宽转换,比如我们有32bit的数据需要转换成128bit或者32bit的数据需要转换成8bit,那么用FIFO来转换也是非常方便的。

3)、跨时钟域的应用,比如数据是2个不同步的时钟,那么我们就可以用FIFO实现跨时钟域的传输。

以上总计的三点,很多时候是混合使用的。FIFO的用途非常大,我们在后面的例子中也看到,只要涉及到DDR传输的都和FIFO有关系。

我们这里的例子通过仿真告诉大家FIFO的基本用法,有两条我总结的办法,包括:

1)半空半满法

2)关键信号法

2配置FIFO IP

点击软件左侧的IP Catalog

输入关键词fifo,会出来非常多的FIFO类型

1)、AXI4-Stream FIFO内核旨在提供对与其他IP连接的AXI4-Stream接口(例如AXI以太网内核)的内存映射访问。 必须通过Vivado Design Suite构建系统,以连接AXI4-Stream FIFO内核,AXI以太网内核,处理器,内存,互连总线,时钟和其他嵌入式组件。

2)、AXI4-Stream Data FIFO 支持AXI4-Stream协议,具备packet包传输模式。

3)、AXI Data FIFO 就是数据FIFO 功能较为单一,接口为Stream接口

4)、FIFO Generator 支持Native 模式,AXI Memory Mapped模式 AXI Steam模式功能比较齐全,在没有AXI4或者AXI Stream协议的场合下,我们更多使用Native模式,这里的课程也以Native模式讲解。

使用Block RAM,BlockRAM是FPGA内部 集成的重要内存单元,速度高资源有限,所以得充分合理利用。时钟模式采用异步方式,也就是这里选择Independent Clocks,可以把IP名字改为FIFO32_2_128

选择First Word Fall Through 这样写入的数据,会先在读端口准备好,否则如果选择Standard FIFO需要读使能后一个时钟输出才有效。

观察almost full 和almost empty flag 这两个信号是可编程的,一些应用场景也是可以用到。

设置读计数器和写计数器,这不是必须的,我们第一个半空半满方法需要用到。

3半空/半满法控制读写FIFO

半空/半满法,功法要点:半空是针对读FIFO计数器而言,半满是针对写FIFO计数器而言;这里强调一点,FIFO的计数器并不精准,计数器会有几个时钟的延迟,所以这里的半空,半满的计数器大部分时候都是不精确的,除非你把程序停下来等几个时钟周期,显然这样不科学,会降低程序的效率。虽然不精确但是完全够用。因为读写FIFO一直在一个动态平衡中。

3 .1测试代码程序

比如,我们这里的FIFO输入32bit 深度1024;输出128bit 深度256,这里的半空值就是128,半满值就是512。下面设计我们的状态机:

1)、状态0:当写入FIFO计数器小于512 则进入状态1

2)、状态1:当连续写入FIFO 512个数据后,再次进入状态0等待

读状态机的设计,每次读出128bt数据:

1)、状态0:当读FIFO计数器大于128 则进入状态1

2)、状态1:连续读出FIFO 128个数据后,再次进入状态0等待

fifo_test.v

cpp 复制代码
/*************FIFO IP的仿真测试***************************************
--版本号1.0
--FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
--通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
--写状态机和读状态机分开运行
*********************************************************************/
`timescale 1ns / 1ns //仿真时间刻度/精度

module fifo_test1
(
input I_sysclk_p,
input I_sysclk_n, //系统时钟输入
input I_rstn     //系统复位
);

wire I_clk;
IBUFGDS CLK_U(
.I(I_sysclk_p),
.IB(I_sysclk_n),
.O(I_clk)
);
wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
wire [127:0]rd_data; //读数据信号
wire fifo_rst;       //fifo 复位,高电平有效
wire full;           //FIFO满,这里没用到
wire empty;          //FIFO空,这里没用到
wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满,这里没用到
wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空,这里没用到
wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数

//写状态机信号
reg WR_REQ = 1'b0;   //写请求信号
reg [0 :0]WR_S;      //写状态机
reg [10:0]wr_cnt;    //写数据计数器
reg wr_en;           //写使能寄存器

// 读状态机
reg RD_REQ = 1'b0;  //读请求信号
reg [0:0]RD_S;      //读状态机
reg [7:0]rd_cnt;    //读数据计数器
reg rd_en;          //读使能寄存器

reg[9:0] rst_cnt = 10'd0; //复位计数器

assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位
//MMCM/PLL 产生200M和100M时钟
clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

//复位计数器模块
always @(posedge clk_100m)begin
    if(!clk_locked)
        rst_cnt <= 10'd0;
    else if(rst_cnt[9] == 1'b0)
        rst_cnt <= rst_cnt + 1'b1;
end

// FIFO写状态机
always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
    if(!rst_cnt[9])begin //复位,重置相关寄存器
        WR_S   <= 1'b0;
        wr_cnt <= 11'd0;
        wr_en  <= 1'b0;
    end
    else begin
        case(WR_S) //状态机
        0:begin
            wr_cnt <= 11'd0;
            if(WR_REQ) //当WR_REQ信号有效,代表了FIFO已经可以写入数据
                WR_S <= 1'b1;//进入下一状态
        end
        1:begin
            if(wr_cnt < 512)begin //如果写入的数据小于512
                wr_en  <= 1'b1; //设置写使能
                wr_cnt <= wr_cnt+1'b1;//写计数器累加
            end
            else begin //否则,重置使能,并且回到状态0
                wr_en <= 1'b0;
                WR_S  <= 1'b0;
            end
        end
        endcase
    end
end

always @(posedge clk_100m)begin//读使用100M时钟
    if(!rst_cnt[9])begin //复位,重置相关寄存器
        RD_S <= 1'b0;
        rd_cnt <= 8'd0;
        rd_en <= 1'b0;
    end
    else begin
        case(RD_S)//读状态机
        0:begin
            rd_cnt <= 8'd0;
            if(RD_REQ) //RD_REQ代表FIFO中有足够的数据
                RD_S <= 1'b1;//下一状态
        end
        1:begin
            if(rd_cnt < 128)begin //判断FIFO中读部分的数据,已经读的数量是否小于128个128bits
                rd_en  <= 1'b1;   //使能读信号
                rd_cnt <= rd_cnt+1'b1;//每读一个数据,累加1
            end
            else begin //否则重置读使能,状态机回到0
                rd_en <= 1'b0;
                RD_S <= 1'b0;
            end
        end
        endcase
    end
end

//判断写FIFO中是否有足够的空间存放下一次写的数据
always @(posedge clk_200m)begin
    WR_REQ <= (wr_data_count < 10'd511);
end

//判断读FIFO中是否有足够的数据可以被读出
always @(posedge clk_100m)begin
    RD_REQ <= (rd_data_count > 8'd127);
end

FIFO32_2_128 FIFO32_2_128_inst0 (
  .rst(fifo_rst),                  //FIFO 复位,高电平有效
  .wr_clk(clk_200m),               //FIFO 写时钟输入
  .rd_clk(clk_100m),               //FIFO 读时钟输入
  .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  .wr_en(wr_en),                   //FIFO 写数据使能
  .rd_en(rd_en),                   //FIFO 读数据使能
  .dout(rd_data),                  //FIFO 读数据输出
  .full(full),                     //FIFO 写通道满,该信号这里没使用
  .almost_full(almost_full),       //FIFO 写通道将满,该信号这里没使用
  .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  .almost_empty(almost_empty),     //FIFO 读通道将空,该信号这里没使用
  .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
);
endmodule

以上代码中关键的控制状态机写FIFO读FIFO的代码如下:

cpp 复制代码
/*************FIFO IP的仿真测试***************************************
--版本号1.0
--FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
--通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
--写状态机和读状态机分开运行
*********************************************************************/
`timescale 1ns / 1ns //仿真时间刻度/精度

module fifo_test1
(
input I_sysclk_p,
input I_sysclk_n, //系统时钟输入
input I_rstn     //系统复位
);

wire I_clk;
IBUFGDS CLK_U(
.I(I_sysclk_p),
.IB(I_sysclk_n),
.O(I_clk)
);
wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
wire [127:0]rd_data; //读数据信号
wire fifo_rst;       //fifo 复位,高电平有效
wire full;           //FIFO满,这里没用到
wire empty;          //FIFO空,这里没用到
wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满,这里没用到
wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空,这里没用到
wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数

//写状态机信号
reg WR_REQ = 1'b0;   //写请求信号
reg [0 :0]WR_S;      //写状态机
reg [10:0]wr_cnt;    //写数据计数器
reg wr_en;           //写使能寄存器

// 读状态机
reg RD_REQ = 1'b0;  //读请求信号
reg [0:0]RD_S;      //读状态机
reg [7:0]rd_cnt;    //读数据计数器
reg rd_en;          //读使能寄存器

reg[9:0] rst_cnt = 10'd0; //复位计数器

assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位
//MMCM/PLL 产生200M和100M时钟
clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

//复位计数器模块
always @(posedge clk_100m)begin
    if(!clk_locked)
        rst_cnt <= 10'd0;
    else if(rst_cnt[9] == 1'b0)
        rst_cnt <= rst_cnt + 1'b1;
end

// FIFO写状态机
always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
    if(!rst_cnt[9])begin //复位,重置相关寄存器
        WR_S   <= 1'b0;
        wr_cnt <= 11'd0;
        wr_en  <= 1'b0;
    end
    else begin
        case(WR_S) //状态机
        0:begin
            wr_cnt <= 11'd0;
            if(WR_REQ) //当WR_REQ信号有效,代表了FIFO已经可以写入数据
                WR_S <= 1'b1;//进入下一状态
        end
        1:begin
            if(wr_cnt < 512)begin //如果写入的数据小于512
                wr_en  <= 1'b1; //设置写使能
                wr_cnt <= wr_cnt+1'b1;//写计数器累加
            end
            else begin //否则,重置使能,并且回到状态0
                wr_en <= 1'b0;
                WR_S  <= 1'b0;
            end
        end
        endcase
    end
end

always @(posedge clk_100m)begin//读使用100M时钟
    if(!rst_cnt[9])begin //复位,重置相关寄存器
        RD_S <= 1'b0;
        rd_cnt <= 8'd0;
        rd_en <= 1'b0;
    end
    else begin
        case(RD_S)//读状态机
        0:begin
            rd_cnt <= 8'd0;
            if(RD_REQ) //RD_REQ代表FIFO中有足够的数据
                RD_S <= 1'b1;//下一状态
        end
        1:begin
            if(rd_cnt < 128)begin //判断FIFO中读部分的数据,已经读的数量是否小于128个128bits
                rd_en  <= 1'b1;   //使能读信号
                rd_cnt <= rd_cnt+1'b1;//每读一个数据,累加1
            end
            else begin //否则重置读使能,状态机回到0
                rd_en <= 1'b0;
                RD_S <= 1'b0;
            end
        end
        endcase
    end
end

//判断写FIFO中是否有足够的空间存放下一次写的数据
always @(posedge clk_200m)begin
    WR_REQ <= (wr_data_count < 10'd511);
end

//判断读FIFO中是否有足够的数据可以被读出
always @(posedge clk_100m)begin
    RD_REQ <= (rd_data_count > 8'd127);
end

FIFO32_2_128 FIFO32_2_128_inst0 (
  .rst(fifo_rst),                  //FIFO 复位,高电平有效
  .wr_clk(clk_200m),               //FIFO 写时钟输入
  .rd_clk(clk_100m),               //FIFO 读时钟输入
  .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  .wr_en(wr_en),                   //FIFO 写数据使能
  .rd_en(rd_en),                   //FIFO 读数据使能
  .dout(rd_data),                  //FIFO 读数据输出
  .full(full),                     //FIFO 写通道满,该信号这里没使用
  .almost_full(almost_full),       //FIFO 写通道将满,该信号这里没使用
  .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  .almost_empty(almost_empty),     //FIFO 读通道将空,该信号这里没使用
  .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
);
endmodule

对于初学者一定要注意,FIFO复位需要经过多个时钟周期后才能完成复位,所以我们这里把FIFO复位设置了常量0,让FIFO在有时钟后就能自己完成复位,FIFO 复位的信号可以根据实际情况去应用,比如我们在后面的图像缓存方面,我们会用图像的VS去复位和同步FIFO,具体的理解还要在实际应用中加深。

3.2 RTL仿真

3.2.1仿真激励文件

下面我们进行仿真,如何编写tb文件,和调用仿真波形图,我就不详细讲了,不懂的人看前面FPGA入门的几个例子。我下面直接给出仿真代码。

仿真TB文件

cpp 复制代码
/*********************仿真文件****************************************

`timescale 1ns / 1ns//仿真时间刻度/精度

module tb_fifo_test;

localparam SYS_TIME = 10;//系统时钟周期10ns

reg I_sysclk_p;
reg I_sysclk_n;//系统时钟
reg I_rstn;//系统复位

//例化fifo_test1
fifo_test1 fifo_test1_inst
(
.I_sysclk_p(I_sysclk_p),
.I_sysclk_n(I_sysclk_n),
.I_rstn(I_rstn)
);

//例化fifo_test2
fifo_test2 fifo_test2_inst
(
.I_sysclk_p(I_sysclk_p),
.I_sysclk_n(I_sysclk_n),
.I_rstn(I_rstn)
);

//例化fifo_test2
fifo_test2 fifo_test2_inst
(
.I_sysclk_p(I_sysclk_p),
.I_sysclk_n(I_sysclk_n),
.I_rstn(I_rstn)
);

//初始化
initial begin
    I_sysclk_p  = 1'b0;
    I_sysclk_n  = 1'b1;
    I_rstn = 1'b0;
    #100;//产生100ns的系统复位
    I_rstn = 1'b1;//复位完成
    #20000 $finish; 
end

//产生仿真时钟
always #(SYS_TIME/2) I_sysclk_p= ~I_sysclk_p;
always #(SYS_TIME/2) I_sysclk_n= ~I_sysclk_n;         
                  
endmodule
3.2.2 仿真结果

写入FIFO的计数器值第一个值是1,注意不是0

每次写数据计数器最后一个值是512

读出FIFO的计数器值1

最后一个读出的值,由于采取8bit写入FIFO,所以512对应的是0

4关键信号法

关键信号法就是利用关键的信号,比如FIFO满标志,FIFO将满标志,FIFO空标志,FIFO将空标志,FIFO可编程空标志,和FIFO可编程满标志,FIFO读Valid标志,去控制FIFO的读写。

事实上,FIFO的满标志是正确的,也就是说FIFO输出满标志的。

修改FIFO IP 增加valid信号观测

4.1 almost_full 和almost_empty

1)、当FIFO非满的时候写

2)、当FIFO非空的时候读

4.1.1 测试代码
cpp 复制代码
/*************FIFO IP的仿真测试***************************************
--版本号1.0
--FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
--通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
--写状态机和读状态机分开运行
*********************************************************************/

`timescale 1ns / 1ns //仿真时间刻度/精度

module fifo_test2
(
input I_sysclk_p,
input I_sysclk_n, //系统时钟输入
input I_rstn     //系统复位
);
wire I_clk;
IBUFGDS CLK_U(
.I(I_sysclk_p),
.IB(I_sysclk_n),
.O(I_clk)
);
wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
wire [127:0]rd_data; //读数据信号
wire fifo_rst;       //fifo 复位,高电平有效
wire full;           //FIFO满
wire empty;          //FIFO空,这里没用到
wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满
wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空
wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
reg  [10: 0] wr_cnt;    //写数据计数器

reg[9:0] rst_cnt = 10'd0; //复位计数器

assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位

//MMCM/PLL 产生200M和100M时钟
clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

//复位计数器模块
always @(posedge clk_100m)begin
    if(!clk_locked)
        rst_cnt <= 10'd0;
    else if(rst_cnt[9] == 1'b0)
        rst_cnt <= rst_cnt + 1'b1;
end

// FIFO写状态机
always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
    if(!rst_cnt[9]) //复位,重置相关寄存器
        wr_cnt <= 11'd0;
    else 
       wr_cnt <= full ? wr_cnt : wr_cnt+1'b1;//写计数器累加
end

FIFO32_2_128 FIFO32_2_128_inst0 (
  .rst(fifo_rst),                  //FIFO 复位,高电平有效
  .wr_clk(clk_200m),               //FIFO 写时钟输入
  .rd_clk(clk_100m),               //FIFO 读时钟输入
  .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  .wr_en(!almost_full&rst_cnt[9]),                   //FIFO 写数据使能
  .rd_en(!almost_empty&rst_cnt[9]),                   //FIFO 读数据使能
  .dout(rd_data),                  //FIFO 读数据输出
  .full(full),                     //FIFO 写通道满
  .almost_full(almost_full),       //FIFO 写通道将满
  .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  .almost_empty(almost_empty),     //FIFO 读通道将空
  .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
);

endmodule

使用almost_full和almost_empyt可以防止FIFO写满和FIFO空读

4.1.2 RTL仿真结果

4.2 almost_full和valid

当读FIFO里面数据有效的时候Valid为1,所以也可以用valid信号读数据

4.2.1 测试代码
cpp 复制代码
/*************FIFO IP的仿真测试***************************************
--版本号1.0
--FIFO通常用于异步数据传输、数据缓存、数据位宽转换,本使用基于XILINX FIFO IP实现数据的位宽转换实验
--通过FIFO实现数据缓存,以及数据位宽从32bits转为128btis
--写状态机和读状态机分开运行
*********************************************************************/

`timescale 1ns / 1ns //仿真时间刻度/精度

module fifo_test3
(
input I_sysclk_p,
input I_sysclk_n, //系统时钟输入
input I_rstn     //系统复位
);
wire I_clk;
IBUFGDS CLK_U(
.I(I_sysclk_p),
.IB(I_sysclk_n),
.O(I_clk)
);
wire clk_100m,clk_200m,clk_locked;//MMCM/PLL 时钟信号
wire [127:0]rd_data; //读数据信号
wire fifo_rst;       //fifo 复位,高电平有效
wire full;           //FIFO满,这里没用到
wire empty;          //FIFO空,这里没用到
wire almost_full;    //FIFO将满,代表FIFO再写入1个数据就会满
wire almost_empty;   //FIFO将空,代表FIFO再读出1个数据就会空,这里没用到
wire [7 : 0] rd_data_count;//读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
wire [9 : 0] wr_data_count;//写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
reg [10 : 0] wr_cnt;    //写数据计数器
wire         valid ;    //读通道数据有效

reg[9:0] rst_cnt = 10'd0; //复位计数器

assign fifo_rst = (rst_cnt[9:7] == 3'b010); //产生一个高脉冲复位

//MMCM/PLL 产生200M和100M时钟
clk_wiz_0 clk_inst(.clk_out1(clk_200m),.clk_out2(clk_100m),.resetn(I_rstn),.locked(clk_locked),.clk_in1(I_clk));  

//复位计数器模块
always @(posedge clk_100m)begin
    if(!clk_locked)
        rst_cnt <= 10'd0;
    else if(rst_cnt[9] == 1'b0)
        rst_cnt <= rst_cnt + 1'b1;
end

// FIFO写状态机
always @(posedge clk_200m)begin //写数据用200MHZ 时钟写
    if(!rst_cnt[9]) //复位,重置相关寄存器
        wr_cnt <= 11'd0;
    else 
       wr_cnt <= almost_full? wr_cnt : wr_cnt+1'b1;//写计数器累加
end

FIFO32_2_128 FIFO32_2_128_inst0 (
  .rst(fifo_rst),                  //FIFO 复位,高电平有效
  .wr_clk(clk_200m),               //FIFO 写时钟输入
  .rd_clk(clk_100m),               //FIFO 读时钟输入
  .din({24'd0,wr_cnt[7:0]}),       //FIFO 写数据输入,测试数据用wr_cnt[7:0]计数器作为输入,其他高位为0
  .wr_en(!almost_full&rst_cnt[9]),                   //FIFO 写数据使能
  .rd_en(valid),                   //FIFO 读数据使能
  .dout(rd_data),                  //FIFO 读数据输出
  .full(full),                     //FIFO 写通道满,该信号这里没使用
  .almost_full(almost_full),       //FIFO 写通道将满,该信号这里没使用
  .empty(empty),                   //FIFO 读通道空,该信号这里没使用
  .valid(valid),                  // FIFO 读通道,数据有效
  .almost_empty(almost_empty),     //FIFO 读通道将空,该信号这里没使用
  .rd_data_count(rd_data_count),   //FIFO 读FIFO的计数器,这个计数器不精准,只是非常接近读FIFO中具有的数据个数
  .wr_data_count(wr_data_count)    //FIFO 写FIFO的计数器,这个计数器不精准,只是非常接近写FIFO中写入的数据个数
);

endmodule
4.2.2 RTL仿真结果

对于代码简单修改,仿真可以线点击1的位置重新load代码,然后再点击2运行。运行一会可以手动停止。

相信阅读完本文,根据本文把FIFO的例子做一遍你就能基本掌握FIFO的使用了,我是比较推荐第一种的半空半满法。

对于Stream接口的FIFO使用起来也差不多,因为本质上他们都是FIFO。在后期的课程中你们会看到Stream FIFO的应用,使用Steam接口有利于在新的FPGA设计中统一接口,方便代码的标准化,我们这里暂时就不讨论了。

相关推荐
邹莉斯2 小时前
FPGA基本结构和简单原理
fpga开发·硬件工程
悲喜自渡7212 小时前
易灵思FPGA开发(一)——软件安装
fpga开发
ZxsLoves2 小时前
【【通信协议ARP的verilog实现】】
fpga开发
爱奔跑的虎子4 小时前
FPGA与Matlab图像处理之伽马校正
图像处理·matlab·fpga开发·fpga·vivado·xilinx
机器未来21 小时前
基于FPGA的SD卡的数据读写实现(SD NAND FLASH)
arm开发·嵌入式硬件·fpga开发
贾saisai1 天前
Xilinx系FPGA学习笔记(八)FPGA与红外遥控
笔记·学习·fpga开发
吉孟雷2 天前
ZYNQ FPGA自学笔记
fpga开发·verilog·led·仿真·vivado·zynq
行者..................2 天前
1. ZYNQ 2. MPSOC 3. FPGA 4. IO分配 5. 硬件设计
fpga开发
tsumikistep2 天前
【无标题】Efinity 0基础进行流水灯项目撰写(FPGA)
fpga开发
行者..................2 天前
FPGA学习 VIVADO Verilog 编程
学习·fpga开发