图像加速中的时序收敛 - 第4节
4.1 什么是时序约束?
时序约束,说白了就是告诉工具:你的设计要在多快的速度下工作。
我刚开始做FPGA那会儿,觉得这玩意儿可有可无。反正代码能跑就行,管它什么约束不约束的。直到有一次,一个图像处理的项目,板子调通了,结果上电跑起来画面时不时闪一下。查了三天,最后发现是时钟路径没约束好,数据跑飞了。嗯,从那以后,我再也不敢不写约束了。
时序约束的核心作用,就是让综合工具和布局布线工具知道你的设计目标。没有约束,工具就不知道往哪个方向优化。就像你让一个人去跑步,不告诉他跑多远、跑多快,他只能瞎跑。
时序约束的本质:定义时钟、定义输入输出延迟、定义路径的时序要求。工具根据这些要求去检查设计是否满足。
在数字芯片设计和FPGA开发中,时序约束文件通常以SDC(Synopsys Design Constraints)格式存在。这是业界标准,几乎所有主流工具都支持。
4.2 SDC文件的基本语法
SDC文件其实就是一个文本文件,里面写的是约束命令。每条命令以分号结尾,支持注释(用#号)。
我个人习惯把SDC文件分成几个部分:时钟约束、输入延迟约束、输出延迟约束、例外路径。这样结构清晰,后期维护也方便。
来看一个最简单的SDC文件长什么样:
# 这是一个简单的SDC文件示例
create_clock -name sys_clk -period 10.000 [get_ports clk]
set_input_delay -clock sys_clk -max 2.5 [get_ports data_in]
set_output_delay -clock sys_clk -max 3.0 [get_ports data_out]
是不是很简单?就三行。但就是这三行,决定了你的设计能不能稳定工作。
小技巧:我建议每条约束命令都加上注释,说明为什么要这么设。三个月后你自己回来看,感谢现在的自己。
4.3 创建时钟约束(create_clock)
时钟是时序分析的基准。没有时钟,时序分析就没法做。
create_clock命令用来定义一个时钟。最基本的语法是:
create_clock -name <时钟名> -period <周期> [get_ports <端口名>]
举个例子:
create_clock -name clk_main -period 10.000 [get_ports clk]
这条命令的意思是:在端口clk上定义一个名为clk_main的时钟,周期是10ns,也就是100MHz。
你可能会问:为什么周期要写成10.000而不是10?
嗯,这里有个细节。工具在处理时序时,精度很重要。写成10.000,表示精度到皮秒级。虽然大多数情况下10和10.000没区别,但养成好习惯总没错。
除了基本参数,create_clock还支持定义占空比、相位偏移等。比如:
# 定义50%占空比的时钟
create_clock -name clk_50 -period 20.000 -waveform {0 10} [get_ports clk_50]
# 定义相位偏移90度的时钟
create_clock -name clk_90 -period 10.000 -waveform {2.5 7.5} [get_ports clk_90]
我在项目中遇到过一种情况:芯片外部输入的时钟是200MHz,但内部需要用到100MHz和50MHz。这时候就需要用PLL或者MMCM来生成,然后在SDC里分别定义这些时钟。记住,每个时钟都要有对应的约束。
注意:同一个端口上只能定义一个主时钟。如果你在同一个端口上定义了多个时钟,工具会报错。需要用到多时钟的场景,请使用create_generated_clock。
4.4 创建输入延迟约束(set_input_delay)
输入延迟约束,描述的是数据从外部芯片到达FPGA引脚的时间。
你想想看,数据从外部芯片发出,经过PCB走线,再到FPGA的输入引脚,这中间是有延迟的。如果不告诉工具这个延迟,工具就不知道数据什么时候到达,也就没法判断时序是否满足。
set_input_delay的基本语法:
set_input_delay -clock <时钟名> -max <最大延迟> [get_ports <端口列表>]
set_input_delay -clock <时钟名> -min <最小延迟> [get_ports <端口列表>]
举个例子:
set_input_delay -clock clk_main -max 2.5 [get_ports data_in]
set_input_delay -clock clk_main -min 0.5 [get_ports data_in]
这里-max表示最大延迟,-min表示最小延迟。为什么要分最大和最小?
因为实际电路中,延迟不是固定的。温度变化、电压波动、工艺偏差都会影响延迟。所以我们要给出一个范围,工具在这个范围内做最坏情况分析。
我曾经在一个项目中,只设了-max没设-min,结果工具默认-min为0。实际板子上电后,数据保持时间不够,导致采样错误。从那以后,我每次都会把-max和-min都写上。
输入延迟的值怎么算?一般来说,它等于:
- 外部芯片的时钟到输出延迟(Tco)
- 加上PCB走线延迟
- 减去时钟偏斜(如果有)
看一个实际的计算例子:
| 参数 | 最小值(ns) | 最大值(ns) |
|---|---|---|
| 外部芯片Tco | 1.0 | 3.0 |
| PCB走线延迟 | 0.2 | 0.5 |
| 时钟偏斜 | -0.1 | 0.1 |
| 输入延迟 | 1.1 | 3.6 |
所以约束应该写成:
set_input_delay -clock clk_main -min 1.1 [get_ports data_in]
set_input_delay -clock clk_main -max 3.6 [get_ports data_in]
避坑指南:我曾经见过有人把输入延迟设得特别大,比如设成周期的一半。这样做虽然时序容易收敛,但实际上是过度约束,会导致面积和功耗增加。正确的做法是根据实际电路计算,不要拍脑袋。
4.5 本章知识体系
下面这张图总结了本章的核心内容,帮你理清思路:
这张图把本章的知识点串起来了。核心就是三个东西:理解时序约束是什么、掌握SDC语法、会用create_clock和set_input_delay。后面的章节会继续讲输出延迟、例外路径等内容,但基础就是这些。
记住一句话:时序约束不是写给工具看的,是写给你自己看的。约束写得好,后期调试少烦恼。