首页专栏详情
打赏
FPGA逻辑设计回顾(8)单比特信号的CDC处理方式之Toggle同步器
易百纳技术社区 李锐博恩 2021-01-23 18:45:14

前言

本文作为本系列CDC的最后一篇吧,作为前几篇有关CDC处理的文章的补充,本文所要介绍的同步器适用场景是:单比特信号的同步处理,且可以用于快时钟到慢时钟的跨时钟域同步。切换同步器,英文名:Toggle synchronizer,无论怎么翻译吧,它的含义就是将快时钟域内的单比特脉冲同步至慢时钟域,这像是一个切换过程,给出原理图:

Toggle Synchronizer

这种方式与本系列的另一篇文章应用场景相似,但更简单一些!

本文不仅讲这一个问题,同时本文还将对文章:FPGA逻辑设计回顾(4)亚稳态与单比特脉冲信号的CDC处理问题中讲到的反馈展宽的同步器进行补充说明,因为有小伙伴问我这个问题,快时钟同步的单脉冲信号同步到慢时钟后如何也保持一个时钟周期呢?

还有一个问题,就是如何使用脉冲展宽同步器对低电平脉冲进行展宽处理呢?

好了,前言里作为一个引入,就不说这么多了,我们进入正文:

脉冲反馈展宽同步器技术补充说明

首先说明的是,这个脉冲反馈展宽同步器是我自己给的名字,因为它的展宽方式是一种握手反馈的方式,但是握手反馈,又与握手同步有点相似,因此为了不误会,单独给了一个名字。

好了,回到前言中的第一个问题,有个小伙伴问我,如何让快时钟域中的单脉冲信号同步到慢时钟域后,还保持一个周期?对于这个问题,我知道你肯定是个新手,然后看了我的这篇文章,问了这样的问题。不过嘛,这总是正常的,因为大家看文章都是随缘,看到哪里是哪里,不能说看你的一整个系列。

这个问题很简单,就是对同步到慢时钟域后的脉冲进行上升沿检测就可以得到一个时钟的高脉冲。我之所以强调高脉冲,是因为我们的同步是对高脉冲的同步,我们设定的场景是高脉冲是我们需要的脉冲。如果我们需要同步低电平脉冲,那么这个同步展宽的方式就要另当别论了,需要简单的修改下。

既然说到了这个问题,我们就把这个问题当作我们今天要补充的第二个问题:如何使用脉冲反馈展宽同步器同步低电平脉冲?

嗯,第一个问题还没有给出详细的可以直接使用的代码,这里给出(如果我是一个老师,可能不是一个好老师吧,什么都给学生了,他们自己会动手吗?好在我不是老师,你们看我的文章也可能就是主动解决疑惑,这样就不需要藏着掖着了,达到这个目的就行了。):

RTL代码

