从零开始的FPGA学习12-FIFO(同步FIFO、异步FIFO)

  • Post author:
  • Post category:其他




原理

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



波形

请添加图片描述