FPGA开发——使用verilog实现异步FIFO

2024-08-27 03:20

本文主要是介绍FPGA开发——使用verilog实现异步FIFO,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、FIFO 介绍

1、FIFO的分类

FIFO First In First Out)先进先出存储器。根据接入的时钟信号,可以分为同步 FIFO 和异步 FIFO
FIFO 底层是基于双口 RAM ,同步 FIFO 的读写时钟一致,异步 FIFO 读时钟和写时钟不同。
作用:
同步 FIFO:主要用于数据缓冲,类似于乒乓缓存思想,可以让后级不必等待前级过多时间。
异步 FIFO a. 跨时钟域传输数据 b. 不同位宽的数据接口

2、FIFO 的常见参数

FIFO 的宽度:即 FIFO 一次读写操作的数据位;
FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
满标志: FIFO 已满或将要满时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的写操作继续向 FIFO 中写数据而造成溢出( overflow)。
空标志: FIFO 已空或将要空时由 FIFO 的状态电路送出的一个信号,以阻止FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出( underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据
读使能:写使能信号有效时写入数据。
写使能:都市能信号有效时读出数据。

二、同步 FIFO 实现

在同步FIFO的实现中我们完全不用考虑不同的时钟和跨时钟的问题,因为在同步FIFO中读写都使用同一个时钟资源。

三、异步 FIFO 实现

        异步 FIFO 实质上是基于中间的双口 RAM,外加一些读写控制电路组成的,主要是实现不同时钟域之间的数据交互。异步 FIFO 读/写操作在两个不同的时钟域,这个过程会设计到跨时钟域处理,所以需要考虑跨时钟域会产生亚稳态的问题。另外,异步 FIFO 也需要通过空满标志去衡量存储器的使用情况,空满标志的产生同样也需要考虑读 /写时钟域,其产生的条件和方式也是需要重点考虑的。以下为具有同步指针的异步 FIFO 示意图。

 

1、/写指针

写指针:总是指向下一次要写的数据地址,写完后自动加 1 ;系统复位后( FIFO为空),写指针指向 0 地址。
读指针:总是指向下一次要读的数据地址,读完后自动加 1 ;系统复位后(FIFO为空),写指针指向 0 地址。
异步 FIFO 中的指针因设计需要,位宽比地址多 1 位。此处 FIFO 的地址对应 FIFO 的存储单元。如深度为 128 的 FIFO,理论上指针位需要[6:0],但为了判断空满都 需要将指针拓展到[7:0]。

2、空满判断

外部电路对异步 FIFO 进行读写操作时,需要根据异步 FIFO 输出的空满信号来判断是否能继续对异步 FIFO 进行读或者写的操作。
空标志:读指针追上写指针,即指针相等。
满标志:写指针追上读指针,即写指针与读指针再次相等,读 /写指针最高位不同即说明再次追上。
异步 FIFO 的读写时钟不同,判断时需要将写指针同步到读时钟域或将读指针同 步到写时钟域进行判断。
跨时钟域传输数据,则有可能会出现亚稳态。亚稳态无法完全避免,但可以通过引入同步机制(打两拍)和格雷码来降低亚稳态出现的概率。

3、亚稳态(打两拍)

上图位亚稳态为打两拍降低亚稳态的示意图。

A 时钟域下的 Din 传递到 B 时钟域,而当 B 时钟上升沿来临时,恰好 Din 数据发生跳变,则 Ds 极有可能出现亚稳态,若使用 Ds,则会导致亚稳态逐级传播下去。固可以再用以及触发器寄存,此时 Dout 的电平就稳定。(有一定可能仍是亚稳态,但概率极低,不做考虑。)单比特信号直接打两拍降低亚稳态的效果较好,但是多比特传输,若多位发生变化时,变化的位都有可能产生亚稳态,所以多比特不直接用打拍的方式进行同步。异步 FIFO 的地址指针每次变化都是加 1,将指针转化为格雷码后(相邻两位格雷码只有 1 位二进制发生变化),可直接进行打两拍来降低亚稳态。

4、格雷码与二进制相互转换

二进制转格雷码

格雷码转二进制

5、代码实现思路

(1 )输入输出端口定义
(2)位宽和深度参数化
(3)如何定义一个内存块?
方法一:
定义一个一维数组。将内存定义为一个 reg 类型的一维数组,这个数组中
的任何一个单元都可以通过一个下标去访问。
如: reg [ 7 : 0 ] data [ 255 : 0 ];
其中 [7:0] 表示一维数组中的每个元素的位宽大小,而在变量后面的
[255:0] ,表示的却不是位宽大小,它表示的是所创建的数组的深度,也就
是一维数组中的元素大小,也可以称作为数组的容量大小。
初始化可用 for 循环清零
方法二:
调用一个双端口的 RAM IP
4 wrusedw rdusedw 怎么求取?写指针领先于读指针的,即写指针减去
读指针即可,异步 FIFO 此处要考虑不同时钟域。

四、代码实现

1、设计文件的编写

新建一个async_fifo.v文件,如下:

//---------<模块及端口声名>------------------------------------------------------
module async_fifo #(parameter FIFO_WIDTH = 8  ,//FIFO输入数据位宽FIFO_DEPTH = 128 //FIFO深度
)(//Write clock domainwrclk   ,//写时钟wrrst_n ,//写侧复位,异步复位,低有效wren    ,//写使能wrdata  ,//写数据输入wrempty ,//写侧空标志wrfull  ,//写侧满标志wrusedw ,//写时钟域下可读数据量//Read clock domainrdclk   ,//读时钟rdrst_n ,//读侧复位,异步复位,低有效rden    ,//读使能rddata  ,//读数据输出rdempty ,//读侧空标志rdfull  ,//读侧满标志rdusedw  //读时钟域下可读数据量
);			//参数声明localparam  ADDR_W = log2b(FIFO_DEPTH),//指针位宽DATA_W = FIFO_WIDTH;//FIFO深度//function声明
/************* 用取对数的方法计算地址指针的位宽 ************************/function integer log2b(input integer data);begin for(log2b=0;data>0;log2b=log2b+1)begindata = data>>1;endlog2b = log2b - 1;end  endfunction//---------<内部信号定义>-----------------------------------------------------//端口声明//Write clock domaininput                               wrclk   ;//写时钟input                               wrrst_n ;//写侧复位,异步复位,低有效input                               wren    ;//写使能input           [DATA_W-1:0]        wrdata  ;//写数据输入output                              wrempty ;//写侧空标志output                              wrfull  ;//写侧满标志output          [ADDR_W-1:0]        wrusedw ;//写时钟域下可读数据量//Read clock domaininput                               rdclk   ;//读时钟input                               rdrst_n ;//读侧复位,异步复位,低有效input                               rden    ;//读使能output          [DATA_W-1:0]        rddata  ;//读数据输出output                              rdempty ;//读侧空标志output                              rdfull  ;//读侧满标志output          [ADDR_W-1:0]        rdusedw ;//读时钟域下可读数据量reg         [DATA_W-1:0]    fifo_mem[FIFO_DEPTH - 1:0]    ;//FIFO存储阵列// reg         [ADDR_W-1:0]    fifo_mem[0:(1'b1<<ADDR_W)-1]    ;//两种写法皆可wire        [ADDR_W-1:0]    wr_addr     ;//写地址wire        [ADDR_W-1:0]    rd_addr     ;//读地址reg         [ADDR_W:0]      wr_ptr      ;//二进制写指针reg         [ADDR_W:0]      rd_ptr      ;//二进制读指针wire        [ADDR_W:0]      wr_ptr_gray ;//格雷码写指针reg         [ADDR_W:0]      wr_ptr_gray1;//打2拍,写指针同步寄存器reg         [ADDR_W:0]      wr_ptr_gray2;wire        [ADDR_W:0]      rd_ptr_gray ;//格雷码读指针reg         [ADDR_W:0]      rd_ptr_gray1;//打2拍,读指针同步寄存器reg         [ADDR_W:0]      rd_ptr_gray2;reg         [ADDR_W:0]      wr_gray2_bin;//将同步至读时钟域的格雷码写指针转换为二进制reg         [ADDR_W:0]      rd_gray2_bin;//将同步至写时钟域的格雷码读指针转换为二进制reg         [DATA_W-1:0]    rd_data_r   ;//读出数据输出寄存器reg         [ADDR_W-1:0]    wr_usedw_r  ;//写时钟域下可读数据量寄存器reg         [ADDR_W-1:0]    rd_usedw_r  ;//读时钟域下可读数据量寄存器integer i;//****************************************************************
//--wr_ptr、rd_ptr
//****************************************************************//写指针always @(posedge wrclk or negedge wrrst_n)begin if(!wrrst_n)beginwr_ptr <= 'd0;end else if(wren && ~wrfull)begin wr_ptr <= wr_ptr + 1'b1;end end//读指针always @(posedge rdclk or negedge rdrst_n)begin if(!rdrst_n)beginrd_ptr <= 'd0;end else if(rden && ~rdempty)begin rd_ptr <= rd_ptr + 1'b1;end end//****************************************************************
//--wr_addr、rd_addr
//****************************************************************assign wr_addr = wr_ptr[ADDR_W-1:0];//数据写入地址assign rd_addr = rd_ptr[ADDR_W-1:0];//数据读出地址//****************************************************************
//--写入数据、读出数据
//****************************************************************//写入数据always @(posedge wrclk or negedge wrrst_n)begin if(!wrrst_n)beginfor (i=0;i<(1'b1<<ADDR_W);i=i+1) begin  //利用for循环循环清零fifo_ramfifo_mem[i] <= 'd0;endend else if(wren && ~wrfull)begin              //只要写使能有效就一直写入数据,数据数据量超过fifo深度,则会重新覆盖fifo_mem[wr_addr] <= wrdata;end  end//读出数据always @(posedge rdclk or negedge rdrst_n)begin if(!rdrst_n)beginrd_data_r <= 'd0;end else if(rden & !rdempty)begin rd_data_r <= fifo_mem[rd_addr];end end//****************************************************************
//--二进制转格雷码
//****************************************************************assign wr_ptr_gray = wr_ptr^(wr_ptr>>1);//写指针格雷码assign rd_ptr_gray = rd_ptr^(rd_ptr>>1);//读指针格雷码//****************************************************************
//--格雷码同步
//****************************************************************//将写指针格雷码同步到读时钟域 always @(posedge rdclk or negedge rdrst_n)begin if(!rdrst_n)beginwr_ptr_gray1 <= 'd0;wr_ptr_gray2 <= 'd0;end else begin wr_ptr_gray1 <= wr_ptr_gray;wr_ptr_gray2 <= wr_ptr_gray1;end end//将读指针格雷码同步到写时钟域always @(posedge wrclk or negedge wrrst_n)begin if(!wrrst_n)beginrd_ptr_gray1 <= 'd0;rd_ptr_gray2 <= 'd0;end else begin rd_ptr_gray1 <= rd_ptr_gray;rd_ptr_gray2 <= rd_ptr_gray1;end end//****************************************************************
//--格雷码转二进制
//****************************************************************/*格雷码转二进制:格雷码的最高位作为二进制的最高位,二进制次高位产生过程是使用二进制的高位和格雷码次高位相异或得到*///将同步至读时钟域的写指针格雷码转换为二进制always @(*)begin wr_gray2_bin[ADDR_W] = wr_ptr_gray2[ADDR_W];for (i=ADDR_W-1;i>=0;i=i-1) beginwr_gray2_bin[i] = wr_gray2_bin[i+1]^wr_ptr_gray2[i];endend//将同步至写时钟域的格雷码读指针转换为二进制always @(*)begin rd_gray2_bin[ADDR_W] = rd_ptr_gray2[ADDR_W];for (i=ADDR_W-1;i>=0;i=i-1) beginrd_gray2_bin[i] = rd_gray2_bin[i+1]^rd_ptr_gray2[i];endend//****************************************************************
//--输出
//****************************************************************//空标志assign wrempty = wr_ptr == rd_gray2_bin;assign rdempty = rd_ptr == wr_gray2_bin;//满标志assign wrfull = (wr_ptr != rd_gray2_bin) && (wr_ptr[ADDR_W-1:0] == rd_gray2_bin[ADDR_W-1:0]);assign rdfull = (rd_ptr != wr_gray2_bin) && (rd_ptr[ADDR_W-1:0] == wr_gray2_bin[ADDR_W-1:0]);//读出数据assign rddata = rd_data_r;//可读数据量always @(posedge wrclk or negedge wrrst_n)begin if(!wrrst_n)beginwr_usedw_r <= 'd0;end else begin wr_usedw_r <= wr_ptr - rd_gray2_bin;end endalways @(posedge rdclk or negedge rdrst_n)begin if(!rdrst_n)beginrd_usedw_r <= 'd0;end else begin rd_usedw_r <= wr_gray2_bin - rd_ptr;end endassign wrusedw = wr_usedw_r;assign rdusedw = rd_usedw_r;endmodule

