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

FPGA中的亚稳态与跨时钟域数据撕裂现象

什么是亚稳态

在 数字电路里,亚稳态(Metastability) 是指 触发器在采样异步信号时,由于 setup/hold 时间被破坏,导致寄存器的输出进入既不是稳定的 0,也不是稳定的 1 的不确定状态。

亚稳态的最直观现象

其最明显的现象就是:同一个寄存器在短时间内不可重复读。

  • 这里的不可重复读 并不是因为寄存器内容被外部逻辑改写了;
    而是因为该寄存器在采样异步信号时进入了亚稳态,输出值还没有稳定下来;
  • 在解析过程中,它的电平可能在阈值附近晃动、延迟收敛,所以不同时间点读到的结果可能不同。

什么是 CDC

CDC = Clock Domain Crossing,中文通常叫 时钟域交叉
在 FPGA/ASIC 里,常常存在多个不同时钟域(例如 50 MHz 外设时钟、100 MHz 核心时钟、125 MHz 千兆网时钟)。
当一个信号或数据从 源时钟域 传到 目的时钟域 时,就发生了 CDC

为什么要关心 CDC?

  • 异步关系:两个时钟频率可能不同,或者相位不固定,无法保证采样点和数据稳定时间对齐。
  • 亚稳态 (Metastability):目的域触发器在采到不稳定电平时,可能进入亚稳状态,导致输出不可预期。
  • 多比特一致性问题:单 bit 信号用双触发同步器基本能解决,但 多比特总线 如果直接采样,会出现“撕裂”现象。

常见的 CDC 类型

  1. 单 bit 信号跨域

    • 典型处理:双触发同步器(2-FF synchronizer)。
    • 用途:复位、标志位、中断请求。
  2. 多比特总线跨域

    • 问题:多个比特不能保证同时到达目的域。
    • 典型处理:握手协议 / 异步 FIFO / Gray 码编码。
  3. 数据流跨域

    • 大量连续数据(如音视频、网络数据)。
    • 典型处理:异步 FIFO,读写两端独立时钟。

CDC 处理的目标

  • 避免亚稳态:降低亚稳态被扩散的概率。
  • 保证一致性:目的域拿到的数据必须是源域的“一个合法快照”。
  • 系统可靠:即使不同频率、不同相位,跨域传输也要稳定、可预测。

什么是撕裂?

  • 定义:目的时钟域在源时钟域 总线翻转的瞬间 采样,捕获到“部分新值 + 部分旧值”的混合结果。
  • 特征:这种混合结果在源域的逻辑里 永远不会出现
  • 又叫 混码非原子采样

撕裂是如何产生的?

  1. 源域:多比特计数器
    例如 4 位计数器从 0111 (7)1000 (8)
  2. 实际硬件:各位翻转有延迟差
    高位翻转慢一点,低位翻转快一点。
  3. 目的域:在翻转过程中采样
    • 可能采到 1111 (15)0000 (0)
    • 这些数是源域计数器 不可能生成的非法值

撕裂的危害

  • 数据错误:比如地址总线撕裂 → 访问错误的存储单元。

  • 状态机异常:非法状态进入 → 系统死锁。

  • 调试困难:RTL 仿真正常,上板波形偶发异常


撕裂仿真实验:

实验方案

  • clk1 = 50 MHz,16 位计数器,计数范围 0…1000。
  • clk2 = 100 MHz,异步采样该计数器。

两种路径

  • 理想直连:RTL 仿真几乎看不到问题(所有位同时翻转)。
  • 带偏斜延迟:人为给总线每位注入不同延迟,更容易复现撕裂。

检测方法

因为 clk2 频率比 clk1 快,所以clk2不会漏检
故撕裂判据为:相邻采样差值 ∉ {0, 1} → 撕裂。
当撕裂次数超过 3 次时,则结束仿真

撕裂测试

counter_clk1.v

`timescale 1ns/1ps// ================== clk1 域:16位计数器,0~1000 循环 ==================
module counter_clk1 #(parameter MAX = 1000
)(input  wire        i_clk1,input  wire        i_rstn,output reg  [15:0] o_cnt
);always @(posedge i_clk1 or negedge i_rstn) beginif (!i_rstn) o_cnt <= 16'd0;else if (o_cnt == MAX) o_cnt <= 16'd0;else o_cnt <= o_cnt + 16'd1;end
endmodule

unsafe_sampler_clk2.v

`timescale 1ns/1ps// ================== clk2 域:不安全直采样(统计撕裂次数) ==================
module unsafe_sampler_clk2 #(parameter MAX = 1000              // 计数器最大值
)(input  wire        i_clk2,        // 目的时钟input  wire        i_rstn,        // 复位,低有效input  wire [15:0] i_bus_async,   // 来自异步域的总线(危险)output reg  [31:0] o_tearing_cnt, // 撕裂次数(非法跳变)output reg  [15:0] o_last_sample  // 最近一次采样值
);reg [15:0] r_sample, r_prev;// 计算 (a - b) mod (MAX+1)function automatic [15:0] moddiff;input [15:0] a, b;reg [16:0] t;beginif (a >= b) t = a - b;else        t = a + (MAX+1) - b;moddiff = t[15:0];endendfunctionalways @(posedge i_clk2 or negedge i_rstn) beginif (!i_rstn) beginr_sample       <= 16'd0;r_prev         <= 16'd0;o_last_sample  <= 16'd0;o_tearing_cnt  <= 32'd0;end else beginr_prev   <= r_sample;r_sample <= i_bus_async;   // 危险:可能撕裂o_last_sample <= r_sample;// 合法跳变只能是 0 或 +1 (mod MAX+1)if (moddiff(r_sample, r_prev) != 0 &&moddiff(r_sample, r_prev) != 1) begino_tearing_cnt <= o_tearing_cnt + 1;// 调试打印(可选)// $display("[%0t ns] Tear: prev=%0d -> now=%0d (diff=%0d)",//          $time, r_prev, r_sample, moddiff(r_sample,r_prev));endendend
endmodule

