2、时序基础理论:建立时间与保持时间、时钟抖动与时钟偏移、时序裕量计算、同步电路时序约束
各位同学,咱们今天聊点硬核的。时序分析,说白了就是数字芯片的命根子。我见过太多设计,功能仿真跑得飞起,一上板子就歇菜,十有八九是时序出了问题。这一章,咱们把时序的底裤扒干净。
2.1 建立时间与保持时间:寄存器的脾气
每个寄存器都有它的脾气。你给它喂数据,它得有个准备过程。这个准备过程,就是建立时间和保持时间。
- 建立时间(\( t_{su} \)):时钟有效沿到来之前,数据必须稳定住的最短时间。
- 保持时间(\( t_h \)):时钟有效沿到来之后,数据必须继续稳定的最短时间。
我打个比方。你坐公交车,车门关上的前一秒你得站好位置,这就是建立时间。车门关上后,你不能马上跳下去,得站稳了,这就是保持时间。寄存器也一样,数据得在时钟沿前后都老老实实的。
2.2 时钟抖动与时钟偏移:理想很丰满,现实很骨感
理想时钟是完美的方波,但现实中的时钟,总有点小毛病。两个最常见的毛病:抖动和偏移。
2.2.1 时钟抖动(Jitter)
抖动,就是时钟沿的位置在时间轴上随机晃动。说白了,时钟沿不是固定的,它有点「哆嗦」。抖动主要来自电源噪声、热噪声、PLL的相位噪声。
抖动分为两种:
- 周期抖动:相邻两个时钟周期的差值。
- 累积抖动:多个周期后,时钟沿偏离理想位置的总量。
我个人习惯,在高速设计中(比如DDR5、PCIe Gen5),会把抖动预算留足。你想想看,如果时钟抖了,建立时间和保持时间的窗口就被压缩了,时序裕量直接缩水。
2.2.2 时钟偏移(Skew)
偏移,是同一个时钟源到达不同寄存器的路径长度不同,导致的相位差。比如时钟从PLL出来,走到A寄存器用了1ns,走到B寄存器用了1.2ns,那A和B之间就有0.2ns的偏移。
偏移不一定是坏事。有时候我们故意做「有用偏移」,让时钟早到一点或晚到一点,来优化时序。但大多数时候,偏移是敌人。
2.3 时序裕量计算:算清楚,别含糊
时序裕量,就是你的设计到底还有多少余量。公式很简单,但用起来有讲究。
先看建立时间裕量:
Setup Slack = T_clk - t_co - t_logic - t_net - t_su - t_jitter - t_skew
再看保持时间裕量:
Hold Slack = t_co + t_logic + t_net - t_h - t_jitter - t_skew
其中:
- T_clk:时钟周期
- t_co:寄存器时钟到输出的延迟
- t_logic:组合逻辑延迟
- t_net:连线延迟
- t_su:建立时间
- t_h:保持时间
- t_jitter:时钟抖动(通常取峰峰值)
- t_skew:时钟偏移
嗯,这里要注意。建立时间裕量如果为负,说明你的组合逻辑太长了,得插流水线。保持时间裕量如果为负,说明数据跑得太快了,得加缓冲器。
2.4 同步电路时序约束:告诉工具你的要求
时序约束,就是告诉EDA工具:「嘿,我的时钟跑多快,哪些路径是关键路径,你给我优化好。」
最常用的约束是SDC(Synopsys Design Constraints)格式。我列几个必用的:
# 创建时钟
create_clock -name clk -period 10.0 [get_ports clk]
# 设置输入延迟
set_input_delay -clock clk -max 2.0 [get_ports data_in]
set_input_delay -clock clk -min 0.5 [get_ports data_in]
# 设置输出延迟
set_output_delay -clock clk -max 3.0 [get_ports data_out]
set_output_delay -clock clk -min 1.0 [get_ports data_out]
# 设置伪路径(不需要时序检查的路径)
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]
# 设置多周期路径
set_multicycle_path -setup 2 -from [get_pins reg_a/Q] -to [get_pins reg_b/D]
你想想看,如果不设约束,工具会默认所有路径都要在一个时钟周期内完成。但有些路径,比如跨时钟域的同步器,根本不需要时序检查。你设了伪路径,工具就不会浪费资源去优化它。
我个人习惯,在项目初期就把约束写好。不要等到综合完了再补约束,那时候改起来很痛苦。我记得有一次,一个同事把时钟周期写错了,写成了20ns,结果综合出来的电路跑不了200MHz,只能跑100MHz。后来重做,浪费了两周。
- ✅ 所有时钟都定义了create_clock
- ✅ 输入输出延迟都设了
- ✅ 跨时钟域路径设了false_path或sync
- ✅ 多周期路径设了set_multicycle_path
- ✅ 时钟抖动和偏移在约束中考虑了
2.5 实战案例:一个简单的同步电路
咱们来个实际的。假设有一个数据通路,时钟频率100MHz(周期10ns),寄存器t_co=0.5ns,组合逻辑t_logic=4ns,连线t_net=0.5ns,建立时间t_su=0.2ns,保持时间t_h=0.1ns,抖动t_jitter=0.3ns,偏移t_skew=0.2ns。
算一下建立时间裕量:
Setup Slack = 10 - 0.5 - 4 - 0.5 - 0.2 - 0.3 - 0.2 = 4.3ns
算一下保持时间裕量:
Hold Slack = 0.5 + 4 + 0.5 - 0.1 - 0.3 - 0.2 = 4.4ns
两个都是正数,说明时序没问题。但如果我把组合逻辑改成8ns,建立时间裕量就变成负的了。这时候就得插一级流水线,把组合逻辑拆成两段。
好了,这一章的内容就到这儿。时序基础是芯片设计的根基,你把它搞透了,后面的跨时钟域、STA、DDR接口什么的,学起来就轻松多了。下一章咱们聊跨时钟域同步,那个坑更多,我到时候给你们讲讲我当年是怎么被异步FIFO折磨的。