数字IC前端学习笔记:FIFO的Verilog实现(二)

news2024/11/15 5:25:06

 相关文章

数字IC前端学习笔记:LSFR(线性反馈移位寄存器)

数字IC前端学习笔记:跨时钟域信号同步

数字IC前端学习笔记:信号同步和边沿检测

数字IC前端学习笔记:锁存器Latch的综合

数字IC前端学习笔记:格雷码(含Verilog实现的二进制格雷码转换器)

数字IC前端学习笔记:FIFO的Verilog实现(一)


目录

4.异步FIFO原理

5.异步FIFO的Verilog实现


4.异步FIFO原理

        在之前,我们讨论了了同步FIFO,因为其具有单一的时钟,因此应用范围有限。在实际的引用中,我们经常遇到多个时钟域的情况,此时数据需要在两个时钟域之间实现传送,并且不能出现毛刺和亚稳态。我们以PCIe插槽上的以太网适配器板卡为例加以说明。该板卡从局域网(LAN)或以太网接收数据包,然后将数据传送给系统存储器。反过来,它从系统存储器接受数据包,然后传送给网络。板卡的一侧和网络通信,用以太网本地处理相关操作。板卡的另一侧与PCEe接口交互,以板卡自身的时钟工作。这两个时钟不仅频率不同,而且是异步的(频率不是倍数关系,如果石是倍数关系,可以认为这是同步时钟,因为相位差恒定)。此时需要使用异步FIFO将数据从一个时钟域传送到另一个时钟域。

        尽管异步FIFO的操作原理与同步FIFO类似,但由于前者与两个时钟有关,电路的复杂度也会增加。对异步FIFO进行数据写入和读出操作的方式与同步FIFO极其相似,写入与读出操作也有自己的信号集,其复杂度主要体现于产生FIFO_full、FIFO_empty、room_avaliable、data_avaliable等标识。异步FIFO中产生这些标识的方法比同步FIFO中要复杂得多。

        在这里我们不能使用计数器根据读取或存储信号保存FIFO中的数据量,因为他们是两个时钟域的信号,不能用两个时钟对同一个reg赋值。我们知道,当FIFO为满或空时,写入指针和读取指针是相等的。但这是不够的,我们需要另外的条件将“空”和“满”进行区分。前面介绍过,FIFO工作时,写入指针在前,读出指针跟随写入指针。当FIFO为满时,写入指针的状态是到达顶部后返回底部最后和读取指针相同,即超越读取指针一轮(FIFO深度),如果我们在指针最高位增加一个辅助位给写入和读取指针,则可以用来指示当这两个指针相同(不包括辅助位)时,是没有超越的相同,还是超越一轮的相同。

        我们以一个深度为4的FIFO为例说明。此时,计数器需要两位表示,计数序列为00→01

10→11→00,在最高位增加一位辅助位后,计数序列为000→001→010→011→100→101→110

→111。正如我们所看到的,除了最高位(辅助位),其他位将循环两次,通过比较辅助位,我们就可以判断FIFO状态是满还是空。

        虽然解决了空满状态的标识,但仍然存在问题,即读指针和写指针分别产生于各自时钟域,不能互相比较,否则会因时序问题产生亚稳态。解决这个问题的办法是将指针从一个时钟域传递到另一个时钟域,然后做比较。而且我们要注意,传递的是多位信号而不是一位信号,此时需要使用格雷码编码和译码电路将指针进行跨时钟域安全传送。在格雷码编码方案中,相邻编码中只有一位发生变化,该特性被用于跨时钟域矢量传递(还有其他方法,可见数字IC前端学习笔记:跨时钟域信号同步一文)。我们将所有这些零散的知识放在一个图中以便于浏览和理解具体的操作方法,如下图所示。

        写指针被转换为格雷码编码,并经过触发器保存,然后经过两级寄存器同步到读时钟域并解码为二进制编码,最后与读指针比教产生fifo_empty信号。读指针的行为类似,在写时钟域产生fifo_full信号。

        需要注意的是,当读指针被传送到写入时钟域时,相对于读时钟域的读指针来说,可能会存在3到4个周期的延时,也就是说,可能出现读指针已经增加,而在写时钟域看来并未增加,这会导致fifo在仍有空位时给出fifo_full信号,这是异步FIFO保守的一面,如此可以确保不产生数据上溢。

