FPGA 37 专题 跨时钟域问题及亚稳态问题的处理方式

在这里插入图片描述

FPGA 37 专题 跨时钟域问题及亚稳态问题的处理方式

原文学习链接 : https://zhuanlan.zhihu.com/p/368552207

原文学习链接:https://www.cnblogs.com/rouwawa/p/7501319.html

注:首先声明,这个是基于上述两个链接一个整理学习笔记和一些自己的理解便于我个人解读,以防后续找不到做个备份,如果原作者对此有争议,后面请博主通过博客联系我处理,避免产生一些误会。

1、什么叫做始时钟域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHHYsJ3H-1631378141233)(img/blog_img/fpga/image-20210911215438352.png)]

​ 假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么我们说这个设计只有一个时钟域。假如设计有两个输入时钟,如图1所示,一个时钟给接口1使用,另一给接口2使用,那么我们说这个设计中有两个时钟域。

2、什么叫做亚稳态

​ 触发器的建立时间和保持时间在时钟上升沿左右定义了一个时间窗口,如果触发器的数据输入端口上数据在这个时间窗口内发生变化(或者数据更新),那么就会产生时序违规,我理解的就是在虚线框中,针对clock2时钟,数据刚刚好捕获Din 的信号,而Din 在这个时钟上升沿的时候刚好在由低电平导高电平的电平变化,这样我们在采集的时候,就会出来Dout 的信号,也就是可能是‘ 0 ’ 或者 可能是 ‘ 1 ’的信号。这就是亚稳态。

​ (原文解释) 如下图所示,在这个时序clock2违规是因为建立时间要求和保持时间要求被违反了,此时触发器内部的一个节点(或者要输出到外部的节点)可能会在一个电压范围内浮动,无法稳定在逻辑0或者逻辑1状态。换句话说,如果数据在上述窗口中被采集,触发器中的晶体管不能可靠地设置为逻辑0或者逻辑1对应的电平上。所以此时的晶体管并未处于饱和区对应的高或者低电平,而是在稳定到一个确定电平之前,徘徊在一个中间电平状态(这个中间电平或许是一个正确值,也许不是)。如图2所示,这就是所谓的亚稳态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KPdpq9ad-1631378238692)(img/blog_img/fpga/image-20210911220111301.png)]

