STA 与 CDC

📺 图片来自【数字 IC 秋招经验:STA、CDC】https://www.bilibili.com/video/BV1aSRzY5E81?vd_source=1a46c7af8e528520d93f15dd5c760634

STA 与 CDC

先给你一张地图:

下面按"从零到能干活"的方式讲。先给一张地图:

STA = 静态时序分析 :不跑激励,检查电路在所有时序路径上是否满足建立/保持时间。

CDC = 跨时钟域检查/设计 :处理不同时钟域之间传信号时的亚稳态、数据丢失、数据错乱问题。

STA 保证同步时序正确;CDC 保证异步交互安全。两者互补,不能互相替代。


一、STA 静态时序分析

1. STA 是什么STA 不需要测试向量,它把电路拆成一条条时序路径,计算每条路径的延迟,然后判断:

STA 不需要测试向量,它把电路拆成一条条时序路径,计算每条路径的延迟,然后判断:

  • 数据能不能在时钟沿前稳定下来?→ Setup 建立时间
  • 数据能不能在时钟沿后保持足够久?→ Hold 保持时间

工具:PrimeTime、Tempus、OpenSTA 等。

2. 四种基本时序路径

  1. 输入端口 → 寄存器

  2. 寄存器 → 寄存器

  3. 寄存器 → 输出端口

  4. 输入端口 → 输出端口(纯组合穿透)

  5. 输入端口 → 寄存器

  6. 寄存器 → 寄存器

  7. 寄存器 → 输出端口

  8. 输入端口 → 输出端口(纯组合穿透)

STA 会报告每条路径的 slack:

  • Setup slack = required time - arrival time
  • Hold slack = arrival time - required time

slack 为正:满足。

slack 为负:违例。

最差的负 slack 叫:

  • WNS:Worst Negative Slack,最差建立违例
  • TNS:Total Negative Slack,所有建立违例总和
  • WHS:Worst Hold Slack,最差保持违例
  • THS:Total Hold Slack,所有保持违例总和

3. Setup 和 Hold

Setup 建立时间

时钟沿到来前,数据必须已经稳定一段时间。

简化公式:

text 复制代码
T_clk + T_skew >= T_co + T_comb + T_setup

含义:

  • 时钟周期要足够长
  • 寄存器时钟到输出延迟 T_co 要小
  • 组合逻辑 T_comb 要短
  • 建立时间 T_setup 要满足
  • 时钟偏斜 T_skew 要有利

Setup 违例怎么修:

  • 降低时钟频率
  • 插入流水线寄存器
  • 优化组合逻辑
  • 换更快单元
  • 调整时钟树 skew
  • 减少扇出

Hold 保持时间

时钟沿到来后,数据还必须再稳定一段时间。

简化公式:

text 复制代码
T_co + T_comb >= T_hold + T_skew

Hold 违例通常发生在:

  • 组合逻辑太短
  • 时钟 skew 太大
  • 路径太快

Hold 违例怎么修:

  • 插入 buffer 增加延迟
  • 换慢单元
  • 调整时钟树
  • 增加布线延迟
  • 修时钟 skew

注意:Setup 违例可以降频救,Hold 违例降频通常没用,因为它是同沿竞争。

4. SDC 约束

STA 需要约束,最常用 SDC:

tcl 复制代码
create_clock -name clk -period 10 [get_ports clk]

set_clock_uncertainty 0.2 [get_clocks clk]

set_input_delay  -clock clk 1.5 [get_ports din]
set_output_delay -clock clk 2.0 [get_ports dout]

set_false_path -from [get_clocks clkA] -to [get_clocks clkB]

set_multicycle_path 2 -setup -from [get_clocks clkA] -to [get_clocks clkB]

set_clock_groups -asynchronous -group {clkA} -group {clkB}

关键约束:

约束 作用
create_clock 定义主时钟
create_generated_clock 定义分频/倍频时钟
set_input_delay 输入端口外部延迟
set_output_delay 输出端口外部延迟
set_clock_uncertainty 时钟抖动/余量
set_false_path 不做时序检查的路径
set_multicycle_path 多周期路径
set_clock_groups 声明异步时钟组
set_max_delay/min_delay 自定义最大/最小延迟

5. 多模式多角

真实芯片要看:

  • PVT:Process、Voltage、Temperature
  • WC:Worst Case,慢工艺、低电压、高温,查 Setup
  • BC:Best Case,快工艺、高电压、低温,查 Hold
  • MMMC:多模式多角,多时钟、多约束、多 Corner

二、CDC 跨时钟域

1. 为什么需要 CDC

当信号从一个时钟域传到另一个时钟域,如果两个时钟异步,接收端触发器可能在数据变化时采样,导致:

  • 亚稳态:输出既不是 0 也不是 1,振荡一段时间后随机稳定
  • 数据丢失:慢时钟采不到快脉冲
  • 数据错乱:多比特信号各位到达时间不同
  • 毛刺:组合逻辑竞争

STA 通常用 set_clock_groups -asynchronous 把异步域切开,不做普通时序检查。但这不代表安全,必须靠 CDC 设计和检查。

2. 亚稳态

触发器有 setup/hold 窗口。如果数据在窗口内变化,输出可能进入亚稳态。

亚稳态不能完全消除,只能降低概率。常用指标:

text 复制代码
MTBF = e^(t_r/τ) / (T0 * f_clk * f_data)

两级同步器可以大幅提高 MTBF。

3. 单比特同步器

两级触发器同步器

systemverilog 复制代码
always_ff @(posedge clk_dst or negedge rst_n) begin
  if (!rst_n) begin
    sync1 <= 1'b0;
    sync2 <= 1'b0;
  end else begin
    sync1 <= async_signal;
    sync2 <= sync1;
  end
end

用途:

  • 单比特电平信号
  • 慢到快通常没问题
  • 快到慢要求信号宽度大于目标时钟周期
  • 只能传电平,不能直接传脉冲

脉冲同步器

如果源域是脉冲,目标域时钟慢,可能漏采。常用:

  • 源域把脉冲转成 toggle
  • 目标域同步 toggle
  • 目标域再还原成脉冲

握手同步

text 复制代码
源域:数据稳定 → 拉 req
目标域:同步 req → 采数据 → 拉 ack
源域:同步 ack → 撤销 req

适合多比特数据,但吞吐率低。

4. 多比特 CDC

绝对不要对多比特总线直接逐位打两拍。

因为各位延迟不同,可能采到中间错误值。

多比特常用方案:

  1. 异步 FIFO
  2. 握手 + 数据保持
  3. 格雷码
  4. MUX 同步
  5. DMUX 同步

打两拍作为跨时钟的基本处理方式

一般认为,一个异步采集的数据打两拍,电路能获得稳定的值参与后续计算。

两 bit 数据进行跨时钟处理

一、同步 FIFO

1. 同步 FIFO 是什么

同步 FIFO :读和写用同一个时钟,所以不需要跨时钟域同步。

对比:

同步 FIFO 异步 FIFO
时钟 读写同一时钟 读时钟、写时钟不同
CDC 不需要 需要
指针编码 二进制即可 通常用格雷码
空满判断 同域判断,简单 跨域同步,复杂
典型用途 同域缓冲、流水线解耦 跨时钟域数据传递

同步 FIFO 解决的是:

  • 生产者和消费者速率不匹配
  • 突发数据缓冲
  • 反压和流控

2. 同步 FIFO 的结构

text 复制代码
        写侧                          读侧
   wr_en, wr_data               rd_en, rd_data
        │                             ▲
        ▼                             │
   ┌─────────┐    ┌──────────┐   ┌─────────┐
   │ 写指针   │───▶│  存储阵列  │──▶│ 读指针   │
   └─────────┘    └──────────┘   └─────────┘
        │                             │
        ▼                             ▼
     满判断                        空判断
        │                             │
        └──────────┬──────────────────┘
                   ▼
              full / empty