写指针也有类似问题,会出现FIFO仍有数据而FIFO给出fifo_empty信号的情况。这些不会影响FIFO的正确操作,在停止写入和读出后几个周期时,就可以给出正确的fifo_emty和fifo_full信号。

5.异步FIFO的Verilog实现

module asynch_fifo #(parameter FIFO_PTR=4,
                               FIFO_WIDTH=32)
                    (wrclk, rstb_wrclk,
                    write_en, write_data,
                    snapshot_wrptr, rollback_wrptr,
                    reset_wrptr,
                    rdclk, rstb_rdclk,
                    read_en, read_data,
                    snapshot_rdptr, rollback_rdptr,
                    reset_rdptr,
                    fifo_full, fifo_empty,
                    room_avail, data_avail);

    input wrclk;
    input rstb_wrclk;
    input write_en;
    input [FIFO_WIDTH-1:0] write_data;
    input snapshot_wrptr; //记录写指针快照
    input rollback_wrptr; //恢复写指针快照
    input reset_wrptr; //写指针重置为0
    input rdclk;
    input rstb_rdclk;
    input read_en;
    output [FIFO_WIDTH-1:0] read_data;
    input snapshot_rdptr; //记录读指针快照
    input rollback_rdptr; //恢复读指针快照
    input reset_rdptr; //读指针重置为0
    output reg fifo_full, fifo_empty;
    output reg [FIFO_PTR:0] room_avail, data_avail;

    localparam FIFO_DEPTH = 1 << FIFO_PTR //2^FIFO_PTR
    localparam FIFO_TWICEDEPTH_MINUS1 = 2*FIFO_DEPTH - 1
    reg [FIFO_PTR:0] wr_ptr_wab, wr_ptr_wab_nxt;//有辅助位的写指针
    wire [FIFO_PTR:0] room_avail_nxt, data_avail_nxt; 
    reg [FIFO_PTR:0] wr_ptr_snapshot_value;
    wire [FIFO_PTR:0] wr_ptr_snapshot_value_nxt;
    wire fifo_full_nxt, fifo_empty_nxt;
    reg [FIFO_PTR:0] rd_ptr_snapshot_value;
    wire [FIFO_PTR:0] rd_ptr_snapshot_value_nxt;       
    wire [FIFO_PTR-1:0] wr_ptr,rd_ptr;
    reg [FIFO_PTR:0] rd_ptr_wab, rd_ptr_wab_nxt;//有辅助位的读指针      
    
    //写指针控制逻辑  
    //*********************************************
    always@(*) begin
        wr_ptr_wab_nxt=wr_ptr_wab;
        
        if(reset_wrptr)
            wr_ptr_wab_nxt = 0;
        else if(rollback_wrptr)
            wr_ptr_wab_nxt = wr_ptr_snapshot_value;
        else if(write_en && (wr_ptr_wab == FIFO_TWICEDEPTH_MINUS1))
            wr_ptr_wab_nxt = 0;
        else if(write_en)
            wr_ptr_wab_nxt = wr_ptr_wab + 1;
    end

    //写指针快照
    //*********************************************
    assign wr_ptr_snapshot_value_nxt = 
        snapshot_wrptr ? wr_ptr_wab : wr_ptr_snapshot_value;
    
    //寄存器操作
    //*********************************************
    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk)begin
            wr_ptr_wab <= 0;
            wr_ptr_snapshot_value <= 0;
        end    
        else begin
            wr_ptr_wab <= wr_ptr_wab_nxt;
            wr_ptr_snapshot_value <= wr_ptr_snapshot_value_nxt;
        end
    end
    
    //写指针二进制转格雷码
    //*********************************************
    reg [FIFO_PTR:0] wr_ptr_wab_gray;
    wire [FIFO_PTR:0] wr_ptr_wab_gray_nxt;


    //实例化转码模块(在之前的文章中已设计)
    binary_to_gray #(.PTR(FIFO_PTR)) binary_to_gray_wr
                    (.binary (wr_ptr_wab_nxt),
                     .gray_value (wr_ptr_wab_gray_nxt));

    always@(posedge wrclk or negedge rstb_wrclk)begin
        if(!rst_wrclk)
            wr_ptr_wab_gray <= 0;
        else 
            wr_ptr_wab_gray <= wr_ptr_wab_gray_nxt;
    end
    
    //写指针同步到读时钟域
    //*********************************************
    reg [FIFO_PTR:0] wr_ptr_wab_gray_sync1;
    reg [FIFO_PTR:0] wr_ptr_wab_gray_sync2;
    
    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk) begin
            wr_ptr_wab_gray_sync1 <= 0;
            wr_ptr_wab_gray_sync2 <= 0;
        else
            wr_ptr_wab_gray_sync1 <= wr_ptr_wab_gray;
            wr_ptr_wab_gray_sync2 <= wr_ptr_wab_gray_sync1;
        end
    end

    //格雷码写指针转二进制
    //*********************************************
    reg [FIFO_PTR:0] wr_ptr_wab_rdclk;
    wire [FIFO_PTR:0] wr_ptr_wab_rdclk_nxt;
    gray_to_binary #(.PTR(FIFO_PTR)) gray_to_binary_wr
                    (.gray_value(wr_ptr_wab_gray_sync2),
                     .binary(wr_ptr_wab_rdclk_nxt));

    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk) begin
            wr_ptr_wab_rdclk <= 0;
        else
            wr_ptr_wab_rdclk <= wr_ptr_wab_rdclk_nxt;
        end
    end

    //读指针控制逻辑  
    //*********************************************
    always@(*) begin
        rd_ptr_wab_nxt=rd_ptr_wab;
        
        if(reset_rdptr)
            rd_ptr_wab_nxt = 0;
        else if(rollback_rdptr)
            rd_ptr_wab_nxt = rd_ptr_snapshot_value;
        else if(read_en && (rd_ptr_wab == FIFO_TWICEDEPTH_MINUS1))
            rd_ptr_wab_nxt = 0;
        else if(read_en)
            rd_ptr_wab_nxt = rd_ptr_wab + 1;
    end

    //读指针快照
    //*********************************************
    assign rd_ptr_snapshot_value_nxt = 
        snapshot_rdptr ? rd_ptr_wab : rd_ptr_snapshot_value;

    //寄存器操作
    //*********************************************
    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk)begin
            rd_ptr_wab <= 0;
            rd_ptr_snapshot_value <= 0;
        end    
        else begin
            rd_ptr_wab <= rd_ptr_wab_nxt;
            rd_ptr_snapshot_value <= rd_ptr_snapshot_value_nxt;
        end
    end

    /读指针二进制转格雷码
    //*********************************************
    reg [FIFO_PTR:0] rd_ptr_wab_gray;
    wire [FIFO_PTR:0] rd_ptr_wab_gray_nxt;
    
    //实例化转码模块(在之前的文章中已设计)
    binary_to_gray #(.PTR(FIFO_PTR)) binary_to_gray_rd
                    (.binary (rd_ptr_wab_nxt),
                     .gray_value (rd_ptr_wab_gray_nxt));

    always@(posedge rdclk or negedge rstb_rdclk)begin
        if(!rst_rdclk)
            rd_ptr_wab_gray <= 0;
        else 
            rd_ptr_wab_gray <= rd_ptr_wab_gray_nxt;
    end

    //读指针同步到写时钟域
    //*********************************************
    reg [FIFO_PTR:0] rd_ptr_wab_gray_sync1;
    reg [FIFO_PTR:0] rd_ptr_wab_gray_sync2;
    
    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk) begin
            rd_ptr_wab_gray_sync1 <= 0;
            rd_ptr_wab_gray_sync2 <= 0;
        else
            rd_ptr_wab_gray_sync1 <= rd_ptr_wab_gray;
            rd_ptr_wab_gray_sync2 <= rd_ptr_wab_gray_sync1;
        end
    end

    //格雷码读指针转二进制
    //*********************************************
    reg [FIFO_PTR:0] rd_ptr_wab_rdclk;
    wire [FIFO_PTR:0] rd_ptr_wab_rdclk_nxt;
    gray_to_binary #(.PTR(FIFO_PTR)) gray_to_binary_rd
                    (.gray_value(rd_ptr_wab_gray_sync2),
                     .binary(rd_ptr_wab_rdclk_nxt));

    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk) 
            rd_ptr_wab_rdclk <= 0;
        else
            rd_ptr_wab_rdclk <= rd_ptr_wab_wrclk_nxt;
    end

    assign wr_ptr = wr_ptr_wab[FIFO_PTR-1:0];
    assign rd_ptr = rd_ptr_wab[FIFO_PTR-1:0];

    //SRAM 存储器实例化
    //*********************************************
    sram #(.FIFO_PTR(FIFO_PTR),
           .FIFO_WIDTH(FIFO_WIDTH)) sram_0
          (.wrclk(wrclk),
           .wren(write_en),
           .wrptr(wr_ptr),
           .wrdata(write_data),
           .rdclk(rdclk),
           .rden(read_en),
           .rdptr(rd_ptr),
           .rddata(read_data));

    //fifo_full和room_avail信号产生
    //*********************************************
    assign fifo_full_nxt = 
            (wr_ptr_wab_nxt[FIFO_PTR] != rd_ptr_wab_wrclk_nxt[FIFO_PTR])&&
            (wr_ptr_wab_nxt[FIFO_PTR-1:0] == rd_ptr_wab_wrclk_nxt[FIFO_PTR-1:0]);
    
    assign room_avail_nxt = 
            (wr_ptr_wab_nxt[FIFO_PTR] == rd_ptr_wab_wrclk_nxt[FIFO_PTR])?
            (FIFO_DEPTH-(wr_ptr_wab_nxt[FIFO_PTR-1:0] - 
            rd_ptr_wab_wrclk_nxt[FIFO_PTR-1:0])):
            (rd_ptr_wab_wrclk_nxt[FIFO_PTR-1:0] - 
            wr_ptr_wab_nxt[FIFO_PTR-1:0]);  

    always@(posedge wrclk or negedge rstb_wrclk) begin
        if(!rstb_wrclk) begin
            fifo_full <= 0;
            room_avail <= 0;
        end        
        else begin
            fifo_full <= fifo_full_nxt;
            room_avail <= room_avail_nxt;
        end
    end 
         
    //如果两者没有差一轮,则指针相减代表着FIFO内数据量,用深度减去数据量则为剩余空间,否则直接相减(不包括辅助位)就可得到剩余空间
    
    //fifo_empty和room_empty信号产生
    //*********************************************
    assign fifo_empty_nxt = 
            (rd_ptr_wab_nxt[FIFO_PTR] != wr_ptr_wab_rdclk_nxt[FIFO_PTR])&&
            (rd_ptr_wab_nxt[FIFO_PTR-1:0] == wr_ptr_wab_dclk_nxt[FIFO_PTR-1:0]);
    
    assign data_avail_nxt = 
            (rd_ptr_wab_nxt[FIFO_PTR] == wr_ptr_wab_rdclk_nxt[FIFO_PTR])?
            (FIFO_DEPTH-(rd_ptr_wab_nxt[FIFO_PTR-1:0] - 
            wr_ptr_wab_rdclk_nxt[FIFO_PTR-1:0])):
            (wr_ptr_wab_rdclk_nxt[FIFO_PTR-1:0] - 
            rd_ptr_wab_nxt[FIFO_PTR-1:0]);   
 
    always@(posedge rdclk or negedge rstb_rdclk) begin
        if(!rstb_rdclk) begin
            fifo_empty <= 0;
            data_avail <= 0;
        end        
        else begin
            fifo_empty <= fifo_empty_nxt;
            data_avail <= data_avail_nxt;
        end
    end 
