目录
一:章节说明
1.1 FIFO IP简介
1.2 FIFO Generato IP 核信号框图
1.3 实验任务
二:FIFO 写模块设计
2.1 简介
2.2 模块框图
2.3 模块端口与功能描述
2.4 写模块代码
三 FIFO 读模块设计
3.1 简介
3.2 模块框图
3.3 模块端口与功能描述
3.4 读模块代码
四: 顶层模块设计
4.1 概述
4.2 模块端口与功能描述
4.3 代码编写
五 仿真测试验证实现
5.1 仿真验证代码
5.2 仿真结果
一:章节说明
1.1 FIFO IP简介
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
时所遵循的时钟,在每个时钟的上升沿触发。
这里还有两点需要大家注意:
1、“
almost_empty
”和“
almost_full
”这两个信号分别被看作“
empty
”和“
full
”的警告信号,他们相对于真正的空(
empty
)和满(
full
)都会
提前一个时钟周期拉高
。
2
、
FIFO
中,先写入的数据被置于高位,后写入的数据被置于低位,由于其先入先出的特性,所以读出的数据也是高位在前,低位在后。这一点在读写数据位宽不对等时尤为重要,例如我们写数据位宽为
8
,读数据位宽为
2
,当写入的数据为
11000111
时,读出的数据依次为
11、00、01、11
1.2 FIFO Generato IP 核信号框图
首先说明下,上图中黑色箭头表示此信号为必要信号;蓝色箭头表示此信号为可选信号;灰色箭头表示此信号为可选的边带信号。从图中我们可以了解到,当被配置为同步
FIFO
时,只使用
wr_clk
,所有的输入输出信号都同步于
wr_clk
信号。而当被配置为异步
FIFO
时,写端口和读端口分别有独立的时钟,所有与写相关的信号都是同步于写
时钟
wr_clk
,所有与读相关的信号都是同步于读时钟
rd_clk
。
这里我们对框图中的常用信号端口做一下讲解,其他很少用到的信号如果大家感兴趣的话也可以在课后打开 IP 核的数据手册进行学习,各常用端口的功能描述如下:
1.3 实验任务
本节的实验任务是使用 Vivado
生成一个异步
FIFO
,并实现以下功能:当
FIFO
为空时,向
FIFO
中写入数据,直至将
FIFO
写满后停止写操作;当
FIFO
为满时,从
FIFO
中读出数据,直到
FIFO
被读空后停
止读操作,以此向大家详细介绍一下
FIFO IP
核的使用方法。
1.4 配置FIFO步骤
详细步骤可以参考正点原子《
领航者ZYNQ 之 FPGA 开发指南
》
P584
。
网盘链接如下:
https://pan.baidu.com/s/1vXxmhg_mZm_OVg4xQeiCVQ 提取码:zdyz
二:FIFO 写模块设计
2.1 简介
首先介绍下 FIFO 写模块的设计,在
FIFO
写模块中,我们的输入信号主要有系统时钟信号(写时钟域的时钟)、系统复位信号;因为
FIFO
的写操作需要在
FIFO
完成复位后进行,所以我们还需要输入
wr_rst_busy
(写复位忙)信号来判断
FIFO
是否结束了复位状态;实验任务中我们提到了
FIFO
为空时进行写操作,因
此还需要引入一个空相关的信号,这里我们引入的是
empty
(空)信号;实验任务中我们还提到了写满了要
停止写操作,所以这里我们引入了
almost_full
(将满)信号,因为将满信号表示
FIFO
还能再进行最后一次
写操作,使用这个信号的话我们正好可以在写入最后一次数据后关闭写使能,当然引入
full(满)信号也是可以,区别只是在于这么做会在写使能关断前执行一次无效的写操作。
2.2 模块框图
2.3 模块端口与功能描述
2.4 写模块代码
module fifo_wr(
//mudule clock
input wr_clk , // 时钟信号
input rst_n , // 复位信号
//FIFO接口
input wr_rst_busy , // 写复位忙信号
input empty , // FIFO 空信号
input almost_full , // FIFO 将满信号
output reg fifo_wr_en , // FIFO 写使能
output reg [7:0] fifo_wr_data // 写入 FIFO 的数据
);
reg empty_d0;
reg empty_d1;
//因为 empty信号是和读信号的时钟同步的,对于写始终来说他是异步信号,所以要进行打拍处理
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)begin
empty_d0 <= 0;
empty_d1 <= 0;
end
else begin
empty_d0 <= empty;
empty_d1 <= empty_d0;
end
//fifo写使能信号赋值,当 FIFO 为空时开始写入,写满后停止写
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
fifo_wr_en <= 0;
else if(!wr_rst_busy)begin
if(empty_d1)
fifo_wr_en <= 1;
else if(almost_full)
fifo_wr_en <= 0;
else
fifo_wr_en <= fifo_wr_en;
end
else
fifo_wr_en <= 0;
//对 fifo_wr_data 赋值,0~254
always@(posedge wr_clk or negedge rst_n)
if(!rst_n)
fifo_wr_data <= 0;
else if(fifo_wr_en && fifo_wr_data < 254)
fifo_wr_data <= fifo_wr_data + 1;
else
fifo_wr_data <= 0;
endmodule
三 FIFO 读模块设计
3.1 简介
首先介绍下 FIFO
读模块的设计,在
FIFO
读模块中,我们的输入信号主要有系统时钟信号(读时钟域时钟)和系统复位信号;因为
FIFO
的读操作需要在
FIFO
完成复位后进行,所以我们还需要输入
rd_rst_busy(读复位忙)信号来判断
FIFO
是否结束了复位状态;实验任务中我们提到了
FIFO 为满时进行读操作,因
此还需要引入一个满相关的信号,这里我们引入的是
full
(满)信号;实验任务中我们还提到了读空了要停
止读操作,所以这里我们引入了
almost_empty
(将空)信号,因为将空信号表示
FIFO
还能再进行最后一次读操作,使用这个信号的话我们正好可以在读出最后一个数据后关闭读使能,当然引入
empty
(空)信号也
是可以,区别只是在于这么做会在读使能关断前执行一次无效的读操作。
3.2 模块框图
3.3 模块端口与功能描述
3.4 读模块代码
module fifo_rd(
//system clock
input rd_clk , //时钟信号
input rst_n , //复位信号
//FIFO接口
input rd_rst_busy , //读复位忙信号
input [7:0] fifo_rd_data, //从 FIFO 读出的数据
input full , //FIFO 满信号
input almost_empty, //FIFO 将空信号
output reg fifo_rd_en //FIFO 读使能
);
reg full_d0;
reg full_d1;
//因为 full 信号是属于 FIFO 写时钟域的,所以对 full 打两拍同步到读时钟域下
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)begin
full_d0 <= 0;
full_d1 <= 0;
end
else begin
full_d0 <= full;
full_d1 <= full_d0;
end
//对 fifo_rd_en 进行赋值,FIFO 写满之后开始读,读空之后停止读
always@(posedge rd_clk or negedge rst_n)
if(!rst_n)
fifo_rd_en <= 0;
else if(!rd_rst_busy)begin
if(full_d1)
fifo_rd_en <= 1;
else if(almost_empty)
fifo_rd_en <= 0;
end
else
fifo_rd_en <= 0;
endmodule
四: 顶层模块设计
4.1 概述
本次实验的目的是为了将 Xilinx FIFO Generato IP
核配置成一个异步
FIFO
并对其进行读写操作,因此可以给模块命名为
ip_fifo
;因为我们做的是异步
FIFO
,所以我们需要一个
PLL IP
核来输出
50MHz
的写时
钟和
100MHz
的读时钟,当然输出其它频率的时钟也是可以的;然后我们还需要一个写模块(
fifo_wr
)和
一个读模块(
fifo_rd
),写模块通过
FIFO
的状态来判断是否给出写请求信号和写数据,读模块通过
FIFO
的状态来判断是否给出读请求信号,并接收从
FIFO 中读出的数据;系统时钟和系统复位是一个完整的工
程中必不可少的输入端口信号,这里就不再多讲了。经过上述分析我们可以画出一个大致的模块框图,如
下图所示:
4.2 模块端口与功能描述
4.3 代码编写
module ip_fifo(
input sys_clk,
input sys_rst_n
);
wire locked;
wire clk_50M;
wire clk_100M;
wire rst_n;
wire wr_rst_busy;
wire empty;
wire almost_full;
wire [7 : 0]fifo_wr_data;
wire rd_rst_busy;
wire [7 : 0]fifo_rd_data;
wire fifo_rd_en;
wire fifo_wr_en;
wire almost_empty;
wire [7 : 0]rd_data_count;
wire [7 : 0]wr_data_count;
wire full;
//通过系统复位信号和时钟锁定信号来产生一个新的复位信号,代表当输出频率时钟稳定后,且复位完成后才能执行
assign rst_n = sys_rst_n & locked;
//例化 PLL IP 核
clk_wiz_0 clk_wiz_0
(
.clk_out1(clk_50M), // output clk_out1
.clk_out2(clk_100M), // output clk_out2
.locked(locked), // output locked
.clk_in1(sys_clk) // input clk_in1
);
//例化 FIFO IP 核
fifo_generator_0 your_instance_name (
.rst(~rst_n), // input wire rst
.wr_clk(clk_50M), // input wire wr_clk
.rd_clk(clk_100M), // input wire rd_clk
.din(fifo_wr_data), // input wire [7 : 0] din
.wr_en(fifo_wr_en), // input wire wr_en
.rd_en(fifo_rd_en), // input wire rd_en
.dout(fifo_rd_data), // 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
.wr_rst_busy(wr_rst_busy), // output wire wr_rst_busy
.rd_rst_busy(rd_rst_busy) // output wire rd_rst_busy
);
//例化写 FIFO 模块
fifo_wr fifo_wr(
.wr_clk(clk_50M) , // 时钟信号
.rst_n(rst_n) , // 复位信号
.wr_rst_busy(wr_rst_busy) , // 写复位忙信号
.empty(empty) , // FIFO 空信号
.almost_full(almost_full) , // FIFO 将满信号
.fifo_wr_en(fifo_wr_en) ,
.fifo_wr_data(fifo_wr_data) // 写入 FIFO 的数据
);
//例化读 FIFO 模块
fifo_rd fifo_rd(
.rd_clk (clk_100M) , //时钟信号
.rst_n (rst_n) , //复位信号
.rd_rst_busy (rd_rst_busy) , //读复位忙信号
.fifo_rd_data (fifo_rd_data) , //从 FIFO 读出的数据
.full (full) , //FIFO 满信号
.almost_empty (almost_empty) , //FIFO 将空信号
.fifo_rd_en (fifo_rd_en) //FIFO 读使能
);
endmodule
可以看出 ip_fifo
顶层模块只是例化了
FIFO IP
核(
fifo_generator_0
)、
PLL IP
(
clk_wiz_0
)、读模块(
fifo_rd
)和写模块(
fifo_wr
),其中写模块负责产生
FIFO IP
核写操作所需的所有数据、写请求等信
号;读模块负责产生
FIFO IP
核读操作所需读请求信号,并将读出的数据也连接至读模块。
因为读写模块的时钟皆来自
PLL IP
核,而
PLL IP
核需要一定的时间才能输出稳定的时钟,所以在第29
行代码中我们通过系统复位和时钟锁定来产生一个信号复位信号,使读
/
写模块及
FIFO IP
核在时钟稳定
后才进入工作状态。
五 仿真测试验证实现
5.1 仿真验证代码
`timescale 1ns / 1ps
module ip_fifo_tb();
parameter CLK_PERIOD = 20; //时钟周期 20ns
//reg define
reg sys_clk;
reg sys_rst_n;
//信号初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200;
sys_rst_n = 1'b1;
//模拟按下复位
#10000 ;
sys_rst_n = 0;
#160 ;
sys_rst_n = 1;
end
//产生时钟
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
ip_fifo u_ip_fifo (
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n)
);
endmodule
5.2 仿真结果
从仿真中可见FIFO读取设计正确。
因为RAM以及FIFO在FPGA中较为重要,所以后面还会有学习RAM以及FIFO的相关案例,敬请期待。