FPGA学习笔记——FIFO

笔记根据正点原子官方教学视频第21讲

时间:2025/4/7

【第一期】手把手教你学领航者&启明星ZYNQ之FPGA开发篇【真人出镜】FPGA教学视频教程_哔哩哔哩_bilibili

 有错误的地方希望可以告诉博主

FIFO是什么

        FIFO(First In First Out):即先进先出,FIFO一般指的是对数据的存储具有先进先出特性的一个缓存器,常被用于数据的缓存(如数据读写带宽不同步),或者高速异步数据的交互也即所谓的跨时钟域信号传递。

FIFO的分类

        同步FIFO:指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作。

        异步FIFO:指读写时钟不一致,读写时钟是互相独立的。

FIFO的端口示意图

        下图中黑色为必须信号、蓝色为可选信号、灰色为可选的附加部分。

                部分常用信号解释如下:

写时钟域信号介绍读时钟域信号介绍
wr_clk写时钟,用于同步写入操作。rd_clk读时钟,用于同步读取操作。
wr_en写使能,当该信号为高时,允许数据写入FIFO。rd_en读使能,当该信号为高时,允许数据从FIFO读出。
din数据输入。dout数据输出。
full满标志,当FIFO已满时,该信号为高。empty空标志,当FIFO为空时,该信号为高。
almost_full几乎满标志,当FIFO接近满时,该信号为高。almost_empty几乎空标志,当FIFO接近空时,该信号为高。
Prog_full可编程满标志,用于用户自定义的满状态。prog_empty可编程空标志,用于用户自定义的空状态。
wr_ack写确认,表示写操作已被确认。valid有效信号,表示输出数据有效。
wr_data_count写数据计数,表示FIFO中已写入的数据量。rd_data_count读数据计数,表示FIFO中已读取的数据量。
overflow上溢信号,当写入已满的FIFO时产生。underflow下溢信号,当读取空的FIFO时产生。

FIFO时序 

同步FIFO读写时序

        下图没有给出 din 信号,din 信号数据用于在写使能时写入 FIFO。

        解释如下: 

        假设 FIFO 可以装入四个数据。当 wr_en 为高电平时,写使能,开始写入数据,写入一个数据后 FIFO 不为空,空标志 empty 拉低。接着 rd_en 拉高,读使能,读出一个数据,此时 FIFO 中只有一个数据,所有 almost_full 标志位仍为高电平。当写入第三个数据后,此时 FIFO 中包含两个数据,almost_full 标志位拉低。

        当 wr_en 再次拉高时,写使能,再次写入数据。写入一个数据后,此时 FIFO 中包含三个数据,almost_full 信号拉高。紧接着同时写入一个数据和读出一个数据,almost_full 信号不变,当再写入一个数据后,FIFO 被写满,full 信号被拉高。

异步FIFO写时序 

        D4 是无法写入的,因为写入数据 D3 后,FIFO 的 full 信号被拉高,表示 FIFO 已经写满了。因为 D4 及之后的数据无法写入,故也没有返回 wr_ack 信号。同时 overflow 信号拉高,表示上溢(即 FIFO 已经写满了,但还在写)了。当 FIFO 写满后,almost_full 信号通常会保持拉高状态,直到FIFO中的数据量减少到阈值以下。

异步FIFO读时序图(正常模式)

        读出 D0 后,在读出 D1 时,almost_empty 被拉高,表示 FIFO 中还剩下一个数据可读。在读出 D2 时,empty 拉高表示 FIFO 中的内容被全部读出。后续继续再读取时,valid 信号拉低,表示数据无效,且 underflow 信号拉高表示发生了下溢(即没有数据后还在读取)。当 FIFO 读空后,almost_empty 信号通常也会保持拉高状态,直到有新的数据写入 FIFO,使得数据量超过阈值。

异步FIFO读时序图(预读模式)

        相当于正常模式提前一拍输出数据。

实验任务

        本节的实验任务是使用 Vivado 生成一个异步 FIFO,并实现以下功能:当FIFO为空时,向FIFO中写入数据,直至将FIFO写满后停止写操作;当FIFO为满时,从FIFO中读出数据,直到FIFO被读空后停止读操作。如:将FIFO设置的深度和宽度分别为256和8进行读写测试。

FIFO IP 核

        详细配置说明请一定要参照官方配置文档。 

        具体 IP 核参数使用请参照手册(FIFO Generator v13.2 Product Guide (PG057) • 查看器 • AMD 技术信息门户网站) ,查看手册是必要的能力,这里不再对更多具体参数进行详细解释。

        这里对 wr_rst_busy rd_rst_busy 信号做一个解释。

        wr_rst_busy 表示写端口的复位操作正在进行中。当写端口接收到复位信号时,wr_rst_busy 会被拉高,表示复位操作正在进行。复位完成后,wr_rst_busy 会自动拉低,表示复位完成。

        rd_rst_busy 表示读端口的复位操作正在进行中。当读端口接收到复位信号时,rd_rst_busy 会被拉高,表示复位操作正在进行。复位完成后,rd_rst_busy 会自动拉低,表示复位完成。

        这样做的好处是确保FIFO模块在复位完成后才能正常工作,避免因复位过程中的状态不稳定导致数据错误。不过通常在实际使用中,这两个信号经常不会使用,因为 FIFO 一般不会在项目启动时就开始工作,一般当 FIFO 被使用时,复位操作早就已经完成了。

