跨时钟域处理------握手协议verilog(究极保姆级教程)

本文详细介绍了在芯片设计中如何通过握手协议进行跨时钟域数据同步,包括单比特传输、FIFO的应用以及握手协议的具体流程。作者提供了两个模块的RTL代码和testbench示例,展示了如何处理亚稳态和确保数据稳定传输。
摘要由CSDN通过智能技术生成

1.绪论

         在芯片设计中经常需要跨时钟域传输处理一些数据,为了避免亚稳态带来的问题,所以需要对这些数据进行同步处理。目前常用的同步技术有以下几类,在不同的情况下会有不同的选择。

        1.单比特数据跨时钟域传输常常只用打两拍即可。

        2.当要传输的数据较多时,可以在不同的时钟域之间使用FIFO。

        3.少量数据传输时,在不同的时钟域之间可以使用握手协议。

        本文主要结合个人的理解介绍握手协议,有许多地方都不够严谨,有误之处还请指出。

PS:关于时钟域和亚稳态的知识可以去看下这篇文章:CDC:跨时钟域处理_cdc clock_杰之行的博客-CSDN博客

2.握手协议框图

        这次设计中有两个模块,其中tx模块负责发送数据rx,rx模块在接收tx模块给的值后会返回数据,如下图:

        

        上图中带t的都代表是发送模块中的输入输出,带r的代表为接收模块中的输入输出。下面来说明各个信号的意义(clk和reset信号就不介绍了):

data_in:输入的数据。

valid:数据有效信号,在高电平时代表输入数据有效。

data_out_t:valid信号上升沿到来时输出的data_in的数据(先存在写数据线上)。

data_in_r:rx从写数据线上接收的数据。

req_t:tx输出的请求信号。

req_r:rx接收到的请求信号(还需经过亚稳态处理才能使用)。

ack_r:rx在接收到打拍后的req请求信号后输出的应答信号。

ack_t:   tx接收到的应答信号(同样要处理后才能使用)。

data_out_r:rx返回给tx的数据(先存在读数据线上)。

data_in_t:tx从读数据线上接收到的数据。

3. 握手协议具体流程

具体流程如下:

1.tx模块接收外部输入信号data_in和valid,在valid上升沿到来时输出data_out_t到写数据线上,并且同时发出请求信号req_t告知rx模块我想要跟你传输数据(这个过程就像是tx给rx伸出象征友好的手)

ps:这里使用valid上升沿而不用valid本身为1来判断的原因是为了避免valid信号持续时间过长导致在一个valid期间多次传输数据。写数据线在顶层模块中用wire模拟了。

2.req_t在传递给rx后再经过打两拍处理得到req_dd,此时经过两拍之后数据已经稳定,rx认为tx的数据已经准备好了便接收写数据线上的数据,同时将要返回的数据data_out_r写到读数据线上,并且返回出一个应答信号ack_r给tx,以此告诉tx自己已经接收了数据并发送了返回值,至此已经完成了半握手(这个过程就像是rx伸出手和tx握手)

3.应答信号ack_r传给ack_t后再经过打两拍处理得到ack_dd,此时数据已经稳定,tx认为rx的数据已经准备好了便接收读数据线上的数据,同时拉低请求信号req_t,告知rx自己已经接收完数据。(tx伸出的手收回)

4.rx在检测到req_dd拉低后,知道了tx接收了返回数据,将应答信号ack_r也拉低(rx伸出的手收回)

5.tx检测到ack_dd拉低,本次传输过程结束。等下一个valid上升沿拉发起下一次数据交换。

4.代码编写与仿真验证

接下来是各模块代码和testbench的编写,有大量注释。

4.1 tx模块RTL编写

module fullhs_tx(
input              tclk        ,
input              treset      ,
input      [31:0]  data_in     ,
input      [31:0]  data_in_t   ,
input              valid       ,
input              ack_t       ,
output reg [31:0]  data_out_t  ,
output reg          req_t         
    );
reg  [31:0]   data_in_t_valid ; //从接收模块中返回的数据稳定后的值。
reg           ack_d           ; //打1拍。
reg           ack_dd          ; //打2拍。
reg           valid_d         ; //valid 信号延时1拍。
wire          valid_posflag   ; //valid上升沿flag信号。
//为了解决当一个valid信号过长时导致在一个valid期间多次传输数据的问题,这里使用valid的上升沿作为判断条件。
//上升沿检测。
always@(posedge tclk or negedge treset) begin
if(!treset) begin
    valid_d <= 'b0;
end
else begin
    valid_d <= valid;
end
end

assign  valid_posflag = valid & (~valid_d);
//刚返还tx的数据并不稳定,在这里认为数据在应答信号ack经过tx的两个D触发器(打两拍)消除亚稳态之后才算稳定有效的值
always@(posedge tclk or negedge treset) begin
if(!treset) begin
    ack_d  <= 'b0;
    ack_dd <= 'b0;