核心组成:

  • 存储阵列:寄存器堆或双端口 RAM
  • 写指针:指向下一个写入位置
  • 读指针:指向下一个读出位置
  • 满判断:写侧
  • 空判断:读侧
  • 计数器(可选):记录当前元素个数

3. 空满判断的经典问题

如果只用 N 位指针表示深度 2^N 的 FIFO:

  • 写指针追上读指针:可能是满,也可能是空
  • 读指针追上写指针:可能是空,也可能是满

当 wr_ptr == rd_ptr 时,无法区分空和满。

解决方案一:多一位指针

指针用 N+1 位,地址用低 N 位,最高位作为"圈数"标志。

  • 空 :wr_ptr == rd_ptr,所有位相同
  • 满:低 N 位相同,最高位不同
systemverilog 复制代码
// 深度 8,指针 4 位
// 空:wr_ptr[3:0] == rd_ptr[3:0]
// 满:wr_ptr[3:0] == rd_ptr[3:0] 且 wr_ptr[3] != rd_ptr[3]

解决方案二:使用计数器

用一个 count 记录当前元素个数:

  • count == 0:空
  • count == DEPTH:满

简单直观,但计数器在高速时可能成为关键路径。

4. 同步 FIFO 代码示例

下面用计数器法,深度 8:

systemverilog 复制代码
module sync_fifo #(
  parameter int DATA_WIDTH = 8,
  parameter int DEPTH      = 8,
  parameter int ADDR_WIDTH = $clog2(DEPTH)
)(
  input  logic                  clk,
  input  logic                  rst_n,

  input  logic                  wr_en,
  input  logic [DATA_WIDTH-1:0] wr_data,
  output logic                  full,

  input  logic                  rd_en,
  output logic [DATA_WIDTH-1:0] rd_data,
  output logic                  empty,

  output logic [ADDR_WIDTH:0]   count
);

  logic [DATA_WIDTH-1:0] mem [0:DEPTH-1];
  logic [ADDR_WIDTH-1:0] wr_ptr, rd_ptr;
  logic [ADDR_WIDTH:0]   cnt;

  assign full  = (cnt == DEPTH);
  assign empty = (cnt == 0);
  assign count = cnt;

  // 写
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      wr_ptr <= '0;
    else if (wr_en && !full) begin
      mem[wr_ptr] <= wr_data;
      wr_ptr      <= wr_ptr + 1'b1;
    end
  end

  // 读
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      rd_ptr <= '0;
    else if (rd_en && !empty)
      rd_ptr <= rd_ptr + 1'b1;
  end

  assign rd_data = mem[rd_ptr];

  // 计数
  always_ff @(posedge clk or negedge rst_n) begin
    if (!rst_n)
      cnt <= '0;
    else begin
      case ({wr_en && !full, rd_en && !empty})
        2'b10:   cnt <= cnt + 1'b1;  // 只写
        2'b01:   cnt <= cnt - 1'b1;  // 只读
        default: cnt <= cnt;          // 同读同写或都不动
      endcase
    end
  end

endmodule

要点:

  • full 时写无效,empty 时读无效
  • 同拍读写且非空非满时,cnt 不变
  • rd_data 用组合读,也可以加一级寄存器输出

5. 同步 FIFO 的验证要点

  • 空时读:不应读出有效数据,empty 保持
  • 满时写:不应覆盖数据,full 保持
  • 同拍读写:计数不变,数据正确
  • 复位后:empty=1,full=0,指针归零
  • 写满再读空:数据顺序正确
  • 随机读写:与参考模型比对
  • 覆盖率:满、空、同拍读写、边界地址

