赞
踩
在芯片设计中经常需要跨时钟域传输处理一些数据,为了避免亚稳态带来的问题,所以需要对这些数据进行同步处理。目前常用的同步技术有以下几类,在不同的情况下会有不同的选择。
1.单比特数据跨时钟域传输常常只用打两拍即可。
2.当要传输的数据较多时,可以在不同的时钟域之间使用FIFO。
3.少量数据传输时,在不同的时钟域之间可以使用握手协议。
本文主要结合个人的理解介绍握手协议,有许多地方都不够严谨,有误之处还请指出。
PS:关于时钟域和亚稳态的知识可以去看下这篇文章:CDC:跨时钟域处理_cdc clock_杰之行的博客-CSDN博客
这次设计中有两个模块,其中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从读数据线上接收到的数据。
具体流程如下:
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上升沿拉发起下一次数据交换。
接下来是各模块代码和testbench的编写,有大量注释。
- 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
- 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
这个模块的作用是将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
- `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
修改testbench中的tclk和rclk
跨时钟域之全握手_跨时钟 握手_DeamonYang的博客-CSDN博客
这篇文章写得很不错可以看看,但是没有设置valid信号。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。