原理
FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写方式。与 ROM 或 RAM 的按地址读写方式不同,FIFO 的读写遵循“先进先出”的原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以FIFO存储器没有地址线。
FIFO 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用,后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了FIFO。FIFO 根据读写时钟是否相同,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步FIFO),SCFIFO 的读写为同一时钟,应用在同步时钟系统中;DCFIFO 的读写时钟不同,应用在异步时钟系统中。
SCFIFO
vivado配置IP
实例化
`timescale 1ns/1ns
module fifo
(
input wire sys_clk , //系统时钟50Mhz
input wire sys_rst_n , //复位信号
input wire [7:0] pi_data , //输入顶层模块的数据
//要写入到FIFO中的数据
input wire pi_flag , //输入数据有效标志信号
//也作为FIFO的写请求信号
input wire rd_en , //FIFO读请求信号
output wire [7:0] po_data , //FIFO读出的数据
output wire empty , //FIFO空标志信号,高有效
output wire full , //FIFO满标志信号,高有效
output wire [7:0] data_count //FIFO中存在的数据个数
);
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
scfifo_256x8 scfifo_256x8_inst
(
.clk (sys_clk ), // input clk
.srst (~sys_rst_n), // input srst
.din (pi_data ), // input [7 : 0] din
.wr_en (pi_flag ), // input wr_en
.rd_en (rd_en ), // input rd_en
.dout (po_data ), // output [7 : 0] dout
.full (full ), // output full
.empty (empty ), // output empty
.data_count(data_count) // output [7 : 0] data_count
);
endmodule
仿真
`timescale 1ns/1ns
module tb_fifo();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg sys_clk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rd_en ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
//wire define
wire [7:0] po_data ;
wire empty ;
wire full ;
wire [7:0] data_count ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化系统时钟、复位
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#200;
sys_rst_n <= 1'b1;
end
//sys_clk:模拟系统时钟,每10ns电平翻转一次,周期为20ns,频率为50Mhz
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:输入数据有效标志信号,也作为FIFO的写请求信号
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:输入顶层模块的数据,要写入到FIFO中的数据
always@(posedge sys_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_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;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------fifo_inst------------------------
fifo fifo_inst
(
.sys_clk (sys_clk ), //input sys_clk
.sys_rst_n (sys_rst_n ), //input sys_rst_n
.pi_data (pi_data ), //input [7:0] pi_data
.pi_flag (pi_flag ), //input pi_flag
.rd_en (rd_en ), //input rd_en
.po_data (po_data ), //output [7:0] po_data
.full (full ), //output full
.empty (empty ), //output empty
.data_count (data_count ) //output [7:0] data_count
);
endmodule
波形
full、data_count 信号的状态:我们可以看到当 pi_flag 为高且 pi_data 为 255 的同时full 满标志信号拉高了,说明 FIFO 的存储空间已经满了,而 data_count 信号也从 255 变成了 0,因为产生的 SCFIFO IP 核中 data_count 的位宽是 8bit 的,而十进制 256 需要 9bit 才能完全显示,这样最高位就无法显示出来,所以 data_count 的值显示为 0。
DCFIFO
配置异步FIFO的IP
实例化
`timescale 1ns/1ns
/
module fifo
(
input wire wr_clk , //同步于FIFO写数据的时钟50MHz
input wire [7:0] pi_data , //输入顶层模块的数据,要写入到FIFO中
//的数据同步于wrclk时钟
input wire pi_flag , //输入数据有效标志信号,也作为FIFO的
//写请求信号,同步于wrclk时钟
input wire rd_clk , //同步于FIFO读数据的时钟25MHz
input wire rd_en , //FIFO读请求信号,同步于rdclk时钟
output wire [15:0] po_data , //FIFO读出的数据,同步于rdclk时钟
output wire empty , //空标志信号,高有效,
output wire full , //满标志信号,高有效,
output wire [6:0] rd_data_count,//FIFO读端口中存在的数据个数,
//同步于rdclk时钟
output wire [7:0] wr_data_count //FIFO写端口中存在的数据个数,
//同步于wrclk时钟
);
//----------------------dcfifo_256x8to128x16_inst-----------------------
dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst
(
.din (pi_data), //input [7:0] din
.rd_clk (rd_clk ), //input rd_clk
.rd_en (rd_en ), //input rd_en
.wr_clk (wr_clk ), //input wr_clk
.wr_en (pi_flag), //input wr_en
.dout (po_data), //output [15:0] dout
.empty (empty ), //output empty
.full (full ), //output full
.rd_data_count (rd_data_count),//output [6:0] rd_data_count
.wr_data_count (wr_data_count) //output [7:0] wr_data_count
);
endmodule
仿真
快写慢读,跨时钟域,打两拍延迟,时钟同步
`timescale 1ns/1ns
/
module tb_fifo();
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
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 define
wire empty ;
wire full ;
wire [7:0] wr_data_count ;
wire [15:0] po_data ;
wire [6:0] rd_data_count ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//初始化时钟、复位
initial begin
wr_clk = 1'b1;
rd_clk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
#100000
sys_rst_n <= 1'b0;
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 == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:输入数据有效标志信号,也作为FIFO的写请求信号
always@(posedge wr_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:输入顶层模块的数据,要写入到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;
//如果full信号有效就立刻读,则不会看到full信号拉高,
//所以此处使用full在rd_clk时钟下打两拍后的信号
else if(full_reg1 == 1'b1)
rd_en <= 1'b1;
else if(empty == 1'b1)//当FIFO中的数据被读空时停止读取FIFO中的数据
rd_en <= 1'b0;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//------------------------fifo_inst------------------------
fifo fifo_inst
(
.wr_clk (wr_clk ), //input wr_clk
.pi_data (pi_data ), //input [7:0] pi_data
.pi_flag (pi_flag ), //input pi_flag
.rd_clk (rd_clk ), //input rd_clk
.rd_en (rd_en ), //input rd_en
.po_data (po_data ), //output [15:0] po_data
.empty (empty ), //output empty
.full (full ), //output full
.rd_data_count(rd_data_count),//output [6:0] rd_data_count
.wr_data_count(wr_data_count) //output [7:0] wr_data_count
);
endmodule
波形