跨时钟域处理

一、什么是跨时钟域处理

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

所谓亚稳态就是:数据无法在规定的时间内达到稳定的状态。

造成亚稳态的原因:电路不满足建立时间和保持时间。

建立时间:在时钟上升沿到来前,数据需要保持稳定的最小时间。

保持时间:时钟上升沿到来后,数据需要保持的稳定的最小时间。

二、单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)

    1. 把从外部来的 data_ext[3:0]空闲期 先锁存到 data

    2. 到了计数门限后拉高 data_req 发起一次"我有有效数据"的请求;

    3. data_req=1 期间保持 data 不变(满足 data--valid 绑定);

    4. 收到对端 data_ack上升沿 后拉低 data_req,本次握手完成,进入下一轮。

  • data_receiver(目的域,clk_b)

    1. 把跨域来的 data_req 打拍同步并做上升沿检测

    2. 在该上升沿瞬间锁存多比特数据总线 data 到本域 data_in

    3. 同时打一拍 data_ack 脉冲回给源域,通知"我收到了这笔数据"。

2.异步fifo

下一次单独出一篇来写异步FIFO

3.格雷码

当跨域的是"单步递增/递减的计数(特别是 FIFO 指针)"时,用 Gray 码;
当跨域的是一帧/多比特"数据本身"时,用握手或异步 FIFO,而不是 Gray。

相关推荐
szxinmai主板定制专家10 小时前
柔宇柔性显示屏+x86、arm显示解决方案,还有库存
arm开发·人工智能·fpga开发
CHY_12811 小时前
JESD204B 协议解析(5)ILA序列
fpga开发·jesd204
yang)14 小时前
PLL之鉴相器PFD
fpga开发
FPGA_无线通信17 小时前
RRU CFR算法
fpga开发
国科安芯19 小时前
AS32系列MCU芯片TIM模块的捕获和比较
单片机·嵌入式硬件·fpga开发·架构·risc-v
kk哥889919 小时前
高性能计算 FPGA 开发:Quartus Prime 18.0 下载安装教程 高带宽内存(HBM2)支持
fpga开发
雨洛lhw19 小时前
vivado FFT IP 学习及仿真笔记
verilog·fft ip 核
FPGA_无线通信20 小时前
RRU 1588同步
fpga开发
燎原星火*2 天前
FMC接口定义
fpga开发
CHY_1282 天前
JESD204B 协议解析(4)Subclass2 时序分析
嵌入式硬件·fpga开发·jesd204