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 多万个标准单元。手动写故障列表?想都别想。所以我写了一个自动化脚本,流程大概是这样的:

  1. 解析网表:读入 Verilog/VHDL 网表,提取所有模块、端口、连线、实例
  2. 遍历节点:对每个节点,判断它是否是可测的故障点(排除电源、地、冗余逻辑)
  3. 生成故障:对每个可测节点,生成 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 本章知识体系:一张图看懂

说了这么多,我画了一张流程图,把故障列表生成的整个逻辑串起来。你一看就明白了:

故障列表生成核心流程 Verilog/VHDL 网表 解析网表结构 提取所有可测节点 等价故障压缩 生成 SA0/SA1 故障 标准故障列表文件 图:故障列表生成流程(从网表到标准故障列表)

这张图把整个流程串起来了。你从网表出发,经过解析、提取、压缩、生成,最后得到一份标准故障列表。每一步都有对应的工具或脚本支持。

4.8 小结

故障列表生成,说白了就是把网表里的“可测点”翻译成故障清单。这件事看起来简单,但要做好,需要你对网表结构、故障模型、等价关系都有深入理解。

我个人建议,刚开始做的时候,先用小网表练手,把流程跑通。等熟练了,再上大网表。另外,一定要做等价故障压缩,不然你的仿真时间会哭的。

好,这一章就聊到这里。下一章我们聊聊怎么用这些故障列表去跑仿真,以及如何分析仿真结果。到时候见。


专注资料整理