1. 为什么非要折腾MBFF:功耗、面积与时序的三重账
第一次接触MBFF,是在一颗28nm的MCU上。综合跑完,面积比预期多出3%,功耗更是超标不少。那时候我做后端没多久,第一反应是调整floorplan,完全没有想到问题竟然出在寄存器本身的物理结构上。后来被资深工程师点了一句:“你把设计里的单bit触发器统计一下,看看有多少能合并成multi-bit。”我才真正开始系统接触Multi-bit触发器(MBFF)这条优化路径。
1.1 单bit触发器重复造轮子的浪费
绝大多数RTL工程师在写代码时,习惯用always @(posedge clk)描述一组寄存器,一个bit对应一个触发器。这个习惯本身没有问题,但落到标准单元库里,每个单bit触发器背后都有一套完整的“配套设备”:独立的时钟输入端、独立的复位端、独立的内部反相器、独立的驱动管。这些配套设备在每个单bit触发器里都是重复的。
做个生活化类比:一个办公室有20个人,如果每个人都在自己的工位放一台饮水机,那需要20台饮水机、20套水路、20份电力维护。更好的做法是集中放两三台大饮水机,大家排队接水就行。MBFF做的事情就是“共享饮水机”——把多个bit的时钟反相器、复位逻辑、功耗开关等共用起来,只保留每个bit自己的数据存储部分。
这颗28nm MCU里的寄存器数量大概在50万bit左右,其中大量是按字节组织的控制寄存器、状态寄存器、FIFO指针,天然具备合并条件。如果全部换成2bit或4bit的MBFF单元,共享逻辑的比例相当可观,面积和功耗都能省下一大块。
1.2 MBFF在物理上到底长什么样
标准单元库里的MBFF,常见的有DFQD1BWP这类2bit触发器、DFQD2BWP这类4bit触发器,命名规则各家略有差异,但思路一致:一个单元内部包含2个或4个相同的触发器bit,它们共享时钟、共享复位、共享电源和地,内部还有一个共用的时钟反相器网络。
与单bit触发器相比,MBFF的高度通常更高,占用的row数更多。比如单bit触发器可能是9轨高度,一个4bit MBFF可能是12轨或者更高。这意味着在布局阶段,MBFF无法和普通单bit单元挤在同一行里,它需要跨越多个标准单元的row,这对后续legalization和congestion都有直接影响。
正因为物理尺寸更大,MBFF在布局阶段不像单bit触发器那样“见缝插针”。它更像一块小号的宏单元,摆下去之后周边一段区域内的布线通道都会受影响,尤其是M1、M2层的资源。很多项目在MBFF比例超过30%后,congestion会突然恶化,原因就在这里。
1.3 什么时候该用、什么时候该收手
MBFF不是万能的。我在一个项目里踩过这样的坑:为了追求面积,把大量单个bit互相无关的触发器强行合并成4bit MBFF,结果布局阶段发现congestion爆表,布线绕线严重,时序反而变差了,最后不得不回头拆掉一部分合并。
合并的前提是这些bit在逻辑上密切相关,最好满足以下任一条件:
- 是同一个寄存器的连续bit位,比如
reg[3:0] data; - 是同一个控制信号的多个状态位,比如一组使能信号;
- 数据更新条件相同,拥有相同的时钟门控或使能条件。
如果两个bit的数据路径完全不相关、更新时机不同、使能条件不同,强行合并之后只会让综合工具在MBFF内部增加额外的数据选择逻辑,得不偿失。另外,DFT(可测试性设计)也要提前沟通,很多MBFF单元对scan chain的连接方式有限制,如果RTL里已经规划好了scan chain顺序,合并策略会牵一发动全身。
2. 综合阶段的MBFF:合并不是靠RTL写出来的
MBFF的合并发生在综合阶段,RTL本身不需要任何改动。综合工具会读入标准单元库中的MBFF单元,根据RTL中寄存器的逻辑关系、库单元的面积和时序信息,自动决定哪些单bit触发器可以合并成一个多bit触发器。
2.1 工具怎么判断“可以合并”
在Synopsys Design Compiler里,做MBFF合并的核心逻辑是看两个条件:逻辑兼容性和物理可行性。
逻辑兼容性指的是:两个或多个寄存器的时钟端、复位端、置位端必须完全一致,而且数据输入不能相互依赖。比如reg[3:0] a和reg[3:0] b,如果它们的赋值条件都是同一个enable信号,那就可以考虑合并成一个8bit的MBFF,或者合并成两个4bit MBFF。
物理可行性指的是:目标库里必须有对应bit数的MBFF单元,而且综合出来的面积、时序不能比合并前更差。工具会遍历所有可用的MBFF单元,计算合并前后的面积对比、时序余量变化,然后决定是否执行合并。
我自己用DC做28nm项目时,常用的流程是这样的:
# 开启寄存器合并 set_register_merging_options -allow_retime true -max_bit_width 4 compile_ultra -timing其中-max_bit_width 4限制最大合并宽度为4bit。这个参数很关键,无脑合并成8bit甚至16bit在物理上未必划算,因为MBFF宽度越大,单元内部的布线拥塞越严重,而且一旦需要ECO(工程变更单)修改其中一个bit,整个大单元都要跟着动。
Cadence Genus流程中类似的做法是:
set_db design:work .merge_registers true syn_generic syn_map syn_opt两边工具的选项名字不一样,但要表达的核心意思是一致的:让工具在综合阶段自动寻找可合并的寄存器组,用MBFF单元替换。
2.2 库文件里的学问:not used列表就是“封杀令”
综合阶段有个容易被忽略的细节:库文件里的dont_use属性。如果lib文件里MBFF单元没有标注dont_use,工具默认会尝试使用;如果标了dont_use,工具会完全忽略这些单元,即使它们在面积和功耗上更有优势。
我在一个项目里遇到过很奇怪的现象:同样的设计,同样的约束,两版综合结果面积差了8%。排查到最后发现,是第二版SDC里多了一句针对MBFF单元的set_dont_use命令,而写这句命令的工程师是想“暂时禁用”某个有问题的4bit单元,结果整类单元都被封杀了。
所以,库文件检查是MBFF综合的第一步。我的习惯是:
# 查看当前设计里MBFF单元的使用情况 report_cell_usage | grep "DF.*Q.*D.*BWP"确认哪些MBFF单元是允许使用的、哪些被dont_use封杀了。如果发现某个bit宽度的MBFF时序有问题,不要整个disable,而是单独disable那一个有问题的cell,保留其他可用的。
2.3 合并后网表的检查:formal verify不能省
MBFF合并会改变网表中的寄存器分组关系,但逻辑功能不应该改变。综合完成后,一定要跑形式验证,确认合并前后的网表逻辑等价。
这里有一个容易踩的坑:set_register_merging_options -allow_retime true里的-allow_retime会让工具在合并的同时进行寄存器重定时优化,也就是允许寄存器在不同逻辑层级之间移动。这种移动虽然能改善时序,但会给形式验证增加不少难度。如果项目对formal verification要求特别严格,建议先关掉-allow_retime,只做纯粹的寄存器合并。
另外,合并后的网表里,寄存器名称会发生变化。原来RTL里的信号名data_reg[0]、data_reg[1]可能被合并成一个名为multibit_reg_inst的单元内部bit。在做ECO或查询信号时,需要适应这套新的命名规则,否则会出现找不到信号的情况。
2.4 综合阶段最令人困惑的面积反弹
MBFF明明是省面积的,但有时候综合报告里面积反而变大了,这是怎么回事?
我第一次遇到时也懵了。后来仔细分析才发现,问题出在“面积反弹”上:合并后的MBFF单元虽然本身比多个单bit加起来的面积小,但MBFF的物理尺寸更大,工具在综合阶段无法准确预估布局布线时的绕线资源消耗,于是会插入更多的缓冲器和反相器来保证时序收敛,这些额外插入的cell往往抵消甚至超过MBFF本身省下的面积。
解决办法是:综合阶段把时钟约束适当收紧一点,给布局布线预留更多余量,同时关注congestion报告。如果综合报告里的MBFF合并率已经很高,但面积没有明显下降,需要回头检查是不是因为大量MBFF周围的时序缓冲器太多了。
3. 布局阶段的地盘之争:density、congestion与legalization
MBFF从综合进入布局后,真正的技术活才刚刚开始。综合阶段看到的只是面积和功耗的数字变化,布局阶段看到的是物理空间上的冲突。
3.1 MBFF在版图上的物理形态:大块头的碰撞
以我们用的28nm标准单元库为例,普通单bit触发器的尺寸大概是1.4μm宽、2.0μm高,而一个4bit MBFF的尺寸达到了2.1μm宽、3.2μm高,几乎是单bit触发器的两倍多。这个尺寸变化意味着,原本可以在同一行里连续摆放多个单bit触发器的区域,现在只能放一个MBFF,旁边还会留下不规则的空隙。
这些空隙如果太小,其他标准单元塞不进去,就会造成更多的空隙碎片,降低整体的cell利用率。所以,MBFF比例高的设计,布局阶段的Density(利用率)目标不能照搬单bit设计,通常要降低5%到8%。
3.2 place阶段的第一个坑:density与congestion互相拉扯
我在一个40nm项目里遇到过这样的场景:综合后MBFF比例大约25%,我按老经验把Density目标设成0.75,结果place之后congestion报告一片红,特别是几个大的MBFF集群附近,M2、M3层的overflow高得吓人。
分析之后找到原因:MBFF内部的两个bit之间是有物理隔离的,它们之间的布线通道不能走任何互联线,相当于在版图上放置了一个“内部阻塞区”。如果多个MBFF挤在一起,这些阻塞区叠加起来,就会把周边的布线通道全部堵死。这就像晚高峰的地铁站,站台人多还可以走动,但如果有人在站台中间堆了几面墙,人流就直接卡死了。
处理办法是在place阶段就把MBFF的摆放打散,不要让他们形成大面积的连续堆叠。具体操作有两种思路:
一种是在floorplan阶段预先规划placement blockage,把一些区域设置为MBFF禁止摆放区,强制工具把MBFF分散到芯片各处;
另一种是在place时提高congestion优化力度:
place_opt -congestion -effort high -max_density 0.68实测下来,把Density从0.75降到0.68,congestion问题基本缓解,时序余量反而因为布线通畅而变好了。
3.3 用“防爆区”的思路处理MBFF摆放密度
沿用刚才“地铁站墙”的比喻,我在后来的项目里摸索出一套比较实用的做法:给MBFF集群设置虚拟的“防爆区”。
具体做法是,在floorplan阶段先把设计中MBFF数量比较密集的区域圈出来,用工具命令在这个区域周围设置一圈placement halo,让其他普通单元不能紧贴着这一圈摆放,给MBFF集群留出足够的布线空间。
# Innovus中给选中的MBFF实例添加halo selectInst [get_cells *multibit*] add_placement_halo -halo 2.0 2.0 place_opt这里的2.0表示在MBFF实例周围留出2微米的摆放禁布禁止区域。虽然这会让局部密度看起来更高,但实际布线资源反而更宽裕了。这是因为MBFF本身占用的M1/M2资源很少,真正的布线压力来自周边信号线的绕线需求,留出这些区域后,绕线拥堵得到了有效缓解。
3.4 legalization与pre-placement检查清单
MBFF因为高度比常规单元高,legalization阶段需要额外注意:
- 确认MBFF是否落在正确的row上,如果有旋转或翻转到非标准row位,DRC会报错;
- 检查MBFF与邻近单元之间的间距是否满足最小间距要求,特别是同高度但不同row对齐方式下;
- 确认电源/地轨连接是否正确,部分MBFF单元因为高度更高,需要的电源轨连接方式与单bit不同。
我一般在place完成后会跑一个快速检查:
# Innovus中检查放置合法性 check_placement如果MBFF翻转方向不对、row对齐错误,这个命令会直接报出来。这里补充一个经验:不要等到CTS阶段再检查placement,那时候改起来成本高得多。
4. CTS与布线阶段容易被忽视的坑
很多文章讲MBFF都到place就结束了,但实际上CTS和布线阶段才是问题集中爆发的时候。MBFF对时钟树的影响,被严重低估了。
4.1 时钟树上MBFF的“共享时钟pin”
MBFF的时钟端在物理上是共享的,一个MBFF单元内部所有bit共用一个时钟pin、一条内部时钟网络。这对CTS来说有好处也有坏处。
好处是:时钟树上的负载点数量大大减少。一个4bit MBFF只贡献1个clock pin load,而4个单bit触发器贡献4个。这样一来,时钟树的级数可以减少,latency和skew都有改善。
坏处是:MBFF内部的共享时钟网络会把“时钟到达时间差异”进一步放大。因为MBFF内部从时钟pin到每个bit的时钟端距离不同,会导致同一个MBFF内部不同bit的clock latency有微小差异。虽然这个差异通常在皮秒级别,但在高速设计中,它直接影响hold timing的分析精度。
我在一个7nm项目里就因为这个内部skew吃过亏。一个16bit的MBFF,内部从时钟pin到最远bit的距离,比到最近bit的距离多了将近12ps。在时钟频率1.2GHz的设计里,12ps的偏差足以让一组本来看起来没问题的hold路径出现数百条违例。
解决办法是,在CTS阶段对MBFF的使用情况做特殊处理,不要让工具把MBFF当成普通单元自动优化。我通常会先手动约束MBFF的时钟树延迟,再运行CCopt或CTS工具:
# CCopt中为MBFF时钟pin设置目标延迟 set_ccopt_property -pin_filter [get_pins *multibit*/CK] target_skew 20ps ccopt_design这样可以让工具针对MBFF的时钟端做额外的skew优化,降低内部偏差。
4.2 hold fix与incremental的边界问题
MBFF在hold修复阶段有个很麻烦的特性:如果原设计里两个bit之间的路径存在hold问题,合并成MBFF后,这两个bit的物理距离从“可能很远”变成了“非常近”,这对setup修复是好消息,因为路径变短了;但对hold修复来说反而是坏消息,因为时钟到达时间差异可能变大,hold余量变得更紧。
更麻烦的是,如果MBFF内部的某个bit需要插入hold buffer,这个buffer必须放在MBFF单元外部,因为MBFF内部是无法插入任何单元的。这会导致本来很简单的一次hold修复,变成在MBFF附近找地方插buffer,如果附近空间紧张,还得先移动其他cell腾位置。
我遇到过一次这样的场景:一个4bit MBFF的bit0和bit1之间的hold违例有150ps,需要在bit0的数据路径上插入一个delay buffer。原本以为很简单,结果发现MBFF周围全是高密度的标准单元,连一个能放buffer的空位都没有。最后只能通过挪动周边几个不相关的cell才把buffer插进去,前后花了两个小时。
这类问题的最佳解决思路是前置防御:在综合阶段就把MBFF的比例控制在一个合理范围内,不要在数据路径敏感的场景里大量使用高位宽MBFF。
4.3 布线后的EM/IR风险
MBFF还有一个不容易察觉的风险:IR drop。因为多个bit共享电源和地,一个MBFF在工作时,如果多个bit同时翻转,瞬间电流会很大,在这个单元的电源网络上造成明显的电压降。
我在一个项目里跑IR drop分析时发现,某个4bit MBFF在极端工作条件下的IR drop超过了5%的阈值,被redhawk标红。原因就是那个MBFF的4个bit在同一个时钟沿全部翻转,瞬间抽走了大量电流,而周边的电源网格密度不足以支撑这么大的瞬态电流。
处理方案有两种:一种是在floorplan阶段提前在MBFF密集区域加强电源网格密度,增加额外的M4或M5电源走线;另一种是在布局阶段把这些高翻转率bit的MBFF分散到不同区域,避免多个高功耗MBFF挤在同一片电源网格下。
我个人更推荐第二种做法,因为电源网格的修改会影响整个芯片的供电规划,牵涉面广,而MBFF摆放位置的调整相对容易。前提是要在前端提供的数据里拿到每个寄存器的翻转率信息,提前把高翻转率bit标记出来。
5. 实测收益:一组可以当参考的数据
说了这么多理论,到底实际效果如何?我把自己最近几个项目的MBFF数据整理了一下,分享给大家做参考。
5.1 面积、功耗、时序的对比
| 指标 | 不使用MBFF | 使用30% MBFF | 使用60% MBFF |
|---|---|---|---|
| 面积 | 100% | 91% | 86% |
| 总功耗 | 100% | 92% | 85% |
| 时钟树级数 | 12 | 9 | 7 |
| CTS后时钟skew | 65ps | 48ps | 39ps |
| 布线后congestion | 中等 | 中等 | 偏高 |
| 总布线长度 | 100% | 95% | 93% |
这组数据来自一颗28nm Cortex-M级别芯片,工作频率800MHz,逻辑规模约200万门。可以看到,面积和功耗的下降是明确的,时钟树质量也有改善,但congestion在60% MBFF比例时已经开始出现压力。
5.2 数据背后的逻辑:为什么绕线资源节省更多
仔细观察表格会发现,面积节省只有14%,但总布线长度节省了7%。这个现象很有意思。
原因是MBFF不仅合并了寄存器件本身,还合并了连接到这些寄存器的局部网络。比如4个bit如果共用同一个时钟门控,那这个时钟门控就只需要驱动1个MBFF的时钟pin,而不是4个单bit触发器的时钟pin。时钟树上的走线少了一大截,总布线长度自然下降了。
另外,数据通路上也有类似效果。如果一个数据位宽的寄存器原本要从ALU输出一路接到寄存器输入,合并成MBFF后,这些数据线会归拢到同一个MBFF周边,绕线长度更集中,布线资源利用效率更高。
5.3 和FPGA布局布线的区别
这部分也回应一个经常被问到的问题:“FPGA里不是本来就有很多触发器成组排列吗?为什么搞ASIC还要专门讲MBFF?”
FPGA里的触发器布局确实天生是“成组”的,一个CLB(可配置逻辑块)内部包含多个FF,这些FF共享时钟资源,物理位置紧邻。但FPGA的FF数量是固定的,不存在“合并”的概念——反正每个LUT里已经预置了固定数量的FF,用不用都占着资源。
而ASIC的MBFF是库层面的物理优化,需要综合工具在逻辑和物理之间做全局权衡。它更灵活,可以根据设计需求选择不同的bit宽度,但也把物理约束问题提前带到了综合阶段,增加了全流程的复杂度。这也是为什么MBFF优化不能只看综合报告,必须从综合一路盯到布局布线,全流程联动。
6. 我踩过的坑和一套可复用的实战清单
文章最后分享一个我在MBFF项目里印象最深的踩坑经历,以及我现在每次做MBFF项目都会检查一遍的实战清单。
6.1 一个让我通宵排查的时钟问题
那是一个16nm的项目,芯片已经跑通了CTS,时序报告看起来一切正常,但前仿真验证时发现部分寄存器数据在指定时钟沿之后出现了异常翻转。花了整整一个晚上排查,最后才发现问题出在一个8bit MBFF上——这个MBFF的时钟pin到内部第7个bit的时钟端距离太远,导致该bit的时钟延迟比其它bit大了将近30ps。在做时钟树综合时,工具默认认为MBFF是一个“单个负载”,没有对内部bit的延迟差异做精细修正,最终导致了这个bit的建立时间不足。
后来我养成了一个习惯:CTS之后,专门抽查MBFF的内部时钟延迟,确认工具报告的CK pin到内部各bit的延迟差异是否在可接受范围内。如果差异过大,说明这个MBFF要么需要降位宽,要么需要手工调整时钟树约束。
6.2 每次做MBFF项目都会过的检查清单
下面这个清单是我做完几个MBFF项目后总结出来的,权当一份“抄作业”材料:
- RTL阶段确认:确认待合并的寄存器组是否满足逻辑兼容性,数据路径无互依赖,使能条件一致。
- 综合阶段确认:检查库文件中MBFF单元的
dont_use设置,确认允许使用的MBFF位宽范围;跑完综合后,用report_cell_usage确认MBFF的实际使用比例。 - 形式验证确认:合并后的网表和原始RTL跑formal verify,确保逻辑等价。
- floorplan阶段确认:对MBFF密集区域设置halo,分散布局,避免大块MBFF堆叠形成布线空白区。
- place阶段确认:关注Density与congestion的平衡,必要时降低Density目标;place后跑
check_placement确认MBFF的row对齐和翻转方向。 - CTS阶段确认:检查MBFF内部时钟pin到各bit的时钟延迟差异,确认在可接受范围内。
- hold fix阶段确认:如果需要在MBFF外部插入hold buffer,提前检查周边空间是否足够,避免边插边搬cell。
- IR drop确认:对高翻转率bit聚集的区域做IR drop分析,有问题及时调整摆放。
- 布线完成后确认:跑DRC和LVS,特别关注MBFF单元的电源地连接和DRC检查,因为高位宽MBFF的物理尺寸异常,容易在某些角落产生间距违例。
6.3 关于工具版本和库版本的配合
最后提醒一点:MBFF的使用效果和工具版本、库版本高度相关。同一个设计,用DC 2016版和DC 2020版综合出来的MBFF比例可能差很多,因为新版本工具的合并算法更激进,对congestion的感知也更强。
同样的,不同工艺节点下的MBFF单元设计差异也很大。28nm时代的MBFF单元相对简单,内部bit数量通常只有2到4个;到了7nm、5nm,出现了一些8bit甚至16bit的MBFF单元,内部的共享时钟网络设计更复杂,但也带来了更大的物理风险。
所以,拿到一个新的工艺库,不要急着直接上高位宽MBFF,先做一个小规模测试模块,分别用不用MBFF、用不同的MBFF位宽策略跑一遍,看看面积、功耗、时序、congestion四类指标的变化趋势,再决定正式设计里怎么定MBFF策略。这个“先试水再铺开”的思路,帮我避免了好几次大改方案。