4. 控制信号同步:单比特信号同步、边沿检测同步器、脉冲同步器

时钟域处理里,控制信号的同步是最基础、也最容易出问题的地方。我这些年做DFT,见过太多因为控制信号没处理好导致的芯片功能失效。说白了,跨时钟域传递控制信号,核心就三个字:防亚稳态

你想想看,一个信号从时钟域A跳到时钟域B,如果刚好在时钟B的采样窗口边缘变化,那采样结果可能既不是0也不是1,而是介于两者之间的电压值。这个不确定状态如果传播下去,整个逻辑就乱了。嗯,这就是亚稳态。

那怎么解决?三种经典方案:单比特同步器、边沿检测同步器、脉冲同步器。咱们一个一个说。

4.1 单比特信号同步器

这是最基础的同步结构。说白了就是两级触发器串联。第一级采样可能采到亚稳态,但第二级采样时,第一级的输出已经稳定了。这样亚稳态就被「憋」在了第一级,不会传到后面的逻辑。

我个人习惯,代码里这样写:

module sync_2ff (
    input  wire clk_b,
    input  wire rst_n,
    input  wire data_in,   // 来自时钟域A
    output wire data_out   // 同步到时钟域B
);

reg sync_reg1, sync_reg2;

always @(posedge clk_b or negedge rst_n) begin
    if (!rst_n) begin
        sync_reg1 <= 1'b0;
        sync_reg2 <= 1'b0;
    end else begin
        sync_reg1 <= data_in;
        sync_reg2 <= sync_reg1;
    end
end

assign data_out = sync_reg2;

endmodule

这里有个细节要注意:第一级触发器的时序约束。我在项目中遇到过,后端工具默认会对所有路径做时序检查。但跨时钟域路径,你根本没法保证满足建立时间。所以必须用 set_false_pathset_clock_groups 把这条路径设为异步路径。否则工具会报一堆时序违例,你看着头疼,它改着也头疼。

警告:两级同步器只能降低亚稳态概率,不能完全消除。对于超高速设计(比如5GHz以上),可能需要三级甚至四级同步器。不过DFT测试模式下,频率通常不高,两级足够了。

4.2 边沿检测同步器

有时候我们不只要同步电平,还要检测信号的变化。比如某个控制信号从0变1,表示开始测试。这时候就需要边沿检测同步器。

结构很简单:在两级同步器后面再加一级触发器,然后用组合逻辑比较最后两级的值。

module edge_detect_sync (
    input  wire clk_b,
    input  wire rst_n,
    input  wire data_in,
    output wire rising_edge,
    output wire falling_edge
);

reg sync_reg1, sync_reg2, sync_reg3;

always @(posedge clk_b or negedge rst_n) begin
    if (!rst_n) begin
        {sync_reg3, sync_reg2, sync_reg1} <= 3'b0;
    end else begin
        sync_reg1 <= data_in;
        sync_reg2 <= sync_reg1;
        sync_reg3 <= sync_reg2;
    end
end

assign rising_edge  = sync_reg2 & ~sync_reg3;
assign falling_edge = ~sync_reg2 & sync_reg3;

endmodule

你看,rising_edge 在检测到上升沿时只拉高一个时钟周期。这个脉冲宽度正好是时钟域B的一个周期。我曾经在ATE测试时遇到一个问题:ATE给的测试使能信号宽度只有5ns,但我的同步器时钟周期是10ns。结果边沿检测根本没抓到。后来我加了一个展宽电路才解决。

提示:边沿检测同步器要求输入信号的宽度至少大于同步时钟周期的1.5倍。否则可能漏检。如果源时钟域比目标时钟域快,这个条件很容易满足。反过来就要小心了。

4.3 脉冲同步器

脉冲同步器是专门处理「窄脉冲」跨时钟域问题的。什么叫窄脉冲?就是宽度小于目标时钟周期的脉冲。比如源时钟域100MHz,目标时钟域10MHz,源时钟域的一个脉冲宽度只有10ns,目标时钟域采样周期是100ns。你直接用两级同步器,大概率采不到。

脉冲同步器的思路是:把脉冲转换成电平变化,同步后再还原成脉冲。具体做法是用一个Toggle触发器,每次来脉冲就翻转一次。这样电平变化至少保持一个源时钟周期,目标时钟域肯定能采到。

module pulse_sync (
    input  wire clk_a,
    input  wire clk_b,
    input  wire rst_n,
    input  wire pulse_in,   // 时钟域A的脉冲
    output wire pulse_out   // 同步到时钟域B的脉冲
);

// 时钟域A:脉冲转电平
reg toggle_a;
always @(posedge clk_a or negedge rst_n) begin
    if (!rst_n)
        toggle_a <= 1'b0;
    else if (pulse_in)
        toggle_a <= ~toggle_a;
end

// 两级同步器同步电平
reg sync1, sync2;
always @(posedge clk_b or negedge rst_n) begin
    if (!rst_n) begin
        sync1 <= 1'b0;
        sync2 <= 1'b0;
    end else begin
        sync1 <= toggle_a;
        sync2 <= sync1;
    end
end

// 时钟域B:电平还原成脉冲
reg sync2_delay;
always @(posedge clk_b or negedge rst_n) begin
    if (!rst_n)
        sync2_delay <= 1'b0;
    else
        sync2_delay <= sync2;
end

assign pulse_out = sync2 ^ sync2_delay;

endmodule

这个结构有个限制:两个脉冲之间的间隔必须大于同步器的延迟。否则第二个脉冲来的时候,第一个脉冲的电平还没同步完,Toggle会连续翻转两次,导致目标时钟域只看到一个脉冲。我记得在某个DFT项目中,测试向量里连续给了两个紧挨着的扫描使能脉冲,结果同步器漏掉了第二个。查了三天才找到原因。

核心要点:
  • 单比特同步器:适用于慢变信号,两级防亚稳态
  • 边沿检测同步器:需要检测信号跳变时使用,注意脉冲宽度要求
  • 脉冲同步器:窄脉冲跨时钟域专用,注意最小脉冲间隔

4.4 三种同步器的选择指南

实际项目中怎么选?我一般这样判断:

信号类型 推荐方案 典型应用场景
电平信号(如复位、模式选择) 单比特同步器 DFT测试模式切换、复位同步
边沿触发信号(如开始、结束标志) 边沿检测同步器 测试开始信号、状态机跳转条件
窄脉冲信号(如单周期脉冲) 脉冲同步器 扫描使能脉冲、测试时钟门控信号

嗯,这里要特别提醒:千万不要把脉冲同步器用在电平信号上。我曾经见过有人把测试模式选择信号用脉冲同步器处理,结果每次模式切换都产生一个脉冲,而不是保持电平。那后果就是测试模式一直在跳,根本没法正常工作。

另外,如果你用的是Synopsys的DesignWare或Cadence的Inferface IP,它们都有现成的同步器单元。我建议直接例化这些库单元,比自己手写的RTL更可靠。毕竟人家是经过硅验证的,亚稳态MTBF(平均故障间隔时间)都算得清清楚楚。

好了,控制信号同步就讲到这里。下一章咱们聊聊数据总线的同步,那个更复杂,涉及到握手协议和FIFO。到时候再细说。