2、测试文件的编写

新建一个tb_async_fifo.v文件,如下:

`timescale 1ns/1nsmodule tb_async_fifo();//激励信号定义 reg				tb_clk  	;reg				tb_rst_n	;reg             wren        ;reg     [7:0]   wrdata      ;reg             rden        ;//输出信号定义wire            wrempty     ;wire            wrfull      ;wire    [6:0]   wrusedw     ;wire    [7:0]   rddata      ;wire            rdempty     ;wire            rdfull      ;wire    [6:0]   rdusedw     ;//时钟周期参数定义	parameter		CLOCK_CYCLE = 20;   //模块例化async_fifo #(.FIFO_WIDTH(8),.FIFO_DEPTH(128)) async_fifo_inst(//Write clock domain/*input                               */.wrclk   (tb_clk    ),//写时钟/*input                               */.wrrst_n (tb_rst_n  ),//写侧复位,异步复位,低有效/*input                               */.wren    (wren      ),//写使能/*input           [FIFO_WIDTH-1:0]    */.wrdata  (wrdata    ),//写数据输入/*output                              */.wrempty (wrempty   ),//写侧空标志/*output                              */.wrfull  (wrfull    ),//写侧满标志/*output          [FIFO_DEPTH-1:0]    */.wrusedw (wrusedw   ),//写时钟域下可读数据量/*input                               */.rdclk   (tb_clk    ),//读时钟/*input                               */.rdrst_n (tb_rst_n  ),//读侧复位,异步复位,低有效/*input                               */.rden    (rden      ),//读使能/*output          [FIFO_WIDTH-1:0]    */.rddata  (rddata    ),//读数据输出/*output                              */.rdempty (rdempty   ),//读侧空标志/*output                              */.rdfull  (rdfull    ),//读侧满标志/*output          [FIFO_DEPTH-1:0]    */.rdusedw (rdusedw   ) //读时钟域下可读数据量
);		//产生时钟initial 		tb_clk = 1'b0;always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;integer k;
//产生激励initial  begin tb_rst_n = 1'b1;wren   = 'd0;wrdata = 'd0;rden   = 'd0;#(CLOCK_CYCLE*2);tb_rst_n = 1'b0;#(CLOCK_CYCLE*20);tb_rst_n = 1'b1;#1;//模拟写操作for (k=0;k<127;k=k+1) beginwren = 1'b1;wrdata = {$random}%256;#CLOCK_CYCLE;endwren = 1'b0;#(CLOCK_CYCLE*50);//模拟读操作for (k=0;k<127;k=k+1) beginrden = 1'b1;#CLOCK_CYCLE;endrden = 1'b0;#(CLOCK_CYCLE*50);//模拟复位,清空fifo// tb_rst_n = 1'b0;// #(CLOCK_CYCLE*150);#(CLOCK_CYCLE*50);$stop;endendmodule 

3、波形图仿真

 通过前后两张波形图我们可以看到是先写入128个数据之后再进行数据的输出,其中的剩余量,空满信号啥的也是正常变换,说明我们设计的异步FIFO成功。

这篇关于FPGA开发——使用verilog实现异步FIFO的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1110492

相关文章

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

Hadoop企业开发案例调优场景

需求 (1)需求:从1G数据中,统计每个单词出现次数。服务器3台,每台配置4G内存,4核CPU,4线程。 (2)需求分析: 1G / 128m = 8个MapTask;1个ReduceTask;1个mrAppMaster 平均每个节点运行10个 / 3台 ≈ 3个任务(4    3    3) HDFS参数调优 (1)修改:hadoop-env.sh export HDFS_NAMENOD

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof