4、故障列表生成:如何从网表中提取并生成标准故障列表(Fault List)
好,咱们接着聊故障仿真。前面几章我们把故障模型和仿真器搭建聊得差不多了,但有个关键问题一直悬着——故障从哪来?
你想想看,一个芯片几百万个门,总不能让我一个一个手动去敲故障吧?那得敲到猴年马月去。所以,这一章我们就来聊聊故障列表的自动生成。说白了,就是怎么让工具替我们从网表里把该测的故障点全找出来,然后整理成一份标准格式的清单。
核心观点:故障列表是故障仿真的“弹药库”。弹药库建得好不好,直接决定了你的仿真覆盖率能冲到多高。
4.1 网表里到底藏着哪些故障点?
我个人习惯,拿到一个网表后,第一件事不是急着跑仿真,而是先“扫描”一遍网表结构。网表里能出故障的地方,其实就那么几类:
- 输入/输出端口:每个端口都可能 stuck-at 0 或 stuck-at 1
- 内部节点:组合逻辑的输出节点、寄存器的输出端
- 分支点:一个信号扇出到多个门,每个分支都是独立的故障点
- 电源/地:VDD 和 VSS 的短路故障(虽然不常见,但我在项目中遇到过)
嗯,这里要注意:不是所有节点都需要测。比如一些冗余逻辑、测试模式下才使能的路径,可以酌情跳过。否则故障列表会膨胀到无法接受。
4.2 故障列表的标准格式长什么样?
行业内最通用的格式是 STIL (Standard Test Interface Language) 的故障列表子集,或者更简单的 Fault List File。我给大家看一个我常用的格式:
// 故障列表文件示例:fault_list.txt
// 格式:节点名 故障类型 故障值
// 故障类型:SA0 (Stuck-At 0), SA1 (Stuck-At 1)
U1/A SA0
U1/A SA1
U2/Z SA0
U2/Z SA1
U3/Q SA0
U3/Q SA1
// 内部节点
U4/N1 SA0
U4/N1 SA1
// 分支点
U5/Z -> U6/A SA0
U5/Z -> U6/A SA1
U5/Z -> U7/B SA0
U5/Z -> U7/B SA1
你看,这个格式很简单。但实际项目中,我们通常会用 IEEE 1450.1 标准格式,它支持更复杂的故障类型和层次化结构。不过原理都一样——节点名 + 故障类型 + 故障值。
4.3 自动提取的流程:三步走
我曾经在做一个 28nm 的 SoC 项目时,网表有 500 多万个标准单元。手动写故障列表?想都别想。所以我写了一个自动化脚本,流程大概是这样的:
- 解析网表:读入 Verilog/VHDL 网表,提取所有模块、端口、连线、实例
- 遍历节点:对每个节点,判断它是否是可测的故障点(排除电源、地、冗余逻辑)
- 生成故障:对每个可测节点,生成 SA0 和 SA1 两个故障,写入文件
这里有个坑——扇出分支的处理。如果一个信号驱动了多个门,每个分支的故障是独立的。比如 U5/Z 驱动了 U6/A 和 U7/B,那么 U5/Z 到 U6/A 的路径上 stuck-at 0,和 U5/Z 到 U7/B 的路径上 stuck-at 0,是两个不同的故障。这个细节很容易漏掉,我刚开始做的时候就吃过这个亏。
4.4 代码示例:一个简单的故障列表生成器
下面是我写的一个 Python 脚本片段,专门用来从 Verilog 网表里提取故障列表。代码不长,但核心逻辑都在里面了:
import re
def parse_verilog_netlist(file_path):
"""解析 Verilog 网表,提取所有节点和连接"""
nodes = []
with open(file_path, 'r') as f:
content = f.read()
# 提取所有 wire 和 reg 声明
wires = re.findall(r'wire\s+(\w+);', content)
regs = re.findall(r'reg\s+(\w+);', content)
# 提取所有实例的输入输出
instances = re.findall(r'(\w+)\s+(\w+)\s*\(', content)
return wires + regs + [inst[1] for inst in instances]
def generate_fault_list(nodes, output_file):
"""为每个节点生成 SA0 和 SA1 故障"""
with open(output_file, 'w') as f:
f.write("// 自动生成的故障列表\n")
f.write("// 格式:节点名 故障类型\n\n")
for node in nodes:
if node.startswith('VDD') or node.startswith('VSS'):
continue # 跳过电源和地
f.write(f"{node} SA0\n")
f.write(f"{node} SA1\n")
print(f"故障列表已生成:{output_file}")
# 使用示例
nodes = parse_verilog_netlist("design.v")
generate_fault_list(nodes, "fault_list.txt")
小提示:实际项目中,网表里会有很多层次化引用(比如 top/u1/u2/z)。我建议在生成故障列表时,保留完整的层次路径,这样在定位故障时能快速找到物理位置。
4.5 故障列表的优化:别让列表太“胖”
你想想看,一个 100 万门的芯片,每个门平均 3-5 个节点,每个节点 2 个故障,那就是 600 万到 1000 万个故障。全跑一遍仿真?时间成本扛不住。
所以,我们需要对故障列表做等价故障压缩。什么意思呢?就是有些故障是等价的,测一个就能覆盖另一个。比如:
| 原始故障 | 等价故障 | 说明 |
|---|---|---|
| 与非门输入 A SA0 | 与非门输出 SA1 | 因为 A=0 时,输出固定为 1 |
| 或非门输入 B SA1 | 或非门输出 SA0 | 因为 B=1 时,输出固定为 0 |
| 反相器输入 SA0 | 反相器输出 SA1 | 反相器天然反转 |
我在项目中做过统计,经过等价故障压缩后,故障列表通常能减少 40%-60%。这可不是小数目,直接让你的仿真时间缩短一半。
4.6 避坑指南:我踩过的三个坑
做故障列表生成这么多年,我踩过的坑不少。挑三个最典型的跟大家说说:
坑一:忘记处理三态门
我曾经在一个项目中,网表里有很多三态门(tristate buffer)。三态门的输出除了 0 和 1,还有高阻态 Z。如果只生成 SA0 和 SA1,那高阻态的故障就漏掉了。后来我专门加了一段逻辑,对三态门的输出额外生成 SAZ(Stuck-At Z)故障。
坑二:忽略时钟和复位信号
时钟和复位信号通常是全局的,扇出非常大。如果对每个时钟分支都生成故障,列表会爆炸。我的做法是:对时钟和复位信号,只保留主干上的故障,分支上的故障通过等价关系压缩掉。
坑三:网表版本不一致
有一次我拿着前一个版本的网表生成了故障列表,结果综合后的网表改了节点命名规则,导致故障列表和网表对不上。从那以后,我每次生成故障列表前,都会先跑一个 网表一致性检查,确保节点名能一一对应。
4.7 本章知识体系:一张图看懂
说了这么多,我画了一张流程图,把故障列表生成的整个逻辑串起来。你一看就明白了:
这张图把整个流程串起来了。你从网表出发,经过解析、提取、压缩、生成,最后得到一份标准故障列表。每一步都有对应的工具或脚本支持。
4.8 小结
故障列表生成,说白了就是把网表里的“可测点”翻译成故障清单。这件事看起来简单,但要做好,需要你对网表结构、故障模型、等价关系都有深入理解。
我个人建议,刚开始做的时候,先用小网表练手,把流程跑通。等熟练了,再上大网表。另外,一定要做等价故障压缩,不然你的仿真时间会哭的。
好,这一章就聊到这里。下一章我们聊聊怎么用这些故障列表去跑仿真,以及如何分析仿真结果。到时候见。