当前位置: 首页 > news >正文

FPGA FIFO一篇完整解释

1.FIFO 简介

FIFO (先入先出, First In First Out )存储器,在 FPGA 和数字 IC 设计中非常常用。 根据接入的时钟信号,可以分为同步 FIFO 和异步 FIFO 。

FIFO 底层基于双口 RAM,同步 FIFO 的读写时钟一致,异步 FIFO 读时钟和写时钟不同。同步时钟主要应用于速率匹配(数据缓冲),类似于乒乓存储提高性能的思想,可以让后级不必等待前级过多时间; 异步 FIFO 主要用于多 bit 信号的跨时钟域处理。

本文讨论同步 FIFO 的结构及控制逻辑设计,并给出代码。

2.同步 FIFO 接口

对于同步 FIFO ,包含必要的接口如下图所示:

(1) clk : 时钟信号,读写共用;

(2) rst_n : 复位信号,视具体设计和芯片采用同步复位还是异步复位,此处默认使用异步低电平复位;

(3) wdata : 写数据信号,信号后带“ \ ”表示是多 bit 信号;

(4) rdata : 读数据信号,信号后带“ \ ”表示是多 bit 信号;

(5) wfull : 满信号,指示 FIFO 写满了,不能再写了,如果再写会覆盖掉还没读出的写入数据,造成数据丢失;

(6) rempty : 空信号,指示 FIFO 读空了,不能在读了,如果再读相当于有的数据重复读了第二遍,造成数据错误;

(7) winc : 写使能信号,写使能有效时表示希望能写入数据;

(8) rinc : 读使能信号,读使能有效时表示希望能读出数据;

3.双口 RAM 接口

在实现 FIFO 时,无论是同步 FIFO 还是异步 FIFO ,通常会通过双口 RAM ( Dual Port RAM )并添加一些必要的逻辑来实现。双口 RAM 的接口如下图所示。

左侧全部是写时钟域的,包括写时钟、写数据、写地址和写使能信号;

右侧全部是读时钟域的,包括读时钟、读数据、读地址和读使能信号;

4.基于双口 RAM 的同步 FIFO 结构

根据同步 FIFO 的接口和双口 RAM 的接口,在借助双口 RAM 实现同步 FIFO 时,如下图所示结构,只需要加入读、写控制逻辑即可。在写逻辑中,用于产生写地址和写满信号; 在读逻辑中,用于产生读地址和读空信号。 读写控制逻辑还需要受到读写使能信号的控制。

5.读写地址产生逻辑

读写地址什么时候能够递增?

显然,对于写地址必须满足:

(1) 写使能有效(要写入);

(2) 没写满(能写入);

