当前位置: 首页 > news >正文

FPGA基础 -- CDC(Clock Domain Crossing)实战教程

CDC(Clock Domain Crossing)实战教程


CDC 教程(工程版)

1. 为什么需要 CDC

  • 不同时钟域的采样沿相对相位不确定,跨域信号在建立/保持窗口内被采到会进入亚稳态(介于 0/1 的中间电平,随机解析到 0 或 1,并可能延迟)。
  • 多比特总线跨域时,各位的到达时间不同步 → 在目标域同一拍被采到不一致的位组合(“撕裂”)。
  • 直接跨域短脉冲:如果脉宽 < 目标域周期,有几率完全错过

2. 典型错误

  1. 把多比特总线“每位接 2 级触发器”跨域。
  2. 用帧信号去门控复位(两个域释放相位不一致)。
  3. count = wr_ptr - rd_ptr 直接相减未同步指针。
  4. 未对 CDC 路径做时序例外(STA 试图为异步路径“收敛”)。

3. 方案选型速查(牢记这张表)

场景推荐方案可靠性吞吐/延迟备注
单比特电平2 级触发器同步很高1–2 拍最常见
单次事件/脉冲Toggle → 2FF → XOR很高*2–3 拍*前提:任意两采样沿间最多 1 次事件;可升级握手
事件绝不丢Req/Ack(toggle 握手)极高往返 2T_dst+有背压 busy
数据流/多比特异步 FIFO(灰码指针)极高高吞吐Lattice EBR FIFO 或自研
事件突发(可多发)灰码事件计数1 拍/周期结算有模数上限
复位异步置位、同步释放必须各域各自同步

4. 原理要点(简明数学)

4.1 2 级同步器与 MTBF

  • 第一拍可能亚稳,第二拍提供解析时间 TsettleT_\text{settle}Tsettle。失效率 ~ e−Tsettle/τe^{-T_\text{settle}/\tau}eTsettle/τ
  • 常用 MTBF 近似:MTBF≈KFclk⋅FdataeTsettle/τ\mathrm{MTBF} \approx \frac{K}{F_\text{clk} \cdot F_\text{data}} e^{T_\text{settle}/\tau}MTBFFclkFdataKeTsettle/τ。布置相邻、走线短 → MTBF 指数提高。

4.2 Toggle 脉冲同步

  • 源域将“一拍脉冲”编码为“翻转并保持的电平”;目域 2FF 同步后做 XOR(s1^s2)得 1 拍脉冲。
  • 硬条件:相邻两次目标采样沿之间最多 1 次事件(工程上建议留到 ≥2 个 T_dst)。

4.3 Req/Ack 握手

  • req_t(toggle)到达 → 目域产生 1 拍 pulse_dst 并翻转 ack_t 回源域;
  • 源域只有 req_t == ack_sync(不 busy)时才允许下一个事件 → 零丢失,代价是吞吐由往返决定。

4.4 异步 FIFO(灰码指针)

  • 写/读指针用 ADDR_BITS+1 位二进制;跨域传 灰码(单比特变化),2FF 同步后转回二进制,在本域环形减法。
  • Empty = (rd_gray == wr_gray_sync)
    Full = (wr_gray_next == {~rd_gray_sync[MSB:MSB-1], rd_gray_sync[LSB..]})

5. 复位策略

  • Reset:异步置位、各域同步释放。不要把复位与帧信号相与。
  • 帧末软清空:停写 → 读端排空至 Empty 稳定 → 下一帧。若必须“指针归零”,用 WPReset+RPReset 对拍复位并用握手协调。

6. 约束(以 Lattice LPF 为例)

# 基本时钟
FREQUENCY PORT "pixclk_o" 72.0 MHz;
FREQUENCY PORT "tx_byte_clk" 125.0 MHz;# 切断异步时钟域之间的时序分析(双向)
CUT PATH FROM CLOCKNET "pixclk_o"   TO CLOCKNET "tx_byte_clk";
CUT PATH FROM CLOCKNET "tx_byte_clk" TO CLOCKNET "pixclk_o";

同时给 2 级同步器寄存器打属性(综合器识别并物理相邻放置)。Synplify 可用:
(* syn_async_reg = "true" *) reg s1, s2;
若用其他工具,使用等效 ASYNC_REG/DONT_TOUCH 属性。

7. 验证建议

  • 随机相位仿真:两个时钟不整数相关;随机插入事件,统计“事件数==接收脉冲数”。

  • SVA

    • toggle:约束“事件间隔 ≥ N 个 dst 周期”;
    • 握手:req 触发到 ack 返回之间禁止第二次 req
    • FIFO:禁止 rd_en && empty / wr_en && full

