3、综合阶段的面积优化:逻辑综合中的面积约束、资源共享与操作数隔离、状态机编码优化
综合阶段,说白了就是把RTL代码翻译成门级网表。很多人觉得这步是工具自动完成的,没啥好优化的。嗯,我以前也这么想,直到有一次项目,同样的代码,别人综合出来面积比我小了30%。
从那以后我明白了一个道理:综合不是玄学,是科学。你给工具什么约束,它就给你什么结果。今天我就聊聊综合阶段面积优化的三个核心手段。
3.1 面积约束:别让工具放飞自我
很多工程师做综合时,只设时序约束,面积约束随便写个0。结果呢?工具为了满足时序,拼命复制逻辑,面积直接爆炸。
我个人习惯是,面积约束一定要给。哪怕你给一个宽松的值,也比不给强。
核心命令示例(Design Compiler):
# 设置面积约束 - 目标面积设为当前设计的80%
set_max_area 0
# 或者更实际的做法
set_max_area 10000
set_area_weight 1.0 ;# 面积权重,与时序权重平衡
这里有个坑:set_max_area 0 不是真的让面积变成0,而是告诉工具「尽量往小了做」。但如果你时序很紧,工具会优先满足时序,面积就顾不上了。
我的经验: 先跑一版不加面积约束的综合,看看基线面积是多少。然后设一个比基线小10%-20%的目标,配合适当的权重,效果最好。
我曾经在一个项目中,不加面积约束综合出来是12万门。设了set_max_area 90000后,最终面积优化到了9.5万门,时序只差了0.2ns。后来稍微调整了一下综合策略,两者都满足了。
3.2 资源共享与操作数隔离:别让硬件闲着
资源共享,说白了就是让多个操作共用同一个硬件模块。比如两个加法器,如果不同时使用,完全可以合并成一个。
但这里有个容易忽略的点——操作数隔离。
什么是操作数隔离?举个例子:
// 不好的写法 - 没有操作数隔离
always @(*) begin
if (sel)
result = a + b;
else
result = c + d;
end
这段代码里,两个加法器都在计算,只是最后选一个输出。浪费了功耗和面积。
// 好的写法 - 操作数隔离
always @(*) begin
if (sel) begin
sum = a + b;
end else begin
sum = c + d;
end
result = sum;
end
这样写,综合工具就能识别出只有一个加法器在工作。嗯,其实更推荐用多路选择器放在加法器前面的方式:
// 最佳实践 - 共享加法器
assign op1 = sel ? a : c;
assign op2 = sel ? b : d;
assign result = op1 + op2;
注意: 不是所有情况都适合资源共享。如果两个操作频繁切换,共享反而会增加MUX的延迟和面积。我建议在数据路径比较宽(比如32位以上)且操作不频繁时使用。
我记得有一次做视频处理芯片,里面有个64位的加法器被多个模块调用。我用了资源共享,把三个加法器减成了一个,面积省了将近2000门。代价是时序稍微变差了一点,但完全在预算内。
3.3 状态机编码优化:选对编码,面积减半
状态机是芯片里的常客。状态机的编码方式,直接影响面积。
常见的编码方式有三种:
| 编码方式 | 状态数 | 触发器数 | 组合逻辑 | 适用场景 |
|---|---|---|---|---|
| 二进制编码 | N | log₂(N) | 较多 | 状态数多时 |
| 格雷码 | N | log₂(N) | 中等 | 跨时钟域 |
| 独热码 | N | N | 较少 | 状态数少时 |
你想想看,如果状态机有16个状态:
- 二进制编码:4个触发器,但组合逻辑复杂
- 独热码:16个触发器,但组合逻辑简单
哪个面积小?不一定。得看你的工艺库。如果触发器面积大,二进制编码更优。如果组合逻辑面积大,独热码更优。
我的建议:
- 状态数 < 8:用独热码,综合工具容易优化
- 状态数 8-32:用二进制编码,面积效率高
- 状态数 > 32:考虑用状态压缩或分解
我曾经接手过一个项目,状态机有24个状态,用了独热码。我改成二进制编码后,面积直接降了40%。为什么?因为那个工艺库的触发器特别贵。
另外,状态机编码可以用综合指令指定:
// 在RTL中指定编码方式
// Synopsys DC
// synthesis attribute fsm_encoding of state_machine is "binary";
// 或者在综合脚本中
set_fsm_encoding "binary"
一个小技巧: 如果你不确定哪种编码好,可以让综合工具自动选择。DC有个set_fsm_encoding "auto"选项,工具会根据面积和时序自动选最优编码。我个人习惯先跑auto,再看结果手动微调。
3.4 综合脚本中的面积优化策略
最后,分享一个我常用的综合脚本片段:
# 面积优化综合脚本
# 1. 先设时序约束
create_clock -period 10 [get_ports clk]
set_input_delay 2 [all_inputs]
set_output_delay 2 [all_outputs]
# 2. 面积约束
set_max_area 0
set_area_weight 0.5
# 3. 编译策略
compile_ultra -area_high_effort_script
# 4. 后优化
# 如果面积还大,可以尝试
set_structure -boolean_optimization -resource_sharing
compile -map_effort high -incremental
避坑指南: 我曾经在项目中用了compile_ultra,结果面积反而变大了。后来发现是因为compile_ultra默认会做很多时序优化,复制了大量逻辑。解决办法是加上-area_high_effort_script选项,让工具在面积和时序之间找平衡。
综合阶段的面积优化,说白了就是给工具正确的约束,写对风格的代码。面积约束别偷懒,资源共享要聪明,状态机编码要选对。这三件事做好了,面积优化就成功了一大半。
嗯,今天就聊到这儿。下一章我们讲布局布线阶段的面积优化,到时候会聊到更具体的物理实现技巧。
本章核心要点:
- 面积约束必须给,建议设为目标面积的80%-90%
- 资源共享时注意操作数隔离,避免不必要的硬件翻转
- 状态机编码根据状态数选择:少用独热,多用二进制
- 综合脚本中善用
-area_high_effort_script选项
可可精品资料库