逻辑综合基础:从RTL到门级网表的第一次握手
各位同学,今天咱们聊聊逻辑综合。这是后端流程的第一步,也是很多人容易轻视的一步。
我记得刚入行那会儿,总觉得综合就是跑个脚本的事。直到有一次,我综合出来的网表面积比预期大了30%,后端布局死活塞不进那片区域……嗯,从那以后我才明白,综合这一步要是没打好基础,后面全是坑。
综合的概念与目标
什么叫逻辑综合?说白了,就是把我们写的RTL代码(Verilog/VHDL),翻译成由标准单元库里的门级电路组成的网表。
你想想看,RTL里写的是 assign c = a & b;,综合工具要把它变成具体的与非门、或非门、反相器。这个过程,就是综合。
综合的目标其实就三个:
- 功能正确:综合后的网表,行为必须和RTL完全一致。这是底线。
- 时序达标:所有路径的延迟,都要满足你设定的时钟频率。跑不到目标频率,芯片就是废的。
- 面积和功耗可控:不能为了时序拼命堆大驱动单元,也不能为了省面积把时序搞崩。这是个平衡的艺术。
我在项目中遇到过一种情况:RTL仿真全过,综合后功能也正确,但流片回来芯片就是跑不到目标频率。查到最后,发现是综合时约束没写对,工具把关键路径优化错了方向。所以,综合不是简单的「翻译」,它是一次深度优化。
标准单元库(Liberty)解读
综合工具用什么来「翻译」RTL?答案是标准单元库。这个库通常以Liberty格式(.lib)提供。
我建议你拿到一个.lib文件后,先别急着跑综合。打开看看,里面信息量很大。
Liberty文件里有什么?
| 字段 | 含义 | 我的经验 |
|---|---|---|
| cell | 单元名称,比如INVX1、NAND2X2 | 名字里的数字代表驱动强度,X1最弱,X8最强 |
| pin | 输入输出引脚 | 每个引脚都有电容值,这个值直接影响前级驱动 |
| timing | 时序弧信息 | 包含cell rise/fall、transition等,是STA的基础 |
| power | 功耗信息 | 内部功耗、开关功耗,低功耗设计必看 |
| area | 单元面积 | 单位通常是um²,综合时工具会尽量选面积小的 |
举个例子,一个INVX1的.lib片段长这样:
cell (INVX1) {
area : 0.784;
pin (A) {
direction : input;
capacitance : 0.002;
}
pin (Y) {
direction : output;
function : "(!A)";
timing () {
related_pin : "A";
cell_rise (delay_template_7x7) {
values ( \
"0.012, 0.015, 0.019, 0.024, 0.030, 0.037, 0.045", \
"0.014, 0.017, 0.021, 0.026, 0.032, 0.039, 0.047", \
...
);
}
}
}
}
你看,这个反相器的面积是0.784,输入引脚电容0.002pF。输出延迟是一个7x7的查找表,横轴是输入转换时间,纵轴是输出负载电容。综合工具就是查这个表,算出每条路径的延迟。
重要提醒:同一个功能的不同驱动强度单元,延迟和面积差异很大。比如INVX1面积小但驱动弱,INVX8面积大但驱动强。综合工具会在时序和面积之间做权衡。
怎么看懂时序弧?
每个单元内部,从输入到输出有多条路径,每条路径都有一条时序弧。比如一个二输入与非门,从A到Z有一条,从B到Z也有一条。
每条时序弧包含:
- cell_rise/cell_fall:单元本身的上升/下降延迟
- rise_transition/fall_transition:输出信号的转换时间
我刚开始看.lib的时候,总觉得这些表格密密麻麻的。后来发现一个规律:输入转换时间越慢,输出负载越大,延迟就越大。这个规律在所有单元上都成立。
小技巧:如果你发现某条路径时序紧张,可以试试换用驱动强度更大的单元。但要注意,驱动强度大了,面积和功耗也会涨。我曾经为了修一条hold violation,把整条路径的单元都换大了一号,结果面积涨了5%……得不偿失。
综合约束(SDC)编写入门
SDC(Synopsys Design Constraints)是综合和时序分析的标准约束格式。没有约束,综合工具就不知道你要优化成什么样。
我见过很多新手,SDC就写两三行:create_clock和set_input_delay。结果综合出来的网表,时序一塌糊涂。SDC写得好不好,直接决定了后端能不能顺利收工。
核心SDC命令
下面这几个命令,是每个项目都必须有的:
# 创建时钟,周期10ns,占空比50%
create_clock -name clk -period 10 [get_ports clk]
# 设置输入延迟,相对于时钟上升沿
set_input_delay -max 5 -clock clk [get_ports data_in]
# 设置输出延迟
set_output_delay -max 5 -clock clk [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/CK] -to [get_pins reg_b/D]
我来解释一下这几条命令的含义:
- create_clock:告诉工具时钟长什么样。周期、占空比、时钟源端口。这是所有时序分析的基准。
- set_input_delay:外部输入信号到达芯片引脚的时间。这个值通常由前级芯片的时序参数决定。设小了,工具会过度优化;设大了,时序可能过不了。
- set_output_delay:芯片输出信号被下一级采样的时间。同理,需要和后级芯片对齐。
- set_false_path:有些路径不需要检查时序,比如跨时钟域的同步器路径。告诉工具别管它,能省很多优化资源。
- set_multicycle_path:有些路径需要多个时钟周期才能稳定。比如一个乘法器,可能需要两个周期才能出结果。这时候就要设多周期路径。
避坑指南:我曾经在一个项目里,忘了设set_false_path。结果综合工具花了大把时间去优化一条跨时钟域的路径,导致其他关键路径的时序反而变差了。所以,该设的约束一定要设全,别偷懒。
SDC编写的常见误区
- 时钟定义不完整:只定义了主时钟,忘了生成时钟(generated clock)。比如PLL输出的时钟,必须用create_generated_clock定义。
- 输入输出延迟设得太紧:设得太紧,工具会拼命优化IO路径,导致内部路径资源被挤占。我建议留10%-20%的余量。
- 忘了设时序例外:异步复位、跨时钟域路径、测试模式路径,这些都要设false_path或disable_timing。
- 时钟组没设:多个异步时钟之间,要用set_clock_groups -asynchronous告诉工具它们之间不需要检查时序。
知识体系总览
下面这张图,是我自己总结的逻辑综合知识框架。你可以把它当作学习路线图:
这张图把逻辑综合拆成了三个核心模块:综合概念、标准单元库、SDC约束。三者缺一不可。概念告诉你「为什么」,库告诉你「用什么」,约束告诉你「做到什么程度」。
我个人习惯是,拿到一个新工艺库,先花半天时间把.lib文件从头到尾看一遍。看看有哪些单元类型,驱动强度范围是多少,典型延迟值大概在什么量级。这样后面写SDC的时候,心里就有底了。
好了,逻辑综合的基础就讲到这里。下一节我们会深入综合工具的具体操作,包括如何读入设计、如何设置综合策略、如何分析综合报告。到时候我会拿一个真实项目案例来演示。
公众号:蓝海资料掘金营,微信deep3321