时序分析不是背公式,是理解数据在芯片里怎么跑、会在哪卡住。公式只是工具,真正要掌握的是为什么会违例、怎么定位、如何解决


一、时序分析的本质认知

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变差

工程经验:

  1. 先收Hold,再收Setup - 因为Hold和频率无关,加buffer就能解决
  2. Setup收不了降频即可,但Hold违例降频也没用
  3. 时钟偏斜尽量控制在±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时序"。

2) Input/Output Delay缺失

场景: 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. 起点和终点在哪个模块?
  2. 中间经过了多少级逻辑?
  3. 是时钟问题还是数据路径问题?

解决方法(按优先级)

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 上板调试时序问题

现象: 仿真过,时序报告过,但上板挂了

可能原因:

  1. 温度/电压变化
解决: 查器件手册,看不同corner下的延迟变化  
     在约束里增加margin: set_clock_uncertainty多加0.2ns  
  1. 器件差异
解决: 多测几片板子,看是个例还是普遍问题  
     如果是个例,可能是芯片速度等级(speed grade)不一致  
  1. 电源噪声
解决: 示波器测时钟/电源的波形质量  
     增加去耦电容,检查PCB电源平面  
  1. CDC问题
解决: 用ILA抓信号,看跨时钟域的采样是否稳定  
     检查所有CDC路径是否都加了同步器  
  1. 约束遗漏
解决: 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  

优点:

  • 大项目编译快
  • 模块可以并行开发
  • 已收敛的模块不用重复优化

缺点:

  • 模块间接口时序可能有gap
  • 需要精确的接口约束

八、总结

时序分析的核心能力三层:

L1 - 理解公式: 知道Setup/Hold是什么,会套公式
L2 - 会用工具: 能看懂时序报告,知道怎么加约束
L3 - 工程思维: 理解为什么会违例,知道如何权衡和优化

从L2到L3的跨越,需要踩过足够多的坑。

最后的建议:

  1. 约束写在设计之前,不是最后补救
  2. 时序问题尽早暴露,越晚改代价越大
  3. 工具说过了不算过,板子跑稳了才算过
  4. 时序裕量是用钱买的,不要抠得太紧
  5. 文档记录踩过的坑,下次别再跳

什么时候可以说掌握了时序分析?
不是你能把所有路径都收到WNS>0,而是你能快速判断哪些违例可以忽略、哪些必须修复、怎么用最小代价解决问题。

这需要时间,也需要项目历练。共勉。