• 跨时钟域问题(三)异步FIFO的Verilog实现(格雷码)


    🏡 博客首页:安静到无声

    ⛳️ 欢迎关注: ❤️ 点赞 🎒 收藏 ✏️ 留言

    🚧 系列专栏:跨时钟域

    🎈 如有错误不吝赐教

    在这里插入图片描述

    异步FIFO概述

    异步FIFO是用来在两个异步时钟域间传输数据。
    在这里插入图片描述

    图1 用异步FIFO进行数据传输

    System X利用xclk时钟将数据写入FIFO,并利用System X利用yclk时钟进行输出。

    其中fifo_full和fifo_empty分别是满标志和空标志,用于说明数据状态,当fifo_full时,不再进行数据的写入,当fifo_empty时不再进行数据的读取。

    格雷码(gray code)的使用

    在产生FIFO满信号时,要将写指针和读指针进行比较,由于两个指针分别在各自的时钟域,彼此之间是异步的,在使用二进制进行计数器实现指针时,就会导致用于比较的指针取样错误。

    使用自然二进制码计数时,相邻数据之间可能会产生多bit的变化。这会产生较大的尖峰电流以及其他问题。

    比如,二进制计数器的值会从FFF变为000。这时所有位会同时改变。虽然能通过同步计数器避免亚稳态,但是仍然能得到极不相关的取样值,所以同步计数器不是最终的解决方案。

    从FFF 到000可能的转换:

    • FFF→000
    • FFF→001
    • FFF→010
    • FFF→011
    • FFF→100
    • FFF→101
    • FFF→110
    • FFF→111

    如果同步时钟边沿在FFF向000转换的中间位置到来,就可能将三位二进制数的任何值取样并同步到新的时钟域中。鉴于以上情况,强烈建议避免使用二进制计数器实现读、写指针

    格雷码是一种相邻数据只有1bit变化的码制。因此可以使用格雷码去取代二进制计数器,并且用打拍的方式去同步(跨时钟域问题(二)(单bit信号跨时钟域 1. 电平同步器 2. 边沿同步器 3. 脉冲检测器))(只有深度为2的n次方才能用格雷码的方式去同步,这样才能保证最大值和最小值只有一位的变化)。我们可以将指针转为格雷码同步到另一个时钟域再进行比较。如果同步时钟在计数值转换期间到来,这种编码能够消除绝大部分的错误。
    在这里插入图片描述

    图2 格雷码编码计数器

    2.1 格雷码编码的verilog的实现

    自然二进制编码转换为格雷码如下:
    在这里插入图片描述

    图3 二进制转化为格雷码

    g r a y _ c o d e = b i n a r y _ c o d e ⊕ ( b i n a r y _ c o d e > > 1 ) gray\_code = binary\_code \oplus (binary\_code > > 1) gray_code=binary_code(binary_code>>1)

    格雷码转化为自然二进制
    在这里插入图片描述

    图4 格雷码转化为自然二进制

    在这里插入图片描述
    我们以牛客网VL47 格雷码计数器为例!

    在这里插入图片描述
    代码

    `timescale 1ns/1ns
    
    module gray_counter(
        input                               clk                        ,
        input                               rst_n                      ,
    
        output reg         [   3:0]         gray_out                    
    );
    
    reg                    [   3:0]         binary_cnt                 ;
    reg                                     flag                       ;
    
    
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            flag <= 1'd0;
        end
        else begin
            flag <= ~flag;
        end
    end
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            binary_cnt <= 1'd0;
        end else begin
            if (flag == 1'd1) begin
                binary_cnt <= binary_cnt + 1'd1;
            end else begin
                binary_cnt <= binary_cnt;
            end
        end
    end
    
    always @(*) begin
        gray_out <= binary_cnt ^ (binary_cnt >> 1);
    end
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    格雷码下的 fifo空满判断

    在判断写满时,要将读地址指针同步到写地址指针的时钟域;在判读读空是,要将写地址指针同步到读地址指针的时钟域。

    对于“空”的判断依然依据二者完全相等(包括MSB);

    而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:

    1. wr_adr_gray和同步过来的rd_adr_gray的MSB不相等,因为wptr必须比rptr多折回一次。
    2. wr_adr_gray与rd_adr_gray的次高位不相等(如下图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置,而对应的格雷码是最高位相反同时次高位也相反)
    3. 剩下的其余位完全相等。

    在这里插入图片描述

    图5 空满信号的判断

    异步FIFO代码及仿真实现

    module asyn_fifo
        #(
        parameter                           data_width = 16            ,//数据的宽度
        parameter                           data_depth = 8             ,//数据的深度
        parameter                           ram_depth = 256             //定义ram
        )
        (
        //复位时钟
        input                               rst_n                      ,//复位时钟
        //写时钟域
        input                               wr_clk                     ,//写时钟域时钟
        input                               wr_en                      ,//写使能
        input              [data_width-1:0] data_in                    ,//写入数据
        output                              full                       ,//满标志
        //读时钟域
        input                               rd_clk                     ,//读时钟域时钟
        input                               rd_en                      ,//读使能
        output reg         [data_width-1:0] data_out                   ,//读出数据
        output                              empty                       //满标志
    );
    
    //定义双端口ram的读写地址
    wire                   [data_depth-1:0] wr_adr                     ;//双端口ram的写地址
    wire                   [data_depth-1:0] rd_adr                     ;//双端口ram的读地址
    
    //定义双端口ram的读写指针!为什么比读写地址多一位,在博客中讲解!
    reg                    [data_depth:0]   wr_adr_ptr                 ;//写指针
    reg                    [data_depth:0]   rd_adr_ptr                 ;//读指针
    
    //转换为格雷码进行打拍操作
    wire                   [data_depth:0]   wr_adr_gray                ;//写地址指针二进制转化为格雷码
    reg                    [data_depth:0]   wr_adr_gray1               ;//打一拍缓存
    reg                    [data_depth:0]   wr_adr_gray2               ;//打两拍缓存
    
    wire                   [data_depth:0]   rd_adr_gray                ;//读地址指针二进制转化为格雷码 
    reg                    [data_depth:0]   rd_adr_gray1               ;//打一拍缓存
    reg                    [data_depth:0]   rd_adr_gray2               ;//打两拍缓存
    
    //读写地址比控制指针少一位
    assign wr_adr = wr_adr_ptr[data_depth-1:0];
    assign rd_adr = rd_adr_ptr[data_depth-1:0];
    
    //开辟一段内存空间
    reg                    [data_width-1:0] ram_fifo [ram_depth-1:0]                           ;//data_width*ram_depth
    
    //写数据
    integer i;
    always @(posedge wr_clk or negedge rst_n) begin
        if (!rst_n) begin
            for (i = 0; i < ram_depth; i = i + 1) begin
                ram_fifo[i] <= 'd0;
            end
        end else begin
            if(wr_en && (~full)) begin                                  //如果写使能开启,且未满
                ram_fifo[wr_adr] <= data_in;
            end else begin
                ram_fifo[wr_adr] <= ram_fifo[wr_adr];
            end
        end
    end
    
    //读数据
    always @(posedge rd_clk or negedge rst_n) begin
        if (!rst_n) begin
            data_out <= 'd0;
        end else begin
            if(rd_en && (~empty)) begin                                 //如果读使能开启,且未空
                data_out <= ram_fifo [rd_adr];
            end else begin
                data_out <= 'd0;                                        //否则读出为0
            end
        end
    end
    
    //写地址指针控制
    always @(posedge wr_clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_adr_ptr <= 'd0;
        end else begin
            if(wr_en && (~full)) begin
                wr_adr_ptr <= wr_adr_ptr + 1'b1;
            end else begin
                wr_adr_ptr <= wr_adr_ptr;
            end
        end
    end
    
    //读地址指针控制
    always @(posedge rd_clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_adr_ptr <= 'd0;
        end else begin
            if(rd_en && (~empty)) begin
                rd_adr_ptr <= rd_adr_ptr + 1'b1;
            end else begin
                rd_adr_ptr <= rd_adr_ptr;
            end
        end
    end
    
    //二进制指针转化为格雷码
    assign wr_adr_gray = (wr_adr_ptr >> 1) ^ wr_adr_ptr;
    assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;
    
    //格雷码的同步 读时钟域同步到写时钟域
    always @(posedge wr_clk or negedge rst_n) begin
        if (!rst_n) begin
            rd_adr_gray1 <= 'd0;
            rd_adr_gray2 <= 'd0;
        end else begin
            rd_adr_gray1 <= rd_adr_gray;
            rd_adr_gray2 <= rd_adr_gray1;
        end
    end
    
    //格雷码的同步 写时钟域同步到读时钟域
    always @(posedge rd_clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_adr_gray1 <= 'd0;
            wr_adr_gray2 <= 'd0;
        end else begin
            wr_adr_gray1 <= wr_adr_gray;
            wr_adr_gray2 <= wr_adr_gray1;
        end
    end
    
    //空标志---->表示读空---->同步到读时钟域的写地址指针和读地址指针相同
    assign empty = (rd_adr_gray == wr_adr_gray2) ? 1'b1 : 1'b0;
    assign full = (wr_adr_gray[data_depth] != rd_adr_gray2[data_depth]) && (wr_adr_gray[data_depth-1] != rd_adr_gray2[data_depth-1]) && (wr_adr_gray[data_depth-2:0] == rd_adr_gray2[data_depth-2:0]);
    
    
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133

    仿真

    `timescale 1ns / 1ps
    
    module asyn_fifo_tb;
        
    reg                                     rst_n                      ;
    								
    reg                                     wr_clk                     ;
    reg                                     wr_en                      ;
    reg                    [  15:0]         data_in                    ;
    wire                                    full                       ;
    				
    reg                                     rd_clk                     ;
    reg                                     rd_en                      ;
    wire                   [  15:0]         data_out                   ;
    wire                                    empty                      ;
    
        asyn_fifo asyn_fifo_inst
        (
        .rst_n                             (rst_n                     ),
    								
        .wr_clk                            (wr_clk                    ),
        .wr_en                             (wr_en                     ),
        .data_in                           (data_in                   ),
        .full                              (full                      ),
    				
        .rd_clk                            (rd_clk                    ),
        .rd_en                             (rd_en                     ),
        .data_out                          (data_out                  ),
        .empty                             (empty                     ) 
    );
        
        initial wr_clk = 0;
        always#10 wr_clk = ~wr_clk;
        
        initial rd_clk = 0;
        always#30 rd_clk = ~rd_clk;
        
        always@(posedge wr_clk or negedge rst_n)begin
            if(!rst_n)
                data_in <= 'd0;
            else if(wr_en)
                data_in <= data_in + 1'b1;
            else
                data_in <= data_in;
        end
        
        initial begin
            rst_n = 0;
            wr_en = 0;
            rd_en = 0;
            #200;
            rst_n = 1;
            wr_en = 1;
            #6000;
            wr_en = 0;
            rd_en = 1;
            #18000;
            rd_en = 0;
            $stop;
        end
        
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    仿真结果
    在这里插入图片描述
    vivado的工程已将经传至地址

    参考

    1. 硬件架构的艺术——数字电路设计方法与艺术
    2. 牛客网——格雷码计数器
    3. https://blog.csdn.net/weixin_41458037/article/details/96499750
    4. https://www.bilibili.com/video/BV1Wf4y1N7h4?spm_id_from=333.337.search-card.all.click&vd_source=e3e56f2c8b99f0309ca6937cefb13991
  • 相关阅读:
    MySQL高级十四:索引的基本使用
    JavaOOP-类、对象、方法、变量作用域及JavaDoc注释
    牛客--汽水瓶python
    谷歌浏览器F12开发者模式网络不要展示请求毫秒 谷歌浏览器控制台network界面那个时间段怎么隐藏
    高防服务器主要运用在哪些场景?
    城市轨道交通站应急照明疏散指示系统设计
    代码随想录算法训练营第二十三天|669 修剪二叉搜索树 108 将有序数组转换为二叉搜索树 538 把二叉搜索树转换为累加树
    SpringBoot介绍及自动装配
    RabbitMQ 模拟实现【四】:虚拟主机设计
    触发器,寄存器,三态输出电路
  • 原文地址:https://blog.csdn.net/lihuanyu520/article/details/126292771