手撕代码——异步FIFO

news2024/11/27 14:27:27

手撕代码——异步FIFO

  • 一、异步FIFO原理与设计
    • 读写地址指针控制
    • 读写地址指针跨时钟处理与空满信号判断
    • 读写地址与读写操作
  • 二、完整代码与仿真文件
  • 三、仿真结果

一、异步FIFO原理与设计

  在FIFO的设计中,无论是同步FIFO,还是异步FIFO,最最最最重要的就是如何判断空信号empty与满信号full。在上文《手撕代码——同步FIFO》中,我们设计的同步FIFO,使用一个数据计数器cnt来计算当前FIFO中存储的数据个数,根据计数器cnt的值来判断FIFO的空信号empty与满信号full。如果计数器cnt=0,表示FIFO中当前存储的数据个数为0,则拉高空信号empty;如果计数器cnt=FIFO_DEPTH(FIFO深度),则表示拉高满信号full。那么,在异步FIFO的设计中,是否也可以使用计数器的方式判断空满信号呢?在异步FIFO的设计中,我们不用计数器的方式来判断空满,而是将读写指针地址的位宽增加一位冗余,用于判断空满信号。

读写地址指针控制

  首先,是读写地址指针的控制。在写时钟域,当写使能wr_en有效且满信号full为低电平时,表示FIFO中数据未写满,执行写操作,写地址指针wr_ptr自加1;在读时钟域,当读使能rd_en有效且空信号empty为低电平时,表示FIFO中数据未读空,执行读操作,读地址指针rd_ptr自加1。

//write pointer
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        wr_ptr <= 'd0;
    else if(wr_en && !full)
        wr_ptr <= wr_ptr + 1'b1;
end

//read pointer
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        rd_ptr <= 'd0;
    else if(rd_en && !empty)
        rd_ptr <= rd_ptr + 1'b1;
end

读写地址指针跨时钟处理与空满信号判断

  其次,就是使用读写地址指针进行判断空满信号。读地址指针rd_ptr是在读时钟域wr_clk内,空信号empty也是在读时钟域内产生的;而写地址指针wr_ptr是在写时钟域内,且满信号也是在写时钟域内产生的。
  那么,要使用读地址指针rd_ptr与写地址指针wr_ptr对比,产生空信号empty,可以直接对比吗?答案是不可以。因为这两个信号处于不同的时钟域内,要做跨时钟域CDC处理,而多bit信号做跨时钟域处理,常用的方法就是使用异步FIFO进行同步,可是我们不是在设计异步FIFO吗?于是,在这里设计异步FIFO,多bit跨时钟域处理的问题可以转化为单bit跨时钟域的处理,把读写地址指针转换为格雷码后再进行跨时钟域处理,因为无论多少比特的格雷码,每次加1,只改变1位。把读地址指针rd_ptr转换为格雷码,然后同步到写时钟域wr_clk,与写指针地址wr_ptr的格雷码表示进行对比判断,得到满信号full;同样的,把写地址指针wr_ptr转换为格雷码,然后同步到读时钟域rd_clk,与读地址指针rd_ptr的格雷码表示进行对比判断,得到空信号empty。
  所以,需要先把二进制码表示的读地址指针rd_ptr与写地址指针wr_ptr转换为格雷码(二进制码与格雷码的转换原理及设计参考《二进制码与格雷码的相互转换原理与Verilog实现》)。

//write pointer(binary to gray)
assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1);

//read pointer(binary to gray)
assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1);

   然后再对格雷码表示的读写地址指针做单bit跨时钟域处理,打上两拍。需要注意的是:写地址指针的格雷码wr_ptr_gray表示要同步到读时钟域,所以要使用读时钟域的时钟信号rd_clk;而读地址指针的格雷码表示rd_ptr_gray表示要同步到写时钟域,所以要使用写时钟域的时钟信号wr_clk。

//gray write pointer syncrous
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n) begin
        wr_ptr_gray_w2r_1 <= 'd0;
        wr_ptr_gray_w2r_2 <= 'd0;
    end
    else begin
        wr_ptr_gray_w2r_1 <= wr_ptr_gray;
        wr_ptr_gray_w2r_2 <= wr_ptr_gray_w2r_1;
    end
