第四章:扫描链综合与实现
扫描链的综合与实现,说白了就是把我们设计好的 DFT 逻辑真正“焊”进芯片里。这一步要是出问题,后面测试覆盖率再高也白搭。我这些年见过太多项目,前仿真跑得飞起,一到综合就崩盘——嗯,今天咱们就把这块硬骨头啃下来。
4.1 DC/Genus 中扫描链插入命令
先说说工具。Synopsys 的 DC 和 Cadence 的 Genus,这两家是主流。我个人习惯用 DC 多一些,但 Genus 这几年也追得很紧。命令上大同小异,核心思路是一样的。
在 DC 里,扫描链插入主要靠 insert_dft 这条命令。但别急着敲回车——你得先配好环境。
# 设置扫描模式
set_scan_configuration -style multiplexed_flip_flop
set_scan_configuration -chain_count 4
set_scan_configuration -clock_mixing no_mix
# 定义扫描端口
set_scan_scan_in "scan_in_0 scan_in_1 scan_in_2 scan_in_3"
set_scan_scan_out "scan_out_0 scan_out_1 scan_out_2 scan_out_3"
# 插入扫描链
insert_dft
这里有个坑:-clock_mixing 参数。我曾经在一个多时钟域的项目里,图省事设成了 mix,结果后仿真时序一塌糊涂。不同时钟域的触发器混在一条链上,setup 和 hold 根本修不过来。所以我的建议是——除非你特别清楚自己在做什么,否则老老实实用 no_mix。
Genus 这边,命令风格稍微不同:
# 创建扫描配置
set_db dft_scan_style "muxed_scan"
set_db dft_scan_chain_count 4
# 指定扫描端口
set_db dft_scan_in_port "scan_in_0 scan_in_1 scan_in_2 scan_in_3"
set_db dft_scan_out_port "scan_out_0 scan_out_1 scan_out_2 scan_out_3"
# 执行插入
build_scan_chain
check_dft_rules(DC)或 check_dft(Genus)。这步能帮你提前发现 80% 的问题,比如未约束的时钟、异步复位没处理好等等。
4.2 扫描链的 Stitching(缝合)
Stitching,中文叫“缝合”,听着挺暴力,其实就是把一个个扫描触发器串起来。你想想看,每个触发器都有一个 SI(扫描输入)和 SO(扫描输出),stitching 就是把前一个的 SO 连到后一个的 SI。
但这里有个讲究:缝合顺序。工具默认是按物理位置就近缝合,这样线最短、面积最小。但有时候你得手动干预——比如某些关键路径上的触发器,我希望它们离得近一点,减少绕线延迟。
我记得有个项目,芯片面积特别紧张,后端反馈说扫描链绕线把标准单元都挤变形了。后来我用了 set_scan_chain_stitching 命令,指定了缝合顺序,让工具按逻辑层次来串,问题就解决了。
# 手动指定缝合顺序
set_scan_chain_stitching -chain 0 -elements { \
u_core/u_alu/reg_A_reg \
u_core/u_alu/reg_B_reg \
u_core/u_alu/reg_C_reg \
u_core/u_ctrl/state_reg \
}
嗯,这里要注意:手动缝合虽然灵活,但千万别把不同时钟域的触发器串在一起。除非你做了跨时钟域同步处理,否则测试时很容易出亚稳态。
4.3 扫描链的时序约束
扫描链的时序约束,和功能模式下的约束完全不同。功能模式看的是数据路径,扫描模式看的是扫描时钟到 SI/SO 的路径。
说白了,你要告诉工具:在测试模式下,时钟怎么走,数据怎么传。我一般会单独创建一个 scan.sdc 文件:
# 扫描模式时钟约束
create_clock -name scan_clk -period 100 [get_ports scan_clk]
# 扫描输入延迟
set_input_delay -clock scan_clk -max 10 [get_ports scan_in_*]
set_input_delay -clock scan_clk -min 2 [get_ports scan_in_*]
# 扫描输出延迟
set_output_delay -clock scan_clk -max 12 [get_ports scan_out_*]
set_output_delay -clock scan_clk -min 3 [get_ports scan_out_*]
# 禁用功能路径
set_false_path -from [get_ports scan_in_*] -to [get_pins -hierarchical *data_in*]
还有一个容易忽略的点:扫描使能信号(scan_enable)的时序。这个信号要在扫描时钟沿之前稳定下来,否则触发器可能误判是功能模式还是扫描模式。我习惯给 scan_enable 加一个 set_clock_gating_check,确保它不会在时钟沿附近跳变。
4.4 扫描链的验证
验证这一步,很多人觉得跑个 ATPG 就行了。其实远远不够。我总结了三步验证法:
- 结构检查:用工具检查扫描链是否完整,有没有断链、短路。
- 功能仿真:在测试模式下,灌入扫描向量,看能不能正确移位和捕获。
- 时序签核:在扫描模式下做 STA,确保所有路径都满足时序。
结构检查最简单,一条命令搞定:
# DC 中检查扫描链
report_scan_chain -verbose
# 或者用 TetraMAX 做结构测试
run_drc
run_build_model
run_atpg -auto
功能仿真这块,我踩过一个大坑。有一次结构检查全通过,ATPG 也生成了向量,但仿真就是跑不过。查了两天才发现——有个触发器的复位端在扫描模式下被拉低了,导致所有扫描数据都被清零。后来我在仿真脚本里加了 force 语句,把复位信号强制拉高,问题才解决。
所以我的建议是:仿真时一定要检查所有控制信号的状态。复位、置位、时钟门控、三态使能——这些在扫描模式下都得是“安全”值。
时序签核就更不用说了。扫描链的路径通常很长,几十万甚至上百万个触发器串在一起,延迟累积很可观。我一般会在 STA 脚本里单独检查扫描路径:
# 检查扫描路径时序
report_timing -from [get_ports scan_in_0] -to [get_pins u_core/*/SO] -max_paths 10
report_scan_chain,确认链长和预期一致。
知识体系总览
下面这张图,是我自己总结的扫描链综合与实现的核心流程。你可以把它当作一个检查清单:
这张图把整个流程串起来了。你从准备阶段开始,一步步往下走,每一步都有对应的命令和检查点。我个人习惯在每个阶段结束时,都跑一遍 check_dft 或 report_scan_chain,确保没有遗漏。
好了,扫描链综合与实现这块,核心内容就这些。记住一句话:前期准备越充分,后期验证越轻松。别像我当年那样,等到 tapeout 前才发现问题——那滋味,真不好受。
公众号:蓝海资料掘金营,微信deep3321