目录
FIFO 简介
FIFO 分类
FIFO 信号解释
实验任务
实验框图
创建工程
添加 IP 并配置
设计文件
写 FIFO 模块
读 FIFO 模块
顶层模块
管脚时钟约束
验证功能
写 FIFO 部分
读 FIFO 部分
FIFO 的英文全称是 First In First Out,即先进先出。FPGA 使用的 FIFO 一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存,或者高速异步数据的交互也即所谓的跨时钟域信号传递,比如 DDR 的数据读写。它与 FPGA 内部的 RAM 和 ROM 的区别是没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,使用起来简单方便,由此带来的缺点就是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。
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。
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 模块也是这样。
打开 Vivado,创建一个名为 ip_fifo 的实验工程,具体的器件配信息等根据板子设定。
双击点开 IP Catalog 搜索fifo,双击 fifo generate 进入 IP 配置界面。
FIFO implementation:选择异步时钟的BRAM,表示读写的时钟是独立的,但是为了方便这个示例读写所使用的时钟是一个时钟。
其余选项保持默认。
Read Mode:选择标准的 FIFO,first word fall through 的选项表示的时在读操作进行前,会将第一个将要读出的数据提前出来,这样读操作就没有延时,一旦读使能拉高,数据就能同步的被读出来。
Data Port Parameters:读写位宽都为8,读写深度都为256,其余参数配置保持不变。
Option Flags:将满信号核将空信号都选上,两个信号所表示的含义前面有介绍。
其余保持不变
勾选写数据计数核读数据计数,可以看到左侧就会有相应的端口,用于计数 FIFO 中有多少写入的数据以及 FIFO 中还有多少没有读出的数据,按照这个设置,两者的值是一样的,但是体现在时序上会有若干个时钟延时。
最后点击 ok 即可完成 FIFO IP 核的配置
根据实验设计框图所示,需要添加三个设计文件:读模块、写模块、顶层模块
点击 source 中的+添加三个设计文件分别为写、读、顶层。
`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
`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 的时序图。
可以看到,到将空信号上升沿来临时,延时一个时钟周期空信号拉高,再延时一个时钟周期后,写使能拉高,数据便从 0 开始写入(红色箭头所指的就是数据开始写入的时刻),与此同时,wr_data_count 和 rd_data_count 开始计数。
可以看到,到将满信号上升沿来临时,延时一个时钟周期满信号拉高,再延时一个时钟周期后,读使能拉高,数据便从 255 开始写入(红色箭头所指的就是数据开始读出的时刻),因为使用的是将满信号作为指示信号,所以数据的最后一个再上一次没有读完全,遗留了一个数据,因此从255开始读出。与此同时,wr_data_count 和 rd_data_count 开始计数。
验证完毕,功能正常!