时序约束基础:为你的卷积加速器定好“规矩”
各位同学,大家好。今天我们来聊聊时序约束的基础。说白了,这就是给芯片设计定规矩——告诉工具你的时钟长什么样,数据什么时候来,什么时候走。
我刚开始做卷积加速器的时候,觉得约束嘛,随便写写就行。结果呢?后仿时序一团糟,修都修不过来。后来我才明白,约束写得好,后端少烦恼。
核心观点:时序约束不是应付工具的,而是你和工具沟通的语言。你写清楚,工具才能帮你把路走通。
1. 创建时钟:一切时序的起点
时钟是数字电路的“心跳”。没有时钟,所有寄存器都不知道什么时候该干活。
创建时钟的语法很简单,但坑不少。我常用的命令是:
create_clock -name clk_sys -period 10.0 [get_ports clk_in]
这里我指定了时钟名字叫 clk_sys,周期10ns,也就是100MHz。端口是 clk_in。
注意:时钟名字别乱起。我见过有人起名叫 aaa、bbb,结果查时序报告时根本分不清哪个是哪个。建议用 clk_模块名_频率 的格式,比如 clk_conv_200m。
我的习惯:创建时钟时,我会同时加上 -waveform 参数,明确指定上升沿和下降沿的位置。虽然默认是50%占空比,但显式写出来更安全。
还有一个容易忽略的点——时钟源。如果你的时钟来自PLL,那就要在PLL输出端创建时钟,而不是在输入端口。我曾经在项目里搞混了,导致时钟树综合时多了一级buffer,白白浪费了功耗。
2. 生成时钟:分频、倍频的那些事儿
卷积加速器里经常需要不同频率的时钟。比如核心计算用200MHz,控制逻辑用100MHz。这时候就要用到 generate_clock。
generate_clock -name clk_div2 -divide_by 2 -source [get_clocks clk_sys] [get_pins divider_reg/Q]
这条命令的意思是:从 divider_reg/Q 这个引脚输出一个时钟,频率是 clk_sys 的一半。
关键点:生成时钟的 -source 一定要指定对。我见过有人写成 -source [get_ports clk_in],结果工具把分频器和源时钟当成异步处理,时序分析全乱了。
我曾经踩过的坑:有一次我生成了一个2分频时钟,但忘记指定 -master_clock。结果工具自动把分频器和源时钟关联起来,导致跨时钟域路径被错误地当作同步路径分析。修了整整两天才找到原因。
对于卷积加速器,我建议把所有生成时钟都显式定义出来。哪怕你觉得工具能自动推断,也最好写清楚。这样后期查问题会省很多时间。
3. 输入延迟约束:数据从外面来,你得告诉工具
数据从芯片外部进来时,相对于时钟边沿有一个延迟。这个延迟包括PCB走线、片外驱动器的输出延迟等。你不告诉工具,工具就默认数据在时钟沿处变化——这显然不现实。
set_input_delay -clock clk_sys -max 2.5 [get_ports data_in]
这里我设置了 data_in 的最大输入延迟是2.5ns。意思是数据到达芯片引脚的时间,最晚不会超过时钟沿后2.5ns。
为什么要有最大和最小? 最大延迟用于建立时间检查,最小延迟用于保持时间检查。你想想看,数据可能早到也可能晚到,工具得把两种情况都考虑到。
实战经验:在卷积加速器项目中,输入延迟通常来自前一级的DDR或SRAM。我一般会先拿到前级芯片的数据手册,找到它的输出延迟参数,然后加上PCB走线延迟,再留一点余量。这样算出来的输入延迟比较靠谱。
还有一个细节:输入延迟是相对于哪个时钟边沿? 默认是上升沿。如果你的数据在下降沿变化,记得加 -clock_fall 选项。
4. 输出延迟约束:数据送出去,也要说清楚
输出延迟和输入延迟是对称的。它告诉工具:数据从芯片输出后,外部器件需要多长时间才能稳定采样。
set_output_delay -clock clk_sys -max 3.0 [get_ports data_out]
这条命令的意思是:data_out 的数据必须在时钟沿前3.0ns就稳定下来,留给外部器件足够的建立时间。
容易搞混的地方:输出延迟的数值越大,对内部逻辑的要求越严格。因为你要更早地把数据准备好。我刚开始做时总搞反,以为数值大是放松约束,结果时序一直不满足。
我的建议:写输出约束时,先画一个时序图。把时钟沿、数据有效窗口、外部器件的建立/保持时间都标出来。然后根据图来算延迟值。这样不容易出错。
对于卷积加速器,输出通常接DDR或FIFO。这些器件的时序要求比较严格,我一般会留10%~20%的余量,防止PVT变化导致时序失效。
5. 时序例外:打破常规,给工具指条明路
不是所有路径都需要严格的时序检查。比如跨时钟域的同步器路径、测试模式下的路径、或者一些不关心的逻辑。这时候就要用到时序例外。
常用的例外命令有:
- set_false_path:完全忽略这条路径。比如异步FIFO的跨时钟域路径。
- set_multicycle_path:允许数据在多个时钟周期内传输。比如一些低速控制信号。
- set_max_delay / set_min_delay:自定义延迟约束,覆盖默认的时钟周期约束。
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
set_multicycle_path -setup 2 -from [get_pins slow_logic_reg/CK] -to [get_pins slow_logic_reg2/D]
注意:时序例外是双刃剑。用得好,可以大幅减少不必要的时序优化;用得不好,可能漏掉真正的时序问题。
我曾经犯过的错:有一次为了赶进度,我把一条路径设成了false_path,觉得它不重要。结果流片回来,那条路径在特定条件下确实出错了。从那以后,我给自己定了个规矩:除非100%确定,否则不要轻易设false_path。
对于卷积加速器,常见的时序例外场景包括:
- 配置寄存器路径:通常用multicycle_path,因为配置更新很慢
- 测试模式下的扫描链路径:设false_path
- 异步复位信号:设false_path,但要保证复位同步
知识体系总览
下面这张图总结了时序约束的核心逻辑。你可以看到,所有约束最终都服务于一个目标:让工具知道数据什么时候该到,什么时候该走。
这张图把五个核心知识点串起来了。你可以看到,创建时钟和生成时钟是基础,输入/输出延迟约束是接口,时序例外是补充。它们共同构成了一个完整的约束体系。
最后说一句:时序约束不是写一次就完事的。随着设计迭代,约束也要不断调整。我每次跑完STA都会回头看看约束有没有遗漏或错误。养成这个习惯,你的芯片成功率会高很多。
好了,这一章就到这里。记住:约束写得好,加班少一半。下一章我们聊聊具体的时序分析报告怎么看,到时候我会拿一个真实的卷积加速器案例来拆解。
可可精品资料库