4. 综合优化技巧:时序、面积、功耗与DFT插入

综合这一步,说白了就是把RTL代码翻译成门级网表。但翻译得好不好,差别可大了去了。我见过不少新手,跑完综合一看时序过了就交差,结果后端布局布线时发现一堆问题。嗯,综合阶段的优化,其实是为后端铺路。

核心原则:综合不是终点,而是后端流程的起点。时序、面积、功耗三者需要权衡,DFT插入则要提前规划。

4.1 时序优化策略

时序优化,说白了就是让信号在规定时间内到达。我个人习惯把时序优化分成三步:约束、结构、单元。

4.1.1 约束驱动优化

没有合理的约束,综合工具就像没头苍蝇。我建议你至少把下面这几类约束写清楚:

约束类型 关键点 常见坑
时钟约束 频率、抖动、不确定性 时钟不确定性设太小,工具过度优化
输入延迟 外部到端口的延迟 设太紧导致面积爆炸
输出延迟 端口到外部寄存器的延迟 设太松导致后端时序难收敛
伪路径 跨时钟域、测试模式路径 漏设伪路径,工具白费力气
多周期路径 允许跨多个时钟周期的路径 设错周期数导致功能错误

我的经验:时钟不确定性我一般设时钟周期的5%~10%。设太小了,工具会拼命插缓冲器,面积和功耗都受不了。

4.1.2 结构优化技巧

约束写好了,接下来看代码结构。工具再聪明,也架不住烂代码。我遇到过几次,综合后关键路径都在一个巨大的多路选择器上。为什么会这样?因为代码里写了一大堆嵌套的if-else。

常用的结构优化方法:

  • 关键路径寄存器平衡:把组合逻辑分散到多个时钟周期
  • 操作符共享:复用加法器、乘法器等资源
  • 逻辑重定时:移动寄存器位置来缩短关键路径
  • 流水线插入:在长组合逻辑中插入寄存器
// 不好的写法:长组合逻辑链
always @(*) begin
  result = a + b + c + d + e + f;  // 6级加法
end

// 好的写法:流水线结构
always @(posedge clk) begin
  sum1 <= a + b;
  sum2 <= c + d;
  sum3 <= e + f;
end
always @(posedge clk) begin
  result <= sum1 + sum2 + sum3;
end

4.1.3 单元级优化

工具在单元选择上也有讲究。我记得有一次,一个关键路径总是差那么一点点。后来发现,工具默认用了标准阈值的单元。换成低阈值单元后,时序立马就过了。

单元类型 速度 漏电功耗 适用场景
高阈值(HVT) 慢 低 非关键路径
标准阈值(SVT) 中 中 一般路径
低阈值(LVT) 快 高 关键路径
超低阈值(ULVT) 最快 最高 最关键的路径

注意:低阈值单元用多了,漏电功耗会飙升。我曾经在一个项目中,为了赶时序,大面积用了LVT单元,结果功耗超标了30%。后来不得不重新优化,把非关键路径换回HVT。

4.2 面积优化策略

面积优化,说白了就是省钱。芯片面积直接决定成本。我见过一些团队,综合时完全不看面积报告,结果后端发现芯片放不下。

面积优化的常用手段:

  • 资源共享:多个功能模块共用同一个运算单元
  • 状态机优化:减少状态数,优化状态编码
  • 逻辑简化:去除冗余逻辑,简化布尔表达式
  • 门控时钟:减少不必要的寄存器翻转
  • 模块边界优化:跨模块的寄存器合并
// 面积优化示例:资源共享
// 不好的写法:两个独立的加法器
assign sum_a = data_a + offset_a;
assign sum_b = data_b + offset_b;

// 好的写法:复用同一个加法器
always @(*) begin
  if (sel) begin
    result = data_a + offset_a;
  end else begin
    result = data_b + offset_b;
  end
end

我的习惯:综合完成后,第一件事就是看面积报告。如果某个模块面积异常大,我会去检查代码,看看是不是有冗余逻辑或者不必要的寄存器。

4.3 功耗优化策略

功耗优化现在越来越重要。特别是移动设备和AI芯片,功耗直接决定产品竞争力。功耗优化可以从动态功耗和静态功耗两个角度入手。

4.3.1 动态功耗优化