6. 同步 FIFO vs 异步 FIFO 的 CDC 关系

  • 同步 FIFO 不涉及 CDC,STA 按同一时钟约束即可。
  • 异步 FIFO 是 CDC 的典型方案,需要:
    • 格雷码指针
    • 两级同步器
    • set_clock_groups -asynchronous
    • CDC 工具检查

异步 FIFO

典型结构:

  • 写指针:二进制转格雷码 → 同步到读域
  • 读指针:二进制转格雷码 → 同步到写域
  • 空判断在读域
  • 满判断在写域

为什么用格雷码?

格雷码相邻值只变一位,跨域采样时最多错一位,不会出现多位同时变化的错乱。

FIFO 深度估算:

text 复制代码
写快读慢:
深度 >= 突发长度 - 突发期间读出的数据量

还要考虑同步延迟、空满判断延迟。

5. 复位 CDC

异步复位,同步释放:

systemverilog 复制代码
always_ff @(posedge clk or negedge rst_async_n) begin
  if (!rst_async_n) begin
    rst_sync1 <= 1'b0;
    rst_sync2 <= 1'b0;
  end else begin
    rst_sync1 <= 1'b1;
    rst_sync2 <= rst_sync1;
  end
end

好处:

  • 复位可以异步生效
  • 释放与时钟同步,避免不同触发器退出复位时间不同

6. CDC 约束

常见写法:

tcl 复制代码
set_clock_groups -asynchronous -group {clkA} -group {clkB}

或者:

tcl 复制代码
set_false_path -from [get_clocks clkA] -to [get_clocks clkB]

但对同步器第一级,有些流程会要求:

tcl 复制代码
set_max_delay -datapath_only -from [get_clocks clkA] -to [get_clocks clkB] 2.0

目的:限制跨域数据路径延迟,防止第一级同步器前组合逻辑太慢。

注意:

  • 不要对同步器输出后级乱设 false path
  • 同步器后的逻辑要按目标时钟域正常约束
  • set_clock_groups 和 set_false_path 作用范围不同

7. CDC 检查工具

  • SpyGlass CDC
  • Meridian CDC
  • Questa CDC
  • Conformal CDC
  • VC CDC

检查内容:

  • 时钟域识别
  • 跨域路径
  • 同步器识别
  • 亚稳态模型
  • 数据一致性
  • 复位跨域
  • 毛刺
  • 协议检查

验证工程师还要做:

  • 仿真注入跨域场景
  • 断言检查
  • 覆盖率
  • 形式验证
  • 门级仿真

异步 FIFO

为什么异步 FIFO 用写格雷码与打两拍的读格雷码进行比较?为什么在写时钟域做写满判断?写满信号产生的目的?

  1. 让外部不再对 FIFO 进行写操作,外部需要第一时间获得 FIFO 已满信息,哪怕信息是滞后的

三、STA 和 CDC 的关系

STA CDC
对象 同步时序路径 异步跨时钟域
是否跑激励 不需要 设计检查 + 仿真/形式
核心问题 Setup/Hold 亚稳态、数据一致性
典型工具 PrimeTime、Tempus SpyGlass CDC、Meridian
约束 SDC 异步时钟组、同步器约束
输出 slack 报告 CDC 违例、同步器检查
关系 保证同步时序 保证异步安全

一句话:

STA 管"同一个时钟域内别迟到别早退";CDC 管"不同时钟域之间别传错"。


四、学习路线

第 1 阶段:数字时序基础

  • 触发器、建立保持时间
  • 时钟偏斜、抖动
  • 组合逻辑延迟
  • 同步/异步复位

第 2 阶段:STA

  • 四种时序路径
  • Setup/Hold 公式
  • SDC 约束
  • 读时序报告:WNS/TNS/WHS/THS
  • 多周期、false path、时钟组
  • PVT、OCV、MMMC

第 3 阶段:CDC

  • 亚稳态
  • 两级同步器
  • 脉冲同步、握手
  • 异步 FIFO、格雷码
  • 复位同步
  • CDC 约束
  • CDC 工具检查