endmodule

以上内容来源于《Verilog高级数字系统设计技术和实例分析》

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

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

相关文章

最新站长必备在线工具箱系统源码 含上百款工具 带后台版本

&#x1f388; 限时活动领体验会员&#xff1a;可下载程序网创项目短视频素材 &#x1f388; 最新站长必备在线工具箱系统源码 含上百款工具 带后台版本 自适应模板 优化修复版 系统一切正常可用&#xff0c;后台登录方式是QQ扫码登录的,建议有能力的可以改一改 此工具箱系统…

【LVS + Keepalived 群集】

目录 一、Keepalived 案列分析二、Keeoalived 工具介绍1、keepalived 实现原理剖析2、VRRP &#xff08;虚拟路由冗余协议&#xff09;是指对路由器的一种备份解决方案3、keepalived 案例讲解4、keepalived 的安装与启动5、Keepalived及其工作原理Keepalived体系主要模块及其作…

SQL锁总结

一、概述 介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源(CPU、RAM、I/O)的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁…

springboot3嵌入式容器源码解析

问题分析 不同于使用springmvc,在我们使用springboot时无需配置tomcat就可以直接使用&#xff0c;这就说明springboot已经在我们启动项目时将tomcat配置好了&#xff0c;接下来我们就来看看springboot底层是怎么实现的。 源码解析 ServletWebServerFactoryAutoConfiguratio…