模块框图

        因为读模块和写模块的时钟独立,故需要使用 PLL 模块来得到两个时钟。

         写模块的框图如下:

        为什么不使用 full 信号作为满标志?因为当 full 信号拉高时,要在下一个时钟周期才可以采集到,这会导致 fifo_wr_en 使能信号慢一个周期被拉低。 当然使用 full 信号也是可以的,通过组合逻辑的方式,与上 full 信号的非,也可以做到及时拉低使能信号。

        同理,读模块的框图如下:

        最后,需要将这些信号接入 FIFO IP核例化的模块中,系统框图如下所示。

模块时序 

读模块时序

        对于读模块而言,full 信号属于异步信号,需要打两拍来进行同步。 

写模块时序 

         对于写模块而言,empty 信号属于异步信号,需要打两拍来进行同步。

代码编写

        fifo_wr 写模块代码如下:

module fifo_wr(
    input                       clk_50m     , // 写时钟
    input                       rst_n       ,
    // FIFO
    input                       empty       ,
    input                       almost_full ,
    input                       wr_rst_busy ,
    
    output      reg             fifo_wr_en  ,
    output      reg     [7:0]   fifo_wr_data
    
);

reg     empty_d0;
reg     empty_d1;

// 对empty打两拍同步到写时钟域下
always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n) begin
        empty_d0 <= 1'b0;
        empty_d1 <= 1'b0;
    end
    else begin
        empty_d0 <= empty;
        empty_d1 <= empty_d0;
    end
end

// 对写使能信号进行赋值
always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n)
        fifo_wr_en <= 1'b0;
    else if (!wr_rst_busy) begin
        if (empty_d1)
            fifo_wr_en <= 1'b1;
        else if (almost_full)
            fifo_wr_en <= 1'b0;
    end
end

// 对fifo_wr_data赋值,0-254
always @(posedge clk_50m or negedge rst_n) begin
    if (!rst_n)
        fifo_wr_data <= 8'd0;
    else if (fifo_wr_en && fifo_wr_data < 8'd254)
        fifo_wr_data <= fifo_wr_data + 8'd1;
    else
        fifo_wr_data <= 8'd0;
end


endmodule

        fifo_rd 读模块代码如下:

module fifo_rd(
    input                   clk_100m    , // 读时钟
    input                   rst_n       ,
    // FIFO
    input   reg     [7:0]   fifo_rd_data, 
    input                   full        , 
    input                   almost_empty, 
    input                   rd_rst_busy , 
    
    output  reg             fifo_rd_en    
    
);

reg     full_d0;
reg     full_d1;

// 对full打两拍同步到读时钟域下
always @(posedge clk_100m or negedge rst_n) begin
    if (!rst_n) begin
        full_d0 <= 1'b0;
        full_d1 <= 1'b0;
    end
    else begin
        full_d0 <= full;
        full_d1 <= full_d0;
    end
end 

// 对fifo_rd_en进行赋值,FIFO写满之后开始读,读空之后停止
always @(posedge clk_100m or negedge rst_n) begin
    if (!rst_n)
        fifo_rd_en <= 1'b0;
    else if (!rd_rst_busy) begin
        if (full_d1)
            fifo_rd_en <= 1'b1;
        else if (almost_empty)
            fifo_rd_en <= 1'b0;
    end
    
end

endmodule

        顶层模块代码如下:

module ip_fifo(
    input       sys_clk,
    input       sys_rst_n
);

wire            clk_50m;
wire            clk_100m;
wire            locked;
wire            rst_n;

wire            empty;
wire            almost_full;
wire            wr_rst_busy;
wire            fifo_wr_en;
wire    [7:0]   fifo_wr_data;


wire            full;
wire            almost_empty;
wire            rd_rst_busy;
wire            fifo_rd_en;
wire    [7:0]   fifo_rd_data;

wire    [7:0]   rd_data_count;
wire    [7:0]   wr_data_count;

assign  rst_n = sys_rst_n & locked;

clk_wiz_0 instance_name
(
   
    .clk_out1   (clk_50m    ),     
    .clk_out2   (clk_100m   ),    
    
    .locked     (locked     ),       
   
    .clk_in1    (sys_clk    )
);      

fifo_wr u_fifo_wr(
    .clk_50m        (clk_50m       ), 
    .rst_n          (rst_n         ),
                        
    .empty          (empty         ),
    .almost_full    (almost_full   ),
    .wr_rst_busy    (wr_rst_busy   ),
                    
    .fifo_wr_en     (fifo_wr_en    ),
    .fifo_wr_data   (fifo_wr_data  )
    
);


fifo_rd u_fifo_rd(
    .clk_100m       (clk_100m       ), 
    .rst_n          (rst_n          ),
                   
    .fifo_rd_data   (fifo_rd_data   ), 
    .full           (full           ), 
    .almost_empty   (almost_empty   ), 
    .rd_rst_busy    (rd_rst_busy    ), 
                    
    .fifo_rd_en     (fifo_rd_en     )  
    
);


fifo_generator_0 your_instance_name (
  .rst                          (~rst         ), // 取反是因为FIFO的复位是高有效                  
  .wr_clk                       (clk_50m      ),                
  .rd_clk                       (clk_100m     ),                
  .din                          (fifo_wr_data ),                      
  .wr_en                        (fifo_wr_en   ),                  
  .rd_en                        (fifo_rd_en   ),                  
  .dout                         (fifo_rd_data ),                    
  .full                         (full         ),                    
  .almost_full                  (almost_full  ),      
  .empty                        (empty        ),                  
  .almost_empty                 (almost_empty ),    
  .rd_data_count                (rd_data_count),  
  .wr_data_count                (wr_data_count),  
  .wr_rst_busy                  (wr_rst_busy  ),      
  .rd_rst_busy                  (rd_rst_busy  )      
);

endmodule

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值