ZYNQ 7020 FIFO讲解。

1 篇文章 0 订阅

ZYNQ 7020 FIFO讲解

 FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先

进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递。它与 FPGA 内部的 RAM和 ROM的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。本章我们将对 Vivado 软件生成的 FIFO IP 核进行读写测试,来向大家介绍 Xilinx FIFO IP 核的使用方法。
本章包括以下几个部分:
1 FIFO IP 核简介
2 实验任务
3 硬件设计
4 程序设计
5 下载验证

1 FIFO IP 核 简介

根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO。同步 FIFO 是指读时钟和写时钟

为同一个时钟,在时钟沿来临时同时发生读写操作。异步 FIFO 是指读写时钟不一致,读写时钟是互相独立的。Xilinx 的 FIFO IP 核可以被配置为同步 FIFO 或异步 FIFO,其信号框图如下图所示。从图中可以了解到,当被配置为同步 FIFO 时,只使用 wr_clk,所有的输入输出信号都同步于 wr_clk 信号。而当被配置为异步FIFO时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关的信号都是同步于读时钟 rd_clk。
在这里插入图片描述
Xilinx 的 FIFO IP 核的信号框图

对于 FIFO 需要了解一些常见参数:
FIFO 的宽度:FIFO 一次读写操作的数据位 N。
FIFO 的深度:FIFO 可以存储多少个宽度为 N 位的数据。
将空标志:almost_empty。FIFO 即将被读空。
空标志:empty。FIFO 已空时由 FIFO 的状态电路送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO
中读出数据而造成无效数据的读出。
将满标志:almost_full。FIFO 即将被写满。
满标志:full。FIFO 已满或将要写满时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的写操作继
续向 FIFO 中写数据而造成溢出。
读时钟:读 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
写时钟:写 FIFO 时所遵循的时钟,在每个时钟的上升沿触发。
这里请注意,“almost_empty”和“almost_full”这两个信号分别被看作“empty”和“full”的警告信号,他们相对于真正的空(empty)和满(full)都会提前一个时钟周期拉高。对于 FIFO 的基本知识先了解这些就足够了,可能有人会好奇为什么会有同步 FIFO 和异步 FIFO,它们各自的用途是什么。之所以有同步 FIFO 和异步 FIFO 是因为各自的作用不同。同步 FIFO 常用于同步时钟的数据缓存,异步 FIFO 常用于跨时钟域的数据信号的传递,例如时钟域 A 下的数据 data1 传递给异步时钟域 B,当 data1 为连续变化信号时,如果直接传递给时钟域 B 则可能会导致收非所送的情况,即在采集过程中会出现包括亚稳态问题在内的一系列问题,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中。

2 实验 任务

本节的实验任务是使用 Vivado 生成 FIFO IP 核,并实现以下功能:当 FIFO 为空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;然后从 FIFO 中读出数据,直到 FIFO 被读空为止,以此向大家详细介绍一下 FIFO IP 核的使用方法。

3 硬件设计

本章实验只用到了输入的时钟信号和按键复位信号,没有用到其它硬件外设。
本实验中,各端口信号的管脚分配如下表所示。
在这里插入图片描述
对应的 XDC 约束语句如下所示:
create_clock -period 20.000 -name clk [get_ports sys_clk]
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

4 程序 设计

根据实验任务要求和模块化设计的思想,我们需要如下 4 个模块:fifo IP 核、写 fifo 模块、读 fifo 模块
以及顶层例化模块实现前三个模块的信号交互。由于 FIFO 多用于跨时钟域信号的处理,所以本实验我们使用异步 FIFO 来向大家详细介绍双时钟 FIFO IP 核的创建和使用。为了方便大家理解,这里我们将读/写时钟都用系统时钟来驱动。系统的功能框图如下图所示:
在这里插入图片描述
系统框图

首先创建一个名为 ip_fifo 的工程,接下来我们创建 fifo IP 核。在 Vivado 软件的左侧“Flow Navigator”
栏中单击“IP Catalog”,“IP Catalog”按钮以及单击后弹出的“IP Catalog”窗口如下图所示。
在这里插入图片描述
“IP Catalog”按钮

在这里插入图片描述
“IP Catalog”窗口
在“IP Catalog”窗口中,在搜索栏中输入“fifo”关键字,这时 Vivado 会自动查找出与关键字匹配的 IP 核
名称,我们双击“FIFO Generator”,如下图所示。
在这里插入图片描述搜索栏中输入关键字
弹出“Customize IP”窗口,如下图所示。
在这里插入图片描述
“Customize IP”窗口