【Python爬虫】整站40万条房价数据并行抓取,可更换抓取城市

目录 前言一、获取索引一级位置&#xff1a;区域信息二级位置&#xff1a;板块信息三级位置&#xff1a;地铁信息&#xff08;搜索地铁周边房源信息&#xff09; 二、获取索引页最大页数三、抓取房源信息Tag四、分配任务&#xff0c;并行抓取五、将抓取结果存储到excel中&#…

go map源码探索(查找、插入、删除、扩容、遍历)

文章目录 概要一、Go map结构二、Go map初始化2.1、不带容量初始化2.2、带容量初始化 三、Go map查找四、Go map插入4.1、插入源码分析4.2、溢出桶申请策略 五、删除源码分析六、扩容与迁移源码分析6.1、扩容条件6.1.1、当前负载因子大于6.56.1.2、有过多的溢出桶 6.2、扩容6.3…

LVS+Keepalivedd

Keepalived 一、Keepalived及其工作原理二、实验非抢占模式的设置 三、脑裂现象四、Nginx高可用模式 一、Keepalived及其工作原理 keepalived是一个基于VRRP协议来实现的LVS服务高可用方案&#xff0c;可用解决静态路由出现的单点故障问题。 在一个LVS服务集群中通常有主服务器…

11.枚举和注解|Java学习笔记