即:

    always @ (posedge clk or negedge rst_n)beginif(~rst_n) beginwaddr <= 'b0;endelse beginif( winc && ~wfull ) beginwaddr <= waddr + 1'b1;endelse beginwaddr <= waddr;endendend

    对于读地址必须满足:

    (1) 读使能有效(要读出);

    (2) 没读空(能读出);

    即:

      always @ (posedge clk or negedge rst_n)beginif(~rst_n) beginraddr <= 'b0;endelse beginif( rinc && ~rempty ) beginraddr <= raddr + 1'b1;endelse beginraddr <= raddr;endendend

      6.空满信号产生逻辑

      搞定了读写地址的控制逻辑,还差最后一步也是最关键的信号:空满信号如何产生。

      空: 读空,读地址追上写地址;

      满: 写满,写地址追上读地址。

      问题来了: 怎么判地址断追上了呢? 如果地址相等那应该是追上了,即 raadr == waddr 或者 wddr == raddr 。 如果按照这种判断,显然这两个地址追上对方的判断是等效的,无法区分出来到底是写追上读还是读追上写。

      可以考虑: 使用 1 个标志位 flag 来额外指示写追上读还是读追上写。

      参考前人的文献,判断空满的方式有多种,非常常用的一种是 Clifford E. Cummings 文章中提到的扩展 1 bit 的读写地址方法,也就是说,将前面提到的 flag 指示信号和原本 N 位的读写地址结合,使用 N+1 位的读写地址,其中最高位用于判断空满信号,其余低位还是正常用于读写地址索引。

      以一个 4 深度的 FIFO 实例来说明, 4 深度原本需要 2 bit 的读写地址,现在扩展成 3 bit 。

      使用低 2 位来进行双口 RAM 的地址索引,高位用于判断空满。 对于空信号,可以知道当 FIFO 里没有待读出的数据时产生。也就是说,此时读追上了写,把之前写的数据刚刚全部都出,读地址和写地址此时指向相同的位置,读地址 - 写地址 =0,即

        raddr == waddr

        对于写满信号,当写入后还没被读出的数据恰好是 FIFO 深度的时候,产生满信号,即写地址 - 读地址 = FIFO 深度 = 4。 对照下图可以发现,此时对于双口 RAM 的 2 bit 的地址来说,读写地址一致; 对于最高位来所,写是 1 而读是 0 。

        再考虑下图所示的一种情况,写入待读出的数据仍然是 4 个,此时也是 4 深度的 FIFO 已经满了。 读写地址的低位相同,高位是写 0 读 1 。

        对于写满的 2 种情况,总结下来,都是低位相同,最高位相反。

        即:

          raddr[N] = = ~waddr[N]raddr[N-1:0] = = waddr[N-1:0]

          也就是:

            raddr == {~waddr[N], waddr[N-1:0]}

            所以,空满逻辑产生的代码为:

              always @ (posedge clk or negedge rst_n)beginif(~rst_n) beginwfull <= 'b0;rempty <= 'b0;endelse beginwfull <= (raddr == {~waddr[ADDR_WIDTH], waddr[ADDR_WIDTH-1:0]});rempty <= (raddr == waddr);endend

              7.全部代码

                `timescale 1ns/1ns/****************************/// 作者:FPGA探索者/****************************/module sfifo#(parameter WIDTH = 8,parameter DEPTH = 16)(input clk ,input rst_n ,input winc ,input rinc ,input [WIDTH-1:0] wdata ,output reg wfull ,output reg rempty ,output wire [WIDTH-1:0] rdata);// 用localparam定义一个参数,可以在文件内使用localparam ADDR_WIDTH = $clog2(DEPTH);reg [ADDR_WIDTH:0] waddr;reg [ADDR_WIDTH:0] raddr;always @ (posedge clk or negedge rst_n) beginif(~rst_n) beginwaddr <= 'b0;endelse beginif( winc && ~wfull ) beginwaddr <= waddr + 1'b1;endelse beginwaddr <= waddr;endendendalways @ (posedge clk or negedge rst_n) beginif(~rst_n) beginraddr <= 'b0;endelse beginif( rinc && ~rempty ) beginraddr <= raddr + 1'b1;endelse beginraddr <= raddr;endendendalways @ (posedge clk or negedge rst_n) beginif(~rst_n) beginwfull <= 'b0;rempty <= 'b0;endelse beginwfull <= (raddr == {~waddr[ADDR_WIDTH], waddr[ADDR_WIDTH-1:0]});rempty <= (raddr == waddr);endend// 带有 parameter 参数的例化格式dual_port_RAM#(.DEPTH(DEPTH),.WIDTH(WIDTH))dual_port_RAM_U0(.wclk(clk),.wenc(winc),.waddr(waddr[ADDR_WIDTH-1:0]),.wdata(wdata),.rclk(clk),.renc(rinc),.raddr(raddr[ADDR_WIDTH-1:0]),.rdata(rdata));endmodule/**************RAM 子模块*************/module dual_port_RAM #(parameter DEPTH = 16,parameter WIDTH = 8)(input wclk,input wenc,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。,input [WIDTH-1:0] wdata //数据写入,input rclk,input renc,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。,output reg [WIDTH-1:0] rdata //数据输出);reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];always @(posedge wclk) beginif(wenc)RAM_MEM[waddr] <= wdata;endalways @(posedge rclk) beginif(renc)rdata <= RAM_MEM[raddr];endendmodule
                http://www.jsqmd.com/news/1138002/

                相关文章:

              • PIC18F8520驱动WS2812:硬件SPI与DMA的精准控制
              • 大鼠骨髓间充质干细胞(BMMSCs),云克隆现货可订,从武汉到全球,数十个国家的实验室已经在用
              • 工业级条码扫描系统架构与优化实践
              • 呵护一生模式商城软件开发(现成案例)
              • MAX9744与PIC18F4455构建高效D类音频放大器方案
              • Agentic AI与提示工程架构:构建医院门诊智能体协同系统的实战解析
              • 如何快速掌握Wwise音频处理:面向游戏开发者的完整实战指南
              • PIC18F86J55与IS31FL3731的LED矩阵控制方案
              • 计算机Java毕设实战-基于前后端分离的保姆服务预约管理系统的设计与实现 基于 SpringBoot 的智慧家政服务平台【完整源码+LW+部署说明+演示视频,全bao一条龙等】
              • 如何在5分钟内掌握UE4SS:无需源码的Unreal Engine游戏修改终极指南
              • 智能代码审查实战:用AI辅助Java后端代码质量管控
              • MediaCrawler:5分钟上手,一站式采集小红书、抖音、B站等主流平台数据
              • Codex CLI 国内怎么安装配置?从环境准备到跑通项目的完整教程
              • UOS Libvirt Exporter源码解析:深入理解Go语言实现的监控采集器
              • 实战指南:3步掌握LibreCAD开源CAD软件,告别商业软件依赖
              • 员工分布不同城市管不过来?跨区域灵活用工选人力资源服务看四个硬指标
              • 偶信DVL 300K可适配ROV、AUV水下航行器各类复杂作业工况吗?
              • Postman进阶指南:电商API测试自动化与持续集成实践
              • 每日 AI 研究简报 · 2026-07-06
              • 英雄联盟Akari助手:终极免费开源游戏效率提升工具完整指南
              • R-CNN 到 Faster R-CNN 演进:3 大核心改进与 10 倍速度提升的架构解析
              • AI绘画批量生成实战:从队列模式到生产流水线设计
              • 132.带限位报警与急停联锁的智能分拣流水线 PLC 设计
              • AI登录验证技术在中考系统中的应用与公平性探讨
              • Python爬虫经典案例第88篇:在线票务平台爬取:大麦网数据采集实战
              • 3分钟搞定全网热门资源:res-downloader视频号批量下载终极方案
              • VBrowser-Android解决方案:为移动视频爱好者打造的智能缓存革新体验
              • Vuforia 10.20 图像识别性能实测:5种图片类型对识别率与稳定性的影响
              • Camera Shakify终极指南:为Blender动画注入真实动态相机效果的深度解析
              • 课程论文用AI写作好吗?5款工具优缺点分析