接下来就是配置 IP 核的时钟参数的过程。
最上面的“Component Name”一栏设置该 IP 元件的名称,这里保持默认即可。在第一个“Basic”选项卡
中,“Interface Type”选项用于选择 FIFO 接口的类型,这里我们选择默认的“Native”,即传统意义上的 FIFO接口。“Fifo Implementation”选项用于选择我们想要实现的是同步 FIFO 还是异步 FIFO 以及使用哪种资源实现 FIFO,这里我们选择“Independent Clocks Block RAM”,即使用块 RAM 来实现的异步 FIFO。如下图所示。
在这里插入图片描述
“Basic”选项卡

接下来是“Native Ports”选项卡,用于设置 FIFO 端口的参数。“Read Mode”选项用于设置读 FIFO
时的读模式,这里我们选择默认的“Standard FIFO”。“Data Port Parameters”一栏用于设置读写端口的数
据总线的宽度以及 FIFO 的深度,写宽度“Write Width”我们设置为 8 位,写深度“Write Depth”我们设置
为 256,注意此时 FIFO IP 核所能实现的实际深度却是 255;虽然读宽度“Read Width”能够设置成和写宽度不一样的位宽,且此时读深度“Read Depth”会根据上面三个参数被动地自动设置成相应的值;但是我们还是将读宽度“Read Width”设置成和写宽度“Write Width”一样的位宽,这也是在实际应用中最常用的情况。由于我们只是观察 FIFO 的读写,所以最下面的“Reset Pin”选项我们可以不使用,把它取消勾选。其他设置保持默认即可,如下图所示。
在这里插入图片描述
“Native Ports”选项卡

“Status Flags”选项卡,用于设置用户自定义接口或者用于设定专用的输入口。这里我们使用“即将写
满”和“即将读空”这两个信号,所以我们把它们勾选上,其他保持默认即可,如下图所示。
在这里插入图片描述
“Status Flags”选项卡

“Data Counts”选项卡用于设置 FIFO 内数据计数的输出信号,此信号表示当前在 FIFO 内存在多少个
有效数据。为了更加方便地观察读/写过程,这里我们把读/写端口的数据计数都打开,且计数值总线的位宽设置为满位宽,即 8 位,如下图所示。
在这里插入图片描述

“Data Counts”选项卡
最后的“Summary”选项卡是对前面所有配置的一个总结,在这里我们直接点击“OK”按钮即可,如
下图所示。
在这里插入图片描述“Summary”选项卡
接着就弹出了“Genarate Output Products”窗口,我们直接点击“Generate”即可,如下图所示。
在这里插入图片描述
“Genarate Output Products”窗口

之后我们就可以在“Design Run”窗口的“Out-of-Context Module Runs”一栏中出现了该 IP 核对应的
run“fifo_generator_0_synth_1”,其综合过程独立于顶层设计的综合,所以在我们可以看到其正在综合,如下图所示。

在这里插入图片描述“fifo_generator _0_synth_1”run
在其 Out-of-Context 综合的过程中,我们就可以进行 RTL 编码了。首先打开IP 核的例化模板,在“Source”窗口中的“IP Sources”选项卡中,依次用鼠标单击展开“IP”-“fifo_generator _0”-“Instantitation Template”,我们可以看到“fifo_generator_0.veo”文件,它是由 IP 核自动生成的只读的 verilog 例化模板文件,双击就可以打开它,如下图所示。

在这里插入图片描述“fifo_generator_0.veo”文件
我们创建一个 verilog 源文件,其名称为 ip_fifo.v,作为顶层模块,其代码如下:
module ip_fifo(
input sys_clk , // 时钟信号
input sys_rst_n // 复位信号
);

//wire define
(mark_debug = “true”) wire fifo_wr_en ; // FIFO写使能信号
(mark_debug = “true”) wire fifo_rd_en ; // FIFO读使能信号
(mark_debug = “true”) wire [7:0] fifo_din ; // 写入到FIFO的数据
(mark_debug = “true”) wire [7:0] fifo_dout ; // 从FIFO读出的数据
(mark_debug = “true”) wire almost_full ; // FIFO将满信号
(mark_debug = “true”) wire almost_empty ; // FIFO将空信号
(mark_debug = “true”) wire fifo_full ; // FIFO满信号
(mark_debug = “true”) wire fifo_empty ; // FIFO空信号
(mark_debug = “true”) wire [7:0] fifo_wr_data_count ; // FIFO写时钟域的数据计数
(mark_debug = “true”) wire [7:0] fifo_rd_data_count ; // FIFO读时钟域的数据计数

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