文章目录 枚举 enumeration enum自定义实现枚举自定义类实现枚举总结 使用enum关键字实现枚举实现方法Enum类方法enum实现接口 注解注解的理解JDK的元Annotation&#xff08;元注解&#xff09; 枚举 enumeration enum 从一个需求引入&#xff1a; 要求创建季节(Season) 对象&…

matlab 使用预训练神经网络和SVM进行苹果分级(带图形界面)支持其他物品图片分级或者分类

目录 数据集&#xff1a; 实验代码&#xff1a;alexnet版 如果你的matlab不是正版&#xff0c;先看这里&#xff1a; 数据集结构&#xff1a; 训练代码&#xff1a; 训练结果&#xff1a; 图形界面&#xff1a; 界面展示&#xff1a; 其他&#xff1a; 输出结果: 实验…

代码规范

一 、代码规范 程序员写代码&#xff0c;不仅仅是实现功能 1. 名称 在Python开发过程中会创建文件夹/文件/变量等&#xff0c;这些在命名有一些潜规则&#xff08;编写代码时也要注意pep8规范&#xff09;。 文件夹&#xff0c;小写 & 小写下划线连接&#xff0c;例如&a…

jmeter 在linux服务器中执行性能测试、监听服务器资源指标

jmeter监控服务器资源 资源准备jmeter安装&#xff08;Windows 版&#xff09;jmeter安装&#xff08;linux 版&#xff09;ServerAgent安装&#xff08;linux 版&#xff09;配置脚本并执行测试 资源准备 下载apache-jmeter-5.5文件;下载ServerAgent-2.2.3文件; jmeter安装&…

