在芯片设计中经常需要跨时钟域传输处理一些数据,为了避免亚稳态带来的问题,所以需要对这些数据进行同步处理。目前常用的同步技术有以下几类,在不同的情况下会有不同的选择。
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;
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.2 快时钟域到慢时钟域
修改testbench中的tclk和rclk
跨时钟域之全握手_跨时钟 握手_DeamonYang的博客-CSDN博客
这篇文章写得很不错可以看看,但是没有设置valid信号。
相关知识
跨时钟域处理
C语言的小问题
HTML显示人体时钟和仓鼠互动效果
私域用户超百万,年销数亿,拆解宠物电商第一股私域打法
私域or本地生活?这场宠物零售主题大会都分享了啥
私域用户超百万,年销数亿,拆解宠物电商第一股私域打法 – 人人都是产品经理,
【爆爽魔域世界10元=无限元宝 100元=顶级权限】【人鱼传说上线送V4】
宠物行业如何做私域社群运营?
狗狗行为学——爬跨
人狗同桌进餐?公域不同私域
网址: 跨时钟域处理 https://m.mcbbbk.com/newsview310095.html
上一篇: 怎么训练猫咪握手 |
下一篇: 宠物狗训练握手方法:教你轻松教会 |