`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: fast2slow_CDC
//

module fast2slow_CDC(
    input   wire    clk1            ,
    input   wire    clk2            ,
    input   wire    rst             ,
    input   wire    pulse_clk1      ,
    output  wire    pulse_syn_clk2  

    );

    reg     pulse_wide_clk2         ;
    reg     reg1_pulse_wide_clk2    ;

    reg     reg1_pulse_wide_clk1    ;
    reg     reg2_pulse_wide_clk1    ;

    //生成脉冲展宽信号
    reg     pulse_wide_clk1         ;
    // always@(posedge clk1 or posedge rst) begin
    //     if(rst) begin
    //         pulse_wide_clk1 <= 1'b0             ;
    //     end
    //     else if(pulse_clk1) begin
    //         pulse_wide_clk1 <= 1'b1             ;
    //     end
    //     else if(reg2_pulse_wide_clk1) begin
    //         pulse_wide_clk1 <= 1'b0             ;
    //     end
    //     else begin
    //         pulse_wide_clk1 <= pulse_wide_clk1  ;
    //     end
    // end

    always@(posedge clk1 or posedge rst) begin
        if(rst) begin
            pulse_wide_clk1 <= 1'b0                 ;
        end
        else if(pulse_clk1) begin
            pulse_wide_clk1 <= 1'b1                 ;
        end
        else begin
            if(reg2_pulse_wide_clk1) begin
                pulse_wide_clk1 <= 1'b0             ;
            end
            else begin
                pulse_wide_clk1 <= pulse_wide_clk1  ;
            end
        end
    end

    //在目的时钟域内采样展宽后的信号

    always@(posedge clk2 or posedge rst) begin
        if(rst) begin
            pulse_wide_clk2 <= 1'b0                 ;
            reg1_pulse_wide_clk2 <= 1'b0            ;
        end
        else begin
            pulse_wide_clk2 <= pulse_wide_clk1      ;
            reg1_pulse_wide_clk2 <= pulse_wide_clk2 ;
        end
    end

    //在源时钟域内同步目的时钟域内的展宽信号,以生成反馈信号

    always@(posedge clk1 or posedge rst) begin
        if(rst) begin
            reg1_pulse_wide_clk1 <= 1'b0                    ;
            reg2_pulse_wide_clk1 <= 1'b0                    ;
        end
        else begin
            reg1_pulse_wide_clk1 <= reg1_pulse_wide_clk2    ;
            reg2_pulse_wide_clk1 <= reg1_pulse_wide_clk1    ;
        end
    end

    //目的时钟域将展宽后的信号变成1个脉冲的信号,取目的时钟域展宽后的上上升沿实现

    reg reg2_pulse_wide_clk2; //对reg1_pulse_wide_clk2同步一拍

    reg pos_pulse_wide_clk2;

    always@(posedge clk2 or posedge rst) begin
        if(rst) begin
            reg2_pulse_wide_clk2 <= 1'd0;
            pos_pulse_wide_clk2 <= 1'd0;
        end
        else begin
            reg2_pulse_wide_clk2 <= reg2_pulse_wide_clk1;
            pos_pulse_wide_clk2 <= ~reg2_pulse_wide_clk2 && reg1_pulse_wide_clk2;
        end
    end

    //将时钟域clk2中的单脉冲信号作为输出;
    assign  pulse_syn_clk2 = pos_pulse_wide_clk2;

endmodule

RTL原理图:

RTL原理图

图中方框内部就是上升沿检测的实现,最后一个触发器是对上升沿检测结果打了一拍,对时序还是有点好处的。

行为仿真

仿真平台和以前的一样:

`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: cdc_tb
//
module cdc_tb(

    );

    reg     clk1            ;
    reg     clk2            ;
    reg     rst             ;
    reg     pulse_clk1      ;
    wire    pulse_syn_clk2  ;

    initial begin
        clk1 = 0;
        forever
        #3 clk1 = ~clk1;
    end

    initial begin
        clk2 = 0;
        forever
        #5.5 clk2 = ~clk2;
    end

    initial begin
        rst = 1;
        pulse_clk1 = 0;
        #15
        rst = 0;
        #15
        @(posedge clk1) begin
           pulse_clk1 = #0.5 1'b1;
        end
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b0;
        end
        #3 //距离太近
        #59.5 //距离足够远
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b1;
        end
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b0;
        end        

    end

    fast2slow_CDC u_fast2slow_CDC(
        .clk1        ( clk1        ),
        .clk2        ( clk2        ),
        .rst         ( rst         ),
        .pulse_clk1  ( pulse_clk1  ),
        .pulse_syn_clk2  ( pulse_syn_clk2  )
    );

endmodule

仿真波形:

仿真波形

可见,实现了预期结果。 内部信号我就不拉出来了,有兴趣的自己拉出来看看实现!

低电平脉冲的展宽处理

现在我们进入第二个补充问题,即如何实现对低电平脉冲的展宽同步处理?

先给一个仿真截图:也就是需要这个效果:

低电平脉冲展宽处理

我们如何了解了高电平脉冲的展宽处理方式之后,低电平脉冲的展宽处理也就很简单了,一句话,展宽目标改为低电平脉冲就好了,下面给出RTL代码:

