第二章 时序约束入门:创建时钟、生成时钟、输入延迟、输出延迟、伪路径与多周期路径
好,咱们正式开始聊时序约束。很多刚入行的朋友觉得这玩意儿就是写几行SDC,没什么技术含量。说实话,我当年也这么想。直到有一次,我负责一个高速接口模块,因为少写了一条生成时钟的约束,导致整个芯片在高温下跑不过测试——嗯,从那以后,我再也不敢小看时序约束了。
时序约束,说白了就是告诉工具:你的设计要在什么频率下工作,数据什么时候来,什么时候走,哪些路径可以放宽要求。没有约束,STA就是瞎分析。今天咱们就把最核心的六种约束讲透。
2.1 创建时钟(create_clock)
这是最基础的约束。你想想看,芯片里所有时序逻辑都靠时钟驱动。没有时钟定义,工具根本不知道你要跑多快。
语法很简单:
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这条命令的意思是:在端口clk_in上创建一个名为clk_sys的时钟,周期10ns,也就是100MHz。
我个人习惯,创建时钟时一定要加-name。为什么?因为后续所有约束都要引用时钟名,如果没名字,工具会自动生成一个,调试时你根本找不到。我在项目中遇到过好几次,同事没写-name,结果查约束时满屏的clk_1、clk_2,完全分不清哪个是哪个。
另外,时钟源可以是端口,也可以是内部节点。比如PLL输出:
create_clock -name clk_pll -period 5.0 [get_pins u_pll/clk_out]
这里要注意,如果时钟源是内部节点,工具会自动传播时钟波形。但如果你手动在端口上也创建了时钟,就会产生冲突。我曾经踩过这个坑——在PLL输入和输出同时定义了时钟,结果STA报告里全是奇怪的违例,查了两天才发现是时钟定义重复了。
2.2 生成时钟(generate_clock)
生成时钟,说白了就是由已有时钟派生出来的时钟。比如分频、倍频、门控时钟。这些时钟不能直接用create_clock,要用generate_clock。
看个例子:
generate_clock -name clk_div2 -divide_by 2 -source [get_clocks clk_sys] [get_pins u_div/q]
这条命令的意思是:在u_div/q这个引脚上,从clk_sys分频得到clk_div2,分频系数是2。
这里有个关键点:-source指定的是源时钟,而不是源时钟的端口。很多新手写成-source [get_ports clk_in],结果工具报错说找不到时钟。嗯,这个错误我见过不下十次。
生成时钟还有几个常用参数:
- -multiply_by:倍频,比如PLL输出
- -duty_cycle:指定占空比,默认50%
- -invert:反相时钟,相当于180度相移
- -edges:更灵活地定义时钟边沿,用于非整数分频
我个人建议,生成时钟的名字最好能体现它的来源和功能。比如clk_sys_div2、clk_pll_200M,这样看约束文件时一目了然。
2.3 输入延迟(set_input_delay)
输入延迟描述的是:数据从外部芯片到达当前芯片输入端口时,相对于时钟边沿的时间关系。说白了,就是告诉工具:数据什么时候来。
语法:
set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]
这条命令的意思是:data_in这个端口的数据,相对于clk_sys的时钟边沿,最晚到达时间是2.5ns。
你想想看,为什么要有-max和-min?因为外部芯片的延迟不是固定的,有最大和最小。max用于建立时间分析,min用于保持时间分析。
set_input_delay = T_co(外部芯片时钟到输出延迟) + T_pcb(PCB走线延迟)
如果是源同步接口,还要考虑时钟偏斜。
我在项目中遇到过最头疼的问题,就是输入延迟设得太紧。有一次,一个DDR接口的输入延迟我设了1.8ns,结果STA报了一大堆建立时间违例。后来一查,外部芯片的T_co是2.0ns,PCB走线还有0.3ns,加起来2.3ns。我设1.8ns,相当于要求内部逻辑在-0.5ns内完成——这怎么可能?
所以,输入延迟一定要和系统设计人员确认,不要自己瞎猜。
2.4 输出延迟(set_output_delay)
输出延迟和输入延迟是对称的。它描述的是:数据从当前芯片输出后,到达外部芯片时,相对于时钟边沿的时间要求。
set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
这条命令的意思是:data_out的数据必须在时钟边沿前3.0ns到达外部芯片的输入引脚。
注意,输出延迟是外部芯片的建立时间要求,加上PCB走线延迟。公式:
set_output_delay = T_setup(外部芯片建立时间) + T_pcb(PCB走线延迟)
还有一个容易忽略的点:输出延迟的时钟参考。如果输出数据是DDR模式,要分别指定上升沿和下降沿的延迟。用-add_delay参数:
set_output_delay -clock clk_sys -max 2.0 -rise [get_ports ddr_out]
set_output_delay -clock clk_sys -max 2.5 -fall -add_delay [get_ports ddr_out]
2.5 伪路径(set_false_path)
伪路径,就是那些不需要做时序分析的路径。比如跨时钟域的同步器路径、测试模式下的路径、上电复位路径等。
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
这条命令的意思是:从clk_a到clk_b的所有路径都不做时序分析。
为什么要设伪路径?因为不设的话,工具会分析这些路径,报出一堆违例。而这些违例其实是假的,你花时间去修就是浪费时间。
常见的伪路径场景:
- 异步FIFO的读写指针比较路径
- 双触发器同步器的第一级触发器到第二级的路径
- 测试模式下的扫描链路径(通常用set_disable_timing更合适)
- 上电复位信号路径
我个人习惯,伪路径的注释一定要写清楚原因。比如:
# 跨时钟域同步器,不需要STA
set_false_path -from [get_clocks clk_100M] -to [get_clocks clk_200M]
这样以后别人看代码,或者你自己半年后回来看,都能明白为什么这么设。
2.6 多周期路径(set_multicycle_path)
多周期路径,就是那些不需要在一个时钟周期内完成的数据路径。比如,一个乘法器需要两个周期才能算出结果,那就可以设成多周期路径。
set_multicycle_path -setup 2 -from [get_pins u_mul/a] -to [get_pins u_mul/result]
这条命令的意思是:从u_mul/a到u_mul/result的路径,建立时间分析时放宽到2个周期。
注意,设了-setup 2之后,保持时间分析默认也会变成2个周期。但保持时间通常只需要1个周期就够了。所以,一般要再补一条:
set_multicycle_path -hold 1 -from [get_pins u_mul/a] -to [get_pins u_mul/result]
这个细节很多人会忽略。我刚开始做STA时也犯过这个错,设了多周期路径后,保持时间违例一大堆。后来才明白,保持时间默认会跟随建立时间的周期数。
- 设-setup N,建立时间放宽到N个周期
- 再设-hold N-1,保持时间回到1个周期
这是标准做法,几乎所有资深工程师都这么用。
多周期路径的典型应用场景:
- 慢速外设接口(比如I2C、SPI)
- 复杂的组合逻辑(比如乘法器、除法器)
- 状态机中的某些状态转换路径
- 数据使能信号控制的路径
嗯,说到这里,我想起一个项目。当时一个视频处理芯片,里面的像素计算路径需要3个周期。我设了set_multicycle_path -setup 3,但忘了设-hold。结果后仿时发现保持时间违例,导致数据采样错误。从那以后,我每次设多周期路径都会检查-hold的设置。
小结
今天讲的这六种约束,是时序约束的基石。创建时钟和生成时钟定义了时钟域,输入延迟和输出延迟描述了接口时序,伪路径和多周期路径则是对特殊路径的处理。我个人建议,初学者可以先从创建时钟和输入输出延迟入手,把这两个搞懂了,再学伪路径和多周期路径会容易很多。
下一章,咱们会讲如何用PrimeTime做基本的STA分析,包括如何读入设计、如何检查约束、如何看报告。到时候我会分享一些我常用的调试技巧,保证实用。