FIFO 的英文全称是 First In First Out, 即先进先出。与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线, 采取顺序写入数据, 顺序读出数据的方式,使用起来简单方便,缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址
根据 FIFO 工作的时钟域,可以将 FIFO 分为同步 FIFO 和异步 FIFO:
FIFO常用参数:
使用 Vivado 生成 FIFO IP 核,并实现以下功能:当 FIFO 为空时,向 FIFO 中写入数据,写入的数据量和 FIFO 深度一致,即 FIFO 被写满;然后从 FIFO 中读出数据,直到 FIFO 被读空为止,系统框图如下:

图片来自《领航者ZYNQ之FPGA开发指南》
新建工程,操作如图所示:

输入工程名和工程路径,如图所示:

选择创建RTL工程,如图所示:

直接点击Next:

继续点击Next:

添加芯片型号,操作如图所示:

完成工程创建:

点击IP Catalog,搜索fifo,如图所示:

双击打开后,弹出窗口如下,Interface Type 选项用于选择 FIFO 接口的类型,选择默认的 Native;Fifo Implementation 选项用于选择是同步 FIFO 还是异步 FIFO 以及使用哪种资源实现 FIFO,选择 Independent Clocks Block RAM,即使用块 RAM 来实现的异步 FIFO:

Read Mode 选项用于设置读 FIFO时的读模式,选择默认的Standard FIFO。Data Port Parameters 选型用于设置读写端口的数据总线的宽度以及 FIFO 的深度,写宽度 Write Width 设置为 8 位,写深度 Write Depth 设置为 256。Reset Pin 不使用,取消勾选:

Status Flags窗口,用于设置用户自定义接口或者用于设定专用的输入口,勾选 即将写满 和 即将读空 这两个信号:

Data Counts 窗口用于设置 FIFO 内数据计数的输出信号,此信号表示当前在 FIFO 内存在多少个有效数据。为了更加方便地观察读/写过程,设置如图所示:

设置完成:

弹出如下窗口,直接点击Generate:

点击OK:

继续添加ILA(集成逻辑分析器) IP核,即在线逻辑分析仪的功能,操作如图所示:

进行如下设置,Number of Probes 用于设置所需的探针数量;Sample Data Depth 用于设置采样深度:

如下进行设置每个探针的参数:

设置完成,点击OK:

创建工程顶层文件,操作如图所示:

创建文件ip_fifo,作为顶层模块:

创建完成:

双击打开,输入代码如下:
module ip_fifo( input sys_clk , // 时钟信号 input sys_rst_n // 复位信号
); //wire define
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 空信号
wire [7:0] fifo_wr_data_count ; // FIFO 写时钟域的数据计数
wire [7:0] fifo_rd_data_count ; // FIFO 读时钟域的数据计数
//例化 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 将满信号
);
//例化 ILA IP 核
ila_0 ila_0 ( .clk ( sys_clk ), // input wire clk .probe0 ( fifo_wr_en ), // input wire [0:0] probe0 .probe1 ( fifo_rd_en ), // input wire [0:0] probe1 .probe2 ( fifo_din ), // input wire [7:0] probe2 .probe3 ( fifo_dout ), // input wire [7:0] probe3 .probe4 ( fifo_empty ), // input wire [0:0] probe4 .probe5 ( almost_empty ), // input wire [0:0] probe5 .probe6 ( fifo_full ), // input wire [0:0] probe6 .probe7 ( almost_full ), // input wire [0:0] probe7 .probe8 ( fifo_wr_data_count ), // input wire [7:0] probe8 .probe9( fifo_rd_data_count ) // input wire [7:0] probe9
); endmodule
如图所示:

继续创建fifo_wr文件,即写FIFO模块,如图所示:

双击打开,输入代码如下:
module fifo_wr( input clk , // 时钟信号 input rst_n , // 复位信号 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 ; //延迟计数器//因为 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 endmodule
如图所示:

继续创建fifo_rd文件,即读FIFO模块,如图所示:

双击打开,输入代码如下:
module fifo_rd( input clk , // 时钟信号 input rst_n , // 复位信号 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 ; //almost_full 延迟一拍
reg almost_full_syn ; //almost_full 延迟两拍
reg [3:0] dly_cnt ; //延迟计数器 //因为 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
如图所示:

对设计进行分析,操作如图所示:

分析后的设计,Vivado自动生成顶层原理图,如图所示:

对设计进行综合,操作如图所示:

综合完成后,弹出窗口如下,直接关闭:

创建约束文件,操作如图所示:

创建约束文件,输入文件名:

双击打开,输入约束代码:
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 J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
如图所示:

点击 Flow Navigator 窗口中的 Run Implementation,如图所示:

点击OK:

完成后,关闭即可:

创建TestBench,操作如图所示:

创建激励文件,输入文件名:

创建完成:

双击打开,输入TestBench(激励)代码:
`timescale 1ns / 1ps
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
如图所示:

开始进行仿真,操作如下:

选择HDL仿真对象:

点击Restart,波形窗口中的当前仿真时刻点回归到0ns:

写满数据后,fifo_full信号拉高:

读完数据后,fifo_empty信号拉高:

由于疫情,一直无法去实验室,故ZYNQ开发板不在身边,该步骤内容待更新
致谢领航者ZYNQ开发板,开启FPGA学习之路!
希望本文对大家有帮助,上文若有不妥之处,欢迎指正
分享决定高度,学习拉开差距