模块模板(可直接用)

7.1 单比特电平同步(2FF)

module cdc_sync_bit(input clk_dst, input rstn_dst,input in_async, output reg out_sync
);(* syn_async_reg = "true" *) reg s1;always @(posedge clk_dst or negedge rstn_dst)if(!rstn_dst) begin s1<=0; out_sync<=0; endelse begin s1<=in_async; out_sync<=s1; end
endmodule

7.2 事件(脉冲)同步(Toggle)

module cdc_pulse_sync(input clk_src, input rstn_src, input pulse_src,input clk_dst, input rstn_dst, output wire pulse_dst
);reg t_src;always @(posedge clk_src or negedge rstn_src)if(!rstn_src) t_src<=0; else if(pulse_src) t_src<=~t_src;(* syn_async_reg = "true" *) reg s1, s2;always @(posedge clk_dst or negedge rstn_dst)if(!rstn_dst) begin s1<=0; s2<=0; end else begin s1<=t_src; s2<=s1; endassign pulse_dst = s1 ^ s2; // 严格一拍
endmodule

7.3 事件握手(Req/Ack)

module cdc_event_req_ack(input clk_src, input rstn_src, input event_src, output wire busy_src,input clk_dst, input rstn_dst, output wire pulse_dst
);reg req_t; wire ack_sync_s; assign busy_src = (req_t != ack_sync_s);always @(posedge clk_src or negedge rstn_src)if(!rstn_src) req_t<=0; else if(event_src && !busy_src) req_t<=~req_t;(* syn_async_reg = "true" *) reg r1,r2; reg ack_t;assign pulse_dst = r1 ^ r2;always @(posedge clk_dst or negedge rstn_dst)if(!rstn_dst) begin r1<=0; r2<=0; ack_t<=0; endelse begin r1<=req_t; r2<=r1; if(pulse_dst) ack_t<=~ack_t; end(* syn_async_reg = "true" *) reg a1,a2; assign ack_sync_s = a2;always @(posedge clk_src or negedge rstn_src)if(!rstn_src) begin a1<=0; a2<=0; end else begin a1<=ack_t; a2<=a1; end
endmodule

7.4 异步 FIFO(80b 核心片段,灰码指针)

你前面已经用过完整版本;这里强调关键点。

// 指针宽度 ADDR_BITS+1;Empty/Full 用灰码公式;计数在本地域算
// 写域:wr_bin_n = wr_bin + (wr_en && !full);
// 读域:rd_bin_n = rd_bin + (rd_en && !empty);
// 跨域:wr_gray -> rd 同步;rd_gray -> wr 同步;再 gray2bin 环形减法

使用案例 1:像素域“行开始/行结束”事件 → 发送域 FSM

场景pixclk_o 下由 lv_o 边沿产生行开始/结束事件,需要在 tx_byte_clk 域驱动 CSI 发包状态机的开始/收尾。

代码(事件检测 + toggle 同步 + FSM 触发)

// 源域:行边沿检测
reg lv_d1;
always @(posedge pixclk_o or negedge rstn) beginif(!rstn) lv_d1<=0; else lv_d1<=lv_o;
end
wire line_start_pulse_pix =  lv_o & ~lv_d1;
wire line_end_pulse_pix   = ~lv_o &  lv_d1;// 跨域(两条事件线)
wire line_start_tx, line_end_tx;
cdc_pulse_sync u_ls(.clk_src(pixclk_o),.rstn_src(rstn),.pulse_src(line_start_pulse_pix),.clk_dst(tx_byte_clk),.rstn_dst(rstn),.pulse_dst(line_start_tx));
cdc_pulse_sync u_le(.clk_src(pixclk_o),.rstn_src(rstn),.pulse_src(line_end_pulse_pix),.clk_dst(tx_byte_clk),.rstn_dst(rstn),.pulse_dst(line_end_tx));// 目标域:发包 FSM
typedef enum logic[2:0]{IDLE, SOF, PAYLOAD, EOL} txs_t;
txs_t st;
always @(posedge tx_byte_clk or negedge rstn) beginif(!rstn) st<=IDLE;else case(st)IDLE:     if(frame_start_tx) st<=SOF;SOF:      if(sof_done)       st<=PAYLOAD;PAYLOAD:  if(line_end_tx)    st<=EOL;EOL:      if(eol_done)       st<=PAYLOAD; // 下一行或回 IDLE 视帧逻辑endcase
end