`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: fast2slow_CDC_LL
//
module fast2slow_CDC_LL(
    input   wire    clk1            ,
    input   wire    clk2            ,
    input   wire    rst             ,
    input   wire    pulse_clk1      ,
    output  wire    pulse_syn_clk2  

    );

    reg     pulse_wide_clk2         ;
    reg     reg1_pulse_wide_clk2    ;

    reg     reg1_pulse_wide_clk1    ;
    reg     reg2_pulse_wide_clk1    ;

    //生成脉冲展宽信号
    reg     pulse_wide_clk1         ;
    // always@(posedge clk1 or posedge rst) begin
    //     if(rst) begin
    //         pulse_wide_clk1 <= 1'b0             ;
    //     end
    //     else if(pulse_clk1) begin
    //         pulse_wide_clk1 <= 1'b1             ;
    //     end
    //     else if(reg2_pulse_wide_clk1) begin
    //         pulse_wide_clk1 <= 1'b0             ;
    //     end
    //     else begin
    //         pulse_wide_clk1 <= pulse_wide_clk1  ;
    //     end
    // end

    always@(posedge clk1 or posedge rst) begin
        if(rst) begin
            pulse_wide_clk1 <= 1'b1                 ;
        end
        else if(~pulse_clk1) begin
            pulse_wide_clk1 <= 1'b0                 ;
        end
        else begin
            if(~reg2_pulse_wide_clk1) begin
                pulse_wide_clk1 <= 1'b1             ;
            end
            else begin
                pulse_wide_clk1 <= pulse_wide_clk1  ;
            end
        end
    end

    //在目的时钟域内采样展宽后的信号

    always@(posedge clk2 or posedge rst) begin
        if(rst) begin
            pulse_wide_clk2 <= 1'b1                 ;
            reg1_pulse_wide_clk2 <= 1'b1            ;
        end
        else begin
            pulse_wide_clk2 <= pulse_wide_clk1      ;
            reg1_pulse_wide_clk2 <= pulse_wide_clk2 ;
        end
    end

    //在源时钟域内同步目的时钟域内的展宽信号,以生成反馈信号

    always@(posedge clk1 or posedge rst) begin
        if(rst) begin
            reg1_pulse_wide_clk1 <= 1'b1                    ;
            reg2_pulse_wide_clk1 <= 1'b1                    ;
        end
        else begin
            reg1_pulse_wide_clk1 <= reg1_pulse_wide_clk2    ;
            reg2_pulse_wide_clk1 <= reg1_pulse_wide_clk1    ;
        end
    end

    //目的时钟域将展宽后的信号变成1个脉冲的信号,取目的时钟域展宽后的上上升沿实现

    reg reg2_pulse_wide_clk2; //对reg1_pulse_wide_clk2同步一拍

    reg neg_pulse_wide_clk2;

    always@(posedge clk2 or posedge rst) begin
        if(rst) begin
            reg2_pulse_wide_clk2 <= 1'd1;
            neg_pulse_wide_clk2 <= 1'd0;
        end
        else begin
            reg2_pulse_wide_clk2 <= reg2_pulse_wide_clk1;
            neg_pulse_wide_clk2 <= ~reg1_pulse_wide_clk2 && reg2_pulse_wide_clk2;
        end
    end

    //将时钟域clk2中的单脉冲信号作为输出;
    assign  pulse_syn_clk2 = ~neg_pulse_wide_clk2;

endmodule

我在高电平展宽电路的设计基础上改了下,就是上述的RTL代码,下面给出仿真平台:

`timescale 1ns / 1ps
//
// Engineer: 李锐博恩
// Create Date: 2021/01/23
// Module Name: cdc_tb
//

module cdc_tb(

    );

    reg     clk1            ;
    reg     clk2            ;
    reg     rst             ;
    reg     pulse_clk1      ;
    wire    pulse_syn_clk2  ;

    initial begin
        clk1 = 0;
        forever
        #3 clk1 = ~clk1;
    end

    initial begin
        clk2 = 0;
        forever
        #5.5 clk2 = ~clk2;
    end

    initial begin
        rst = 1;
        pulse_clk1 = 1;
        #15
        rst = 0;
        #15
        @(posedge clk1) begin
           pulse_clk1 = #0.5 1'b0;
        end
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b1;
        end
        #3 //距离太近
        #59.5 //距离足够远
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b0;
        end
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b1;
        end        

    end

    fast2slow_CDC_LL u_fast2slow_CDC_LL(
        .clk1        ( clk1        ),
        .clk2        ( clk2        ),
        .rst         ( rst         ),
        .pulse_clk1  ( pulse_clk1  ),
        .pulse_syn_clk2  ( pulse_syn_clk2  )
    );

endmodule

也仅仅是微调而已!

仿真波形前面也已经给出: 低电平脉冲展宽处理

切换同步器的原理与实现

Toggle同步器用于将源时钟域产生的脉冲同步到目的时钟域。脉冲不能直接使用2 FF同步器进行同步。当使用2个FF同步器从快时钟域同步到慢时钟域时,脉冲可以被跳过,这可能会导致脉冲检测的损失,因此依赖于它的后续电路可能无法正常工作。

其实现原理图:

Toggle 同步器

时序图:

Toggle同步器的时序图

如何理解呢?

首先就是对源时钟域的脉冲进行处理,处理方式是将脉冲信号作为一个MUX选择器的选择信号,如果为1,选择同步源触发器的反信号作为输出,如果为0,选择同步源触发器输出信号本身作为输出;源触发器的输出经过目的时钟域两级同步,取上升沿即可实现高电平脉冲的同步。

RTL实现

