异步FIFO原理及其实现

异步FIFO原理及其实现FPGA 一 异步 FIFO 实现 包含源码和仿真文件 一 异步 FIFO 的重要参数及其作用 1 FIFO FirstInputFi 即先入先出队列 本质是 RAM FIFO 有几个最重要的参数 2 wr clk 写时钟 所有与写有关的操作都是基于写时钟 3 rd clk 读时钟 所有与读有关的操作都是基于读时钟 4 FIFO WIDTH FIFO 的位宽 即 FIFO 中每个地址对应的数据的位宽 5 FIFO DEPTH FIFO 的深度 即 FIFO 中能存入多少个 位宽为 FIFO

本次设计主要介绍异步FIFO中读写指针和格雷码的原理及其实现,最后会有代码和仿真文件

一、异步FIFO的重要参数及其作用

异步FIFO主要用作跨时钟域的数据缓存。

二、设计要点

异步FIFO设计中,最重要的就是空满判断,格雷码是现在使用最多用于判断空满的一种码制,虽然都知道用格雷码,那为什么要用格雷码?

还有就是为什么读写指针要比FIFO_DEPTH多一位,以及格雷码在这之中起到的重要作用,这个在说完格雷码之后会有解释。

格雷码:因1953年公开的弗兰克·格雷专利 “Pulse Code Communication”而得名。
格雷码是一种安全码,因为相邻的格雷码只有一位不同,和二进制不同,二进制一般相邻的都有多位不同。格雷码在传输中,因为相邻只有一位不同,所以其误码率比二进制低得多。
在同步时,出现亚稳态的概率也比二进制低。




二进制转换为格雷码
在这里插入图片描述
Verilog代码描述:gray = binary ^ (binary >> 1)




在这里插入图片描述
注:以上两幅关于格雷码的图片均不是原创,不知道出处,若有侵权,请联系删除

1、首先是读写指针为什么可以多一位,对读写地址有没有影响

2、其次是为什么要用格雷码

3、空满信号的同步
因为空信号是对读操作有影响,所以,将空信号在rd_clk下同步(多采用两级寄存器)
因为满信号是对写操作有影响,所以,将满信号在wr_clk下同步(多采用两级寄存器)




三、源代码及仿真