bus_skew.v

`timescale 1ns/1ps// ================== 位间偏斜注入器(仿真用) ==================
// 功能:给每一位加不同固定延迟,模拟 FPGA 中布线/门延
// 例:BASE=0, STEP=2ns -> bit0=0ns, bit1=2ns, bit2=4ns ...
module bus_skew #(parameter integer W    = 16,  // 位宽parameter integer BASE = 0,   // 起始延迟 (ns)parameter integer STEP = 2    // 位间递增延迟 (ns) —— 建议 2ns 起步
)(input  wire [W-1:0] i_bus,output wire [W-1:0] o_bus
);genvar k;generatefor (k = 0; k < W; k = k + 1) begin : g_skewlocalparam integer DLY = BASE + k*STEP;assign #(DLY) o_bus[k] = i_bus[k]; // 固定延迟(连续赋值延迟)endendgenerate
endmodule

tb.sv

`timescale 1ns/1ps// ================== Testbench ==================
module tb;// ====== 时钟与复位 ======reg clk1 = 0, clk2 = 0, rstn = 0;// clk1: 50MHz -> 周期 20nsalways #10 clk1 = ~clk1;// clk2: 100MHz -> 周期 10ns,但添加 3ns 相位偏移(关键)initial beginclk2 = 0;#3;               // 相位偏移 3ns,让采样点卡进翻转传播窗口forever #5 clk2 = ~clk2;end// 复位流程:先保持若干个 clk1 周期的低电平initial beginrstn = 0;repeat (5) @(posedge clk1);rstn = 1;end// ====== 源域计数器 ======wire [15:0] w_cnt_clk1;counter_clk1 #(.MAX(1000)) u_cnt (.i_clk1 (clk1),.i_rstn (rstn),.o_cnt  (w_cnt_clk1));// ====== 路径A:理想直连(无位间偏斜)用于对比 ======wire [31:0] w_tearing_ideal;wire [15:0] w_last_ideal;unsafe_sampler_clk2 #(.MAX(1000)) u_ideal (.i_clk2        (clk2),.i_rstn        (rstn),.i_bus_async   (w_cnt_clk1),.o_tearing_cnt (w_tearing_ideal),.o_last_sample (w_last_ideal));// ====== 路径B:带位间偏斜(稳稳复现撕裂) ======wire [15:0] w_cnt_skewed;// 提示:若你一开始仍看不到撕裂,可把 STEP 再加大到 3 或 4bus_skew #(.W(16), .BASE(0), .STEP(2)) u_skew (.i_bus (w_cnt_clk1),.o_bus (w_cnt_skewed));wire [31:0] w_tearing_skew;wire [15:0] w_last_skew;unsafe_sampler_clk2 #(.MAX(1000)) u_skewed (.i_clk2        (clk2),.i_rstn        (rstn),.i_bus_async   (w_cnt_skewed),.o_tearing_cnt (w_tearing_skew),.o_last_sample (w_last_skew));// 2) 限时运行;也可在命中多次撕裂后提前结束initial begin// 运行 30 ms(按需调整)#30000_000;$display("\n================= SUMMARY =================");$display("Ideal path   : tearing=%0d, last=%0d",w_tearing_ideal, w_last_ideal);$display("Skewed path  : tearing=%0d, last=%0d",w_tearing_skew , w_last_skew );$display("===========================================\n");$finish;end// 3) 命中多次撕裂后提前退出(例如 >= 3 次)initial beginwait (rstn == 1'b1);wait (w_tearing_skew >= 3);$display("[%0t ns] Enough tearing hits (>=3). Stopping...", $time);$finish;end
endmodule

仿真结果

# [1388000 ns] Enough tearing hits (>=3). Stopping...
# ** Note: $finish    : ../tb.sv(84)
#    Time: 1388 ns  Iteration: 2  Instance: /tb

亚稳态测试

亚稳态现象只能在真机上复现。
例如,用一个按键作为异步输入信号:

  • o_led_a 表示本拍寄存器读取的值;
  • o_led_b 表示上一拍寄存器读取的值。
  • 当连续两个时钟周期对同一个寄存器 r_meta 进行读取时,如果两次结果不同,就说明该寄存器在采样异步信号时进入了亚稳态。此时,通过翻转 o_led_diff 显示,能直观地观察到这一现象。

