3. 综合(Synthesis)实战:综合策略、综合选项、综合报告解读、常见综合错误

综合这一步,说白了就是把你的RTL代码翻译成门级网表。很多新手觉得这步交给工具跑就行了,其实不然。我见过太多人综合出来一堆问题,最后布局布线跑不通,回头一看全是综合阶段埋的坑。

今天咱们就聊聊综合实战。我会把策略、选项、报告解读和常见错误串起来讲。你想想看,这些东西其实是一体的——你选什么策略,就会影响报告里出现什么信息,而错误往往就藏在那些你没注意到的选项里。

3.1 综合策略:别只会用默认

Vivado和Quartus都提供了几种综合策略。我个人习惯,项目初期用面积优化,后期调时序再用性能优化。但这不是绝对的。

策略名称 适用场景 我踩过的坑
Default (Vivado) 快速原型验证 资源利用率一般,别用于量产
Area Optimized 资源紧张的项目 曾经为了省LUT,结果时序崩了
Performance Optimized 高频设计 综合时间翻倍,但值得
Flow Runtime Optimized 调试阶段 快速迭代时用,别用于最终版本
我的建议: 项目前期用Runtime Optimized快速迭代,中期切到Area,最后冲刺时序时用Performance。别一上来就跑Performance,等得你怀疑人生。

3.2 综合选项:这些开关你得知道

综合选项里有些开关,默认是关的。但实际项目中,你得手动打开。我列几个常用的:

  • retiming:允许工具移动寄存器位置。我有个项目,打开后时序直接收敛了。但注意,它会改变你的流水线结构,仿真时要小心。
  • flatten_hierarchy:把层次结构打平。资源利用率能提升5-10%,但调试时你会疯掉——所有信号名都变了。
  • keep_equivalent_registers:保留等效寄存器。默认是合并的,但如果你要做跨时钟域处理,必须打开。
  • fsm_extraction:状态机提取。建议用"one_hot"或"gray",别用"auto",我遇到过auto提取出奇怪的状态机。
注意: 综合选项不是越多越好。我曾经在一个项目里同时开了retiming和flatten_hierarchy,结果综合跑了8小时,网表还出了问题。后来发现这两个选项有冲突。嗯,这里要注意,选项之间要搭配着来。

3.3 综合报告解读:别只看Summary

综合报告里最有价值的信息,往往不在Summary里。我一般按这个顺序看:

  1. 资源利用率:LUT、FF、BRAM、DSP。如果某个资源用了超过80%,就要警惕了。布局布线时可能会因为资源不够而失败。
  2. 时序预估:综合阶段的时序是估算的,不准。但如果这里已经红了,布局布线基本没救。我有个项目综合时序WNS是-0.5ns,布局布线后变成了-1.2ns。所以综合阶段就要把时序做到WNS > 0。
  3. 扇出警告:扇出超过200的信号,要手动复制寄存器。我曾经有个时钟使能信号扇出到了500,结果布局布线后时钟偏移大得离谱。
  4. 未使用的引脚:这个容易被忽略。如果顶层模块有未连接的输入引脚,综合会把它优化掉。但如果你后续要加功能,就麻烦了。

核心经验: 综合报告里的WNS(最差负时序裕量)是硬指标。如果WNS是负的,别指望布局布线能救回来。我见过有人综合WNS=-0.3ns,布局布线后变成了-0.8ns。为什么会这样?因为综合阶段的线延迟是估算的,实际布线后只会更差。

3.4 常见综合错误:这些坑我替你踩过了

综合错误分两类:语法错误和逻辑错误。语法错误好修,逻辑错误才要命。

3.4.1 锁存器推断

这是最常见的错误。if语句缺少else,case语句缺少default,都会推断出锁存器。锁存器在FPGA里很坑——它会导致时序分析困难,而且容易受毛刺影响。

// 错误示例:缺少else,会推断出锁存器
always @(*) begin
  if (sel)
    out = a;
  // 缺少else
end

// 正确写法
always @(*) begin
  if (sel)
    out = a;
  else
    out = b;
end
避坑指南: 我曾经在一个状态机里忘了写default,综合出来一堆锁存器。仿真时功能正常,上板后状态机乱跳。查了两天才发现是锁存器的问题。从那以后,我写组合逻辑必写else和default。

3.4.2 多驱动错误

同一个信号在多个always块里赋值,或者同时被assign和always赋值。综合工具会报multi-driver错误。这个错误在仿真阶段是看不出来的,因为仿真器会按最后赋值的为准。但综合后网表里,这个信号会变成未知态。

// 错误示例:两个always块驱动同一个信号
always @(posedge clk)
  data <= a;

always @(posedge clk)
  data <= b;  // 多驱动错误

3.4.3 组合逻辑环路

组合逻辑的输出反馈到输入,中间没有寄存器。这种环路会导致综合工具报combinatorial loop。我记得有个项目,一个计数器没写满复位条件,结果综合出来一个环路,仿真时计数器一直跳,上板后直接冒烟了。

警告: 组合逻辑环路是硬件设计的大忌。它不仅会导致功能错误,还可能引起芯片过热甚至烧毁。如果你在综合报告里看到combinatorial loop,必须立即修复。

3.4.4 信号未使用

综合工具会优化掉未使用的信号。如果你在代码里声明了一个信号但没用到,综合后它就消失了。这本身不是错误,但如果你后续要调试,会发现这个信号在网表里找不到。

我的做法是:在综合选项里关掉"remove_unused_signals",或者用(* keep = "true" *)属性保留关键信号。这样调试时还能看到它们。

3.5 综合实战流程:我的一套组合拳

说了这么多,总结一下我个人的综合流程:

  1. 代码检查:先跑lint检查,修掉语法错误和潜在问题。这一步能省掉综合时80%的报错。
  2. 设置约束:时钟约束、输入输出延迟约束。没有约束的综合就是瞎跑。
  3. 选择策略:根据项目阶段选策略。调试期用Runtime,中期用Area,冲刺用Performance。
  4. 跑综合:同时打开retiming和keep_equivalent_registers。
  5. 看报告:重点看资源利用率、WNS、扇出警告。如果WNS是负的,先别往下走,回头调代码或约束。
  6. 修错误:锁存器、多驱动、组合环路,一个都不能留。
  7. 保存网表:综合后的网表和约束文件一起保存,方便后续布局布线使用。

嗯,这套流程我用了好几年,基本没出过大问题。你刚开始可能觉得麻烦,但习惯了就会发现,综合阶段多花一小时,布局布线阶段能省一整天。

最后说一句: 综合不是终点,是起点。一个好的综合结果,能让后续的布局布线事半功倍。反过来,综合阶段埋下的坑,会在布局布线阶段加倍还给你。所以,别急着点"Run Synthesis",先把代码和约束检查清楚。