end 

//gray read pointer syncrous
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n) begin
        rd_ptr_gray_r2w_1 <= 'd0;
        rd_ptr_gray_r2w_2 <= 'd0;
    end
    else begin
        rd_ptr_gray_r2w_1 <= rd_ptr_gray;
        rd_ptr_gray_r2w_2 <= rd_ptr_gray_r2w_1;
    end
end 

  然后使用同步处理后的读写地址指针格雷码表示进行对比,得到空满信号。如果写地址指针的格雷码表示同步到读时钟域后,与写地址指针的格雷码表示一致,则表示读空;如果读地址指针的格雷码表示同步到写时钟域后,高两位与写地址指针的格雷码表示相反,而剩余的位与写地址指针的格雷码表示一致,则表示写满。

//full and empty
assign full = (wr_ptr_gray == {~rd_ptr_gray_r2w_2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_r2w_2[ADDR_WIDTH-2:0]}) ? 1'b1 : 1'b0;
assign empty = (rd_ptr_gray == wr_ptr_gray_w2r_2) ? 1'b1 : 1'b0;

读写地址与读写操作

  读写地址指针中,我们设置了一位的冗余位宽,用于判断空满,那么,实际上读写地址应该为读写地址指针的次高位到第0位。取读写地址指针的次高位到第0位,得到读写地址,同时根据读写地址进行读写操作。

//write address and read address
assign wr_addr = wr_ptr[ADDR_WIDTH-1:0];
assign rd_addr = rd_ptr[ADDR_WIDTH-1:0];
//write
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        for(i=0;i<FIFO_DEPTH;i=i+1) begin
            mem[i] <= 'd0;
        end
    else if(wr_en && !full)
        mem[wr_addr] <= din;
end

//read
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        dout_r <= 'd0;
    else if(rd_en && !empty)
        dout_r <= mem[rd_addr];
end
assign dout = dout_r;

二、完整代码与仿真文件

  异步FIFO的完整代码如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/05/17 20:24:34
// Design Name: 
// Module Name: async_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 异步FIFO
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

module async_fifo
#(
    parameter   FIFO_WIDTH = 8,
    parameter   FIFO_DEPTH = 8,
    parameter   ADDR_WIDTH = $clog2(FIFO_DEPTH)
)
(
    //write clock domain
    input                       wr_clk      ,
    input                       wr_rst_n    ,
    input                       wr_en       ,
    input   [FIFO_WIDTH-1:0]    din         ,
    output                      full        ,
    //read clock domain
    input                       rd_clk      ,
    input                       rd_rst_n    ,
    input                       rd_en       ,
    output  [FIFO_WIDTH-1:0]    dout        ,
    output                      empty        
);

//------------------------------------------------//
integer i;
//memory
reg     [FIFO_WIDTH-1:0]    mem     [FIFO_DEPTH-1:0];
//write address and read address
wire    [ADDR_WIDTH-1:0]    wr_addr;
wire    [ADDR_WIDTH-1:0]    rd_addr;
//write pointer
reg     [ADDR_WIDTH:0]      wr_ptr;
wire    [ADDR_WIDTH:0]      wr_ptr_gray;
reg     [ADDR_WIDTH:0]      wr_ptr_gray_w2r_1;
reg     [ADDR_WIDTH:0]      wr_ptr_gray_w2r_2;
//read pointer
reg     [ADDR_WIDTH:0]      rd_ptr;
wire    [ADDR_WIDTH:0]      rd_ptr_gray;
reg     [ADDR_WIDTH:0]      rd_ptr_gray_r2w_1;
reg     [ADDR_WIDTH:0]      rd_ptr_gray_r2w_2;
//
reg     [FIFO_WIDTH-1:0]    dout_r;

//------------------------------------------------//
//----------------------//
//write pointer
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        wr_ptr <= 'd0;
    else if(wr_en && !full)
        wr_ptr <= wr_ptr + 1'b1;
end

//write pointer(binary to gray)
assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1);

