一、什么是跨时钟域处理
跨时钟域处理的目的就是在数据从一个时钟域传到另一个时钟域的过程中,避免出现亚稳态,同时又使电路在传输数据的过程中避免丢输掉数据。

所谓亚稳态就是:数据无法在规定的时间内达到稳定的状态。
造成亚稳态的原因:电路不满足建立时间和保持时间。
建立时间:在时钟上升沿到来前,数据需要保持稳定的最小时间。
保持时间:时钟上升沿到来后,数据需要保持的稳定的最小时间。
二、单bit传输
单bit传输又分为:从慢时钟到快时钟和从快时钟到慢时钟。
1.从慢到快
数据从慢时钟域传到快时钟域,常常采用两级触发器同步,再用边沿检测电路得到脉冲信号。

//单比特脉冲信号:慢到快
module single_cdc(
input clk1,
input clk2,
input rst_n,
input signal_in,
output reg signal_out
);
reg signal_out_r; //打一拍
reg signal_out_rr; //打两拍
reg signal_out_rrr; //边沿检测电路
always @(posedge clk2 or negedge rst_n) begin
if(!rst_n) begin
signal_out_r <= 1'b0;
signal_out_rr <= 1'b0;
end
else if(signal_in == 1'b1) begin
signal_out_r <= signal_in;
signal_out_rr <= signal_out_r;
signal_out_rrr <= signal_out_rr;
end
else begin
signal_out_r <= 1'b0;
signal_out_rr <= 1'b0;
signal_out_rrr <= 1'b0;
end
end
//组合逻辑(与逻辑)输出脉冲
assign signal_out = signal_out_rr && !signal_out_rrr;
endmodule
这里为什么打两拍是因为每过一级DFF亚稳态的概率就会大大降低,打一拍不太稳,打两拍就够用了,如果打三排可能会对整个电路的性能造成影响还会增加面积。
2.从快到慢
从慢到快用两级触发器即可满足条件,但是如果是从快到慢,就不能适用了,因为快时钟到慢时钟可能会丢失数据。因此采用脉冲展宽的方式:将快时钟域下的信号多稳定一段时间,等慢时钟域采到了再拉低。

module cdc_f2s(
input clk_fast,
input clk_slow,
input rst_n,
input signal_in,
output signal_out
);
//快时钟域脉冲展宽
reg signal_a;
always @(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
signal_a <= 1'b0;
else if(signal_in == 1'b1)//拉高
signal_a <= signal_in;
else if(signal_a_rr == 1'b1)//拉低
signal_a <= 1'b0;
//其它情况,保持上一时刻的值,这里可以省略
end
//慢时钟域采集脉冲展宽信号
//信号同步不用加判断条件
reg signal_b; //打一拍
reg signal_b_r; //打两拍
always @(posedge clk_slow or negedge rst_n) begin
if(!rst_n) begin
signal_b <= 1'b0;
signal_b_r <= 1'b0;
end
else begin
signal_b <= signal_a;
signal_b_r <= signal_b;
end
end
//快时钟域采集慢时钟域返回信息:signal_b_r
reg signal_a_r; //打一拍
reg signal_a_rr; //打两拍
always @(posedge clk_fast or negedge rst_n) begin
if(!rst_n)
{signal_a_rr,signal_a_r} <= {2{1'b0}};
else
{signal_a_rr,signal_a_r} <= {signal_a_r,signal_b_r};//左边给左边,右边给右边
end
//慢时钟域边沿检测,得到脉冲信号
//与逻辑检测上升沿:signal_b_r为1,且signal_b_rr为0
reg signal_b_rr; //上升沿检测,将signal_b_r打一拍
always @(posedge clk_slow or negedge rst_n) begin
if(!rst_n)
signal_b_rr <= 1'b0;
else
signal_b_rr <= signal_b_r;
end
assign signal_out = signal_b_r && (!signal_b_rr);
endmodule
三、多bit跨时钟域处理
1.握手

发送方
①处理收到的响应信号data_ack,进行打2拍处理;
②所发送数据data在收到响应后重新生成,即+1;
③发送请求信号data_req,会用到计数器来记时data_req的时间间隔。
接收方
①处理收到的请求信号data_req,进行打2拍处理;
②收到了请求后,两件事同时进行:a.发送响应, b.数据接收。
`timescale 1ns/1ns
// ===============================
// 源域:data_driver (clk_a 域)
// - 从外部 data_ext 取数
// - 在空闲时先锁存要发送的数据(data)
// - 到计数门限后拉高 data_req 发起请求
// - 等待对端 ack 上升沿后:拉低 data_req,准备下一笔
// - 在请求期间(data_req=1)保持 data 不变(data-valid 绑定)
// ===============================
module data_driver(
input clk_a,
input rst_n,
input data_ack, // 来自接收端(clk_b 域)的应答
input [3:0] data_ext, // ✅ 外部输入数据
output reg [3:0] data, // 跨域要发送的数据(保持期内稳定)
output reg data_req // 请求/有效(valid) 信号
);
// 打拍同步 + 沿检测(把 ack 同步到 clk_a)
reg data_ack_reg0, data_ack_reg1, data_ack_reg2;
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
data_ack_reg0 <= 1'b0;
data_ack_reg1 <= 1'b0;
data_ack_reg2 <= 1'b0;
end else begin
data_ack_reg0 <= data_ack;
data_ack_reg1 <= data_ack_reg0;
data_ack_reg2 <= data_ack_reg1; // 三拍方便做上升沿检测
end
end
wire ack_edge = data_ack_reg1 & ~data_ack_reg2; // ack 上升沿(已同步且稳定)
// 发送节拍控制:简单计数器到门限后发起一次请求
reg [3:0] cnt;
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
cnt <= 4'd0;
end else if (ack_edge) begin
cnt <= 4'd0; // 完成一次握手,计数清零
end else if (data_req) begin
cnt <= cnt; // 请求期间计数暂停
end else begin
cnt <= cnt + 1'b1; // 空闲时累积
end
end
// data_req 产生/清除:到门限拉高,请求被对端确认后清零
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
data_req <= 1'b0;
end else if (cnt == 4'd4) begin
data_req <= 1'b1; // 发起一次请求(valid=1)
end else if (ack_edge) begin
data_req <= 1'b0; // 收到 ack,结束本次传输
end else begin
data_req <= data_req;
end
end
// ✅ 数据锁存策略(data--valid 绑定):
// - 空闲期且尚未请求:先把 data_ext 锁到 data(准备要发的值)
// - 请求期间保持 data 不变,直到收到 ack 才可能更新下一笔
always @(posedge clk_a or negedge rst_n) begin
if (!rst_n) begin
data <= 4'd0;
end else if (!data_req && !ack_edge) begin
data <= data_ext; // 空闲时提前装载外部数据
end else begin
data <= data; // 请求窗口保持不变
end
end
endmodule
// ===============================
// 目的域:data_receiver (clk_b 域)
// - 把跨域的 data_req 打拍同步,检测其上升沿
// - 在上升沿:锁存 data 到本域 data_in,并产生 1 拍 data_ack 脉冲
// - data_ack 脉冲返回源域用于结束本次握手
// ===============================
module data_receiver(
input clk_b,
input rst_n,
input [3:0] data, // ✅ 多比特数据总线(来自 clk_a 域)
input data_req, // 请求/有效(valid)(来自 clk_a 域)
output reg data_ack, // 单拍应答脉冲(回到 clk_a 域)
output reg [3:0] data_in // 本域锁存到的数据
);
// 打拍同步 + 沿检测(把 req 同步到 clk_b)
reg data_req_reg0, data_req_reg1, data_req_reg2;
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
data_req_reg0 <= 1'b0;
data_req_reg1 <= 1'b0;
data_req_reg2 <= 1'b0;
end else begin
data_req_reg0 <= data_req;
data_req_reg1 <= data_req_reg0;
data_req_reg2 <= data_req_reg1;
end
end
wire req_edge = data_req_reg1 & ~data_req_reg2; // req 上升沿(已同步)
// 产生应答:上升沿打一拍 ack 脉冲
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
data_ack <= 1'b0;
end else if (req_edge) begin
data_ack <= 1'b1; // 单拍脉冲
end else begin
data_ack <= 1'b0;
end
end
// 锁存数据:在 req 上升沿采样多比特总线 data
always @(posedge clk_b or negedge rst_n) begin
if (!rst_n) begin
data_in <= 4'd0;
end else if (req_edge) begin
data_in <= data; // 此刻 data 在对端被保持稳定
end else begin
data_in <= data_in;
end
end
endmodule
-
data_driver(源域,clk_a)-
把从外部来的
data_ext[3:0]在空闲期 先锁存到data; -
到了计数门限后拉高
data_req发起一次"我有有效数据"的请求; -
在
data_req=1期间保持data不变(满足 data--valid 绑定); -
收到对端
data_ack的上升沿 后拉低data_req,本次握手完成,进入下一轮。
-
-
data_receiver(目的域,clk_b)-
把跨域来的
data_req打拍同步并做上升沿检测; -
在该上升沿瞬间锁存多比特数据总线
data到本域data_in; -
同时打一拍
data_ack脉冲回给源域,通知"我收到了这笔数据"。
-
2.异步fifo
下一次单独出一篇来写异步FIFO
3.格雷码
当跨域的是"单步递增/递减的计数(特别是 FIFO 指针)"时,用 Gray 码;
当跨域的是一帧/多比特"数据本身"时,用握手或异步 FIFO,而不是 Gray。