FPGA学习笔记:FIFO IP核的使用方法

描述

理论知识

FIFO(First In First Out, 先入先出 ),是一种数据缓冲器,用来实现数据先入先出的读写方式。数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO存储器没有地址线,有一个写端口和一个读端口。

FIFO 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步 FIFO)。后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了FIFO。

同步FIFO-SCFIFO

SCFIFO IP核配置

fifo

fifo

fifo

full:写满标志位,有效表示 FIFO 已经存储满了,此时禁止再往FIFO中写入数据,防止数据溢出丢失。当写入数据量达到FIFO设置的最大空间时,时钟上升沿写入最后一个数据同时full拉高;读取数据时随时钟上升沿触发同时拉低。

empty:读空标志位,有效表示 FIFO 中已经没有数据了,此时禁止FIFO继续再读出数据,否则读出的将是无效数据。写入数据同时拉低;读到最后一个数据同时拉高。

usedw:显示当前FIFO中已存数据个数,写第一个数据时就置1,空或满时值为0(满是因为寄存器溢出)。

almost full:几乎满标志信号,我们可以控制FIFO快要被写满的时候和full信号的作用一样。

almost empty:几乎空标志信号,我们可以控制FIFO快要被读空的时候和empty信号的作用一样。

Asynchronous clear:异步复位信号,用于清空FIFO。

Synchronous clear:同步复位信号,用于清空FIFO。

后面三个没有使用

fifo

fifo

fifo

fifo

SCFIFO IP核调用

我们需要写一个顶层模块,并通过testbench定义激励来观察信号变化,验证SCFIFO IP核。我们可以看到生成的模块文件中有以下几个端口,顶层模块需要进行实例化

fifo

输入信号有:sys_clk、输入256个8bit的数据pi_data(值为十进制0~255),输入数据有效的标志信号pi_flag,写请求信号rdreq。输出信号有:读取的数据po_data、空标志信号empty、满标志信号full、指示FIFO中存在数据个数的信号usedw。

编写代码

module fifo(
input wire sys_clk , 
input wire [7:0] pi_data ,
input wire pi_flag , 
input wire rdreq , 
output wire [7:0] po_data ,
 output wire empty , 
 output wire full , 
 output wire [7:0] usedw 
 );


 scfifo_256x8 scfifo_256x8_inst(
 .clock (sys_clk ), 
 .data (pi_data ), 
 .rdreq (rdreq ), 
 .wrreq (pi_flag ),
 .empty (empty ), 
 .full (full ), 
 .q (po_data ), 
 .usedw (usedw ) 
 );
 endmodule

fifo

编写testbench