//gray write pointer syncrous
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n) begin
        wr_ptr_gray_w2r_1 <= 'd0;
        wr_ptr_gray_w2r_2 <= 'd0;
    end
    else begin
        wr_ptr_gray_w2r_1 <= wr_ptr_gray;
        wr_ptr_gray_w2r_2 <= wr_ptr_gray_w2r_1;
    end
end 

//----------------------//
//read pointer
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        rd_ptr <= 'd0;
    else if(rd_en && !empty)
        rd_ptr <= rd_ptr + 1'b1;
end

//read pointer(binary to gray)
assign rd_ptr_gray = rd_ptr ^ (rd_ptr>>1);

//gray read pointer syncrous
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n) begin
        rd_ptr_gray_r2w_1 <= 'd0;
        rd_ptr_gray_r2w_2 <= 'd0;
    end
    else begin
        rd_ptr_gray_r2w_1 <= rd_ptr_gray;
        rd_ptr_gray_r2w_2 <= rd_ptr_gray_r2w_1;
    end
end 

//----------------------//
//write address and read address
assign wr_addr = wr_ptr[ADDR_WIDTH-1:0];
assign rd_addr = rd_ptr[ADDR_WIDTH-1:0];

//full
assign full = (wr_ptr_gray == {~rd_ptr_gray_r2w_2[ADDR_WIDTH:ADDR_WIDTH-1],rd_ptr_gray_r2w_2[ADDR_WIDTH-2:0]}) ? 1'b1 : 1'b0;
//empty
assign empty = (rd_ptr_gray == wr_ptr_gray_w2r_2) ? 1'b1 : 1'b0;

//----------------------//
//write
always @(posedge wr_clk or negedge wr_rst_n) begin
    if(!wr_rst_n)
        for(i=0;i<FIFO_DEPTH;i=i+1) begin
            mem[i] <= 'd0;
        end
    else if(wr_en && !full)
        mem[wr_addr] <= din;
end

//read
always @(posedge rd_clk or negedge rd_rst_n) begin
    if(!rd_rst_n)
        dout_r <= 'd0;
    else if(rd_en && !empty)
        dout_r <= mem[rd_addr];
end
assign dout = dout_r;

endmodule

  仿真文件如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2023/05/17 21:18:22
// Design Name: 
// Module Name: tb_async_fifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module tb_async_fifo();
parameter   FIFO_WIDTH = 8;
parameter   FIFO_DEPTH = 8;
parameter   ADDR_WIDTH = $clog2(FIFO_DEPTH);

reg                        rst_n       ;
reg                        wr_clk      ;
reg                        wr_en       ;
reg    [FIFO_WIDTH-1:0]    din         ;
wire                       full        ;
reg                        rd_clk      ;
reg                        rd_en       ;
wire   [FIFO_WIDTH-1:0]    dout        ;
wire                       empty       ;


initial begin
  rst_n  <= 1'b0;
  wr_clk = 1'b1;
  rd_clk = 1'b1;
  wr_en  <= 1'b0;
  rd_en  <= 1'b0;
  din <= 8'b0;

  # 5 rst_n <= 1'b1;

end

initial begin
  #20 wr_en <= 1'b1;
      rd_en <= 1'b0;
  #40 wr_en <= 1'b0;
      rd_en <= 1'b1;
  #30 wr_en <= 1'b1;
      rd_en <= 1'b0;
  #13 rd_en <= 1'b1;
  #10
  repeat(100)
  begin
      #5 wr_en <= {$random}%2 ;
         rd_en <= {$random}%2 ;
  end

  #100
  $finish;

end

always #1.5 wr_clk = ~wr_clk ;
always #1   rd_clk = ~rd_clk ;
always #3   din <= {$random}%8'hFF;

async_fifo
#(
    .FIFO_WIDTH(FIFO_WIDTH),
    .FIFO_DEPTH(FIFO_DEPTH),
    .ADDR_WIDTH(ADDR_WIDTH)
)
async_fifo
(
    .wr_clk      (wr_clk      ),
    .wr_rst_n    (rst_n       ),
    .wr_en       (wr_en       ),
    .din         (din         ),
    .full        (full        ),
    .rd_clk      (rd_clk      ),
    .rd_rst_n    (rst_n       ),
    .rd_en       (rd_en       ),
    .dout        (dout        ),
    .empty       (empty       )
);