//例化FIFO IP核
fifo_generator_0 fifo_generator_0 (
.wr_clk ( sys_clk ), // input wire wr_clk
.rd_clk ( sys_clk ), // input wire rd_clk

.wr_en         ( fifo_wr_en         ),  // input wire wr_en
.rd_en         ( fifo_rd_en         ),  // input wire rd_en

.din           ( fifo_din           ),  // input wire [7 : 0] din
.dout          ( fifo_dout          ),  // output wire [7 : 0] dout

.almost_full   (almost_full         ),  // output wire almost_full
.almost_empty  (almost_empty        ),  // output wire almost_empty
.full          ( fifo_full          ),  // output wire full
.empty         ( fifo_empty         ),  // output wire empty

.wr_data_count ( fifo_wr_data_count ),  // output wire [7 : 0] wr_data_count	
.rd_data_count ( fifo_rd_data_count )   // output wire [7 : 0] rd_data_count

);

//例化写FIFO模块
fifo_wr u_fifo_wr(
.clk ( sys_clk ), // 写时钟
.rst_n ( sys_rst_n ), // 复位信号

.fifo_wr_en     ( fifo_wr_en )  , // fifo写请求
.fifo_wr_data   ( fifo_din    ) , // 写入FIFO的数据
.almost_empty   ( almost_empty ), // fifo空信号
.almost_full    ( almost_full  )  // fifo满信号

);

//例化读FIFO模块
fifo_rd u_fifo_rd(
.clk ( sys_clk ), // 读时钟
.rst_n ( sys_rst_n ), // 复位信号

.fifo_rd_en   ( fifo_rd_en ),      // fifo读请求
.fifo_dout    ( fifo_dout  ),      // 从FIFO输出的数据
.almost_empty ( almost_empty ),    // fifo空信号
.almost_full  ( almost_full  )     // fifo满信号

);

endmodule
//
module fifo_wr(
//mudule clock
input clk , // 时钟信号
input rst_n , // 复位信号
//FIFO interface
input almost_empty, // FIFO将空信号
input almost_full , // FIFO将满信号
output reg fifo_wr_en , // FIFO写使能
output reg [7:0] fifo_wr_data // 写入FIFO的数据
);

//reg define
reg [1:0] state ; //动作状态
reg almost_empty_d0 ; //almost_empty 延迟一拍
reg almost_empty_syn ; //almost_empty 延迟两拍
reg [3:0] dly_cnt ; //延迟计数器
//*****************************************************
//** main code
//*****************************************************

//因为 almost_empty 信号是属于FIFO读时钟域的
//所以要将其同步到写时钟域中
always@( posedge clk ) begin
if( !rst_n ) begin
almost_empty_d0 <= 1’b0 ;
almost_empty_syn <= 1’b0 ;
end
else begin
almost_empty_d0 <= almost_empty ;
almost_empty_syn <= almost_empty_d0 ;
end
end

//向FIFO中写入数据
always @(posedge clk ) begin
if(!rst_n) begin
fifo_wr_en <= 1’b0;
fifo_wr_data <= 8’d0;
state <= 2’d0;
dly_cnt <= 4’d0;
end
else begin
case(state)
2’d0: begin
if(almost_empty_syn) begin //如果检测到FIFO将被读空
state <= 2’d1; //就进入延时状态
end
else
state <= state;
end
2’d1: begin
if(dly_cnt == 4’d10) begin //延时10拍
//原因是FIFO IP核内部状态信号的更新存在延时
//延迟10拍以等待状态信号更新完毕
dly_cnt <= 4’d0;
state <= 2’d2; //开始写操作
fifo_wr_en <= 1’b1; //打开写使能
end
else
dly_cnt <= dly_cnt + 4’d1;
end
2’d2: begin
if(almost_full) begin //等待FIFO将被写满
fifo_wr_en <= 1’b0; //关闭写使能
fifo_wr_data <= 8’d0;
state <= 2’d0; //回到第一个状态
end
else begin //如果FIFO没有被写满
fifo_wr_en <= 1’b1; //则持续打开写使能
fifo_wr_data <= fifo_wr_data + 1’d1; //且写数据值持续累加
end
end
default : state <= 2’d0;
endcase
end
end
///
module fifo_rd(
//system clock
input clk , // 时钟信号
input rst_n , // 复位信号
//FIFO interface
input [7:0] fifo_dout , // 从FIFO读出的数据
input almost_full ,// FIFO将满信号
input almost_empty,// FIFO将空信号
output reg fifo_rd_en // FIFO读使能
);

//reg define
reg [1:0] state ; // 动作状态
reg almost_full_d0 ; // fifo_full 延迟一拍
reg almost_full_syn ; // fifo_full 延迟两拍
reg [3:0] dly_cnt ; //延迟计数器

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

