首页专栏详情
打赏
FPGA逻辑设计回顾(7)多比特信号的CDC处理方式之握手同步
易百纳技术社区 李锐博恩 2021-01-17 00:16:33

前言

每种跨时钟域处理的方式都有其适用范围,例如:两级同步器,用于单比特信号处理,且是从慢时钟域到快时钟域:

两级同步器

还有反馈展宽同步方式,用于单比特信号同步,且从慢时钟域到快时钟域:FPGA逻辑设计回顾(4)亚稳态与单比特脉冲信号的CDC处理问题

Mux同步器,用于单向同步的多比特同步:

Mux同步器

FPGA逻辑设计回顾(5)多比特信号的CDC处理方式之MUX同步器

异步FIFO用途倒是挺广,但是过于有时杀鸡用牛刀也是多次一举:

异步FIFO

FPGA逻辑设计回顾(6)多比特信号的CDC处理方式之异步FIFO

格雷码同步在异步FIFO的内部得以应用,用于读写指针的跨时钟域传输,在上述异步FIFO链接中也有讲到。

格雷码同步

本文要介绍的CDC处理方式是握手同步,它适用于数据稳定(不频繁变化)情况下的数据跨时钟域同步。

看见了吧,这就是数字设计的多样性,针对不同的场景可以使用不同的同步方式。

这些方式都掌握是一件好事,一是可以锤炼自己的设计水平,另外,只有熟练掌握才能随机应变。还有,这是找工作的常考知识!

握手同步介绍

在这种同步方案中,无论源时钟和目的时钟之间的时钟周期比如何,都采用请求和确认机制来保证正确的数据采样到目的时钟域。这种技术主要用于不连续变化或非常频繁地变化的数据。

如下图表显示了这种实现方式: 握手同步

如下图握手同步的时序所示,数据应该在总线上保持稳定,直到从目的端接收到同步确认信号(A2-q),并且它(A2-q)变为低电平。

握手同步的时序

握手同步的RTL实现

虽然上面给出了实现框图,但是毕竟是框图,图中一个Sender FSM,以及Received FSM,没有给出实现细节,对初学者可能还像是空中楼阁一样,没什么实际的作用。 其实有一个技巧,就是看时序图,可以从时序图中得到握手同步的实现方式。 这也是作为一个设计者的素质,RTL代码,时序图,无缝切换,所谓,做逻辑的,不画时序图算什么?

从图中时序图,我们提炼出输入信号:

  • clk_a, clk_b
  • rst(这个图中没有,但是很有必要)
  • a_en,data_a(这两者作为输入,可以当成一种协议,检测到a_en有效(下降沿)的时候,输入数据就更新,更新数据一直持续到a_en的下一个下降沿)

输出信号:

  • b_en,这是对a_en的同步
  • data_b_out,这是对b的同步
  • ack_a,这是我个人想要加进来的,它是B时钟域的响应信号ack,同步到a时钟域后的信号,通知a时钟域(下降沿通知),可以发送下一个数据了。事实上,a_en就是该信号的下降沿;

好了,有了这些信息,可以进行RTL逻辑设计了:

我给出我的设计代码:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 李锐博恩
// Create Date: 2021/01/16 00:34:50
// Module Name: syn_handshake
//////////////////////////////////////////////////////////////////////////////////
module syn_handshake(
    input   wire       clk_a        ,
    input   wire       clk_b        ,
    input   wire       rst          ,
    input   wire       a_en         , //来自于外部的使能信号,脉冲持续一个时钟周期
    input   wire [3:0] data_a_in    , //外部输入信号
    output  reg  [3:0] data_b_out   ,
    output  wire       b_en         ,
    output  wire       ack_a

    );

    //生成a_en的下降沿
    reg     a_en_d1     ;
    reg     a_en_d2     ;
    reg     a_en_neg    ;
    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a_en_d1 <= 1'd0;
            a_en_d2 <= 1'd0;
            a_en_neg <= 1'd0;
        end
        else begin
            a_en_d1 <= a_en;
            a_en_d2 <= a_en_d1;
            // a_en_neg <= ~a_en_d1 && a_en_d2;
            a_en_neg <= ~a_en && a_en_d1;
        end
    end

    //生成请求信号
    reg     a_req   ; 
    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a_req <= 1'd0;
        end
        else if(a_en_neg) begin
            a_req <= 1'd1;
        end
        else if(a2_q_pos) begin //a_req 拉低条件
            a_req <= 1'd0;
        end
        else begin
            a_req <= a_req;
        end

    end

    //请求信号a_req跨时钟域处理
    reg     b1_q, b2_q;
    always@(posedge clk_b or posedge rst) begin
        if(rst) begin
            b1_q <= 1'd0;
            b2_q <= 1'd0;
        end
        else begin
            b1_q <= a_req;
            b2_q <= b1_q;
        end
    end

    //生成时钟域b内的数据使能信号
    //b2_q信号的上升沿
    //b时钟域对a时钟域的响应信号
    reg     b2_q_d1;
    reg     b2_q_d2;
    reg     b2_q_d3;
    wire     b_en;
    reg     ack_b;
    always@(posedge clk_b or posedge rst) begin
        if(rst) begin
            b2_q_d1 <= 1'd0;
            b2_q_d2 <= 1'd0;
            b2_q_d3 <= 1'd0;
            ack_b <= 1'd0;            
        end
        else begin
            b2_q_d1 <= b2_q;
            b2_q_d2 <= b2_q_d1;
            b2_q_d3 <= b2_q_d2;
            ack_b <= b2_q_d2;
        end
    end
    assign b_en = ~b2_q_d3 && b2_q_d2;

    //b_en有效,则表示数据有效,可以采样a时钟域的数据了
    always@(posedge clk_b or posedge rst) begin
        if(rst) begin
            data_b_out <= 4'd0;
        end
        else if(b_en) begin
            data_b_out <= data_a_in;
        end
    end

    //响应信号同步到a时钟域
    //a2_q上升沿作为a_req拉低的条件
    reg a1_q, a2_q;
    reg a3_q ;
    wire a2_q_pos;
    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a1_q <= 1'd0;
            a2_q <= 1'd0;
            a3_q <= 1'd0;
        end
        else begin
            a1_q <= ack_b;
            a2_q <= a1_q;
            a3_q <= a2_q;
        end
    end
    assign a2_q_pos = ~a3_q && a2_q;

    assign ack_a = a2_q; //此信号作为a_en的反馈信号,a_en取此信号的下降沿