约束与验证

  • LPF:对 pixclk_o ↔ tx_byte_clk 双向 CUT PATH
  • 事件间隔(行周期)远大于 2*T_tx,天然满足 toggle 条件。
  • SVA:断言“每次 line_start_pulse_pix 导致 line_start_tx 1 拍脉冲”;仿真用不同频比验证。

使用案例 2:APB(或 CPU)配置写 → 发送域寄存器(绝不丢)

场景:APB 在 pclk 域写配置(如 DT/VC/行长),必须可靠送到 tx_byte_clk 域,不丢、不重入。

代码(req/ack 总线握手)

// 源域:APB 写命中时发起事件(带 busy 反压)
wire wr_hit = psel & penable & pwrite & (paddr==CFG_DT);
wire busy_cfg;
wire cfg_strobe_tx;
wire [15:0] cfg_data_tx;cdc_event_req_ack u_cfg (.clk_src(pclk), .rstn_src(rstn), .event_src(wr_hit), .busy_src(busy_cfg),.clk_dst(tx_byte_clk), .rstn_dst(rstn), .pulse_dst(cfg_strobe_tx)
);// 数据保持:源域锁存写数据(仅在 !busy 时更新,避免覆盖)
reg [15:0] cfg_latch;
always @(posedge pclk or negedge rstn)if(!rstn) cfg_latch<=16'h012B;else if (wr_hit && !busy_cfg) cfg_latch<=pwdata[15:0];// 目的域:在 strobe 时采入数据
reg [15:0] dt;
always @(posedge tx_byte_clk or negedge rstn)if(!rstn) dt<=16'h012B;else if(cfg_strobe_tx) dt<=cfg_latch;  // 跨域静态线,“拉住”有效

说明:这里把数据放在源域保持寄存器上,目标域在 cfg_strobe_tx 到达时采样即可;由于握手保证“在 ack 前不会发下一次”,因此采样稳定且无丢失。

约束与验证

  • LPF:同样 CUT PATH
  • SVA:wr_hit → 目标域在有限拍内 cfg_strobe_tx,且 busy_cfg 在 ack 前恒 1;禁止在 busy_cfg 期间再次 wr_hit

附:当“目标域更慢且事件可能很密”怎么办?

  • 优先把案例 1 的 cdc_pulse_sync 升级为握手(案例 2 的写法),保证零丢失;
  • 或用灰码事件计数减少握手开销(允许一个 dst 周期内累计多个事件);
  • 对“数据面”,始终用异步 FIFO
http://www.dtcms.com/a/393376.html

相关文章:

  • 低碳经济:碳汇——从生态固碳到金融资产的价值转化
  • QGC 通信模块架构梳理
  • Application接口拓展功能(三)
  • 【Python】错误和异常
  • 【状态机实现】初识——基于状态机实现的流程编排和Activiti、Camunda、Flowable等工作流的区别
  • SpringBoot自动配置核心原理
  • Python 中的 Builder 模式实践 —— 以 UserProfileBuilder 为例
  • 探秘陌讯AIGC检测算法优化:详解MPS加速与模型热重载的实现原理
  • 1.3 管道(Pipe)核心知识点总结
  • GLUE:自然语言理解评估的黄金基准
  • 第13章 智能监测-设备数据处理
  • GEO技术科普
  • B004基于三菱FX2NPLC智能自提柜控制系统仿真
  • MTK CPU温度调节一知半解
  • V90伺服驱动器“速度模式“双极性模拟量速度控制
  • 课前练习题-20250919
  • C++类与对象
  • 企业级Docker镜像仓库Harbor
  • ESD防护设计宝典(七):生命线的秩序——关键信号线布线规则
  • 【ROS2】Beginner : CLI tools - 理解 ROS 2 话题
  • RL知识回顾
  • Java多线程编程指南
  • 【论文速读】基于地面激光扫描(TLS)和迭 代最近点(ICP)算法的土坝监测变形分析
  • GAMES101:现代计算机图形学入门(Chapter2 向量与线性代数)迅猛式学线性代数学习笔记
  • 汉语构词智慧:从历史优势到现实考量——兼论“汉语全面改造英语”的可能性
  • 仿tcmalloc高并发内存池
  • 墨者学院-通关攻略(持续更新持续改进)
  • 10厘米钢板矫平机:把“波浪”压成“镜面”的科学
  • ESP32- 项目应用1 智能手表之网络配置 #6
  • TCP/IP 互联网的真相:空间域和时间域的统计学