通信算法之328:Vivado中FIFO的IP核


一. 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) begin
		dout_valid<=fifo_rd_en;
	end
	
	// ila
	generate
		if(`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 clock
    input                 clk,         //时钟信号
    input                 rst_n,       //复位信号
    //FIFO interface
    input                 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) 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 if(!wr_rst_busy) begin
         case(state)
              2'd0: begin
                   if(almost_empty_syn) begin //如果检测到FIFO 将被读空
                       state <= 2'd1;  //就进入延时状态
                   end
                   else
                       state <= state;
              end
              2'd1: begin
              //原因是 FIFO IP 核内部状态信号的更新存在延时 //延迟 10 拍以等待状态信号更新完毕
                   if(dly_cnt==4'd10) begin //延时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
    else begin
        fifo_wr_en   <= 1'b0;
        fifo_wr_data <= 8'd0;
        state        <= 2'd0;
        dly_cnt      <= 4'd0;
    end    
end
endmodule

 fifo_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 clock
     input             clk,        //时钟信号
     input             rst_n,      //复位信号
     //FIFO interface
     input             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

提示:如果文章对你有帮助,请记得关注CSDN号。本公众号将持续更新相关的技术话题,谢谢。

提示:如果文章对你有帮助,请记得关注CSDN号。本公众号将持续更新相关的技术话题,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋风战士

你的鼓励是我创造的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值