IC每日一题:CDC多bits跨时钟传输+handShake
- 1 八股题:CDC多bits跨时钟传输
- 1.1 慢到快:MUX enbale同步器法
- 1.1.1 分析
- 1.1.2 时序图
- 1.1.3 代码
- 1.2 快到慢:握手协议
- 1.2.1 原理分析
- 1.2.2 时序图
- 1.2.3 代码题
- 2 手撕题:使用握手信号实现跨时钟数据传输问题
- 2.1 步骤
- 2.2 波形图
- 2.2 代码
1 八股题:CDC多bits跨时钟传输
上篇文章介绍CDC单bit跨时钟传输的解决办法:
- 当慢到快:采用两级同步打拍;
- 当快到慢:1.信号展宽+边沿检测;2.握手(request + ack);
本篇文章介绍CDC多bits跨时钟数据传输的解决办法; - 慢到快:MUX同步器法
- 快到慢:握手协议
- 异步FIFO通杀:放在另一个专题博客中;
1.1 慢到快:MUX enbale同步器法
1.1.1 分析
分 析:慢到快这种情况在快时钟接收端是一定能够采样得到的,多bits数据在传输过程中有多位同时变化,那么有什么解决办法呢?解决办法是在传输过程中不变化!所以必须在写入使能信号有效时传输!
解决办法:传输非同步数据到接收时钟域时配上一个同步的控制信号,数据和控制信号被同时发送到接收时钟域,同时控制信号在接收时钟域使用两级寄存器同步到接收时钟域,使用此同步后的控制信号来加载数据,这样数据就可以在目的寄存器被安全加载。
1.1.2 时序图
dout_en多打几拍din_en,这样能够保持din这段时间不变化;
1.1.3 代码
//==========================================================
//--Author : colonel
//--Date : 11-01
//--Module : sync_multi_bits_slow_2_fast
//--Function: sync multi bits from slow_clk to fast_clk using MUX
//-- Only considering the yaWenTai;
//==========================================================
module sync_multi_bits_slow_2_fast (
//==========================< 端口 >=========================
input wire slow_clk,
input wire rst_n,
input wire din_en,
input wire [32 -1:0] din,
input wire fast_clk,
output wire dout_en,
output wire [32 -1:0] dout
);
//==========================< 信号 >=========================
reg din_en_r1;
reg din_en_r2;
reg din_en_r3;
//=========================================================
//-- din_en_r1/r2/r3: sync din_en to fast_clk
//=========================================================
always @(posedge fast_clk or negedge rst_n) begin
if (!rst_n) begin
din_en_r1 <= 1'b0;
din_en_r2 <= 1'b0;
din_en_r3 <= 1'b0;
end else begin
din_en_r1 <= din_en;
din_en_r2 <= din_en_r1;
din_en_r3 <= din_en_r2;
end
end
wire din_en_pos = din_en_r2 && !din_en_r3;
//==========================< 信号 >=========================
reg [32 -1:0] dout_r;
reg dout_en_r;
//=========================================================
//-- dout_r, dout_en_r
//=========================================================
always @(posedge fast_clk or negedge rst_n) begin
if (!rst_n) begin
dout_r <= 'b0;
end else if(din_en_pos) begin
dout_r <= din;
end else begin
dout_r <= dout_r;
end
end
always @(posedge fast_clk or negedge rst_n) begin
if (!rst_n) begin
dout_en_r <= 'b0;
end else begin
dout_en_r <= din_en_pos;
end
end
//=========================================================
//-- dout,dout_en
//=========================================================
assign dout_en = dout_en_r;
assign dout = dout_r;
endmodule
1.2 快到慢:握手协议
快到慢涉及考虑漏采的风险,在多bits传输过程中,这里采用握手的方式;
1.2.1 原理分析
完全握手的特点是发送域和接收域两端在发送请求或者终止请求之前都需要等待对端的回应。完全握手在收发两侧都使用的是电平同步器。
1.2.2 时序图
对时序图解释:
用前缀t_表示发送域,用前缀r_表示接收域,发送时钟用t_clk表示,接收时钟用r_clk表示。数据由发送域向接收域传输;以下是完全握手的工作步骤:
1.当发送域数据准备好后,发送域将t_req信号置为有效;
2.接收域采用两级电平同步器同步t_req信号,并把同步后的信号命名为r_req_sync2;
3.接收域检测到t_req_sync2信号有效时, 在下一个r_clk周期中,接收域将r_ack信号置为有效,并使用r_clk对发送域数据进行采样,可以得到r_data_out;
4.发送域采用两级电平同步器同步r_ack信号,并把同步后的信号命名为t_ack_sync2;
5.发送域检测到t_ack_sync2信号有效时,接收域将t_req信号置为无效;
6.接收域在经过两个r_clk周期后检测到r_req_sync2信号无效;
7.在下一个r_clk周期中,接收域将r_ack置为无效
8.发送域在通过两级电平同步器后检测到r_ack信号无效时,完成一次完全握手,发送域可以输入新的数据。
这里,全部握手在发送域中最多需要5个周期,在接收域中最多需要6个周期。
全握手协议在异步数据跨时钟的安全性方面很强,因为通过检测请求与响应信号,每一侧电路都清楚地知道对端的状态。但是这种方式也有其不足,那就是完成所有交互的整个过程要花费很多个时钟周期,数据信号跨时钟的延时比较大。
1.2.3 代码题
根据上述时序图描述进行写代码,逻辑很清晰;
根据上述时序图进行写个握手协议;
//==========================================================
//--Author : colonel
//--Date : 11-01
//--Module : sync_multi_bits_fast_2_slow
//--Function: sync multi bits from slow_clk to fast_clk using MUX
//-- Only considering the yaWenTai;
//==========================================================
module sync_multi_bits_fast_2_slow(
//==========================< 端口 >=========================
input wire t_clk,
input wire rst_n,
input wire t_din_en,
input wire [32 -1:0] t_din,
output wire o_t_ready,
input wire r_clk,
output wire r_dout_en,
output wire [32 -1:0] r_dout
);
//==========================< 端口 >=========================
reg t_req;
reg r_req_sync1;
reg r_req_sync2;
reg r_ack;
//=========================================================
//-- t_req, r_req_sync1/2
//=========================================================
always @(posedge t_clk or negedge rst_n) begin
if (!rst_n) begin
t_req <= 1'b0;
end else begin
if(t_din_en) begin
t_req <= 1'b1;
end else if (t_ack_sync2) begin
t_req <= 1'b0;
end else begin
t_req <= t_req;
end
end
end
always @(posedge r_clk or negedge rst_n) begin
if (!rst_n) begin
r_req_sync1 <= 1'b0;
r_req_sync2 <= 1'b0;
end else begin
r_req_sync1 <= t_req;
r_req_sync2 <= r_req_sync1;
end
end
always @(posedge r_clk or negedge rst_n) begin
if (!rst_n) begin
r_ack <= 1'b0;
end else begin
r_ack <= r_req_sync2;
end
end
//==========================< 端口 >=========================
reg r_dout_en_r;
reg r_dout_r;
wire r_req_sync_2_pos = r_req_sync2 & !r_ack;
//=========================================================
//-- r_dout_en_r,r_dout_r
//=========================================================
always @(posedge r_clk or negedge rst_n) begin
if (!rst_n) begin
r_dout_en_r <= 1'b0;
end else begin
if (r_req_sync_2_pos) begin
r_dout_en_r <= 1'b1;
end else begin
r_dout_en_r <= 1'b0;
end
end
end
always @(posedge r_clk or negedge rst_n) begin
if (!rst_n) begin
r_dout_r <= 'b0;
end else begin
if (r_req_sync2) begin
r_dout <= t_din;
end else begin
r_dout <= r_dout;
end
end
end
//=========================================================
//-- r_dout_en,r_dout
//=========================================================
assign r_dout_en= r_dout_en_r;
assign r_dout = r_dout_r;
//==========================< 端口 >=========================
reg t_ack_sync1;
reg t_ack_sync2;
//=========================================================
//-- t_ack_sync1/2
//=========================================================
always @(posedge t_clk or negedge rst_n) begin
if (!rst_n) begin
t_ack_sync1 <= 1'b0;
t_ack_sync2 <= 1'b0;
end else begin
t_ack_sync1 <= r_ack;
t_ack_sync2 <= t_ack_sync1;
end
end
//==========================< 端口 >=========================
reg o_t_ready_r;
wire t_ack_sync2_neg = t_ack_sync2 & ~t_ack_sync1;
//=========================================================
//-- o_t_ready_r
//=========================================================
always @(posedge t_clk or negedge rst_n) begin
if (!rst_n) begin
o_t_ready_r <= 1'b1;
end else begin
if (t_req) begin
o_t_ready_r <= 1'b0;
end else if (t_ack_sync2_neg) begin
o_t_ready_r <= 1'b1;
end else begin
o_t_ready_r <= o_t_ready_r;
end
end
end
assign o_t_ready = o_t_ready_r;
endmodule
2 手撕题:使用握手信号实现跨时钟数据传输问题
题目:分别编写一个数据发送模块和一个数据接收模块,模块的时钟信号分别为clk_a,clk_b。两个时钟的频率不相同。数据发送模块循环发送0-7,在每个数据传输完成之后,间隔5个时钟,发送下一个数据。请在两个模块之间添加必要的握手信号,保证数据传输不丢失。
模块的接口信号如下:
2.1 步骤
信号进一步描述:
- data_req:data_req表示数据请求接受信号。当data_out发出时,该信号拉高,在确认数据被成功接收之前,保持为高,期间data应该保持不变,等待接收端接收数据。
当数据发送端检测到data_ack,表示上一个发送的数据已经被接收。撤销data_req,然后可以改变数据data。等到下次发送时,再一次拉高data_req。 - data_ack:当数据接收端检测到data_req为高,表示该时刻的信号data有效,保存数据,并拉高data_ack.
- 当数据发送端检测到data_ack,表示上一个发送的数据已经被接收。撤销data_req,然后可以改变数据data。等到下次发送时,再一次拉高data_req。
2.2 波形图
2.2 代码
//==========================================================
//--Author : colonel
//--Date : 11-01
//--Module : sync_multi_bits_using_handShake
//--Function: sync multi bits of newcoder
//==========================================================
module data_driver (
//==========================< 端口 >=========================
input clk_a,
input rst_n,
input data_ack,
output [4 -1:0] data,
output data_req
);
//==========================< 信号 >=========================
reg data_ack_sync1;
reg data_ack_sync2;
//=========================================================
//-- data_ack_sync1/2:sync data_ack to clk_a
//=========================================================
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
data_ack_sync1 <= 1'b0;
data_ack_sync2 <= 1'b0;
end else begin
data_ack_sync1 <= data_ack;
data_ack_sync2 <= data_ack_sync1;
end
end
wire data_ack_sync1_pos = data_ack_sync1 && !data_ack_sync2;
//==========================< 信号 >=========================
reg [4 -1:0] data_r;
//=========================================================
//-- data: can change when ack is ok
//=========================================================
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
data_r <= 0;
end else begin
if (data_ack_sync1_pos) begin
data_r <= data_r + 1;
end else begin
data_r <= data_r;
end
end
end
assign data = data_r;
//==========================< 信号 >=========================
reg [3 -1:0] cnt;
//=========================================================
//-- cnt: cnt is 0 when ack is ok,and keep when data_req
//=========================================================
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
cnt <= 0;
end else begin
if (data_ack_sync1_pos) begin
cnt <= 0;
end else if (data_req) begin
cnt <= cnt;
end else begin
cnt <= cnt + 1;
end
end
end
//==========================< 信号 >=========================
reg data_req_r;
//=========================================================
//-- data_req_r: when is 1'b1 and when is 1'b0
//=========================================================
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
data_req_r <= 1'b0;
end else begin
if (data_ack_sync1_pos) begin
data_req_r <= 1'b0;
end else if (cnt==3'd4) begin
data_req_r <= 1'b1;
end else begin
data_req_r <= data_req_r;
end
end
end
assign data_req = data_req_r;
endmodule
module data_receiver (
//==========================< 端口 >=========================
input clk_b,
input rst_n,
input data_req,
input [4 -1:0] data,
output data_ack,
output data_sync
);
//==========================< 端口 >=========================
reg data_req_sync1;
reg data_req_sync2;
//=========================================================
//-- data_req_sync1/2
//=========================================================
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
data_req_sync1 <= 1'b0;
data_req_sync2 <= 1'b0;
end else begin
data_req_sync1 <= data_req;
data_req_sync2 <= data_req_sync1;
end
end
wire data_req_sync1_pos = data_req_sync1 && !data_req_sync2;
//==========================< 端口 >=========================
reg data_ack_r;
//=========================================================
//-- data_ack_r
//=========================================================
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
data_ack_r <= 1'b0;
end else begin
if (data_req_sync1_pos) begin
data_ack_r <= 1'b1;
end else begin
data_ack_r <= 1'b0;
end
end
end
//==========================< 端口 >=========================
reg [4 -1:0] data_sync_r;
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
data_sync_r <= 'b0;
end else begin
if (data_req_sync1_pos) begin
data_sync_r <= data;
end else begin
data_sync_r <= data_sync_r;
end
end
end
assign data_sync = data_sync_r;
endmodule
这是一个非常好的练习 handshake的好练习例子
【Ref】
- https://blog.csdn.net/Loudrs/article/details/131076559
- https://blog.csdn.net/qq_32355037/article/details/124571258?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168612550516800222811622%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=168612550516800222811622&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-124571258-null-null.142%5Ev88%5Ekoosearch_v1,239%5Ev2%5Einsert_chatgpt&utm_term=%E5%A4%9AbitCDC&spm=1018.2226.3001.4187
3.https://www.nowcoder.com/practice/2bf1b28a4e634d1ba447d3495134baac?tab=note