一般解决信号亚稳态有三种方法:

  1. 相位控制

    相位控制技术可以在一个时钟频率是另外一个时钟的【数倍】(注: 跨时钟的两个时钟频率必须呈现倍数关系才能用这种方式)并且其中一个时钟可以由FPGA 内部PLL 或者DLL 控制时使用,初始相位必须是相同的。

  2. 多级寄存器
    一般针对单bit控制信号跨越两个异步时钟域传输,可以采用多级寄存器,俗称多打拍。同步电路中的第一拍后也许会产生亚稳态,但是信号有机会在其被第二级寄存以及被其它逻辑看到之前稳定下来。常用的就是对单bit信号打两拍,这也是最简单、最常见的处理方式。

    单bit信号消除亚稳态的方法:多级触发器法

    假设A和B是两个时钟域,各自的频率是clk_a和clk_b,clk_a的频率高于clk_b(同频相位差稳定的,不在讨论范围内)

    2.1信号从B到A(慢到快)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zhOM30sN-1631378141238)(img/blog_img/fpga/image-20210911232013113.png)]

    ​ 在时钟域B下的脉冲信号pulse_b在时钟域A看来,是一个很慢(宽)的“电平”信号会,保持多个clk_a的时钟周期,所以一定能被clk_a采到。

    ​ 经验设计采集过程必须寄存两拍。第一拍将输入信号同步化,同步化后的输出可能带来建立/保持时间的冲突,产生亚稳态。需要再寄存一拍,减少亚稳态带来的影响。

    ​ 一般来说两级是最基本要求,如果是高频率设计,则需要增加寄存级数来大幅降低系统的不稳定性。也就是说采用多级触发器来采样来自异步时钟域的信号,级数越多,同步过来的信号越稳定。

    ​ 特别需要强调的是,此时pulse_b必须是clk_b下的寄存器信号,如果pulse_b是clk_b下的组合逻辑信号(可以理解是一个按键的非异步信号),一定要先在clk_b先用D触发器(DFF)抓一拍,再使用两级DFF向clk_a传递。这是因为clk_b下的组合逻辑信号会有毛刺,在clk_b下使用时会由setup/hold时间保证毛刺不会被clk_b采到,但由于异步相位不确定,组合逻辑的毛刺却极有可能被clk_a采到。一般代码设计如下:

    always @ (posedge clk_a or negedge rst_n) begin
        if (rst_n == 1'b0)  begin
            pules_a_r1 <= 1'b0;
            pules_a_r2 <= 1'b0;
            pules_a_r3 <= 1'b0;
        end
        else begin  //打3拍
            pules_a_r1 <= pulse_b;   // D 触发器抓一拍子,异步组合逻辑,或者是类似与按键信号的状态
            pules_a_r2 <= pules_a_r1; // 处理亚稳态信号一级
            pules_a_r3 <= pules_a_r2; // 处理亚稳态信号二级
        end
    end
    
    assign pulse_a_pos = pules_a_r2 & (~pules_a_r3); //上升沿检测
    assign pulse_a_neg = pules_a_r3 & (~pules_a_r2); //下降沿检测
    assign pulse_a = pules_a_r2;
    

    2.2信号从B到A(快到慢)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JztO8HQE-1631378141240)(img/blog_img/fpga/v2-2f386a8fc72bb424a13b967e597e092b_r.jpg)]

    ​ 如果单bit信号从时钟域A到时钟域B,那么存在两种不同的情况,传输脉冲信号pulse_a或传输电平信号level_a。实际上,在一般情况下只有电平信号level_a的宽度能被clk_b采集到才可以保证系统正常工作。那么对于脉冲信号pulse_a采取怎样的处理方法呢?可以用一个展宽信号来替代pulse_a实现垮时钟域的握手。

    ​ 主要原理就是先把脉冲信号在clk_a下展宽,变成电平信号signal_a,再向clk_b传递,当确认clk_b已经“看见”信号同步过去之后,再清掉signal_a。代码通用框架如下:

    module Sync_Pulse (
        clk_a,  
        clk_b, 
        rst_n,  
        pulse_a_in,  
     
        pulse_b_out,  
        b_out 
    );
    /****************************************************/
     
    input clk_a;
    input clk_b;
    input rst_n;
    input pulse_a;
     
    output pulse_b_out;
    output b_out; 
     
    /****************************************************/ 
     
    reg signal_a;
    reg signal_b;
    reg signal_b_r1;
    reg signal_b_r2;
    reg signal_b_a1;
    reg signal_b_a2;
     
    /****************************************************/
    //在时钟域clk_a下,生成展宽信号signal_a
    always @ (posedge clk_a or negedge rst_n) begin
        if (rst_n == 1'b0) begin
            signal_a <= 1'b0;
        end
        else if (pulse_a_in) begin //检测到到输入信号pulse_a_in被拉高,则拉高signal_a
            signal_a <= 1'b1;
        end
        else if (signal_b_a2) begin //检测到signal_b1_a2被拉高,则拉低signal_a
            signal_a <= 1'b0;
        end
        else begin
            signal_a <= signal_a;
        end
    end
     
    //在时钟域clk_b下,采集signal_a,生成signal_b
    always @ (posedge clk_b or negedge rst_n) begin
        if (rst_n == 1'b0) begin
            signal_b <= 1'b0;
        end
        else begin
            signal_b <= signal_a;
        end
    end
    //多级触发器处理
    always @ (posedge clk_b or negedge rst_n) begin
        if (rst_n == 1'b0) begin
            signal_b_r1 <= 1'b0;
            signal_b_r2 <= 1'b0;
        end
        else begin
            signal_b_r1 <= signal_b;  //对signal_b打两拍
            signal_b_r2 <= signal_b_r1;
        end
    end
    //在时钟域clk_a下,采集signal_b_r1,用于反馈来拉低展宽信号signal_a
    always @ (posedge clk_a or negedge rst_n) begin
        if (rst_n == 1'b0)  begin
            signal_b_a1 <= 1'b0;
            signal_b_a2 <= 1'b0;
        end
        else begin
            signal_b_a1 <= signal_b_r1;  //对signal_b_r1打两拍,因为同样涉及到跨时钟域 
            signal_b_a2 <= signal_b_a1;
        end
    end
     
    assign pulse_b_out = signal_b_r1 & (~signal_b_r2);
    assign b_out = signal_b_r1;
     
    endmodule
    

    在顶层为设计分区是一个好的设计实践行为,这样任何功能模块外面都包含一个独立的同步器模块(Sync_Pulse 模块,就是上面的那个代码模块)。这样有利于在划分模块的基础上实现所谓的理想时钟域情况(即整个设计模块只有一个时钟),如下图所示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kXEGa9qt-1631378141242)(img/blog_img/fpga/v2-fe8edbdb6429c46b24109340a5726275_r.jpg)]

    对设计进行分区有很多优势。首先,对每个独立的功能模块进行时序分析变得简易,因为模块都是完全的同步设计。其次,整个同步模块中的时序例外也很容易得到定义。再次,底层模块的同步器加时序例外在代入到设计顶层时,大大降低了由于人为失误造成的疏漏。所以,同步寄存器应该在功能模块外单独分区。

  3. 异步FIFO缓存
    一般用于跨时钟域传输数据,写端和读端分别对应两个时钟域,由【空/满信号控制着读写过程】,实现数据的跨域传输。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值