第 4 阶段:实践

  1. 写一个简单同步电路,跑 STA,看 slack。
  2. 故意加长组合逻辑,制造 setup 违例,再修。
  3. 写一个跨时钟域单比特同步器,跑 CDC 工具。
  4. 写一个异步 FIFO,仿真验证空满。
  5. 读一份 CDC 报告,解释每条违例。

五、自测题

  1. Setup 违例和 Hold 违例分别怎么修?
  2. 为什么 Hold 违例不能靠降频解决?
  3. set_false_path 和 set_clock_groups 有什么区别?
  4. 为什么多比特不能直接打两拍?
  5. 异步 FIFO 为什么用格雷码?
  6. 慢到快和快到慢的单比特同步分别要注意什么?
  7. 什么是亚稳态?为什么不能完全消除?
  8. CDC 工具和 STA 工具各自检查什么?
  9. 同步 FIFO 为什么不需要格雷码?
  10. 同步 FIFO 中 wr_ptr == rd_ptr 时,如何区分空和满?
  11. 同步 FIFO 同拍读写时,计数器怎么变化?
  12. 同步 FIFO 和异步 FIFO 在 STA 约束上有什么区别?
  13. 同步 FIFO 验证需要覆盖哪些边界场景?

六、自测题答案

1. Setup 违例和 Hold 违例分别怎么修?

Setup 违例(数据到得太晚):

  • 降低时钟频率
  • 插入流水线寄存器,切短组合逻辑
  • 优化组合逻辑,减少级数
  • 换更快单元
  • 减少扇出
  • 调整时钟树 skew
  • 使用多周期路径约束(如果逻辑允许多周期)

Hold 违例(数据变得太早):

  • 插入 buffer 增加数据路径延迟
  • 换慢单元
  • 增加布线延迟
  • 调整时钟树,修 skew
  • 减小时钟 uncertainty
  • 修 hold 通常不能靠降频

2. 为什么 Hold 违例不能靠降频解决?

Hold 检查的是同一个时钟沿附近数据是否保持稳定。

公式:

text 复制代码
T_co + T_comb >= T_hold + T_skew

这个不等式里没有时钟周期 T_clk 。

降频只增大 T_clk,影响的是 setup:

text 复制代码
T_clk + T_skew >= T_co + T_comb + T_setup

所以降频对 hold 违例无效。

3. set_false_path 和 set_clock_groups 有什么区别?

set_false_path set_clock_groups
作用 指定某条/某组路径不做时序检查 声明时钟组之间异步
方向 有方向性,-from ... -to ... 组之间双向异步
范围 可精细到端口/单元 通常按时钟组
典型用途 个别不需要检查的路径 异步时钟域
优先级 高 高

区别:

  • set_false_path 是路径级裁剪,可以只切一个方向。
  • set_clock_groups -asynchronous 是时钟级声明,组间所有路径都不检查,双向。
  • 异步时钟域通常用 set_clock_groups。
  • 个别假路径、测试逻辑、跨域已同步路径可用 set_false_path。

注意:两者都可能掩盖真实违例,要谨慎。

4. 为什么多比特不能直接打两拍?

因为多比特信号的每一位路径延迟不同,跨时钟域采样时:

  • 有的位已经变成新值
  • 有的位还是旧值
  • 接收端可能采到新旧混合的中间值

例如 4 位数据从 0111 变到 1000,四位同时变化。

如果采样时刻正好在变化窗口,可能采到 0000、1111、0110 等错误值。

所以多比特跨域必须用:

  • 异步 FIFO
  • 握手
  • 格雷码
  • MUX/DMUX 同步

不能逐位打两拍。

5. 异步 FIFO 为什么用格雷码?

格雷码相邻两个数只变一位。

跨时钟域采样指针时:

  • 即使采样时刻在变化窗口
  • 最多只可能采到旧值或新值
  • 不会出现多位同时跳变导致的错误中间值

