首页 > 分享 > 跨时钟域处理

跨时钟域处理

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;

reset = 1;

data_in = 32'hf0f0f0f0;

valid = 1;

#200 ;

valid = 0;

#300 ;

data_in = 32'hffff0000;

valid = 1;

valid = 0;

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信号。

相关知识

跨时钟域处理
C语言的小问题
HTML显示人体时钟和仓鼠互动效果
私域用户超百万,年销数亿,拆解宠物电商第一股私域打法
​私域or本地生活?这场宠物零售主题大会都分享了啥
私域用户超百万,年销数亿,拆解宠物电商第一股私域打法 – 人人都是产品经理,
【爆爽魔域世界10元=无限元宝 100元=顶级权限】【人鱼传说上线送V4】
宠物行业如何做私域社群运营?
狗狗行为学——爬跨
人狗同桌进餐?公域不同私域

网址: 跨时钟域处理 https://m.mcbbbk.com/newsview310095.html

所属分类:萌宠日常
上一篇: 怎么训练猫咪握手
下一篇: 宠物狗训练握手方法:教你轻松教会