endmodule

三、仿真结果

  根据TestBench文件,对异步FIFO进行仿真。可以看到,在写时钟域wr_clk下,写使能wr_en有效,成功进行数据的写入操作,成功写入8个数据后,写满信号full拉高,在满信号full为高电平期间,即使写使能wr_en有效,也不进行数据的写操作。

在这里插入图片描述

  同时可以看到写地址指针wr_ptr在写时钟域wr_clk下,当写使能wr_en有效时,自加1,同时通过打两拍把写指针地址的格雷码表示wr_ptr_gray同步到读时钟域。

在这里插入图片描述

  可以看到FIFO读数据正确,在读完FIFO中所有数据后,拉高读空信号empty。

在这里插入图片描述
在这里插入图片描述
  综上,异步FIFO设计验证通过。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/551223.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT:4. 使用OpenAI API创建自己的AI网站:3. flask web框架将OpenAI 创作的图片显示在网页界面上

ChatGPT&#xff1a;4. 使用OpenAI API创建自己的AI网站&#xff1a;3. flask web框架将OpenAI 创作的图片显示在网页界面上 如果你还是一个OpenAI的小白&#xff0c;有OpenAI的账号&#xff0c;但想调用OpenAI的API搞一些有意思的事&#xff0c;那么这一系列的教程将仔细的为…

nginx缓存

实验&#xff1a; 步骤一 tomcat1&&tomcat2部署&#xff1a; tomcat部署参照下面博客 (232条消息) Tomcat部署及优化_zhangchang3的博客-CSDN博客 两个网页入下图设计 步骤二 nginx 七层转发设置 编译安装nginx 修改配置文件 vim /usr/local/nginx/conf/nginx.…

5.21学习周报

文章目录 前言文献阅读摘要简介方法介绍讨论结论 时间序列预测 前言 本周阅读文献《Streamflow and rainfall forecasting by two long short-term memory-based models》&#xff0c;文献主要提出两种基于长短时记忆网络的混合模型用于对水流量和降雨量进行预测。小波-LSTM&a…

全网最全,性能测试-性能瓶颈分析详全,优秀的性能测试工程师养成记...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 内存分析 内存的…

06:mysql---约束

目录 1介绍 2:约束演示(建表) 3:外键约束 4:外建行为 5:外建是否可以删除 6:多表查询 1介绍 1:概念:约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 2:目的:保证数据库中数据的正确、有效性和完整性。 3:分类: 约束描述关键字非空约束限制…

ESP32S3---eFuse固化VDD_SPI释放GPIO45

使用esp-idf里面的esptool_py工具集吧.首先切换到工具所在目录. 比如WROOM设置(默认ttyUSB0或者ttyACM0): espefuse.py set_flash_voltage 3.3V 对于WROVER设置(默认ttyUSB0或者ttyACM0): espefuse.py set_flash_voltage 1.8V 运行后会提示你输入BURN,然后确认才能写,因为…

Vue电商项目--平台售卖属性和的排序操作制作

平台售卖属性的操作 就是点击平台的售卖属性&#xff0c;下面显示对应的内容 这里我们要借助这个props属性 这里块是平台的售卖属性&#xff0c;我们在这里绑定回调&#xff0c;一点击就把id传给父组件 我们需要把这俩个参数传进入 商品属性的数组: ["属性ID:属性值:…

FFmpeg学习:FFmpeg4数据结构分析

FFmpeg数据结构分析 FFMPEG中结构体很多。最关键的结构体可以分成以下几类&#xff1a; 1、解协议&#xff08;http,rtsp,rtmp,mms&#xff09; AVIOContext&#xff0c;URLProtocol&#xff0c;URLContext主要存储视音频使用的协议的类型以及状态。URLProtocol存储输入视音…

【软件工程题库】第一章 软件工程概述

&#x1f57a;作者&#xff1a; 迷茫的启明星 学习路线C语言从0到1C初阶数据结构从0到1 &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…

View的事件分发机制