endmodule

有的朋友,可能会说了,我看过其他的握手同步的设计方法,为什么代码和你的不一样,或者我看过你的其他篇,握手同步,但是为什么代码和以前的不一样;

这个嘛!不同时期写的嘛,以前我怎么写的,我也忘了。但是我相信是万变不离其中的。

握手同步的行为仿真

有了设计,没有仿真怎么能行,每一个设计都是需要仿真的,要不然怎么证明你的设计是没有问题的。

行为仿真是验证逻辑功能的关键一步!

本逻辑仿真的重点在于设计输入信号,输入信号中的重点在于我刚才说的握手同步是有适用场景的,因此,要设计这个场景的输入信号,这里要设计的是输入数据一定要是稳定的,不要频繁的变化;

如下我设计的仿真逻辑,data_a,只有在a_en有效的时候才更新,a_en有效,那必然是时钟域B同步完成了。这就相当于一个握手的过程完成了,协议达成, 成 交!

这里给出仿真平台:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 李锐博恩
// Create Date: 2021/01/16 00:34:50
// Module Name: syn_handshake_tb
//////////////////////////////////////////////////////////////////////////////////

module syn_handshake_tb(

    );

    reg       clk_a        ;
    reg       clk_b        ;
    reg       rst          ;
    reg       a_en         ; //来自于外部的使能信号,脉冲持续一个时钟周期
    reg [3:0] data_a_in    ; //外部输入信号
    wire [3:0] data_b_out  ;
    wire       b_en        ;
    wire       ack_a       ;

    initial begin
        clk_a = 1'd0;
        forever begin
            #2 clk_a = ~clk_a;
        end
    end

    initial begin
        clk_b = 1'd0;
        forever begin
            #3 clk_b = ~clk_b;
        end
    end

    initial begin
        rst = 1'd1;
        #15
        @(negedge clk_a);
        rst = 1'd0;

    end

    reg ack_a_d1;

    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            a_en <= 1'd1;
            ack_a_d1 <= 1'd0;
        end
        else begin
            ack_a_d1 <= ack_a;
            a_en <= ~ack_a && ack_a_d1;
        end
    end

    always@(posedge clk_a or posedge rst) begin
        if(rst) begin
            data_a_in <= 4'd0;
        end
        else if(a_en) begin
            data_a_in <= $random;
        end
    end

    syn_handshake u_syn_handshake(
        .clk_a       ( clk_a       ),
        .clk_b       ( clk_b       ),
        .rst         ( rst         ),
        .a_en        ( a_en        ),
        .data_a_in   ( data_a_in   ),
        .data_b_out  ( data_b_out  ),
        .b_en        ( b_en        ),
        .ack_a       ( ack_a       )
    );

endmodule

一个简单的TB文件!

仿真波形:

握手同步仿真波形

下面标出重点:

握手同步时序波形

其他不说,红色方框内的信号表示输入信号已经同步到时钟域B了。

中间信号呢?下面也给出:

握手同步仿真中间信号

建议大家放到自己平台上仿真,然后对着代码以及仿真,分析实现的过程。

最后给出RTL原理图:

RTL原理图 可见,我并没有如原理中所说,使用到状态机的方式,方法可能很多,选择适合你的吧。

再给出综合后的原理图,以验证我的设计是可综合的:

综合后的原理图

不得不说,这可能是我要总结的CDC的最后一种方式了。

也不一定,还有一些方法,说不定我有空了,还会总结几个,但基本就这些了,再见吧,朋友们。

参考资料

Synchronizer techniques for multi-clock domain SoCs & FPGAs

FIFO, handshake synchronizers a challenge for CDC analysis

Get those clock domains in sync

Crossing the abyss:asynchronous signals in a synchronous world

Clock Domain Crossing

有空多读读这些英文资料,可能会有启发!

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

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

审核成功

发布时间设置
发布时间:

审核失败

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

恭喜您由入门

社区送出礼品一份

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

易百纳技术社区