//reg define
reg sys_clk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rdreq ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;


 //wire define
 wire [7:0] po_data ;
 wire empty ;
 wire full ;
 wire [7:0] usedw ;


 //初始化系统时钟、复位
 initial begin
 sys_clk = 1'b1;
 sys_rst_n <= 1'b0;
 #100;
 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;
 else if((cnt_baud == 2'd0) && (rdreq == 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;
 else if((pi_data == 8'd255) && (pi_flag == 1'b1))
 pi_data <= 8'b0;
 else if(pi_flag == 1'b1) 
 pi_data <= pi_data + 1'b1;


 //rdreq:FIFO读请求信号
 always@(posedge sys_clk or negedge sys_rst_n)
 if(sys_rst_n == 1'b0)
 rdreq <= 1'b0;
 else if(full == 1'b1) 
 rdreq <= 1'b1;
 else if(empty == 1'b1) 
 rdreq <= 1'b0;


 fifo fifo_inst(
 .sys_clk (sys_clk ), 
 .pi_data (pi_data ),
 .pi_flag (pi_flag ), 
 .rdreq (rdreq ),
 .po_data (po_data ), 
 .empty (empty ), 
 .full (full ), 
 .usedw (usedw ) 
 );


 endmodule

初始化:初始时钟为高电平,复位有效,延迟100ns后复位释放

模拟时钟:每隔10ns翻转,时钟周期20ns,频率50MHz

输入间隔计数器cnt_baud:从0-3计数,复位和溢出归0,其他情况+1,这里溢出判断条件(cnt_baud=11)用的是位与为1

输入有效标志信号pi_flag:也是写请求信号,变化条件是时钟上升沿和复位下降沿。复位有效时归0;计数0且没有读请求时拉高相当于四个时钟周期产生一次;其他情况归0

输入数据pi_data:是要写到FIFO中的数据,变化条件是时钟上升沿和复位下降沿。复位有效时归0;输入255且pi_flag有效时归0;其他情况+1,意味着输入数据从0-255循环

读请求rdreq:变化条件是时钟上升沿和复位下降沿。复位有效时归0;full信号拉高时也拉高说明存满该读了;empty拉高时就归0说明读空禁读了

实例化

波形变化

fifo

我们选择的是普通模式,读出数据比读使能晚一拍fifo

fifo

对比一下"先出数据FIFO模式"的波形,就可以看出延迟不延迟一拍的区别,这里没有演示,实际上只需要在下面这一步时选择先出数据模式即可

fifo

fifo

异步FIFO-SCFIFO

DCFIFO IP核配置

命名为"dcfifo_256x8to128x16",我们调用的dcfifo是输入256个深度8位宽、输出128个深度16位宽

fifo

fifo

fifo

fifo

fifo

fifo

fifo

DSCFIFO IP核调用

我们需要写一个顶层模块,并通过testbench定义激励来观察信号变化,验证DCFIFO IP核。我们可以看到生成的模块文件中有以下几个端口,顶层模块需要进行实例化

fifo

输入信号 :50MHz写时钟wrclk,输入256个8bit的数据pi_data(值为十进制0~255),输入数据有效的标志信号pi_flag,25MHz的读时钟rdclk,写请求信号rdreq。 输出信号 :同步于wrclk的空标志信号wrempty,同步于wrclk的满标志信号wrfull,同步于wrclk指示FIFO中存在数据个数的信号wrusedw,从FIFO中读取的数据po_data,同步于rdclk的FIFO空标志信号rdempty,同步于rdclk 的FIFO满标志信号rdfull,同步于rdclk指示FIFO中存在数据个数的信号rdusedw。

编写代码

module fifo
(
//同步于FIFO写时钟
input wire wrclk , 
input wire [7:0] pi_data , 
input wire pi_flag , 
 //同步于FIFO读时钟
 input wire rdclk , 
 input wire rdreq , 
 //同步于FIFO写时钟
 output wire wrempty ,
 output wire wrfull , 
 output wire [7:0] wrusedw ,
 //同步于FIFO读时钟
 output wire [15:0] po_data ,
 output wire rdempty , 
 output wire rdfull , 
 output wire [6:0] rdusedw 
 );


 dcfifo_256x8to128x16 dcfifo_256x8to128x16_inst
 (
 .data (pi_data), //input [7:0] data
 .rdclk (rdclk ), //input rdclk
 .rdreq (rdreq ), //input rdreq
 .wrclk (wrclk ), //input wrclk
 .wrreq (pi_flag), //input wrreq
 .q (po_data), //output [15:0] q
 .rdempty(rdempty), //output rdempty
 .rdfull (rdfull ), //output rdfull
 .rdusedw(rdusedw), //output [6:0] rdusedw
 .wrempty(wrempty), //output wrempty
 .wrfull (wrfull ), //output wrfull
 .wrusedw(wrusedw) //output [7:0] wrusedw
 );


 endmodule

编写testbench

`timescale 1ns/1ns
module tb_fifo();


//reg define
reg wrclk ;
reg [7:0] pi_data ;
reg pi_flag ;
reg rdclk ;
reg rdreq ;
reg sys_rst_n ;
reg [1:0] cnt_baud ;
reg wrfull_reg0 ;
reg wrfull_reg1 ;


//wire define
wire wrempty ;
wire wrfull ;
wire [7:0] wrusedw ;
wire [15:0] po_data ;
wire rdempty ;
wire rdfull ;
wire [6:0] rdusedw ;


//初始化时钟、复位
initial begin
wrclk = 1'b1;
rdclk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
end


//wrclk:模拟FIFO的写时钟,每10ns电平翻转一次,周期为20ns,频率为50MHz
always #10 wrclk = ~wrclk;
//rdclk:模拟FIFO的读时钟,每20ns电平翻转一次,周期为40ns,频率为25MHz
always #20 rdclk = ~rdclk;


//cnt_baud:计数从0到3的计数器,用于产生输入数据间的间隔
always@(posedge wrclk 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 wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rdreq == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;


//pi_data:输入顶层模块的数据,要写入到FIFO中的数据
always@(posedge wrclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1) 
pi_data <= pi_data + 1'b1;


//将同步于rdclk时钟的写满标志信号wrfull在rdclk时钟下打两拍
always@(posedge rdclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
wrfull_reg0 <= 1'b0;
wrfull_reg1 <= 1'b0;
end
else
begin
wrfull_reg0 <= wrfull;
wrfull_reg1 <= wrfull_reg0;
end


//rdreq:FIFO读请求信号同步于rdclk时钟
always@(posedge rdclk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rdreq <= 1'b0;


//如果wrfull信号有效就立刻读,则不会看到rd_full信号拉高,
//所以此处使用wrfull在rdclk时钟下打两拍后的信号
else if(wrfull_reg1 == 1'b1)
rdreq <= 1'b1;
else if(rdempty == 1'b1)//当FIFO中的数据被读空时停止读取FIFO中的数据
rdreq <= 1'b0;

 fifo fifo_inst(
 .wrclk (wrclk ), 
 .pi_data(pi_data),
 .pi_flag(pi_flag),
 .rdclk (rdclk ),
 .rdreq (rdreq ), 
 .wrempty(wrempty), 
 .wrfull (wrfull ), 
 .wrusedw(wrusedw), 
 .po_data(po_data), 
 .rdempty(rdempty), 
 .rdfull (rdfull ), 
 .rdusedw(rdusedw) 
 );


 endmodule

初始化:和同步fifo的区别在于读写时钟是异步的,需要初始化两个时钟

时钟模拟:写时钟50MHz每隔10ns翻转一次,读时钟25MHz每隔20ns翻转一次

cnt_baud:计数器是基于写时钟的,将变化条件中时钟上升沿改为写时钟的上升沿

pi_flag:输入数据有效标志信号,基于写时钟,同上

pi_data:输入数据,基于写时钟,同上

wrfull:写满标志信号,基于读时钟,变化条件是读时钟上升沿和复位下降沿。wrfull在读时钟下打两拍,(always块中的语句是顺序执行的,使用两个寄存器b,c,令b=输入a,c=b,并输出c,那么c相对于a而言波形不会产生变化,只是有两个时钟周期的延迟,这个就叫打拍,使用几个寄存器就是延迟几个周期就是打几拍),这里打两拍的原因是如果wrfull有效立刻就读,就看不到rd_full拉高

rdreq:读请求信号,基于读时钟,变化条件是读时钟上升沿和复位下降沿。复位时归0;写满信号打两拍之后的信号拉高就拉高表示写满需要读,读空信号拉高就拉低表示读空不能读

波形变化

fifo

可以看到当pi_flag为高且pi_data为255的同时wrfull满标志信号先拉高了,延后一段时间rdfull满标志信号也拉高了,说明FIFO的存储空间已经满了。wrfull满标志信号和rdfull满标志信号同步于 不同的时钟 ,所以拉高的时间不同步。

还可以看到wrusedw信号是8位的计数到255,而rdusedw信号是7位的计数到127,因为输入是8btit的,输出是16bit的,8256=16128。wrusedw信号从255变成了0、rdusedw信号从127变成0的原因和SCFIFO中的情况一样,都是因为数据存储满了,FIFO内部的计数器溢出所导致的。

另外可以看出读出的16bit数据,是两次的输入8bit数据拼凑而成,先输入的在低位,后输入的在高位,例如输入00,01,02,03...,两次输入的数据拼凑为01_00,03_02进行输出。

如果不打两拍会发生什么呢?我们来看波形

fifo

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分