meta_read_twice_demo.v

`timescale 1ns/1ps
// ============================================================
// Single-Clock Metastability Read-Diff Demo (50MHz, 低电平=按下)
// - 只有一个系统时钟 i_clk50
// - 异步按键 i_btn_n(低=按下),不做消抖,便于“贴边”触发
// - r_meta 为“同一个寄存器”
// - 连续两拍分别读取 r_meta,比对是否不同;不同时翻转 o_led_diff
// - o_led_a 显示“本拍读取值”,o_led_b 显示“上一拍读取值”
// ============================================================
module meta_read_twice_demo (input  wire i_clk50,   // 50MHz 单时钟input  wire i_btn_n,   // 异步按键(低=按下),建议上拉电阻output reg  o_led_a,   // 本拍读取显示output reg  o_led_b,   // 上一拍读取显示output reg  o_led_diff // 发现“两次读取不同”就翻转,便于目视计数
);// 归一化按键:高=按下(仅用于内部逻辑,实际 i_btn_n 低=按下)wire btn = ~i_btn_n;// “同一个寄存器”:用系统时钟直接采样异步输入// 这里最容易发生 setup/hold 违例 -> 触发器可能亚稳reg r_meta /* synthesis keep = 1 */;always @(posedge i_clk50) beginr_meta <= btn; // 不消抖,不同步,刻意“糙”来放大问题end// 连续两拍“读取同一个寄存器”的值// read_now  : 本拍对 r_meta 的读取// read_prev : 上一拍对 r_meta 的读取(=“第二次读取”的对照)reg read_now, read_prev;always @(posedge i_clk50) beginread_now  <= r_meta;      // 第一次读取(本拍)read_prev <= read_now;    // 第二次读取(下一拍对比上一拍读取的结果)end// 可视化:把两次读取结果各接一颗 LEDalways @(posedge i_clk50) begino_led_a <= read_now;o_led_b <= read_prev;end// 只要发现“同一个寄存器两次读取不同”,就翻转 o_led_diff// (翻转比点亮更显眼,方便你数“发生了几次”)reg mismatch_armed;always @(posedge i_clk50) beginif (read_now != read_prev) beginif (!mismatch_armed) begino_led_diff    <= ~o_led_diff;mismatch_armed <= 1'b1; // 边沿一次只记一次endend else beginmismatch_armed <= 1'b0;endendendmodule

HC_FPGA_Demo_Top.v

`timescale 1ns/1ps
module HC_FPGA_Demo_Top
(input  CLOCK_XTAL_50MHz,input  RESET,input  KEY2,output LED0,output LED1,output LED2
);// 实例化亚稳态演示模块meta_read_twice_demo u_meta_demo (.i_clk50   (CLOCK_XTAL_50MHz), // 板载 50MHz 时钟.i_btn_n   (KEY2),             // 按键低电平=按下.o_led_a   (LED0),             // 本拍读取.o_led_b   (LED1),             // 上一拍读取.o_led_diff(LED2)              // 不同翻转);endmodule

测试结果

三个灯都会亮,说明发生了亚稳态
http://www.xdnf.cn/news/1421551.html

相关文章:

  • 眼底病害图像分类数据集
  • MYSQL速通(4/5)
  • KL Loss
  • Python OpenCV图像处理与深度学习:Python OpenCV图像滤波入门
  • [系统架构设计师]论文(二十三)
  • 基于SpringBoot+MYSQL开发的师生成果管理系统
  • 美术馆预约小程序|基于微信小程序的美术馆预约平台设计与实现(源码+数据库+文档)
  • zotero.sqlite已损坏
  • 第9篇:监控与运维 - 集成Actuator健康检查
  • 『C++成长记』vector模拟实现
  • 车载总线架构 --- 车载LIN总线传输层概述
  • 百胜软件获邀出席第七届中国智慧零售大会,智能中台助力品牌零售数智变革
  • C++ 虚继承:破解菱形继承的“双亲困境”
  • 【macOS】垃圾箱中文件无法清理的--特殊方法
  • Linux | 走进网络世界:MAC、IP 与通信的那些事
  • PyTorch 实战(3)—— PyTorch vs. TensorFlow:深度学习框架的王者之争
  • mysql中如何解析某个字段是否是中文
  • 攻防演练笔记
  • Frida Hook API 转换/显示堆栈
  • 【数学建模学习笔记】缺失值处理
  • 数学分析原理答案——第七章 习题13
  • 文件夹上传 (UploadFolder)
  • crypto-babyrsa(2025YC行业赛)
  • 【系统架构师设计(8)】需求分析之 SysML系统建模语言:从软件工程到系统工程的跨越
  • 【机器学习学习笔记】numpy基础2
  • 基于 HTML、CSS 和 JavaScript 的智能图像边缘检测系统
  • ESB 走向黄昏,为什么未来属于 iPaaS?
  • 【第十一章】Python 队列全方位解析:从基础到实战
  • 计算机网络技术(四)完结
  • 9月1日