“不同时区”的数据快递指南 ⭐

2.9 时钟域跨越基础

时钟域跨越:为啥要“跨时钟”? 🧠

关键字

:同步设计、时钟域、亚稳态 内容详解: 咱们先类比个场景:你在“北京时区”(时钟1)发消息,朋友在“纽约时区”(时钟2)收消息——俩时区时间不同步,消息很容易“传错”。 对应到FPGA里: ✋

同步设计

是“同一个时区”办事,所有电路用同一个时钟,稳得很; ☁️ 但实际设计里,总有“不同时区”的模块(比如FPGA外接的传感器用自己的时钟),这时候数据从一个时钟域传到另一个,就叫

时钟域跨越(CDC)

。 这里有个大雷:

亚稳态

——数据传到新时钟域时,可能卡在“既不是0也不是1”的中间状态,导致电路发疯(比如作者当年因为这bug熬夜脱发😢)。

2.9.1 单个电平信号的时钟域跨越

单个电平信号:双触发器“消雷器” 🔧

关键字

:双触发器同步、亚稳态消除 内容详解: 单个电平信号(比如“开关开/关”)跨时钟域,最常用的是“双触发器串联”——像给数据“过两道安检”: 1. 第一级触发器:数据刚跨过来,可能触发亚稳态,但触发器从亚稳态恢复的概率会随时间变高; 2. 第二级触发器:等第一级稳定后,再采样,亚稳态概率基本为0。 类比:快递先放小区驿站(第一级),等你确认安全了再拿回家(第二级),避免直接上门踩坑~ 电路示意图:起点时钟域→触发器1→触发器2→终点时钟域。

2.9.2 单个脉冲信号的时钟域跨越

单个脉冲信号:翻转同步器“变魔术” 🎩

关键字

:翻转同步器、脉冲转电平、边沿检测 内容详解: 脉冲信号(比如“按一下按钮”)跨时钟域,直接用双触发器会丢脉冲(因为脉冲持续时间短),得用“翻转同步器”三步走: 1.

脉冲转电平

:把“短脉冲”变成“电平翻转”(比如来一个脉冲,电平从0变1,再来一个变0)——用SystemVerilog实现:

// 脉冲转电平(Pulse to Toggle)
always_ff @(posedge clk, negedge reset_n) begin
  if (!reset_n) begin
    toggle_out <= 0; // 复位时电平为0
  end else begin
    if (data_in) begin // 收到脉冲
      toggle_out <= ~toggle_out; // 电平翻转
    end
  end
end

双触发器同步

:把翻转后的电平信号用双触发器传到终点时钟域; 3.

电平变脉冲

:检测电平的“变化沿”,重新生成脉冲——用SystemVerilog实现(对应课本代码2-3):

// 电平变脉冲(边沿检测)
logic data_in_d1; // 打一拍的信号
wire pulse_out;

always_ff @(posedge clk, negedge reset_n) begin
  if (!reset_n) begin
    data_in_d1 <= 0;
  end else begin
    data_in_d1 <= data_in; // 把当前电平存到寄存器
  end
end
assign pulse_out = data_in ^ data_in_d1; // 异或:电平变化时输出脉冲

类比:把“闪一下的灯光”(脉冲)变成“灯常亮/常灭”(电平),传到对面再变回“闪一下”,保证对面能接住~

2.9.3 多比特总线的时钟域跨越

多比特总线:格雷码+异步FIFO“快递箱” 📦

关键字

:格雷码、异步FIFO、双端口RAM 内容详解: 多比特总线(比如“8位数据”)跨时钟域,直接用多个双触发器会“数据错位”(每个比特亚稳态恢复时间不一样),得用这俩办法: 1.

格雷码编码

:如果传的是计数器,把二进制转成

格雷码

(相邻数只有1个比特不同)——比如3位格雷码,000→001→011→010… 这样即使错位,也只会错1个比特,后续能纠正。 2.

异步FIFO

:通用大招!像一个“跨时区快递箱”,由三部分组成: - 双端口RAM:存数据; - 写指针:在写时钟域记录“写到哪了”; - 读指针:在读时钟域记录“读到哪了”; 指针用格雷码编码后跨时钟域,再比较指针判断FIFO“空/满”——FPGA厂商会提供现成的FIFO IP,不用自己写~ 实践延伸:异步FIFO适合高速数据跨域(比如摄像头数据传到FPGA内部),但占资源多;如果数据变化慢,也可以用“翻转同步器+使能”(对应课本图2-16),省资源但速度慢。