module toggle_synchronizer(
    input wire clk1,
    input wire clk2,
    input wire rst,
    input wire pulse_clk1,
    output reg pulse_syn_clk2

    );

    reg toggle_pulse_clk1;
    always@(posedge clk1 or posedge rst) begin
        if(rst) begin
            toggle_pulse_clk1 <= 1'd0;
        end
        else if(pulse_clk1) begin
            toggle_pulse_clk1 <= ~toggle_pulse_clk1;
        end
        else begin
            toggle_pulse_clk1 <= toggle_pulse_clk1;
        end
    end

    reg toggle_pulse_clk2, toggle_pulse_clk2_d1, toggle_pulse_clk2_d2;
    always@(posedge clk2 or posedge rst) begin
        if(rst) begin
            toggle_pulse_clk2 <= 1'd0;
            toggle_pulse_clk2_d1 <= 1'd0;
            toggle_pulse_clk2_d2 <= 1'd0;
            pulse_syn_clk2 <= 1'd0;
        end
        else begin
            toggle_pulse_clk2 <= toggle_pulse_clk1;
            toggle_pulse_clk2_d1 <= toggle_pulse_clk2;
            toggle_pulse_clk2_d2 <= toggle_pulse_clk2_d1;
            pulse_syn_clk2 <= ~toggle_pulse_clk2_d2 && toggle_pulse_clk2_d1;
        end
    end

endmodule

这种方式我不想说太多,因为局限性太强,只适用于只有一个脉冲的情况,

为什么呢?给出仿真平台来看看:

module cdc_tb(

    );

    reg     clk1            ;
    reg     clk2            ;
    reg     rst             ;
    reg     pulse_clk1      ;
    wire    pulse_syn_clk2  ;

    initial begin
        clk1 = 0;
        forever
        #3 clk1 = ~clk1;
    end

    initial begin
        clk2 = 0;
        forever
        #5.5 clk2 = ~clk2;
    end

    initial begin
        rst = 1;
        pulse_clk1 = 0;
        #15
        rst = 0;
        #15
        @(posedge clk1) begin
           pulse_clk1 = #0.5 1'b1;
        end
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b0;
        end
        #3 //距离太近
        #79.5 //距离足够远
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b1;
        end
        @(posedge clk1) begin
            pulse_clk1 = #0.5 1'b0;
        end        

    end

toggle_synchronizer u_toggle_synchronizer(
    .clk1       ( clk1       ),
    .clk2       ( clk2       ),
    .rst        ( rst        ),
    .pulse_clk1 ( pulse_clk1 ),
    .pulse_syn_clk2  ( pulse_syn_clk2  )
);

endmodule

仿真波形如下:

toggle 同步器的波形图

可见,第二个脉冲被吞掉了!

如果仅有一个脉冲呢?

toggle同步器的波形图

看起来就很完美,所以这是一个适用性的问题!

打赏
共1人已赏
一个努力写作的FPGA爱好者、从业者,CSDN博客专家,CSDN上万关注量,百万
评论
0个
内容存在敏感词
相关专栏
打赏作者
易百纳技术社区
李锐博恩
您的支持将鼓励我继续创作!
打赏金额:
¥1 易百纳技术社区
¥5 易百纳技术社区
¥10 易百纳技术社区
¥50 易百纳技术社区
¥100 易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区 微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

审核成功

发布时间设置
发布时间:

审核失败

失败原因
备注
Loading...
易百纳技术社区
确定要删除此文章、专栏、评论吗?
确定
取消
易百纳技术社区
易百纳技术社区
在专栏模块发布专栏,可获得其他E友的打赏
易百纳技术社区
回答悬赏问答,被题主采纳后即可获得悬赏金
易百纳技术社区
在上传资料时,有价值的资料可设置为付费资源
易百纳技术社区
达到一定金额,收益即可提现~
收益也可用来充值ebc,下载资料、兑换礼品更容易
易百纳技术社区
活动规则
  • 1.周任务为周期性任务,每周周一00:00刷新,上周完成的任务不会累计到本周,本周需要从头开始任务,当前任务完成后才可以完成下一个任务
  • 2.发布的专栏与资料需要与平台的板块有相关性,禁止注水,专栏/资料任务以审核通过的篇数为准
  • 3.任务完成后,现金奖励直接打款到微信账户;EBC/收益将自动发放到个人账户,可前往“我的钱包”查看;其他奖励请联系客服兑换
  • 4.每周最后三个任务将会有以下奖品掉落:社区热卖开发板、小米音响、视频年度会员、京东卡、华为手机等等
易百纳技术社区
升级提醒
易百纳技术社区

恭喜您由入门

社区送出礼品一份

请填写您的收件地址,礼品将在3个工作日寄出

易百纳技术社区