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_delay 和 set_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 的值有物理依据,从而间接引导布局布线的松紧分配。