3. 综合(Synthesis)实战:综合策略、综合选项、综合报告解读、常见综合错误
综合这一步,说白了就是把你的RTL代码翻译成门级网表。很多新手觉得这步交给工具跑就行了,其实不然。我见过太多人综合出来一堆问题,最后布局布线跑不通,回头一看全是综合阶段埋的坑。
今天咱们就聊聊综合实战。我会把策略、选项、报告解读和常见错误串起来讲。你想想看,这些东西其实是一体的——你选什么策略,就会影响报告里出现什么信息,而错误往往就藏在那些你没注意到的选项里。
3.1 综合策略:别只会用默认
Vivado和Quartus都提供了几种综合策略。我个人习惯,项目初期用面积优化,后期调时序再用性能优化。但这不是绝对的。
| 策略名称 | 适用场景 | 我踩过的坑 |
|---|---|---|
| Default (Vivado) | 快速原型验证 | 资源利用率一般,别用于量产 |
| Area Optimized | 资源紧张的项目 | 曾经为了省LUT,结果时序崩了 |
| Performance Optimized | 高频设计 | 综合时间翻倍,但值得 |
| Flow Runtime Optimized | 调试阶段 | 快速迭代时用,别用于最终版本 |
3.2 综合选项:这些开关你得知道
综合选项里有些开关,默认是关的。但实际项目中,你得手动打开。我列几个常用的:
- retiming:允许工具移动寄存器位置。我有个项目,打开后时序直接收敛了。但注意,它会改变你的流水线结构,仿真时要小心。
- flatten_hierarchy:把层次结构打平。资源利用率能提升5-10%,但调试时你会疯掉——所有信号名都变了。
- keep_equivalent_registers:保留等效寄存器。默认是合并的,但如果你要做跨时钟域处理,必须打开。
- fsm_extraction:状态机提取。建议用"one_hot"或"gray",别用"auto",我遇到过auto提取出奇怪的状态机。
3.3 综合报告解读:别只看Summary
综合报告里最有价值的信息,往往不在Summary里。我一般按这个顺序看:
- 资源利用率:LUT、FF、BRAM、DSP。如果某个资源用了超过80%,就要警惕了。布局布线时可能会因为资源不够而失败。
- 时序预估:综合阶段的时序是估算的,不准。但如果这里已经红了,布局布线基本没救。我有个项目综合时序WNS是-0.5ns,布局布线后变成了-1.2ns。所以综合阶段就要把时序做到WNS > 0。
- 扇出警告:扇出超过200的信号,要手动复制寄存器。我曾经有个时钟使能信号扇出到了500,结果布局布线后时钟偏移大得离谱。
- 未使用的引脚:这个容易被忽略。如果顶层模块有未连接的输入引脚,综合会把它优化掉。但如果你后续要加功能,就麻烦了。
核心经验: 综合报告里的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
3.4.2 多驱动错误
同一个信号在多个always块里赋值,或者同时被assign和always赋值。综合工具会报multi-driver错误。这个错误在仿真阶段是看不出来的,因为仿真器会按最后赋值的为准。但综合后网表里,这个信号会变成未知态。
// 错误示例:两个always块驱动同一个信号
always @(posedge clk)
data <= a;
always @(posedge clk)
data <= b; // 多驱动错误
3.4.3 组合逻辑环路
组合逻辑的输出反馈到输入,中间没有寄存器。这种环路会导致综合工具报combinatorial loop。我记得有个项目,一个计数器没写满复位条件,结果综合出来一个环路,仿真时计数器一直跳,上板后直接冒烟了。
3.4.4 信号未使用
综合工具会优化掉未使用的信号。如果你在代码里声明了一个信号但没用到,综合后它就消失了。这本身不是错误,但如果你后续要调试,会发现这个信号在网表里找不到。
我的做法是:在综合选项里关掉"remove_unused_signals",或者用(* keep = "true" *)属性保留关键信号。这样调试时还能看到它们。
3.5 综合实战流程:我的一套组合拳
说了这么多,总结一下我个人的综合流程:
- 代码检查:先跑lint检查,修掉语法错误和潜在问题。这一步能省掉综合时80%的报错。
- 设置约束:时钟约束、输入输出延迟约束。没有约束的综合就是瞎跑。
- 选择策略:根据项目阶段选策略。调试期用Runtime,中期用Area,冲刺用Performance。
- 跑综合:同时打开retiming和keep_equivalent_registers。
- 看报告:重点看资源利用率、WNS、扇出警告。如果WNS是负的,先别往下走,回头调代码或约束。
- 修错误:锁存器、多驱动、组合环路,一个都不能留。
- 保存网表:综合后的网表和约束文件一起保存,方便后续布局布线使用。
嗯,这套流程我用了好几年,基本没出过大问题。你刚开始可能觉得麻烦,但习惯了就会发现,综合阶段多花一小时,布局布线阶段能省一整天。
最后说一句: 综合不是终点,是起点。一个好的综合结果,能让后续的布局布线事半功倍。反过来,综合阶段埋下的坑,会在布局布线阶段加倍还给你。所以,别急着点"Run Synthesis",先把代码和约束检查清楚。