动态功耗跟信号翻转率直接相关。信号翻得越频繁,功耗越大。我建议从下面几个方向入手:

  • 门控时钟:这是最有效的动态功耗优化手段。不用的模块,直接把时钟关掉
  • 操作数隔离:当运算结果不被使用时,阻止输入信号翻转
  • 数据编码优化:减少总线上的翻转次数
  • 低功耗状态机:使用格雷码或one-hot编码
// 门控时钟示例
// 不好的写法:时钟一直开着
always @(posedge clk) begin
  if (enable) begin
    data_out <= data_in;
  end
end

// 好的写法:使用门控时钟
wire gated_clk = clk & enable;
always @(posedge gated_clk) begin
  data_out <= data_in;
end

4.3.2 静态功耗优化

静态功耗主要来自漏电流。随着工艺节点越来越小,漏电功耗占比越来越高。我记得在28nm以下工艺,漏电功耗能占到总功耗的40%以上。

静态功耗优化方法:

  • 电源门控:彻底关掉不用的模块的电源
  • 多阈值单元:非关键路径用HVT,关键路径用LVT
  • 衬底偏置:通过调整衬底电压来控制阈值
  • 长沟道器件:在非关键路径使用长沟道器件降低漏电

避坑指南:我曾经在一个项目中,为了降低漏电,把所有非关键路径都换成了HVT单元。结果发现,有些路径虽然时序不紧张,但信号翻转率很高,用HVT反而导致动态功耗增加。所以,功耗优化一定要综合考虑动态和静态。

4.4 综合中的DFT插入

DFT(可测试性设计)插入,说白了就是给芯片装个"体检系统"。没有DFT,芯片生产出来坏了你都不知道坏在哪。

4.4.1 DFT的基本结构

综合阶段最常用的DFT技术是扫描链。我建议你在综合时就规划好扫描链的结构,不要等到后端再做。

DFT技术 功能 面积开销 测试覆盖率
扫描链 将寄存器串成链,方便测试 5%~10% 高
BIST 内建自测试,不需要外部设备 10%~20% 中
边界扫描 测试芯片引脚连接 5%~15% 高
MBIST 专门测试存储器 5%~10% 高

4.4.2 扫描链插入流程

综合阶段做DFT插入,我一般按这个流程来:

  1. 检查RTL代码:确保所有寄存器都能被扫描
  2. 定义测试模式:设置测试时钟、测试使能等信号
  3. 插入扫描链:工具自动把寄存器串成链
  4. 验证扫描链:跑仿真确认扫描链功能正确
  5. 生成测试向量:ATPG工具生成测试向量
  6. 评估覆盖率:检查测试覆盖率是否达标
// DFT插入示例:扫描链配置
// 在综合脚本中设置
set_scan_configuration -chain_count 4
set_scan_configuration -clock_mixing no_mix
set_scan_configuration -create_dedicated_scan_ports true

// 指定扫描链的输入输出
set_scan_path test_chain1 -view spec \
  -scan_data_in  scan_in_1 \
  -scan_data_out scan_out_1

我的经验:扫描链的数量要根据芯片面积和测试时间来决定。链越多,测试时间越短,但需要的测试引脚也越多。我一般控制在4~8条链,这样测试时间和引脚数量都比较平衡。

4.4.3 DFT插入的注意事项

DFT插入看起来简单,但坑不少。我遇到过几次问题,分享给你:

  • 异步复位寄存器:要确保复位信号在测试模式下可控
  • 时钟门控:测试模式下要旁路掉时钟门控
  • 三态总线:测试模式下要避免总线冲突
  • 存储器:要单独处理,通常用MBIST
  • 模拟模块:数字和模拟的接口要特殊处理

避坑指南:我曾经在一个项目中,忘了处理异步复位寄存器。结果扫描测试时,复位信号不受控,导致测试覆盖率只有60%。后来不得不重新插入扫描链,浪费了两周时间。所以,DFT一定要在综合阶段就规划好。

综合优化知识体系 综合优化 时序优化 约束驱动 结构优化 单元选择 面积优化 资源共享 逻辑简化 状态机优化 功耗优化 门控时钟 操作数隔离 多阈值单元 DFT插入 扫描链 BIST 边界扫描 权衡:时序、面积、功耗三者需要平衡 DFT插入需在综合阶段提前规划

综合优化不是一蹴而就的事。我做了这么多年,每次项目都会遇到新的挑战。但只要你掌握了时序、面积、功耗和DFT这四个维度的优化方法,大部分问题都能找到解决方案。记住,没有最好的优化,只有最适合的优化。