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. 空读、满写、同拍读写、复位、写满读空、随机读写、边界地址。


相关推荐
科恒盛远1 小时前
ZU902 与 FMC200 高速采集示波器实测
fpga开发
NamoAmitabha1282 小时前
日常问答1---deepseek
fpga开发
鸥梨菌Honevid3 小时前
异步FIFO为什么用打两拍的格雷码?
fpga开发
努力搬砖的小H4 小时前
FPGA应用技巧之AXI4-FULL-MASTER IP FDMA(Fast Direct Memory)详细介绍
经验分享·fpga开发·fdma·axi总线·米联客
szxinmai主板定制专家1 天前
电力边缘 AI 落地难点剖析:RK 系列 + FPGA 异构平台在储能监控终端的 EMC 与算力优化
人工智能·fpga开发·arm+fpga·rk3588+fpga
szxinmai主板定制专家1 天前
基于RK3588+FPGA+AI的储能电站边缘网关,实现电力数据采集与智能预警
arm开发·人工智能·fpga开发·rk3576·半导体设备
XMAIPC_Robot1 天前
储能控制器产品化:ARM+FPGA整机高低温、EMC可靠性测试实战总结
arm开发·人工智能·fpga开发·arm+fpga·rk3588+fpga
szxinmai主板定制专家1 天前
面向虚拟电厂协调控制:RK3588+FPGA+AI边缘控制器设计思路与算力评估
人工智能·嵌入式硬件·fpga开发·机器人·jetson·zynq·振动采集