【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验

目录

 

FIFO 简介

FIFO 分类

FIFO 信号解释

实验任务

实验框图

创建工程

添加 IP 并配置

设计文件

写 FIFO 模块

读 FIFO 模块

顶层模块

管脚时钟约束

验证功能

写 FIFO 部分

读 FIFO 部分


 

FIFO 简介

FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递,比如 DDR 的数据读写。它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。

FIFO 分类

  • 同步FIFO:读写时钟是同一个时钟,在时钟沿来临时同时发生读写操作。常用于同步时钟下的数据缓存。
  • 异步FIFO:读写时钟不一样,读写时钟相互独立。常用于不同时钟域下的数据传输,因为时钟不同的话,在数据采集的时候可能会出现亚稳态的情况,导致数据传输错误,使用异步 FIFO 能够将不同时钟域中的数据同步到所需的时钟域中,使得数据传输稳定。

Xilinx 的 FIFO IP 核可以被配置为同步 FIFO 或异步 FIFO,其信号框图如下图所示。从图中可以看到,当被配置为同步 FIFO 时,只使用 wr_clk,所有的输入输出信号都同步于 wr_clk 信号。而当被配置为异步FIFO 时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写时钟 wr_clk,所有与读相关的信号都是同步于读时钟 rd_clk。

下图是 FIFO 的示意图,包含了 FIFO IP 的各种信号定义,需要注意的是:FIFO可以用 BRAM 和 DRAM 生成,但是两者有区别,BRAM 生成的 FIFO 可以进行读写位宽不同的操作,而 DRAM 生成的 FIFO 则不可以,因此通常使用 BRAM 生成 FIFO。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第1张图片

FIFO 信号解释

wr_en

写使能信号

rd_en

读使能信号

full

写满信号,当FIFO写满时此信号拉高

empty

读空信号,当FIFO读空时此信号拉高

almost_full

快要写满信号,当FIFO将要写满时此信号拉高,比full信号提前一个时钟周期

almost_empty

快要读空信号,当FIFO将要读空时此信号拉高,比empty信号提前一个时钟周期

prog_full

可编程满信号,如果FIFO的深度为1024,可以配置成写满512个数据时将此信号拉高

prog_empty

可编程空信号,如果FIFO的深度为1024,可以配置成读了512个数据时将此信号拉高

din[n:0]

写入的数据

dout[n:0]

读出的数据

wr_ack

写反馈,当一个时钟周期中,FIFO成功处理了使能,就会返回一个wr_ack信号表示写使能成功处理

valid

数据有效信号,当读出的数据是稳定有效的,则将此信号拉高

overflow

溢出信号,当写满时,就会将溢出信号拉高

underflow

下溢出,当数据读完之后就会将此信号拉高表示数据已经空,无法再读

wr_data_count[p:0]

表示FIFO中有多少个写数据

rd_data_count[p:0]

表示FIFO中还有多少数据没有读出,如果读写时钟、读写数据位宽都是一样的话,这两个信号的值是一样的

prog_full_thresh_assert

动态编程prog_full的门限值

prog_empty_thresh_assert

动态编程prog_empty的门限值

prog_full_thresh_negate

门限值失效,可以使设定的门限值失效

prog_empty_thresh_negate

门限值失效,可以使设定的门限值失效

prog_full_thresh

实际输入的门限值信号,以上两个信号作为确认信号,三个信号相互配合使用

prog_empty_thresh

实际输入的门限值信号,以上两个信号作为确认信号,三个信号相互配合使用

injectsbiterr

注入单比特的错误,一般这四个信号不使用,再V系列才能用

sbiterr

输出单比特的错误信号

injectdbiterr

注入双比特的错误

dbiterr

输出双比特的错误信号

实验任务

使用 Vivado 生成 FIFO IP 核,并实现功能:当 FIFO 为空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;然后从 FIFO 中读出数据,直到 FIFO 被读空为止。

实验框图