1、源代码

 `timescale 1ns / 1ns module asynchronous_fifo #( parameter FIFO_WIDTH = 8, parameter FIFO_DEPTH = 8 )( input wr_clk, //写时钟 input rd_clk, //读时钟 input wr_en, //写使能 input rd_en, //读使能 input wr_rst_n, //写复位 input rd_rst_n, //读复位 input [FIFO_WIDTH-1:0] wr_data, //写入的数据 output reg [FIFO_WIDTH-1:0] rd_data, //输出的数据 output reg wr_full, output reg rd_empty, output wr_fifo_en, output rd_fifo_en, output reg [$clog2(FIFO_DEPTH):0] wr_gray_g2, // 写指针 output reg [$clog2(FIFO_DEPTH):0] rd_gray_g2 ); // reg define reg [$clog2(FIFO_DEPTH):0] wr_pointer = 0; // 写指针 reg [$clog2(FIFO_DEPTH):0] rd_pointer = 0; // 读指针 reg [$clog2(FIFO_DEPTH):0] wr_gray_g1; // 写格雷码,用格雷码来判断空满 reg [$clog2(FIFO_DEPTH):0] rd_gray_g1; wire [$clog2(FIFO_DEPTH):0] wr_gray; // 写格雷码,用格雷码来判断空满 wire [$clog2(FIFO_DEPTH):0] rd_gray; // 读格雷码,用格雷码来判断空满 wire [$clog2(FIFO_DEPTH)-1:0] wr_addr; wire [$clog2(FIFO_DEPTH)-1:0] rd_addr; wire full_1; wire empty_1; //ram define //(*ram_style = "distributed"*) reg [FIFO_WIDTH - 1 : 0] fifo_buffer [FIFO_DEPTH - 1:0]; //寄存器组当FIFO reg [FIFO_WIDTH - 1 : 0] fifo_buffer [FIFO_DEPTH - 1:0]; //寄存器组当FIFO assign wr_fifo_en = wr_en && !full_1; assign rd_fifo_en = rd_en && !empty_1; assign wr_gray = wr_pointer^(wr_pointer>>1); assign rd_gray = rd_pointer^(rd_pointer>>1); assign wr_addr = wr_pointer[$clog2(FIFO_DEPTH)-1:0]; assign rd_addr = rd_pointer[$clog2(FIFO_DEPTH)-1:0]; assign full_1 = ({~rd_gray_g2[$clog2(FIFO_DEPTH):$clog2(FIFO_DEPTH)-1], rd_gray_g2[$clog2(FIFO_DEPTH)-2:0]} == wr_gray)?1:0; assign empty_1 = (wr_gray_g2 == rd_gray)?1:0; //将rd_gray在写时钟域与wr_gray比较,如果wr_gray与rd_gray高两位相反,低位相等,则写满 always@(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) begin rd_gray_g1 <= 0; rd_gray_g2 <= 0; end else begin rd_gray_g1 <= rd_gray; rd_gray_g2 <= rd_gray_g1; end end always@(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) wr_full <= 1'b0; else if(wr_full && wr_en) wr_full <= 1'b1; else wr_full <= full_1; end //将wr_gray在读时钟域与rd_gray比较,相等为FIFO空 always@(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) begin wr_gray_g1 <= 0; wr_gray_g2 <= 0; end else begin wr_gray_g1 <= wr_gray; wr_gray_g2 <= wr_gray_g1; end end always@(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) rd_empty <= 1'b0; else if(rd_empty && rd_en) rd_empty <= 1'b1; else rd_empty <= empty_1; end // 写数据指针计数 always@(posedge wr_clk or negedge wr_rst_n) begin if(!wr_rst_n) begin fifo_buffer[wr_addr] <= 'bz; wr_pointer <= 0; end else if(wr_fifo_en) begin fifo_buffer[wr_addr] <= wr_data; wr_pointer <= wr_pointer + 1'b1; end else begin fifo_buffer[wr_addr] <= fifo_buffer[wr_addr]; wr_pointer <= wr_pointer; end end // 读数据指针计数 always@(posedge rd_clk or negedge rd_rst_n) begin if(!rd_rst_n) begin rd_data <= 'bz; rd_pointer <= 0; end else if(rd_fifo_en) begin rd_data <= fifo_buffer[rd_addr]; rd_pointer <= rd_pointer + 1'b1; end else begin rd_data <= fifo_buffer[rd_addr]; rd_pointer <= rd_pointer; end end endmodule 

2、仿真文件

改变clk_period_wr和clk_period_rd就可以实现读写快慢切换

 `timescale 1ns / 1ns `define clk_period_wr 50 `define clk_period_rd 20 module asynchronous_fifo_tb(); parameter FIFO_WIDTH = 8; parameter FIFO_DEPTH = 16; reg wr_clk; reg rd_clk; reg wr_en; reg rd_en; reg wr_rst_n; reg rd_rst_n; reg [FIFO_WIDTH-1:0] wr_data; wire [FIFO_WIDTH-1:0] rd_data; wire wr_full; wire rd_empty; wire wr_fifo_en; wire rd_fifo_en; wire [$clog2(FIFO_DEPTH):0] wr_gray_g2; wire [$clog2(FIFO_DEPTH):0] rd_gray_g2; assign wr_fifo_en = wr_en && !wr_full; assign rd_fifo_en = rd_en && !rd_empty; initial begin wr_clk = 0; forever begin #(`clk_period_wr/2) wr_clk = ~wr_clk; end end initial begin rd_clk = 0; forever begin #(`clk_period_rd/2) rd_clk = ~rd_clk; end end initial begin wr_rst_n = 1; rd_rst_n = 1; wr_en = 0; rd_en = 0; #5; wr_rst_n = 0; rd_rst_n = 0; #5; wr_rst_n = 1; rd_rst_n = 1; end initial begin //第一段仿真 @(negedge wr_clk) wr_en = 1;wr_data = $random; repeat(7) begin @(negedge wr_clk) wr_data = $random; end @(negedge wr_clk) wr_en = 0; @(negedge rd_clk) rd_en = 1; repeat(8) begin @(negedge rd_clk); end @(negedge rd_clk) rd_en = 0; # 150 //第二段仿真 @(negedge wr_clk) wr_en = 1; wr_data = $random; repeat(18) begin @(negedge wr_clk) wr_data = $random; end @(negedge wr_clk) wr_en = 0; @(negedge rd_clk) rd_en = 1; repeat(18) begin @(negedge rd_clk); end rd_en = 0; // 第三段仿真 repeat(9) begin @(negedge wr_clk) wr_data = $random;wr_en = 1; end rd_en = 1; repeat(19) begin @(negedge wr_clk) wr_data = $random; end @(negedge wr_clk) wr_en = 0; @(negedge rd_clk) rd_en = 0; #20 $finish; end asynchronous_fifo #( .FIFO_WIDTH (FIFO_WIDTH), .FIFO_DEPTH (FIFO_DEPTH) ) asynchronous_fifo ( .wr_clk (wr_clk), .rd_clk (rd_clk), .wr_rst_n (wr_rst_n), .rd_rst_n (rd_rst_n), .wr_en (wr_fifo_en), .rd_en (rd_fifo_en), .wr_data (wr_data), .wr_full (wr_full), .rd_empty (rd_empty), .rd_data (rd_data) ); endmodule 

