FPGA 虚拟时钟(Virtual Clock):为什么需要、什么时候用、怎么用

FPGA 虚拟时钟(Virtual Clock):为什么需要、什么时候用、怎么用

目录

  • [1. 一句话定义](#1. 一句话定义)
  • [2. 为什么需要虚拟时钟](#2. 为什么需要虚拟时钟)
  • [3. 四种使用场景](#3. 四种使用场景)
  • [4. 场景展开:input_delay 参考时钟不在 FPGA 内](#4. 场景展开:input_delay 参考时钟不在 FPGA 内)
  • [5. 场景展开:output_delay 参考时钟不在 FPGA 内](#5. 场景展开:output_delay 参考时钟不在 FPGA 内)
  • [6. 场景展开:源同步------虚拟时钟分担物理时钟](#6. 场景展开:源同步——虚拟时钟分担物理时钟)
  • [7. 场景展开:分层板级延迟建模](#7. 场景展开:分层板级延迟建模)
  • [8. 虚拟时钟 vs max_delay](#8. 虚拟时钟 vs max_delay)
  • [9. 虚拟时钟改变布局布线吗](#9. 虚拟时钟改变布局布线吗)
  • [10. 速查](#10. 速查)

1. 一句话定义

虚拟时钟 = 存在约束文件里、但没有物理管脚的时钟。它描述"外部世界的时间基准",让 set_input_delay / set_output_delay 有合法的参考时钟。


2. 为什么需要虚拟时钟

set_input_delayset_output_delay 的语法长这样:

tcl 复制代码
set_input_delay -clock <参考时钟> -max 4.0 [get_ports i_data]
#                     ↑
#              这里必须填一个时钟

这个时钟就是"数据跟着谁来的/去谁那的"。

问题来了------如果那个时钟没有进 FPGA

复制代码
              25MHz 晶振 (ADC 专用)          100MHz 晶振 (FPGA 专用)
                     │                              │
                ┌────┴────┐                    ┌────┴────┐
                │   ADC   │                    │  FPGA   │
                │         │  DOUT ────────────→│         │
                └─────────┘                    └─────────┘
                
问: i_dout 的 input_delay 参考哪个时钟?
   ADC 的 25MHz → 没进 FPGA, 没有物理 pin
   FPGA 的 100MHz → 和数据没关系, 数据不跟着它走
   
答: 造一个虚拟的 25MHz 时钟 → create_clock -name clk_adc_virt

3. 四种使用场景

场景 问题 虚拟时钟的角色
1. 输入:外部器件时钟不在 FPGA 内 set_input_delay-clock 没东西填 代表外部器件的时钟
2. 输出:下游器件时钟不在 FPGA 内 set_output_delay-clock 没东西填 代表下游器件的时钟
3. 源同步:时钟和数据都进 FPGA,但有时钟歪斜 DCO pin 和 DATA pin 的 PCB 不等长 分开描述时钟延迟和数据延迟
4. 板级建模:多级时钟树 从晶振到外部寄存器的路径上有好几段延迟 每段一个虚拟时钟,分层建模

4. 场景展开:input_delay 参考时钟不在 FPGA 内

物理连接

复制代码
          25MHz 晶振 (ADC 侧)              100MHz 晶振 (FPGA 侧)
                │                                │
                │ PCB_clk_adc=2ns                │ PCB_clk_fpga=3ns
                │                                │
           ┌────┴────┐                      ┌────┴────┐
           │   ADC   │                      │  FPGA   │
           │  CLK pin│                      │ CLK pin │
           │         │  DOUT ──────────────→│ DATA pin│
           └─────────┘    PCB_data=1.5ns    └─────────┘

ADC 手册:t_co = 3~10ns(CLK↑ → DOUT 有效)

约束写法

tcl 复制代码
# 晶振源头: ADC 的 25MHz
create_clock -period 40.000 -name clk_xtal

# 虚拟时钟: ADC 内部寄存器看到的时钟
# 晶振→ADC pin(2ns) + ADC内部时钟树(1ns) = 3ns
create_clock -period 40.000 -name clk_adc_reg
set_clock_latency -source 3.000 [get_clocks clk_adc_reg]

# FPGA 自己的时钟
create_clock -period 10.000 -name clk_fpga [get_ports i_fpga_clk]

# input_delay: 数据从 ADC 寄存器 Q 端 → FPGA data pin
# = t_co_max(10ns) + PCB_data(1.5ns) = 11.5ns
# = t_co_min(3ns)  + PCB_data(1.5ns) = 4.5ns
set_input_delay -clock clk_adc_reg -max 11.500 [get_ports i_dout]
set_input_delay -clock clk_adc_reg -min  4.500 [get_ports i_dout]

# 两个独立晶振 → 异步
set_clock_groups -asynchronous \
    -group [get_clocks clk_fpga] \
    -group [get_clocks clk_adc_reg]

如果没有虚拟时钟

就只能把所有外部延迟揉成一个数,直接搭在 FPGA 时钟上:

tcl 复制代码
set_input_delay -clock clk_fpga -max 某个数 [get_ports i_dout]

但这个"某个数"没有物理意义------数据根本不跟着 clk_fpga 走。工具用假的参考时钟算 setup/hold,算出假的 slack。

虚拟时钟在这里干什么

复制代码
虚拟时钟告诉工具:
  "数据在 clk_adc_reg 沿后 11.5ns 到 FPGA pin"

set_clock_groups 告诉工具:
  "clk_adc_reg 和 clk_fpga 异步, 不用拿两个钟比 phase"

最终效果:
  工具知道外部延迟是 11.5ns
  内部只按 max_delay 逻辑走
  不会算出 "相对于 clk_fpga 的假 setup 违例"

5. 场景展开:output_delay 参考时钟不在 FPGA 内

物理连接

复制代码
          100MHz 晶振 (FPGA 侧)            50MHz 晶振 (DAC 侧)
                │                                │
                │                                │ PCB_clk_dac=2ns
                │                                │
           ┌────┴────┐                      ┌────┴────┐
           │  FPGA   │                      │   DAC   │
           │         │  DATA ──────────────→│ DIN pin │
           └─────────┘    PCB_data=2.5ns    └─────────┘

DAC 手册:Tsu = 1.5ns, Th = 0.8ns

约束写法

tcl 复制代码
# FPGA 时钟
create_clock -period 10.000 -name clk_fpga [get_ports i_fpga_clk]

# 虚拟时钟: DAC 的 50MHz 时钟
create_clock -period 20.000 -name clk_dac

# output_delay: FPGA data pin → DAC DIN pin (含 Tsu)
# = PCB_data(2.5ns) + Tsu(1.5ns) = 4.0ns
set_output_delay -clock clk_dac -max 4.000 [get_ports o_data[*]]

# = PCB_data(2.5ns) - Th(0.8ns) = 1.7ns
set_output_delay -clock clk_dac -min 1.700 [get_ports o_data[*]]

set_clock_groups -asynchronous -group clk_fpga -group clk_dac

虚拟时钟在这里干什么

工具知道数据出了 FPGA pin 之后还要走 1.7~4.0ns 才到 DAC 的数据输入端。结合 FPGA 内部的 Tco + routing,算出内部那条路能不能在 DAC 要求的时间内送达。虚拟时钟提供了"送达时间"的参考。


6. 场景展开:源同步------虚拟时钟分担物理时钟

物理连接

复制代码
ADC DCO  ── PCB=3ns ──→ FPGA pin A ── BUFG ──→ dco_bufg (物理时钟)
ADC DATA ── PCB=2ns ──→ FPGA pin B ── IBUF ──→ 寄存器

DCO 和 DATA 的 PCB 不等长。DCO 多走了 1ns,意味着 FPGA pin 上 DCO 沿比 DATA 晚到 1ns。

如果只用物理时钟

tcl 复制代码
create_clock -period 25.000 -name dco_phys [get_ports i_dco]

set_input_delay -clock dco_phys -max 4.000 [get_ports i_data[*]]
#                             ↑
#          这个值是多少? ADC t_co=2ns, DATA PCB=2ns, 但 DCO PCB=3ns
#          input_delay = t_co + DATA_PCB - DCO_PCB = 2+2-3 = 1ns
#          必须手算歪斜, 容易错

用虚拟时钟

tcl 复制代码
# ADC DCO pin 处的理想时钟
create_clock -period 25.000 -name dco_adc_pin

# FPGA DCO pin 处的物理时钟
create_clock -period 25.000 -name dco_fpga [get_ports i_dco]

# ADC pin → FPGA pin: DCO PCB 3ns
set_clock_latency -source 3.000 [get_clocks dco_fpga]

# input_delay 以 ADC pin 为基准, 不含 PCB 歪斜
# = t_co(2ns) + DATA_PCB(2ns) = 4ns
set_input_delay -clock dco_adc_pin -max 4.000 [get_ports i_data[*]]
复制代码
时间线:

DCO at ADC pin (虚拟):    ──┐        ┌──      t=0
                            └────────┘

DCO at FPGA pin (物理):   ───┐       ┌──      t=3ns (PCB走了3ns)
                             └───────┘

DATA at FPGA pin:             ├t_co=2ns┤├PCB=2ns┤
                              到达 t=4ns (相对ADC DCO)
                              到达 t=1ns (相对FPGA DCO) ← 比DCO早到1ns!

input_delay = 4ns (相对虚拟时钟)
工具知道: 数据在对齐ADC DCO沿后4ns到FPGA data pin
          DCO 在对齐后3ns到FPGA DCO pin
          → 数据比DCO早1ns
          → 内部时钟预算 = 25 - (-1) - Tsu = ~25.5ns ← 超宽裕

虚拟时钟把"外部歪斜"和"内部时钟"解耦了。 不用手算歪斜塞进 input_delay。


7. 场景展开:分层板级延迟建模

完整的延迟链条

复制代码
      晶振
        │
   ┌────┴────┐
   │时钟缓冲器│
   └──┬───┬──┘
      │   │
      │   └── PCB=4ns ──→ FPGA CLK pin
      │
      └── PCB=3ns ──→ ADC CLK pin
                        │
                    ADC内部时钟树 = 1.5ns
                        │
                    ADC寄存器CLK (真正触发)
                        │
                    t_co = 2~10ns
                        │
                    ADC DOUT pin
                        │
                    PCB_data = 2ns
                        │
                    FPGA DATA pin

分层建模

tcl 复制代码
# 第0层: 晶振
create_clock -period 25.000 -name clk_xtal

# 第1层: ADC pin 收到的时钟
create_clock -period 25.000 -name clk_adc_pin

# 第2层: ADC 内部寄存器看到的时钟 (真正的"发射沿")
create_clock -period 25.000 -name clk_adc_reg

# 第3层: FPGA 物理时钟
create_clock -period 10.000 -name clk_fpga [get_ports i_fpga_clk]

# 逐级延迟
set_clock_latency -source 3.000 [get_clocks clk_adc_pin]   # 晶振→ADC pin
set_clock_latency -source 1.500 [get_clocks clk_adc_reg]   # ADC pin→内部寄存器

# input_delay: ADC寄存器Q → FPGA data pin
# = t_co(10ns max) + PCB_data(2ns) = 12ns max
set_input_delay -clock clk_adc_reg -max 12.000 [get_ports i_data[*]]
set_input_delay -clock clk_adc_reg -min  4.000 [get_ports i_data[*]]
#                              t_co(2ns min) + 2ns = 4ns min

set_clock_groups -asynchronous -group clk_fpga -group clk_adc_reg

好处:如果 PCB 改版、时钟树换了缓冲器、或者换了 ADC 型号,改约束只改对应那层的一个数,不用推倒重算整个 input_delay。


8. 虚拟时钟 vs max_delay

能不能用 max_delay 替代虚拟时钟?

以 ADS1274 例子:ADC 独立晶振,FPGA 独立晶振。

方案 A:虚拟时钟 + input_delay

tcl 复制代码
create_clock -period 40.000 -name clk_adc_virt
set_input_delay -clock clk_adc_virt -max 12.000 [get_ports i_dout]
set_clock_groups -asynchronous -group clk_fpga -group clk_adc_virt

方案 B:max_delay

tcl 复制代码
set_false_path -from [get_ports i_dout]
set_max_delay -datapath_only 5.000 -from [get_ports i_dout] -to [get_cells ...]

差异

虚拟时钟 + input_delay max_delay
外部延迟信息 :12ns 外部延迟告知工具 :工具不知道外面花了多久
布局策略 外部 12ns → 内部预算宽裕 → 寄存器不抢关键位置 硬限内部 ≤ 5ns → 寄存器硬贴 pin
设计意图 "数据来得很慢,里面不用急" "不管外面怎样,里面布线不得超过 5ns"
可维护性 改 PCB/换器件 → 改一个数 没有外部信息 → 改了也不知道

直观对比

复制代码
一根路径: 外部 12ns, 内部实际只需 1ns, 允许内部 20ns

方案 A:
  工具: "外面 12ns, 里面你有 20ns 预算, 1ns 就够了, 放远点没事"
  → 寄存器放得远 (比如 3ns routing), pin 旁边空位留给真正的紧路径

方案 B:
  工具: "里面 ≤ 5ns"
  → 寄存器硬贴 pin (0.5ns)
  → pin 旁边的位置被无谓占据
  → 真正的紧路径 (比如 400MHz DDR) 被挤到远处, 反而违例

虚拟时钟保留了"慢"的信息,让工具不浪费关键资源。max_delay 把"慢"丢了。


9. 虚拟时钟改变布局布线吗

不直接改。 它没有物理 pin,不存在"虚拟时钟的走线"。

间接驱动布局 ------通过传递延迟信息给 input_delay / output_delay

复制代码
set_input_delay -max 3ns → 工具: "外面很快, 里面也得快" → 寄存器贴 pin
set_input_delay -max 25ns → 工具: "外面很慢, 里面不急" → 寄存器随便放

影响链:

复制代码
虚拟时钟 + set_clock_latency
         ↓
    input_delay 的值变准确了
         ↓
    setup/hold 预算变准确了
         ↓
    布局布线密度变合理了
    紧的地方紧,松的地方松

10. 速查

我需要虚拟时钟吗?

复制代码
问: 数据参考的那个时钟进了 FPGA 吗?
  ├── 进了 (有物理 pin + BUFG) → 不需要, 用物理时钟
  │
  └── 没进 → 需要虚拟时钟
       ├── 外部器件独立晶振 → 虚拟时钟代表它
       ├── 下游器件独立晶振 → 虚拟时钟代表它
       └── 进了但不完全 (有歪斜) → 虚拟时钟分担物理时钟

约束套路

tcl 复制代码
# 1. 造虚拟时钟
create_clock -period <外部周期> -name <名字>

# 2. (可选) 加源延迟
set_clock_latency -source <晶振到器件的总延迟> [get_clocks <名字>]

# 3. 绑 input/output delay
set_input_delay  -clock <名字> -max <值> [get_ports ...]
set_output_delay -clock <名字> -max <值> [get_ports ...]

# 4. 异步组
set_clock_groups -asynchronous -group <FPGA时钟> -group <虚拟时钟>

总结

虚拟时钟让工具知道外面世界的钟长什么样。它不改变任何物理走线,但让 input_delay 和 output_delay 的值有物理依据,从而间接引导布局布线的松紧分配。