实验框图比较简单,分别包含 FIFO IP 模块、写 FIFO 模块、读 FIFO 模块以及顶层模块四部分,写 FIFO 模块依次往 FIFO 中写数据,FIFO 会反馈将空信号和将满信号,同样的读 FIFO 模块也是这样。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第2张图片

创建工程

打开 Vivado,创建一个名为 ip_fifo 的实验工程,具体的器件配信息等根据板子设定。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第3张图片

添加 IP 并配置

双击点开 IP Catalog 搜索fifo,双击 fifo generate 进入 IP 配置界面。

FIFO implementation:选择异步时钟的BRAM,表示读写的时钟是独立的,但是为了方便这个示例读写所使用的时钟是一个时钟。

其余选项保持默认。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第4张图片

Read Mode:选择标准的 FIFO,first word fall through 的选项表示的时在读操作进行前,会将第一个将要读出的数据提前出来,这样读操作就没有延时,一旦读使能拉高,数据就能同步的被读出来。

Data Port Parameters:读写位宽都为8,读写深度都为256,其余参数配置保持不变。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第5张图片

Option Flags:将满信号核将空信号都选上,两个信号所表示的含义前面有介绍。

其余保持不变

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第6张图片

勾选写数据计数核读数据计数,可以看到左侧就会有相应的端口,用于计数 FIFO 中有多少写入的数据以及 FIFO 中还有多少没有读出的数据,按照这个设置,两者的值是一样的,但是体现在时序上会有若干个时钟延时。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第7张图片

最后点击 ok 即可完成 FIFO IP 核的配置

设计文件

根据实验设计框图所示,需要添加三个设计文件:读模块、写模块、顶层模块

点击 source 中的+添加三个设计文件分别为写、读、顶层。

写 FIFO 模块

`timescale 1ns / 1ps
//
// Company: 
// Engineer: Linest
// Create Date: 2022/04/24 14:56:47
// Design Name: 
// Module Name: fifo_wr
// Target Devices: 
// Tool Versions: 
// Description: 当将空信号拉高时,将写使能信号拉高,依次计数将数据写入fifo中,
// 		将满信号拉高时,写使能信号拉低,停止写数据
// Dependencies: 
// Revision:
// 
//


module fifo_wr(
	input					clk,
	input					rst_n,
	input					almost_full,		//在将要为满信号拉高时停止往fifo里写数据
	input					almost_empty,		//在将要为空信号拉高时往fifo里写数据
	output	reg				wr_en,
	output	reg		[7:0]	data_wr
    );

	reg						almost_empty_reg1;	//将空信号寄存	
	reg						almost_empty_reg2;
	reg 					almost_full_reg1;	//将满信号寄存
	reg 					almost_full_reg2;
	reg 	[9:0]			data_cnt;	

	//对将空信号打一拍
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			almost_empty_reg1 <= 'd0;
			almost_empty_reg2 <= 'd0;
		end
		else begin
			almost_empty_reg1 <= almost_empty;
			almost_empty_reg2 <= almost_empty_reg1;
		end
	end

	//对将满信号打一拍
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			almost_full_reg1 <= 'd0;
			almost_full_reg2 <= 'd0;
		end
		else begin
			almost_full_reg1 <= almost_full;
			almost_full_reg2 <= almost_full_reg1;
		end
	end

	//检测到将空信号拉高时,写使能拉高,检测到将满信号拉高时,写使能拉低
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			wr_en <= 'd0;
		end
		else if ((almost_empty_reg1 ^ almost_empty_reg2) == 'd1) begin
			wr_en <= 'd1;
		end
		else if ((almost_full_reg1 ^ almost_full_reg2) == 'd1) begin
			wr_en <= 'd0;
		end
		else begin
			wr_en <= wr_en;
		end
	end

	//计数器作为发送数据
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			data_cnt <= 'd0;
		end
		else if (wr_en == 'd1) begin
			data_cnt <= data_cnt + 'd1;
		end
		else if (data_cnt == 'd1023) begin
			data_cnt <= 'd0;
		end
		else begin
			data_cnt <= data_cnt;
		end
	end

	//在写使能拉高时,将数据依次写入fifo中
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			data_wr <= 'd0;
		end
		else if (wr_en == 'd1) begin
			data_wr <= data_cnt;
		end
		else begin
			data_wr <= data_wr;
		end
	end

endmodule

读 FIFO 模块

`timescale 1ns / 1ps
//
// Company: 
// Engineer: Linest
// Create Date: 2022/04/24 14:57:15
// Design Name: 
// Module Name: fifo_rd
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 当将满信号拉高时,将读使能拉高,并依次从fifo中读出数据,
// 					将空信号拉高时,读使能信号拉低,停止读数据
// Revision:
// Additional Comments:
// 
//


