时序分析不是背公式,是理解数据在芯片里怎么跑、会在哪卡住。公式只是工具,真正要掌握的是为什么会违例、怎么定位、如何解决。
一、时序分析的本质认知#
1.1 不只是建立/保持时间#
教科书会告诉你:
Setup: T_slack = T_period + T_skew - T_co - T_dp - T_setup
Hold: T_slack = T_co + T_dp - T_skew - T_hold
但实际工程中,这些公式里的每一项都可能出问题:
T_period - 你约束的是100MHz,但PLL输出真的准确吗?Jitter多少?
T_skew - 时钟树平衡了吗?高扇出节点的偏斜有多大?
T_co - 寄存器到输出延迟,温度电压变化会不会影响?
T_dp - 组合逻辑延迟,最坏情况(slow corner)下是多少?
T_setup/T_hold - 器件手册给的值已经包含了裕量吗?
核心认知: 时序分析不是验证理想情况,是验证最坏情况下的最差路径能否工作。
1.2 Setup和Hold的矛盾统一#
- Setup关注数据能否及时到达 - 要求路径快(频率高了会违例)
- Hold关注数据是否保持足够久 - 要求路径慢(路径太快会违例)
这就导致了经典矛盾:
- 提高频率 → Setup变差
- 加Buffer增加延迟 → 解决Hold但Setup更差
- 时钟正偏斜 → Setup变好但Hold变差
- 时钟负偏斜 → Hold变好但Setup变差
工程经验:
- 先收Hold,再收Setup - 因为Hold和频率无关,加buffer就能解决
- Setup收不了降频即可,但Hold违例降频也没用
- 时钟偏斜尽量控制在±50ps以内,避免顾此失彼
二、完整的约束体系#
2.1 为什么约束不全比没约束更危险#
没约束: 工具报错,你知道有问题
约束不全: 工具说过了,上板挂了,你找不到原因
常见的约束缺失:
1) 虚拟时钟缺失#
场景: RGMII/GMII接口,PHY芯片提供TX_CLK给FPGA
1
2
3
4
5
6
7
| # 错误做法:直接用FPGA内部时钟约束IO
create_clock -period 8.0 [get_ports rgmii_txc]
# 正确做法:创建虚拟时钟
create_clock -period 8.0 -name rgmii_txc_virtual
set_output_delay -clock rgmii_txc_virtual -max 2.0 [get_ports rgmii_txd[*]]
set_output_delay -clock rgmii_txc_virtual -min -0.8 [get_ports rgmii_txd[*]]
|
为什么: 因为PHY芯片的时钟和FPGA内部时钟不是同一个源,工具无法分析。虚拟时钟相当于告诉工具"假设有个时钟源在外面,按这个标准分析IO时序"。
场景: ADC/DAC接口,外部器件有Tco和Tsu
1
2
3
4
5
6
7
| # 必须设置input delay
set_input_delay -clock adc_clk -max 3.5 [get_ports adc_data[*]]
set_input_delay -clock adc_clk -min 1.2 [get_ports adc_data[*]]
# 必须设置output delay
set_output_delay -clock dac_clk -max 2.0 [get_ports dac_data[*]]
set_output_delay -clock dac_clk -min -0.5 [get_ports dac_data[*]]
|
为什么: 工具默认认为外部器件的Tco=0、Tsu=0,即无限快。不设置delay,相当于告诉工具"IO路径不需要分析时序",这显然是错的。
如何确定delay值:
- 查外部器件datasheet的Tco(max/min)和Tsu/Th
- Input delay = 外部器件Tco
- Output delay = 外部器件Tsu
- 记得考虑PCB走线延迟(通常0.1-0.5ns)
3) 跨时钟域路径未标记#
场景: 两个异步时钟域之间的信号
1
2
3
4
5
| # 必须设置false path
set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_200m]
# 或者针对特定信号
set_false_path -from [get_pins sync_reg*/C] -to [get_pins async_reg*/D]
|
为什么: 跨时钟域的路径本身就不满足时序要求(需要用双拍同步等方式处理),强行分析会报一堆违例。但不标记false path,工具会拼命优化这些路径,可能破坏双拍同步的结构。
4) 多周期路径#
场景: FIFO控制逻辑、除法器、状态机
1
2
3
| # 2个周期完成的路径
set_multicycle_path 2 -setup -from [get_pins div_start_reg/C] -to [get_pins div_done_reg/D]
set_multicycle_path 1 -hold -from [get_pins div_start_reg/C] -to [get_pins div_done_reg/D]
|
注意: Hold约束是Setup周期数-1,否则会hold违例
2.2 时钟约束的细节#
衍生时钟(Generated Clock)#
1
2
3
4
5
| # PLL输出的时钟必须用create_generated_clock
create_generated_clock -name clk_200m \
-source [get_pins pll_inst/CLKIN] \
-multiply_by 2 \
[get_pins pll_inst/CLKOUT0]
|
常见错误: 直接create_clock而不是create_generated_clock,导致时钟关系丢失
时钟不确定性(Uncertainty)#
1
2
3
| # 考虑Jitter和时钟树偏斜
set_clock_uncertainty -setup 0.2 [get_clocks sys_clk]
set_clock_uncertainty -hold 0.1 [get_clocks sys_clk]
|
如何设置:
- Setup uncertainty = PLL jitter + 时钟树偏斜 + 裕量(通常0.1-0.3ns)
- Hold uncertainty = 时钟树偏斜(通常0.05-0.15ns)
- 过大会让Setup难收,过小会低估风险
三、实战问题诊断#
3.1 Setup违例的解决思路#
问题定位#
1
2
3
4
5
| # Vivado中查看最差路径
report_timing -max_paths 100 -slack_less_than 0 -sort_by slack
# 看哪个模块违例最多
report_timing_summary
|
分析路径:
- 起点和终点在哪个模块?
- 中间经过了多少级逻辑?
- 是时钟问题还是数据路径问题?
解决方法(按优先级)#
1) 添加流水线
1
2
3
4
5
6
| // 违例路径: a -> 20级组合逻辑 -> b
// 拆分成两级
always @(posedge clk) begin
mid_reg <= 组合逻辑前半段;
b <= 组合逻辑后半段(mid_reg);
end
|
代价: 增加1周期延迟,多用寄存器资源
2) 优化组合逻辑
- 减少逻辑层级(LUT合并)
- 用DSP替代纯逻辑乘法器
- 用BRAM替代大型MUX
1
2
| // 64选1的MUX用BRAM替代
assign data_out = lut_ram[addr];
|
3) 时钟域优化
- 高速模块用快时钟
- 低速模块用慢时钟
- 不同频率的模块CDC处理
4) 约束调整(谨慎使用)
1
2
| # 某些不重要的路径可以放宽要求
set_multicycle_path 2 -setup -from ... -to ...
|
5) 降频
- 最后的手段,但很有效
- 200MHz降到150MHz,裕量瞬间多5ns
3.2 Hold违例的解决思路#
- 和时钟频率无关,降频没用
- 通常是时钟偏斜或路径太短导致
解决方法#
1) 增加数据路径延迟
1
2
| # Vivado会自动加Buffer,但也可以手动指定
set_property DELAY_VALUE 500 [get_cells path_delay_cell]
|
2) 平衡时钟树
1
2
| # 检查时钟偏斜
report_clock_networks -name clk_skew
|
如果某个时钟域偏斜过大(>200ps),需要:
- 调整时钟buffer位置
- 增加时钟buffer层级
- 使用BUFG/BUFH等全局资源
3) 物理约束
1
2
3
4
| # 让两个寄存器靠近,减少布线延迟
create_pblock pblock_critical
add_cells_to_pblock pblock_critical [get_cells critical_path/*]
resize_pblock pblock_critical -add {SLICE_X0Y0:SLICE_X10Y10}
|
3.3 CDC问题诊断#
跨时钟域问题在时序报告里看不出来,需要专门检查:
1
2
3
4
5
6
7
| # Vivado CDC检查
report_cdc -severity warning
# 常见CDC违例
# 1. 没有同步器
# 2. 用了单拍同步(应该双拍)
# 3. 多bit信号直接跨时钟域(应该用格雷码或握手)
|
标准CDC处理:
1
2
3
4
5
6
7
8
9
| // 双拍同步器
reg [1:0] sync_reg;
always @(posedge clk_dst) begin
sync_reg <= {sync_reg[0], signal_src};
end
assign signal_dst = sync_reg[1];
// 多bit信号用握手
// 或者用异步FIFO
|
四、工具使用技巧#
4.1 Vivado时序分析流程#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 先跑综合,看初步时序
synth_design -top top_module
report_timing_summary
# 2. 如果Setup违例严重(>1ns),先优化RTL再继续
# 3. 实现
place_design
route_design
# 4. 详细时序报告
report_timing_summary -delay_type min_max -max_paths 100 -file timing_summary.rpt
report_timing -sort_by slack -max_paths 100 -file timing_worst.rpt
# 5. 检查关键路径
report_design_analysis -timing -max_paths 100
|
4.2 时序报告的阅读#
重点看这几项:
- WNS (Worst Negative Slack): 最差路径的裕量,必须>0
- TNS (Total Negative Slack): 所有负裕量之和,体现违例严重程度
- WHS (Worst Hold Slack): Hold最差裕量
- WPWS (Worst Pulse Width Slack): 时钟占空比问题
经验值:
- WNS > 0.5ns: 安全,有裕量应对PVT变化
- 0 < WNS < 0.5ns: 勉强过,需要测试验证
- WNS < 0: 不可接受,必须修复
4.3 增量编译技巧#
Setup时序收敛很花时间,可以用增量编译加速:
1
2
3
4
5
6
7
| # 第一次编译后,保存布局布线结果
write_checkpoint -force design_routed.dcp
# 修改RTL后,复用之前的布局
read_checkpoint design_routed.dcp
lock_design -level routing
# 然后重新综合实现
|
这样可以保留已经收敛的部分,只重新优化修改的模块。
五、典型场景的约束模板#
5.1 RGMII接口约束#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 输入时钟(PHY提供)
create_clock -period 8.0 -name rgmii_rxc [get_ports rgmii_rxc]
# 2. 输入数据(考虑PHY的Tco)
set_input_delay -clock rgmii_rxc -max 2.0 [get_ports {rgmii_rxd[*] rgmii_rx_ctl}]
set_input_delay -clock rgmii_rxc -min -0.8 [get_ports {rgmii_rxd[*] rgmii_rx_ctl}]
# 3. 输出时钟(FPGA生成给PHY)
create_generated_clock -name rgmii_txc_gen \
-source [get_pins rgmii_clk_gen/clk_in] \
-divide_by 1 \
[get_ports rgmii_txc]
# 4. 输出数据(考虑PHY的Tsu/Th)
set_output_delay -clock rgmii_txc_gen -max 2.0 [get_ports {rgmii_txd[*] rgmii_tx_ctl}]
set_output_delay -clock rgmii_txc_gen -min -0.8 [get_ports {rgmii_txd[*] rgmii_tx_ctl}]
# 5. RX和TX时钟异步
set_clock_groups -asynchronous -group [get_clocks rgmii_rxc] -group [get_clocks rgmii_txc_gen]
|
5.2 DDR3接口约束#
1
2
3
4
5
6
7
8
9
10
| # DDR3通常用MIG生成,约束在example design里
# 关键点:
# 1. DQS差分对必须在同一个Bank
# 2. 地址/命令信号走同一个时钟域
# 3. 读数据路径有calibration,约束宽松
# 4. 写数据路径紧张,需要精确对齐
# MIG会自动生成,手动调整时注意:
set_output_delay -clock ddr_ck -max 0.25 [get_ports {ddr3_addr[*]}]
set_output_delay -clock ddr_ck -min -0.25 [get_ports {ddr3_addr[*]}]
|
5.3 多时钟域系统约束#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 定义所有时钟
create_clock -period 10.0 [get_ports clk_100m]
create_clock -period 5.0 [get_ports clk_200m]
create_clock -period 20.0 [get_ports clk_50m]
# 2. PLL输出的衍生时钟
create_generated_clock -name clk_250m \
-source [get_pins pll/CLKIN] \
-multiply_by 2.5 \
[get_pins pll/CLKOUT0]
# 3. 定义异步时钟组
set_clock_groups -asynchronous \
-group [get_clocks clk_100m] \
-group [get_clocks clk_200m] \
-group [get_clocks clk_50m]
# 4. 同步时钟域(从同一个PLL出来的)
set_clock_groups -physically_exclusive \
-group [get_clocks clk_250m] \
-group [get_clocks clk_100m]
|
六、调试方法论#
6.1 时序违例的优先级#
不是所有违例都要立刻修复,按优先级处理:
P0 - 必须立刻修复:
- Hold违例(任何一条)
- Setup WNS < -0.5ns
- 关键路径违例(复位、时钟树、IO接口)
P1 - 尽快修复:
- Setup WNS < 0
- TNS < -10ns
- 脉冲宽度违例
P2 - 优化项:
- Setup 0 < WNS < 0.5ns
- 非关键路径的轻微违例
6.2 上板调试时序问题#
现象: 仿真过,时序报告过,但上板挂了
可能原因:
- 温度/电压变化
解决: 查器件手册,看不同corner下的延迟变化
在约束里增加margin: set_clock_uncertainty多加0.2ns
- 器件差异
解决: 多测几片板子,看是个例还是普遍问题
如果是个例,可能是芯片速度等级(speed grade)不一致
- 电源噪声
解决: 示波器测时钟/电源的波形质量
增加去耦电容,检查PCB电源平面
- CDC问题
解决: 用ILA抓信号,看跨时钟域的采样是否稳定
检查所有CDC路径是否都加了同步器
- 约束遗漏
解决: report_cdc检查CDC
report_methodology检查设计规则
确保所有时钟都有约束
6.3 经验法则#
1. 时序裕量留够:
- Setup: WNS > 0.5ns (高速接口建议>1ns)
- Hold: WHS > 0.2ns
- 关键路径: 裕量>1ns
2. 时钟质量优先:
- 宁可组合逻辑慢点,时钟树必须干净
- BUFG资源有限,优先给高速时钟
- 不要让组合逻辑生成时钟(除非必须)
3. 分模块收敛:
- 大系统一次收敛困难,先分模块单独验证
- 用Pblock做物理隔离,避免模块间相互影响
- 接口部分(CDC、IO)最后整合调试
4. 工具可信但不绝对:
- 时序报告说过了,不代表板子一定能跑
- 留足裕量应对PVT变化
- 关键项目要多板子、多批次验证
七、进阶话题#
7.1 PVT Corner分析#
工艺(Process)、电压(Voltage)、温度(Temperature)会影响延迟:
- Fast Corner: 工艺快+高电压+低温 → 延迟小 → Hold容易违例
- Slow Corner: 工艺慢+低电压+高温 → 延迟大 → Setup容易违例
工程实践:
- 默认按Slow corner做Setup分析
- 按Fast corner做Hold分析
- 商业级芯片: 0-85°C,通常按85°C分析
- 工业级芯片: -40-100°C,裕量要更大
7.2 时钟域交叉(CDC)的完整处理#
CDC不只是双拍同步,还要考虑:
1. 单bit信号:
1
2
3
| // 标准双拍
reg [2:0] sync;
always @(posedge clk_dst) sync <= {sync[1:0], sig_src};
|
2. 多bit信号(慢到快):
1
2
3
4
5
| // 格雷码
gray_code <= binary_to_gray(counter);
// 目的时钟域双拍同步
sync_gray <= sync(gray_code);
counter_dst <= gray_to_binary(sync_gray);
|
3. 多bit信号(快到慢):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 握手协议
// 源时钟域
always @(posedge clk_src) begin
if (valid && !ack_sync) begin
data_reg <= data;
req <= 1;
end else if (ack_sync) begin
req <= 0;
end
end
// 目的时钟域
always @(posedge clk_dst) begin
req_sync <= sync(req);
if (req_sync && !req_sync_d1) begin
data_dst <= data_reg;
ack <= 1;
end else begin
ack <= 0;
end
end
|
4. 突发数据:
1
2
3
4
5
6
7
| // 异步FIFO
async_fifo_inst (
.wr_clk(clk_src),
.rd_clk(clk_dst),
.din(data_in),
.dout(data_out)
);
|
7.3 OOC(Out-of-Context)综合#
大项目模块太多,可以分模块综合:
1
2
3
4
5
6
7
8
9
10
11
| # 模块级综合
synth_design -mode out_of_context -top sub_module
# 保存结果
write_checkpoint sub_module_ooc.dcp
# 顶层链接
link_design -top top_module -part xcku040 \
-reconfig_partitions {sub_module_i}
read_checkpoint -cell sub_module_i sub_module_ooc.dcp
|
优点:
- 大项目编译快
- 模块可以并行开发
- 已收敛的模块不用重复优化
缺点:
八、总结#
时序分析的核心能力三层:
L1 - 理解公式: 知道Setup/Hold是什么,会套公式
L2 - 会用工具: 能看懂时序报告,知道怎么加约束
L3 - 工程思维: 理解为什么会违例,知道如何权衡和优化
从L2到L3的跨越,需要踩过足够多的坑。
最后的建议:
- 约束写在设计之前,不是最后补救
- 时序问题尽早暴露,越晚改代价越大
- 工具说过了不算过,板子跑稳了才算过
- 时序裕量是用钱买的,不要抠得太紧
- 文档记录踩过的坑,下次别再跳
什么时候可以说掌握了时序分析?
不是你能把所有路径都收到WNS>0,而是你能快速判断哪些违例可以忽略、哪些必须修复、怎么用最小代价解决问题。
这需要时间,也需要项目历练。共勉。