end
else begin
    ack_d  <= ack_t;
    ack_dd <= ack_d;
end   
end


always@(posedge tclk or negedge treset) begin
if(!treset) begin
    data_in_t_valid <= 'b0;
    req_t           <= 1'b0;
    data_out_t      <= 'b0;
end
else if(ack_dd == 1) begin  //收到返回的akc信号(打两拍后的)后让req信号置零。
     data_in_t_valid <= data_in_t ;
     req_t           <= 1'b0; 
end   

else if(valid_posflag == 1) begin    //在数据有效信号为1时让data_out_t获取data_in的值,让req信号在valid来时拉起。
     req_t           <= 1'b1 ;
     data_out_t      <= data_in;
end    


end




endmodule

4.2 rx模块RTL编写

module fullhs_rx(
input                    rclk            ,
input                    rreset          ,
input          [31:0]    data_in_r       ,
input                    req_r           ,
output    reg            ack_r           ,
output    reg  [31:0]    data_out_r          //为了方便观察波形,让这里返还的数据与接受数据取反相同。
    );
reg  [31:0]    data_in_r_valid ; //从发送模块中接受的稳定后的数据。
reg            req_d           ; //req经过一个D触发器。
reg            req_dd          ; //req经过两个D触发器。


//刚进入rx的数据并不稳定,在这里认为数据在请求信号req经过rx的两个D触发器(打两拍)消除亚稳态之后才算稳定有效的值
always@(posedge rclk or negedge rreset) begin
if(!rreset) begin
    req_d  <= 'b0;
    req_dd <= 'b0;
end
else begin
    req_d  <= req_r;
    req_dd <= req_d;
end   
end

//在数据消除亚稳态之后,根据req信号做出相应操作。
always@(posedge rclk or negedge rreset) begin
if(!rreset) begin
    data_in_r_valid <= 'b0;
    ack_r            <= 'b0;
    data_out_r      <= 'b0;
end
else if(req_dd == 1) begin   //在req信号为1时,拉高应答信号ack,接收tx发送信号,返回反还值。
     data_in_r_valid <= data_in_r ;
     ack_r           <= 1'b1; 
     data_out_r      <= ~data_in_r_valid ;

end   
else if(req_dd == 0) begin //req信号拉低后,拉低ack信号,结束一次通信。

     ack_r           <= 1'b0 ;
    

end    
end

 
endmodule

4.3顶层模块编写

        这个模块的作用是将tx和rx的io相互绑定,定义了数据线,并且给后续testbench提供输入激励的端口。

module fullhs_top(
input tclk,
input rclk,
input [31:0] data_in,
input valid ,
input reset
    );
wire [31:0]	wrdata;
wire [31:0]	rddata;
wire req;
wire ack;

 fullhs_tx fullhs_tx_u(
.tclk      ( tclk       ),
.treset    ( reset      ),
.data_in_t ( rddata     ),
.ack_t     ( ack        ),
.data_out_t( wrdata     ),
. req_t    ( req        ),
.data_in   (data_in     ),
.valid     (valid       )

);

fullhs_rx fullhs_rx_u(
.rclk       (  rclk      ),
.rreset     (  reset     ),
.data_in_r  ( wrdata    ),
.req_r      (  req       ),
.ack_r      (  ack       ),
.data_out_r (  rddata    )


);   
endmodule

4.4 testbench编写

`timescale 1ns / 1ps

module tb_fullhs(    );
reg     tclk;
reg     rclk;
reg  [31:0]   data_in;
reg     valid;
reg     reset;

initial begin
tclk = 0;
rclk = 0;
reset = 0;
data_in = 0;
valid = 0;
#200 ;
reset = 1;
#300 ;
data_in = 32'hf0f0f0f0;
valid   = 1;
#200 ;
valid = 0;
#300 ;
data_in = 32'hffff0000;
valid = 1;
#200 ;
valid = 0;
# 300;
data_in = 32'hff00ff00;
valid =1;

end
always # 5 tclk = ~tclk;
always # 10  rclk = ~rclk;


fullhs_top fullhs_top_u(
.tclk (tclk  ),
.rclk (rclk  ),
.data_in(data_in),
.valid(valid),
.reset(reset )


);

endmodule

4.5 仿真波形

4.5.1 慢时钟域到快时钟域

     

4.5.2 快时钟域到慢时钟域

修改testbench中的tclk和rclk

5.参考文献

跨时钟域之全握手_跨时钟 握手_DeamonYang的博客-CSDN博客

这篇文章写得很不错可以看看,但是没有设置valid信号。

        

  • 14
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值