ZooKeeper的集群部署和启动与关闭

ZooKeeper是一个分布式应用程序协调服务。一个ZooKeeper集群可以存在多个Follower和Observer服务器&#xff0c;但只允许存在一台Leader服务器。如果Leader服务器宕机&#xff0c;那么ZooKeeper集群的其它服务器会投票选举出一个新的Leader服务器&#xff0c;为防止投票数不过半…

2023最新社交圈子即时聊天通信小程序+前端UNIAPP

&#x1f388; 限时活动领体验会员&#xff1a;可下载程序网创项目短视频素材 &#x1f388; &#x1f389; 有需要的朋友记得关赞评&#xff0c;阅读文章底部来交流&#xff01;&#xff01;&#xff01; &#x1f389; ✨ 源码介绍 2023最新多端社交圈子系统源码 | 陌生人社交…

[openeuler]Yocto embedded 联合sig例会 (2022-12-15)

Yocto & embedded 联合sig例会 (2022-12-15)_哔哩哔哩_bilibili

【C/C++】详解 函数重载和应用

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

【零基础学JS - 11 】javaScript中的if..else表达式

&#x1f468;‍&#x1f4bb; 作者简介&#xff1a;程序员半夏 , 一名全栈程序员&#xff0c;擅长使用各种编程语言和框架&#xff0c;如JavaScript、React、Node.js、Java、Python、Django、MySQL等.专注于大前端与后端的硬核干货分享,同时是一个随缘更新的UP主. 你可以在各个…

[操作系统]4.文件管理

因为复习时间来不及了 还是老规矩,知识点覆盖不完全,具体内容请参考黑书,知识脉络来自王道操作系统 关于文件操作系统 1.文件系统基础 (1)文件的定义 文件时操作系统中的重要概念,是以计算机硬盘为基础载体存储在假期上的信息的集合 (2)文件的属性 文件的属性可以理解为文…

json-server 详解

这几天在写react的前端项目,想着后端接口没有,在网上也找不到比较合适的接口,所以在github和npm上翻了许久关于前端简单生成后端接口的工具,终于被找到了这个神仙工具json-server JSON-Server 是一个 Node 模块&#xff0c;运行 Express 服务器&#xff0c;你可以指定一个 jso…

CENTOS上的网络安全工具(二十四)Windows下的Hadoop+Spark编程环境构建

前面我们搭建了hadoop集群&#xff0c;spark集群&#xff0c;也利用容器构建了spark的编程环境。但是一般来说&#xff0c;就并行计算程序的开发&#xff0c;一刚开始一般是在单机上的&#xff0c;比如hadoop的single node。但是老师弄个容器或虚拟机用vscode远程访问式开发&am…

APM代码阅读(一):串口驱动

文章目录 前言一、AP_RangeFinder_TeraRanger_Serial.h二、AP_RangeFinder_TeraRanger_Serial.cpp三、AP_RangeFinder.cppinitdetect_instance_add_backendupdate 四、 AP_RangeFinder_Backend_Serial.cpp 前言 APM 4.2.3 以测距传感器的串口驱动为例进行阅读 其他的传感驱动都…