4. 单比特CDC同步器设计与验证
在Zynq PL中,跨时钟域信号按位宽可分为单比特与多比特两类。单比特CDC是工程中最常见、也最容易被低估的一类问题:一个来自慢时钟域的控制信号进入快时钟域,若直接采样,可能因建立/保持时间违例导致亚稳态;若信号宽度不足,还可能被快时钟完全漏采。本节围绕三种最典型的单比特同步器——两级触发器同步器、脉冲同步器、电平同步器——讨论其适用条件、RTL实现与仿真验证方法。
一、两级触发器同步器:最基础的亚稳态抑制结构
两级触发器同步器用于把单比特电平信号从源时钟域同步到目的时钟域。其核心思想是:第一级触发器允许出现亚稳态,但通过一个目的时钟周期的“恢复窗口”,让第二级触发器在采样时大概率已获得稳定值。两级触发器必须使用目的时钟dst_clk驱动,且两级之间不能插入组合逻辑。
module cdc_sync2 (
input wire dst_clk,
input wire rst_n,
input wire src_level,
output wire dst_level
);
reg sync_meta, sync_stable;
always @(posedge dst_clk or negedge rst_n) begin
if (!rst_n) begin
sync_meta <= 1'b0;
sync_stable <= 1'b0;
end else begin
sync_meta <= src_level;
sync_stable <= sync_meta;
end
end
assign dst_level = sync_stable;
endmodule
该结构有两个关键约束。第一,源信号必须保持足够长时间,通常要求大于目的时钟周期加上同步器延迟,即至少两个dst_clk周期,否则可能漏采。第二,两级触发器之间不能有组合逻辑,否则会破坏亚稳态恢复窗口。在Zynq PL中,建议将两级触发器放入同一SLICE或相邻SLICE,并在XDC中对该路径设置set_max_delay或set_false_path,避免时序工具对跨时钟路径做无意义的建立/保持分析。
二、脉冲同步器:把窄脉冲搬到另一个时钟域
当源信号是单周期脉冲时,两级触发器同步器无法直接使用,因为脉冲宽度可能小于目的时钟周期,导致漏采。脉冲同步器的做法是:在源时钟域用翻转电路把脉冲转换为电平翻转,再在目的时钟域用两级触发器同步该翻转信号,最后通过边沿检测还原为单周期脉冲。
module cdc_pulse_sync (
input wire src_clk,
input wire dst_clk,
input wire rst_n,
input wire src_pulse,
output wire dst_pulse
);
reg src_toggle;
always @(posedge src_clk or negedge rst_n) begin
if (!rst_n) src_toggle <= 1'b0;
else if (src_pulse) src_toggle <= ~src_toggle;
end
reg meta, sync_r, sync_r_d;
always @(posedge dst_clk or negedge rst_n) begin
if (!rst_n) begin
meta <= 1'b0; sync_r <= 1'b0; sync_r_d <= 1'b0;
end else begin
meta <= src_toggle;
sync_r <= meta;
sync_r_d <= sync_r;
end
end
assign dst_pulse = sync_r ^ sync_r_d;
endmodule
该结构要求两个相邻源脉冲之间的间隔大于目的时钟域同步延迟,否则翻转信号来不及被目的域完整捕获。在Zynq PL中,若源脉冲来自PS端GPIO或PL内部计数器,需确认脉冲间隔满足src_pulse_interval > 2 * dst_clk_period + 2 * src_clk_period。仿真时可用src_clk = 50 MHz、dst_clk = 100 MHz,源脉冲间隔设为200 ns,观察dst_pulse是否在每个源脉冲后产生一个目的时钟周期宽度的脉冲。
三、电平同步器:慢变控制信号的可靠传递
电平同步器本质上就是两级触发器同步器,但强调源信号是“慢变电平”,例如使能信号、模式选择、复位释放等。其关键区别在于:源信号必须保持稳定至少两个目的时钟周期,且不能是窄脉冲。若源信号变化频率接近目的时钟频率,则不适合用电平同步器,而应改用握手或异步FIFO。
在Zynq PL中实现电平同步器时,建议在源端加一级寄存器输出,确保信号从源触发器直接进入同步器,避免组合毛刺。目的端两级触发器后可直接使用,但若该电平用于控制逻辑,建议再打一拍形成dst_level_d,用于边沿检测或状态机同步。
四、仿真验证:如何判断同步器是否工作正常
仿真验证的目标不是“看波形有没有跳变”,而是确认三件事:目的域是否只产生一个脉冲、是否没有漏采、是否没有亚稳态传播。以下给出一个可复现的测试平台结构,参数为示例假设值。
`timescale 1ns/1ps
module tb_cdc_sync;
reg src_clk = 0, dst_clk = 0, rst_n = 0;
reg src_pulse = 0;
wire dst_pulse;
always #10 src_clk = ~src_clk; // 50 MHz
always #5 dst_clk = ~dst_clk; // 100 MHz
cdc_pulse_sync u_pulse (
.src_clk(src_clk), .dst_clk(dst_clk), .rst_n(rst_n),
.src_pulse(src_pulse), .dst_pulse(dst_pulse)
);
initial begin
rst_n = 0;
#100 rst_n = 1;
#50;
src_pulse = 1; #20; src_pulse = 0; // 20ns 窄脉冲
#200;
src_pulse = 1; #20; src_pulse = 0;
#300;
$finish;
end
endmodule
在仿真波形中,应重点观察dst_pulse是否在每个src_pulse之后出现一次,且宽度为一个dst_clk周期。若dst_pulse出现两次或宽度异常,通常说明边沿检测逻辑有误或源脉冲间隔不足。若dst_pulse完全消失,则可能是源脉冲太窄,翻转信号未被目的域捕获。
对于电平同步器,验证重点是:源电平变化后,目的电平应在2~3个dst_clk周期内稳定跟随,且不出现毛刺。可在测试平台中让src_level在src_clk域随机变化,但每次变化后保持至少100 ns,然后检查dst_level是否在预期延迟后与src_level一致。
sync_meta和sync_stable加入波形窗口,观察第一级是否出现“X”态。若第一级出现X态但第二级稳定,说明同步器正在正常工作;若第二级也出现X态,则说明恢复窗口不足或时钟约束有问题。
五、三种同步器的选择依据
| 同步器类型 | 适用信号 | 关键约束 | 典型应用 |
|---|---|---|---|
| 两级触发器同步器 | 单比特电平 | 源信号保持 > 2 个 dst_clk 周期 | 使能、模式选择、复位释放 |
| 脉冲同步器 | 单比特脉冲 | 脉冲间隔 > 2*dst_clk + 2*src_clk | 事件触发、中断请求、计数器进位 |
| 电平同步器 | 慢变电平 | 变化频率远低于 dst_clk | 状态标志、配置位、握手请求 |
选择时先判断源信号是脉冲还是电平:脉冲优先用脉冲同步器,电平优先用两级触发器或电平同步器。若信号变化频繁且无法保证保持时间,则应改用异步FIFO或握手协议,而不是强行使用单比特同步器。在Zynq PL中,所有跨时钟域路径都应在XDC中显式约束,避免工具误分析导致实现结果与仿真不一致。
实际工程中,建议把三种同步器封装为独立模块,并在模块端口上明确标注src_clk和dst_clk,避免误接。仿真时至少覆盖三种场景:正常间隔脉冲、最小间隔脉冲、源信号在目的时钟边沿附近变化。通过对比dst_pulse和dst_level的波形,可以快速判断同步器是否满足设计意图。