View的事件分发机制 View的事件分发机制主要分为三点 ,第一点是Activity将点击事件分发给ViewGroup 第二点是ViewGroup将事件自己处理或者分发给子View 第三点便是子View自行处理,或者子View处理不了转交给ViewGroup 现在依次来看 Activity对点击事件的分发过程 Activit…

运用go语言的模板(template)写的第一个程序示例

一、模板&#xff08;template&#xff09;与渲染 模板其实就相当于一个简历模板&#xff0c;上面的格式都是已经确定了的渲染就是往对应的地方填写相应的数据 二、模板 模板文件通常定义为.tmpl和.tpl为后缀&#xff08;也可以使用其他的后缀&#xff09;&#xff0c;必须…

手把手带你利用苹果手机使用美区礼品卡升级ChatGPT Plus,轻松搞定!

大家好&#xff0c;我是五竹。 昨天用苹果手机尝试了一下&#xff0c;借助App Store&#xff08;苹果应用商店&#xff09;升级 Plus&#xff0c;成功了&#xff01;一共升级了三个号&#xff01;有两个一气呵成&#xff0c;轻松搞定。最后一个可能触发风控了&#xff0c;但第…

OOB配对原理及应用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言一、OOB是啥&#xff1f;二、OOB配对实践总结 前言 本文先简单介绍OOB配对的流程&#xff0c;然后结合CC2652蓝牙芯片调试OOB配对 一、OOB是啥&#xff1f; OOB就是…

博客系统后端设计(六) -实现登录页面要求强制登录功能

文章目录 实现页面要求强制登录实现思路1.约定前后端交互接口2.实现后端代码3.修改前端代码 实现页面要求强制登录 当用户访问列表页/详情页/编辑页的时候&#xff0c;要求用户已经是登录的状态了&#xff0c; 如果用户还没登录&#xff0c;就会强制跳转到登录页面。 实现思路…

一文盘点PoseiSwap近期的生态利好

PoseiSwap 是 Nautilus Chain 上首个 DEX&#xff0c;其继承了 Nautilus Chain 的模块化、Layer3 以及 Zk-rollup 所带来的优势&#xff08;TPS 在 2000&#xff09;&#xff0c;包括吞吐量、安全度、隐私性等。基于 Nautilus Chain&#xff0c;PoseiSwap 也将具备基于 Zk 的隐…

Ceph crush运行图

Crush map介绍 ceph集群中由monitor负责维护的运行图包括&#xff1a; Monitor map&#xff1a;监视器运行图osd map&#xff1a;osd运行图PG map&#xff1a;PG运行图Crush map&#xff1a;crush运行图Mds map&#xff1a;mds运行图 crush map是ceph集群物理拓扑的抽象&…

HTML、PHP实战:搭建一个网页登录页面。

一、实验环境。 MySQL5.7.26 FTP0.9.60 Apache2.4.39 我这里用的是PHPstudy小皮一键搭建的。 数据库 二、登录页面。 登录页面前端代码 文件名&#xff1a;denglu.html <html> <head> <meta charset"UTF-8"> <title>登录界面</ti…

StarRocks 极速全场景 MPP 数据库介绍及使用

一、简介 StarRocks 是一款高性能分析型数据仓库&#xff0c;使用向量化、MPP 架构、CBO、智能物化视图、可实时更新的列式存储引擎等技术实现多维、实时、高并发的数据分析。既支持从各类实时和离线的数据源高效导入数据&#xff0c;也支持直接分析数据湖上各种格式的数据。兼…

Linux基本指令3

目录 一.基本常用指令 指令1&#xff1a;find命令&#xff1a; 指令2&#xff1a;which命令&#xff1a; 指令3&#xff1a;alias命令&#xff1a; 指令4&#xff1a;whereis which&#xff0c;find&#xff0c;whereis这三个搜索命令的区别&#xff1a; 指令5&#xff…

Vue初始

一、Vue的概述 Vue 发音类似 view Vue 游雨溪 鱿鱼须 鱿鱼须不懂Vue Vue历史 Angular React Vue 2013 Seed 2013 Seed命名为Vue 2014 Vue正式发布 0.8 - 0.10 2015 6.13 0.12 2015下半年 vue-cli vueRouter vueX 版本好1.0 vue正式跨入大众 渐进式框架(JQuery) 2016国…