目录
说明:
本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,写博客记录自己的学习。
开发软件:ise14.7 仿真:modelsim 10.5
一、FIFO IP核简介
FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写。与 ROM 或 RAM 的按地址读写方式不同, FIFO 的读写遵循“先进先出”的原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO存储器没有地址线。 FIFO 有一个写端口和一个读端口外部无需使用者控制地址,使用方便。
FIFO 存储器主要是作用为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了异步FIFO,示意图如下。 FIFO 根据读写时钟是否相同,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步FIFO),SCFIFO 的读写为同一时钟,应用在同步时钟系统中; DCFIFO 的读写时钟不同,应用在异步时钟系统中。
二、SCFIFO IP核配置
配置过程比较简单,故不展示这里注意选择时钟统一。
三、SCFIFO IP核调用
实验目标:实现数据0-255的数据缓冲。
3.1 顶层设计
3.2 RTL代码
`timescale 1ns/1ns
module fifo
(
input wire sys_clk ,
input wire [7:0] pi_data , //输入数据
input wire pi_flag , //输入数据有效标志信号
input wire rd_en , //FIFO读数据有效信号
output wire empty , //FIFO空标志信号,高有效
output wire full , //FIFO满标志信号,高有效
output wire [7:0] po_data , //输出数据
output wire [7:0] data_count //FIFO中存在的数据个数
);
//调用FIFOip核
scfifo_256X8 scfifo_256X8_inst
(
.clk (sys_clk ),
.din (pi_data ),
.wr_en (pi_flag ),
.rd_en (rd_en ),
.dout (po_data ),
.full (full ),
.empty (empty ),
.data_count(data_count)
);
endmodule
3.3 仿真
仿真代码:
`timescale 1ns/1ns
module tb_fifo();
reg sys_clk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rd_en ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
wire [7:0] po_data ;
wire empty ;
wire full ;
wire [7:0] data_count ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:输入数据写请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
//每4个时钟周期且没有读请求时产生一个数据有效标志信号
else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:输入数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd225) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1) //每当pi_flag有效时产生一个数据
pi_data <= pi_data + 1'b1;
//rd_en:FIFO读请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(full == 1'b1) //当FIFO中的数据存满时,开始读取FIFO中的数据
rd_en <= 1'b1;
else if(empty == 1'b1) //当FIFO中的数据被读空时停止读取FIFO中的数据
rd_en <= 1'b0;
fifo fifo_inst
(
.sys_clk (sys_clk ),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.rd_en (rd_en ),
.po_data (po_data ),
.empty (empty ),
.full (full ),
.data_count (data_count )
);
endmodule
仿真结果:
从上图中可以看出full为高电平时,rd_en也在下一个节拍拉高,fifo开始读数据。
从上图中可以看出empty为高电平时,rd_en也在下一个节拍拉低并且pi_flag为高电平,fifo开始写数据。
四、DCFIFO IP 核配置
配置过程比较简单,故不展示这里注意选择时钟统一。
五、DCFIFO IP 核调用
实验目标:实现输入 256 个深度 8 位宽、 输出 128个深度 16 位宽的数据缓存器。
5.1 整体设计
5.2 RTL代码
`timescale 1ns/1ns
module fifo
(
input wire [7:0] pi_data , //FIFO写入的数据,同步于wrclk时钟
input wire wr_clk , //同步于FIFO 写数据 的时钟 50MHz
input wire pi_flag , //输入数据有效标志信号,同步于wr_clk时钟
output wire full , //满标志信号,高有效
output wire [7:0] wr_data_count, //FIFO写端口中存在的数据个数,
//同步于wrclk时钟
output wire [15:0] po_data , //FIFO读出的数据,同步于rdclk时钟
input wire rd_clk , //同步于FIFO 读数据 的时钟 25MHz
input wire rd_en , //FIFO读请求信号,同步于rdclk时钟
output wire empty , //空标志信号,高有效,
output wire [6:0] rd_data_count //FIFO读端口中存在的数据个数,
//同步于rdclk时钟
);
//调用FIFO ip核
dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst
(
.din (pi_data),
.rd_clk (rd_clk ),
.rd_en (rd_en ),
.wr_clk (wr_clk ),
.wr_en (pi_flag),
.dout (po_data),
.empty (empty ),
.full (full ),
.rd_data_count (rd_data_count),
.wr_data_count (wr_data_count)
);
endmodule
5.3仿真验证
仿真代码:
`timescale 1ns/1ns
module tb_fifo();
reg wr_clk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rd_clk ;
reg rd_en ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
reg full_reg0 ;
reg full_reg1 ;
wire empty ;
wire full ;
wire [7:0] wr_data_count ;
wire [15:0] po_data ;
wire [6:0] rd_data_count ;
initial begin
wr_clk = 1'b1;
rd_clk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end
//wr_clk:模拟FIFO的写时钟,每10ns电平翻转一次,周期为 20ns,频率为50MHz
always #10 wr_clk = ~wr_clk;
//rd_clk:模拟FIFO的读时钟,每20ns电平翻转一次,周期为40ns,频率为25MHz
always #20 rd_clk = ~rd_clk;
//cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
always@(posedge wr_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(cnt_baud == 2'd3)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
always@(posedge wr_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:输入顶层模块的数据,要写入到FIFO中的数据
always@(posedge wr_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
pi_data的值为0~255依次循环
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1) //每当pi_flag有效时产生一个数据
pi_data <= pi_data + 1'b1;
//将同步于rd_clk时钟的写满标志信号full在rd_clk时钟下打两拍
always@(posedge rd_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
full_reg0 <= 1'b0;
full_reg1 <= 1'b0;
end
else
begin
full_reg0 <= full;
full_reg1 <= full_reg0;//打两拍
end
//rd_en:FIFO读请求信号同步于rd_clk时钟
always@(posedge rd_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(full_reg1 == 1'b1)
rd_en <= 1'b1;
else if(empty == 1'b1)//当FIFO中的数据被读空时停止读取FIFO中的数据
rd_en <= 1'b0;
fifo fifo_inst
(
.wr_clk (wr_clk ),
.pi_data (pi_data ),
.pi_flag (pi_flag ),
.rd_clk (rd_clk ),
.rd_en (rd_en ),
.po_data (po_data ),
.empty (empty ),
.full (full ),
.rd_data_count(rd_data_count),
.wr_data_count(wr_data_count)
);
endmodule
仿真结果:
整体波形变化:
数据输入部分:
数据输出部分:
由上可知读写位宽不同的FIFO,数据输入输出顺序是当输入位宽小于输出位宽时,先入存高位后入存低位。反之则,先入存低位后入存高位。
学完fifo,简单总结三点:
1. 合理控制 IP核大小(数据深度与位宽),避免资源浪费。
2. 注意利用好 FIFO 的关键信号,如读写时钟、读写使能、空满标志信号。
3. 写数据的总带宽一定要等于读数据的总带宽,否则一定会存在写满或读空的现象。