module fifo_rd(
	input					clk,
	input					rst_n,
	input					almost_full,		//在将要为满信号拉高时从fifo读数据												
	input					almost_empty,		//在将要为空信号拉高时停止从fifo读数据
	output	reg				rd_en	
    );

	reg						almost_empty_reg1;	//将空信号寄存	
	reg						almost_empty_reg2;
	reg 					almost_full_reg1;	//将满信号寄存
	reg 					almost_full_reg2;

	//对将空信号打一拍
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			almost_empty_reg1 <= 'd0;
			almost_empty_reg2 <= 'd0;
		end
		else begin
			almost_empty_reg1 <= almost_empty;
			almost_empty_reg2 <= almost_empty_reg1;
		end
	end

	//对将满信号打一拍
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			almost_full_reg1 <= 'd0;
			almost_full_reg2 <= 'd0;
		end
		else begin
			almost_full_reg1 <= almost_full;
			almost_full_reg2 <= almost_full_reg1;
		end
	end

	//检测到将满信号拉高时,读使能拉高,检测到将空信号拉高时,读使能拉低
	always @(posedge clk or negedge rst_n) begin
		if (rst_n == 'd0) begin
			rd_en <= 'd0;
		end
		else if ((almost_full_reg1 ^ almost_full_reg2) == 'd1) begin
			rd_en <= 'd1;
		end
		else if ((almost_empty_reg1 ^ almost_empty_reg2) == 'd1) begin
			rd_en <= 'd0;
		end
		else begin
			rd_en <= rd_en;
		end
	end

endmodule

顶层模块

在顶层模块中,定义了端口,例化了需要的模块:读、写模块、FIFO IP、原语模块、ILA模块

需要注意的是:由于使用的板子的时钟为差分信号,所以需要例化添加一个Xilinx原语,用于差分时钟转成单端时钟。

ILA 中我将一般需要的端口都连接上,以便观察。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: Linest
// Create Date: 2022/04/24 14:42:54
// Design Name: 
// Module Name: ip_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 定义端口,例化各个模块
// Revision:
// Additional Comments:
// 
//


module ip_fifo(	
	input 			 clk_p,
	input 			 clk_n,
	input 			 rst_n
    );
    
    wire             clk;
	wire			 almost_full; 
	wire			 almost_empty;
	wire			 wr_en;       
	wire	[7:0]	 data_wr;
	wire	[7:0]	 data_out;        
	wire			 rd_en;   
	wire			 full;
	wire			 empty;
	wire	[7:0]	 rd_data_count;	   
	wire	[7:0]	 wr_data_count;

	//xilinx原语,差分时钟转单端时钟
    IBUFDS #(
        .DIFF_TERM	  ("FALSE"),       	// Differential Termination
        .IBUF_LOW_PWR ("TRUE"),     	// Low power="TRUE", Highest performance="FALSE" 
        .IOSTANDARD	  ("DEFAULT")     	// Specify the input I/O standard
    ) IBUFDS_inst (
        .O  		  (clk),  			// Buffer output
        .I 			  (clk_p),  		// Diff_p buffer input (connect directly to top-level port)
        .IB 		  (clk_n) 			// Diff_n buffer input (connect directly to top-level port)
    );
 
	fifo_wr fifo_wr_inst (
		.clk          (clk),
		.rst_n        (rst_n),
		.almost_full  (almost_full),
		.almost_empty (almost_empty),
		.wr_en        (wr_en),
		.data_wr      (data_wr)
	);
	
	fifo_rd inst_fifo_rd (
		.clk          (clk),
		.rst_n        (rst_n),
		.almost_full  (almost_full),
		.almost_empty (almost_empty),
		.rd_en        (rd_en)
	);
   
    fifo_gen fifo_gen_inst (
      	.wr_clk		  (clk),            // input wire wr_clk
      	.rd_clk		  (clk),            // input wire rd_clk
      	.din 		  (data_wr),        // input wire [7 : 0] din
      	.wr_en 		  (wr_en),          // input wire wr_en
      	.rd_en 		  (rd_en),          // input wire rd_en
      	.dout 		  (data_out),       // output wire [7 : 0] dout
      	.full 		  (full),           // output wire full
      	.almost_full  (almost_full),    // output wire almost_full
      	.empty 		  (empty),          // output wire empty
      	.almost_empty (almost_empty),   // output wire almost_empty
      	.rd_data_count(rd_data_count),  // output wire [7 : 0] rd_data_count
      	.wr_data_count(wr_data_count)   // output wire [7 : 0] wr_data_count
    );
    
    ila_0 fifo_ila (
        .clk 		  (clk), 			// input wire clk
        .probe0 	  (almost_full), 	// input wire [0:0]  probe0  
        .probe1 	  (almost_empty), 	// input wire [0:0]  probe1 
        .probe2 	  (wr_en), 			// input wire [0:0]  probe2 
        .probe3 	  (data_wr), 		// input wire [7:0]  probe3 
        .probe4 	  (data_out), 		// input wire [7:0]  probe4 
        .probe5 	  (rd_en), 			// input wire [0:0]  probe5 
        .probe6 	  (full), 			// input wire [0:0]  probe6 
        .probe7 	  (empty), 			// input wire [0:0]  probe7 
        .probe8 	  (rd_data_count), 	// input wire [7:0]  probe8 
        .probe9 	  (wr_data_count) 	// input wire [7:0]  probe9
    );

endmodule

管脚时钟约束

创建约束文件,添加以下约束。包含的约束有:差分时钟、复位、IBUFDS

set_property PACKAGE_PIN R4 [get_ports clk_p]
set_property IOSTANDARD DIFF_SSTL15 [get_ports clk_p]
set_property IOSTANDARD DIFF_SSTL15 [get_ports clk_n]
set_property PACKAGE_PIN T6 [get_ports rst_n]
set_property IOSTANDARD LVCMOS15 [get_ports rst_n]
set_property C_CLK_INPUT_FREQ_HZ 300000000 [get_debug_cores dbg_hub]
set_property C_ENABLE_CLK_DIVIDER false [get_debug_cores dbg_hub]
set_property C_USER_SCAN_CHAIN 1 [get_debug_cores dbg_hub]
connect_debug_port dbg_hub/clk [get_nets clk_BUFG]

验证功能

最后生成比特流,连接开发板,将程序烧录进去并运行,观察 ILA 的时序图。

写 FIFO 部分

可以看到,到将空信号上升沿来临时,延时一个时钟周期空信号拉高,再延时一个时钟周期后,写使能拉高,数据便从 0 开始写入(红色箭头所指的就是数据开始写入的时刻),与此同时,wr_data_count 和 rd_data_count 开始计数。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第8张图片

读 FIFO 部分

可以看到,到将满信号上升沿来临时,延时一个时钟周期满信号拉高,再延时一个时钟周期后,读使能拉高,数据便从 255 开始写入(红色箭头所指的就是数据开始读出的时刻),因为使用的是将满信号作为指示信号,所以数据的最后一个再上一次没有读完全,遗留了一个数据,因此从255开始读出。与此同时,wr_data_count 和 rd_data_count 开始计数。

【Xilinx IP调用】FIFO IP 核介绍及用 Verilog 进行读写实验_第9张图片

验证完毕,功能正常!

 

 

你可能感兴趣的:(Vivado,#,常见,IP,fpga开发,Vivado,FIFO,IP,嵌入式)