这样空满判断才安全。

如果指针用二进制,多个位同时变化,跨域采样可能得到完全错误的值,导致:

  • 假空
  • 假满
  • 数据覆盖
  • 数据丢失

6. 慢到快和快到慢的单比特同步分别要注意什么?

慢到快:

  • 源信号宽度远大于目标时钟周期
  • 目标域能采到多个周期
  • 两级同步器即可
  • 注意信号是电平还是脉冲

快到慢:

  • 源脉冲可能比目标时钟周期短
  • 目标域可能完全采不到
  • 必须用脉冲展宽、toggle 同步、握手
  • 要求源信号保持足够长时间

共同点:

  • 两级触发器同步
  • 第一级可能亚稳态
  • 第二级输出稳定
  • 不能直接传多比特

7. 什么是亚稳态?为什么不能完全消除?

亚稳态:触发器在 setup/hold 窗口内采样到变化的数据,输出既不是稳定 0 也不是稳定 1,而是在中间电平振荡一段时间,最终随机稳定到 0 或 1。

不能完全消除的原因:

  • 建立/保持窗口是物理存在的
  • 异步信号与时钟沿的关系是随机的
  • 只要存在异步采样,就有概率进入亚稳态
  • 只能通过同步器、增加等待时间、提高 MTBF 来降低概率
  • 无法把概率降到绝对零

8. CDC 工具和 STA 工具各自检查什么?

STA 工具:

  • 同步时序路径的 setup/hold
  • 时钟约束、输入输出延迟
  • 多周期、false path
  • 时钟偏斜、抖动
  • PVT corner
  • 报告 slack、WNS、TNS

CDC 工具:

  • 时钟域识别
  • 跨域路径
  • 同步器结构识别
  • 亚稳态风险
  • 多比特数据一致性
  • 复位跨域
  • 毛刺
  • 握手协议
  • 格雷码使用
  • FIFO 空满逻辑

关系:

  • STA 通常把异步域用 set_clock_groups 切开,不检查跨域时序
  • CDC 工具专门检查这些被切开的路径
  • 两者互补,缺一不可

9. 读写同一时钟域,指针不需要跨域采样,不会出现多位跳变采样错误。

10. 用多一位指针,或加计数器;空是全等,满时高位不同。

11. 同拍读写且非空非满时,计数器不变。

12. 同步 FIFO 按单时钟约束;异步 FIFO 要声明异步时钟组,并对同步器加约束。

13. 空读、满写、同拍读写、复位、写满读空、随机读写、边界地址。


相关推荐
Hello-FPGA1 天前
基于RFSOC FPGA ZCU216 的CoaXPress 采集demo
fpga开发
9527华安1 天前
FPGA基于ADV7182解码PAL视频,Video Processing Subsystem去隔行,提供6套工程源码和技术支持
fpga开发·音视频·pal·adv7182
hahaha60161 天前
数字图像时域滤波或者3D降噪算法FPGA实现
算法·计算机视觉·fpga开发
GateWorld1 天前
Xilinx 7系列FPGA时钟网络详解
fpga开发·时钟网络·xilinx 7系列
szxinmai主板定制专家2 天前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x
szxinmai主板定制专家2 天前
AMR 项目落地:机器人控制器硬件定制化需求复盘
arm开发·单片机·fpga开发·系统架构·机器人·am62x
FPGA小迷弟3 天前
FPGA 与卫星:从星载硬件到在轨智能的完整技术解析
fpga开发·架构·fpga·1024程序员节·无线电
NamoAmitabha1283 天前
CPU 内部结构是什么?---deepseek
fpga开发
abc light3 天前
ARM 裸机开发(6)——时钟树与定时器
arm开发·单片机·fpga开发
156082072194 天前
JFMZQ28DR、JFMZQ68DR、RFsoc XCZU47DR、XCZU48DR 信号处理板
fpga开发·信号处理