3、仿真截图

读比写快
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述






写比读快
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这个代码也还有些问题,希望大家多多包容并且指出错误或者可以改善的地方,一起进步。


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229050.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午5:35
下一篇 2026年3月16日 下午5:35


相关推荐

  • 平面方程的几种形式及推导过程(总结)

    平面方程的几种形式及推导过程(总结)平面方程的基本定义三维空间中所有处于同一平面的点所对应的方程 任何一个平面都可以用三元一次方程来表示 确定一个平面需要的条件点法式 1 平面上的一个点 2 以该点为起点的法向量 标准式 1 平面上的一个点 2 两个不线且与平面平行的向量 三点式 1 三个不同时共线的点 注意 若已知三个点求平面方程 通常利用这三个点得出两

    2026年3月17日
    2
  • UVC协议学习前奏

    UVC协议学习前奏nbsp nbsp 根据一些资料对 UVC 规范进行了一些了解 加上进一步接触 UVC 协议才发现有一些新的东西 将新的理解记录下来 以免忘记 1 关于 UVC 协议所需要知道的 nbsp nbsp nbsp UVC 全称为 USB nbsp video device class nbsp nbsp UVC 是微软与另外几家设备厂商联合推出的为 USB 视频捕获设备定义的协议标准 目前已成为 USB nbsp org 标准之一 nbsp nbsp nbsp USB 协议中 除了通用的软硬件电气接口规范等

    2026年3月20日
    2
  • REST API和SOAP API之间的区别

    REST API和SOAP API之间的区别TheRepresentationalStateTransfer(REST)架构风格不是可以购买的技术,也不是可以添加到软件开发项目中的库。REST是一种世界观,将信息提升为我们构建的体系结构的第一流元素。RoyFielding博士的论文“架构风格和基于网络的软件架构设计”介绍并整理了用于描述“RESTful”系统的思想和术语。这是一份学术文件,但通过提供RESTful架构的基础,可…

    2022年7月13日
    18
  • 【喂饭教程】最详细的DeepSeek-R1:7B+RAGFlow本地知识库搭建教程,建议收藏起来慢慢学!!

    【喂饭教程】最详细的DeepSeek-R1:7B+RAGFlow本地知识库搭建教程,建议收藏起来慢慢学!!

    2026年3月16日
    4
  • 时限调度算法给出的调度顺序_时间片轮转法进行进程调度

    时限调度算法给出的调度顺序_时间片轮转法进行进程调度调度算法-时间轮一.背景在我们的业务场景中,经常会使用到定时任务功能,比如定时发送消息,定时执行数据同步,比如之前的文章介绍的分布式事务中的本地事务表方式的解决方案等等,特别是在现在大数据量和分布式服务环境下,定时任务调度越来越频繁,所以对应的定时任务调度的算法实现也越来越完善。在之前的单机环境下,我们可以使用ScheduledThreadPool起一个延迟任务线程池,定时的执行任务,又或者使用spring提供的@Schedule注解配合上cron表达式开启一个定时任务,又或者是lin

    2026年4月17日
    4
  • MySQL – SQL语句增加字段/修改字段/修改类型/修改默认值[通俗易懂]

    MySQL – SQL语句增加字段/修改字段/修改类型/修改默认值[通俗易懂]1.应用场景 有时[比如在Linux服务器下,或者借助数据库管理工具执行SQL],需要使用sql语句直接对数据表进行新建/修改表结构,填充/更新数据等. 好处:可以避免繁琐操作,遗漏,快捷方便,一步到位. 2.学习/操作 20190903新补充 1.添加字段,比如我在数据表中添加一个age字段,类型为int(11…

    2022年4月30日
    547

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号