//因为 fifo_full 信号是属于FIFO写时钟域的
//所以要将其同步到读时钟域中
always@( posedge clk ) begin
if( !rst_n ) begin
almost_full_d0 <= 1’b0 ;
almost_full_syn <= 1’b0 ;
end
else begin
almost_full_d0 <= almost_full ;
almost_full_syn <= almost_full_d0 ;
end
end

//读出FIFO的数据
always @(posedge clk ) begin
if(!rst_n) begin
fifo_rd_en <= 1’b0;
state <= 2’d0;
dly_cnt <= 4’d0;
end
else begin
case(state)
2’d0: begin
if(almost_full_syn) //如果检测到FIFO将被写满
state <= 2’d1; //就进入延时状态
else
state <= state;
end
2’d1: begin
if(dly_cnt == 4’d10) begin //延时10拍
//原因是FIFO IP核内部状态信号的更新存在延时
//延迟10拍以等待状态信号更新完毕
dly_cnt <= 4’d0;
state <= 2’d2; //开始读操作
end
else
dly_cnt <= dly_cnt + 4’d1;
end
2’d2: begin
if(almost_empty) begin //等待FIFO将被读空
fifo_rd_en <= 1’b0; //关闭读使能
state <= 2’d0; //回到第一个状态
end
else //如果FIFO没有被读空
fifo_rd_en <= 1’b1; //则持续打开读使能
end
default : state <= 2’d0;
endcase
end
end

endmodule
///
代码解析:
顶层模块主要是对 FIFO IP 核、写 FIFO 模块、读 FIFO 模块进行例化,除此之外本实验还生成并例化
了一个 ILA IP 核,用于对顶层模块信号的进行在线捕获观察。
fifo_wr 模块的核心部分是一个不断进行状态循环的小状态机,如果检测到 FIFO 为空,则先延时 10
拍,这里注意,由于 FIFO 的内部信号的更新比实际的数据读/写操作有所延时,所以延时 10 拍的目的是等待 FIFO 的空/满状态信号、数据计数信号等信号的更新完毕之后再进行 FIFO 写操作,如果写满,则回到状态 0,即等待 FIFO 被读空,以进行下一轮的写操作。
读模块的代码结构与写模块几乎一样,也是使用一个不断进行状态循环的小的状态机来控制操作过程,
读者参考着代码应该很容易能够理解,这里就不再赘述。
我们对代码进行仿真,TestBench 中只要送出时钟的复位信号即可。TB 文件如下:
module tb_ip_fifo( );
// Inputs
reg sys_clk;
reg sys_rst_n;

// Instantiate the Unit Under Test (UUT)
ip_fifo  u_ip_fifo (
    .sys_clk         (sys_clk), 
    .sys_rst_n       (sys_rst_n)
);

//Genarate the clk
parameter PERIOD = 20;
always begin
    sys_clk = 1'b0;
    #(PERIOD/2) sys_clk = 1'b1;
    #(PERIOD/2);
end   

initial begin
    // Initialize Inputs
    sys_rst_n = 0;
    // Wait 100 ns for global reset to finish
    #100  ;
    sys_rst_n = 1;
    // Add stimulus here
    
end

endmodule
写满后转为读的仿真波形图如下图所示:
在这里插入图片描述

由波形图可知,当写满 255 个数据后,fifo_full 满信号就会拉高。经过延时之后,fifo_rd_en 写使能信
号拉高,经过一拍之后就开始将 fifo 中的数据送到 fifo_dout 端口上。
写满后转为读的仿真波形图如下图所示:
在这里插入图片描述

由波形图可知,当读完 255 个数据后,fifo_empty 空信号就会拉高。经过延时之后,fifo_wr_en 写使能
信号拉高,经过一拍之后就开始向 fifo 中继续写入数据。

5 下载验证

编译工程并生成比特流.bit 文件,将比特流.bit 文件下载到 Zynq 中。
下载完成后,接下来在 Vivado 中会自动出现“hw_ila_1”Dashboard 窗口。如下图所示:
在这里插入图片描述
将探针信号添加到波形窗口中
将有关探针信号添加到波形窗口中,这里我们已经完成信号的添加,方法是点击“hw_ila_1”Dashboard
窗口左上角的“+”。同时我们在窗口右下角将“fifo_rd_en”信号添加到触发窗口中且设置为上升沿触发,
单击左上角的触发按钮,如下图所示:

在这里插入图片描述
触发按钮
最后就看到了 ILA 捕获得到的数据,展开波形图如下图所示:

在这里插入图片描述捕获得到的波形图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值