4、逻辑综合基础:RTL到网表、综合流程、时序约束(SDC)、综合策略与优化

各位同学,欢迎来到第四章。前面我们聊了芯片设计的宏观流程,也看了标准单元库的细节。今天要讲的,是数字后端工程师的「第一道硬菜」——逻辑综合。

说白了,逻辑综合就是把我们写的RTL代码(硬件描述语言),翻译成由标准单元(与门、或门、触发器)组成的网表。这个过程,就像把建筑设计图纸变成钢筋水泥的施工图。我刚开始做后端时,总觉得综合是前端的事,后来发现——综合做不好,后端跑断腿。

4.1 从RTL到网表:综合到底干了啥?

你写了一段Verilog:

always @(posedge clk) begin
    if (rst)
        q <= 8'b0;
    else if (en)
        q <= d;
end

综合工具会把它变成什么?

  • 一个8位的D触发器阵列
  • 一个多路选择器(MUX),选择是复位还是加载数据
  • 时钟网络和复位网络的连接

这个过程分三步:

  1. 翻译:把RTL转成布尔表达式和状态机
  2. 逻辑优化:化简冗余逻辑,共享公共子表达式
  3. 映射:把优化后的逻辑映射到标准单元库

嗯,这里要注意——映射这一步,直接决定了你的面积和速度。我见过一个项目,同样的RTL,换了个库,面积差了30%。

4.2 综合流程:不是点一下按钮那么简单

很多新手以为综合就是跑个脚本。其实完整的流程是这样的:

步骤 输入 输出 我踩过的坑
1. 读入设计 RTL文件、库文件 内存中的设计数据 文件路径写错,白跑两小时
2. 定义约束 SDC文件 时序/面积/功耗目标 时钟定义漏了,结果全错
3. 编译 设计+约束 未优化的网表
4. 优化 未优化网表 优化后的网表 面积约束太紧,时序崩了
5. 输出 优化网表 .v网表、.sdf、报告 忘了输出sdf,后端没法做STA

我个人习惯,在编译之前先跑一遍 check_design。这个命令能帮你发现很多低级错误——比如悬空端口、未连接的时钟。我曾经有一次漏了检查,结果综合出来的网表有几百个悬空引脚,后端布局时直接报错。

4.3 时序约束(SDC):综合的灵魂

SDC(Synopsys Design Constraints)是综合的「宪法」。没有约束,工具不知道你要多快、多省电。

最基本的几条命令:

# 创建时钟
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 rst_clk] -to [get_clocks sys_clk]

# 多周期路径
set_multicycle_path 2 -setup -from [get_pins reg_a/Q] -to [get_pins reg_b/D]

你想想看,如果时钟周期设错了,比如实际是10ns,你设成了5ns,工具会拼命优化速度,面积和功耗就炸了。反过来,设得太松,芯片流片回来跑不到目标频率。

警告:我曾经接手过一个项目,前同事把 create_clock 的周期写成了100ns(实际是10ns)。综合出来的网表面积小了40%,但时序全红。后来花了三周重新综合,项目差点延期。

4.4 综合策略:面积、速度、功耗的三角博弈

综合策略说白了就是——你更想要什么?

  • 速度优先:用大驱动单元,逻辑深度浅,但面积大、功耗高
  • 面积优先:共享逻辑,用最小单元,但路径延迟大
  • 功耗优先:多用低功耗单元,门控时钟,但可能牺牲速度

我一般这样操作:

  1. 先跑一次 面积优先 的综合,看看最差能到多少
  2. 再跑一次 速度优先,看看极限在哪里
  3. 最后取中间值,微调约束

举个例子,一个32位加法器:

  • 面积优先:用行波进位加法器,面积小但延迟大
  • 速度优先:用超前进位加法器,速度快但面积翻倍
  • 折中:用进位选择加法器,面积和速度都居中
小技巧:综合时可以用 set_optimize_strategy 命令指定策略。我个人习惯在综合脚本里写三个版本,最后对比报告选最优的。

4.5 优化技巧:让工具为你打工

综合工具很聪明,但也很笨。你得告诉它怎么优化。

1. 寄存器重定时(Retiming)

把组合逻辑在两级的寄存器之间重新分配,平衡延迟。比如:

// 优化前:第一级延迟大,第二级延迟小
always @(posedge clk) a <= x + y + z;  // 延迟大
always @(posedge clk) b <= a & w;      // 延迟小

// 优化后:工具自动平衡
// 可能变成 a <= x + y; b <= (a + z) & w;

2. 逻辑结构选择

同样的功能,不同写法影响很大:

  • case 语句:综合成MUX,面积小但延迟固定
  • if-else 语句:综合成优先级编码器,关键路径可能更短

我建议:关键路径上用 case,非关键路径上用 if-else 省面积。

3. 门控时钟

这是降低功耗的利器。工具会自动插入时钟门控单元:

// 手动写法
assign gated_clk = clk & enable;

// 工具自动插入
// 综合工具会识别:if (enable) q <= d;
// 然后自动生成门控时钟逻辑
核心要点:综合不是一次性的。我通常要迭代3-5轮:第一轮看面积,第二轮调时序,第三轮压功耗。每轮都要仔细看报告,特别是 report_timingreport_area

4.6 综合后的检查清单

综合跑完了,别急着交给后端。先检查这几项:

  1. 时序报告:有没有setup/hold违例?违例了多少?
  2. 面积报告:和预估的差多少?是不是有冗余逻辑?
  3. 功耗报告:动态功耗和静态功耗的比例是否合理?
  4. 网表质量:有没有悬空引脚?有没有未连接的线?
  5. 一致性检查:综合后的网表和RTL功能一致吗?(用Formality跑)

我曾经有一次,综合出来的网表面积比预期大了50%。查了半天,发现是RTL里写了一个巨大的 case 语句,工具把它展开成了几千个MUX。后来改成 if-else,面积立刻降下来了。

好了,这一章的内容就到这里。逻辑综合是后端工程师的基本功,也是前端和后端的分水岭。下一章我们会讲布局规划——把网表里的单元放到芯片上。到时候你会发现,综合做得好,布局就轻松一半。