通信算法之328:Vivado中FIFO的IP核
文章目录
- 一. FIFO IP 核简介
- 二. FIFO例程演示
- 三. 例化FIFO IP核
- 四. 例化FIFO写模块
- 五.例化FIFO读模块
- 六. 学习总结
一. FIFO IP 核简介
- FIFO(First In First Out,即先进先出),是一种数据缓存器,用来实现数据先进先出的读写方式。
- 在FPGA 或者 ASIC 中使用到的 FIFO 一般指的是对数据的存储具有先进先出特性的缓存器,常被用于数据的缓存、多比特数据跨时钟域的转换、读写数据带宽不同步等场合,或者高速异步数据的交互也即所谓的跨时钟域信号传递。
- 它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式, 使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定 的地址。
- FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器,其与普通存储器 RAM 的 区别在于 FIFO 没有外部读写地址线,使用起来非常简单,但 FIFO 只能顺序写入数据,并按顺序读出数据,其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址,不过也正是因为这个特性,使得 FIFO 在使用时并不存在像 RAM 那样的读写冲突问题。
根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 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 中剩余的数据量已经减少到可配置空阈值中配置的数值。
二. FIFO例程演示
- 本次实验是使用 FIFO Generator IP 核来生成一个异步 FIFO,所以我们需要使用到 PLL IP 核来输出两路不同频率的时钟,
- 除此之外我们还需要一个读模块(fifo_rd)和一个写模块(fifo_wr)来进行异步的读写操作,
- 所以我们需要创建一个顶层模块来例化两个 IP 核与读/写模块。
- 完整代码如下:
`timescale 1ns / 1ps
`include "head.v"//函数名称:ad93xx_rx_buf
//函数功能:将接收到的数据进行跨时钟域处理
//实现方法:利用fifo
//注意事项:产生读使能的信号的时钟为线时钟
module ad93xx_rx_buf(input clk , //i,线时钟input rst ,input din_valid , //i,每四个时钟周期有一个时钟周期有效input [11:0] din_i ,input [11:0] din_q ,input clk_div4 , //i,数据时钟,一个时钟周期一个采样数据output [11:0] dout_i ,output [11:0] dout_q ,output reg dout_valid //o,输出数据的有效标志,每一个数据时钟对应一个有效标志
);//根据写有效标志产生读使能信号,主要时钟为写使能的时钟wire fifo_rd_en;fifo_rd_ctl u_fifo_rd_ctl (.clk (clk ),.rst (rst ), .trig (din_valid ),.fifo_rd_en (fifo_rd_en ) );//缓冲存储器fifo_generator u_fifo_generator (.wr_clk (clk ), // input wire wr_clk.rd_clk (clk_div4 ), // input wire rd_clk.din ({din_q,din_i} ), // input wire [23 : 0] din.wr_en (din_valid ), // input wire wr_en.rd_en (fifo_rd_en ), // input wire rd_en.dout ({dout_q,dout_i}), // output wire [23 : 0] dout.full ( ), // output wire full.empty ( ) // output wire empty);//fifo输出有延时,所以需要将读使能延时后作为数据有效标志always@(posedge clk_div4) begindout_valid<=fifo_rd_en;end// ilagenerateif(`ILA_RX_BUF==1'b1)ila_rx_buf u_ila_rx_buf (.clk (clk_div4 ), // input wire clk.probe0 (dout_i ), // input wire [15:0] probe0 .probe1 (dout_q ), // input wire [0:0] probe1 .probe2 (dout_valid ) // input wire [0:0] probe2);endgenerate
endmodule
三. 例化FIFO IP核
提示:例化
//缓冲存储器fifo_generator u_fifo_generator (.wr_clk (clk ), // input wire wr_clk.rd_clk (clk_div4 ), // input wire rd_clk.din ({din_q,din_i} ), // input wire [23 : 0] din.wr_en (din_valid ), // input wire wr_en.rd_en (fifo_rd_en ), // input wire rd_en.dout ({dout_q,dout_i}), // output wire [23 : 0] dout.full ( ), // output wire full.empty ( ) // output wire empty);
//wire define
wire clk_50m; //50M时钟
wire clk_100m; //100M时钟
wire locked; //时钟锁定信号
wire rst_n; //复位,低电平有效
wire wr_rst_busy; //写复位忙信号
wire rd_rst_busy; //读复位忙信号
wire fifo_wr_en; //FIFO写使能信号
wire fifo_rd_en; //FIFO读使能信号
wire [7:0] fifo_din; //写入到FIFO的数据
wire [7:0] fifo_dout; //从FIFO读出的数据
wire almost_full; //FIFO将满信号
wire almost_empty; //FIFO将空信号
wire fifo_full; //FIFO满信号
wire fifo_empty; //FIFO空信号//例化 FIFO IP 核
fifo_generator_0 fifo_generator_0(.rst (~sys_rst_n ), //input wire rst.wr_clk (clk_50m ), //input wire wr_clk.rd_clk (clk_100m ), //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.wr_rst_busy (wr_rst_busy ), //output wire wr_rst_busy.rd_rst_busy (rd_rst_busy ) //output wire rd_rst_busy
);
四. 例化FIFO写模块
- 首先介绍下 FIFO 写模块的设计,在 FIFO 写模块中,我们的输入信号主要有系统时钟信号、系统复位信号;因为 FIFO 的写操作需要在 FIFO 完成复位后进行,所以我们还需要输入 wr_rst_busy(写复位忙)来作为判断 FIFO 是否结束了复位状态;
- 实验任务中我们提到了 FIFO 为空时进行写操作,因此还需要引入一个空相关的信号,这里我们引入的是 almost_empty(将空)信号,当然引入 empty(空)信号也是可以的;
- 实验任务中我们还提到了写满了要停止写操作,所以这里我们引入了 almost_full(将满)信号,因为将满信号表示 FIFO 还能再进行最后一次写操作,使用这个信号的话我们正好可以在写入最后一次数据后关闭写使能,当然引入 full(满)信号也是可以,区别只是在于这么做会在写使能关断前执行一次无效的写操作。
- 输出信号有控制写 FIFO 所需的 fifo_wr_en(写端口使能)和 fifo_wr_data(写数据)这两个信号
提示:例化
//例化写 FIFO 模块
fifo_wr u_fifo_wr(.clk (clk_50m ), //写时钟.rst_n (rst_n ), //复位信号.wr_rst_busy (wr_rst_busy ), //写复位忙信号.fifo_wr_en (fifo_wr_en ), //fifo 写请求 output .fifo_wr_data (fifo_din ), //写入FIFO的数据 output .almost_empty (almost_empty ), //fifo 空信号.almost_full (almost_full ) //fifo 满信号
);
提示:写模块
//////////////////////////////////////////////////////////////////////////////////
//写模块的运行是通过一个小的状态机(state)实现的,当 FIFO 复位结束后,
//在 state=0(空闲状态)时,若检测到 almost_empty_syn 信号为高,则跳转到延迟状态(state=1);
//延迟状态用于延迟 10 拍(dly_cnt 计数实现),其目的是等待 FIFO 的内部状态信号更新完成,
//在第 10 拍时,打开 fifo_wr_en(写使能)信号,清空 dly_cnt 计数器并跳转到写操作状态(state=2);
//写操作状态时,向 FIFO 中写入从 0 开始的累加数据,直至检测到 almost_full(将满)信号时,
//关闭写使能,将写数据清零并跳转回空闲状态(state=0),等待下一次的写操作。//fifo_wr 模块用于产生 FIFO 写操作所需的信号
module fifo_wr(//module clockinput clk, //时钟信号input rst_n, //复位信号//FIFO interfaceinput wr_rst_busy, //写复位忙信号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) beginif(!rst_n) beginalmost_empty_d0 <= 1'b0;almost_empty_syn <= 1'b0;endelse beginalmost_empty_d0 <= almost_empty;almost_empty_syn <= almost_empty_d0;end
end//向FIFO中写入数据
always @(posedge clk) beginif(!rst_n) beginfifo_wr_en <= 1'b0;fifo_wr_data <= 8'd0;state <= 2'd0;dly_cnt <= 4'd0;end//当写复位处于空闲状态时else if(!wr_rst_busy) begincase(state)2'd0: beginif(almost_empty_syn) begin //如果检测到FIFO 将被读空state <= 2'd1; //就进入延时状态endelsestate <= state;end2'd1: begin//原因是 FIFO IP 核内部状态信号的更新存在延时 //延迟 10 拍以等待状态信号更新完毕if(dly_cnt==4'd10) begin //延时10拍dly_cnt <= 4'd0;state <= 2'd2; //开始写操作fifo_wr_en <= 1'b1; //打开写使能 endelsedly_cnt <= dly_cnt + 4'd1;end2'd2: beginif(almost_full) begin //等待FIFO将被写满fifo_wr_en <= 1'b0; //关闭写使能fifo_wr_data <= 8'd0;state <= 2'd0; //回到第一个状态endelse begin //如果FIFO没有被写满fifo_wr_en <= 1'b1; //则持续打开写使能fifo_wr_data <= fifo_wr_data + 1'd1; //且写数据持续累加endenddefault: state <= 2'd0;endcase endelse beginfifo_wr_en <= 1'b0;fifo_wr_data <= 8'd0;state <= 2'd0;dly_cnt <= 4'd0;end
end
endmodulefifo_wr 模块的核心部分是一个不断进行状态循环的小状态机,如果检测到 FIFO 为空,则先延时 10 拍,这里注意,由于 FIFO 的内部信号的更新比实际的数据读/写操作有所延时,所以延时 10 拍的目的是等待 FIFO 的空/满状态信号、数据计数信号等信号的更新完毕之后再进行 FIFO 写操作,如果写满,则回到状态 0,即等待 FIFO 被读空,以进行下一轮的写操作。
五.例化FIFO读模块
- 首先介绍下 FIFO 读模块的设计,在 FIFO 读模块中,我们的输入信号主要有系统时钟信号、系统复位信号;
- 因为 FIFO 的读操作需要在 FIFO 完成复位后进行,所以我们还需要输入 rd_rst_busy(读复位忙)来作为判断 FIFO 是否结束了复位状态;
- 实验任务中我们提到了 FIFO 为满时进行读操作,因此还需要引入一个满相关的信号,这里我们引入的是 almost_full(将满)信号,当然引入 full(满)信号也是可以的;
- 实验任务中我们还提到了读空了要停止读操作,所以这里我们引入了 almost_empty(将空)信号,因为将空信号表示 FIFO 还能再进行最后一次读操作,使用这个信号的话我们正好可以在读出最后一个数据后关闭读使能,当然引入 empty(空)信号也是可以,区别只是在于这么做会在读使能关断前执行一次无效的读操作。输出信号有控制写 FIFO 所需的 fifo_rd_en(读端口使能)信号
例化读模块:
//例化读 FIFO 模块
fifo_rd u_fifo_rd (.clk (clk_100m ), // 读时钟.rst_n (rst_n ), // 复位信号.rd_rst_busy (rd_rst_busy ), // 读复位忙信号.fifo_rd_en (fifo_rd_en ), // fifo 读请求.fifo_dout (fifo_dout ), // 从 FIFO 输出的数据.almost_empty (almost_empty ), // fifo 空信号.almost_full (almost_full ) // fifo 满信号
);
//////////////////////////////////////////////////////////////////////////////////
//读模块的运行同样是通过一个小的状态机(state)实现的,当 FIFO 复位结束后,
//在 state=0(空闲状态)时,若检测到 almost_full_syn 信号为高,则跳转到延迟状态(state=1);
//延迟状态用于延迟 10 拍(dly_cnt 计数实现),其目的是等待 FIFO 的内部状态信号更新完成,
//在第 10 拍时,打开 fifo_rd_en(读使能)信号,清空 dly_cnt 计数器并跳转到读操作状态(state=2);
//读操作状态时,模块接收从 FIFO 中读出的数据,直至检测到 almost_empty(将空)信号时,
//关闭读使能并跳转回空闲状态(state=0),等待下一次的读操作。//读 FIFO 模块 fifo_rd.v
//fifo_rd 模块用于产生 FIFO 读操作所需的信号
module fifo_rd(//system clockinput clk, //时钟信号input rst_n, //复位信号//FIFO interfaceinput rd_rst_busy, //读复位忙信号input [7:0] fifo_dout, //从FIFO读出数据input almost_full, //FIFO将满信号input almost_empty, //FIFO将空信号output reg fifo_rd_en //FIFO读使能);
六. 学习总结
- 搞复杂了
- 搞糊涂了
- 参考:https://blog.csdn.net/yishuihanq/article/details/131163858?spm=1001.2101.3001.10796